diff --git a/src/execution/__tests__/executor-test.ts b/src/execution/__tests__/executor-test.ts index 5283aa4de51..b3090202580 100644 --- a/src/execution/__tests__/executor-test.ts +++ b/src/execution/__tests__/executor-test.ts @@ -18,7 +18,7 @@ import { GraphQLUnionType, } from '../../type/definition'; -import { execute, executeSync } from '../execute'; +import { Executor, execute, executeSync } from '../execute'; describe('Execute: Handles basic execution tasks', () => { it('throws if no document is provided', () => { @@ -1151,6 +1151,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 1d369a9054c..345913d8e4d 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -128,6 +128,7 @@ export interface ExecutionArgs { operationName?: Maybe; fieldResolver?: Maybe>; typeResolver?: Maybe>; + CustomExecutor?: Maybe; } /** @@ -150,6 +151,7 @@ export function execute(args: ExecutionArgs): PromiseOrValue { operationName, fieldResolver, typeResolver, + CustomExecutor, } = args; // If arguments are missing or incorrect, throw an error. @@ -173,7 +175,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/execution/index.ts b/src/execution/index.ts index 5ae0706ec95..52c9aed00f8 100644 --- a/src/execution/index.ts +++ b/src/execution/index.ts @@ -1,6 +1,7 @@ export { pathToArray as responsePathAsArray } from '../jsutils/Path'; export { + Executor, execute, executeSync, defaultFieldResolver, diff --git a/src/graphql.ts b/src/graphql.ts index 03e6b95882e..c8cff1681a0 100644 --- a/src/graphql.ts +++ b/src/graphql.ts @@ -14,7 +14,7 @@ import type { import type { GraphQLSchema } from './type/schema'; import { validateSchema } from './type/validate'; -import type { ExecutionResult } from './execution/execute'; +import type { ExecutionResult, Executor } from './execution/execute'; import { execute } from './execution/execute'; /** @@ -55,6 +55,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 +77,7 @@ export interface GraphQLArgs { operationName?: Maybe; fieldResolver?: Maybe>; typeResolver?: Maybe>; + CustomExecutor?: Maybe; } export function graphql(args: GraphQLArgs): Promise { @@ -99,6 +112,7 @@ function graphqlImpl(args: GraphQLArgs): PromiseOrValue { operationName, fieldResolver, typeResolver, + CustomExecutor, } = args; // Validate Schema @@ -131,5 +145,6 @@ function graphqlImpl(args: GraphQLArgs): PromiseOrValue { operationName, fieldResolver, typeResolver, + CustomExecutor, }); } diff --git a/src/index.ts b/src/index.ts index b9aec6a43ae..b2bccf4deba 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 3f813f4dd6c..368a14a1129 100644 --- a/src/subscription/subscribe.ts +++ b/src/subscription/subscribe.ts @@ -35,6 +35,7 @@ export interface SubscriptionArgs { operationName?: Maybe; fieldResolver?: Maybe>; subscribeFieldResolver?: Maybe>; + CustomExecutor?: Maybe; } /** @@ -70,6 +71,7 @@ export async function subscribe( operationName, fieldResolver, subscribeFieldResolver, + CustomExecutor, } = args; const resultOrStream = await createSourceEventStream( @@ -80,6 +82,7 @@ export async function subscribe( variableValues, operationName, subscribeFieldResolver, + CustomExecutor, ); if (!isAsyncIterable(resultOrStream)) { @@ -101,6 +104,7 @@ export async function subscribe( variableValues, operationName, fieldResolver, + CustomExecutor, }); // Map every source value to a ExecutionResult value as described above. @@ -143,6 +147,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. @@ -165,7 +170,7 @@ export async function createSourceEventStream( return { errors: exeContext }; } - const executor = new SubscriptionExecutor(exeContext); + const executor = new (CustomExecutor ?? SubscriptionExecutor)(exeContext); const eventStream = await executor.executeSubscription();