@@ -46,21 +46,25 @@ import {
46
46
isNullableType ,
47
47
isObjectType ,
48
48
} from '../type/definition.js' ;
49
+ import { GraphQLStreamDirective } from '../type/directives.js' ;
49
50
import type { GraphQLSchema } from '../type/schema.js' ;
50
51
import { assertValidSchema } from '../type/validate.js' ;
51
52
52
53
import type {
53
54
DeferUsage ,
54
55
FieldGroup ,
55
56
GroupedFieldSet ,
56
- PreValidatedStreamUsage ,
57
57
} from './collectFields.js' ;
58
58
import {
59
59
collectFields ,
60
60
collectSubfields as _collectSubfields ,
61
61
} from './collectFields.js' ;
62
62
import { mapAsyncIterable } from './mapAsyncIterable.js' ;
63
- import { getArgumentValues , getVariableValues } from './values.js' ;
63
+ import {
64
+ getArgumentValues ,
65
+ getDirectiveValues ,
66
+ getVariableValues ,
67
+ } from './values.js' ;
64
68
65
69
/* eslint-disable max-params */
66
70
// This file contains a lot of such errors but we plan to refactor it anyway
@@ -267,10 +271,17 @@ export interface ExecutionArgs {
267
271
subscribeFieldResolver ?: Maybe < GraphQLFieldResolver < any , any > > ;
268
272
}
269
273
270
- export interface ValidatedStreamUsage extends PreValidatedStreamUsage {
274
+ export interface StreamUsage {
275
+ label : string | undefined ;
271
276
initialCount : number ;
272
- // for memoization of the streamed field's FieldGroup
273
- _fieldGroup ?: FieldGroup | undefined ;
277
+ fieldGroup : FieldGroup ;
278
+ }
279
+
280
+ declare module './collectFields.js' {
281
+ interface FieldGroup {
282
+ // for memoization
283
+ _streamUsage ?: StreamUsage | undefined ;
284
+ }
274
285
}
275
286
276
287
const UNEXPECTED_EXPERIMENTAL_DIRECTIVES =
@@ -1279,41 +1290,78 @@ async function completePromisedValue(
1279
1290
}
1280
1291
1281
1292
/**
1282
- * Returns an object containing the validated `@stream` arguments.
1293
+ * Returns an object containing info for streaming if a field should be
1294
+ * streamed based on the experimental flag, stream directive present and
1295
+ * not disabled by the "if" argument.
1283
1296
*/
1284
- function getValidatedStreamUsage (
1297
+ function getStreamUsage (
1285
1298
exeContext : ExecutionContext ,
1286
1299
fieldGroup : FieldGroup ,
1287
1300
path : Path < FieldGroup > ,
1288
- ) : ValidatedStreamUsage | undefined {
1301
+ ) : StreamUsage | undefined {
1289
1302
// do not stream inner lists of multi-dimensional lists
1290
1303
if ( typeof path . key === 'number' ) {
1291
1304
return ;
1292
1305
}
1293
1306
1294
- const streamUsage = fieldGroup . streamUsage ;
1307
+ // TODO: add test for this case (a streamed list nested under a list).
1308
+ /* c8 ignore next 3 */
1309
+ if ( fieldGroup . _streamUsage !== undefined ) {
1310
+ return fieldGroup . _streamUsage ;
1311
+ }
1312
+
1313
+ // validation only allows equivalent streams on multiple fields, so it is
1314
+ // safe to only check the first fieldNode for the stream directive
1315
+ const stream = getDirectiveValues (
1316
+ GraphQLStreamDirective ,
1317
+ toNodes ( fieldGroup ) [ 0 ] ,
1318
+ exeContext . variableValues ,
1319
+ ) ;
1295
1320
1296
- if ( ! streamUsage ) {
1321
+ if ( ! stream ) {
1297
1322
return ;
1298
1323
}
1299
1324
1300
- const { label, initialCount } = streamUsage ;
1325
+ if ( stream . if === false ) {
1326
+ return ;
1327
+ }
1301
1328
1302
- invariant ( typeof initialCount === 'number' , 'initialCount must be a number' ) ;
1329
+ invariant (
1330
+ typeof stream . initialCount === 'number' ,
1331
+ 'initialCount must be a number' ,
1332
+ ) ;
1303
1333
1304
- invariant ( initialCount >= 0 , 'initialCount must be a positive integer' ) ;
1334
+ invariant (
1335
+ stream . initialCount >= 0 ,
1336
+ 'initialCount must be a positive integer' ,
1337
+ ) ;
1305
1338
1306
1339
invariant (
1307
1340
exeContext . operation . operation !== OperationTypeNode . SUBSCRIPTION ,
1308
1341
'`@stream` directive not supported on subscription operations. Disable `@stream` by setting the `if` argument to `false`.' ,
1309
1342
) ;
1310
1343
1311
- return {
1312
- label : typeof label === 'string' ? label : undefined ,
1313
- initialCount,
1344
+ const streamFields = new AccumulatorMap < DeferUsage | undefined , FieldNode > ( ) ;
1345
+ for ( const [ , fieldNodes ] of fieldGroup . fields ) {
1346
+ for ( const node of fieldNodes ) {
1347
+ streamFields . add ( undefined , node ) ;
1348
+ }
1349
+ }
1350
+ const streamedFieldGroup : FieldGroup = {
1351
+ ...fieldGroup ,
1352
+ fields : streamFields ,
1314
1353
} ;
1315
- }
1316
1354
1355
+ const streamUsage = {
1356
+ initialCount : stream . initialCount ,
1357
+ label : typeof stream . label === 'string' ? stream . label : undefined ,
1358
+ fieldGroup : streamedFieldGroup ,
1359
+ } ;
1360
+
1361
+ fieldGroup . _streamUsage = streamUsage ;
1362
+
1363
+ return streamUsage ;
1364
+ }
1317
1365
/**
1318
1366
* Complete a async iterator value by completing the result and calling
1319
1367
* recursively until all the results are completed.
@@ -1329,7 +1377,7 @@ async function completeAsyncIteratorValue(
1329
1377
streamRecord : StreamRecord | undefined ,
1330
1378
parentRecords : Array < AsyncPayloadRecord > | undefined ,
1331
1379
) : Promise < ReadonlyArray < unknown > > {
1332
- const streamUsage = getValidatedStreamUsage ( exeContext , fieldGroup , path ) ;
1380
+ const streamUsage = getStreamUsage ( exeContext , fieldGroup , path ) ;
1333
1381
let containsPromise = false ;
1334
1382
const completedResults : Array < unknown > = [ ] ;
1335
1383
let index = 0 ;
@@ -1347,7 +1395,7 @@ async function completeAsyncIteratorValue(
1347
1395
index ,
1348
1396
iterator ,
1349
1397
exeContext ,
1350
- getStreamedFieldGroup ( fieldGroup , streamUsage ) ,
1398
+ streamUsage . fieldGroup ,
1351
1399
info ,
1352
1400
itemType ,
1353
1401
path ,
@@ -1406,29 +1454,6 @@ async function completeAsyncIteratorValue(
1406
1454
return containsPromise ? Promise . all ( completedResults ) : completedResults ;
1407
1455
}
1408
1456
1409
- function getStreamedFieldGroup (
1410
- fieldGroup : FieldGroup ,
1411
- streamUsage : ValidatedStreamUsage ,
1412
- ) : FieldGroup {
1413
- // TODO: add test for this case
1414
- /* c8 ignore next 3 */
1415
- if ( streamUsage . _fieldGroup ) {
1416
- return streamUsage . _fieldGroup ;
1417
- }
1418
- const streamFields = new AccumulatorMap < DeferUsage | undefined , FieldNode > ( ) ;
1419
- for ( const [ , fieldNodes ] of fieldGroup . fields ) {
1420
- for ( const node of fieldNodes ) {
1421
- streamFields . add ( undefined , node ) ;
1422
- }
1423
- }
1424
- const streamedFieldGroup : FieldGroup = {
1425
- ...fieldGroup ,
1426
- fields : streamFields ,
1427
- } ;
1428
- streamUsage . _fieldGroup = streamedFieldGroup ;
1429
- return streamedFieldGroup ;
1430
- }
1431
-
1432
1457
/**
1433
1458
* Complete a list value by completing each item in the list with the
1434
1459
* inner type
@@ -1468,7 +1493,7 @@ function completeListValue(
1468
1493
) ;
1469
1494
}
1470
1495
1471
- const streamUsage = getValidatedStreamUsage ( exeContext , fieldGroup , path ) ;
1496
+ const streamUsage = getStreamUsage ( exeContext , fieldGroup , path ) ;
1472
1497
1473
1498
// This is specified as a simple map, however we're optimizing the path
1474
1499
// where the list contains no Promises by avoiding creating another Promise.
@@ -1494,7 +1519,7 @@ function completeListValue(
1494
1519
itemPath ,
1495
1520
item ,
1496
1521
exeContext ,
1497
- getStreamedFieldGroup ( fieldGroup , streamUsage ) ,
1522
+ streamUsage . fieldGroup ,
1498
1523
info ,
1499
1524
itemType ,
1500
1525
streamContext ,
0 commit comments