77 * @flow
88 */
99
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' ;
1811
1912export type ReactModelRoot < T > = { |
2013 model : T ,
2114| } ;
2215
23- type JSONValue =
16+ export type JSONValue =
2417 | number
2518 | null
2619 | boolean
2720 | string
28- | { [ key : string ] : JSONValue , ...} ;
21+ | { [ key : string ] : JSONValue }
22+ | Array < JSONValue > ;
23+
24+ const isArray = Array . isArray ;
2925
3026const PENDING = 0 ;
3127const RESOLVED = 1 ;
@@ -48,39 +44,23 @@ type ErroredChunk = {|
4844| } ;
4945type Chunk = PendingChunk | ResolvedChunk | ErroredChunk ;
5046
51- type OpaqueResponseWithoutDecoder = {
52- source : Source ,
47+ export type Response = {
5348 partialRow : string ,
5449 modelRoot : ReactModelRoot < any > ,
5550 chunks : Map < number , Chunk> ,
56- fromJSON : ( key : string , value : JSONValue ) => any ,
57- ...
58- } ;
59-
60- type OpaqueResponse = OpaqueResponseWithoutDecoder & {
61- stringDecoder : StringDecoder ,
62- ...
6351} ;
6452
65- export function createResponse ( source : Source ) : OpaqueResponse {
53+ export function createResponse ( ) : Response {
6654 let modelRoot : ReactModelRoot < any > = ( { } : any ) ;
6755 let rootChunk : Chunk = createPendingChunk ( ) ;
6856 definePendingProperty ( modelRoot , 'model' , rootChunk ) ;
6957 let chunks : Map < number , Chunk > = new Map ( ) ;
7058 chunks . set ( 0 , rootChunk ) ;
71-
72- let response : OpaqueResponse = ( ( {
73- source,
59+ let response = {
7460 partialRow : '' ,
7561 modelRoot,
7662 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+ } ;
8464 return response ;
8565}
8666
@@ -138,10 +118,7 @@ function resolveChunk(chunk: Chunk, value: mixed): void {
138118
139119// Report that any missing chunks in the model is now going to throw this
140120// 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 {
145122 response . chunks . forEach ( chunk => {
146123 // If this chunk was already resolved or errored, it won't
147124 // trigger an error but if it wasn't then we need to
@@ -168,39 +145,91 @@ function definePendingProperty(
168145 } ) ;
169146}
170147
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 ,
173191 targetObj : Object ,
174192 key : string ,
175193 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 ;
190214 }
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 ] ) ;
193223 }
194224 }
195225 return value ;
196226}
197227
198- function resolveJSONRow (
199- response : OpaqueResponse ,
228+ export function resolveModelChunk < T > (
229+ response : Response ,
200230 id : number ,
201- json : string ,
231+ model : T ,
202232) : void {
203- let model = JSON . parse ( json , response . fromJSON ) ;
204233 let chunks = response . chunks ;
205234 let chunk = chunks . get ( id ) ;
206235 if ( ! chunk ) {
@@ -210,88 +239,31 @@ function resolveJSONRow(
210239 }
211240}
212241
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 ,
269247) : 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 ) ;
283256 }
284- response . partialRow += readPartialStringChunk ( stringDecoder , chunk ) ;
285257}
286258
287- export function complete ( response : OpaqueResponse ) : void {
259+ export function close ( response : Response ) : void {
288260 // In case there are any remaining unresolved chunks, they won't
289261 // be resolved now. So we need to issue an error to those.
290262 // Ideally we should be able to early bail out if we kept a
291263 // ref count of pending chunks.
292264 reportGlobalError ( response , new Error ( 'Connection closed.' ) ) ;
293265}
294266
295- export function getModelRoot < T > ( response : OpaqueResponse ) : ReactModelRoot < T > {
267+ export function getModelRoot < T > ( response : Response ) : ReactModelRoot < T > {
296268 return response . modelRoot ;
297269}
0 commit comments