diff --git a/src/execution/__tests__/executor-test.ts b/src/execution/__tests__/executor-test.ts index 5283aa4de5..7fbdebfdb9 100644 --- a/src/execution/__tests__/executor-test.ts +++ b/src/execution/__tests__/executor-test.ts @@ -18,6 +18,7 @@ import { GraphQLUnionType, } from '../../type/definition'; +import { Executor } from '../executor'; import { execute, executeSync } from '../execute'; describe('Execute: Handles basic execution tasks', () => { @@ -1151,6 +1152,32 @@ describe('Execute: Handles basic execution tasks', () => { expect(result).to.deep.equal({ data: { foo: null } }); }); + it('uses a custom Executor', () => { + const schema = new GraphQLSchema({ + query: new GraphQLObjectType({ + name: 'Query', + fields: { + foo: { type: GraphQLString }, + }, + }), + }); + const document = parse('{ foo }'); + + class CustomExecutor extends Executor { + executeField() { + return 'foo'; + } + } + + const result = executeSync({ + schema, + document, + CustomExecutor, + }); + + expect(result).to.deep.equal({ data: { foo: 'foo' } }); + }); + it('uses a custom field resolver', () => { const schema = new GraphQLSchema({ query: new GraphQLObjectType({ diff --git a/src/execution/execute.ts b/src/execution/execute.ts index e55fe6f1b0..4c588b5e9b 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -97,6 +97,7 @@ export interface ExecutionArgs { operationName?: Maybe; fieldResolver?: Maybe>; typeResolver?: Maybe>; + CustomExecutor?: Maybe; } /** @@ -119,6 +120,7 @@ export function execute(args: ExecutionArgs): PromiseOrValue { operationName, fieldResolver, typeResolver, + CustomExecutor, } = args; // If arguments are missing or incorrect, throw an error. @@ -142,7 +144,7 @@ export function execute(args: ExecutionArgs): PromiseOrValue { return { errors: exeContext }; } - const executor = new Executor(exeContext); + const executor = new (CustomExecutor ?? Executor)(exeContext); // Return a Promise that will eventually resolve to the data described by // The "Response" section of the GraphQL specification. diff --git a/src/graphql.ts b/src/graphql.ts index 03e6b95882..e42f1872ce 100644 --- a/src/graphql.ts +++ b/src/graphql.ts @@ -14,6 +14,7 @@ import type { import type { GraphQLSchema } from './type/schema'; import { validateSchema } from './type/validate'; +import type { Executor } from './execution/executor'; import type { ExecutionResult } from './execution/execute'; import { execute } from './execution/execute'; @@ -55,6 +56,18 @@ import { execute } from './execution/execute'; * A type resolver function to use when none is provided by the schema. * If not provided, the default type resolver is used (which looks for a * `__typename` field or alternatively calls the `isTypeOf` method). + * CustomExecutor: + * A custom Executor class to allow overriding execution behavior. + * + * Note: The Executor class is exported only to assist people in + * implementing their own executors without duplicating too much code and + * should be used only as last resort for cases requiring custom execution + * or if certain features could not be contributed upstream. + * + * It is still part of the internal API and is versioned, so any changes to + * it are never considered breaking changes. If you still need to support + * multiple versions of the library, please use the `versionInfo` variable + * for version detection. */ export interface GraphQLArgs { schema: GraphQLSchema; @@ -65,6 +78,7 @@ export interface GraphQLArgs { operationName?: Maybe; fieldResolver?: Maybe>; typeResolver?: Maybe>; + CustomExecutor?: Maybe; } export function graphql(args: GraphQLArgs): Promise { @@ -99,6 +113,7 @@ function graphqlImpl(args: GraphQLArgs): PromiseOrValue { operationName, fieldResolver, typeResolver, + CustomExecutor, } = args; // Validate Schema @@ -131,5 +146,6 @@ function graphqlImpl(args: GraphQLArgs): PromiseOrValue { operationName, fieldResolver, typeResolver, + CustomExecutor, }); } diff --git a/src/index.ts b/src/index.ts index b9aec6a43a..b2bccf4deb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -299,6 +299,7 @@ export type { /** Execute GraphQL queries. */ export { + Executor, execute, executeSync, defaultFieldResolver, diff --git a/src/subscription/subscribe.ts b/src/subscription/subscribe.ts index 03e7b00516..6ee9e36c75 100644 --- a/src/subscription/subscribe.ts +++ b/src/subscription/subscribe.ts @@ -34,6 +34,7 @@ export interface SubscriptionArgs { operationName?: Maybe; fieldResolver?: Maybe>; subscribeFieldResolver?: Maybe>; + CustomExecutor?: Maybe; } /** @@ -69,6 +70,7 @@ export async function subscribe( operationName, fieldResolver, subscribeFieldResolver, + CustomExecutor, } = args; const resultOrStream = await createSourceEventStream( @@ -79,6 +81,7 @@ export async function subscribe( variableValues, operationName, subscribeFieldResolver, + CustomExecutor, ); if (!isAsyncIterable(resultOrStream)) { @@ -100,6 +103,7 @@ export async function subscribe( variableValues, operationName, fieldResolver, + CustomExecutor, }); // Map every source value to a ExecutionResult value as described above. @@ -142,6 +146,7 @@ export async function createSourceEventStream( variableValues?: Maybe<{ readonly [variable: string]: unknown }>, operationName?: Maybe, fieldResolver?: Maybe>, + CustomExecutor?: Maybe, ): Promise | ExecutionResult> { // If arguments are missing or incorrectly typed, this is an internal // developer mistake which should throw an early error. @@ -164,7 +169,7 @@ export async function createSourceEventStream( return { errors: exeContext }; } - const executor = new SubscriptionExecutor(exeContext); + const executor = new (CustomExecutor ?? SubscriptionExecutor)(exeContext); const eventStream = await executor.executeSubscription();