@@ -21,6 +21,9 @@ import type {
2121} from '../client/ReactFlightClientConfigBundlerParcel' ;
2222
2323import { Readable } from 'stream' ;
24+
25+ import { ASYNC_ITERATOR } from 'shared/ReactSymbols' ;
26+
2427import {
2528 createRequest ,
2629 createPrerenderRequest ,
@@ -35,6 +38,7 @@ import {
3538 reportGlobalError ,
3639 close ,
3740 resolveField ,
41+ resolveFile ,
3842 resolveFileInfo ,
3943 resolveFileChunk ,
4044 resolveFileComplete ,
@@ -56,9 +60,12 @@ export {
5660 registerServerReference ,
5761} from '../ReactFlightParcelReferences' ;
5862
63+ import { textEncoder } from 'react-server/src/ReactServerStreamConfigNode' ;
64+
5965import type { TemporaryReferenceSet } from 'react-server/src/ReactFlightServerTemporaryReferences' ;
6066
6167export { createTemporaryReferenceSet } from 'react-server/src/ReactFlightServerTemporaryReferences' ;
68+
6269export type { TemporaryReferenceSet } ;
6370
6471function createDrainHandler ( destination : Destination , request : Request ) {
@@ -88,6 +95,7 @@ type PipeableStream = {
8895
8996export function renderToPipeableStream (
9097 model : ReactClientValue ,
98+
9199 options ?: Options ,
92100) : PipeableStream {
93101 const request = createRequest (
@@ -131,11 +139,91 @@ export function renderToPipeableStream(
131139 } ;
132140}
133141
134- function createFakeWritable ( readable : any ) : Writable {
142+ function createFakeWritableFromReadableStreamController (
143+ controller : ReadableStreamController ,
144+ ) : Writable {
145+ // The current host config expects a Writable so we create
146+ // a fake writable for now to push into the Readable.
147+ return ( {
148+ write ( chunk : string | Uint8Array ) {
149+ if ( typeof chunk === 'string' ) {
150+ chunk = textEncoder . encode ( chunk ) ;
151+ }
152+ controller . enqueue ( chunk ) ;
153+ // in web streams there is no backpressure so we can alwas write more
154+ return true ;
155+ } ,
156+ end ( ) {
157+ controller . close ( ) ;
158+ } ,
159+ destroy ( error ) {
160+ // $FlowFixMe[method-unbinding]
161+ if ( typeof controller . error === 'function' ) {
162+ // $FlowFixMe[incompatible-call]: This is an Error object or the destination accepts other types.
163+ controller . error ( error ) ;
164+ } else {
165+ controller . close ( ) ;
166+ }
167+ } ,
168+ } : any ) ;
169+ }
170+
171+ export function renderToReadableStream (
172+ model : ReactClientValue ,
173+
174+ options ?: Options & {
175+ signal ?: AbortSignal ,
176+ } ,
177+ ) : ReadableStream {
178+ const request = createRequest (
179+ model ,
180+ null ,
181+ options ? options . onError : undefined ,
182+ options ? options . identifierPrefix : undefined ,
183+ options ? options . onPostpone : undefined ,
184+ options ? options . temporaryReferences : undefined ,
185+ __DEV__ && options ? options . environmentName : undefined ,
186+ __DEV__ && options ? options . filterStackFrame : undefined ,
187+ ) ;
188+ if ( options && options . signal ) {
189+ const signal = options . signal ;
190+ if ( signal . aborted ) {
191+ abort ( request , ( signal : any ) . reason ) ;
192+ } else {
193+ const listener = ( ) => {
194+ abort ( request , ( signal : any ) . reason ) ;
195+ signal . removeEventListener ( 'abort' , listener ) ;
196+ } ;
197+ signal . addEventListener ( 'abort' , listener ) ;
198+ }
199+ }
200+ let writable : Writable ;
201+ const stream = new ReadableStream (
202+ {
203+ type : 'bytes' ,
204+ start : ( controller ) : ?Promise < void > => {
205+ writable = createFakeWritableFromReadableStreamController ( controller ) ;
206+ startWork ( request ) ;
207+ } ,
208+ pull : ( controller ) : ?Promise < void > => {
209+ startFlowing ( request , writable ) ;
210+ } ,
211+ cancel : ( reason ) : ?Promise < void > => {
212+ stopFlowing ( request ) ;
213+ abort ( request , reason ) ;
214+ } ,
215+ } ,
216+ // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams.
217+ { highWaterMark : 0 } ,
218+ ) ;
219+ return stream ;
220+ }
221+
222+ function createFakeWritableFromNodeReadable ( readable : any ) : Writable {
135223 // The current host config expects a Writable so we create
136224 // a fake writable for now to push into the Readable.
137225 return ( {
138- write ( chunk ) {
226+ write ( chunk : string | Uint8Array ) {
139227 return readable . push ( chunk ) ;
140228 } ,
141229 end ( ) {
@@ -163,6 +251,7 @@ type StaticResult = {
163251
164252export function prerenderToNodeStream (
165253 model : ReactClientValue ,
254+
166255 options ?: PrerenderOptions ,
167256) : Promise < StaticResult > {
168257 return new Promise ( ( resolve , reject ) => {
@@ -173,7 +262,7 @@ export function prerenderToNodeStream(
173262 startFlowing ( request , writable ) ;
174263 } ,
175264 } ) ;
176- const writable = createFakeWritable ( readable ) ;
265+ const writable = createFakeWritableFromNodeReadable ( readable ) ;
177266 resolve ( { prelude : readable } ) ;
178267 }
179268
@@ -207,6 +296,69 @@ export function prerenderToNodeStream(
207296 } ) ;
208297}
209298
299+ export function prerender (
300+ model : ReactClientValue ,
301+
302+ options ?: Options & {
303+ signal ?: AbortSignal ,
304+ } ,
305+ ) : Promise < {
306+ prelude : ReadableStream ,
307+ } > {
308+ return new Promise ( ( resolve , reject ) => {
309+ const onFatalError = reject ;
310+ function onAllReady ( ) {
311+ let writable : Writable ;
312+ const stream = new ReadableStream (
313+ {
314+ type : 'bytes' ,
315+ start : ( controller ) : ?Promise < void > => {
316+ writable =
317+ createFakeWritableFromReadableStreamController ( controller ) ;
318+ } ,
319+ pull : ( controller ) : ?Promise < void > => {
320+ startFlowing ( request , writable ) ;
321+ } ,
322+ cancel : ( reason ) : ?Promise < void > => {
323+ stopFlowing ( request ) ;
324+ abort ( request , reason ) ;
325+ } ,
326+ } ,
327+ // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams.
328+ { highWaterMark : 0 } ,
329+ ) ;
330+ resolve ( { prelude : stream } ) ;
331+ }
332+ const request = createPrerenderRequest (
333+ model ,
334+ null ,
335+ onAllReady ,
336+ onFatalError ,
337+ options ? options . onError : undefined ,
338+ options ? options . identifierPrefix : undefined ,
339+ options ? options . onPostpone : undefined ,
340+ options ? options . temporaryReferences : undefined ,
341+ __DEV__ && options ? options . environmentName : undefined ,
342+ __DEV__ && options ? options . filterStackFrame : undefined ,
343+ ) ;
344+ if ( options && options . signal ) {
345+ const signal = options . signal ;
346+ if ( signal . aborted ) {
347+ const reason = ( signal : any ) . reason ;
348+ abort ( request , reason ) ;
349+ } else {
350+ const listener = ( ) => {
351+ const reason = ( signal : any ) . reason ;
352+ abort ( request , reason ) ;
353+ signal . removeEventListener ( 'abort' , listener ) ;
354+ } ;
355+ signal . addEventListener ( 'abort' , listener ) ;
356+ }
357+ }
358+ startWork ( request ) ;
359+ } ) ;
360+ }
361+
210362let serverManifest = { } ;
211363export function registerServerActions ( manifest : ServerManifest ) {
212364 // This function is called by the bundler to register the manifest.
@@ -292,6 +444,50 @@ export function decodeReply<T>(
292444 return root ;
293445}
294446
447+ export function decodeReplyFromAsyncIterable < T > (
448+ iterable : AsyncIterable < [ string , string | File ] > ,
449+ options ?: { temporaryReferences ?: TemporaryReferenceSet } ,
450+ ) : Thenable < T > {
451+ const iterator : AsyncIterator < [ string , string | File ] > =
452+ iterable [ ASYNC_ITERATOR ] ( ) ;
453+
454+ const response = createResponse (
455+ serverManifest ,
456+ '' ,
457+ options ? options . temporaryReferences : undefined ,
458+ ) ;
459+
460+ function progress (
461+ entry :
462+ | { done : false , + value : [ string , string | File ] , ...}
463+ | { done : true , + value : void , ...} ,
464+ ) {
465+ if ( entry . done ) {
466+ close ( response ) ;
467+ } else {
468+ const [ name , value ] = entry . value ;
469+ if ( typeof value === 'string' ) {
470+ resolveField ( response , name , value ) ;
471+ } else {
472+ resolveFile ( response , name , value ) ;
473+ }
474+ iterator . next ( ) . then ( progress , error ) ;
475+ }
476+ }
477+ function error ( reason : Error ) {
478+ reportGlobalError ( response , reason ) ;
479+ if ( typeof ( iterator : any ) . throw === 'function' ) {
480+ // The iterator protocol doesn't necessarily include this but a generator do.
481+ // $FlowFixMe should be able to pass mixed
482+ iterator . throw ( reason ) . then ( error , error ) ;
483+ }
484+ }
485+
486+ iterator . next ( ) . then ( progress , error ) ;
487+
488+ return getRoot ( response ) ;
489+ }
490+
295491export function decodeAction < T > ( body : FormData ) : Promise < ( ) => T > | null {
296492 return decodeActionImpl ( body , serverManifest ) ;
297493}
0 commit comments