Skip to content

Commit bf20902

Browse files
committed
allow nested defers at the same level
if we allow nesting of defers at the same level then we have to handle the case where a defer is completely empty -- not just empty because its fields are also contained within a parent. this means that collectFields must directly return the new defer usages so that even empty defer usages can be used to create fragments so that we can track their children
1 parent ef478a2 commit bf20902

File tree

5 files changed

+107
-54
lines changed

5 files changed

+107
-54
lines changed

src/execution/__tests__/defer-test.ts

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -355,28 +355,32 @@ describe('Execute: defer directive', () => {
355355
data: {
356356
hero: {},
357357
},
358-
pending: [
359-
{ id: '0', path: ['hero'], label: 'DeferTop' },
360-
{ id: '1', path: ['hero'], label: 'DeferNested' },
361-
],
358+
pending: [{ id: '0', path: ['hero'], label: 'DeferTop' }],
362359
hasNext: true,
363360
},
364361
{
362+
pending: [{ id: '1', path: ['hero'], label: 'DeferNested' }],
365363
incremental: [
366364
{
367365
data: {
368366
id: '1',
369367
},
370368
id: '0',
371369
},
370+
],
371+
completed: [{ id: '0' }],
372+
hasNext: true,
373+
},
374+
{
375+
incremental: [
372376
{
373377
data: {
374378
friends: [{ name: 'Han' }, { name: 'Leia' }, { name: 'C-3PO' }],
375379
},
376380
id: '1',
377381
},
378382
],
379-
completed: [{ id: '0' }, { id: '1' }],
383+
completed: [{ id: '1' }],
380384
hasNext: false,
381385
},
382386
]);
@@ -472,6 +476,35 @@ describe('Execute: defer directive', () => {
472476
});
473477
});
474478

