main
js 141 lines 3.82 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 import type {Thenable, ReactCustomFormAction} from 'shared/ReactTypes.js';
11
12 import type {
13 DebugChannel,
14 FindSourceMapURLCallback,
15 Response,
16 } from 'react-client/src/ReactFlightClient';
17
18 import type {Readable} from 'stream';
19
20 import {
21 createResponse,
22 createStreamState,
23 getRoot,
24 reportGlobalError,
25 processStringChunk,
26 processBinaryChunk,
27 close,
28 } from 'react-client/src/ReactFlightClient';
29
30 import {createServerReference as createServerReferenceImpl} from 'react-client/src/ReactFlightReplyClient';
31
32 export {registerServerReference} from 'react-client/src/ReactFlightReplyClient';
33
34 function noServerCall() {
35 throw new Error(
36 'Server Functions cannot be called during initial render. ' +
37 'This would create a fetch waterfall. Try to use a Server Component ' +
38 'to pass data to Client Components instead.',
39 );
40 }
41
42 export function createServerReference<A: Iterable<any>, T>(
43 id: any,
44 callServer: any,
45 ): (...A) => Promise<T> {
46 return createServerReferenceImpl(id, noServerCall);
47 }
48
49 type EncodeFormActionCallback = <A>(
50 id: any,
51 args: Promise<A>,
52 ) => ReactCustomFormAction;
53
54 export type Options = {
55 nonce?: string,
56 encodeFormAction?: EncodeFormActionCallback,
57 unstable_allowPartialStream?: boolean,
58 findSourceMapURL?: FindSourceMapURLCallback,
59 replayConsoleLogs?: boolean,
60 environmentName?: string,
61 startTime?: number,
62 endTime?: number,
63 // For the Node.js client we only support a single-direction debug channel.
64 debugChannel?: Readable,
65 };
66
67 function startReadingFromStream(
68 response: Response,
69 stream: Readable,
70 onEnd: () => void,
71 ): void {
72 const streamState = createStreamState(response, stream);
73
74 stream.on('data', chunk => {
75 if (typeof chunk === 'string') {
76 processStringChunk(response, streamState, chunk);
77 } else {
78 processBinaryChunk(response, streamState, chunk);
79 }
80 });
81
82 stream.on('error', error => {
83 reportGlobalError(response, error);
84 });
85
86 stream.on('end', onEnd);
87 }
88
89 function createFromNodeStream<T>(
90 stream: Readable,
91 moduleRootPath: string,
92 moduleBaseURL: string,
93 options?: Options,
94 ): Thenable<T> {
95 const debugChannel: void | DebugChannel =
96 __DEV__ && options && options.debugChannel !== undefined
97 ? {hasReadable: true, callback: null}
98 : undefined;
99
100 const response: Response = createResponse(
101 moduleRootPath,
102 null,
103 moduleBaseURL,
104 noServerCall,
105 options ? options.encodeFormAction : undefined,
106 options && typeof options.nonce === 'string' ? options.nonce : undefined,
107 undefined, // TODO: If encodeReply is supported, this should support temporaryReferences
108 options && options.unstable_allowPartialStream
109 ? options.unstable_allowPartialStream
110 : false,
111 __DEV__ && options && options.findSourceMapURL
112 ? options.findSourceMapURL
113 : undefined,
114 __DEV__ && options ? options.replayConsoleLogs === true : false, // defaults to false
115 __DEV__ && options && options.environmentName
116 ? options.environmentName
117 : undefined,
118 __DEV__ && options && options.startTime != null
119 ? options.startTime
120 : undefined,
121 __DEV__ && options && options.endTime != null ? options.endTime : undefined,
122 debugChannel,
123 );
124
125 if (__DEV__ && options && options.debugChannel) {
126 let streamEndedCount = 0;
127 const handleEnd = () => {
128 if (++streamEndedCount === 2) {
129 close(response);
130 }
131 };
132 startReadingFromStream(response, options.debugChannel, handleEnd);
133 startReadingFromStream(response, stream, handleEnd);
134 } else {
135 startReadingFromStream(response, stream, close.bind(null, response));
136 }
137
138 return getRoot(response);
139 }
140
141 export {createFromNodeStream};