@@ -1052,10 +1052,97 @@ async function completeAsyncIteratorValue(
1052
1052
[ ] ,
1053
1053
] ;
1054
1054
let index = 0 ;
1055
- const streamUsage = getStreamUsage ( exeContext , fieldGroup , path ) ;
1056
1055
// eslint-disable-next-line no-constant-condition
1057
1056
while ( true ) {
1058
- if ( streamUsage && index >= streamUsage . initialCount ) {
1057
+ const itemPath = addPath ( path , index , undefined ) ;
1058
+ let iteration ;
1059
+ try {
1060
+ // eslint-disable-next-line no-await-in-loop
1061
+ iteration = await asyncIterator . next ( ) ;
1062
+ } catch ( rawError ) {
1063
+ throw locatedError ( rawError , toNodes ( fieldGroup ) , pathToArray ( path ) ) ;
1064
+ }
1065
+
1066
+ // TODO: add test case for stream returning done before initialCount
1067
+ /* c8 ignore next 3 */
1068
+ if ( iteration . done ) {
1069
+ break ;
1070
+ }
1071
+
1072
+ const item = iteration . value ;
1073
+ // TODO: add tests for stream backed by asyncIterator that returns a promise
1074
+ /* c8 ignore start */
1075
+ if ( isPromise ( item ) ) {
1076
+ completedResults . push (
1077
+ completePromisedListItemValue (
1078
+ item ,
1079
+ graphqlWrappedResult ,
1080
+ exeContext ,
1081
+ itemType ,
1082
+ fieldGroup ,
1083
+ info ,
1084
+ itemPath ,
1085
+ incrementalContext ,
1086
+ deferMap ,
1087
+ ) ,
1088
+ ) ;
1089
+ containsPromise = true ;
1090
+ } else if (
1091
+ /* c8 ignore stop */
1092
+ completeListItemValue (
1093
+ item ,
1094
+ completedResults ,
1095
+ graphqlWrappedResult ,
1096
+ exeContext ,
1097
+ itemType ,
1098
+ fieldGroup ,
1099
+ info ,
1100
+ itemPath ,
1101
+ incrementalContext ,
1102
+ deferMap ,
1103
+ )
1104
+ // TODO: add tests for stream backed by asyncIterator that completes to a promise
1105
+ /* c8 ignore start */
1106
+ ) {
1107
+ containsPromise = true ;
1108
+ }
1109
+ /* c8 ignore stop */
1110
+ index ++ ;
1111
+ }
1112
+
1113
+ return containsPromise
1114
+ ? /* c8 ignore start */ Promise . all ( completedResults ) . then ( ( resolved ) => [
1115
+ resolved ,
1116
+ graphqlWrappedResult [ 1 ] ,
1117
+ ] )
1118
+ : /* c8 ignore stop */ graphqlWrappedResult ;
1119
+ }
1120
+
1121
+ /**
1122
+ * Complete a async iterator value by completing the result and calling
1123
+ * recursively until all the results are completed.
1124
+ */
1125
+ async function completeAsyncIteratorValueWithPossibleStream (
1126
+ exeContext : ExecutionContext ,
1127
+ itemType : GraphQLOutputType ,
1128
+ fieldGroup : FieldGroup ,
1129
+ info : GraphQLResolveInfo ,
1130
+ path : Path ,
1131
+ asyncIterator : AsyncIterator < unknown > ,
1132
+ streamUsage : StreamUsage ,
1133
+ incrementalContext : IncrementalContext | undefined ,
1134
+ deferMap : ReadonlyMap < DeferUsage , DeferredFragmentRecord > | undefined ,
1135
+ ) : Promise < GraphQLWrappedResult < ReadonlyArray < unknown > > > {
1136
+ let containsPromise = false ;
1137
+ const completedResults : Array < unknown > = [ ] ;
1138
+ const graphqlWrappedResult : GraphQLWrappedResult < Array < unknown > > = [
1139
+ completedResults ,
1140
+ [ ] ,
1141
+ ] ;
1142
+ let index = 0 ;
1143
+ // eslint-disable-next-line no-constant-condition
1144
+ while ( true ) {
1145
+ if ( index >= streamUsage . initialCount ) {
1059
1146
const returnFn = asyncIterator . return ;
1060
1147
let streamRecord : SubsequentResultRecord | CancellableStreamRecord ;
1061
1148
if ( returnFn === undefined ) {
@@ -1166,17 +1253,32 @@ function completeListValue(
1166
1253
deferMap : ReadonlyMap < DeferUsage , DeferredFragmentRecord > | undefined ,
1167
1254
) : PromiseOrValue < GraphQLWrappedResult < ReadonlyArray < unknown > > > {
1168
1255
const itemType = returnType . ofType ;
1256
+ const streamUsage = getStreamUsage ( exeContext , fieldGroup , path ) ;
1169
1257
1170
1258
if ( isAsyncIterable ( result ) ) {
1171
1259
const asyncIterator = result [ Symbol . asyncIterator ] ( ) ;
1172
1260
1173
- return completeAsyncIteratorValue (
1261
+ if ( streamUsage === undefined ) {
1262
+ return completeAsyncIteratorValue (
1263
+ exeContext ,
1264
+ itemType ,
1265
+ fieldGroup ,
1266
+ info ,
1267
+ path ,
1268
+ asyncIterator ,
1269
+ incrementalContext ,
1270
+ deferMap ,
1271
+ ) ;
1272
+ }
1273
+
1274
+ return completeAsyncIteratorValueWithPossibleStream (
1174
1275
exeContext ,
1175
1276
itemType ,
1176
1277
fieldGroup ,
1177
1278
info ,
1178
1279
path ,
1179
1280
asyncIterator ,
1281
+ streamUsage ,
1180
1282
incrementalContext ,
1181
1283
deferMap ,
1182
1284
) ;
@@ -1188,13 +1290,27 @@ function completeListValue(
1188
1290
) ;
1189
1291
}
1190
1292
1191
- return completeIterableValue (
1293
+ if ( streamUsage === undefined ) {
1294
+ return completeIterableValue (
1295
+ exeContext ,
1296
+ itemType ,
1297
+ fieldGroup ,
1298
+ info ,
1299
+ path ,
1300
+ result ,
1301
+ incrementalContext ,
1302
+ deferMap ,
1303
+ ) ;
1304
+ }
1305
+
1306
+ return completeIterableValueWithPossibleStream (
1192
1307
exeContext ,
1193
1308
itemType ,
1194
1309
fieldGroup ,
1195
1310
info ,
1196
1311
path ,
1197
1312
result ,
1313
+ streamUsage ,
1198
1314
incrementalContext ,
1199
1315
deferMap ,
1200
1316
) ;
@@ -1219,13 +1335,79 @@ function completeIterableValue(
1219
1335
[ ] ,
1220
1336
] ;
1221
1337
let index = 0 ;
1222
- const streamUsage = getStreamUsage ( exeContext , fieldGroup , path ) ;
1338
+ for ( const item of items ) {
1339
+ // No need to modify the info object containing the path,
1340
+ // since from here on it is not ever accessed by resolver functions.
1341
+ const itemPath = addPath ( path , index , undefined ) ;
1342
+
1343
+ if ( isPromise ( item ) ) {
1344
+ completedResults . push (
1345
+ completePromisedListItemValue (
1346
+ item ,
1347
+ graphqlWrappedResult ,
1348
+ exeContext ,
1349
+ itemType ,
1350
+ fieldGroup ,
1351
+ info ,
1352
+ itemPath ,
1353
+ incrementalContext ,
1354
+ deferMap ,
1355
+ ) ,
1356
+ ) ;
1357
+ containsPromise = true ;
1358
+ } else if (
1359
+ completeListItemValue (
1360
+ item ,
1361
+ completedResults ,
1362
+ graphqlWrappedResult ,
1363
+ exeContext ,
1364
+ itemType ,
1365
+ fieldGroup ,
1366
+ info ,
1367
+ itemPath ,
1368
+ incrementalContext ,
1369
+ deferMap ,
1370
+ )
1371
+ ) {
1372
+ containsPromise = true ;
1373
+ }
1374
+ index ++ ;
1375
+ }
1376
+
1377
+ return containsPromise
1378
+ ? Promise . all ( completedResults ) . then ( ( resolved ) => [
1379
+ resolved ,
1380
+ graphqlWrappedResult [ 1 ] ,
1381
+ ] )
1382
+ : graphqlWrappedResult ;
1383
+ }
1384
+
1385
+ function completeIterableValueWithPossibleStream (
1386
+ exeContext : ExecutionContext ,
1387
+ itemType : GraphQLOutputType ,
1388
+ fieldGroup : FieldGroup ,
1389
+ info : GraphQLResolveInfo ,
1390
+ path : Path ,
1391
+ items : Iterable < unknown > ,
1392
+ streamUsage : StreamUsage ,
1393
+ incrementalContext : IncrementalContext | undefined ,
1394
+ deferMap : ReadonlyMap < DeferUsage , DeferredFragmentRecord > | undefined ,
1395
+ ) : PromiseOrValue < GraphQLWrappedResult < ReadonlyArray < unknown > > > {
1396
+ // This is specified as a simple map, however we're optimizing the path
1397
+ // where the list contains no Promises by avoiding creating another Promise.
1398
+ let containsPromise = false ;
1399
+ const completedResults : Array < unknown > = [ ] ;
1400
+ const graphqlWrappedResult : GraphQLWrappedResult < Array < unknown > > = [
1401
+ completedResults ,
1402
+ [ ] ,
1403
+ ] ;
1404
+ let index = 0 ;
1223
1405
const iterator = items [ Symbol . iterator ] ( ) ;
1224
1406
let iteration = iterator . next ( ) ;
1225
1407
while ( ! iteration . done ) {
1226
1408
const item = iteration . value ;
1227
1409
1228
- if ( streamUsage && index >= streamUsage . initialCount ) {
1410
+ if ( index >= streamUsage . initialCount ) {
1229
1411
const streamRecord : SubsequentResultRecord = {
1230
1412
label : streamUsage . label ,
1231
1413
path,
0 commit comments