1
1
import { isPromise } from '../jsutils/isPromise.js' ;
2
2
import { promiseWithResolvers } from '../jsutils/promiseWithResolvers.js' ;
3
3
4
+ import type { GraphQLError } from '../error/GraphQLError.js' ;
5
+
4
6
import type {
5
7
DeferredFragmentRecord ,
6
8
DeferredGroupedFieldSetRecord ,
7
9
IncrementalDataRecord ,
8
10
IncrementalDataRecordResult ,
9
11
ReconcilableDeferredGroupedFieldSetResult ,
10
- StreamItemsRecord ,
12
+ StreamItemRecord ,
11
13
StreamRecord ,
12
14
SubsequentResultRecord ,
13
15
} from './types.js' ;
@@ -27,9 +29,9 @@ function isDeferredFragmentNode(
27
29
}
28
30
29
31
function isStreamNode (
30
- subsequentResultNode : SubsequentResultNode ,
31
- ) : subsequentResultNode is StreamRecord {
32
- return 'path ' in subsequentResultNode ;
32
+ record : SubsequentResultNode | IncrementalDataRecord ,
33
+ ) : record is StreamRecord {
34
+ return 'streamItemQueue ' in record ;
33
35
}
34
36
35
37
type SubsequentResultNode = DeferredFragmentNode | StreamRecord ;
@@ -67,7 +69,7 @@ export class IncrementalGraph {
67
69
if ( isDeferredGroupedFieldSetRecord ( incrementalDataRecord ) ) {
68
70
this . _addDeferredGroupedFieldSetRecord ( incrementalDataRecord ) ;
69
71
} else {
70
- this . _addStreamItemsRecord ( incrementalDataRecord ) ;
72
+ this . _addStreamRecord ( incrementalDataRecord ) ;
71
73
}
72
74
}
73
75
}
@@ -95,6 +97,7 @@ export class IncrementalGraph {
95
97
if ( isStreamNode ( node ) ) {
96
98
this . _pending . add ( node ) ;
97
99
newPending . push ( node ) ;
100
+ this . _newIncrementalDataRecords . add ( node ) ;
98
101
} else if ( node . deferredGroupedFieldSetRecords . size > 0 ) {
99
102
for ( const deferredGroupedFieldSetNode of node . deferredGroupedFieldSetRecords ) {
100
103
this . _newIncrementalDataRecords . add ( deferredGroupedFieldSetNode ) ;
@@ -110,12 +113,20 @@ export class IncrementalGraph {
110
113
this . _newPending . clear ( ) ;
111
114
112
115
for ( const incrementalDataRecord of this . _newIncrementalDataRecords ) {
113
- const result = incrementalDataRecord . result . value ;
114
- if ( isPromise ( result ) ) {
116
+ if ( isStreamNode ( incrementalDataRecord ) ) {
115
117
// eslint-disable-next-line @typescript-eslint/no-floating-promises
116
- result . then ( ( resolved ) => this . _enqueue ( resolved ) ) ;
118
+ this . _onStreamItems (
119
+ incrementalDataRecord ,
120
+ incrementalDataRecord . streamItemQueue ,
121
+ ) ;
117
122
} else {
118
- this . _enqueue ( result ) ;
123
+ const result = incrementalDataRecord . result . value ;
124
+ if ( isPromise ( result ) ) {
125
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
126
+ result . then ( ( resolved ) => this . _enqueue ( resolved ) ) ;
127
+ } else {
128
+ this . _enqueue ( result ) ;
129
+ }
119
130
}
120
131
}
121
132
this . _newIncrementalDataRecords . clear ( ) ;
@@ -246,12 +257,8 @@ export class IncrementalGraph {
246
257
}
247
258
}
248
259
249
- private _addStreamItemsRecord ( streamItemsRecord : StreamItemsRecord ) : void {
250
- const streamRecord = streamItemsRecord . streamRecord ;
251
- if ( ! this . _pending . has ( streamRecord ) ) {
252
- this . _newPending . add ( streamRecord ) ;
253
- }
254
- this . _newIncrementalDataRecords . add ( streamItemsRecord ) ;
260
+ private _addStreamRecord ( streamRecord : StreamRecord ) : void {
261
+ this . _newPending . add ( streamRecord ) ;
255
262
}
256
263
257
264
private _addDeferredFragmentNode (
@@ -283,6 +290,66 @@ export class IncrementalGraph {
283
290
return deferredFragmentNode ;
284
291
}
285
292
293
+ private async _onStreamItems (
294
+ streamRecord : StreamRecord ,
295
+ streamItemQueue : Array < StreamItemRecord > ,
296
+ ) : Promise < void > {
297
+ let items : Array < unknown > = [ ] ;
298
+ let errors : Array < GraphQLError > = [ ] ;
299
+ let incrementalDataRecords : Array < IncrementalDataRecord > = [ ] ;
300
+ let streamItemRecord : StreamItemRecord | undefined ;
301
+ while ( ( streamItemRecord = streamItemQueue . shift ( ) ) !== undefined ) {
302
+ let result = streamItemRecord . value ;
303
+ if ( isPromise ( result ) ) {
304
+ if ( items . length > 0 ) {
305
+ this . _enqueue ( {
306
+ streamRecord,
307
+ result :
308
+ // TODO add additional test case or rework for coverage
309
+ errors . length > 0 /* c8 ignore start */
310
+ ? { items, errors } /* c8 ignore stop */
311
+ : { items } ,
312
+ incrementalDataRecords,
313
+ } ) ;
314
+ items = [ ] ;
315
+ errors = [ ] ;
316
+ incrementalDataRecords = [ ] ;
317
+ }
318
+ // eslint-disable-next-line no-await-in-loop
319
+ result = await result ;
320
+ // wait an additional tick to coalesce resolving additional promises
321
+ // within the queue
322
+ // eslint-disable-next-line no-await-in-loop
323
+ await Promise . resolve ( ) ;
324
+ }
325
+ if ( result . item === undefined ) {
326
+ if ( items . length > 0 ) {
327
+ this . _enqueue ( {
328
+ streamRecord,
329
+ result : errors . length > 0 ? { items, errors } : { items } ,
330
+ incrementalDataRecords,
331
+ } ) ;
332
+ }
333
+ this . _enqueue (
334
+ result . errors === undefined
335
+ ? { streamRecord }
336
+ : {
337
+ streamRecord,
338
+ errors : result . errors ,
339
+ } ,
340
+ ) ;
341
+ return ;
342
+ }
343
+ items . push ( result . item ) ;
344
+ if ( result . errors !== undefined ) {
345
+ errors . push ( ...result . errors ) ;
346
+ }
347
+ if ( result . incrementalDataRecords !== undefined ) {
348
+ incrementalDataRecords . push ( ...result . incrementalDataRecords ) ;
349
+ }
350
+ }
351
+ }
352
+
286
353
private * _yieldCurrentCompletedIncrementalData (
287
354
first : IncrementalDataRecordResult ,
288
355
) : Generator < IncrementalDataRecordResult > {
0 commit comments