18
ClientReference as ServerReference,
19
} from 'react-client/src/ReactFlightClientConfig';
20
21
+import type {TemporaryReferenceSet} from './ReactFlightServerTemporaryReferences';
22
+
23
import {
24
resolveServerReference,
25
preloadModule,
26
requireModule,
27
} from 'react-client/src/ReactFlightClientConfig';
28
27
-import {createTemporaryReference} from './ReactFlightServerTemporaryReferences';
29
+import {
30
+ createTemporaryReference,
31
+ registerTemporaryReference,
32
+} from './ReactFlightServerTemporaryReferences';
33
import {
34
enableBinaryFlight,
35
enableFlightReadableStream,
36
} from 'shared/ReactFeatureFlags';
37
import {ASYNC_ITERATOR} from 'shared/ReactSymbols';
38
39
+import hasOwnProperty from 'shared/hasOwnProperty';
40
+
41
interface FlightStreamController {
42
enqueueModel(json: string): void;
43
close(json: string): void;
83
type ResolvedModelChunk<T> = {
84
status: 'resolved_model',
85
value: string,
79
- reason: null,
86
+ reason: number,
87
_response: Response,
88
then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
89
};
173
_prefix: string,
174
_formData: FormData,
175
_chunks: Map<number, SomeChunk<any>>,
169
- _fromJSON: (key: string, value: JSONValue) => any,
176
+ _temporaryReferences: void | TemporaryReferenceSet,
177
};
178
179
export function getRoot<T>(response: Response): Thenable<T> {
240
function createResolvedModelChunk<T>(
241
response: Response,
242
value: string,
243
+ id: number,
244
): ResolvedModelChunk<T> {
245
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
238
- return new Chunk(RESOLVED_MODEL, value, null, response);
246
+ return new Chunk(RESOLVED_MODEL, value, id, response);
247
}
248
241
-function resolveModelChunk<T>(chunk: SomeChunk<T>, value: string): void {
249
+function resolveModelChunk<T>(
250
+ chunk: SomeChunk<T>,
251
+ value: string,
252
+ id: number,
253
+): void {
254
if (chunk.status !== PENDING) {
255
if (enableFlightReadableStream) {
256
// If we get more data to an already resolved ID, we assume that it's
270
const resolvedChunk: ResolvedModelChunk<T> = (chunk: any);
271
resolvedChunk.status = RESOLVED_MODEL;
272
resolvedChunk.value = value;
273
+ resolvedChunk.reason = id;
274
if (resolveListeners !== null) {
275
// This is unfortunate that we're reading this eagerly if
276
// we already have listeners attached since they might no
303
const iteratorResultJSON =
304
(done ? '{"done":true,"value":' : '{"done":false,"value":') + value + '}';
305
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
293
- return new Chunk(RESOLVED_MODEL, iteratorResultJSON, null, response);
306
+ return new Chunk(RESOLVED_MODEL, iteratorResultJSON, -1, response);
307
}
308
309
function resolveIteratorResultChunk<T>(
314
// To reuse code as much code as possible we add the wrapper element as part of the JSON.
315
const iteratorResultJSON =
316
(done ? '{"done":true,"value":' : '{"done":false,"value":') + value + '}';
304
- resolveModelChunk(chunk, iteratorResultJSON);
317
+ resolveModelChunk(chunk, iteratorResultJSON, -1);
318
}
319
320
function bindArgs(fn: any, args: any) {
366
return (null: any);
367
}
368
369
+function reviveModel(
370
+ response: Response,
371
+ parentObj: any,
372
+ parentKey: string,
373
+ value: JSONValue,
374
+ reference: void | string,
375
+): any {
376
+ if (typeof value === 'string') {
377
+ // We can't use .bind here because we need the "this" value.
378
+ return parseModelString(response, parentObj, parentKey, value, reference);
379
+ }
380
+ if (typeof value === 'object' && value !== null) {
381
+ if (
382
+ reference !== undefined &&
383
+ response._temporaryReferences !== undefined
384
+ ) {
385
+ // Store this object's reference in case it's returned later.
386
+ registerTemporaryReference(
387
+ response._temporaryReferences,
388
+ value,
389
+ reference,
390
+ );
391
+ }
392
+ if (Array.isArray(value)) {
393
+ for (let i = 0; i < value.length; i++) {
394
+ const childRef =
395
+ reference !== undefined ? reference + ':' + i : undefined;
396
+ // $FlowFixMe[cannot-write]
397
+ value[i] = reviveModel(response, value, '' + i, value[i], childRef);
398
+ }
399
+ } else {
400
+ for (const key in value) {
401
+ if (hasOwnProperty.call(value, key)) {
402
+ const childRef =
403
+ reference !== undefined && key.indexOf(':') === -1
404
+ ? reference + ':' + key
405
+ : undefined;
406
+ const newValue = reviveModel(
407
+ response,
408
+ value,
409
+ key,
410
+ value[key],
411
+ childRef,
412
+ );
413
+ if (newValue !== undefined) {
414
+ // $FlowFixMe[cannot-write]
415
+ value[key] = newValue;
416
+ } else {
417
+ // $FlowFixMe[cannot-write]
418
+ delete value[key];
419
+ }
420
+ }
421
+ }
422
+ }
423
+ }
424
+ return value;
425
+}
426
+
427
let initializingChunk: ResolvedModelChunk<any> = (null: any);
428
let initializingChunkBlockedModel: null | {deps: number, value: any} = null;
429
function initializeModelChunk<T>(chunk: ResolvedModelChunk<T>): void {
432
initializingChunk = chunk;
433
initializingChunkBlockedModel = null;
434
435
+ const rootReference =
436
+ chunk.reason === -1 ? undefined : chunk.reason.toString(16);
437
+
438
const resolvedModel = chunk.value;
439
440
// We go to the CYCLIC state until we've fully resolved this.
446
cyclicChunk.reason = null;
447
448
try {
375
- const value: T = JSON.parse(resolvedModel, chunk._response._fromJSON);
449
+ const rawModel = JSON.parse(resolvedModel);
450
+
451
+ const value: T = reviveModel(
452
+ chunk._response,
453
+ {'': rawModel},
454
+ '',
455
+ rawModel,
456
+ rootReference,
457
+ );
458
if (
459
initializingChunkBlockedModel !== null &&
460
initializingChunkBlockedModel.deps > 0
508
const backingEntry = response._formData.get(key);
509
if (backingEntry != null) {
510
// We assume that this is a string entry for now.
429
- chunk = createResolvedModelChunk(response, (backingEntry: any));
511
+ chunk = createResolvedModelChunk(response, (backingEntry: any), id);
512
} else {
513
// We're still waiting on this entry to stream in.
514
chunk = createPendingChunk(response);
725
const chunk: ResolvedModelChunk<T> = createResolvedModelChunk(
726
response,
727
json,
728
+ -1,
729
);
730
initializeModelChunk(chunk);
731
const initializedChunk: SomeChunk<T> = chunk;
753
// to synchronous emitting.
754
previousBlockedChunk = null;
755
}
673
- resolveModelChunk(chunk, json);
756
+ resolveModelChunk(chunk, json, -1);
757
});
758
}
759
},
897
obj: Object,
898
key: string,
899
value: string,
900
+ reference: void | string,
901
): any {
902
if (value[0] === '$') {
903
switch (value[1]) {
928
}
929
case 'T': {
930
// Temporary Reference
847
- return createTemporaryReference(value.slice(2));
931
+ if (
932
+ reference === undefined ||
933
+ response._temporaryReferences === undefined
934
+ ) {
935
+ throw new Error(
936
+ 'Could not reference an opaque temporary reference. ' +
937
+ 'This is likely due to misconfiguring the temporaryReferences options ' +
938
+ 'on the server.',
939
+ );
940
+ }
941
+ return createTemporaryReference(
942
+ response._temporaryReferences,
943
+ reference,
944
+ );
945
}
946
case 'Q': {
947
// Map
1079
export function createResponse(
1080
bundlerConfig: ServerManifest,
1081
formFieldPrefix: string,
1082
+ temporaryReferences: void | TemporaryReferenceSet,
1083
backingFormData?: FormData = new FormData(),
1084
): Response {
1085
const chunks: Map<number, SomeChunk<any>> = new Map();
1088
_prefix: formFieldPrefix,
1089
_formData: backingFormData,
1090
_chunks: chunks,
993
- _fromJSON: function (this: any, key: string, value: JSONValue) {
994
- if (typeof value === 'string') {
995
- // We can't use .bind here because we need the "this" value.
996
- return parseModelString(response, this, key, value);
997
- }
998
- return value;
999
- },
1091
+ _temporaryReferences: temporaryReferences,
1092
};
1093
return response;
1094
}
1107
const chunk = chunks.get(id);
1108
if (chunk) {
1109
// We were waiting on this key so now we can resolve it.
1018
- resolveModelChunk(chunk, value);
1110
+ resolveModelChunk(chunk, value, id);
1111
}
1112
}
1113
}