Skip to content

Commit 0f40a2e

Browse files
committed
allow inlining of defers nested under list types to vary
1 parent bc532a5 commit 0f40a2e

File tree

5 files changed

+178
-121
lines changed

5 files changed

+178
-121
lines changed

src/transform/collectFields.ts

Lines changed: 82 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { AccumulatorMap } from '../jsutils/AccumulatorMap.js';
22
import { invariant } from '../jsutils/invariant.js';
33
import type { ObjMap } from '../jsutils/ObjMap.js';
4-
import type { Path } from '../jsutils/Path.js';
5-
import { pathToArray } from '../jsutils/Path.js';
64

75
import type {
86
FieldNode,
@@ -22,7 +20,11 @@ import {
2220
} from '../type/directives.js';
2321
import type { GraphQLSchema } from '../type/schema.js';
2422

25-
import type { GraphQLVariableSignature } from '../execution/getVariableSignature.js';
23+
import type {
24+
FragmentDetails,
25+
GroupedFieldSet,
26+
} from '../execution/collectFields.js';
27+
import type { ValidatedExecutionArgs } from '../execution/execute.js';
2628
import type { VariableValues } from '../execution/values.js';
2729
import {
2830
getDirectiveValues,
@@ -31,32 +33,24 @@ import {
3133

3234
import { typeFromAST } from '../utilities/typeFromAST.js';
3335

34-
import type { TransformationContext } from './buildTransformationContext.js';
35-
3636
export interface FieldDetails {
3737
node: FieldNode;
3838
fragmentVariableValues?: VariableValues | undefined;
3939
}
4040

41-
export type FieldDetailsList = ReadonlyArray<FieldDetails>;
42-
43-
export type GroupedFieldSet = ReadonlyMap<string, FieldDetailsList>;
44-
45-
export interface FragmentDetails {
46-
definition: FragmentDefinitionNode;
47-
variableSignatures?: ObjMap<GraphQLVariableSignature> | undefined;
48-
}
49-
5041
interface CollectFieldsContext {
5142
schema: GraphQLSchema;
5243
fragments: ObjMap<FragmentDetails>;
5344
variableValues: VariableValues;
5445
runtimeType: GraphQLObjectType;
5546
visitedFragmentNames: Set<string>;
56-
pendingLabelsByPath: Map<string, Set<string>>;
5747
hideSuggestions: boolean;
5848
}
5949

50+
export interface GroupedFieldSetTree {
51+
groupedFieldSet: GroupedFieldSet;
52+
deferredGroupedFieldSets: Map<string, GroupedFieldSetTree>;
53+
}
6054
/**
6155
* Given a selectionSet, collects all of the fields and returns them.
6256
*
@@ -68,28 +62,25 @@ interface CollectFieldsContext {
6862
*/
6963

7064
export function collectFields(
71-
transformationContext: TransformationContext,
65+
validateExecutionArgs: ValidatedExecutionArgs,
7266
runtimeType: GraphQLObjectType,
7367
selectionSet: SelectionSetNode,
74-
path: Path | undefined,
75-
): GroupedFieldSet {
76-
const {
77-
transformedArgs: { schema, fragments, variableValues, hideSuggestions },
78-
pendingLabelsByPath,
79-
} = transformationContext;
80-
const groupedFieldSet = new AccumulatorMap<string, FieldDetails>();
68+
): GroupedFieldSetTree {
8169
const context: CollectFieldsContext = {
82-
schema,
83-
fragments,
84-
variableValues,
70+
...validateExecutionArgs,
8571
runtimeType,
8672
visitedFragmentNames: new Set(),
87-
pendingLabelsByPath,
88-
hideSuggestions,
8973
};
9074

91-
collectFieldsImpl(context, selectionSet, groupedFieldSet, path);
92-
return groupedFieldSet;
75+
const groupedFieldSet = new AccumulatorMap<string, FieldDetails>();
76+
const deferredGroupedFieldSets = new Map<string, GroupedFieldSetTree>();
77+
collectFieldsImpl(
78+
context,
79+
selectionSet,
80+
groupedFieldSet,
81+
deferredGroupedFieldSets,
82+
);
83+
return { groupedFieldSet, deferredGroupedFieldSets };
9384
}
9485

9586
/**
@@ -103,25 +94,17 @@ export function collectFields(
10394
* @internal
10495
*/
10596
export function collectSubfields(
106-
transformationContext: TransformationContext,
97+
validatedExecutionArgs: ValidatedExecutionArgs,
10798
returnType: GraphQLObjectType,
108-
fieldDetailsList: FieldDetailsList,
109-
path: Path | undefined,
110-
): GroupedFieldSet {
111-
const {
112-
transformedArgs: { schema, fragments, variableValues, hideSuggestions },
113-
pendingLabelsByPath,
114-
} = transformationContext;
99+
fieldDetailsList: ReadonlyArray<FieldDetails>,
100+
): GroupedFieldSetTree {
115101
const context: CollectFieldsContext = {
116-
schema,
117-
fragments,
118-
variableValues,
102+
...validatedExecutionArgs,
119103
runtimeType: returnType,
120104
visitedFragmentNames: new Set(),
121-
pendingLabelsByPath,
122-
hideSuggestions,
123105
};
124-
const subGroupedFieldSet = new AccumulatorMap<string, FieldDetails>();
106+
const groupedFieldSet = new AccumulatorMap<string, FieldDetails>();
107+
const deferredGroupedFieldSets = new Map<string, GroupedFieldSetTree>();
125108

126109
for (const fieldDetail of fieldDetailsList) {
127110
const selectionSet = fieldDetail.node.selectionSet;
@@ -130,21 +113,21 @@ export function collectSubfields(
130113
collectFieldsImpl(
131114
context,
132115
selectionSet,
133-
subGroupedFieldSet,
134-
path,
116+
groupedFieldSet,
117+
deferredGroupedFieldSets,
135118
fragmentVariableValues,
136119
);
137120
}
138121
}
139122

140-
return subGroupedFieldSet;
123+
return { groupedFieldSet, deferredGroupedFieldSets };
141124
}
142125

143126
function collectFieldsImpl(
144127
context: CollectFieldsContext,
145128
selectionSet: SelectionSetNode,
146129
groupedFieldSet: AccumulatorMap<string, FieldDetails>,
147-
path?: Path | undefined,
130+
deferredGroupedFieldSets: Map<string, GroupedFieldSetTree>,
148131
fragmentVariableValues?: VariableValues,
149132
): void {
150133
const {
@@ -153,7 +136,6 @@ function collectFieldsImpl(
153136
variableValues,
154137
runtimeType,
155138
visitedFragmentNames,
156-
pendingLabelsByPath,
157139
hideSuggestions,
158140
} = context;
159141

@@ -172,8 +154,30 @@ function collectFieldsImpl(
172154
break;
173155
}
174156
case Kind.INLINE_FRAGMENT: {
157+
const deferLabel = isDeferred(selection);
158+
if (deferLabel !== undefined) {
159+
const deferredGroupedFieldSet = new AccumulatorMap<
160+
string,
161+
FieldDetails
162+
>();
163+
const nestedDeferredGroupedFieldSets = new Map<
164+
string,
165+
GroupedFieldSetTree
166+
>();
167+
collectFieldsImpl(
168+
context,
169+
selection.selectionSet,
170+
deferredGroupedFieldSet,
171+
nestedDeferredGroupedFieldSets,
172+
);
173+
deferredGroupedFieldSets.set(deferLabel, {
174+
groupedFieldSet: deferredGroupedFieldSet,
175+
deferredGroupedFieldSets: nestedDeferredGroupedFieldSets,
176+
});
177+
continue;
178+
}
179+
175180
if (
176-
isDeferred(selection, path, pendingLabelsByPath) ||
177181
!shouldIncludeNode(
178182
selection,
179183
variableValues,
@@ -188,7 +192,7 @@ function collectFieldsImpl(
188192
context,
189193
selection.selectionSet,
190194
groupedFieldSet,
191-
path,
195+
deferredGroupedFieldSets,
192196
fragmentVariableValues,
193197
);
194198

@@ -199,7 +203,6 @@ function collectFieldsImpl(
199203

200204
if (
201205
visitedFragmentNames.has(fragName) ||
202-
isDeferred(selection, path, pendingLabelsByPath) ||
203206
!shouldIncludeNode(selection, variableValues, fragmentVariableValues)
204207
) {
205208
continue;
@@ -213,6 +216,29 @@ function collectFieldsImpl(
213216
continue;
214217
}
215218

219+
const deferLabel = isDeferred(selection);
220+
if (deferLabel !== undefined) {
221+
const deferredGroupedFieldSet = new AccumulatorMap<
222+
string,
223+
FieldDetails
224+
>();
225+
const nestedDeferredGroupedFieldSets = new Map<
226+
string,
227+
GroupedFieldSetTree
228+
>();
229+
collectFieldsImpl(
230+
context,
231+
fragment.definition.selectionSet,
232+
deferredGroupedFieldSet,
233+
nestedDeferredGroupedFieldSets,
234+
);
235+
deferredGroupedFieldSets.set(deferLabel, {
236+
groupedFieldSet: deferredGroupedFieldSet,
237+
deferredGroupedFieldSets: nestedDeferredGroupedFieldSets,
238+
});
239+
continue;
240+
}
241+
216242
const fragmentVariableSignatures = fragment.variableSignatures;
217243
let newFragmentVariableValues: VariableValues | undefined;
218244
if (fragmentVariableSignatures) {
@@ -230,7 +256,7 @@ function collectFieldsImpl(
230256
context,
231257
fragment.definition.selectionSet,
232258
groupedFieldSet,
233-
path,
259+
deferredGroupedFieldSets,
234260
newFragmentVariableValues,
235261
);
236262
break;
@@ -305,26 +331,18 @@ function getFieldEntryKey(node: FieldNode): string {
305331
*/
306332
function isDeferred(
307333
selection: FragmentSpreadNode | InlineFragmentNode,
308-
path: Path | undefined,
309-
pendingLabelsByPath: Map<string, Set<string>>,
310-
): boolean {
334+
): string | undefined {
311335
const deferDirective = selection.directives?.find(
312336
(directive) => directive.name.value === GraphQLDeferDirective.name,
313337
);
314338
if (!deferDirective) {
315-
return false;
316-
}
317-
const pathStr = pathToArray(path).join('.');
318-
const labels = pendingLabelsByPath.get(pathStr);
319-
if (labels == null) {
320-
return false;
339+
return;
321340
}
322341
const labelArg = deferDirective.arguments?.find(
323342
(arg) => arg.name.value === 'label',
324343
);
325344
invariant(labelArg != null);
326345
const labelValue = labelArg.value;
327346
invariant(labelValue.kind === Kind.STRING);
328-
const label = labelValue.value;
329-
return labels.has(label);
347+
return labelValue.value;
330348
}

src/transform/completeValue.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { invariant } from '../jsutils/invariant.js';
22
import { isObjectLike } from '../jsutils/isObjectLike.js';
3+
import { memoize3 } from '../jsutils/memoize3.js';
34
import type { ObjMap } from '../jsutils/ObjMap.js';
45
import type { Path } from '../jsutils/Path.js';
56
import { addPath, pathToArray } from '../jsutils/Path.js';
@@ -20,18 +21,20 @@ import {
2021
} from '../type/definition.js';
2122
import { GraphQLStreamDirective } from '../type/directives.js';
2223

24+
import type { GroupedFieldSet } from '../execution/collectFields.js';
25+
import type { ValidatedExecutionArgs } from '../execution/execute.js';
26+
2327
import type { TransformationContext } from './buildTransformationContext.js';
24-
import type { FieldDetails, GroupedFieldSet } from './collectFields.js';
28+
import type { FieldDetails } from './collectFields.js';
2529
import { collectSubfields as _collectSubfields } from './collectFields.js';
26-
import { memoize3of4 } from './memoize3of4.js';
30+
import { groupedFieldSetFromTree } from './groupedFieldSetFromTree.js';
2731

28-
const collectSubfields = memoize3of4(
32+
const collectSubfields = memoize3(
2933
(
30-
context: TransformationContext,
34+
validatedExecutionArgs: ValidatedExecutionArgs,
3135
returnType: GraphQLObjectType,
3236
fieldDetailsList: ReadonlyArray<FieldDetails>,
33-
path: Path | undefined,
34-
) => _collectSubfields(context, returnType, fieldDetailsList, path),
37+
) => _collectSubfields(validatedExecutionArgs, returnType, fieldDetailsList),
3538
);
3639

3740
// eslint-disable-next-line @typescript-eslint/max-params
@@ -141,10 +144,15 @@ function completeObjectType(
141144

142145
const completed = Object.create(null);
143146

144-
const groupedFieldSet = collectSubfields(
145-
context,
147+
const groupedFieldSetTree = collectSubfields(
148+
context.transformedArgs,
146149
runtimeType,
147150
fieldDetailsList,
151+
);
152+
153+
const groupedFieldSet = groupedFieldSetFromTree(
154+
context,
155+
groupedFieldSetTree,
148156
path,
149157
);
150158

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { AccumulatorMap } from '../jsutils/AccumulatorMap.js';
2+
import type { Path } from '../jsutils/Path.js';
3+
import { pathToArray } from '../jsutils/Path.js';
4+
5+
import type {
6+
FieldDetails,
7+
GroupedFieldSet,
8+
} from '../execution/collectFields.js';
9+
10+
import type { TransformationContext } from './buildTransformationContext.js';
11+
import type { GroupedFieldSetTree } from './collectFields.js';
12+
13+
export function groupedFieldSetFromTree(
14+
context: TransformationContext,
15+
groupedFieldSetTree: GroupedFieldSetTree,
16+
path: Path | undefined,
17+
): GroupedFieldSet {
18+
const groupedFieldSetWithInlinedDefers = new AccumulatorMap<
19+
string,
20+
FieldDetails
21+
>();
22+
groupedFieldSetFromTreeImpl(
23+
context,
24+
groupedFieldSetWithInlinedDefers,
25+
groupedFieldSetTree,
26+
path,
27+
);
28+
return groupedFieldSetWithInlinedDefers;
29+
}
30+
31+
function groupedFieldSetFromTreeImpl(
32+
context: TransformationContext,
33+
groupedFieldSetWithInlinedDefers: AccumulatorMap<string, FieldDetails>,
34+
groupedFieldSetTree: GroupedFieldSetTree,
35+
path: Path | undefined,
36+
): void {
37+
const { groupedFieldSet, deferredGroupedFieldSets } = groupedFieldSetTree;
38+
39+
for (const [responseName, fieldDetailsList] of groupedFieldSet) {
40+
for (const fieldDetails of fieldDetailsList) {
41+
groupedFieldSetWithInlinedDefers.add(responseName, fieldDetails);
42+
}
43+
}
44+
45+
for (const [label, childGroupedFieldSetTree] of deferredGroupedFieldSets) {
46+
const pathStr = pathToArray(path).join('.');
47+
const labels = context.pendingLabelsByPath.get(pathStr);
48+
if (labels?.has(label)) {
49+
continue;
50+
}
51+
52+
groupedFieldSetFromTreeImpl(
53+
context,
54+
groupedFieldSetWithInlinedDefers,
55+
childGroupedFieldSetTree,
56+
path,
57+
);
58+
}
59+
}

0 commit comments

Comments
 (0)