Skip to content

Commit aae95e6

Browse files
authored
add new deferred fragments to the wrapped graphql result (#4358)
alternative solution to #4357 in which we add the new deferred fragments to the wrapped graphql result After an execution group "fails," i.e. a null has bubbled up too far, it fails the entire associated deferred fragment. But if the query includes a shared execution group between the failing deferred fragment and a non-failing deferred fragment with some unique subfields, those subfields will still be added to the graph for the failed deferred fragment, which is not present. This throws an error, because the code assumes that in the situation above, i.e. a shared execution group between two fragments and some unique subfields, both of the deferred fragments must already be in the graph. So a runtime-error is triggered because some of the invariants we rely on when adding deferred fragments are violated. But even if we didn't have an error, this would be an erroneous situation, as we should never add back a node to the graph that has been failed! After this change, new deferred fragments are explicitly returned as part of the GraphQLWrappedResult and are only added at a single point when building the graph such that this error can be avoided entirely.
1 parent 02d302e commit aae95e6

File tree

5 files changed

+300
-59
lines changed

5 files changed

+300
-59
lines changed

src/execution/IncrementalGraph.ts

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,22 @@ export class IncrementalGraph {
3535
}
3636

3737
getNewRootNodes(
38+
newDeferredFragmentRecords:
39+
| ReadonlyArray<DeferredFragmentRecord>
40+
| undefined,
3841
incrementalDataRecords: ReadonlyArray<IncrementalDataRecord>,
3942
): ReadonlyArray<DeliveryGroup> {
4043
const initialResultChildren = new Set<DeliveryGroup>();
44+
45+
if (newDeferredFragmentRecords !== undefined) {
46+
for (const deferredFragmentRecord of newDeferredFragmentRecords) {
47+
this._addDeferredFragment(
48+
deferredFragmentRecord,
49+
initialResultChildren,
50+
);
51+
}
52+
}
53+
4154
this._addIncrementalDataRecords(
4255
incrementalDataRecords,
4356
undefined,
@@ -49,8 +62,17 @@ export class IncrementalGraph {
4962
addCompletedSuccessfulExecutionGroup(
5063
successfulExecutionGroup: SuccessfulExecutionGroup,
5164
): void {
52-
const { pendingExecutionGroup, incrementalDataRecords } =
53-
successfulExecutionGroup;
65+
const {
66+
pendingExecutionGroup,
67+
newDeferredFragmentRecords,
68+
incrementalDataRecords,
69+
} = successfulExecutionGroup;
70+
71+
if (newDeferredFragmentRecords !== undefined) {
72+
for (const deferredFragmentRecord of newDeferredFragmentRecords) {
73+
this._addDeferredFragment(deferredFragmentRecord, undefined);
74+
}
75+
}
5476

5577
const deferredFragmentRecords =
5678
pendingExecutionGroup.deferredFragmentRecords;
@@ -135,11 +157,7 @@ export class IncrementalGraph {
135157
removeDeferredFragment(
136158
deferredFragmentRecord: DeferredFragmentRecord,
137159
): boolean {
138-
if (!this._rootNodes.has(deferredFragmentRecord)) {
139-
return false;
140-
}
141-
this._rootNodes.delete(deferredFragmentRecord);
142-
return true;
160+
return this._rootNodes.delete(deferredFragmentRecord);
143161
}
144162

145163
removeStream(streamRecord: StreamRecord): void {
@@ -154,10 +172,6 @@ export class IncrementalGraph {
154172
for (const incrementalDataRecord of incrementalDataRecords) {
155173
if (isPendingExecutionGroup(incrementalDataRecord)) {
156174
for (const deferredFragmentRecord of incrementalDataRecord.deferredFragmentRecords) {
157-
this._addDeferredFragment(
158-
deferredFragmentRecord,
159-
initialResultChildren,
160-
);
161175
deferredFragmentRecord.pendingExecutionGroups.add(
162176
incrementalDataRecord,
163177
);
@@ -170,7 +184,6 @@ export class IncrementalGraph {
170184
initialResultChildren.add(incrementalDataRecord);
171185
} else {
172186
for (const parent of parents) {
173-
this._addDeferredFragment(parent, initialResultChildren);
174187
parent.children.add(incrementalDataRecord);
175188
}
176189
}
@@ -219,17 +232,13 @@ export class IncrementalGraph {
219232
deferredFragmentRecord: DeferredFragmentRecord,
220233
initialResultChildren: Set<DeliveryGroup> | undefined,
221234
): void {
222-
if (this._rootNodes.has(deferredFragmentRecord)) {
223-
return;
224-
}
225235
const parent = deferredFragmentRecord.parent;
226236
if (parent === undefined) {
227237
invariant(initialResultChildren !== undefined);
228238
initialResultChildren.add(deferredFragmentRecord);
229239
return;
230240
}
231241
parent.children.add(deferredFragmentRecord);
232-
this._addDeferredFragment(parent, initialResultChildren);
233242
}
234243

235244
private _onExecutionGroup(
@@ -251,6 +260,7 @@ export class IncrementalGraph {
251260
private async _onStreamItems(streamRecord: StreamRecord): Promise<void> {
252261
let items: Array<unknown> = [];
253262
let errors: Array<GraphQLError> = [];
263+
let newDeferredFragmentRecords: Array<DeferredFragmentRecord> = [];
254264
let incrementalDataRecords: Array<IncrementalDataRecord> = [];
255265
const streamItemQueue = streamRecord.streamItemQueue;
256266
let streamItemRecord: StreamItemRecord | undefined;
@@ -268,10 +278,12 @@ export class IncrementalGraph {
268278
errors.length > 0 /* c8 ignore start */
269279
? { items, errors } /* c8 ignore stop */
270280
: { items },
281+
newDeferredFragmentRecords,
271282
incrementalDataRecords,
272283
});
273284
items = [];
274285
errors = [];
286+
newDeferredFragmentRecords = [];
275287
incrementalDataRecords = [];
276288
}
277289
// eslint-disable-next-line no-await-in-loop
@@ -286,6 +298,7 @@ export class IncrementalGraph {
286298
this._enqueue({
287299
streamRecord,
288300
result: errors.length > 0 ? { items, errors } : { items },
301+
newDeferredFragmentRecords,
289302
incrementalDataRecords,
290303
});
291304
}
@@ -303,6 +316,9 @@ export class IncrementalGraph {
303316
if (result.errors !== undefined) {
304317
errors.push(...result.errors);
305318
}
319+
if (result.newDeferredFragmentRecords !== undefined) {
320+
newDeferredFragmentRecords.push(...result.newDeferredFragmentRecords);
321+
}
306322
if (result.incrementalDataRecords !== undefined) {
307323
incrementalDataRecords.push(...result.incrementalDataRecords);
308324
}

src/execution/IncrementalPublisher.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,14 @@ export function buildIncrementalResponse(
3333
context: IncrementalPublisherContext,
3434
result: ObjMap<unknown>,
3535
errors: ReadonlyArray<GraphQLError> | undefined,
36+
newDeferredFragmentRecords: ReadonlyArray<DeferredFragmentRecord> | undefined,
3637
incrementalDataRecords: ReadonlyArray<IncrementalDataRecord>,
3738
): ExperimentalIncrementalExecutionResults {
3839
const incrementalPublisher = new IncrementalPublisher(context);
3940
return incrementalPublisher.buildResponse(
4041
result,
4142
errors,
43+
newDeferredFragmentRecords,
4244
incrementalDataRecords,
4345
);
4446
}
@@ -74,9 +76,13 @@ class IncrementalPublisher {
7476
buildResponse(
7577
data: ObjMap<unknown>,
7678
errors: ReadonlyArray<GraphQLError> | undefined,
79+
newDeferredFragmentRecords:
80+
| ReadonlyArray<DeferredFragmentRecord>
81+
| undefined,
7782
incrementalDataRecords: ReadonlyArray<IncrementalDataRecord>,
7883
): ExperimentalIncrementalExecutionResults {
7984
const newRootNodes = this._incrementalGraph.getNewRootNodes(
85+
newDeferredFragmentRecords,
8086
incrementalDataRecords,
8187
);
8288

@@ -231,18 +237,16 @@ class IncrementalPublisher {
231237
if (isFailedExecutionGroup(completedExecutionGroup)) {
232238
for (const deferredFragmentRecord of completedExecutionGroup
233239
.pendingExecutionGroup.deferredFragmentRecords) {
234-
const id = deferredFragmentRecord.id;
235240
if (
236-
!this._incrementalGraph.removeDeferredFragment(deferredFragmentRecord)
241+
this._incrementalGraph.removeDeferredFragment(deferredFragmentRecord)
237242
) {
238-
// This can occur if multiple deferred grouped field sets error for a fragment.
239-
continue;
243+
const id = deferredFragmentRecord.id;
244+
invariant(id !== undefined);
245+
context.completed.push({
246+
id,
247+
errors: completedExecutionGroup.errors,
248+
});
240249
}
241-
invariant(id !== undefined);
242-
context.completed.push({
243-
id,
244-
errors: completedExecutionGroup.errors,
245-
});
246250
}
247251
return;
248252
}
@@ -319,9 +323,11 @@ class IncrementalPublisher {
319323

320324
context.incremental.push(incrementalEntry);
321325

322-
const incrementalDataRecords = streamItemsResult.incrementalDataRecords;
326+
const { newDeferredFragmentRecords, incrementalDataRecords } =
327+
streamItemsResult;
323328
if (incrementalDataRecords !== undefined) {
324329
const newRootNodes = this._incrementalGraph.getNewRootNodes(
330+
newDeferredFragmentRecords,
325331
incrementalDataRecords,
326332
);
327333
context.pending.push(...this._toPendingResults(newRootNodes));

0 commit comments

Comments
 (0)