479+
it('Emits children of empty defer fragments', async () => {
480+
const document = parse(`
481+
query HeroNameQuery {
482+
hero {
483+
... @defer {
484+
... @defer {
485+
name
486+
}
487+
}
488+
}
489+
}
490+
`);
491+
const result = await complete(document);
492+
expectJSON(result).toDeepEqual([
493+
{
494+
data: {
495+
hero: {},
496+
},
497+
pending: [{ id: '0', path: ['hero'] }],
498+
hasNext: true,
499+
},
500+
{
501+
incremental: [{ data: { name: 'Luke' }, id: '0' }],
502+
completed: [{ id: '0' }],
503+
hasNext: false,
504+
},
505+
]);
506+
});
507+
475508
it('Can separately emit defer fragments with different labels with varying fields', async () => {
476509
const document = parse(`
477510
query HeroNameQuery {

src/execution/buildFieldPlan.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ export type DeferUsageSet = ReadonlySet<DeferUsage>;
88
export interface FieldGroup {
99
fields: ReadonlyArray<FieldDetails>;
1010
deferUsages?: DeferUsageSet | undefined;
11-
knownDeferUsages?: DeferUsageSet | undefined;
1211
}
1312

1413
export type GroupedFieldSet = Map<string, FieldGroup>;
@@ -21,21 +20,15 @@ export interface NewGroupedFieldSetDetails {
2120
export function buildFieldPlan(
2221
fields: Map<string, ReadonlyArray<FieldDetails>>,
2322
parentDeferUsages: DeferUsageSet = new Set<DeferUsage>(),
24-
knownDeferUsages: DeferUsageSet = new Set<DeferUsage>(),
2523
): {
2624
groupedFieldSet: GroupedFieldSet;
2725
newGroupedFieldSetDetailsMap: Map<DeferUsageSet, NewGroupedFieldSetDetails>;
28-
newDeferUsages: ReadonlyArray<DeferUsage>;
2926
} {
30-
const newDeferUsages: Set<DeferUsage> = new Set<DeferUsage>();
31-
const newKnownDeferUsages = new Set<DeferUsage>(knownDeferUsages);
32-
3327
const groupedFieldSet = new Map<
3428
string,
3529
{
3630
fields: Array<FieldDetails>;
3731
deferUsages: DeferUsageSet;
38-
knownDeferUsages: DeferUsageSet;
3932
}
4033
>();
4134

@@ -47,7 +40,6 @@ export function buildFieldPlan(
4740
{
4841
fields: Array<FieldDetails>;
4942
deferUsages: DeferUsageSet;
50-
knownDeferUsages: DeferUsageSet;
5143
}
5244
>;
5345
shouldInitiateDefer: boolean;
@@ -72,10 +64,6 @@ export function buildFieldPlan(
7264
continue;
7365
}
7466
deferUsageSet.add(deferUsage);
75-
if (!knownDeferUsages.has(deferUsage)) {
76-
newDeferUsages.add(deferUsage);
77-
newKnownDeferUsages.add(deferUsage);
78-
}
7967
}
8068
if (inOriginalResult) {
8169
deferUsageSet.clear();
@@ -99,7 +87,6 @@ export function buildFieldPlan(
9987
fieldGroup = {
10088
fields: [],
10189
deferUsages: deferUsageSet,
102-
knownDeferUsages: newKnownDeferUsages,
10390
};
10491
groupedFieldSet.set(responseKey, fieldGroup);
10592
}
@@ -140,7 +127,6 @@ export function buildFieldPlan(
140127
fieldGroup = {
141128
fields: [],
142129
deferUsages: deferUsageSet,
143-
knownDeferUsages: newKnownDeferUsages,
144130
};
145131
newGroupedFieldSet.set(responseKey, fieldGroup);
146132
}
@@ -150,7 +136,6 @@ export function buildFieldPlan(
150136
return {
151137
groupedFieldSet,
152138
newGroupedFieldSetDetailsMap,
153-
newDeferUsages: Array.from(newDeferUsages),
154139
};
155140
}
156141

src/execution/collectFields.ts

Lines changed: 60 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,12 @@ export function collectFields(
6060
variableValues: { [variable: string]: unknown },
6161
runtimeType: GraphQLObjectType,
6262
operation: OperationDefinitionNode,
63-
): Map<string, ReadonlyArray<FieldDetails>> {
63+
): {
64+
fields: Map<string, ReadonlyArray<FieldDetails>>;
65+
newDeferUsages: ReadonlyArray<DeferUsage>;
66+
} {
6467
const groupedFieldSet = new AccumulatorMap<string, FieldDetails>();
68+
const newDeferUsages: Array<DeferUsage> = [];
6569
const context: CollectFieldsContext = {
6670
schema,
6771
fragments,
@@ -71,8 +75,13 @@ export function collectFields(
7175
visitedFragmentNames: new Set(),
7276
};
7377

74-
collectFieldsImpl(context, operation.selectionSet, groupedFieldSet);
75-
return groupedFieldSet;
78+
collectFieldsImpl(
79+
context,
80+
operation.selectionSet,
81+
groupedFieldSet,
82+
newDeferUsages,
83+
);
84+
return { fields: groupedFieldSet, newDeferUsages };
7685
}
7786

7887
/**
@@ -93,7 +102,10 @@ export function collectSubfields(
93102
operation: OperationDefinitionNode,
94103
returnType: GraphQLObjectType,
95104
fieldDetails: ReadonlyArray<FieldDetails>,
96-
): Map<string, ReadonlyArray<FieldDetails>> {
105+
): {
106+
fields: Map<string, ReadonlyArray<FieldDetails>>;
107+
newDeferUsages: ReadonlyArray<DeferUsage>;
108+
} {
97109
const context: CollectFieldsContext = {
98110
schema,
99111
fragments,
@@ -103,6 +115,7 @@ export function collectSubfields(
103115
visitedFragmentNames: new Set(),
104116
};
105117
const subGroupedFieldSet = new AccumulatorMap<string, FieldDetails>();
118+
const newDeferUsages: Array<DeferUsage> = [];
106119

107120
for (const fieldDetail of fieldDetails) {
108121
const node = fieldDetail.node;
@@ -111,19 +124,23 @@ export function collectSubfields(
111124
context,
112125
node.selectionSet,
113126
subGroupedFieldSet,
127+
newDeferUsages,
114128
fieldDetail.deferUsage,
115129
);
116130
}
117131
}
118132

119-
return subGroupedFieldSet;
133+
return {
134+
fields: subGroupedFieldSet,
135+
newDeferUsages,
136+
};
120137
}
121138

122139
function collectFieldsImpl(
123140
context: CollectFieldsContext,
124141
selectionSet: SelectionSetNode,
125142
groupedFieldSet: AccumulatorMap<string, FieldDetails>,
126-
parentDeferUsage?: DeferUsage,
143+
newDeferUsages: Array<DeferUsage>,
127144
deferUsage?: DeferUsage,
128145
): void {
129146
const {
@@ -143,7 +160,7 @@ function collectFieldsImpl(
143160
}
144161
groupedFieldSet.add(getFieldEntryKey(selection), {
145162
node: selection,
146-
deferUsage: deferUsage ?? parentDeferUsage,
163+
deferUsage,
147164
});
148165
break;
149166
}
@@ -159,16 +176,27 @@ function collectFieldsImpl(
159176
operation,
160177
variableValues,
161178
selection,
162-
parentDeferUsage,
179+
deferUsage,
163180
);
164181

165-
collectFieldsImpl(
166-
context,
167-
selection.selectionSet,
168-
groupedFieldSet,
169-
parentDeferUsage,
170-
newDeferUsage ?? deferUsage,
171-
);
182+
if (!newDeferUsage) {
183+
collectFieldsImpl(
184+
context,
185+
selection.selectionSet,
186+
groupedFieldSet,
187+
newDeferUsages,
188+
deferUsage,
189+
);
190+
} else {
191+
newDeferUsages.push(newDeferUsage);
192+
collectFieldsImpl(
193+
context,
194+
selection.selectionSet,
195+
groupedFieldSet,
196+
newDeferUsages,
197+
newDeferUsage,
198+
);
199+
}
172200

173201
break;
174202
}
@@ -179,7 +207,7 @@ function collectFieldsImpl(
179207
operation,
180208
variableValues,
181209
selection,
182-
parentDeferUsage,
210+
deferUsage,
183211
);
184212

185213
if (
@@ -199,15 +227,23 @@ function collectFieldsImpl(
199227
}
200228
if (!newDeferUsage) {
201229
visitedFragmentNames.add(fragName);
230+
collectFieldsImpl(
231+
context,
232+
fragment.selectionSet,
233+
groupedFieldSet,
234+
newDeferUsages,
235+
deferUsage,
236+
);
237+
} else {
238+
newDeferUsages.push(newDeferUsage);
239+
collectFieldsImpl(
240+
context,
241+
fragment.selectionSet,
242+
groupedFieldSet,
243+
newDeferUsages,
244+
newDeferUsage,
245+
);
202246
}
203-
204-
collectFieldsImpl(
205-
context,
206-
fragment.selectionSet,
207-
groupedFieldSet,
208-
parentDeferUsage,
209-
newDeferUsage ?? deferUsage,
210-
);
211247
break;
212248
}
213249
}

src/execution/execute.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -91,19 +91,18 @@ const buildSubFieldPlan = memoize3(
9191
returnType: GraphQLObjectType,
9292
fieldGroup: FieldGroup,
9393
) => {
94-
const subFields = collectSubfields(
94+
const { fields: subFields, newDeferUsages } = collectSubfields(
9595
exeContext.schema,
9696
exeContext.fragments,
9797
exeContext.variableValues,
9898
exeContext.operation,
9999
returnType,
100100
fieldGroup.fields,
101101
);
102-
return buildFieldPlan(
103-
subFields,
104-
fieldGroup.deferUsages,
105-
fieldGroup.knownDeferUsages,
106-
);
102+
return {
103+
...buildFieldPlan(subFields, fieldGroup.deferUsages),
104+
newDeferUsages,
105+
};
107106
},
108107
);
109108

@@ -408,14 +407,14 @@ function executeOperation(
408407
);
409408
}
410409

411-
const fields = collectFields(
410+
const { fields, newDeferUsages } = collectFields(
412411
schema,
413412
fragments,
414413
variableValues,
415414
rootType,
416415
operation,
417416
);
418-
const { groupedFieldSet, newGroupedFieldSetDetailsMap, newDeferUsages } =
417+
const { groupedFieldSet, newGroupedFieldSetDetailsMap } =
419418
buildFieldPlan(fields);
420419

421420
const newDeferMap = addNewDeferredFragments(
@@ -1807,7 +1806,7 @@ function executeSubscription(
18071806
);
18081807
}
18091808

1810-
const fields = collectFields(
1809+
const { fields } = collectFields(
18111810
schema,
18121811
fragments,
18131812
variableValues,

src/validation/rules/SingleFieldSubscriptionsRule.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export function SingleFieldSubscriptionsRule(
4949
fragments[definition.name.value] = definition;
5050
}
5151
}
52-
const fields = collectFields(
52+
const { fields } = collectFields(
5353
schema,
5454
fragments,
5555
variableValues,

0 commit comments

Comments
 (0)