@@ -2,6 +2,7 @@ import { assert } from 'chai';
2
2
import { describe , it } from 'mocha' ;
3
3
4
4
import { expectJSON } from '../../__testUtils__/expectJSON.js' ;
5
+ import { resolveOnNextTick } from '../../__testUtils__/resolveOnNextTick.js' ;
5
6
6
7
import type { PromiseOrValue } from '../../jsutils/PromiseOrValue.js' ;
7
8
@@ -1134,7 +1135,7 @@ describe('Execute: stream directive', () => {
1134
1135
} ,
1135
1136
] ) ;
1136
1137
} ) ;
1137
- it ( 'Handles async errors thrown by completeValue after initialCount is reached from async iterable for a non-nullable list' , async ( ) => {
1138
+ it ( 'Handles async errors thrown by completeValue after initialCount is reached from async generator for a non-nullable list' , async ( ) => {
1138
1139
const document = parse ( `
1139
1140
query {
1140
1141
nonNullFriendList @stream(initialCount: 1) {
@@ -1181,6 +1182,158 @@ describe('Execute: stream directive', () => {
1181
1182
} ,
1182
1183
] ) ;
1183
1184
} ) ;
1185
+ it ( 'Handles async errors thrown by completeValue after initialCount is reached from async iterable for a non-nullable list when the async iterable does not provide a return method) ' , async ( ) => {
1186
+ const document = parse ( `
1187
+ query {
1188
+ nonNullFriendList @stream(initialCount: 1) {
1189
+ nonNullName
1190
+ }
1191
+ }
1192
+ ` ) ;
1193
+ let count = 0 ;
1194
+ const result = await complete ( document , {
1195
+ nonNullFriendList : {
1196
+ [ Symbol . asyncIterator ] : ( ) => ( {
1197
+ next : async ( ) => {
1198
+ switch ( count ++ ) {
1199
+ case 0 :
1200
+ return Promise . resolve ( {
1201
+ done : false ,
1202
+ value : { nonNullName : friends [ 0 ] . name } ,
1203
+ } ) ;
1204
+ case 1 :
1205
+ return Promise . resolve ( {
1206
+ done : false ,
1207
+ value : {
1208
+ nonNullName : ( ) => Promise . reject ( new Error ( 'Oops' ) ) ,
1209
+ } ,
1210
+ } ) ;
1211
+ case 2 :
1212
+ return Promise . resolve ( {
1213
+ done : false ,
1214
+ value : { nonNullName : friends [ 1 ] . name } ,
1215
+ } ) ;
1216
+ // Not reached
1217
+ /* c8 ignore next 5 */
1218
+ case 3 :
1219
+ return Promise . resolve ( {
1220
+ done : false ,
1221
+ value : { nonNullName : friends [ 2 ] . name } ,
1222
+ } ) ;
1223
+ }
1224
+ } ,
1225
+ } ) ,
1226
+ } ,
1227
+ } ) ;
1228
+ expectJSON ( result ) . toDeepEqual ( [
1229
+ {
1230
+ data : {
1231
+ nonNullFriendList : [ { nonNullName : 'Luke' } ] ,
1232
+ } ,
1233
+ hasNext : true ,
1234
+ } ,
1235
+ {
1236
+ incremental : [
1237
+ {
1238
+ items : null ,
1239
+ path : [ 'nonNullFriendList' , 1 ] ,
1240
+ errors : [
1241
+ {
1242
+ message : 'Oops' ,
1243
+ locations : [ { line : 4 , column : 11 } ] ,
1244
+ path : [ 'nonNullFriendList' , 1 , 'nonNullName' ] ,
1245
+ } ,
1246
+ ] ,
1247
+ } ,
1248
+ ] ,
1249
+ hasNext : true ,
1250
+ } ,
1251
+ {
1252
+ hasNext : false ,
1253
+ } ,
1254
+ ] ) ;
1255
+ } ) ;
1256
+ it ( 'Handles async errors thrown by completeValue after initialCount is reached from async iterable for a non-nullable list when the async iterable provides concurrent next/return methods and has a slow return ' , async ( ) => {
1257
+ const document = parse ( `
1258
+ query {
1259
+ nonNullFriendList @stream(initialCount: 1) {
1260
+ nonNullName
1261
+ }
1262
+ }
1263
+ ` ) ;
1264
+ let count = 0 ;
1265
+ let returned = false ;
1266
+ const result = await complete ( document , {
1267
+ nonNullFriendList : {
1268
+ [ Symbol . asyncIterator ] : ( ) => ( {
1269
+ next : async ( ) => {
1270
+ /* c8 ignore next 3 */
1271
+ if ( returned ) {
1272
+ return Promise . resolve ( { done : true } ) ;
1273
+ }
1274
+ switch ( count ++ ) {
1275
+ case 0 :
1276
+ return Promise . resolve ( {
1277
+ done : false ,
1278
+ value : { nonNullName : friends [ 0 ] . name } ,
1279
+ } ) ;
1280
+ case 1 :
1281
+ return Promise . resolve ( {
1282
+ done : false ,
1283
+ value : {
1284
+ nonNullName : ( ) => Promise . reject ( new Error ( 'Oops' ) ) ,
1285
+ } ,
1286
+ } ) ;
1287
+ case 2 :
1288
+ return Promise . resolve ( {
1289
+ done : false ,
1290
+ value : { nonNullName : friends [ 1 ] . name } ,
1291
+ } ) ;
1292
+ // Not reached
1293
+ /* c8 ignore next 5 */
1294
+ case 3 :
1295
+ return Promise . resolve ( {
1296
+ done : false ,
1297
+ value : { nonNullName : friends [ 2 ] . name } ,
1298
+ } ) ;
1299
+ }
1300
+ } ,
1301
+ return : async ( ) => {
1302
+ await resolveOnNextTick ( ) ;
1303
+ returned = true ;
1304
+ return { done : true } ;
1305
+ } ,
1306
+ } ) ,
1307
+ } ,
1308
+ } ) ;
1309
+ expectJSON ( result ) . toDeepEqual ( [
1310
+ {
1311
+ data : {
1312
+ nonNullFriendList : [ { nonNullName : 'Luke' } ] ,
1313
+ } ,
1314
+ hasNext : true ,
1315
+ } ,
1316
+ {
1317
+ incremental : [
1318
+ {
1319
+ items : null ,
1320
+ path : [ 'nonNullFriendList' , 1 ] ,
1321
+ errors : [
1322
+ {
1323
+ message : 'Oops' ,
1324
+ locations : [ { line : 4 , column : 11 } ] ,
1325
+ path : [ 'nonNullFriendList' , 1 , 'nonNullName' ] ,
1326
+ } ,
1327
+ ] ,
1328
+ } ,
1329
+ ] ,
1330
+ hasNext : true ,
1331
+ } ,
1332
+ {
1333
+ hasNext : false ,
1334
+ } ,
1335
+ ] ) ;
1336
+ } ) ;
1184
1337
it ( 'Filters payloads that are nulled' , async ( ) => {
1185
1338
const document = parse ( `
1186
1339
query {
@@ -1359,9 +1512,6 @@ describe('Execute: stream directive', () => {
1359
1512
] ,
1360
1513
} ,
1361
1514
] ,
1362
- hasNext : true ,
1363
- } ,
1364
- {
1365
1515
hasNext : false ,
1366
1516
} ,
1367
1517
] ) ;
@@ -1421,10 +1571,11 @@ describe('Execute: stream directive', () => {
1421
1571
const iterable = {
1422
1572
[ Symbol . asyncIterator ] : ( ) => ( {
1423
1573
next : ( ) => {
1574
+ /* c8 ignore start */
1424
1575
if ( requested ) {
1425
- // Ignores further errors when filtered .
1576
+ // stream is filtered, next is not called, and so this is not reached .
1426
1577
return Promise . reject ( new Error ( 'Oops' ) ) ;
1427
- }
1578
+ } /* c8 ignore stop */
1428
1579
requested = true ;
1429
1580
const friend = friends [ 0 ] ;
1430
1581
return Promise . resolve ( {
@@ -1563,6 +1714,70 @@ describe('Execute: stream directive', () => {
1563
1714
} ,
1564
1715
] ) ;
1565
1716
} ) ;
1717
+ it ( 'Handles overlapping deferred and non-deferred streams' , async ( ) => {
1718
+ const document = parse ( `
1719
+ query {
1720
+ nestedObject {
1721
+ nestedFriendList @stream(initialCount: 0) {
1722
+ id
1723
+ }
1724
+ }
1725
+ nestedObject {
1726
+ ... @defer {
1727
+ nestedFriendList @stream(initialCount: 0) {
1728
+ id
1729
+ name
1730
+ }
1731
+ }
1732
+ }
1733
+ }
1734
+ ` ) ;
1735
+ const result = await complete ( document , {
1736
+ nestedObject : {
1737
+ async * nestedFriendList ( ) {
1738
+ yield await Promise . resolve ( friends [ 0 ] ) ;
1739
+ yield await Promise . resolve ( friends [ 1 ] ) ;
1740
+ } ,
1741
+ } ,
1742
+ } ) ;
1743
+ expectJSON ( result ) . toDeepEqual ( [
1744
+ {
1745
+ data : {
1746
+ nestedObject : {
1747
+ nestedFriendList : [ ] ,
1748
+ } ,
1749
+ } ,
1750
+ hasNext : true ,
1751
+ } ,
1752
+ {
1753
+ incremental : [
1754
+ {
1755
+ data : {
1756
+ nestedFriendList : [ ] ,
1757
+ } ,
1758
+ path : [ 'nestedObject' ] ,
1759
+ } ,
1760
+ {
1761
+ items : [ { id : '1' , name : 'Luke' } ] ,
1762
+ path : [ 'nestedObject' , 'nestedFriendList' , 0 ] ,
1763
+ } ,
1764
+ ] ,
1765
+ hasNext : true ,
1766
+ } ,
1767
+ {
1768
+ incremental : [
1769
+ {
1770
+ items : [ { id : '2' , name : 'Han' } ] ,
1771
+ path : [ 'nestedObject' , 'nestedFriendList' , 1 ] ,
1772
+ } ,
1773
+ ] ,
1774
+ hasNext : true ,
1775
+ } ,
1776
+ {
1777
+ hasNext : false ,
1778
+ } ,
1779
+ ] ) ;
1780
+ } ) ;
1566
1781
it ( 'Returns payloads in correct order when parent deferred fragment resolves slower than stream' , async ( ) => {
1567
1782
const [ slowFieldPromise , resolveSlowField ] = createResolvablePromise ( ) ;
1568
1783
const document = parse ( `
0 commit comments