Skip to content

Commit 6f1d0d9

Browse files
committed
remove streamUsage from FieldGroup
1 parent 039775a commit 6f1d0d9

File tree

2 files changed

+69
-94
lines changed

2 files changed

+69
-94
lines changed

src/execution/collectFields.ts

-50
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import {
1919
GraphQLDeferDirective,
2020
GraphQLIncludeDirective,
2121
GraphQLSkipDirective,
22-
GraphQLStreamDirective,
2322
} from '../type/directives.js';
2423
import type { GraphQLSchema } from '../type/schema.js';
2524

@@ -31,19 +30,12 @@ export interface DeferUsage {
3130
label: string | undefined;
3231
}
3332

34-
// initialCount is validated during field execution
35-
export interface PreValidatedStreamUsage {
36-
label: string | undefined;
37-
initialCount: unknown;
38-
}
39-
4033
export interface FieldGroup {
4134
parentType: GraphQLObjectType;
4235
fieldName: string;
4336
fields: Map<DeferUsage | undefined, ReadonlyArray<FieldNode>>;
4437
inInitialResult: boolean;
4538
shouldInitiateDefer: boolean;
46-
streamUsage: PreValidatedStreamUsage | undefined;
4739
}
4840
interface MutableFieldGroup extends FieldGroup {
4941
fields: AccumulatorMap<DeferUsage | undefined, FieldNode>;
@@ -180,8 +172,6 @@ function collectFieldsImpl(
180172
fieldGroup.shouldInitiateDefer = false;
181173
}
182174
} else {
183-
const stream = getStreamValues(variableValues, selection);
184-
185175
const fields = new AccumulatorMap<
186176
DeferUsage | undefined,
187177
FieldNode
@@ -203,7 +193,6 @@ function collectFieldsImpl(
203193
fields,
204194
inInitialResult,
205195
shouldInitiateDefer,
206-
streamUsage: stream,
207196
});
208197
}
209198
break;
@@ -380,45 +369,6 @@ function getDeferValues(
380369
};
381370
}
382371

383-
/**
384-
* Returns an object containing the `@stream` arguments if a field should be
385-
* streamed based on the experimental flag, stream directive present and
386-
* not disabled by the "if" argument.
387-
*
388-
* We validate `initialCount` argument later so as to use the correct path
389-
* if an error occurs.
390-
*/
391-
function getStreamValues(
392-
variableValues: { [variable: string]: unknown },
393-
node: FieldNode,
394-
):
395-
| undefined
396-
| {
397-
initialCount: unknown;
398-
label: string | undefined;
399-
} {
400-
// validation only allows equivalent streams on multiple fields, so it is
401-
// safe to only check the first fieldNode for the stream directive
402-
const stream = getDirectiveValues(
403-
GraphQLStreamDirective,
404-
node,
405-
variableValues,
406-
);
407-
408-
if (!stream) {
409-
return;
410-
}
411-
412-
if (stream.if === false) {
413-
return;
414-
}
415-
416-
return {
417-
initialCount: stream.initialCount,
418-
label: typeof stream.label === 'string' ? stream.label : undefined,
419-
};
420-
}
421-
422372
/**
423373
* Determines if a field should be included based on the `@include` and `@skip`
424374
* directives, where `@skip` has higher precedence than `@include`.

src/execution/execute.ts

+69-44
Original file line numberDiff line numberDiff line change
@@ -46,21 +46,25 @@ import {
4646
isNullableType,
4747
isObjectType,
4848
} from '../type/definition.js';
49+
import { GraphQLStreamDirective } from '../type/directives.js';
4950
import type { GraphQLSchema } from '../type/schema.js';
5051
import { assertValidSchema } from '../type/validate.js';
5152

5253
import type {
5354
DeferUsage,
5455
FieldGroup,
5556
GroupedFieldSet,
56-
PreValidatedStreamUsage,
5757
} from './collectFields.js';
5858
import {
5959
collectFields,
6060
collectSubfields as _collectSubfields,
6161
} from './collectFields.js';
6262
import { mapAsyncIterable } from './mapAsyncIterable.js';
63-
import { getArgumentValues, getVariableValues } from './values.js';
63+
import {
64+
getArgumentValues,
65+
getDirectiveValues,
66+
getVariableValues,
67+
} from './values.js';
6468

6569
/* eslint-disable max-params */
6670
// This file contains a lot of such errors but we plan to refactor it anyway
@@ -267,10 +271,17 @@ export interface ExecutionArgs {
267271
subscribeFieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
268272
}
269273

