diff --git a/.changeset/selfish-mugs-promise.md b/.changeset/selfish-mugs-promise.md new file mode 100644 index 00000000000..886098f843f --- /dev/null +++ b/.changeset/selfish-mugs-promise.md @@ -0,0 +1,8 @@ +--- +'@graphql-tools/executor': patch +'@graphql-tools/schema': patch +'@graphql-tools/utils': patch +'@graphql-tools/mock': patch +--- + +\`AbortSignal\` in \`GraphQLResolveInfo\`, and \`AbortSignal\` in \`ExecutionRequest\` diff --git a/packages/executor/src/execution/__tests__/executor-test.ts b/packages/executor/src/execution/__tests__/executor-test.ts index 8009d76920a..80d79c74e1b 100644 --- a/packages/executor/src/execution/__tests__/executor-test.ts +++ b/packages/executor/src/execution/__tests__/executor-test.ts @@ -206,6 +206,7 @@ describe('Execute: Handles basic execution tasks', () => { 'rootValue', 'operation', 'variableValues', + 'signal', ]); const operation = document.definitions[0]; diff --git a/packages/executor/src/execution/execute.ts b/packages/executor/src/execution/execute.ts index a8fa0ce410d..3418143e1ed 100644 --- a/packages/executor/src/execution/execute.ts +++ b/packages/executor/src/execution/execute.ts @@ -13,7 +13,6 @@ import { GraphQLList, GraphQLObjectType, GraphQLOutputType, - GraphQLResolveInfo, GraphQLSchema, GraphQLTypeResolver, isAbstractType, @@ -38,6 +37,7 @@ import { fakePromise, getArgumentValues, getDefinedRootType, + GraphQLResolveInfo, GraphQLStreamDirective, inspect, isAsyncIterable, @@ -758,6 +758,7 @@ export function buildResolveInfo( rootValue: exeContext.rootValue, operation: exeContext.operation, variableValues: exeContext.variableValues, + signal: exeContext.signal, }; } @@ -957,9 +958,13 @@ async function completeAsyncIteratorValue( iterator: AsyncIterator, asyncPayloadRecord?: AsyncPayloadRecord, ): Promise> { - exeContext.signal?.addEventListener('abort', () => { - iterator.return?.(); - }); + exeContext.signal?.addEventListener( + 'abort', + () => { + iterator.return?.(); + }, + { once: true }, + ); const errors = asyncPayloadRecord?.errors ?? exeContext.errors; const stream = getStreamValues(exeContext, fieldNodes, path); let containsPromise = false; @@ -2080,10 +2085,14 @@ function yieldSubsequentPayloads( let isDone = false; const abortPromise = new Promise((_, reject) => { - exeContext.signal?.addEventListener('abort', () => { - isDone = true; - reject(exeContext.signal?.reason); - }); + exeContext.signal?.addEventListener( + 'abort', + () => { + isDone = true; + reject(exeContext.signal?.reason); + }, + { once: true }, + ); }); async function next(): Promise> { @@ -2141,7 +2150,7 @@ function yieldSubsequentPayloads( async throw(error?: unknown): Promise> { await returnStreamIterators(); isDone = true; - return Promise.reject(error); + throw error; }, async [DisposableSymbols.asyncDispose]() { await returnStreamIterators(); diff --git a/packages/executor/src/execution/normalizedExecutor.ts b/packages/executor/src/execution/normalizedExecutor.ts index c6db16af4f3..da96e1d080e 100644 --- a/packages/executor/src/execution/normalizedExecutor.ts +++ b/packages/executor/src/execution/normalizedExecutor.ts @@ -1,6 +1,13 @@ -import { getOperationAST } from 'graphql'; +import { getOperationAST, GraphQLSchema } from 'graphql'; import { ValueOrPromise } from 'value-or-promise'; -import { ExecutionResult, MaybeAsyncIterable, MaybePromise } from '@graphql-tools/utils'; +import { + ExecutionRequest, + ExecutionResult, + Executor, + MaybeAsyncIterable, + MaybePromise, + memoize1, +} from '@graphql-tools/utils'; import { execute, ExecutionArgs, flattenIncrementalResults, subscribe } from './execute.js'; export function normalizedExecutor( @@ -22,3 +29,19 @@ export function normalizedExecutor( resolvers: Array>>, diff --git a/packages/utils/src/Interfaces.ts b/packages/utils/src/Interfaces.ts index 552cdb2da5a..298f2df0e5f 100644 --- a/packages/utils/src/Interfaces.ts +++ b/packages/utils/src/Interfaces.ts @@ -24,7 +24,6 @@ import { GraphQLNamedType, GraphQLObjectType, GraphQLOutputType, - GraphQLResolveInfo, GraphQLScalarLiteralParser, GraphQLScalarSerializer, GraphQLScalarType, @@ -40,6 +39,7 @@ import { ObjectTypeDefinitionNode, ObjectTypeExtensionNode, OperationTypeNode, + GraphQLResolveInfo as OrigGraphQLResolveInfo, ScalarTypeDefinitionNode, ScalarTypeExtensionNode, SelectionNode, @@ -68,6 +68,10 @@ export interface ExecutionResult { items?: TData | null; } +export interface GraphQLResolveInfo extends OrigGraphQLResolveInfo { + signal?: AbortSignal; +} + export interface ExecutionRequest< TVariables extends Record = any, TContext = any, @@ -86,6 +90,7 @@ export interface ExecutionRequest< // If the request originates within execution of a parent request, it may contain the parent context and info context?: TContext; info?: GraphQLResolveInfo; + signal?: AbortSignal; } // graphql-js non-exported typings diff --git a/packages/utils/src/getResponseKeyFromInfo.ts b/packages/utils/src/getResponseKeyFromInfo.ts index 4f1277a0a7f..16e02061c81 100644 --- a/packages/utils/src/getResponseKeyFromInfo.ts +++ b/packages/utils/src/getResponseKeyFromInfo.ts @@ -1,4 +1,4 @@ -import { GraphQLResolveInfo } from 'graphql'; +import { GraphQLResolveInfo } from './Interfaces.js'; /** * Get the key under which the result of this resolver will be placed in the response JSON. Basically, just