@@ -47,6 +47,7 @@ export type JSONValue =
4747
4848const PENDING = 'pending' ;
4949const BLOCKED = 'blocked' ;
50+ const CYCLIC = 'cyclic' ;
5051const RESOLVED_MODEL = 'resolved_model' ;
5152const INITIALIZED = 'fulfilled' ;
5253const ERRORED = 'rejected' ;
@@ -65,6 +66,13 @@ type BlockedChunk<T> = {
6566 _response : Response ,
6667 then ( resolve : ( T ) => mixed , reject ?: ( mixed ) => mixed ) : void ,
6768} ;
69+ type CyclicChunk < T > = {
70+ status : 'cyclic' ,
71+ value : null | Array < ( T ) => mixed > ,
72+ reason : null | Array < ( mixed ) => mixed > ,
73+ _response : Response ,
74+ then ( resolve : ( T ) => mixed , reject ?: ( mixed ) => mixed ) : void ,
75+ } ;
6876type ResolvedModelChunk < T > = {
6977 status : 'resolved_model' ,
7078 value : string ,
@@ -98,6 +106,7 @@ type ErroredChunk<T> = {
98106type SomeChunk < T > =
99107 | PendingChunk < T >
100108 | BlockedChunk < T >
109+ | CyclicChunk < T >
101110 | ResolvedModelChunk < T >
102111 | InitializedChunk < T >
103112 | ErroredChunk < T > ;
@@ -132,6 +141,7 @@ Chunk.prototype.then = function <T>(
132141 break ;
133142 case PENDING :
134143 case BLOCKED :
144+ case CYCLIC :
135145 if ( resolve ) {
136146 if ( chunk . value === null ) {
137147 chunk . value = ( [ ] : Array < ( T ) => mixed > ) ;
@@ -187,6 +197,7 @@ function wakeChunkIfInitialized<T>(
187197 break ;
188198 case PENDING :
189199 case BLOCKED :
200+ case CYCLIC :
190201 chunk . value = resolveListeners ;
191202 chunk . reason = rejectListeners ;
192203 break ;
@@ -334,6 +345,7 @@ function loadServerReference<T>(
334345 false,
335346 response,
336347 createModel,
348+ [],
337349 ),
338350 createModelReject(parentChunk),
339351 );
@@ -348,8 +360,19 @@ function initializeModelChunk<T>(chunk: ResolvedModelChunk<T>): void {
348360 const prevBlocked = initializingChunkBlockedModel ;
349361 initializingChunk = chunk ;
350362 initializingChunkBlockedModel = null ;
363+
364+ const resolvedModel = chunk . value ;
365+
366+ // We go to the CYCLIC state until we've fully resolved this.
367+ // We do this before parsing in case we try to initialize the same chunk
368+ // while parsing the model. Such as in a cyclic reference.
369+ const cyclicChunk : CyclicChunk < T > = ( chunk : any ) ;
370+ cyclicChunk . status = CYCLIC ;
371+ cyclicChunk . value = null ;
372+ cyclicChunk . reason = null ;
373+
351374 try {
352- const value : T = JSON . parse ( chunk . value , chunk . _response . _fromJSON ) ;
375+ const value : T = JSON . parse ( resolvedModel , chunk . _response . _fromJSON ) ;
353376 if (
354377 initializingChunkBlockedModel !== null &&
355378 initializingChunkBlockedModel . deps > 0
@@ -362,9 +385,13 @@ function initializeModelChunk<T>(chunk: ResolvedModelChunk<T>): void {
362385 blockedChunk . value = null ;
363386 blockedChunk . reason = null ;
364387 } else {
388+ const resolveListeners = cyclicChunk . value ;
365389 const initializedChunk : InitializedChunk < T > = ( chunk : any ) ;
366390 initializedChunk . status = INITIALIZED ;
367391 initializedChunk . value = value ;
392+ if ( resolveListeners !== null ) {
393+ wakeChunk ( resolveListeners , value ) ;
394+ }
368395 }
369396 } catch ( error ) {
370397 const erroredChunk : ErroredChunk < T > = ( chunk : any ) ;
@@ -416,6 +443,7 @@ function createModelResolver<T>(
416443 cyclic: boolean,
417444 response: Response,
418445 map: (response: Response, model: any) => T ,
446+ path : Array < string > ,
419447): (value: any) => void {
420448 let blocked ;
421449 if ( initializingChunkBlockedModel ) {
@@ -430,6 +458,9 @@ function createModelResolver<T>(
430458 } ;
431459 }
432460 return value => {
461+ for ( let i = 1 ; i < path . length ; i ++ ) {
462+ value = value [ path [ i ] ] ;
463+ }
433464 parentObject[key] = map(response, value);
434465
435466 // If this is the root object for a model reference, where `blocked.value`
@@ -460,11 +491,13 @@ function createModelReject<T>(chunk: SomeChunk<T>): (error: mixed) => void {
460491
461492function getOutlinedModel< T > (
462493 response: Response,
463- id: number ,
494+ reference: string ,
464495 parentObject: Object,
465496 key: string,
466497 map: (response: Response, model: any) => T ,
467498) : T {
499+ const path = reference . split ( ':' ) ;
500+ const id = parseInt ( path [ 0 ] , 16 ) ;
468501 const chunk = getChunk ( response , id ) ;
469502 switch ( chunk . status ) {
470503 case RESOLVED_MODEL :
@@ -474,18 +507,24 @@ function getOutlinedModel<T>(
474507 // The status might have changed after initialization.
475508 switch ( chunk . status ) {
476509 case INITIALIZED :
477- return map ( response , chunk . value ) ;
510+ let value = chunk . value ;
511+ for ( let i = 1 ; i < path . length ; i ++ ) {
512+ value = value [ path [ i ] ] ;
513+ }
514+ return map ( response , value ) ;
478515 case PENDING :
479516 case BLOCKED :
517+ case CYCLIC :
480518 const parentChunk = initializingChunk ;
481519 chunk . then (
482520 createModelResolver (
483521 parentChunk ,
484522 parentObject ,
485523 key ,
486- false ,
524+ chunk . status === CYCLIC ,
487525 response ,
488526 map ,
527+ path ,
489528 ) ,
490529 createModelReject ( parentChunk ) ,
491530 ) ;
@@ -548,6 +587,7 @@ function parseTypedArray(
548587 false ,
549588 response ,
550589 createModel ,
590+ [ ] ,
551591 ) ,
552592 createModelReject ( parentChunk ) ,
553593 ) ;
@@ -789,10 +829,10 @@ function parseModelString(
789829 }
790830 case 'F' : {
791831 // Server Reference
792- const id = parseInt ( value . slice ( 2 ) , 16 ) ;
832+ const ref = value . slice ( 2 ) ;
793833 // TODO: Just encode this in the reference inline instead of as a model.
794834 const metaData : { id : ServerReferenceId , bound : Thenable < Array < any >> } =
795- getOutlinedModel ( response , id , obj , key , createModel ) ;
835+ getOutlinedModel ( response , ref , obj , key , createModel ) ;
796836 return loadServerReference (
797837 response ,
798838 metaData . id ,
@@ -808,13 +848,13 @@ function parseModelString(
808848 }
809849 case 'Q ': {
810850 // Map
811- const id = parseInt ( value . slice ( 2 ) , 16 ) ;
812- return getOutlinedModel ( response , id , obj , key , createMap ) ;
851+ const ref = value . slice ( 2 ) ;
852+ return getOutlinedModel ( response , ref , obj , key , createMap ) ;
813853 }
814854 case 'W ': {
815855 // Set
816- const id = parseInt ( value . slice ( 2 ) , 16 ) ;
817- return getOutlinedModel ( response , id , obj , key , createSet ) ;
856+ const ref = value . slice ( 2 ) ;
857+ return getOutlinedModel ( response , ref , obj , key , createSet ) ;
818858 }
819859 case 'K ': {
820860 // FormData
@@ -835,8 +875,8 @@ function parseModelString(
835875 }
836876 case 'i ': {
837877 // Iterator
838- const id = parseInt ( value . slice ( 2 ) , 16 ) ;
839- return getOutlinedModel ( response , id , obj , key , extractIterator ) ;
878+ const ref = value . slice ( 2 ) ;
879+ return getOutlinedModel ( response , ref , obj , key , extractIterator ) ;
840880 }
841881 case 'I' : {
842882 // $Infinity
@@ -933,8 +973,8 @@ function parseModelString(
933973 }
934974
935975 // We assume that anything else is a reference ID.
936- const id = parseInt ( value . slice ( 1 ) , 16 ) ;
937- return getOutlinedModel ( response , id , obj , key , createModel ) ;
976+ const ref = value . slice ( 1 ) ;
977+ return getOutlinedModel ( response , ref , obj , key , createModel ) ;
938978 }
939979 return value ;
940980}
0 commit comments