@graphql-tools/executor 4.1.1 → 4.1.2-alpha-20261020172239-6aecb886558928f54033e77a926c32d55e3e81cc

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.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.defaultFieldResolver = exports.defaultTypeResolver = exports.CRITICAL_ERROR = void 0;
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 ?? exports.defaultFieldResolver,
333
- typeResolver: typeResolver ?? exports.defaultTypeResolver,
334
- subscribeFieldResolver: subscribeFieldResolver ?? exports.defaultFieldResolver,
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,125 @@ 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
+ ensureIncrementalCollectState(exeContext);
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, exeContext.deferUsageByFieldNode);
404
420
  if (operation.operation === 'mutation') {
405
- result = executeFieldsSerially(exeContext, rootType, rootValue, path, rootFields);
421
+ result = executeFieldsSerially(exeContext, rootType, rootValue, path, plannedRootFields);
406
422
  }
407
423
  else {
408
- result = executeFields(exeContext, rootType, rootValue, path, rootFields);
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 remainingGroups = new Map();
435
+ const parts = new Map();
436
+ const groups = [...newGroupedFieldSets.entries()].sort(([a], [b]) => deferUsageDepth((0, incrementalCollect_js_1.rootDeferUsage)(a)) - deferUsageDepth((0, incrementalCollect_js_1.rootDeferUsage)(b)));
437
+ for (const [deferUsageSet] of groups) {
438
+ for (const usage of deferUsageSet) {
439
+ remainingGroups.set(usage, (remainingGroups.get(usage) ?? 0) + 1);
440
+ }
441
+ }
442
+ const usages = [...remainingGroups.keys()].sort((a, b) => deferUsageDepth(a) - deferUsageDepth(b));
443
+ for (const usage of usages) {
444
+ const deliveryParent = (usage.parentDeferUsage && records.get(usage.parentDeferUsage)) ?? parentContext;
445
+ records.set(usage, new incremental_js_1.DeferredFragmentRecord({
446
+ label: usage.label,
447
+ path,
448
+ parentContext,
449
+ deliveryParent,
450
+ exeContext,
451
+ deferUsage: usage,
452
+ }));
453
+ }
454
+ for (const [deferUsageSet, groupedFieldSet] of groups) {
455
+ const fields = (0, incrementalCollect_js_1.groupedFieldSetToFieldMap)(groupedFieldSet, exeContext.deferUsageByFieldNode);
456
+ const fieldUsageStore = (exeContext.deferUsagesForFieldNodes ??= new WeakMap());
457
+ for (const fieldNodes of fields.values()) {
458
+ fieldUsageStore.set(fieldNodes, deferUsageSet);
459
+ }
460
+ const primaryUsage = (0, incrementalCollect_js_1.rootDeferUsage)(deferUsageSet);
461
+ const primaryRecord = primaryUsage ? records.get(primaryUsage) : undefined;
462
+ const data = executeDeferredGroupedFields(exeContext, parentType, sourceValue, path, fields, primaryRecord, [...deferUsageSet].map(usage => records.get(usage)).filter(record => record != null));
463
+ for (const usage of deferUsageSet) {
464
+ const list = parts.get(usage) ?? [];
465
+ list.push(data);
466
+ parts.set(usage, list);
467
+ const left = (remainingGroups.get(usage) ?? 1) - 1;
468
+ remainingGroups.set(usage, left);
469
+ if (left === 0) {
470
+ records.get(usage)?.addData(mergeDeferredParts(list));
471
+ }
472
+ }
473
+ }
474
+ }
475
+ function executeDeferredGroupedFields(exeContext, parentType, sourceValue, path, fields, primaryRecord, records) {
476
+ try {
477
+ const data = executeFields(exeContext, parentType, sourceValue, path, fields, primaryRecord);
478
+ if ((0, promise_helpers_1.isPromise)(data)) {
479
+ return data.then(null, e => {
480
+ for (const record of records) {
481
+ record.errors.push(e);
482
+ }
483
+ return null;
484
+ });
485
+ }
486
+ return data;
487
+ }
488
+ catch (e) {
489
+ for (const record of records) {
490
+ record.errors.push(e);
491
+ }
492
+ return null;
493
+ }
494
+ }
495
+ function mergeDeferredParts(parts) {
496
+ if (parts.some(promise_helpers_1.isPromise)) {
497
+ return Promise.all(parts).then(mergeDeferredObjects);
498
+ }
499
+ return mergeDeferredObjects(parts);
500
+ }
501
+ function mergeDeferredObjects(parts) {
502
+ const result = Object.create(null);
503
+ let sawObject = false;
504
+ for (const part of parts) {
505
+ if (part == null) {
506
+ continue;
507
+ }
508
+ sawObject = true;
509
+ Object.assign(result, part);
510
+ }
511
+ return sawObject ? result : null;
512
+ }
513
+ function ensureIncrementalCollectState(exeContext) {
514
+ exeContext.nulledPaths ??= [];
515
+ exeContext.deferUsageByFieldNode ??= new WeakMap();
516
+ }
517
+ function deferUsageDepth(usage) {
518
+ let depth = 0;
519
+ let current = usage;
520
+ while (current?.parentDeferUsage) {
521
+ depth++;
522
+ current = current.parentDeferUsage;
523
+ }
524
+ return depth;
525
+ }
416
526
  /**
417
527
  * Implements the "Executing selection sets" section of the spec
418
528
  * for fields that must be executed serially.
@@ -886,71 +996,41 @@ function invalidReturnTypeError(returnType, result, fieldNodes) {
886
996
  function collectAndExecuteSubfields(exeContext, returnType, fieldNodes, path, result, asyncPayloadRecord) {
887
997
  // Collect sub-fields to execute to complete this value.
888
998
  const { fields: subFieldNodes, patches: subPatches } = collectSubfields(exeContext, returnType, fieldNodes);
889
- const subFields = executeFields(exeContext, returnType, result, path, subFieldNodes, asyncPayloadRecord);
890
- for (const subPatch of subPatches) {
891
- const { label, fields: subPatchFieldNodes, patches: nestedPatches } = subPatch;
892
- executeDeferredFragment(exeContext, returnType, result, subPatchFieldNodes, label, path, asyncPayloadRecord, nestedPatches);
893
- }
894
- return subFields;
895
- }
896
- /**
897
- * If a resolveType function is not given, then a default resolve behavior is
898
- * used which attempts two strategies:
899
- *
900
- * First, See if the provided value has a `__typename` field defined, if so, use
901
- * that value as name of the resolved type.
902
- *
903
- * Otherwise, test each possible type for the abstract type by calling
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
- }
999
+ if (subPatches.length === 0 && exeContext.nulledPaths == null) {
1000
+ return executeFields(exeContext, returnType, result, path, subFieldNodes, asyncPayloadRecord);
1001
+ }
1002
+ ensureIncrementalCollectState(exeContext);
1003
+ const parentDeferUsage = asyncPayloadRecord?.type === 'defer' ? asyncPayloadRecord.deferUsage : undefined;
1004
+ const subfieldCache = (exeContext.incrementalSubfieldCache ??= new WeakMap());
1005
+ let collected = subfieldCache.get(fieldNodes);
1006
+ if (collected == null) {
1007
+ collected = (0, incrementalCollect_js_1.collectIncrementalSubfields)(exeContext.schema, exeContext.fragments, exeContext.variableValues, returnType, fieldNodes, parentDeferUsage, exeContext.deferUsageByFieldNode);
1008
+ subfieldCache.set(fieldNodes, collected);
1009
+ }
1010
+ const { groupedFieldSet } = collected;
1011
+ const parentDeferUsages = new Set();
1012
+ const plannedUsages = exeContext.deferUsagesForFieldNodes?.get(fieldNodes);
1013
+ if (plannedUsages) {
1014
+ for (const usage of plannedUsages) {
1015
+ parentDeferUsages.add(usage);
924
1016
  }
925
1017
  }
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
- }
932
- }
933
- });
1018
+ else if (parentDeferUsage) {
1019
+ parentDeferUsages.add(parentDeferUsage);
934
1020
  }
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);
1021
+ else if (asyncPayloadRecord?.type === 'stream') {
1022
+ for (const node of fieldNodes) {
1023
+ const usage = exeContext.deferUsageByFieldNode?.get(node);
1024
+ if (usage) {
1025
+ parentDeferUsages.add(usage);
1026
+ }
949
1027
  }
950
- return property;
951
1028
  }
952
- };
953
- exports.defaultFieldResolver = defaultFieldResolver;
1029
+ const { groupedFieldSet: initialFields, newGroupedFieldSets } = (0, incrementalCollect_js_1.buildExecutionPlan)(groupedFieldSet, parentDeferUsages);
1030
+ const subFields = executeFields(exeContext, returnType, result, path, (0, incrementalCollect_js_1.groupedFieldSetToFieldMap)(initialFields, exeContext.deferUsageByFieldNode), asyncPayloadRecord);
1031
+ executeDeferredGroups(exeContext, returnType, result, path, newGroupedFieldSets, asyncPayloadRecord);
1032
+ return subFields;
1033
+ }
954
1034
  /**
955
1035
  * Implements the "Subscribe" algorithm described in the GraphQL specification,
956
1036
  * including `@defer` and `@stream` as proposed in
@@ -1158,37 +1238,6 @@ function assertEventStream(result, signal, onSignalAbort) {
1158
1238
  }
1159
1239
  return result;
1160
1240
  }
1161
- function executeDeferredFragment(exeContext, parentType, sourceValue, fields, label, path, parentContext, nestedPatches, deliveryParent) {
1162
- const asyncPayloadRecord = new incremental_js_1.DeferredFragmentRecord({
1163
- label,
1164
- path,
1165
- parentContext,
1166
- deliveryParent: deliveryParent ?? parentContext,
1167
- exeContext,
1168
- });
1169
- let promiseOrData;
1170
- try {
1171
- promiseOrData = executeFields(exeContext, parentType, sourceValue, path, fields, asyncPayloadRecord);
1172
- if ((0, promise_helpers_1.isPromise)(promiseOrData)) {
1173
- promiseOrData = promiseOrData.then(null, e => {
1174
- asyncPayloadRecord.errors.push(e);
1175
- return null;
1176
- });
1177
- }
1178
- }
1179
- catch (e) {
1180
- asyncPayloadRecord.errors.push(e);
1181
- promiseOrData = null;
1182
- }
1183
- asyncPayloadRecord.addData(promiseOrData);
1184
- if (nestedPatches) {
1185
- for (const nested of nestedPatches) {
1186
- executeDeferredFragment(exeContext, parentType, sourceValue, nested.fields, nested.label, path,
1187
- // Keep v0.1 sibling completion; v0.2 pending still nests via deliveryParent.
1188
- parentContext, nested.patches, asyncPayloadRecord);
1189
- }
1190
- }
1191
- }
1192
1241
  function executeStreamField(path, itemPath, item, exeContext, fieldNodes, info, itemType, label, parentContext, streamGroup) {
1193
1242
  const asyncPayloadRecord = new incremental_js_1.StreamRecord({
1194
1243
  label,
@@ -1197,6 +1246,10 @@ function executeStreamField(path, itemPath, item, exeContext, fieldNodes, info,
1197
1246
  exeContext,
1198
1247
  streamGroup,
1199
1248
  });
1249
+ if ((0, incremental_js_1.isAtOrUnderNulledPath)(asyncPayloadRecord.path, exeContext.nulledPaths)) {
1250
+ (0, incremental_js_1.cancelAsyncPayload)(exeContext, asyncPayloadRecord);
1251
+ return asyncPayloadRecord;
1252
+ }
1200
1253
  let completedItem;
1201
1254
  try {
1202
1255
  try {
@@ -1299,6 +1352,15 @@ async function executeStreamIterator(initialIndex, iterator, exeContext, fieldNo
1299
1352
  exeContext,
1300
1353
  streamGroup,
1301
1354
  });
1355
+ if ((0, incremental_js_1.isAtOrUnderNulledPath)(asyncPayloadRecord.path, exeContext.nulledPaths)) {
1356
+ (0, incremental_js_1.cancelAsyncPayload)(exeContext, asyncPayloadRecord);
1357
+ if (iterator.return) {
1358
+ iterator.return().catch(() => {
1359
+ // ignore errors
1360
+ });
1361
+ }
1362
+ break;
1363
+ }
1302
1364
  let iteration;
1303
1365
  try {
1304
1366
  iteration = await executeStreamIteratorItem(iterator, exeContext, fieldNodes, info, itemType, asyncPayloadRecord, itemPath);
@@ -1340,24 +1402,22 @@ async function executeStreamIterator(initialIndex, iterator, exeContext, fieldNo
1340
1402
  }
1341
1403
  function filterSubsequentPayloads(exeContext, nullPath, currentAsyncRecord) {
1342
1404
  const nullPathArray = (0, utils_1.pathToArray)(nullPath);
1405
+ if (exeContext.nulledPaths) {
1406
+ exeContext.nulledPaths.push(nullPathArray);
1407
+ }
1408
+ else if (exeContext.subsequentPayloads.size === 0) {
1409
+ return;
1410
+ }
1343
1411
  exeContext.subsequentPayloads.forEach(asyncRecord => {
1344
- if (asyncRecord === currentAsyncRecord) {
1412
+ if (asyncRecord === currentAsyncRecord || asyncRecord.cancelled) {
1345
1413
  // don't remove payload from where error originates
1346
1414
  return;
1347
1415
  }
1348
- for (let i = 0; i < nullPathArray.length; i++) {
1349
- if (asyncRecord.path[i] !== nullPathArray[i]) {
1350
- // asyncRecord points to a path unaffected by this payload
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
- });
1416
+ if (!(0, incremental_js_1.pathStartsWith)(asyncRecord.path, nullPathArray)) {
1417
+ // ancestor or a different branch: ancestor patches are pruned at yield
1418
+ return;
1359
1419
  }
1360
- (0, incremental_js_1.deleteAsyncPayload)(exeContext, asyncRecord);
1420
+ (0, incremental_js_1.cancelAsyncPayload)(exeContext, asyncRecord);
1361
1421
  });
1362
1422
  }
1363
1423
  /**
@@ -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,122 @@ 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
+ return (typeof data === 'object' &&
104
+ !Array.isArray(data) &&
105
+ Object.keys(data).length === 0);
106
+ }
107
+ /**
108
+ * Drop keys that would write into a subtree already nulled by error bubbling.
109
+ * Ancestor deferred records can still deliver sibling fields (e.g. `someField`
110
+ * next to a nulled `b.c`).
111
+ */
112
+ function pruneUnmergeableIncrementalData(data, basePath, nulledPaths) {
113
+ if (nulledPaths == null || nulledPaths.length === 0 || data == null || typeof data !== 'object') {
114
+ return data;
115
+ }
116
+ if (Array.isArray(data)) {
117
+ return data.map((item, index) => {
118
+ const itemPath = [...basePath, index];
119
+ if (isAtOrUnderNulledPath(itemPath, nulledPaths)) {
120
+ return null;
121
+ }
122
+ return pruneUnmergeableIncrementalData(item, itemPath, nulledPaths);
123
+ });
124
+ }
125
+ const result = {};
126
+ for (const [key, value] of Object.entries(data)) {
127
+ const childPath = [...basePath, key];
128
+ if (isStrictlyUnderNulledPath(childPath, nulledPaths)) {
129
+ continue;
130
+ }
131
+ if (isExactNulledPath(childPath, nulledPaths)) {
132
+ // Keep a null delivered by this payload; drop a non-null write into an
133
+ // already-nulled object (Yaacov: unmergeable incremental).
134
+ if (value != null) {
135
+ continue;
136
+ }
137
+ result[key] = null;
138
+ continue;
139
+ }
140
+ result[key] = pruneUnmergeableIncrementalData(value, childPath, nulledPaths);
141
+ }
142
+ return result;
143
+ }
144
+ function isAnnounced(exeContext, record) {
145
+ const state = exeContext.publisher;
146
+ if (state == null) {
147
+ return false;
148
+ }
149
+ return state.announced.has(getPublishNode(record));
150
+ }
151
+ /**
152
+ * Cancel a subsequent record whose path is at or under a nulled position
153
+ * (graphql-js 17 `hasNulledPosition`). v0.2 records that were already
154
+ * announced stay in the set so the publisher can emit `completed` without
155
+ * incremental data.
156
+ */
157
+ function cancelAsyncPayload(exeContext, record) {
158
+ record.cancelled = true;
159
+ if (isStreamPayload(record) && record.iterator?.return) {
160
+ record.iterator.return().catch(() => {
161
+ // ignore error
162
+ });
163
+ }
164
+ if (!record.isCompleted) {
165
+ if (isStreamPayload(record)) {
166
+ record.addItems(null);
167
+ }
168
+ else {
169
+ record.addData(null);
170
+ }
171
+ }
172
+ if (exeContext.incrementalSpec === 'v0.2' && isAnnounced(exeContext, record)) {
173
+ return;
174
+ }
175
+ deleteAsyncPayload(exeContext, record);
176
+ }
57
177
  class DeferredFragmentRecord {
58
178
  type;
59
179
  errors;
@@ -63,7 +183,9 @@ class DeferredFragmentRecord {
63
183
  data;
64
184
  parentContext;
65
185
  deliveryParent;
186
+ deferUsage;
66
187
  isCompleted;
188
+ cancelled;
67
189
  _exeContext;
68
190
  _resolve;
69
191
  constructor(opts) {
@@ -72,10 +194,12 @@ class DeferredFragmentRecord {
72
194
  this.path = (0, utils_1.pathToArray)(opts.path);
73
195
  this.parentContext = opts.parentContext;
74
196
  this.deliveryParent = opts.deliveryParent ?? opts.parentContext;
197
+ this.deferUsage = opts.deferUsage;
75
198
  this.errors = [];
76
199
  this._exeContext = opts.exeContext;
77
200
  this._exeContext.subsequentPayloads.add(this);
78
201
  this.isCompleted = false;
202
+ this.cancelled = false;
79
203
  this.data = null;
80
204
  this.promise = new Promise(resolve => {
81
205
  this._resolve = MaybePromise => {
@@ -107,6 +231,7 @@ class StreamRecord {
107
231
  iterator;
108
232
  isCompletedIterator;
109
233
  isCompleted;
234
+ cancelled;
110
235
  streamGroup;
111
236
  _exeContext;
112
237
  _resolve;
@@ -123,6 +248,7 @@ class StreamRecord {
123
248
  this._exeContext.subsequentPayloads.add(this);
124
249
  this.streamGroup?.retain();
125
250
  this.isCompleted = false;
251
+ this.cancelled = false;
126
252
  this.items = null;
127
253
  this.promise = new Promise(resolve => {
128
254
  this._resolve = MaybePromise => {
@@ -190,6 +316,9 @@ function toPendingResult(state, record) {
190
316
  return pending;
191
317
  }
192
318
  function announceIfNeeded(record, state, pending) {
319
+ if (record.cancelled) {
320
+ return;
321
+ }
193
322
  const node = getPublishNode(record);
194
323
  if (state.announced.has(node)) {
195
324
  return;
@@ -222,6 +351,9 @@ function getCompletedIncrementalResults(exeContext) {
222
351
  continue;
223
352
  }
224
353
  deleteAsyncPayload(exeContext, asyncPayloadRecord);
354
+ if (asyncPayloadRecord.cancelled) {
355
+ continue;
356
+ }
225
357
  if (isStreamPayload(asyncPayloadRecord)) {
226
358
  const items = asyncPayloadRecord.items;
227
359
  if (asyncPayloadRecord.isCompletedIterator) {
@@ -231,8 +363,12 @@ function getCompletedIncrementalResults(exeContext) {
231
363
  incrementalResult.items = items;
232
364
  }
233
365
  else {
234
- const data = asyncPayloadRecord.data;
235
- incrementalResult.data = data ?? null;
366
+ const data = pruneUnmergeableIncrementalData(asyncPayloadRecord.data, asyncPayloadRecord.path, exeContext.nulledPaths);
367
+ if (isEmptyIncrementalData(data) && asyncPayloadRecord.errors.length === 0) {
368
+ continue;
369
+ }
370
+ incrementalResult.data =
371
+ data ?? null;
236
372
  }
237
373
  incrementalResult.path = asyncPayloadRecord.path;
238
374
  if (asyncPayloadRecord.label) {
@@ -272,13 +408,22 @@ function buildSubsequentV02(exeContext, completedRecords) {
272
408
  continue;
273
409
  }
274
410
  const id = ensureId(state, record);
275
- if (record.data == null && record.errors.length > 0) {
411
+ if (record.cancelled) {
412
+ completed.push({ id });
413
+ continue;
414
+ }
415
+ const data = pruneUnmergeableIncrementalData(record.data, record.path, exeContext.nulledPaths);
416
+ if ((record.data == null && record.errors.length > 0) ||
417
+ (isEmptyIncrementalData(data) && record.errors.length > 0)) {
276
418
  completed.push({ id, errors: record.errors });
277
419
  }
420
+ else if (isEmptyIncrementalData(data) && record.errors.length === 0) {
421
+ completed.push({ id });
422
+ }
278
423
  else {
279
424
  const incrementalResult = {
280
425
  id,
281
- data: record.data ?? null,
426
+ data: data ?? null,
282
427
  };
283
428
  if (record.errors.length > 0) {
284
429
  incrementalResult.errors = record.errors;