14
import type {
15
ModuleReference,
16
ModuleMetaData,
17
+ UninitializedModel,
18
+ Response,
19
} from './ReactFlightClientHostConfig';
20
21
import {
22
resolveModuleReference,
23
preloadModule,
24
requireModule,
25
+ parseModel,
26
} from './ReactFlightClientHostConfig';
27
28
import {
36
| null
37
| boolean
38
| string
36
- | {[key: string]: JSONValue}
37
- | Array<JSONValue>;
39
+ | {+[key: string]: JSONValue}
40
+ | $ReadOnlyArray<JSONValue>;
41
42
const PENDING = 0;
40
-const RESOLVED = 1;
41
-const ERRORED = 2;
43
+const RESOLVED_MODEL = 1;
44
+const INITIALIZED = 2;
45
+const ERRORED = 3;
46
47
type PendingChunk = {
48
_status: 0,
49
_value: null | Array<() => mixed>,
50
+ _response: Response,
51
then(resolve: () => mixed): void,
52
};
48
-type ResolvedChunk<T> = {
53
+type ResolvedModelChunk = {
54
_status: 1,
55
+ _value: UninitializedModel,
56
+ _response: Response,
57
+ then(resolve: () => mixed): void,
58
+};
59
+type InitializedChunk<T> = {
60
+ _status: 2,
61
_value: T,
62
+ _response: Response,
63
then(resolve: () => mixed): void,
64
};
65
type ErroredChunk = {
54
- _status: 2,
66
+ _status: 3,
67
_value: Error,
68
+ _response: Response,
69
then(resolve: () => mixed): void,
70
};
58
-type SomeChunk<T> = PendingChunk | ResolvedChunk<T> | ErroredChunk;
71
+type SomeChunk<T> =
72
+ | PendingChunk
73
+ | ResolvedModelChunk
74
+ | InitializedChunk<T>
75
+ | ErroredChunk;
76
60
-function Chunk(status: any, value: any) {
77
+function Chunk(status: any, value: any, response: Response) {
78
this._status = status;
79
this._value = value;
80
+ this._response = response;
81
}
82
Chunk.prototype.then = function<T>(resolve: () => mixed) {
83
const chunk: SomeChunk<T> = this;
91
}
92
};
93
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
+ ...
98
};
99
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;
113
}
114
}
115
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);
120
}
121
109
-function createPendingChunk(): PendingChunk {
110
- return new Chunk(PENDING, null);
122
+function createPendingChunk(response: Response): PendingChunk {
123
+ return new Chunk(PENDING, null, response);
124
}
125
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);
128
}
129
130
function wakeChunk(listeners: null | Array<() => mixed>) {
148
wakeChunk(listeners);
149
}
150
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);
156
}
157
142
-function resolveChunk<T>(chunk: SomeChunk<T>, value: T): void {
158
+function resolveModelChunk<T>(
159
+ chunk: SomeChunk<T>,
160
+ value: UninitializedModel,
161
+): void {
162
if (chunk._status !== PENDING) {
163
// We already resolved. We didn't expect to see this.
164
return;
165
}
166
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;
169
resolvedChunk._value = value;
170
wakeChunk(listeners);
171
}
172
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
+
181
// Report that any missing chunks in the model is now going to throw this
182
// 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 => {
185
// If this chunk was already resolved or errored, it won't
186
// trigger an error but if it wasn't then we need to
187
// because we won't be getting any new data to resolve it.
195
return maybeChunk;
196
}
197
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);
199
}
200
201
function createElement(type, key, props): React$Element<any> {
243
mixed,
244
ModuleMetaData | SomeChunk<ModuleMetaData>,
245
Data | SomeChunk<Data>,
246
+ Response,
247
];
248
249
function initializeBlock<Props, Data>(
285
return lazyType;
286
}
287
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.
314
return chunk;
315
}
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);
319
}
320
}
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;
323
}
324
return value;
325
}
326
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,
354
id: number,
318
- model: M,
355
+ model: UninitializedModel,
356
): void {
320
- const chunks = response.chunks;
357
+ const chunks = response._chunks;
358
const chunk = chunks.get(id);
359
if (!chunk) {
323
- chunks.set(id, createResolvedChunk(model));
360
+ chunks.set(id, createResolvedModelChunk(response, model));
361
} else {
325
- resolveChunk(chunk, model);
362
+ resolveModelChunk(chunk, model);
363
}
364
}
365
329
-export function resolveErrorChunk<T>(
330
- response: Response<T>,
366
+export function resolveError(
367
+ response: Response,
368
id: number,
369
message: string,
370
stack: string,
371
): void {
372
const error = new Error(message);
373
error.stack = stack;
337
- const chunks = response.chunks;
374
+ const chunks = response._chunks;
375
const chunk = chunks.get(id);
376
if (!chunk) {
340
- chunks.set(id, createErrorChunk(error));
377
+ chunks.set(id, createErrorChunk(response, error));
378
} else {
379
triggerErrorOnChunk(chunk, error);
380
}
381
}
382
346
-export function close<T>(response: Response<T>): void {
383
+export function close(response: Response): void {
384
// In case there are any remaining unresolved chunks, they won't
385
// be resolved now. So we need to issue an error to those.
386
// Ideally we should be able to early bail out if we kept a