@@ -12,6 +12,7 @@ import { addPath, pathToArray } from '../jsutils/Path.js';
12
12
import { promiseForObject } from '../jsutils/promiseForObject.js' ;
13
13
import type { PromiseOrValue } from '../jsutils/PromiseOrValue.js' ;
14
14
import { promiseReduce } from '../jsutils/promiseReduce.js' ;
15
+ import { Publisher } from '../jsutils/Publisher.js' ;
15
16
16
17
import type { GraphQLFormattedError } from '../error/GraphQLError.js' ;
17
18
import { GraphQLError } from '../error/GraphQLError.js' ;
@@ -121,7 +122,10 @@ export interface ExecutionContext {
121
122
typeResolver : GraphQLTypeResolver < any , any > ;
122
123
subscribeFieldResolver : GraphQLFieldResolver < any , any > ;
123
124
errors : Array < GraphQLError > ;
124
- subsequentPayloads : Set < IncrementalDataRecord > ;
125
+ publisher : Publisher <
126
+ IncrementalDataRecord ,
127
+ SubsequentIncrementalExecutionResult
128
+ > ;
125
129
}
126
130
127
131
/**
@@ -353,17 +357,18 @@ function executeImpl(
353
357
// in this case is the entire response.
354
358
try {
355
359
const result = executeOperation ( exeContext ) ;
360
+ const publisher = exeContext . publisher ;
356
361
if ( isPromise ( result ) ) {
357
362
return result . then (
358
363
( data ) => {
359
364
const initialResult = buildResponse ( data , exeContext . errors ) ;
360
- if ( exeContext . subsequentPayloads . size > 0 ) {
365
+ if ( publisher . getPending ( ) . size > 0 ) {
361
366
return {
362
367
initialResult : {
363
368
...initialResult ,
364
369
hasNext : true ,
365
370
} ,
366
- subsequentResults : yieldSubsequentPayloads ( exeContext ) ,
371
+ subsequentResults : publisher . subscribe ( ) ,
367
372
} ;
368
373
}
369
374
return initialResult ;
@@ -375,13 +380,13 @@ function executeImpl(
375
380
) ;
376
381
}
377
382
const initialResult = buildResponse ( result , exeContext . errors ) ;
378
- if ( exeContext . subsequentPayloads . size > 0 ) {
383
+ if ( publisher . getPending ( ) . size > 0 ) {
379
384
return {
380
385
initialResult : {
381
386
...initialResult ,
382
387
hasNext : true ,
383
388
} ,
384
- subsequentResults : yieldSubsequentPayloads ( exeContext ) ,
389
+ subsequentResults : publisher . subscribe ( ) ,
385
390
} ;
386
391
}
387
392
return initialResult ;
@@ -503,7 +508,7 @@ export function buildExecutionContext(
503
508
fieldResolver : fieldResolver ?? defaultFieldResolver ,
504
509
typeResolver : typeResolver ?? defaultTypeResolver ,
505
510
subscribeFieldResolver : subscribeFieldResolver ?? defaultFieldResolver ,
506
- subsequentPayloads : new Set ( ) ,
511
+ publisher : new Publisher ( getIncrementalResult , returnStreamIterators ) ,
507
512
errors : [ ] ,
508
513
} ;
509
514
}
@@ -515,7 +520,7 @@ function buildPerEventExecutionContext(
515
520
return {
516
521
...exeContext ,
517
522
rootValue : payload ,
518
- subsequentPayloads : new Set ( ) ,
523
+ // no need to update publisher, incremental delivery is not supported for subscriptions
519
524
errors : [ ] ,
520
525
} ;
521
526
}
@@ -2098,7 +2103,8 @@ function filterSubsequentPayloads(
2098
2103
currentIncrementalDataRecord : IncrementalDataRecord | undefined ,
2099
2104
) : void {
2100
2105
const nullPathArray = pathToArray ( nullPath ) ;
2101
- exeContext . subsequentPayloads . forEach ( ( incrementalDataRecord ) => {
2106
+ const publisher = exeContext . publisher ;
2107
+ publisher . getPending ( ) . forEach ( ( incrementalDataRecord ) => {
2102
2108
if ( incrementalDataRecord === currentIncrementalDataRecord ) {
2103
2109
// don't remove payload from where error originates
2104
2110
return ;
@@ -2118,24 +2124,26 @@ function filterSubsequentPayloads(
2118
2124
// ignore error
2119
2125
} ) ;
2120
2126
}
2121
- exeContext . subsequentPayloads . delete ( incrementalDataRecord ) ;
2127
+ publisher . delete ( incrementalDataRecord ) ;
2122
2128
} ) ;
2123
2129
}
2124
2130
2125
- function getCompletedIncrementalResults (
2126
- exeContext : ExecutionContext ,
2127
- ) : Array < IncrementalResult > {
2131
+ function getIncrementalResult (
2132
+ subsequentPayloads : Set < IncrementalDataRecord > ,
2133
+ ) : SubsequentIncrementalExecutionResult | undefined {
2128
2134
const incrementalResults : Array < IncrementalResult > = [ ] ;
2129
- for ( const incrementalDataRecord of exeContext . subsequentPayloads ) {
2135
+ let encounteredCompletedAsyncIterator = false ;
2136
+ for ( const incrementalDataRecord of subsequentPayloads ) {
2130
2137
const incrementalResult : IncrementalResult = { } ;
2131
2138
if ( ! incrementalDataRecord . isCompleted ) {
2132
2139
continue ;
2133
2140
}
2134
- exeContext . subsequentPayloads . delete ( incrementalDataRecord ) ;
2141
+ subsequentPayloads . delete ( incrementalDataRecord ) ;
2135
2142
if ( isStreamItemsRecord ( incrementalDataRecord ) ) {
2136
2143
const items = incrementalDataRecord . items ;
2137
2144
if ( incrementalDataRecord . isCompletedAsyncIterator ) {
2138
2145
// async iterable resolver just finished but there may be pending payloads
2146
+ encounteredCompletedAsyncIterator = true ;
2139
2147
continue ;
2140
2148
}
2141
2149
( incrementalResult as IncrementalStreamResult ) . items = items ;
@@ -2153,80 +2161,26 @@ function getCompletedIncrementalResults(
2153
2161
}
2154
2162
incrementalResults . push ( incrementalResult ) ;
2155
2163
}
2156
- return incrementalResults ;
2164
+ return incrementalResults . length
2165
+ ? { incremental : incrementalResults , hasNext : subsequentPayloads . size > 0 }
2166
+ : encounteredCompletedAsyncIterator && subsequentPayloads . size === 0
2167
+ ? { hasNext : false }
2168
+ : undefined ;
2157
2169
}
2158
2170
2159
- function yieldSubsequentPayloads (
2160
- exeContext : ExecutionContext ,
2161
- ) : AsyncGenerator < SubsequentIncrementalExecutionResult , void , void > {
2162
- let isDone = false ;
2163
-
2164
- async function next ( ) : Promise <
2165
- IteratorResult < SubsequentIncrementalExecutionResult , void >
2166
- > {
2167
- if ( isDone ) {
2168
- return { value : undefined , done : true } ;
2169
- }
2170
-
2171
- await Promise . race (
2172
- Array . from ( exeContext . subsequentPayloads ) . map ( ( p ) => p . promise ) ,
2173
- ) ;
2174
-
2175
- if ( isDone ) {
2176
- // a different call to next has exhausted all payloads
2177
- return { value : undefined , done : true } ;
2178
- }
2179
-
2180
- const incremental = getCompletedIncrementalResults ( exeContext ) ;
2181
- const hasNext = exeContext . subsequentPayloads . size > 0 ;
2182
-
2183
- if ( ! incremental . length && hasNext ) {
2184
- return next ( ) ;
2185
- }
2186
-
2187
- if ( ! hasNext ) {
2188
- isDone = true ;
2171
+ async function returnStreamIterators (
2172
+ subsequentPayloads : ReadonlySet < IncrementalDataRecord > ,
2173
+ ) : Promise < void > {
2174
+ const promises : Array < Promise < IteratorResult < unknown > > > = [ ] ;
2175
+ subsequentPayloads . forEach ( ( incrementalDataRecord ) => {
2176
+ if (
2177
+ isStreamItemsRecord ( incrementalDataRecord ) &&
2178
+ incrementalDataRecord . asyncIterator ?. return
2179
+ ) {
2180
+ promises . push ( incrementalDataRecord . asyncIterator . return ( ) ) ;
2189
2181
}
2190
-
2191
- return {
2192
- value : incremental . length ? { incremental, hasNext } : { hasNext } ,
2193
- done : false ,
2194
- } ;
2195
- }
2196
-
2197
- function returnStreamIterators ( ) {
2198
- const promises : Array < Promise < IteratorResult < unknown > > > = [ ] ;
2199
- exeContext . subsequentPayloads . forEach ( ( incrementalDataRecord ) => {
2200
- if (
2201
- isStreamItemsRecord ( incrementalDataRecord ) &&
2202
- incrementalDataRecord . asyncIterator ?. return
2203
- ) {
2204
- promises . push ( incrementalDataRecord . asyncIterator . return ( ) ) ;
2205
- }
2206
- } ) ;
2207
- return Promise . all ( promises ) ;
2208
- }
2209
-
2210
- return {
2211
- [ Symbol . asyncIterator ] ( ) {
2212
- return this ;
2213
- } ,
2214
- next,
2215
- async return ( ) : Promise <
2216
- IteratorResult < SubsequentIncrementalExecutionResult , void >
2217
- > {
2218
- await returnStreamIterators ( ) ;
2219
- isDone = true ;
2220
- return { value : undefined , done : true } ;
2221
- } ,
2222
- async throw (
2223
- error ?: unknown ,
2224
- ) : Promise < IteratorResult < SubsequentIncrementalExecutionResult , void > > {
2225
- await returnStreamIterators ( ) ;
2226
- isDone = true ;
2227
- return Promise . reject ( error ) ;
2228
- } ,
2229
- } ;
2182
+ } ) ;
2183
+ await Promise . all ( promises ) ;
2230
2184
}
2231
2185
2232
2186
class DeferredFragmentRecord {
@@ -2252,7 +2206,7 @@ class DeferredFragmentRecord {
2252
2206
this . parentContext = opts . parentContext ;
2253
2207
this . errors = [ ] ;
2254
2208
this . _exeContext = opts . exeContext ;
2255
- this . _exeContext . subsequentPayloads . add ( this ) ;
2209
+ this . _exeContext . publisher . add ( this ) ;
2256
2210
this . isCompleted = false ;
2257
2211
this . data = null ;
2258
2212
this . promise = new Promise < ObjMap < unknown > | null > ( ( resolve ) => {
@@ -2262,6 +2216,7 @@ class DeferredFragmentRecord {
2262
2216
} ) . then ( ( data ) => {
2263
2217
this . data = data ;
2264
2218
this . isCompleted = true ;
2219
+ this . _exeContext . publisher . complete ( this ) ;
2265
2220
} ) ;
2266
2221
}
2267
2222
@@ -2303,7 +2258,7 @@ class StreamItemsRecord {
2303
2258
this . asyncIterator = opts . asyncIterator ;
2304
2259
this . errors = [ ] ;
2305
2260
this . _exeContext = opts . exeContext ;
2306
- this . _exeContext . subsequentPayloads . add ( this ) ;
2261
+ this . _exeContext . publisher . add ( this ) ;
2307
2262
this . isCompleted = false ;
2308
2263
this . items = null ;
2309
2264
this . promise = new Promise < Array < unknown > | null > ( ( resolve ) => {
@@ -2313,6 +2268,7 @@ class StreamItemsRecord {
2313
2268
} ) . then ( ( items ) => {
2314
2269
this . items = items ;
2315
2270
this . isCompleted = true ;
2271
+ this . _exeContext . publisher . complete ( this ) ;
2316
2272
} ) ;
2317
2273
}
2318
2274
0 commit comments