@graphql-tools/executor 1.4.2 → 1.4.3-alpha-20250226150456-c866968c9e16e4eb2d1460dcbf97397c4a27ca60

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.
@@ -150,6 +150,7 @@ exports.getFragmentsFromDocument = (0, utils_1.memoize1)(function getFragmentsFr
150
150
  */
151
151
  function buildExecutionContext(args) {
152
152
  const { schema, document, rootValue, contextValue, variableValues: rawVariableValues, operationName, fieldResolver, typeResolver, subscribeFieldResolver, signal, } = args;
153
+ signal?.throwIfAborted();
153
154
  // If the schema used for execution is invalid, throw an error.
154
155
  (0, graphql_1.assertValidSchema)(schema);
155
156
  const fragments = (0, exports.getFragmentsFromDocument)(document);
@@ -204,6 +205,28 @@ function buildExecutionContext(args) {
204
205
  if (coercedVariableValues.errors) {
205
206
  return coercedVariableValues.errors;
206
207
  }
208
+ signal?.throwIfAborted();
209
+ let onSignalAbort;
210
+ let signalPromise;
211
+ if (signal) {
212
+ const listeners = new Set();
213
+ const signalDeferred = (0, promise_helpers_1.createDeferredPromise)();
214
+ signalPromise = signalDeferred.promise;
215
+ const sharedListener = () => {
216
+ signalDeferred.reject(signal.reason);
217
+ signal.removeEventListener('abort', sharedListener);
218
+ };
219
+ signal.addEventListener('abort', sharedListener, { once: true });
220
+ signalPromise.catch(() => {
221
+ for (const listener of listeners) {
222
+ listener();
223
+ }
224
+ listeners.clear();
225
+ });
226
+ onSignalAbort = handler => {
227
+ listeners.add(handler);
228
+ };
229
+ }
207
230
  return {
208
231
  schema,
209
232
  fragments,
@@ -217,6 +240,8 @@ function buildExecutionContext(args) {
217
240
  subsequentPayloads: new Set(),
218
241
  errors: [],
219
242
  signal,
243
+ onSignalAbort,
244
+ signalPromise,
220
245
  };
221
246
  }
222
247
  function buildPerEventExecutionContext(exeContext, payload) {
@@ -291,9 +316,9 @@ function executeFields(exeContext, parentType, sourceValue, path, fields, asyncP
291
316
  }
292
317
  }
293
318
  catch (error) {
294
- if (containsPromise) {
319
+ if (error !== exeContext.signal?.reason && containsPromise) {
295
320
  // Ensure that any promises returned by other fields are handled, as they may also reject.
296
- return (0, promiseForObject_js_1.promiseForObject)(results, exeContext.signal).finally(() => {
321
+ return (0, promiseForObject_js_1.promiseForObject)(results, exeContext.signal, exeContext.signalPromise).finally(() => {
297
322
  throw error;
298
323
  });
299
324
  }
@@ -306,7 +331,7 @@ function executeFields(exeContext, parentType, sourceValue, path, fields, asyncP
306
331
  // Otherwise, results is a map from field name to the result of resolving that
307
332
  // field, which is possibly a promise. Return a promise that will return this
308
333
  // same map, but with any promises replaced with the values they resolved to.
309
- return (0, promiseForObject_js_1.promiseForObject)(results, exeContext.signal);
334
+ return (0, promiseForObject_js_1.promiseForObject)(results, exeContext.signal, exeContext.signalPromise);
310
335
  }
311
336
  /**
312
337
  * Implements the "Executing fields" section of the spec
@@ -325,6 +350,7 @@ function executeField(exeContext, parentType, source, fieldNodes, path, asyncPay
325
350
  const info = buildResolveInfo(exeContext, fieldDef, fieldNodes, parentType, path);
326
351
  // Get the resolve function, regardless of if its result is normal or abrupt (error).
327
352
  try {
353
+ exeContext.signal?.throwIfAborted();
328
354
  // Build a JS object of arguments from the field.arguments AST, using the
329
355
  // variables scope to fulfill any variable references.
330
356
  // TODO: find a way to memoize, in case this field is within a List type.
@@ -506,8 +532,9 @@ function getStreamValues(exeContext, fieldNodes, path) {
506
532
  * recursively until all the results are completed.
507
533
  */
508
534
  async function completeAsyncIteratorValue(exeContext, itemType, fieldNodes, info, path, iterator, asyncPayloadRecord) {
509
- if (exeContext.signal && iterator.return) {
510
- (0, utils_1.registerAbortSignalListener)(exeContext.signal, () => {
535
+ exeContext.signal?.throwIfAborted();
536
+ if (iterator.return) {
537
+ exeContext.onSignalAbort?.(() => {
511
538
  iterator.return?.();
512
539
  });
513
540
  }
@@ -949,17 +976,20 @@ function executeSubscription(exeContext) {
949
976
  const resolveFn = fieldDef.subscribe ?? exeContext.subscribeFieldResolver;
950
977
  const result = resolveFn(rootValue, args, contextValue, info);
951
978
  if ((0, utils_1.isPromise)(result)) {
952
- return result.then(assertEventStream).then(undefined, error => {
979
+ return result
980
+ .then(result => assertEventStream(result, exeContext.signal, exeContext.onSignalAbort))
981
+ .then(undefined, error => {
953
982
  throw (0, graphql_1.locatedError)(error, fieldNodes, (0, utils_1.pathToArray)(path));
954
983
  });
955
984
  }
956
- return assertEventStream(result, exeContext.signal);
985
+ return assertEventStream(result, exeContext.signal, exeContext.onSignalAbort);
957
986
  }
958
987
  catch (error) {
959
988
  throw (0, graphql_1.locatedError)(error, fieldNodes, (0, utils_1.pathToArray)(path));
960
989
  }
961
990
  }
962
- function assertEventStream(result, signal) {
991
+ function assertEventStream(result, signal, onSignalAbort) {
992
+ signal?.throwIfAborted();
963
993
  if (result instanceof Error) {
964
994
  throw result;
965
995
  }
@@ -967,12 +997,12 @@ function assertEventStream(result, signal) {
967
997
  if (!(0, utils_1.isAsyncIterable)(result)) {
968
998
  throw (0, utils_1.createGraphQLError)('Subscription field must return Async Iterable. ' + `Received: ${(0, utils_1.inspect)(result)}.`);
969
999
  }
970
- if (signal) {
1000
+ if (onSignalAbort) {
971
1001
  return {
972
1002
  [Symbol.asyncIterator]() {
973
1003
  const asyncIterator = result[Symbol.asyncIterator]();
974
1004
  if (asyncIterator.return) {
975
- (0, utils_1.registerAbortSignalListener)(signal, () => {
1005
+ onSignalAbort?.(() => {
976
1006
  asyncIterator.return?.();
977
1007
  });
978
1008
  }
@@ -1200,14 +1230,13 @@ function getCompletedIncrementalResults(exeContext) {
1200
1230
  }
1201
1231
  function yieldSubsequentPayloads(exeContext) {
1202
1232
  let isDone = false;
1203
- const abortPromise = exeContext.signal ? (0, utils_1.getAbortPromise)(exeContext.signal) : undefined;
1204
1233
  async function next() {
1205
1234
  if (isDone) {
1206
1235
  return { value: undefined, done: true };
1207
1236
  }
1208
1237
  const subSequentPayloadPromises = Array.from(exeContext.subsequentPayloads).map(record => record.promise);
1209
- if (abortPromise) {
1210
- await Promise.race([abortPromise, ...subSequentPayloadPromises]);
1238
+ if (exeContext.signalPromise) {
1239
+ await Promise.race([exeContext.signalPromise, ...subSequentPayloadPromises]);
1211
1240
  }
1212
1241
  else {
1213
1242
  await Promise.race(subSequentPayloadPromises);
@@ -9,14 +9,25 @@ const utils_1 = require("@graphql-tools/utils");
9
9
  * This is akin to bluebird's `Promise.props`, but implemented only using
10
10
  * `Promise.all` so it will work with any implementation of ES6 promises.
11
11
  */
12
- async function promiseForObject(object, signal) {
12
+ function promiseForObject(object, signal, signalPromise) {
13
+ signal?.throwIfAborted();
13
14
  const resolvedObject = Object.create(null);
14
- const promises = Promise.all(Object.entries(object).map(async ([key, value]) => {
15
- resolvedObject[key] = await value;
16
- }));
17
- if (signal) {
18
- const abortPromise = (0, utils_1.getAbortPromise)(signal);
19
- return Promise.race([abortPromise, promises]).then(() => resolvedObject);
15
+ const promises = [];
16
+ for (const key in object) {
17
+ const value = object[key];
18
+ if ((0, utils_1.isPromise)(value)) {
19
+ promises.push(value.then(value => {
20
+ signal?.throwIfAborted();
21
+ resolvedObject[key] = value;
22
+ }));
23
+ }
24
+ else {
25
+ resolvedObject[key] = value;
26
+ }
20
27
  }
21
- return promises.then(() => resolvedObject);
28
+ const promiseAll = Promise.all(promises);
29
+ if (signalPromise) {
30
+ return Promise.race([signalPromise, promiseAll]).then(() => resolvedObject);
31
+ }
32
+ return promiseAll.then(() => resolvedObject);
22
33
  }
@@ -1,7 +1,7 @@
1
1
  import { assertValidSchema, getDirectiveValues, GraphQLError, isAbstractType, isLeafType, isListType, isNonNullType, isObjectType, Kind, locatedError, SchemaMetaFieldDef, TypeMetaFieldDef, TypeNameMetaFieldDef, versionInfo, } from 'graphql';
2
- import { collectSubFields as _collectSubfields, addPath, collectFields, createGraphQLError, fakePromise, getAbortPromise, getArgumentValues, getDefinedRootType, GraphQLStreamDirective, inspect, isAsyncIterable, isIterableObject, isObjectLike, isPromise, mapAsyncIterator, memoize1, memoize3, pathToArray, promiseReduce, registerAbortSignalListener, } from '@graphql-tools/utils';
2
+ import { collectSubFields as _collectSubfields, addPath, collectFields, createGraphQLError, fakePromise, getArgumentValues, getDefinedRootType, GraphQLStreamDirective, inspect, isAsyncIterable, isIterableObject, isObjectLike, isPromise, mapAsyncIterator, memoize1, memoize3, pathToArray, promiseReduce, } from '@graphql-tools/utils';
3
3
  import { DisposableSymbols } from '@whatwg-node/disposablestack';
4
- import { handleMaybePromise } from '@whatwg-node/promise-helpers';
4
+ import { createDeferredPromise, handleMaybePromise } from '@whatwg-node/promise-helpers';
5
5
  import { coerceError } from './coerceError.js';
6
6
  import { flattenAsyncIterable } from './flattenAsyncIterable.js';
7
7
  import { invariant } from './invariant.js';
@@ -137,6 +137,7 @@ export const getFragmentsFromDocument = memoize1(function getFragmentsFromDocume
137
137
  */
138
138
  export function buildExecutionContext(args) {
139
139
  const { schema, document, rootValue, contextValue, variableValues: rawVariableValues, operationName, fieldResolver, typeResolver, subscribeFieldResolver, signal, } = args;
140
+ signal?.throwIfAborted();
140
141
  // If the schema used for execution is invalid, throw an error.
141
142
  assertValidSchema(schema);
142
143
  const fragments = getFragmentsFromDocument(document);
@@ -191,6 +192,28 @@ export function buildExecutionContext(args) {
191
192
  if (coercedVariableValues.errors) {
192
193
  return coercedVariableValues.errors;
193
194
  }
195
+ signal?.throwIfAborted();
196
+ let onSignalAbort;
197
+ let signalPromise;
198
+ if (signal) {
199
+ const listeners = new Set();
200
+ const signalDeferred = createDeferredPromise();
201
+ signalPromise = signalDeferred.promise;
202
+ const sharedListener = () => {
203
+ signalDeferred.reject(signal.reason);
204
+ signal.removeEventListener('abort', sharedListener);
205
+ };
206
+ signal.addEventListener('abort', sharedListener, { once: true });
207
+ signalPromise.catch(() => {
208
+ for (const listener of listeners) {
209
+ listener();
210
+ }
211
+ listeners.clear();
212
+ });
213
+ onSignalAbort = handler => {
214
+ listeners.add(handler);
215
+ };
216
+ }
194
217
  return {
195
218
  schema,
196
219
  fragments,
@@ -204,6 +227,8 @@ export function buildExecutionContext(args) {
204
227
  subsequentPayloads: new Set(),
205
228
  errors: [],
206
229
  signal,
230
+ onSignalAbort,
231
+ signalPromise,
207
232
  };
208
233
  }
209
234
  function buildPerEventExecutionContext(exeContext, payload) {
@@ -278,9 +303,9 @@ function executeFields(exeContext, parentType, sourceValue, path, fields, asyncP
278
303
  }
279
304
  }
280
305
  catch (error) {
281
- if (containsPromise) {
306
+ if (error !== exeContext.signal?.reason && containsPromise) {
282
307
  // Ensure that any promises returned by other fields are handled, as they may also reject.
283
- return promiseForObject(results, exeContext.signal).finally(() => {
308
+ return promiseForObject(results, exeContext.signal, exeContext.signalPromise).finally(() => {
284
309
  throw error;
285
310
  });
286
311
  }
@@ -293,7 +318,7 @@ function executeFields(exeContext, parentType, sourceValue, path, fields, asyncP
293
318
  // Otherwise, results is a map from field name to the result of resolving that
294
319
  // field, which is possibly a promise. Return a promise that will return this
295
320
  // same map, but with any promises replaced with the values they resolved to.
296
- return promiseForObject(results, exeContext.signal);
321
+ return promiseForObject(results, exeContext.signal, exeContext.signalPromise);
297
322
  }
298
323
  /**
299
324
  * Implements the "Executing fields" section of the spec
@@ -312,6 +337,7 @@ function executeField(exeContext, parentType, source, fieldNodes, path, asyncPay
312
337
  const info = buildResolveInfo(exeContext, fieldDef, fieldNodes, parentType, path);
313
338
  // Get the resolve function, regardless of if its result is normal or abrupt (error).
314
339
  try {
340
+ exeContext.signal?.throwIfAborted();
315
341
  // Build a JS object of arguments from the field.arguments AST, using the
316
342
  // variables scope to fulfill any variable references.
317
343
  // TODO: find a way to memoize, in case this field is within a List type.
@@ -493,8 +519,9 @@ function getStreamValues(exeContext, fieldNodes, path) {
493
519
  * recursively until all the results are completed.
494
520
  */
495
521
  async function completeAsyncIteratorValue(exeContext, itemType, fieldNodes, info, path, iterator, asyncPayloadRecord) {
496
- if (exeContext.signal && iterator.return) {
497
- registerAbortSignalListener(exeContext.signal, () => {
522
+ exeContext.signal?.throwIfAborted();
523
+ if (iterator.return) {
524
+ exeContext.onSignalAbort?.(() => {
498
525
  iterator.return?.();
499
526
  });
500
527
  }
@@ -934,17 +961,20 @@ function executeSubscription(exeContext) {
934
961
  const resolveFn = fieldDef.subscribe ?? exeContext.subscribeFieldResolver;
935
962
  const result = resolveFn(rootValue, args, contextValue, info);
936
963
  if (isPromise(result)) {
937
- return result.then(assertEventStream).then(undefined, error => {
964
+ return result
965
+ .then(result => assertEventStream(result, exeContext.signal, exeContext.onSignalAbort))
966
+ .then(undefined, error => {
938
967
  throw locatedError(error, fieldNodes, pathToArray(path));
939
968
  });
940
969
  }
941
- return assertEventStream(result, exeContext.signal);
970
+ return assertEventStream(result, exeContext.signal, exeContext.onSignalAbort);
942
971
  }
943
972
  catch (error) {
944
973
  throw locatedError(error, fieldNodes, pathToArray(path));
945
974
  }
946
975
  }
947
- function assertEventStream(result, signal) {
976
+ function assertEventStream(result, signal, onSignalAbort) {
977
+ signal?.throwIfAborted();
948
978
  if (result instanceof Error) {
949
979
  throw result;
950
980
  }
@@ -952,12 +982,12 @@ function assertEventStream(result, signal) {
952
982
  if (!isAsyncIterable(result)) {
953
983
  throw createGraphQLError('Subscription field must return Async Iterable. ' + `Received: ${inspect(result)}.`);
954
984
  }
955
- if (signal) {
985
+ if (onSignalAbort) {
956
986
  return {
957
987
  [Symbol.asyncIterator]() {
958
988
  const asyncIterator = result[Symbol.asyncIterator]();
959
989
  if (asyncIterator.return) {
960
- registerAbortSignalListener(signal, () => {
990
+ onSignalAbort?.(() => {
961
991
  asyncIterator.return?.();
962
992
  });
963
993
  }
@@ -1185,14 +1215,13 @@ function getCompletedIncrementalResults(exeContext) {
1185
1215
  }
1186
1216
  function yieldSubsequentPayloads(exeContext) {
1187
1217
  let isDone = false;
1188
- const abortPromise = exeContext.signal ? getAbortPromise(exeContext.signal) : undefined;
1189
1218
  async function next() {
1190
1219
  if (isDone) {
1191
1220
  return { value: undefined, done: true };
1192
1221
  }
1193
1222
  const subSequentPayloadPromises = Array.from(exeContext.subsequentPayloads).map(record => record.promise);
1194
- if (abortPromise) {
1195
- await Promise.race([abortPromise, ...subSequentPayloadPromises]);
1223
+ if (exeContext.signalPromise) {
1224
+ await Promise.race([exeContext.signalPromise, ...subSequentPayloadPromises]);
1196
1225
  }
1197
1226
  else {
1198
1227
  await Promise.race(subSequentPayloadPromises);
@@ -1,4 +1,4 @@
1
- import { getAbortPromise } from '@graphql-tools/utils';
1
+ import { isPromise } from '@graphql-tools/utils';
2
2
  /**
3
3
  * This function transforms a JS object `Record<string, Promise<T>>` into
4
4
  * a `Promise<Record<string, T>>`
@@ -6,14 +6,25 @@ import { getAbortPromise } from '@graphql-tools/utils';
6
6
  * This is akin to bluebird's `Promise.props`, but implemented only using
7
7
  * `Promise.all` so it will work with any implementation of ES6 promises.
8
8
  */
9
- export async function promiseForObject(object, signal) {
9
+ export function promiseForObject(object, signal, signalPromise) {
10
+ signal?.throwIfAborted();
10
11
  const resolvedObject = Object.create(null);
11
- const promises = Promise.all(Object.entries(object).map(async ([key, value]) => {
12
- resolvedObject[key] = await value;
13
- }));
14
- if (signal) {
15
- const abortPromise = getAbortPromise(signal);
16
- return Promise.race([abortPromise, promises]).then(() => resolvedObject);
12
+ const promises = [];
13
+ for (const key in object) {
14
+ const value = object[key];
15
+ if (isPromise(value)) {
16
+ promises.push(value.then(value => {
17
+ signal?.throwIfAborted();
18
+ resolvedObject[key] = value;
19
+ }));
20
+ }
21
+ else {
22
+ resolvedObject[key] = value;
23
+ }
17
24
  }
18
- return promises.then(() => resolvedObject);
25
+ const promiseAll = Promise.all(promises);
26
+ if (signalPromise) {
27
+ return Promise.race([signalPromise, promiseAll]).then(() => resolvedObject);
28
+ }
29
+ return promiseAll.then(() => resolvedObject);
19
30
  }
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@graphql-tools/executor",
3
- "version": "1.4.2",
3
+ "version": "1.4.3-alpha-20250226150456-c866968c9e16e4eb2d1460dcbf97397c4a27ca60",
4
4
  "sideEffects": false,
5
5
  "peerDependencies": {
6
6
  "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
7
7
  },
8
8
  "dependencies": {
9
- "@graphql-tools/utils": "^10.8.3",
9
+ "@graphql-tools/utils": "10.8.4-alpha-20250226150456-c866968c9e16e4eb2d1460dcbf97397c4a27ca60",
10
10
  "@graphql-typed-document-node/core": "^3.2.0",
11
11
  "@repeaterjs/repeater": "^3.0.4",
12
12
  "@whatwg-node/disposablestack": "^0.0.6",
@@ -44,6 +44,8 @@ export interface ExecutionContext<TVariables = any, TContext = any> {
44
44
  errors: Array<GraphQLError>;
45
45
  subsequentPayloads: Set<AsyncPayloadRecord>;
46
46
  signal?: AbortSignal;
47
+ onSignalAbort?(handler: () => void): void;
48
+ signalPromise?: Promise<never>;
47
49
  }
48
50
  export interface FormattedExecutionResult<TData = Record<string, unknown>, TExtensions = Record<string, unknown>> {
49
51
  errors?: ReadonlyArray<GraphQLFormattedError>;
@@ -44,6 +44,8 @@ export interface ExecutionContext<TVariables = any, TContext = any> {
44
44
  errors: Array<GraphQLError>;
45
45
  subsequentPayloads: Set<AsyncPayloadRecord>;
46
46
  signal?: AbortSignal;
47
+ onSignalAbort?(handler: () => void): void;
48
+ signalPromise?: Promise<never>;
47
49
  }
48
50
  export interface FormattedExecutionResult<TData = Record<string, unknown>, TExtensions = Record<string, unknown>> {
49
51
  errors?: ReadonlyArray<GraphQLFormattedError>;
@@ -8,5 +8,5 @@ type ResolvedObject<TData> = {
8
8
  * This is akin to bluebird's `Promise.props`, but implemented only using
9
9
  * `Promise.all` so it will work with any implementation of ES6 promises.
10
10
  */
11
- export declare function promiseForObject<TData>(object: TData, signal?: AbortSignal): Promise<ResolvedObject<TData>>;
11
+ export declare function promiseForObject<TData>(object: TData, signal?: AbortSignal, signalPromise?: Promise<never>): Promise<ResolvedObject<TData>>;
12
12
  export {};
@@ -8,5 +8,5 @@ type ResolvedObject<TData> = {
8
8
  * This is akin to bluebird's `Promise.props`, but implemented only using
9
9
  * `Promise.all` so it will work with any implementation of ES6 promises.
10
10
  */
11
- export declare function promiseForObject<TData>(object: TData, signal?: AbortSignal): Promise<ResolvedObject<TData>>;
11
+ export declare function promiseForObject<TData>(object: TData, signal?: AbortSignal, signalPromise?: Promise<never>): Promise<ResolvedObject<TData>>;
12
12
  export {};