@graphql-tools/executor 4.1.1 → 4.1.2-alpha-20261020170708-72d61a1bbfbf64a0a9d61e801c80001b7ea79dc3
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.
- package/cjs/execution/execute.js +94 -86
- package/cjs/execution/getBySet.js +13 -0
- package/cjs/execution/incremental.js +157 -4
- package/cjs/execution/incrementalCollect.js +194 -0
- package/cjs/execution/isSameSet.js +15 -0
- package/esm/execution/execute.js +91 -82
- package/esm/execution/getBySet.js +10 -0
- package/esm/execution/incremental.js +153 -4
- package/esm/execution/incrementalCollect.js +185 -0
- package/esm/execution/isSameSet.js +12 -0
- package/package.json +1 -1
- package/typings/execution/execute.d.cts +7 -19
- package/typings/execution/execute.d.ts +7 -19
- package/typings/execution/getBySet.d.cts +2 -0
- package/typings/execution/getBySet.d.ts +2 -0
- package/typings/execution/incremental.d.cts +25 -0
- package/typings/execution/incremental.d.ts +25 -0
- package/typings/execution/incrementalCollect.d.cts +35 -0
- package/typings/execution/incrementalCollect.d.ts +35 -0
- package/typings/execution/isSameSet.d.cts +2 -0
- package/typings/execution/isSameSet.d.ts +2 -0
package/cjs/execution/execute.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.CRITICAL_ERROR = exports.defaultTypeResolver = exports.defaultFieldResolver = void 0;
|
|
4
4
|
exports.execute = execute;
|
|
5
5
|
exports.executeSync = executeSync;
|
|
6
6
|
exports.assertValidExecutionArguments = assertValidExecutionArguments;
|
|
@@ -13,12 +13,15 @@ exports.flattenIncrementalResults = flattenIncrementalResults;
|
|
|
13
13
|
exports.getFieldDef = getFieldDef;
|
|
14
14
|
exports.isIncrementalResult = isIncrementalResult;
|
|
15
15
|
const graphql_1 = require("graphql");
|
|
16
|
+
Object.defineProperty(exports, "defaultFieldResolver", { enumerable: true, get: function () { return graphql_1.defaultFieldResolver; } });
|
|
17
|
+
Object.defineProperty(exports, "defaultTypeResolver", { enumerable: true, get: function () { return graphql_1.defaultTypeResolver; } });
|
|
16
18
|
const utils_1 = require("@graphql-tools/utils");
|
|
17
19
|
const disposablestack_1 = require("@whatwg-node/disposablestack");
|
|
18
20
|
const promise_helpers_1 = require("@whatwg-node/promise-helpers");
|
|
19
21
|
const coerceError_js_1 = require("./coerceError.js");
|
|
20
22
|
const flattenAsyncIterable_js_1 = require("./flattenAsyncIterable.js");
|
|
21
23
|
const incremental_js_1 = require("./incremental.js");
|
|
24
|
+
const incrementalCollect_js_1 = require("./incrementalCollect.js");
|
|
22
25
|
const invariant_js_1 = require("./invariant.js");
|
|
23
26
|
const promiseForObject_js_1 = require("./promiseForObject.js");
|
|
24
27
|
const EMPTY_ARGS = Object.freeze(Object.create(null));
|
|
@@ -329,9 +332,9 @@ function buildExecutionContext(args) {
|
|
|
329
332
|
operation,
|
|
330
333
|
variableValues,
|
|
331
334
|
resolveInfoVariableValues,
|
|
332
|
-
fieldResolver: fieldResolver ??
|
|
333
|
-
typeResolver: typeResolver ??
|
|
334
|
-
subscribeFieldResolver: subscribeFieldResolver ??
|
|
335
|
+
fieldResolver: fieldResolver ?? graphql_1.defaultFieldResolver,
|
|
336
|
+
typeResolver: typeResolver ?? graphql_1.defaultTypeResolver,
|
|
337
|
+
subscribeFieldResolver: subscribeFieldResolver ?? graphql_1.defaultFieldResolver,
|
|
335
338
|
subsequentPayloads: new Set(),
|
|
336
339
|
incrementalSpec,
|
|
337
340
|
publisher: incrementalSpec === 'v0.2' ? (0, incremental_js_1.createIncrementalPublisherState)() : undefined,
|
|
@@ -401,18 +404,52 @@ function executeOperation(exeContext) {
|
|
|
401
404
|
const { fields: rootFields, patches } = (0, utils_1.collectFields)(schema, fragments, variableValues, rootType, operation.selectionSet);
|
|
402
405
|
const path = undefined;
|
|
403
406
|
let result;
|
|
407
|
+
if (patches.length === 0) {
|
|
408
|
+
if (operation.operation === 'mutation') {
|
|
409
|
+
result = executeFieldsSerially(exeContext, rootType, rootValue, path, rootFields);
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
result = executeFields(exeContext, rootType, rootValue, path, rootFields);
|
|
413
|
+
}
|
|
414
|
+
return result;
|
|
415
|
+
}
|
|
416
|
+
exeContext.nulledPaths ??= [];
|
|
417
|
+
const { groupedFieldSet } = (0, incrementalCollect_js_1.collectIncrementalFields)(schema, fragments, variableValues, rootType, operation.selectionSet);
|
|
418
|
+
const { groupedFieldSet: initialFields, newGroupedFieldSets } = (0, incrementalCollect_js_1.buildExecutionPlan)(groupedFieldSet);
|
|
419
|
+
const plannedRootFields = (0, incrementalCollect_js_1.groupedFieldSetToFieldMap)(initialFields);
|
|
404
420
|
if (operation.operation === 'mutation') {
|
|
405
|
-
result = executeFieldsSerially(exeContext, rootType, rootValue, path,
|
|
421
|
+
result = executeFieldsSerially(exeContext, rootType, rootValue, path, plannedRootFields);
|
|
406
422
|
}
|
|
407
423
|
else {
|
|
408
|
-
result = executeFields(exeContext, rootType, rootValue, path,
|
|
409
|
-
}
|
|
410
|
-
for (const patch of patches) {
|
|
411
|
-
const { label, fields: patchFields, patches: nestedPatches } = patch;
|
|
412
|
-
executeDeferredFragment(exeContext, rootType, rootValue, patchFields, label, path, undefined, nestedPatches);
|
|
424
|
+
result = executeFields(exeContext, rootType, rootValue, path, plannedRootFields);
|
|
413
425
|
}
|
|
426
|
+
executeDeferredGroups(exeContext, rootType, rootValue, path, newGroupedFieldSets, undefined);
|
|
414
427
|
return result;
|
|
415
428
|
}
|
|
429
|
+
function executeDeferredGroups(exeContext, parentType, sourceValue, path, newGroupedFieldSets, parentContext) {
|
|
430
|
+
if ((0, incremental_js_1.isAtOrUnderNulledPath)((0, utils_1.pathToArray)(path), exeContext.nulledPaths)) {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
const records = new Map();
|
|
434
|
+
const groups = [...newGroupedFieldSets.entries()].sort(([a], [b]) => deferUsageDepth((0, incrementalCollect_js_1.rootDeferUsage)(a)) - deferUsageDepth((0, incrementalCollect_js_1.rootDeferUsage)(b)));
|
|
435
|
+
for (const [deferUsageSet, groupedFieldSet] of groups) {
|
|
436
|
+
const usage = (0, incrementalCollect_js_1.rootDeferUsage)(deferUsageSet);
|
|
437
|
+
const deliveryParent = (usage?.parentDeferUsage && records.get(usage.parentDeferUsage)) ?? parentContext;
|
|
438
|
+
const record = executeDeferredFragment(exeContext, parentType, sourceValue, (0, incrementalCollect_js_1.groupedFieldSetToFieldMap)(groupedFieldSet), (0, incrementalCollect_js_1.labelForDeferUsageSet)(deferUsageSet), path, parentContext, undefined, deliveryParent, usage);
|
|
439
|
+
if (usage) {
|
|
440
|
+
records.set(usage, record);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
function deferUsageDepth(usage) {
|
|
445
|
+
let depth = 0;
|
|
446
|
+
let current = usage;
|
|
447
|
+
while (current?.parentDeferUsage) {
|
|
448
|
+
depth++;
|
|
449
|
+
current = current.parentDeferUsage;
|
|
450
|
+
}
|
|
451
|
+
return depth;
|
|
452
|
+
}
|
|
416
453
|
/**
|
|
417
454
|
* Implements the "Executing selection sets" section of the spec
|
|
418
455
|
* for fields that must be executed serially.
|
|
@@ -886,71 +923,29 @@ function invalidReturnTypeError(returnType, result, fieldNodes) {
|
|
|
886
923
|
function collectAndExecuteSubfields(exeContext, returnType, fieldNodes, path, result, asyncPayloadRecord) {
|
|
887
924
|
// Collect sub-fields to execute to complete this value.
|
|
888
925
|
const { fields: subFieldNodes, patches: subPatches } = collectSubfields(exeContext, returnType, fieldNodes);
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
* isTypeOf for the object being coerced, returning the first type that matches.
|
|
905
|
-
*/
|
|
906
|
-
const defaultTypeResolver = function (value, contextValue, info, abstractType) {
|
|
907
|
-
// First, look for `__typename`.
|
|
908
|
-
if ((0, utils_1.isObjectLike)(value) && typeof value['__typename'] === 'string') {
|
|
909
|
-
return value['__typename'];
|
|
910
|
-
}
|
|
911
|
-
// Otherwise, test each possible type.
|
|
912
|
-
const possibleTypes = info.schema.getPossibleTypes(abstractType);
|
|
913
|
-
const promisedIsTypeOfResults = [];
|
|
914
|
-
for (let i = 0; i < possibleTypes.length; i++) {
|
|
915
|
-
const type = possibleTypes[i];
|
|
916
|
-
if (type.isTypeOf) {
|
|
917
|
-
const isTypeOfResult = type.isTypeOf(value, contextValue, info);
|
|
918
|
-
if ((0, promise_helpers_1.isPromise)(isTypeOfResult)) {
|
|
919
|
-
promisedIsTypeOfResults[i] = isTypeOfResult;
|
|
920
|
-
}
|
|
921
|
-
else if (isTypeOfResult) {
|
|
922
|
-
return type.name;
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
if (promisedIsTypeOfResults.length) {
|
|
927
|
-
return Promise.all(promisedIsTypeOfResults).then(isTypeOfResults => {
|
|
928
|
-
for (let i = 0; i < isTypeOfResults.length; i++) {
|
|
929
|
-
if (isTypeOfResults[i]) {
|
|
930
|
-
return possibleTypes[i].name;
|
|
931
|
-
}
|
|
926
|
+
if (subPatches.length === 0 && exeContext.nulledPaths == null) {
|
|
927
|
+
return executeFields(exeContext, returnType, result, path, subFieldNodes, asyncPayloadRecord);
|
|
928
|
+
}
|
|
929
|
+
exeContext.nulledPaths ??= [];
|
|
930
|
+
const parentDeferUsage = asyncPayloadRecord?.type === 'defer' ? asyncPayloadRecord.deferUsage : undefined;
|
|
931
|
+
const { groupedFieldSet } = (0, incrementalCollect_js_1.collectIncrementalSubfields)(exeContext.schema, exeContext.fragments, exeContext.variableValues, returnType, fieldNodes, parentDeferUsage);
|
|
932
|
+
const parentDeferUsages = new Set();
|
|
933
|
+
if (parentDeferUsage) {
|
|
934
|
+
parentDeferUsages.add(parentDeferUsage);
|
|
935
|
+
}
|
|
936
|
+
else if (asyncPayloadRecord?.type === 'stream') {
|
|
937
|
+
for (const node of fieldNodes) {
|
|
938
|
+
const usage = (0, incrementalCollect_js_1.deferUsageForFieldNode)(node);
|
|
939
|
+
if (usage) {
|
|
940
|
+
parentDeferUsages.add(usage);
|
|
932
941
|
}
|
|
933
|
-
});
|
|
934
|
-
}
|
|
935
|
-
};
|
|
936
|
-
exports.defaultTypeResolver = defaultTypeResolver;
|
|
937
|
-
/**
|
|
938
|
-
* If a resolve function is not given, then a default resolve behavior is used
|
|
939
|
-
* which takes the property of the source object of the same name as the field
|
|
940
|
-
* and returns it as the result, or if it's a function, returns the result
|
|
941
|
-
* of calling that function while passing along args and context value.
|
|
942
|
-
*/
|
|
943
|
-
const defaultFieldResolver = function (source, args, contextValue, info) {
|
|
944
|
-
// ensure source is a value for which property access is acceptable.
|
|
945
|
-
if ((0, utils_1.isObjectLike)(source) || typeof source === 'function') {
|
|
946
|
-
const property = source[info.fieldName];
|
|
947
|
-
if (typeof property === 'function') {
|
|
948
|
-
return source[info.fieldName](args, contextValue, info);
|
|
949
942
|
}
|
|
950
|
-
return property;
|
|
951
943
|
}
|
|
952
|
-
};
|
|
953
|
-
|
|
944
|
+
const { groupedFieldSet: initialFields, newGroupedFieldSets } = (0, incrementalCollect_js_1.buildExecutionPlan)(groupedFieldSet, parentDeferUsages);
|
|
945
|
+
const subFields = executeFields(exeContext, returnType, result, path, (0, incrementalCollect_js_1.groupedFieldSetToFieldMap)(initialFields), asyncPayloadRecord);
|
|
946
|
+
executeDeferredGroups(exeContext, returnType, result, path, newGroupedFieldSets, asyncPayloadRecord);
|
|
947
|
+
return subFields;
|
|
948
|
+
}
|
|
954
949
|
/**
|
|
955
950
|
* Implements the "Subscribe" algorithm described in the GraphQL specification,
|
|
956
951
|
* including `@defer` and `@stream` as proposed in
|
|
@@ -1158,13 +1153,14 @@ function assertEventStream(result, signal, onSignalAbort) {
|
|
|
1158
1153
|
}
|
|
1159
1154
|
return result;
|
|
1160
1155
|
}
|
|
1161
|
-
function executeDeferredFragment(exeContext, parentType, sourceValue, fields, label, path, parentContext, nestedPatches, deliveryParent) {
|
|
1156
|
+
function executeDeferredFragment(exeContext, parentType, sourceValue, fields, label, path, parentContext, nestedPatches, deliveryParent, deferUsage) {
|
|
1162
1157
|
const asyncPayloadRecord = new incremental_js_1.DeferredFragmentRecord({
|
|
1163
1158
|
label,
|
|
1164
1159
|
path,
|
|
1165
1160
|
parentContext,
|
|
1166
1161
|
deliveryParent: deliveryParent ?? parentContext,
|
|
1167
1162
|
exeContext,
|
|
1163
|
+
deferUsage,
|
|
1168
1164
|
});
|
|
1169
1165
|
let promiseOrData;
|
|
1170
1166
|
try {
|
|
@@ -1188,6 +1184,7 @@ function executeDeferredFragment(exeContext, parentType, sourceValue, fields, la
|
|
|
1188
1184
|
parentContext, nested.patches, asyncPayloadRecord);
|
|
1189
1185
|
}
|
|
1190
1186
|
}
|
|
1187
|
+
return asyncPayloadRecord;
|
|
1191
1188
|
}
|
|
1192
1189
|
function executeStreamField(path, itemPath, item, exeContext, fieldNodes, info, itemType, label, parentContext, streamGroup) {
|
|
1193
1190
|
const asyncPayloadRecord = new incremental_js_1.StreamRecord({
|
|
@@ -1197,6 +1194,10 @@ function executeStreamField(path, itemPath, item, exeContext, fieldNodes, info,
|
|
|
1197
1194
|
exeContext,
|
|
1198
1195
|
streamGroup,
|
|
1199
1196
|
});
|
|
1197
|
+
if ((0, incremental_js_1.isAtOrUnderNulledPath)(asyncPayloadRecord.path, exeContext.nulledPaths)) {
|
|
1198
|
+
(0, incremental_js_1.cancelAsyncPayload)(exeContext, asyncPayloadRecord);
|
|
1199
|
+
return asyncPayloadRecord;
|
|
1200
|
+
}
|
|
1200
1201
|
let completedItem;
|
|
1201
1202
|
try {
|
|
1202
1203
|
try {
|
|
@@ -1299,6 +1300,15 @@ async function executeStreamIterator(initialIndex, iterator, exeContext, fieldNo
|
|
|
1299
1300
|
exeContext,
|
|
1300
1301
|
streamGroup,
|
|
1301
1302
|
});
|
|
1303
|
+
if ((0, incremental_js_1.isAtOrUnderNulledPath)(asyncPayloadRecord.path, exeContext.nulledPaths)) {
|
|
1304
|
+
(0, incremental_js_1.cancelAsyncPayload)(exeContext, asyncPayloadRecord);
|
|
1305
|
+
if (iterator.return) {
|
|
1306
|
+
iterator.return().catch(() => {
|
|
1307
|
+
// ignore errors
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
break;
|
|
1311
|
+
}
|
|
1302
1312
|
let iteration;
|
|
1303
1313
|
try {
|
|
1304
1314
|
iteration = await executeStreamIteratorItem(iterator, exeContext, fieldNodes, info, itemType, asyncPayloadRecord, itemPath);
|
|
@@ -1340,24 +1350,22 @@ async function executeStreamIterator(initialIndex, iterator, exeContext, fieldNo
|
|
|
1340
1350
|
}
|
|
1341
1351
|
function filterSubsequentPayloads(exeContext, nullPath, currentAsyncRecord) {
|
|
1342
1352
|
const nullPathArray = (0, utils_1.pathToArray)(nullPath);
|
|
1353
|
+
if (exeContext.nulledPaths) {
|
|
1354
|
+
exeContext.nulledPaths.push(nullPathArray);
|
|
1355
|
+
}
|
|
1356
|
+
else if (exeContext.subsequentPayloads.size === 0) {
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1343
1359
|
exeContext.subsequentPayloads.forEach(asyncRecord => {
|
|
1344
|
-
if (asyncRecord === currentAsyncRecord) {
|
|
1360
|
+
if (asyncRecord === currentAsyncRecord || asyncRecord.cancelled) {
|
|
1345
1361
|
// don't remove payload from where error originates
|
|
1346
1362
|
return;
|
|
1347
1363
|
}
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
return;
|
|
1352
|
-
}
|
|
1353
|
-
}
|
|
1354
|
-
// asyncRecord path points to nulled error field
|
|
1355
|
-
if ((0, incremental_js_1.isStreamPayload)(asyncRecord) && asyncRecord.iterator?.return) {
|
|
1356
|
-
asyncRecord.iterator.return().catch(() => {
|
|
1357
|
-
// ignore error
|
|
1358
|
-
});
|
|
1364
|
+
if (!(0, incremental_js_1.pathStartsWith)(asyncRecord.path, nullPathArray)) {
|
|
1365
|
+
// ancestor or a different branch: ancestor patches are pruned at yield
|
|
1366
|
+
return;
|
|
1359
1367
|
}
|
|
1360
|
-
(0, incremental_js_1.
|
|
1368
|
+
(0, incremental_js_1.cancelAsyncPayload)(exeContext, asyncRecord);
|
|
1361
1369
|
});
|
|
1362
1370
|
}
|
|
1363
1371
|
/**
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getBySet = getBySet;
|
|
4
|
+
const isSameSet_js_1 = require("./isSameSet.js");
|
|
5
|
+
/** @internal */
|
|
6
|
+
function getBySet(map, setToMatch) {
|
|
7
|
+
for (const set of map.keys()) {
|
|
8
|
+
if ((0, isSameSet_js_1.isSameSet)(set, setToMatch)) {
|
|
9
|
+
return map.get(set);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
@@ -4,6 +4,10 @@ exports.StreamRecord = exports.DeferredFragmentRecord = exports.StreamGroup = vo
|
|
|
4
4
|
exports.createIncrementalPublisherState = createIncrementalPublisherState;
|
|
5
5
|
exports.createStreamGroupIfV02 = createStreamGroupIfV02;
|
|
6
6
|
exports.deleteAsyncPayload = deleteAsyncPayload;
|
|
7
|
+
exports.pathStartsWith = pathStartsWith;
|
|
8
|
+
exports.isAtOrUnderNulledPath = isAtOrUnderNulledPath;
|
|
9
|
+
exports.pruneUnmergeableIncrementalData = pruneUnmergeableIncrementalData;
|
|
10
|
+
exports.cancelAsyncPayload = cancelAsyncPayload;
|
|
7
11
|
exports.isStreamPayload = isStreamPayload;
|
|
8
12
|
exports.collectInitialPending = collectInitialPending;
|
|
9
13
|
exports.yieldSubsequentPayloads = yieldSubsequentPayloads;
|
|
@@ -54,6 +58,130 @@ function deleteAsyncPayload(exeContext, record) {
|
|
|
54
58
|
record.streamGroup?.release();
|
|
55
59
|
}
|
|
56
60
|
}
|
|
61
|
+
function pathStartsWith(path, prefix) {
|
|
62
|
+
if (prefix.length > path.length) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
for (let i = 0; i < prefix.length; i++) {
|
|
66
|
+
if (path[i] !== prefix[i]) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
function isAtOrUnderNulledPath(path, nulledPaths) {
|
|
73
|
+
if (nulledPaths == null || nulledPaths.length === 0) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
for (const nulledPath of nulledPaths) {
|
|
77
|
+
if (pathStartsWith(path, nulledPath)) {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
function isStrictlyUnderNulledPath(path, nulledPaths) {
|
|
84
|
+
for (const nulledPath of nulledPaths) {
|
|
85
|
+
if (path.length > nulledPath.length && pathStartsWith(path, nulledPath)) {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
function isExactNulledPath(path, nulledPaths) {
|
|
92
|
+
for (const nulledPath of nulledPaths) {
|
|
93
|
+
if (path.length === nulledPath.length && pathStartsWith(path, nulledPath)) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
function isEmptyIncrementalData(data) {
|
|
100
|
+
if (data == null) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
if (typeof data !== 'object') {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
if (Array.isArray(data)) {
|
|
107
|
+
return data.length === 0;
|
|
108
|
+
}
|
|
109
|
+
const entries = Object.entries(data);
|
|
110
|
+
if (entries.length === 0) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
return entries.every(([, value]) => value != null && typeof value === 'object' && isEmptyIncrementalData(value));
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Drop keys that would write into a subtree already nulled by error bubbling.
|
|
117
|
+
* Ancestor deferred records can still deliver sibling fields (e.g. `someField`
|
|
118
|
+
* next to a nulled `b.c`).
|
|
119
|
+
*/
|
|
120
|
+
function pruneUnmergeableIncrementalData(data, basePath, nulledPaths) {
|
|
121
|
+
if (nulledPaths == null || nulledPaths.length === 0 || data == null || typeof data !== 'object') {
|
|
122
|
+
return data;
|
|
123
|
+
}
|
|
124
|
+
if (Array.isArray(data)) {
|
|
125
|
+
return data.map((item, index) => {
|
|
126
|
+
const itemPath = [...basePath, index];
|
|
127
|
+
if (isAtOrUnderNulledPath(itemPath, nulledPaths)) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
return pruneUnmergeableIncrementalData(item, itemPath, nulledPaths);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
const result = {};
|
|
134
|
+
for (const [key, value] of Object.entries(data)) {
|
|
135
|
+
const childPath = [...basePath, key];
|
|
136
|
+
if (isStrictlyUnderNulledPath(childPath, nulledPaths)) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (isExactNulledPath(childPath, nulledPaths)) {
|
|
140
|
+
// Keep a null delivered by this payload; drop a non-null write into an
|
|
141
|
+
// already-nulled object (Yaacov: unmergeable incremental).
|
|
142
|
+
if (value != null) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
result[key] = null;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
result[key] = pruneUnmergeableIncrementalData(value, childPath, nulledPaths);
|
|
149
|
+
}
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
function isAnnounced(exeContext, record) {
|
|
153
|
+
const state = exeContext.publisher;
|
|
154
|
+
if (state == null) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
return state.announced.has(getPublishNode(record));
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Cancel a subsequent record whose path is at or under a nulled position
|
|
161
|
+
* (graphql-js 17 `hasNulledPosition`). v0.2 records that were already
|
|
162
|
+
* announced stay in the set so the publisher can emit `completed` without
|
|
163
|
+
* incremental data.
|
|
164
|
+
*/
|
|
165
|
+
function cancelAsyncPayload(exeContext, record) {
|
|
166
|
+
record.cancelled = true;
|
|
167
|
+
if (isStreamPayload(record) && record.iterator?.return) {
|
|
168
|
+
record.iterator.return().catch(() => {
|
|
169
|
+
// ignore error
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
if (!record.isCompleted) {
|
|
173
|
+
if (isStreamPayload(record)) {
|
|
174
|
+
record.addItems(null);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
record.addData(null);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (exeContext.incrementalSpec === 'v0.2' && isAnnounced(exeContext, record)) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
deleteAsyncPayload(exeContext, record);
|
|
184
|
+
}
|
|
57
185
|
class DeferredFragmentRecord {
|
|
58
186
|
type;
|
|
59
187
|
errors;
|
|
@@ -63,7 +191,9 @@ class DeferredFragmentRecord {
|
|
|
63
191
|
data;
|
|
64
192
|
parentContext;
|
|
65
193
|
deliveryParent;
|
|
194
|
+
deferUsage;
|
|
66
195
|
isCompleted;
|
|
196
|
+
cancelled;
|
|
67
197
|
_exeContext;
|
|
68
198
|
_resolve;
|
|
69
199
|
constructor(opts) {
|
|
@@ -72,10 +202,12 @@ class DeferredFragmentRecord {
|
|
|
72
202
|
this.path = (0, utils_1.pathToArray)(opts.path);
|
|
73
203
|
this.parentContext = opts.parentContext;
|
|
74
204
|
this.deliveryParent = opts.deliveryParent ?? opts.parentContext;
|
|
205
|
+
this.deferUsage = opts.deferUsage;
|
|
75
206
|
this.errors = [];
|
|
76
207
|
this._exeContext = opts.exeContext;
|
|
77
208
|
this._exeContext.subsequentPayloads.add(this);
|
|
78
209
|
this.isCompleted = false;
|
|
210
|
+
this.cancelled = false;
|
|
79
211
|
this.data = null;
|
|
80
212
|
this.promise = new Promise(resolve => {
|
|
81
213
|
this._resolve = MaybePromise => {
|
|
@@ -107,6 +239,7 @@ class StreamRecord {
|
|
|
107
239
|
iterator;
|
|
108
240
|
isCompletedIterator;
|
|
109
241
|
isCompleted;
|
|
242
|
+
cancelled;
|
|
110
243
|
streamGroup;
|
|
111
244
|
_exeContext;
|
|
112
245
|
_resolve;
|
|
@@ -123,6 +256,7 @@ class StreamRecord {
|
|
|
123
256
|
this._exeContext.subsequentPayloads.add(this);
|
|
124
257
|
this.streamGroup?.retain();
|
|
125
258
|
this.isCompleted = false;
|
|
259
|
+
this.cancelled = false;
|
|
126
260
|
this.items = null;
|
|
127
261
|
this.promise = new Promise(resolve => {
|
|
128
262
|
this._resolve = MaybePromise => {
|
|
@@ -190,6 +324,9 @@ function toPendingResult(state, record) {
|
|
|
190
324
|
return pending;
|
|
191
325
|
}
|
|
192
326
|
function announceIfNeeded(record, state, pending) {
|
|
327
|
+
if (record.cancelled) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
193
330
|
const node = getPublishNode(record);
|
|
194
331
|
if (state.announced.has(node)) {
|
|
195
332
|
return;
|
|
@@ -222,6 +359,9 @@ function getCompletedIncrementalResults(exeContext) {
|
|
|
222
359
|
continue;
|
|
223
360
|
}
|
|
224
361
|
deleteAsyncPayload(exeContext, asyncPayloadRecord);
|
|
362
|
+
if (asyncPayloadRecord.cancelled) {
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
225
365
|
if (isStreamPayload(asyncPayloadRecord)) {
|
|
226
366
|
const items = asyncPayloadRecord.items;
|
|
227
367
|
if (asyncPayloadRecord.isCompletedIterator) {
|
|
@@ -231,8 +371,12 @@ function getCompletedIncrementalResults(exeContext) {
|
|
|
231
371
|
incrementalResult.items = items;
|
|
232
372
|
}
|
|
233
373
|
else {
|
|
234
|
-
const data = asyncPayloadRecord.data;
|
|
235
|
-
|
|
374
|
+
const data = pruneUnmergeableIncrementalData(asyncPayloadRecord.data, asyncPayloadRecord.path, exeContext.nulledPaths);
|
|
375
|
+
if (isEmptyIncrementalData(data) && asyncPayloadRecord.errors.length === 0) {
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
incrementalResult.data =
|
|
379
|
+
data ?? null;
|
|
236
380
|
}
|
|
237
381
|
incrementalResult.path = asyncPayloadRecord.path;
|
|
238
382
|
if (asyncPayloadRecord.label) {
|
|
@@ -272,13 +416,22 @@ function buildSubsequentV02(exeContext, completedRecords) {
|
|
|
272
416
|
continue;
|
|
273
417
|
}
|
|
274
418
|
const id = ensureId(state, record);
|
|
275
|
-
if (record.
|
|
419
|
+
if (record.cancelled) {
|
|
420
|
+
completed.push({ id });
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
const data = pruneUnmergeableIncrementalData(record.data, record.path, exeContext.nulledPaths);
|
|
424
|
+
if ((record.data == null && record.errors.length > 0) ||
|
|
425
|
+
(isEmptyIncrementalData(data) && record.errors.length > 0)) {
|
|
276
426
|
completed.push({ id, errors: record.errors });
|
|
277
427
|
}
|
|
428
|
+
else if (isEmptyIncrementalData(data) && record.errors.length === 0) {
|
|
429
|
+
completed.push({ id });
|
|
430
|
+
}
|
|
278
431
|
else {
|
|
279
432
|
const incrementalResult = {
|
|
280
433
|
id,
|
|
281
|
-
data:
|
|
434
|
+
data: data ?? null,
|
|
282
435
|
};
|
|
283
436
|
if (record.errors.length > 0) {
|
|
284
437
|
incrementalResult.errors = record.errors;
|