From 23421d131d58256f0f3ac8c1355590547b4d0041 Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Sun, 6 Jun 2021 16:56:09 +0300 Subject: [PATCH] feat: add CustomExecutor option to graphql, execute, and subscribe allows customization of the execution algorithm by overriding any of the protected members of the now exported internal Executor class. Reference: https://github.com/graphql/graphql-js/pull/3163#issuecomment-859546546 Note: This 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. --- src/execution/__tests__/executor-test.ts | 26 ++++++++++++++++++++++++ src/execution/execute.ts | 4 +++- src/graphql.ts | 15 ++++++++++++++ src/index.ts | 1 + src/subscription/subscribe.ts | 7 ++++++- 5 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/execution/__tests__/executor-test.ts b/src/execution/__tests__/executor-test.ts index 8dbe1d53625..7fbdebfdb95 100644 --- a/src/execution/__tests__/executor-test.ts +++ b/src/execution/__tests__/executor-test.ts @@ -1152,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 e55fe6f1b0b..4c588b5e9bf 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 2e4cc9707fc..e42f1872ce8 100644 --- a/src/graphql.ts +++ b/src/graphql.ts @@ -56,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; @@ -66,6 +78,7 @@ export interface GraphQLArgs { operationName?: Maybe; fieldResolver?: Maybe>; typeResolver?: Maybe>; + CustomExecutor?: Maybe; } export function graphql(args: GraphQLArgs): Promise { @@ -100,6 +113,7 @@ function graphqlImpl(args: GraphQLArgs): PromiseOrValue { operationName, fieldResolver, typeResolver, + CustomExecutor, } = args; // Validate Schema @@ -132,5 +146,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 03e7b005163..6ee9e36c75c 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();