Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/execution/execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -798,7 +798,7 @@ function executeField(
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
): PromiseOrValue<GraphQLWrappedResult<unknown>> | undefined {
const validatedExecutionArgs = exeContext.validatedExecutionArgs;
const { schema, contextValue, variableValues, hideSuggestions } =
const { schema, contextValue, variableValues, hideSuggestions, abortSignal } =
validatedExecutionArgs;
const fieldName = fieldDetailsList[0].node.name.value;
const fieldDef = schema.getField(parentType, fieldName);
Expand Down Expand Up @@ -833,7 +833,7 @@ function executeField(
// The resolve function's optional third argument is a context value that
// is provided to every resolve function within an execution. It is commonly
// used to represent an authenticated user, or request-specific caches.
const result = resolveFn(source, args, contextValue, info);
const result = resolveFn(source, args, contextValue, info, abortSignal);

if (isPromise(result)) {
return completePromisedValue(
Expand Down Expand Up @@ -2115,6 +2115,7 @@ function executeSubscription(
operation,
variableValues,
hideSuggestions,
abortSignal,
} = validatedExecutionArgs;

const rootType = schema.getSubscriptionType();
Expand Down Expand Up @@ -2180,7 +2181,7 @@ function executeSubscription(
// The resolve function's optional third argument is a context value that
// is provided to every resolve function within an execution. It is commonly
// used to represent an authenticated user, or request-specific caches.
const result = resolveFn(rootValue, args, contextValue, info);
const result = resolveFn(rootValue, args, contextValue, info, abortSignal);

if (isPromise(result)) {
return result
Expand Down
1 change: 1 addition & 0 deletions src/type/definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -990,6 +990,7 @@ export type GraphQLFieldResolver<
args: TArgs,
context: TContext,
info: GraphQLResolveInfo,
abortSignal: AbortSignal | undefined,
Copy link
Contributor

@glasser glasser Jun 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yaacovCR I'm curious — how intentional is it that this was parameter defined as abortSignal: AbortSignal | undefined vs abortSignal?: AbortSignal? This means that any call in userland to a GraphQLFieldResolver (ie, that some user code may have plucked from a schema's field.resolve) will break if it doesn't explicitly pass in the signal.

In a sense I suppose that's good, because it means wrapping code (like this in Apollo Server) has to actually notice this new feature and not break it.

But I do wonder — why can't this just go on GraphQLResolveInfo rather than being a brand new field? To me, the arguments to a GraphQL-JS resolver have always felt like:

  • the parent/source
  • the arguments
  • a grab-bag object for whatever metadata the app developer wants available to resolvers
  • a grab-bag object for whatever metadata GraphQL JS wants available to resolvers

It feels like abortSignal fits perfectly fine on argument 4 without needing to have a brand new argument (which anything wrapping resolvers needs to pass through, etc). It also seems a bit more self-documenting to have the abortSignal come in an actual named field named abortSignal rather than just a 5th parameter.

(I think I suggested this back in 2022... I didn't realize the implementation didn't end up working that way.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

) => TResult;

export interface GraphQLResolveInfo {
Expand Down
Loading