@graphql-tools/executor 1.2.6 → 2.0.0-alpha-20240606144658-8963c8b8f661638eaee0e101a55f3b6e46cc03ff

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/cjs/execution/AccumulatorMap.js +21 -0
  2. package/cjs/execution/BoxedPromiseOrValue.js +25 -0
  3. package/cjs/execution/IncrementalGraph.js +271 -0
  4. package/cjs/execution/IncrementalPublisher.js +274 -0
  5. package/cjs/execution/buildFieldPlan.js +62 -0
  6. package/cjs/execution/collectFields.js +174 -0
  7. package/cjs/execution/execute.js +548 -513
  8. package/cjs/execution/getBySet.js +13 -0
  9. package/cjs/execution/isSameSet.js +15 -0
  10. package/cjs/execution/promiseWithResolvers.js +18 -0
  11. package/cjs/execution/types.js +19 -0
  12. package/esm/execution/AccumulatorMap.js +17 -0
  13. package/esm/execution/BoxedPromiseOrValue.js +21 -0
  14. package/esm/execution/IncrementalGraph.js +267 -0
  15. package/esm/execution/IncrementalPublisher.js +270 -0
  16. package/esm/execution/buildFieldPlan.js +58 -0
  17. package/esm/execution/collectFields.js +169 -0
  18. package/esm/execution/execute.js +549 -514
  19. package/esm/execution/getBySet.js +9 -0
  20. package/esm/execution/isSameSet.js +11 -0
  21. package/esm/execution/promiseWithResolvers.js +14 -0
  22. package/esm/execution/types.js +12 -0
  23. package/package.json +2 -2
  24. package/typings/execution/AccumulatorMap.d.cts +7 -0
  25. package/typings/execution/AccumulatorMap.d.ts +7 -0
  26. package/typings/execution/BoxedPromiseOrValue.d.cts +15 -0
  27. package/typings/execution/BoxedPromiseOrValue.d.ts +15 -0
  28. package/typings/execution/IncrementalGraph.d.cts +32 -0
  29. package/typings/execution/IncrementalGraph.d.ts +32 -0
  30. package/typings/execution/IncrementalPublisher.d.cts +8 -0
  31. package/typings/execution/IncrementalPublisher.d.ts +8 -0
  32. package/typings/execution/buildFieldPlan.d.cts +7 -0
  33. package/typings/execution/buildFieldPlan.d.ts +7 -0
  34. package/typings/execution/collectFields.d.cts +40 -0
  35. package/typings/execution/collectFields.d.ts +40 -0
  36. package/typings/execution/execute.d.cts +8 -106
  37. package/typings/execution/execute.d.ts +8 -106
  38. package/typings/execution/getBySet.d.cts +1 -0
  39. package/typings/execution/getBySet.d.ts +1 -0
  40. package/typings/execution/isSameSet.d.cts +1 -0
  41. package/typings/execution/isSameSet.d.ts +1 -0
  42. package/typings/execution/promiseWithResolvers.d.cts +10 -0
  43. package/typings/execution/promiseWithResolvers.d.ts +10 -0
  44. package/typings/execution/types.d.cts +155 -0
  45. package/typings/execution/types.d.ts +155 -0
  46. package/cjs/execution/flattenAsyncIterable.js +0 -89
  47. package/esm/execution/flattenAsyncIterable.js +0 -85
  48. package/typings/execution/flattenAsyncIterable.d.cts +0 -7
  49. package/typings/execution/flattenAsyncIterable.d.ts +0 -7
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getBySet = void 0;
4
+ const isSameSet_js_1 = require("./isSameSet.js");
5
+ function getBySet(map, setToMatch) {
6
+ for (const set of map.keys()) {
7
+ if ((0, isSameSet_js_1.isSameSet)(set, setToMatch)) {
8
+ return map.get(set);
9
+ }
10
+ }
11
+ return undefined;
12
+ }
13
+ exports.getBySet = getBySet;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isSameSet = void 0;
4
+ function isSameSet(setA, setB) {
5
+ if (setA.size !== setB.size) {
6
+ return false;
7
+ }
8
+ for (const item of setA) {
9
+ if (!setB.has(item)) {
10
+ return false;
11
+ }
12
+ }
13
+ return true;
14
+ }
15
+ exports.isSameSet = isSameSet;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.promiseWithResolvers = void 0;
4
+ /**
5
+ * Based on Promise.withResolvers proposal
6
+ * https://github.com/tc39/proposal-promise-with-resolvers
7
+ */
8
+ function promiseWithResolvers() {
9
+ // these are assigned synchronously within the Promise constructor
10
+ let resolve;
11
+ let reject;
12
+ const promise = new Promise((res, rej) => {
13
+ resolve = res;
14
+ reject = rej;
15
+ });
16
+ return { promise, resolve, reject };
17
+ }
18
+ exports.promiseWithResolvers = promiseWithResolvers;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isCancellableStreamRecord = exports.isNonReconcilableDeferredGroupedFieldSetResult = exports.isDeferredGroupedFieldSetResult = exports.isDeferredGroupedFieldSetRecord = void 0;
4
+ function isDeferredGroupedFieldSetRecord(incrementalDataRecord) {
5
+ return 'deferredFragmentRecords' in incrementalDataRecord;
6
+ }
7
+ exports.isDeferredGroupedFieldSetRecord = isDeferredGroupedFieldSetRecord;
8
+ function isDeferredGroupedFieldSetResult(subsequentResult) {
9
+ return 'deferredGroupedFieldSetRecord' in subsequentResult;
10
+ }
11
+ exports.isDeferredGroupedFieldSetResult = isDeferredGroupedFieldSetResult;
12
+ function isNonReconcilableDeferredGroupedFieldSetResult(deferredGroupedFieldSetResult) {
13
+ return deferredGroupedFieldSetResult.errors !== undefined;
14
+ }
15
+ exports.isNonReconcilableDeferredGroupedFieldSetResult = isNonReconcilableDeferredGroupedFieldSetResult;
16
+ function isCancellableStreamRecord(subsequentResultRecord) {
17
+ return 'earlyReturn' in subsequentResultRecord;
18
+ }
19
+ exports.isCancellableStreamRecord = isCancellableStreamRecord;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * ES6 Map with additional `add` method to accumulate items.
3
+ */
4
+ export class AccumulatorMap extends Map {
5
+ get [Symbol.toStringTag]() {
6
+ return 'AccumulatorMap';
7
+ }
8
+ add(key, item) {
9
+ const group = this.get(key);
10
+ if (group === undefined) {
11
+ this.set(key, [item]);
12
+ }
13
+ else {
14
+ group.push(item);
15
+ }
16
+ }
17
+ }
@@ -0,0 +1,21 @@
1
+ import { isPromise } from '@graphql-tools/utils';
2
+ /**
3
+ * A BoxedPromiseOrValue is a container for a value or promise where the value
4
+ * will be updated when the promise resolves.
5
+ *
6
+ * A BoxedPromiseOrValue may only be used with promises whose possible
7
+ * rejection has already been handled, otherwise this will lead to unhandled
8
+ * promise rejections.
9
+ *
10
+ * @internal
11
+ * */
12
+ export class BoxedPromiseOrValue {
13
+ constructor(value) {
14
+ this.value = value;
15
+ if (isPromise(value)) {
16
+ value.then(resolved => {
17
+ this.value = resolved;
18
+ });
19
+ }
20
+ }
21
+ }
@@ -0,0 +1,267 @@
1
+ import { isPromise } from '@graphql-tools/utils';
2
+ import { BoxedPromiseOrValue } from './BoxedPromiseOrValue.js';
3
+ import { promiseWithResolvers } from './promiseWithResolvers.js';
4
+ import { isDeferredGroupedFieldSetRecord } from './types.js';
5
+ function isDeferredFragmentNode(node) {
6
+ return node !== undefined;
7
+ }
8
+ function isStreamNode(record) {
9
+ return 'streamItemQueue' in record;
10
+ }
11
+ /**
12
+ * @internal
13
+ */
14
+ export class IncrementalGraph {
15
+ constructor() {
16
+ this._pending = new Set();
17
+ this._deferredFragmentNodes = new Map();
18
+ this._newIncrementalDataRecords = new Set();
19
+ this._newPending = new Set();
20
+ this._completedQueue = [];
21
+ this._nextQueue = [];
22
+ }
23
+ addIncrementalDataRecords(incrementalDataRecords) {
24
+ for (const incrementalDataRecord of incrementalDataRecords) {
25
+ if (isDeferredGroupedFieldSetRecord(incrementalDataRecord)) {
26
+ this._addDeferredGroupedFieldSetRecord(incrementalDataRecord);
27
+ }
28
+ else {
29
+ this._addStreamRecord(incrementalDataRecord);
30
+ }
31
+ }
32
+ }
33
+ addCompletedReconcilableDeferredGroupedFieldSet(reconcilableResult) {
34
+ const deferredFragmentNodes = reconcilableResult.deferredGroupedFieldSetRecord.deferredFragmentRecords
35
+ .map(deferredFragmentRecord => this._deferredFragmentNodes.get(deferredFragmentRecord))
36
+ .filter(isDeferredFragmentNode);
37
+ for (const deferredFragmentNode of deferredFragmentNodes) {
38
+ deferredFragmentNode.deferredGroupedFieldSetRecords.delete(reconcilableResult.deferredGroupedFieldSetRecord);
39
+ deferredFragmentNode.reconcilableResults.add(reconcilableResult);
40
+ }
41
+ }
42
+ getNewPending() {
43
+ const newPending = [];
44
+ for (const node of this._newPending) {
45
+ if (isStreamNode(node)) {
46
+ this._pending.add(node);
47
+ newPending.push(node);
48
+ this._newIncrementalDataRecords.add(node);
49
+ }
50
+ else if (node.deferredGroupedFieldSetRecords.size > 0) {
51
+ for (const deferredGroupedFieldSetNode of node.deferredGroupedFieldSetRecords) {
52
+ this._newIncrementalDataRecords.add(deferredGroupedFieldSetNode);
53
+ }
54
+ this._pending.add(node);
55
+ newPending.push(node.deferredFragmentRecord);
56
+ }
57
+ else {
58
+ for (const child of node.children) {
59
+ this._newPending.add(child);
60
+ }
61
+ }
62
+ }
63
+ this._newPending.clear();
64
+ for (const incrementalDataRecord of this._newIncrementalDataRecords) {
65
+ if (isStreamNode(incrementalDataRecord)) {
66
+ this._onStreamItems(incrementalDataRecord, incrementalDataRecord.streamItemQueue);
67
+ }
68
+ else {
69
+ const deferredGroupedFieldSetResult = incrementalDataRecord.result;
70
+ const result = deferredGroupedFieldSetResult instanceof BoxedPromiseOrValue
71
+ ? deferredGroupedFieldSetResult.value
72
+ : deferredGroupedFieldSetResult().value;
73
+ if (isPromise(result)) {
74
+ result.then(resolved => this._enqueue(resolved));
75
+ }
76
+ else {
77
+ this._enqueue(result);
78
+ }
79
+ }
80
+ }
81
+ this._newIncrementalDataRecords.clear();
82
+ return newPending;
83
+ }
84
+ completedIncrementalData() {
85
+ return {
86
+ [Symbol.asyncIterator]() {
87
+ return this;
88
+ },
89
+ next: () => {
90
+ const firstResult = this._completedQueue.shift();
91
+ if (firstResult !== undefined) {
92
+ return Promise.resolve({
93
+ value: this._yieldCurrentCompletedIncrementalData(firstResult),
94
+ done: false,
95
+ });
96
+ }
97
+ const { promise, resolve } = promiseWithResolvers();
98
+ this._nextQueue.push(resolve);
99
+ return promise;
100
+ },
101
+ return: () => {
102
+ for (const resolve of this._nextQueue) {
103
+ resolve({ value: undefined, done: true });
104
+ }
105
+ return Promise.resolve({ value: undefined, done: true });
106
+ },
107
+ };
108
+ }
109
+ hasNext() {
110
+ return this._pending.size > 0;
111
+ }
112
+ completeDeferredFragment(deferredFragmentRecord) {
113
+ const deferredFragmentNode = this._deferredFragmentNodes.get(deferredFragmentRecord);
114
+ // TODO: add test case?
115
+ /* c8 ignore next 3 */
116
+ if (deferredFragmentNode === undefined) {
117
+ return undefined;
118
+ }
119
+ if (deferredFragmentNode.deferredGroupedFieldSetRecords.size > 0) {
120
+ return;
121
+ }
122
+ const reconcilableResults = Array.from(deferredFragmentNode.reconcilableResults);
123
+ for (const reconcilableResult of reconcilableResults) {
124
+ for (const otherDeferredFragmentRecord of reconcilableResult.deferredGroupedFieldSetRecord
125
+ .deferredFragmentRecords) {
126
+ const otherDeferredFragmentNode = this._deferredFragmentNodes.get(otherDeferredFragmentRecord);
127
+ if (otherDeferredFragmentNode === undefined) {
128
+ continue;
129
+ }
130
+ otherDeferredFragmentNode.reconcilableResults.delete(reconcilableResult);
131
+ }
132
+ }
133
+ this._removePending(deferredFragmentNode);
134
+ for (const child of deferredFragmentNode.children) {
135
+ this._newPending.add(child);
136
+ }
137
+ return reconcilableResults;
138
+ }
139
+ removeDeferredFragment(deferredFragmentRecord) {
140
+ const deferredFragmentNode = this._deferredFragmentNodes.get(deferredFragmentRecord);
141
+ if (deferredFragmentNode === undefined) {
142
+ return false;
143
+ }
144
+ this._removePending(deferredFragmentNode);
145
+ this._deferredFragmentNodes.delete(deferredFragmentRecord);
146
+ // TODO: add test case for an erroring deferred fragment with child defers
147
+ /* c8 ignore next 3 */
148
+ for (const child of deferredFragmentNode.children) {
149
+ this.removeDeferredFragment(child.deferredFragmentRecord);
150
+ }
151
+ return true;
152
+ }
153
+ removeStream(streamRecord) {
154
+ this._removePending(streamRecord);
155
+ }
156
+ _removePending(subsequentResultNode) {
157
+ this._pending.delete(subsequentResultNode);
158
+ if (this._pending.size === 0) {
159
+ for (const resolve of this._nextQueue) {
160
+ resolve({ value: undefined, done: true });
161
+ }
162
+ }
163
+ }
164
+ _addDeferredGroupedFieldSetRecord(deferredGroupedFieldSetRecord) {
165
+ for (const deferredFragmentRecord of deferredGroupedFieldSetRecord.deferredFragmentRecords) {
166
+ const deferredFragmentNode = this._addDeferredFragmentNode(deferredFragmentRecord);
167
+ if (this._pending.has(deferredFragmentNode)) {
168
+ this._newIncrementalDataRecords.add(deferredGroupedFieldSetRecord);
169
+ }
170
+ deferredFragmentNode.deferredGroupedFieldSetRecords.add(deferredGroupedFieldSetRecord);
171
+ }
172
+ }
173
+ _addStreamRecord(streamRecord) {
174
+ this._newPending.add(streamRecord);
175
+ }
176
+ _addDeferredFragmentNode(deferredFragmentRecord) {
177
+ let deferredFragmentNode = this._deferredFragmentNodes.get(deferredFragmentRecord);
178
+ if (deferredFragmentNode !== undefined) {
179
+ return deferredFragmentNode;
180
+ }
181
+ deferredFragmentNode = {
182
+ deferredFragmentRecord,
183
+ deferredGroupedFieldSetRecords: new Set(),
184
+ reconcilableResults: new Set(),
185
+ children: [],
186
+ };
187
+ this._deferredFragmentNodes.set(deferredFragmentRecord, deferredFragmentNode);
188
+ const parent = deferredFragmentRecord.parent;
189
+ if (parent === undefined) {
190
+ this._newPending.add(deferredFragmentNode);
191
+ return deferredFragmentNode;
192
+ }
193
+ const parentNode = this._addDeferredFragmentNode(parent);
194
+ parentNode.children.push(deferredFragmentNode);
195
+ return deferredFragmentNode;
196
+ }
197
+ async _onStreamItems(streamRecord, streamItemQueue) {
198
+ let items = [];
199
+ let errors = [];
200
+ let incrementalDataRecords = [];
201
+ let streamItemRecord;
202
+ while ((streamItemRecord = streamItemQueue.shift()) !== undefined) {
203
+ let result = typeof streamItemRecord === 'function' ? streamItemRecord().value : streamItemRecord.value;
204
+ if (isPromise(result)) {
205
+ if (items.length > 0) {
206
+ this._enqueue({
207
+ streamRecord,
208
+ result:
209
+ // TODO add additional test case or rework for coverage
210
+ errors.length > 0 /* c8 ignore start */
211
+ ? { items, errors } /* c8 ignore stop */
212
+ : { items },
213
+ incrementalDataRecords,
214
+ });
215
+ items = [];
216
+ errors = [];
217
+ incrementalDataRecords = [];
218
+ }
219
+ result = await result;
220
+ // wait an additional tick to coalesce resolving additional promises
221
+ // within the queue
222
+ await Promise.resolve();
223
+ }
224
+ if (result.item === undefined) {
225
+ if (items.length > 0) {
226
+ this._enqueue({
227
+ streamRecord,
228
+ result: errors.length > 0 ? { items, errors } : { items },
229
+ incrementalDataRecords,
230
+ });
231
+ }
232
+ this._enqueue(result.errors === undefined
233
+ ? { streamRecord }
234
+ : {
235
+ streamRecord,
236
+ errors: result.errors,
237
+ });
238
+ return;
239
+ }
240
+ items.push(result.item);
241
+ if (result.errors !== undefined) {
242
+ errors.push(...result.errors);
243
+ }
244
+ if (result.incrementalDataRecords !== undefined) {
245
+ incrementalDataRecords.push(...result.incrementalDataRecords);
246
+ }
247
+ }
248
+ }
249
+ *_yieldCurrentCompletedIncrementalData(first) {
250
+ yield first;
251
+ let completed;
252
+ while ((completed = this._completedQueue.shift()) !== undefined) {
253
+ yield completed;
254
+ }
255
+ }
256
+ _enqueue(completed) {
257
+ const next = this._nextQueue.shift();
258
+ if (next !== undefined) {
259
+ next({
260
+ value: this._yieldCurrentCompletedIncrementalData(completed),
261
+ done: false,
262
+ });
263
+ return;
264
+ }
265
+ this._completedQueue.push(completed);
266
+ }
267
+ }
@@ -0,0 +1,270 @@
1
+ import { pathToArray } from '@graphql-tools/utils';
2
+ import { IncrementalGraph } from './IncrementalGraph.js';
3
+ import { invariant } from './invariant.js';
4
+ import { isCancellableStreamRecord, isDeferredGroupedFieldSetResult, isNonReconcilableDeferredGroupedFieldSetResult, } from './types.js';
5
+ export function buildIncrementalResponse(context, result, errors, incrementalDataRecords) {
6
+ const incrementalPublisher = new IncrementalPublisher(context);
7
+ return incrementalPublisher.buildResponse(result, errors, incrementalDataRecords);
8
+ }
9
+ /**
10
+ * This class is used to publish incremental results to the client, enabling semi-concurrent
11
+ * execution while preserving result order.
12
+ *
13
+ * @internal
14
+ */
15
+ class IncrementalPublisher {
16
+ constructor(context) {
17
+ this._context = context;
18
+ this._nextId = 0;
19
+ this._incrementalGraph = new IncrementalGraph();
20
+ }
21
+ buildResponse(data, errors, incrementalDataRecords) {
22
+ this._incrementalGraph.addIncrementalDataRecords(incrementalDataRecords);
23
+ const newPending = this._incrementalGraph.getNewPending();
24
+ const pending = this._pendingSourcesToResults(newPending);
25
+ const initialResult = errors === undefined
26
+ ? { data, pending, hasNext: true }
27
+ : { errors, data, pending, hasNext: true };
28
+ return {
29
+ initialResult,
30
+ subsequentResults: this._subscribe(),
31
+ };
32
+ }
33
+ _pendingSourcesToResults(newPending) {
34
+ const pendingResults = [];
35
+ for (const pendingSource of newPending) {
36
+ const id = String(this._getNextId());
37
+ pendingSource.id = id;
38
+ const pendingResult = {
39
+ id,
40
+ path: pathToArray(pendingSource.path),
41
+ };
42
+ if (pendingSource.label !== undefined) {
43
+ pendingResult.label = pendingSource.label;
44
+ }
45
+ pendingResults.push(pendingResult);
46
+ }
47
+ return pendingResults;
48
+ }
49
+ _getNextId() {
50
+ return String(this._nextId++);
51
+ }
52
+ _subscribe() {
53
+ let isDone = false;
54
+ this._context.signal?.addEventListener('abort', () => {
55
+ isDone = true;
56
+ });
57
+ const _next = async () => {
58
+ if (isDone) {
59
+ await this._returnAsyncIteratorsIgnoringErrors();
60
+ return { value: undefined, done: true };
61
+ }
62
+ const context = {
63
+ pending: [],
64
+ incremental: [],
65
+ completed: [],
66
+ };
67
+ const completedIncrementalData = this._incrementalGraph.completedIncrementalData();
68
+ // use the raw iterator rather than 'for await ... of' so as not to trigger the
69
+ // '.return()' method on the iterator when exiting the loop with the next value
70
+ const asyncIterator = completedIncrementalData[Symbol.asyncIterator]();
71
+ let iteration = await asyncIterator.next();
72
+ while (!iteration.done) {
73
+ if (this._context.signal?.aborted) {
74
+ throw this._context.signal.reason;
75
+ }
76
+ for (const completedResult of iteration.value) {
77
+ this._handleCompletedIncrementalData(completedResult, context);
78
+ }
79
+ const { incremental, completed } = context;
80
+ if (incremental.length > 0 || completed.length > 0) {
81
+ const hasNext = this._incrementalGraph.hasNext();
82
+ if (!hasNext) {
83
+ isDone = true;
84
+ }
85
+ const subsequentIncrementalExecutionResult = {
86
+ hasNext,
87
+ };
88
+ const pending = context.pending;
89
+ if (pending.length > 0) {
90
+ subsequentIncrementalExecutionResult.pending = pending;
91
+ }
92
+ if (incremental.length > 0) {
93
+ subsequentIncrementalExecutionResult.incremental = incremental;
94
+ }
95
+ if (completed.length > 0) {
96
+ subsequentIncrementalExecutionResult.completed = completed;
97
+ }
98
+ return { value: subsequentIncrementalExecutionResult, done: false };
99
+ }
100
+ iteration = await asyncIterator.next();
101
+ }
102
+ await this._returnAsyncIteratorsIgnoringErrors();
103
+ return { value: undefined, done: true };
104
+ };
105
+ const _return = async () => {
106
+ isDone = true;
107
+ await this._returnAsyncIterators();
108
+ return { value: undefined, done: true };
109
+ };
110
+ const _throw = async (error) => {
111
+ isDone = true;
112
+ await this._returnAsyncIterators();
113
+ return Promise.reject(error);
114
+ };
115
+ return {
116
+ [Symbol.asyncIterator]() {
117
+ return this;
118
+ },
119
+ next: _next,
120
+ return: _return,
121
+ throw: _throw,
122
+ };
123
+ }
124
+ _handleCompletedIncrementalData(completedIncrementalData, context) {
125
+ if (isDeferredGroupedFieldSetResult(completedIncrementalData)) {
126
+ this._handleCompletedDeferredGroupedFieldSet(completedIncrementalData, context);
127
+ }
128
+ else {
129
+ this._handleCompletedStreamItems(completedIncrementalData, context);
130
+ }
131
+ const newPending = this._incrementalGraph.getNewPending();
132
+ context.pending.push(...this._pendingSourcesToResults(newPending));
133
+ }
134
+ _handleCompletedDeferredGroupedFieldSet(deferredGroupedFieldSetResult, context) {
135
+ if (isNonReconcilableDeferredGroupedFieldSetResult(deferredGroupedFieldSetResult)) {
136
+ for (const deferredFragmentRecord of deferredGroupedFieldSetResult
137
+ .deferredGroupedFieldSetRecord.deferredFragmentRecords) {
138
+ const id = deferredFragmentRecord.id;
139
+ if (!this._incrementalGraph.removeDeferredFragment(deferredFragmentRecord)) {
140
+ // This can occur if multiple deferred grouped field sets error for a fragment.
141
+ continue;
142
+ }
143
+ invariant(id !== undefined);
144
+ context.completed.push({
145
+ id,
146
+ errors: deferredGroupedFieldSetResult.errors,
147
+ });
148
+ this._incrementalGraph.removeDeferredFragment(deferredFragmentRecord);
149
+ }
150
+ return;
151
+ }
152
+ this._incrementalGraph.addCompletedReconcilableDeferredGroupedFieldSet(deferredGroupedFieldSetResult);
153
+ const incrementalDataRecords = deferredGroupedFieldSetResult.incrementalDataRecords;
154
+ if (incrementalDataRecords !== undefined) {
155
+ this._incrementalGraph.addIncrementalDataRecords(incrementalDataRecords);
156
+ }
157
+ for (const deferredFragmentRecord of deferredGroupedFieldSetResult.deferredGroupedFieldSetRecord
158
+ .deferredFragmentRecords) {
159
+ const id = deferredFragmentRecord.id;
160
+ // TODO: add test case for this.
161
+ // Presumably, this can occur if an error causes a fragment to be completed early,
162
+ // while an asynchronous deferred grouped field set result is enqueued.
163
+ /* c8 ignore next 3 */
164
+ if (id === undefined) {
165
+ continue;
166
+ }
167
+ const reconcilableResults = this._incrementalGraph.completeDeferredFragment(deferredFragmentRecord);
168
+ if (reconcilableResults === undefined) {
169
+ continue;
170
+ }
171
+ const incremental = context.incremental;
172
+ for (const reconcilableResult of reconcilableResults) {
173
+ const { bestId, subPath } = this._getBestIdAndSubPath(id, deferredFragmentRecord, reconcilableResult);
174
+ const incrementalEntry = {
175
+ ...reconcilableResult.result,
176
+ id: bestId,
177
+ };
178
+ if (subPath !== undefined) {
179
+ incrementalEntry.subPath = subPath;
180
+ }
181
+ incremental.push(incrementalEntry);
182
+ }
183
+ context.completed.push({ id });
184
+ }
185
+ }
186
+ _handleCompletedStreamItems(streamItemsResult, context) {
187
+ const streamRecord = streamItemsResult.streamRecord;
188
+ const id = streamRecord.id;
189
+ invariant(id !== undefined);
190
+ if (streamItemsResult.errors !== undefined) {
191
+ context.completed.push({
192
+ id,
193
+ errors: streamItemsResult.errors,
194
+ });
195
+ this._incrementalGraph.removeStream(streamRecord);
196
+ if (isCancellableStreamRecord(streamRecord)) {
197
+ invariant(this._context.cancellableStreams !== undefined);
198
+ this._context.cancellableStreams.delete(streamRecord);
199
+ streamRecord.earlyReturn().catch(() => {
200
+ /* c8 ignore next 1 */
201
+ // ignore error
202
+ });
203
+ }
204
+ }
205
+ else if (streamItemsResult.result === undefined) {
206
+ context.completed.push({ id });
207
+ this._incrementalGraph.removeStream(streamRecord);
208
+ if (isCancellableStreamRecord(streamRecord)) {
209
+ invariant(this._context.cancellableStreams !== undefined);
210
+ this._context.cancellableStreams.delete(streamRecord);
211
+ }
212
+ }
213
+ else {
214
+ const incrementalEntry = {
215
+ id,
216
+ ...streamItemsResult.result,
217
+ };
218
+ context.incremental.push(incrementalEntry);
219
+ if (streamItemsResult.incrementalDataRecords !== undefined) {
220
+ this._incrementalGraph.addIncrementalDataRecords(streamItemsResult.incrementalDataRecords);
221
+ }
222
+ }
223
+ }
224
+ _getBestIdAndSubPath(initialId, initialDeferredFragmentRecord, deferredGroupedFieldSetResult) {
225
+ let maxLength = pathToArray(initialDeferredFragmentRecord.path).length;
226
+ let bestId = initialId;
227
+ for (const deferredFragmentRecord of deferredGroupedFieldSetResult.deferredGroupedFieldSetRecord
228
+ .deferredFragmentRecords) {
229
+ if (deferredFragmentRecord === initialDeferredFragmentRecord) {
230
+ continue;
231
+ }
232
+ const id = deferredFragmentRecord.id;
233
+ // TODO: add test case for when an fragment has not been released, but might be processed for the shortest path.
234
+ /* c8 ignore next 3 */
235
+ if (id === undefined) {
236
+ continue;
237
+ }
238
+ const fragmentPath = pathToArray(deferredFragmentRecord.path);
239
+ const length = fragmentPath.length;
240
+ if (length > maxLength) {
241
+ maxLength = length;
242
+ bestId = id;
243
+ }
244
+ }
245
+ const subPath = deferredGroupedFieldSetResult.path.slice(maxLength);
246
+ return {
247
+ bestId,
248
+ subPath: subPath.length > 0 ? subPath : undefined,
249
+ };
250
+ }
251
+ async _returnAsyncIterators() {
252
+ await this._incrementalGraph.completedIncrementalData().return();
253
+ const cancellableStreams = this._context.cancellableStreams;
254
+ if (cancellableStreams === undefined) {
255
+ return;
256
+ }
257
+ const promises = [];
258
+ for (const streamRecord of cancellableStreams) {
259
+ if (streamRecord.earlyReturn !== undefined) {
260
+ promises.push(streamRecord.earlyReturn());
261
+ }
262
+ }
263
+ await Promise.all(promises);
264
+ }
265
+ async _returnAsyncIteratorsIgnoringErrors() {
266
+ await this._returnAsyncIterators().catch(() => {
267
+ // Ignore errors
268
+ });
269
+ }
270
+ }