Skip to content

Commit 3bbbb08

Browse files
committed
refactor(IncrementalGraph): use Subsequent Result nodes to reduce mutation
1 parent 28e079a commit 3bbbb08

File tree

4 files changed

+142
-93
lines changed

4 files changed

+142
-93
lines changed

src/execution/IncrementalGraph.ts

Lines changed: 130 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,49 @@ import type {
99
ReconcilableDeferredGroupedFieldSetResult,
1010
SubsequentResultRecord,
1111
} from './types.js';
12-
import {
13-
isDeferredFragmentRecord,
14-
isDeferredGroupedFieldSetRecord,
15-
} from './types.js';
12+
import { isDeferredGroupedFieldSetRecord } from './types.js';
13+
14+
interface DeferredFragmentNode {
15+
deferredFragmentRecord: DeferredFragmentRecord;
16+
expectedReconcilableResults: number;
17+
results: Array<DeferredGroupedFieldSetResult>;
18+
reconcilableResults: Array<ReconcilableDeferredGroupedFieldSetResult>;
19+
children: Array<DeferredFragmentNode>;
20+
}
21+
22+
function isDeferredFragmentNode(
23+
node: DeferredFragmentNode | undefined,
24+
): node is DeferredFragmentNode {
25+
return node !== undefined;
26+
}
27+
28+
function isStreamNode(
29+
subsequentResultNode: SubsequentResultNode,
30+
): subsequentResultNode is SubsequentResultRecord {
31+
return 'path' in subsequentResultNode;
32+
}
33+
34+
type SubsequentResultNode = DeferredFragmentNode | SubsequentResultRecord;
1635

