Skip to content

Commit c675033

Browse files
committed
hoist depth out of TaggedFieldNode into FieldGroup
1 parent cdbfbde commit c675033

File tree

5 files changed

+61
-39
lines changed

5 files changed

+61
-39
lines changed

src/execution/__tests__/executor-test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ describe('Execute: Handles basic execution tasks', () => {
241241

242242
const fieldNode = operation.selectionSet.selections[0];
243243
expect(resolvedInfo).to.deep.include({
244-
fieldGroup: [{ fieldNode, depth: 0, deferDepth: undefined }],
244+
fieldGroup: { depth: 0, fields: [{ fieldNode, deferDepth: undefined }] },
245245
deferDepth: undefined,
246246
variableValues: { var: 'abc' },
247247
});

src/execution/collectFields.ts

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { AccumulatorMap } from '../jsutils/AccumulatorMap.js';
21
import { invariant } from '../jsutils/invariant.js';
32
import type { ObjMap } from '../jsutils/ObjMap.js';
43

@@ -26,28 +25,38 @@ import { typeFromAST } from '../utilities/typeFromAST.js';
2625

2726
import { getDirectiveValues } from './values.js';
2827

29-
export type FieldGroup = ReadonlyArray<TaggedFieldNode>;
30-
31-
export type GroupedFieldSet = Map<string, FieldGroup>;
32-
3328
/**
34-
* A tagged field node includes metadata necessary to determine whether a field should
35-
* be executed.
29+
* A field group is a list of fields with the same response key.
3630
*
37-
* A field's depth is equivalent to the number of fields between the given field and
31+
* The group's depth is equivalent to the number of fields between the group and
3832
* the operation root. For example, root fields have a depth of 0, their sub-fields
39-
* have a depth of 1, and so on. Tagging fields with their depth is necessary only to
40-
* compute a field's "defer depth".
33+
* have a depth of 1, and so on.
4134
*
42-
* A field's defer depth is the depth of the closest containing defer directive , or
43-
* undefined, if the field is not contained by a deferred fragment.
35+
* The groups's depth is provided so that CollectField algorithm can compute the
36+
* depth of an inline or named fragment with a defer directive.
37+
*/
38+
export interface FieldGroup {
39+
depth: number;
40+
fields: ReadonlyArray<TaggedFieldNode>;
41+
}
42+
43+
export type GroupedFieldSet = Map<string, FieldGroup>;
44+
45+
interface MutableFieldSet {
46+
depth: number;
47+
fields: Array<TaggedFieldNode>;
48+
}
49+
50+
/**
51+
* A tagged field node includes the depth of any enclosing defer directive, or
52+
* undefined, if the field is not contained within a deferred fragment.
4453
*
45-
* Because deferred fragments at a given level are merged, the defer depth may be used
46-
* as a unique id to tag the fields for inclusion within a given deferred payload.
54+
* Because deferred fragments at a given level are merged, the defer depth for a
55+
* field node may be used as a unique id to tag the fields for inclusion within a
56+
* given deferred payload.
4757
*/
4858
export interface TaggedFieldNode {
4959
fieldNode: FieldNode;
50-
depth: number;
5160
deferDepth: number | undefined;
5261
}
5362

@@ -70,7 +79,7 @@ export function collectFields(
7079
groupedFieldSet: GroupedFieldSet;
7180
newDeferDepth: number | undefined;
7281
} {
73-
const groupedFieldSet = new AccumulatorMap<string, TaggedFieldNode>();
82+
const groupedFieldSet = new Map<string, MutableFieldSet>();
7483
const newDeferDepth = collectFieldsImpl(
7584
schema,
7685
fragments,
@@ -111,11 +120,11 @@ export function collectSubfields(
111120
groupedFieldSet: GroupedFieldSet;
112121
newDeferDepth: number | undefined;
113122
} {
114-
const groupedFieldSet = new AccumulatorMap<string, TaggedFieldNode>();
123+
const groupedFieldSet = new Map<string, MutableFieldSet>();
115124
let newDeferDepth: number | undefined;
116125
const visitedFragmentNames = new Set<string>();
117126

118-
for (const field of fieldGroup) {
127+
for (const field of fieldGroup.fields) {
119128
if (field.fieldNode.selectionSet) {
120129
const nestedNewDeferDepth = collectFieldsImpl(
121130
schema,
@@ -126,7 +135,7 @@ export function collectSubfields(
126135
field.fieldNode.selectionSet,
127136
groupedFieldSet,
128137
visitedFragmentNames,
129-
fieldGroup[0].depth + 1,
138+
fieldGroup.depth + 1,
130139
field.deferDepth,
131140
);
132141
if (nestedNewDeferDepth !== undefined) {
@@ -149,7 +158,7 @@ function collectFieldsImpl(
149158
operation: OperationDefinitionNode,
150159
runtimeType: GraphQLObjectType,
151160
selectionSet: SelectionSetNode,
152-
groupedFieldSet: AccumulatorMap<string, TaggedFieldNode>,
161+
groupedFieldSet: Map<string, MutableFieldSet>,
153162
visitedFragmentNames: Set<string>,
154163
depth: number,
155164
deferDepth: number | undefined,
@@ -161,11 +170,19 @@ function collectFieldsImpl(
161170
if (!shouldIncludeNode(variableValues, selection)) {
162171
continue;
163172
}
164-
groupedFieldSet.add(getFieldEntryKey(selection), {
165-
fieldNode: selection,
166-
depth,
167-
deferDepth,
168-
});
173+
const key = getFieldEntryKey(selection);
174+
const fieldSet = groupedFieldSet.get(key);
175+
if (fieldSet) {
176+
fieldSet.fields.push({
177+
fieldNode: selection,
178+
deferDepth,
179+
});
180+
} else {
181+
groupedFieldSet.set(key, {
182+
depth,
183+
fields: [{ fieldNode: selection, deferDepth }],
184+
});
185+
}
169186
break;
170187
}
171188
case Kind.INLINE_FRAGMENT: {

src/execution/execute.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -660,7 +660,7 @@ function shouldExecute(
660660
fieldGroup: FieldGroup,
661661
deferDepth?: number | undefined,
662662
): boolean {
663-
return fieldGroup.some(
663+
return fieldGroup.fields.some(
664664
({ deferDepth: fieldDeferDepth }) => fieldDeferDepth === deferDepth,
665665
);
666666
}
@@ -724,7 +724,7 @@ function executeFields(
724724
}
725725

726726
function toNodes(fieldGroup: FieldGroup): ReadonlyArray<FieldNode> {
727-
return fieldGroup.map(({ fieldNode }) => fieldNode);
727+
return fieldGroup.fields.map(({ fieldNode }) => fieldNode);
728728
}
729729
/**
730730
* Implements the "Executing fields" section of the spec
@@ -741,7 +741,7 @@ function executeField(
741741
asyncPayloadRecord?: AsyncPayloadRecord,
742742
): PromiseOrValue<unknown> {
743743
const errors = asyncPayloadRecord?.errors ?? exeContext.errors;
744-
const fieldName = fieldGroup[0].fieldNode.name.value;
744+
const fieldName = fieldGroup.fields[0].fieldNode.name.value;
745745
const fieldDef = exeContext.schema.getField(parentType, fieldName);
746746
if (!fieldDef) {
747747
return;
@@ -766,7 +766,7 @@ function executeField(
766766
// TODO: find a way to memoize, in case this field is within a List type.
767767
const args = getArgumentValues(
768768
fieldDef,
769-
fieldGroup[0].fieldNode,
769+
fieldGroup.fields[0].fieldNode,
770770
exeContext.variableValues,
771771
);
772772

@@ -1045,7 +1045,7 @@ function getStreamValues(
10451045
// safe to only check the first fieldNode for the stream directive
10461046
const stream = getDirectiveValues(
10471047
GraphQLStreamDirective,
1048-
fieldGroup[0].fieldNode,
1048+
fieldGroup.fields[0].fieldNode,
10491049
exeContext.variableValues,
10501050
);
10511051

@@ -1780,7 +1780,7 @@ function executeSubscription(
17801780
FieldGroup,
17811781
];
17821782
const [responseName, fieldGroup] = firstRootField;
1783-
const fieldName = fieldGroup[0].fieldNode.name.value;
1783+
const fieldName = fieldGroup.fields[0].fieldNode.name.value;
17841784
const fieldDef = schema.getField(rootType, fieldName);
17851785

17861786
if (!fieldDef) {
@@ -1807,7 +1807,7 @@ function executeSubscription(
18071807
// variables scope to fulfill any variable references.
18081808
const args = getArgumentValues(
18091809
fieldDef,
1810-
fieldGroup[0].fieldNode,
1810+
fieldGroup.fields[0].fieldNode,
18111811
variableValues,
18121812
);
18131813

src/type/definition.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -890,11 +890,13 @@ export type GraphQLFieldResolver<
890890

891891
export interface GraphQLResolveInfo {
892892
readonly fieldName: string;
893-
readonly fieldGroup: ReadonlyArray<{
894-
fieldNode: FieldNode;
893+
readonly fieldGroup: {
895894
depth: number;
896-
deferDepth: number | undefined;
897-
}>;
895+
fields: ReadonlyArray<{
896+
fieldNode: FieldNode;
897+
deferDepth: number | undefined;
898+
}>;
899+
};
898900
readonly deferDepth: number | undefined;
899901
readonly returnType: GraphQLOutputType;
900902
readonly parentType: GraphQLObjectType;

src/validation/rules/SingleFieldSubscriptionsRule.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export function SingleFieldSubscriptionsRule(
5252
const fieldGroups = [...groupedFieldSet.values()];
5353
const extraFieldGroups = fieldGroups.slice(1);
5454
const extraFields = extraFieldGroups
55+
.map(({ fields }) => fields)
5556
.flat()
5657
.map(({ fieldNode }) => fieldNode);
5758
context.reportError(
@@ -63,8 +64,10 @@ export function SingleFieldSubscriptionsRule(
6364
),
6465
);
6566
}
66-
for (const fieldSet of groupedFieldSet.values()) {
67-
const fieldNodes = fieldSet.map(({ fieldNode }) => fieldNode);
67+
for (const fieldGroup of groupedFieldSet.values()) {
68+
const fieldNodes = fieldGroup.fields.map(
69+
({ fieldNode }) => fieldNode,
70+
);
6871
const fieldName = fieldNodes[0].name.value;
6972
if (fieldName.startsWith('__')) {
7073
context.reportError(

0 commit comments

Comments
 (0)