7
7
* @flow
8
8
*/
9
9
10
- import type { Source , StringDecoder } from './ReactFlightClientHostConfig' ;
11
-
12
- import {
13
- supportsBinaryStreams ,
14
- createStringDecoder ,
15
- readPartialStringChunk ,
16
- readFinalStringChunk ,
17
- } from './ReactFlightClientHostConfig' ;
10
+ import { REACT_ELEMENT_TYPE } from 'shared/ReactSymbols' ;
18
11
19
12
export type ReactModelRoot < T > = { |
20
13
model : T ,
21
14
| } ;
22
15
23
- type JSONValue =
16
+ export type JSONValue =
24
17
| number
25
18
| null
26
19
| boolean
27
20
| string
28
- | { [ key : string ] : JSONValue , ...} ;
21
+ | { [ key : string ] : JSONValue }
22
+ | Array < JSONValue > ;
23
+
24
+ const isArray = Array . isArray ;
29
25
30
26
const PENDING = 0 ;
31
27
const RESOLVED = 1 ;
@@ -48,39 +44,23 @@ type ErroredChunk = {|
48
44
| } ;
49
45
type Chunk = PendingChunk | ResolvedChunk | ErroredChunk ;
50
46
51
- type OpaqueResponseWithoutDecoder = {
52
- source : Source ,
47
+ export type Response = {
53
48
partialRow : string ,
54
49
modelRoot : ReactModelRoot < any > ,
55
50
chunks : Map < number , Chunk> ,
56
- fromJSON : ( key : string , value : JSONValue ) => any ,
57
- ...
58
- } ;
59
-
60
- type OpaqueResponse = OpaqueResponseWithoutDecoder & {
61
- stringDecoder : StringDecoder ,
62
- ...
63
51
} ;
64
52
65
- export function createResponse ( source : Source ) : OpaqueResponse {
53
+ export function createResponse ( ) : Response {
66
54
let modelRoot : ReactModelRoot < any > = ( { } : any ) ;
67
55
let rootChunk : Chunk = createPendingChunk ( ) ;
68
56
definePendingProperty ( modelRoot , 'model' , rootChunk ) ;
69
57
let chunks : Map < number , Chunk > = new Map ( ) ;
70
58
chunks . set ( 0 , rootChunk ) ;
71
-
72
- let response : OpaqueResponse = ( ( {
73
- source,
59
+ let response = {
74
60
partialRow : '' ,
75
61
modelRoot,
76
62
chunks : chunks ,
77
- fromJSON : function ( key , value ) {
78
- return parseFromJSON ( response , this , key , value ) ;
79
- } ,
80
- } : OpaqueResponseWithoutDecoder ) : any ) ;
81
- if ( supportsBinaryStreams ) {
82
- response . stringDecoder = createStringDecoder ( ) ;
83
- }
63
+ } ;
84
64
return response ;
85
65
}
86
66
@@ -138,10 +118,7 @@ function resolveChunk(chunk: Chunk, value: mixed): void {
138
118
139
119
// Report that any missing chunks in the model is now going to throw this
140
120
// error upon read. Also notify any pending promises.
141
- export function reportGlobalError (
142
- response : OpaqueResponse ,
143
- error : Error ,
144
- ) : void {
121
+ export function reportGlobalError ( response : Response , error : Error ) : void {
145
122
response . chunks . forEach ( chunk => {
146
123
// If this chunk was already resolved or errored, it won't
147
124
// trigger an error but if it wasn't then we need to
@@ -168,39 +145,91 @@ function definePendingProperty(
168
145
} ) ;
169
146
}
170
147
171
- function parseFromJSON (
172
- response : OpaqueResponse ,
148
+ function createElement ( type , key , props ) : React$Element < any > {
149
+ const element : any = {
150
+ // This tag allows us to uniquely identify this as a React Element
151
+ $$typeof : REACT_ELEMENT_TYPE ,
152
+
153
+ // Built-in properties that belong on the element
154
+ type : type ,
155
+ key : key ,
156
+ ref : null ,
157
+ props : props ,
158
+
159
+ // Record the component responsible for creating this element.
160
+ _owner : null ,
161
+ } ;
162
+ if ( __DEV__ ) {
163
+ // We don't really need to add any of these but keeping them for good measure.
164
+ // Unfortunately, _store is enumerable in jest matchers so for equality to
165
+ // work, I need to keep it or make _store non-enumerable in the other file.
166
+ element . _store = { } ;
167
+ Object . defineProperty ( element . _store , 'validated' , {
168
+ configurable : false ,
169
+ enumerable : false ,
170
+ writable : true ,
171
+ value : true , // This element has already been validated on the server.
172
+ } ) ;
173
+ Object . defineProperty ( element , '_self' , {
174
+ configurable : false ,
175
+ enumerable : false ,
176
+ writable : false ,
177
+ value : null ,
178
+ } ) ;
179
+ Object . defineProperty ( element , '_source' , {
180
+ configurable : false ,
181
+ enumerable : false ,
182
+ writable : false ,
183
+ value : null ,
184
+ } ) ;
185
+ }
186
+ return element ;
187
+ }
188
+
189
+ export function parseModelFromJSON (
190
+ response : Response ,
173
191
targetObj : Object ,
174
192
key : string ,
175
193
value : JSONValue ,
176
- ) : any {
177
- if ( typeof value === 'string' && value [ 0 ] === '$' ) {
178
- if ( value [ 1 ] === '$' ) {
179
- // This was an escaped string value.
180
- return value . substring ( 1 ) ;
181
- } else {
182
- let id = parseInt ( value . substring ( 1 ) , 16 ) ;
183
- let chunks = response . chunks ;
184
- let chunk = chunks . get ( id ) ;
185
- if ( ! chunk ) {
186
- chunk = createPendingChunk ( ) ;
187
- chunks . set ( id , chunk ) ;
188
- } else if ( chunk . status === RESOLVED ) {
189
- return chunk . value ;
194
+ ) : mixed {
195
+ if ( typeof value === 'string' ) {
196
+ if ( value [ 0 ] === '$' ) {
197
+ if ( value === '$' ) {
198
+ return REACT_ELEMENT_TYPE ;
199
+ } else if ( value [ 1 ] === '$' || value [ 1 ] === '@' ) {
200
+ // This was an escaped string value.
201
+ return value . substring ( 1 ) ;
202
+ } else {
203
+ let id = parseInt ( value . substring ( 1 ) , 16 ) ;
204
+ let chunks = response . chunks ;
205
+ let chunk = chunks . get ( id ) ;
206
+ if ( ! chunk ) {
207
+ chunk = createPendingChunk ( ) ;
208
+ chunks . set ( id , chunk ) ;
209
+ } else if ( chunk . status === RESOLVED ) {
210
+ return chunk . value ;
211
+ }
212
+ definePendingProperty ( targetObj , key , chunk ) ;
213
+ return undefined ;
190
214
}
191
- definePendingProperty ( targetObj , key , chunk ) ;
192
- return undefined ;
215
+ }
216
+ }
217
+ if ( isArray ( value ) ) {
218
+ let tuple : [ mixed , mixed , mixed , mixed ] = ( value : any ) ;
219
+ if ( tuple [ 0 ] === REACT_ELEMENT_TYPE ) {
220
+ // TODO: Consider having React just directly accept these arrays as elements.
221
+ // Or even change the ReactElement type to be an array.
222
+ return createElement ( tuple [ 1 ] , tuple [ 2 ] , tuple [ 3 ] ) ;
193
223
}
194
224
}
195
225
return value ;
196
226
}
197
227
198
- function resolveJSONRow (
199
- response : OpaqueResponse ,
228
+ export function resolveModelChunk < T > (
229
+ response : Response ,
200
230
id : number ,
201
- json : string ,
231
+ model : T ,
202
232
) : void {
203
- let model = JSON . parse ( json , response . fromJSON ) ;
204
233
let chunks = response . chunks ;
205
234
let chunk = chunks . get ( id ) ;
206
235
if ( ! chunk ) {
@@ -210,88 +239,31 @@ function resolveJSONRow(
210
239
}
211
240
}
212
241
213
- function processFullRow ( response : OpaqueResponse , row : string ) : void {
214
- if ( row === '' ) {
215
- return ;
216
- }
217
- let tag = row [ 0 ] ;
218
- switch ( tag ) {
219
- case 'J' : {
220
- let colon = row . indexOf ( ':' , 1 ) ;
221
- let id = parseInt ( row . substring ( 1 , colon ) , 16 ) ;
222
- let json = row . substring ( colon + 1 ) ;
223
- resolveJSONRow ( response , id , json ) ;
224
- return ;
225
- }
226
- case 'E' : {
227
- let colon = row . indexOf ( ':' , 1 ) ;
228
- let id = parseInt ( row . substring ( 1 , colon ) , 16 ) ;
229
- let json = row . substring ( colon + 1 ) ;
230
- let errorInfo = JSON . parse ( json ) ;
231
- let error = new Error ( errorInfo . message ) ;
232
- error . stack = errorInfo . stack ;
233
- let chunks = response . chunks ;
234
- let chunk = chunks . get ( id ) ;
235
- if ( ! chunk ) {
236
- chunks . set ( id , createErrorChunk ( error ) ) ;
237
- } else {
238
- triggerErrorOnChunk ( chunk , error ) ;
239
- }
240
- return ;
241
- }
242
- default : {
243
- // Assume this is the root model.
244
- resolveJSONRow ( response , 0 , row ) ;
245
- return ;
246
- }
247
- }
248
- }
249
-
250
- export function processStringChunk (
251
- response : OpaqueResponse ,
252
- chunk : string ,
253
- offset : number ,
254
- ) : void {
255
- let linebreak = chunk . indexOf ( '\n' , offset ) ;
256
- while ( linebreak > - 1 ) {
257
- let fullrow = response . partialRow + chunk . substring ( offset , linebreak ) ;
258
- processFullRow ( response , fullrow ) ;
259
- response . partialRow = '' ;
260
- offset = linebreak + 1 ;
261
- linebreak = chunk . indexOf ( '\n' , offset ) ;
262
- }
263
- response . partialRow += chunk . substring ( offset ) ;
264
- }
265
-
266
- export function processBinaryChunk (
267
- response : OpaqueResponse ,
268
- chunk : Uint8Array ,
242
+ export function resolveErrorChunk (
243
+ response : Response ,
244
+ id : number ,
245
+ message : string ,
246
+ stack : string ,
269
247
) : void {
270
- if ( ! supportsBinaryStreams ) {
271
- throw new Error ( "This environment don't support binary chunks." ) ;
272
- }
273
- let stringDecoder = response . stringDecoder ;
274
- let linebreak = chunk . indexOf ( 10 ) ; // newline
275
- while ( linebreak > - 1 ) {
276
- let fullrow =
277
- response . partialRow +
278
- readFinalStringChunk ( stringDecoder , chunk . subarray ( 0 , linebreak ) ) ;
279
- processFullRow ( response , fullrow ) ;
280
- response . partialRow = '' ;
281
- chunk = chunk . subarray ( linebreak + 1 ) ;
282
- linebreak = chunk . indexOf ( 10 ) ; // newline
248
+ let error = new Error ( message ) ;
249
+ error . stack = stack ;
250
+ let chunks = response . chunks ;
251
+ let chunk = chunks . get ( id ) ;
252
+ if ( ! chunk ) {
253
+ chunks . set ( id , createErrorChunk ( error ) ) ;
254
+ } else {
255
+ triggerErrorOnChunk ( chunk , error ) ;
283
256
}
284
- response . partialRow += readPartialStringChunk ( stringDecoder , chunk ) ;
285
257
}
286
258
287
- export function complete ( response : OpaqueResponse ) : void {
259
+ export function close ( response : Response ) : void {
288
260
// In case there are any remaining unresolved chunks, they won't
289
261
// be resolved now. So we need to issue an error to those.
290
262
// Ideally we should be able to early bail out if we kept a
291
263
// ref count of pending chunks.
292
264
reportGlobalError ( response , new Error ( 'Connection closed.' ) ) ;
293
265
}
294
266
295
- export function getModelRoot < T > ( response : OpaqueResponse ) : ReactModelRoot < T > {
267
+ export function getModelRoot < T > ( response : Response ) : ReactModelRoot < T > {
296
268
return response . modelRoot ;
297
269
}
0 commit comments