1736
/**
1837
* @internal
1938
*/
2039
export class IncrementalGraph {
21-
private _pending: Set<SubsequentResultRecord>;
22-
private _newPending: Set<SubsequentResultRecord>;
40+
private _pending: Set<SubsequentResultNode>;
41+
private _deferredFragmentNodes: Map<
42+
DeferredFragmentRecord,
43+
DeferredFragmentNode
44+
>;
45+
46+
private _newPending: Set<SubsequentResultNode>;
2347
private _completedQueue: Array<IncrementalDataRecordResult>;
2448
private _nextQueue: Array<
2549
(iterable: IteratorResult<Iterable<IncrementalDataRecordResult>>) => void
2650
>;
2751

2852
constructor() {
2953
this._pending = new Set();
54+
this._deferredFragmentNodes = new Map();
3055
this._newPending = new Set();
3156
this._completedQueue = [];
3257
this._nextQueue = [];
@@ -38,9 +63,10 @@ export class IncrementalGraph {
3863
for (const incrementalDataRecord of incrementalDataRecords) {
3964
if (isDeferredGroupedFieldSetRecord(incrementalDataRecord)) {
4065
for (const deferredFragmentRecord of incrementalDataRecord.deferredFragmentRecords) {
41-
deferredFragmentRecord.expectedReconcilableResults++;
42-
43-
this._addDeferredFragmentRecord(deferredFragmentRecord);
66+
const deferredFragmentNode = this._addDeferredFragmentNode(
67+
deferredFragmentRecord,
68+
);
69+
deferredFragmentNode.expectedReconcilableResults++;
4470
}
4571

4672
const result = incrementalDataRecord.result;
@@ -73,21 +99,33 @@ export class IncrementalGraph {
7399
}
74100
}
75101

102+
addCompletedReconcilableDeferredGroupedFieldSet(
103+
reconcilableResult: ReconcilableDeferredGroupedFieldSetResult,
104+
): void {
105+
const deferredFragmentNodes: Array<DeferredFragmentNode> =
106+
reconcilableResult.deferredFragmentRecords
107+
.map((deferredFragmentRecord) =>
108+
this._deferredFragmentNodes.get(deferredFragmentRecord),
109+
)
110+
.filter<DeferredFragmentNode>(isDeferredFragmentNode);
111+
for (const deferredFragmentNode of deferredFragmentNodes) {
112+
deferredFragmentNode.reconcilableResults.push(reconcilableResult);
113+
}
114+
}
115+
76116
getNewPending(): ReadonlyArray<SubsequentResultRecord> {
77-
const newPending = [];
117+
const newPending: Array<SubsequentResultRecord> = [];
78118
for (const node of this._newPending) {
79-
if (isDeferredFragmentRecord(node)) {
80-
if (node.expectedReconcilableResults) {
81-
this._pending.add(node);
82-
newPending.push(node);
83-
continue;
84-
}
119+
if (isStreamNode(node)) {
120+
this._pending.add(node);
121+
newPending.push(node);
122+
} else if (node.expectedReconcilableResults) {
123+
this._pending.add(node);
124+
newPending.push(node.deferredFragmentRecord);
125+
} else {
85126
for (const child of node.children) {
86127
this._newPending.add(child);
87128
}
88-
} else {
89-
this._pending.add(node);
90-
newPending.push(node);
91129
}
92130
}
93131
this._newPending.clear();
@@ -134,15 +172,23 @@ export class IncrementalGraph {
134172
completeDeferredFragment(
135173
deferredFragmentRecord: DeferredFragmentRecord,
136174
): Array<ReconcilableDeferredGroupedFieldSetResult> | undefined {
137-
const reconcilableResults = deferredFragmentRecord.reconcilableResults;
175+
const deferredFragmentNode = this._deferredFragmentNodes.get(
176+
deferredFragmentRecord,
177+
);
178+
// TODO: add test case?
179+
/* c8 ignore next 3 */
180+
if (deferredFragmentNode === undefined) {
181+
return undefined;
182+
}
183+
const reconcilableResults = deferredFragmentNode.reconcilableResults;
138184
if (
139-
deferredFragmentRecord.expectedReconcilableResults !==
185+
deferredFragmentNode.expectedReconcilableResults !==
140186
reconcilableResults.length
141187
) {
142188
return;
143189
}
144-
this.removeSubsequentResultRecord(deferredFragmentRecord);
145-
for (const child of deferredFragmentRecord.children) {
190+
this._removePending(deferredFragmentNode);
191+
for (const child of deferredFragmentNode.children) {
146192
this._newPending.add(child);
147193
for (const result of child.results) {
148194
this._enqueue(result);
@@ -151,53 +197,86 @@ export class IncrementalGraph {
151197
return reconcilableResults;
152198
}
153199

154-
removeSubsequentResultRecord(
155-
subsequentResultRecord: SubsequentResultRecord,
156-
): void {
157-
this._pending.delete(subsequentResultRecord);
200+
removeDeferredFragment(deferredFragmentRecord: DeferredFragmentRecord): void {
201+
const deferredFragmentNode = this._deferredFragmentNodes.get(
202+
deferredFragmentRecord,
203+
);
204+
// TODO: add test case?
205+
/* c8 ignore next 3 */
206+
if (deferredFragmentNode === undefined) {
207+
return;
208+
}
209+
this._removePending(deferredFragmentNode);
210+
this._deferredFragmentNodes.delete(deferredFragmentRecord);
211+
// TODO: add test case for an erroring deferred fragment with child defers
212+
/* c8 ignore next 3 */
213+
for (const child of deferredFragmentNode.children) {
214+
this.removeDeferredFragment(child.deferredFragmentRecord);
215+
}
216+
}
217+
218+
removeStream(streamRecord: SubsequentResultRecord): void {
219+
this._removePending(streamRecord);
220+
}
221+
222+
private _removePending(subsequentResultNode: SubsequentResultNode): void {
223+
this._pending.delete(subsequentResultNode);
158224
if (this._pending.size === 0) {
159225
for (const resolve of this._nextQueue) {
160226
resolve({ value: undefined, done: true });
161227
}
162228
}
163229
}
164230

165-
private _addDeferredFragmentRecord(
231+
private _addDeferredFragmentNode(
166232
deferredFragmentRecord: DeferredFragmentRecord,
167-
): void {
233+
): DeferredFragmentNode {
234+
let deferredFragmentNode = this._deferredFragmentNodes.get(
235+
deferredFragmentRecord,
236+
);
237+
if (deferredFragmentNode !== undefined) {
238+
return deferredFragmentNode;
239+
}
240+
deferredFragmentNode = {
241+
deferredFragmentRecord,
242+
expectedReconcilableResults: 0,
243+
results: [],
244+
reconcilableResults: [],
245+
children: [],
246+
};
247+
this._deferredFragmentNodes.set(
248+
deferredFragmentRecord,
249+
deferredFragmentNode,
250+
);
168251
const parent = deferredFragmentRecord.parent;
169252
if (parent === undefined) {
170-
// Below is equivalent and slightly faster version of:
171-
// if (this._pending.has(deferredFragmentRecord)) { ... }
172-
// as all released deferredFragmentRecords have ids.
173-
if (deferredFragmentRecord.id !== undefined) {
174-
return;
175-
}
176-
177-
this._newPending.add(deferredFragmentRecord);
178-
return;
253+
this._newPending.add(deferredFragmentNode);
254+
return deferredFragmentNode;
179255
}
180-
181-
if (parent.children.has(deferredFragmentRecord)) {
182-
return;
183-
}
184-
185-
parent.children.add(deferredFragmentRecord);
186-
187-
this._addDeferredFragmentRecord(parent);
256+
const parentNode = this._addDeferredFragmentNode(parent);
257+
parentNode.children.push(deferredFragmentNode);
258+
return deferredFragmentNode;
188259
}
189260

190261
private _enqueueCompletedDeferredGroupedFieldSet(
191262
result: DeferredGroupedFieldSetResult,
192263
): void {
193-
let hasPendingParent = false;
264+
let isPending = false;
194265
for (const deferredFragmentRecord of result.deferredFragmentRecords) {
195-
if (deferredFragmentRecord.id !== undefined) {
196-
hasPendingParent = true;
266+
const deferredFragmentNode = this._deferredFragmentNodes.get(
267+
deferredFragmentRecord,
268+
);
269+
// TODO: add test case?
270+
/* c8 ignore next 3 */
271+
if (deferredFragmentNode === undefined) {
272+
continue;
273+
}
274+
if (this._pending.has(deferredFragmentNode)) {
275+
isPending = true;
197276
}
198-
deferredFragmentRecord.results.push(result);
277+
deferredFragmentNode.results.push(result);
199278
}
200-
if (hasPendingParent) {
279+
if (isPending) {
201280
this._enqueue(result);
202281
}
203282
}

src/execution/IncrementalPublisher.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -237,18 +237,15 @@ class IncrementalPublisher {
237237
id,
238238
errors: deferredGroupedFieldSetResult.errors,
239239
});
240-
this._incrementalGraph.removeSubsequentResultRecord(
241-
deferredFragmentRecord,
242-
);
240+
this._incrementalGraph.removeDeferredFragment(deferredFragmentRecord);
243241
}
244242
}
245243
return;
246244
}
247-
for (const deferredFragmentRecord of deferredGroupedFieldSetResult.deferredFragmentRecords) {
248-
deferredFragmentRecord.reconcilableResults.push(
249-
deferredGroupedFieldSetResult,
250-
);
251-
}
245+
246+
this._incrementalGraph.addCompletedReconcilableDeferredGroupedFieldSet(
247+
deferredGroupedFieldSetResult,
248+
);
252249

253250
const incrementalDataRecords =
254251
deferredGroupedFieldSetResult.incrementalDataRecords;
@@ -306,7 +303,7 @@ class IncrementalPublisher {
306303
id,
307304
errors: streamItemsResult.errors,
308305
});
309-
this._incrementalGraph.removeSubsequentResultRecord(streamRecord);
306+
this._incrementalGraph.removeStream(streamRecord);
310307
if (isCancellableStreamRecord(streamRecord)) {
311308
invariant(this._context.cancellableStreams !== undefined);
312309
this._context.cancellableStreams.delete(streamRecord);
@@ -317,7 +314,7 @@ class IncrementalPublisher {
317314
}
318315
} else if (streamItemsResult.result === undefined) {
319316
context.completed.push({ id });
320-
this._incrementalGraph.removeSubsequentResultRecord(streamRecord);
317+
this._incrementalGraph.removeStream(streamRecord);
321318
if (isCancellableStreamRecord(streamRecord)) {
322319
invariant(this._context.cancellableStreams !== undefined);
323320
this._context.cancellableStreams.delete(streamRecord);

src/execution/execute.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ import { buildIncrementalResponse } from './IncrementalPublisher.js';
6262
import { mapAsyncIterable } from './mapAsyncIterable.js';
6363
import type {
6464
CancellableStreamRecord,
65+
DeferredFragmentRecord,
6566
DeferredGroupedFieldSetRecord,
6667
DeferredGroupedFieldSetResult,
6768
ExecutionResult,
@@ -71,10 +72,7 @@ import type {
7172
StreamItemsResult,
7273
SubsequentResultRecord,
7374
} from './types.js';
74-
import {
75-
DeferredFragmentRecord,
76-
isReconcilableStreamItemsResult,
77-
} from './types.js';
75+
import { isReconcilableStreamItemsResult } from './types.js';
7876
import {
7977
getArgumentValues,
8078
getDirectiveValues,
@@ -1674,11 +1672,11 @@ function addNewDeferredFragments(
16741672
: deferredFragmentRecordFromDeferUsage(parentDeferUsage, newDeferMap);
16751673

16761674
// Instantiate the new record.
1677-
const deferredFragmentRecord = new DeferredFragmentRecord({
1675+
const deferredFragmentRecord: DeferredFragmentRecord = {
16781676
path,
16791677
label: newDeferUsage.label,
16801678
parent,
1681-
});
1679+
};
16821680

16831681
// Update the map.
16841682
newDeferMap.set(newDeferUsage, deferredFragmentRecord);

src/execution/types.ts

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -166,12 +166,6 @@ export interface FormattedCompletedResult {
166166
errors?: ReadonlyArray<GraphQLError>;
167167
}
168168

169-
export function isDeferredFragmentRecord(
170-
subsequentResultRecord: SubsequentResultRecord,
171-
): subsequentResultRecord is DeferredFragmentRecord {
172-
return 'parent' in subsequentResultRecord;
173-
}
174-
175169
export function isDeferredGroupedFieldSetRecord(
176170
incrementalDataRecord: IncrementalDataRecord,
177171
): incrementalDataRecord is DeferredGroupedFieldSetRecord {
@@ -221,30 +215,11 @@ export interface SubsequentResultRecord {
221215
id?: string | undefined;
222216
}
223217

224-
/** @internal */
225-
export class DeferredFragmentRecord implements SubsequentResultRecord {
218+
export interface DeferredFragmentRecord extends SubsequentResultRecord {
226219
path: Path | undefined;
227220
label: string | undefined;
228221
id?: string | undefined;
229222
parent: DeferredFragmentRecord | undefined;
230-
expectedReconcilableResults: number;
231-
results: Array<DeferredGroupedFieldSetResult>;
232-
reconcilableResults: Array<ReconcilableDeferredGroupedFieldSetResult>;
233-
children: Set<DeferredFragmentRecord>;
234-
235-
constructor(opts: {
236-
path: Path | undefined;
237-
label: string | undefined;
238-
parent: DeferredFragmentRecord | undefined;
239-
}) {
240-
this.path = opts.path;
241-
this.label = opts.label;
242-
this.parent = opts.parent;
243-
this.expectedReconcilableResults = 0;
244-
this.results = [];
245-
this.reconcilableResults = [];
246-
this.children = new Set();
247-
}
248223
}
249224

250225
export interface CancellableStreamRecord extends SubsequentResultRecord {

0 commit comments

Comments
 (0)