@@ -14,12 +14,15 @@ import type {LazyComponent} from 'react/src/ReactLazy';
1414import type {
1515 ModuleReference ,
1616 ModuleMetaData ,
17+ UninitializedModel ,
18+ Response ,
1719} from './ReactFlightClientHostConfig' ;
1820
1921import {
2022 resolveModuleReference ,
2123 preloadModule ,
2224 requireModule ,
25+ parseModel ,
2326} from './ReactFlightClientHostConfig' ;
2427
2528import {
@@ -33,33 +36,48 @@ export type JSONValue =
3336 | null
3437 | boolean
3538 | string
36- | { [ key : string ] : JSONValue }
37- | Array < JSONValue > ;
39+ | { + [ key : string ] : JSONValue }
40+ | $ReadOnlyArray < JSONValue > ;
3841
3942const PENDING = 0 ;
40- const RESOLVED = 1 ;
41- const ERRORED = 2 ;
43+ const RESOLVED_MODEL = 1 ;
44+ const INITIALIZED = 2 ;
45+ const ERRORED = 3 ;
4246
4347type PendingChunk = {
4448 _status : 0 ,
4549 _value : null | Array < ( ) => mixed > ,
50+ _response : Response ,
4651 then ( resolve : ( ) => mixed ) : void ,
4752} ;
48- type ResolvedChunk < T > = {
53+ type ResolvedModelChunk = {
4954 _status : 1 ,
55+ _value : UninitializedModel ,
56+ _response : Response ,
57+ then ( resolve : ( ) => mixed ) : void ,
58+ } ;
59+ type InitializedChunk < T > = {
60+ _status : 2 ,
5061 _value : T ,
62+ _response : Response ,
5163 then ( resolve : ( ) => mixed ) : void ,
5264} ;
5365type ErroredChunk = {
54- _status : 2 ,
66+ _status : 3 ,
5567 _value : Error ,
68+ _response : Response ,
5669 then ( resolve : ( ) => mixed ) : void ,
5770} ;
58- type SomeChunk < T > = PendingChunk | ResolvedChunk < T > | ErroredChunk ;
71+ type SomeChunk < T > =
72+ | PendingChunk
73+ | ResolvedModelChunk
74+ | InitializedChunk < T >
75+ | ErroredChunk ;
5976
60- function Chunk ( status : any , value : any ) {
77+ function Chunk ( status : any , value : any , response : Response ) {
6178 this . _status = status ;
6279 this . _value = value ;
80+ this . _response = response ;
6381}
6482Chunk . prototype . then = function < T > (resolve: () => mixed ) {
6583 const chunk : SomeChunk < T > = this ;
@@ -73,45 +91,40 @@ Chunk.prototype.then = function<T>(resolve: () => mixed) {
7391 }
7492} ;
7593
76- export type Response < T > = {
77- partialRow : string ,
78- rootChunk : SomeChunk < T > ,
79- chunks : Map < number , SomeChunk< any >> ,
80- readRoot ( ) : T ,
94+ export type ResponseBase = {
95+ _chunks : Map < number , SomeChunk< any >> ,
96+ readRoot < T > ( ) : T ,
97+ ...
8198} ;
8299
83- function readRoot < T > (): T {
84- const response : Response < T > = this ;
85- const rootChunk = response . rootChunk ;
86- if ( rootChunk . _status === RESOLVED ) {
87- return rootChunk . _value ;
88- } else if ( rootChunk . _status === PENDING ) {
89- // eslint-disable-next-line no-throw-literal
90- throw ( rootChunk : Wakeable ) ;
91- } else {
92- throw rootChunk . _value ;
100+ export type { Response} ;
101+
102+ function readChunk < T > (chunk: SomeChunk< T > ): T {
103+ switch ( chunk . _status ) {
104+ case INITIALIZED :
105+ return chunk . _value ;
106+ case RESOLVED_MODEL:
107+ return initializeModelChunk ( chunk ) ;
108+ case PENDING :
109+ // eslint-disable-next-line no-throw-literal
110+ throw ( chunk : Wakeable ) ;
111+ default:
112+ throw chunk . _value ;
93113 }
94114}
95115
96- export function createResponse < T > ( ) : Response < T > {
97- const rootChunk : SomeChunk < any > = createPendingChunk ( ) ;
98- const chunks : Map < number , SomeChunk < any > > = new Map ( ) ;
99- chunks . set ( 0 , rootChunk ) ;
100- const response = {
101- partialRow : '' ,
102- rootChunk,
103- chunks : chunks ,
104- readRoot : readRoot ,
105- } ;
106- return response ;
116+ function readRoot < T > (): T {
117+ const response : Response = this ;
118+ const chunk = getChunk ( response , 0 ) ;
119+ return readChunk ( chunk ) ;
107120}
108121
109- function createPendingChunk ( ) : PendingChunk {
110- return new Chunk ( PENDING , null ) ;
122+ function createPendingChunk ( response : Response ) : PendingChunk {
123+ return new Chunk ( PENDING , null , response ) ;
111124}
112125
113- function createErrorChunk ( error : Error ) : ErroredChunk {
114- return new Chunk ( ERRORED , error ) ;
126+ function createErrorChunk ( response : Response , error : Error ) : ErroredChunk {
127+ return new Chunk ( ERRORED , error , response ) ;
115128}
116129
117130function wakeChunk ( listeners : null | Array < ( ) => mixed > ) {
@@ -135,29 +148,40 @@ function triggerErrorOnChunk<T>(chunk: SomeChunk<T>, error: Error): void {
135148 wakeChunk ( listeners ) ;
136149}
137150
138- function createResolvedChunk < T > (value: T): ResolvedChunk< T > {
139- return new Chunk ( RESOLVED , value ) ;
151+ function createResolvedModelChunk (
152+ response : Response ,
153+ value : UninitializedModel ,
154+ ) : ResolvedModelChunk {
155+ return new Chunk ( RESOLVED_MODEL , value , response ) ;
140156}
141157
142- function resolveChunk< T > (chunk: SomeChunk< T > , value: T): void {
158+ function resolveModelChunk < T > (
159+ chunk: SomeChunk< T > ,
160+ value: UninitializedModel,
161+ ): void {
143162 if ( chunk . _status !== PENDING ) {
144163 // We already resolved. We didn't expect to see this.
145164 return ;
146165 }
147166 const listeners = chunk._value;
148- const resolvedChunk: ResolvedChunk < T > = (chunk: any);
149- resolvedChunk._status = RESOLVED ;
167+ const resolvedChunk: ResolvedModelChunk = (chunk: any);
168+ resolvedChunk._status = RESOLVED_MODEL ;
150169 resolvedChunk._value = value;
151170 wakeChunk(listeners);
152171}
153172
173+ function initializeModelChunk < T > (chunk: ResolvedModelChunk): T {
174+ const value : T = parseModel ( chunk . _response , chunk . _value ) ;
175+ const initializedChunk : InitializedChunk < T > = ( chunk : any ) ;
176+ initializedChunk . _status = INITIALIZED ;
177+ initializedChunk . _value = value ;
178+ return value ;
179+ }
180+
154181// Report that any missing chunks in the model is now going to throw this
155182// error upon read. Also notify any pending promises.
156- export function reportGlobalError < T > (
157- response: Response< T > ,
158- error: Error,
159- ): void {
160- response . chunks . forEach ( chunk => {
183+ export function reportGlobalError(response: Response, error: Error): void {
184+ response . _chunks . forEach ( chunk => {
161185 // If this chunk was already resolved or errored, it won't
162186 // trigger an error but if it wasn't then we need to
163187 // because we won't be getting any new data to resolve it.
@@ -171,14 +195,7 @@ function readMaybeChunk<T>(maybeChunk: SomeChunk<T> | T): T {
171195 return maybeChunk ;
172196 }
173197 const chunk: SomeChunk< T > = (maybeChunk: any);
174- if (chunk._status === RESOLVED) {
175- return chunk . _value ;
176- } else if (chunk._status === PENDING) {
177- // eslint-disable-next-line no-throw-literal
178- throw ( chunk : Wakeable ) ;
179- } else {
180- throw chunk . _value ;
181- }
198+ return readChunk(chunk);
182199}
183200
184201function createElement ( type , key , props ) : React$Element < any > {
@@ -226,6 +243,7 @@ type UninitializedBlockPayload<Data> = [
226243 mixed,
227244 ModuleMetaData | SomeChunk< ModuleMetaData > ,
228245 Data | SomeChunk< Data > ,
246+ Response,
229247];
230248
231249function initializeBlock< Props , Data > (
@@ -267,83 +285,102 @@ function createLazyBlock<Props, Data>(
267285 return lazyType ;
268286}
269287
270- export function parseModelFromJSON< T > (
271- response: Response< T > ,
272- targetObj: Object,
273- key: string,
274- value: JSONValue,
275- ): mixed {
276- if ( typeof value === 'string' ) {
277- if ( value [ 0 ] === '$' ) {
278- if ( value === '$' ) {
279- return REACT_ELEMENT_TYPE ;
280- } else if (value[1] === '$' || value[1] === '@') {
281- // This was an escaped string value.
282- return value . substring ( 1 ) ;
283- } else {
284- const id = parseInt ( value . substring ( 1 ) , 16 ) ;
285- const chunks = response . chunks ;
286- let chunk = chunks . get ( id ) ;
287- if ( ! chunk ) {
288- chunk = createPendingChunk ( ) ;
289- chunks . set ( id , chunk ) ;
290- }
288+ function getChunk(response: Response, id: number): SomeChunk< any > {
289+ const chunks = response . _chunks ;
290+ let chunk = chunks . get ( id ) ;
291+ if ( ! chunk ) {
292+ chunk = createPendingChunk ( response ) ;
293+ chunks . set ( id , chunk ) ;
294+ }
295+ return chunk ;
296+ }
297+
298+ export function parseModelString(
299+ response: Response,
300+ parentObject: Object,
301+ value: string,
302+ ): any {
303+ if ( value [ 0 ] === '$' ) {
304+ if ( value === '$' ) {
305+ return REACT_ELEMENT_TYPE ;
306+ } else if (value[1] === '$' || value[1] === '@') {
307+ // This was an escaped string value.
308+ return value . substring ( 1 ) ;
309+ } else {
310+ const id = parseInt ( value . substring ( 1 ) , 16 ) ;
311+ const chunk = getChunk ( response , id ) ;
312+ if ( parentObject [ 0 ] === REACT_BLOCK_TYPE ) {
313+ // Block types know how to deal with lazy values.
291314 return chunk ;
292315 }
293- }
294- if ( value === '@' ) {
295- return REACT_BLOCK_TYPE ;
316+ // For anything else we must Suspend this block if
317+ // we don't yet have the value.
318+ return readChunk ( chunk ) ;
296319 }
297320 }
298- if ( typeof value === 'object ' && value !== null ) {
299- const tuple : [ mixed , mixed , mixed , mixed ] = ( value : any ) ;
300- switch ( tuple [ 0 ] ) {
301- case REACT_ELEMENT_TYPE : {
302- // TODO: Consider having React just directly accept these arrays as elements.
303- // Or even change the ReactElement type to be an array.
304- return createElement ( tuple [ 1 ] , tuple [ 2 ] , tuple [ 3 ] ) ;
305- }
306- case REACT_BLOCK_TYPE: {
307- // TODO: Consider having React just directly accept these arrays as blocks.
308- return createLazyBlock ( ( tuple : any ) ) ;
309- }
310- }
321+ if ( value === '@') {
322+ return REACT_BLOCK_TYPE ;
311323 }
312324 return value;
313325}
314326
315- export function resolveModelChunk < T , M > (
316- response: Response< T > ,
327+ export function parseModelTuple (
328+ response : Response ,
329+ value : { + [ key : string ] : JSONValue } | $ReadOnlyArray< JSONValue > ,
330+ ): any {
331+ const tuple : [ mixed , mixed , mixed , mixed ] = ( value : any ) ;
332+ if ( tuple [ 0 ] === REACT_ELEMENT_TYPE ) {
333+ // TODO: Consider having React just directly accept these arrays as elements.
334+ // Or even change the ReactElement type to be an array.
335+ return createElement ( tuple [ 1 ] , tuple [ 2 ] , tuple [ 3 ] ) ;
336+ } else if (tuple[0] === REACT_BLOCK_TYPE) {
337+ // TODO: Consider having React just directly accept these arrays as blocks.
338+ return createLazyBlock ( ( tuple : any ) ) ;
339+ }
340+ return value;
341+ }
342+
343+ export function createResponse ( ) : ResponseBase {
344+ const chunks : Map < number , SomeChunk < any >> = new Map ( ) ;
345+ const response = {
346+ _chunks : chunks ,
347+ readRoot : readRoot ,
348+ } ;
349+ return response ;
350+ }
351+
352+ export function resolveModel(
353+ response: Response,
317354 id: number,
318- model: M ,
355+ model: UninitializedModel ,
319356): void {
320- const chunks = response . chunks ;
357+ const chunks = response . _chunks ;
321358 const chunk = chunks . get ( id ) ;
322359 if ( ! chunk ) {
323- chunks . set ( id , createResolvedChunk ( model ) ) ;
360+ chunks . set ( id , createResolvedModelChunk ( response , model ) ) ;
324361 } else {
325- resolveChunk ( chunk , model ) ;
362+ resolveModelChunk ( chunk , model ) ;
326363 }
327364}
328365
329- export function resolveErrorChunk < T > (
330- response: Response< T > ,
366+ export function resolveError (
367+ response : Response ,
331368 id : number ,
332369 message : string ,
333370 stack : string ,
334371) : void {
335372 const error = new Error ( message ) ;
336373 error . stack = stack ;
337- const chunks = response . chunks ;
374+ const chunks = response . _chunks ;
338375 const chunk = chunks . get ( id ) ;
339376 if ( ! chunk ) {
340- chunks . set ( id , createErrorChunk ( error ) ) ;
377+ chunks . set ( id , createErrorChunk ( response , error ) ) ;
341378 } else {
342379 triggerErrorOnChunk ( chunk , error ) ;
343380 }
344381}
345382
346- export function close < T > (response: Response< T > ): void {
383+ export function close ( response : Response ) : void {
347384 // In case there are any remaining unresolved chunks, they won't
348385 // be resolved now. So we need to issue an error to those.
349386 // Ideally we should be able to early bail out if we kept a
0 commit comments