Skip to content

Commit 51f41eb

Browse files
authored
incremental: allow nested defers at the same level (#4002)
Implements Option A from graphql/defer-stream-wg#80
1 parent ef478a2 commit 51f41eb

File tree

5 files changed

+107
-54
lines changed

5 files changed

+107
-54
lines changed

src/execution/__tests__/defer-test.ts

+38-5
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

-15
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

+60-24
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

+8-9
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

+1-1
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)