270-
export interface ValidatedStreamUsage extends PreValidatedStreamUsage {
274+
export interface StreamUsage {
275+
label: string | undefined;
271276
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+
}
274285
}
275286

276287
const UNEXPECTED_EXPERIMENTAL_DIRECTIVES =
@@ -1279,41 +1290,78 @@ async function completePromisedValue(
12791290
}
12801291

12811292
/**
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.
12831296
*/
1284-
function getValidatedStreamUsage(
1297+
function getStreamUsage(
12851298
exeContext: ExecutionContext,
12861299
fieldGroup: FieldGroup,
12871300
path: Path<FieldGroup>,
1288-
): ValidatedStreamUsage | undefined {
1301+
): StreamUsage | undefined {
12891302
// do not stream inner lists of multi-dimensional lists
12901303
if (typeof path.key === 'number') {
12911304
return;
12921305
}
12931306

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+
);
12951320

1296-
if (!streamUsage) {
1321+
if (!stream) {
12971322
return;
12981323
}
12991324

1300-
const { label, initialCount } = streamUsage;
1325+
if (stream.if === false) {
1326+
return;
1327+
}
13011328

1302-
invariant(typeof initialCount === 'number', 'initialCount must be a number');
1329+
invariant(
1330+
typeof stream.initialCount === 'number',
1331+
'initialCount must be a number',
1332+
);
13031333

1304-
invariant(initialCount >= 0, 'initialCount must be a positive integer');
1334+
invariant(
1335+
stream.initialCount >= 0,
1336+
'initialCount must be a positive integer',
1337+
);
13051338

13061339
invariant(
13071340
exeContext.operation.operation !== OperationTypeNode.SUBSCRIPTION,
13081341
'`@stream` directive not supported on subscription operations. Disable `@stream` by setting the `if` argument to `false`.',
13091342
);
13101343

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,
13141353
};
1315-
}
13161354

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+
}
13171365
/**
13181366
* Complete a async iterator value by completing the result and calling
13191367
* recursively until all the results are completed.
@@ -1329,7 +1377,7 @@ async function completeAsyncIteratorValue(
13291377
streamRecord: StreamRecord | undefined,
13301378
parentRecords: Array<AsyncPayloadRecord> | undefined,
13311379
): Promise<ReadonlyArray<unknown>> {
1332-
const streamUsage = getValidatedStreamUsage(exeContext, fieldGroup, path);
1380+
const streamUsage = getStreamUsage(exeContext, fieldGroup, path);
13331381
let containsPromise = false;
13341382
const completedResults: Array<unknown> = [];
13351383
let index = 0;
@@ -1347,7 +1395,7 @@ async function completeAsyncIteratorValue(
13471395
index,
13481396
iterator,
13491397
exeContext,
1350-
getStreamedFieldGroup(fieldGroup, streamUsage),
1398+
streamUsage.fieldGroup,
13511399
info,
13521400
itemType,
13531401
path,
@@ -1406,29 +1454,6 @@ async function completeAsyncIteratorValue(
14061454
return containsPromise ? Promise.all(completedResults) : completedResults;
14071455
}
14081456

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-
14321457
/**
14331458
* Complete a list value by completing each item in the list with the
14341459
* inner type
@@ -1468,7 +1493,7 @@ function completeListValue(
14681493
);
14691494
}
14701495

1471-
const streamUsage = getValidatedStreamUsage(exeContext, fieldGroup, path);
1496+
const streamUsage = getStreamUsage(exeContext, fieldGroup, path);
14721497

14731498
// This is specified as a simple map, however we're optimizing the path
14741499
// where the list contains no Promises by avoiding creating another Promise.
@@ -1494,7 +1519,7 @@ function completeListValue(
14941519
itemPath,
14951520
item,
14961521
exeContext,
1497-
getStreamedFieldGroup(fieldGroup, streamUsage),
1522+
streamUsage.fieldGroup,
14981523
info,
14991524
itemType,
15001525
streamContext,

0 commit comments

Comments
 (0)