Skip to content

Commit e356494

Browse files
committed
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: #3163 (comment) 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.
1 parent 913b16f commit e356494

File tree

5 files changed

+53
-2
lines changed

5 files changed

+53
-2
lines changed

src/execution/__tests__/executor-test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
GraphQLUnionType,
1919
} from '../../type/definition';
2020

21+
import { Executor } from '../executor';
2122
import { execute, executeSync } from '../execute';
2223

2324
describe('Execute: Handles basic execution tasks', () => {
@@ -1151,6 +1152,32 @@ describe('Execute: Handles basic execution tasks', () => {
11511152
expect(result).to.deep.equal({ data: { foo: null } });
11521153
});
11531154

1155+
it('uses a custom Executor', () => {
1156+
const schema = new GraphQLSchema({
1157+
query: new GraphQLObjectType({
1158+
name: 'Query',
1159+
fields: {
1160+
foo: { type: GraphQLString },
1161+
},
1162+
}),
1163+
});
1164+
const document = parse('{ foo }');
1165+
1166+
class CustomExecutor extends Executor {
1167+
executeField() {
1168+
return 'foo';
1169+
}
1170+
}
1171+
1172+
const result = executeSync({
1173+
schema,
1174+
document,
1175+
CustomExecutor,
1176+
});
1177+
1178+
expect(result).to.deep.equal({ data: { foo: 'foo' } });
1179+
});
1180+
11541181
it('uses a custom field resolver', () => {
11551182
const schema = new GraphQLSchema({
11561183
query: new GraphQLObjectType({

src/execution/execute.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ export interface ExecutionArgs {
9797
operationName?: Maybe<string>;
9898
fieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
9999
typeResolver?: Maybe<GraphQLTypeResolver<any, any>>;
100+
CustomExecutor?: Maybe<typeof Executor>;
100101
}
101102

102103
/**
@@ -119,6 +120,7 @@ export function execute(args: ExecutionArgs): PromiseOrValue<ExecutionResult> {
119120
operationName,
120121
fieldResolver,
121122
typeResolver,
123+
CustomExecutor,
122124
} = args;
123125

124126
// If arguments are missing or incorrect, throw an error.
@@ -142,7 +144,7 @@ export function execute(args: ExecutionArgs): PromiseOrValue<ExecutionResult> {
142144
return { errors: exeContext };
143145
}
144146

145-
const executor = new Executor(exeContext);
147+
const executor = new (CustomExecutor ?? Executor)(exeContext);
146148

147149
// Return a Promise that will eventually resolve to the data described by
148150
// The "Response" section of the GraphQL specification.

src/graphql.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type {
1414
import type { GraphQLSchema } from './type/schema';
1515
import { validateSchema } from './type/validate';
1616

17+
import type { Executor } from './execution/executor';
1718
import type { ExecutionResult } from './execution/execute';
1819
import { execute } from './execution/execute';
1920

@@ -55,6 +56,18 @@ import { execute } from './execution/execute';
5556
* A type resolver function to use when none is provided by the schema.
5657
* If not provided, the default type resolver is used (which looks for a
5758
* `__typename` field or alternatively calls the `isTypeOf` method).
59+
* CustomExecutor:
60+
* A custom Executor class to allow overriding execution behavior.
61+
*
62+
* Note: The Executor class is exported only to assist people in
63+
* implementing their own executors without duplicating too much code and
64+
* should be used only as last resort for cases requiring custom execution
65+
* or if certain features could not be contributed upstream.
66+
*
67+
* It is still part of the internal API and is versioned, so any changes to
68+
* it are never considered breaking changes. If you still need to support
69+
* multiple versions of the library, please use the `versionInfo` variable
70+
* for version detection.
5871
*/
5972
export interface GraphQLArgs {
6073
schema: GraphQLSchema;
@@ -65,6 +78,7 @@ export interface GraphQLArgs {
6578
operationName?: Maybe<string>;
6679
fieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
6780
typeResolver?: Maybe<GraphQLTypeResolver<any, any>>;
81+
CustomExecutor?: Maybe<typeof Executor>;
6882
}
6983

7084
export function graphql(args: GraphQLArgs): Promise<ExecutionResult> {
@@ -99,6 +113,7 @@ function graphqlImpl(args: GraphQLArgs): PromiseOrValue<ExecutionResult> {
99113
operationName,
100114
fieldResolver,
101115
typeResolver,
116+
CustomExecutor,
102117
} = args;
103118

104119
// Validate Schema
@@ -131,5 +146,6 @@ function graphqlImpl(args: GraphQLArgs): PromiseOrValue<ExecutionResult> {
131146
operationName,
132147
fieldResolver,
133148
typeResolver,
149+
CustomExecutor,
134150
});
135151
}

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ export type {
299299

300300
/** Execute GraphQL queries. */
301301
export {
302+
Executor,
302303
execute,
303304
executeSync,
304305
defaultFieldResolver,

src/subscription/subscribe.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export interface SubscriptionArgs {
3434
operationName?: Maybe<string>;
3535
fieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
3636
subscribeFieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
37+
CustomExecutor?: Maybe<typeof SubscriptionExecutor>;
3738
}
3839

3940
/**
@@ -69,6 +70,7 @@ export async function subscribe(
6970
operationName,
7071
fieldResolver,
7172
subscribeFieldResolver,
73+
CustomExecutor,
7274
} = args;
7375

7476
const resultOrStream = await createSourceEventStream(
@@ -79,6 +81,7 @@ export async function subscribe(
7981
variableValues,
8082
operationName,
8183
subscribeFieldResolver,
84+
CustomExecutor,
8285
);
8386

8487
if (!isAsyncIterable(resultOrStream)) {
@@ -100,6 +103,7 @@ export async function subscribe(
100103
variableValues,
101104
operationName,
102105
fieldResolver,
106+
CustomExecutor,
103107
});
104108

105109
// Map every source value to a ExecutionResult value as described above.
@@ -142,6 +146,7 @@ export async function createSourceEventStream(
142146
variableValues?: Maybe<{ readonly [variable: string]: unknown }>,
143147
operationName?: Maybe<string>,
144148
fieldResolver?: Maybe<GraphQLFieldResolver<any, any>>,
149+
CustomExecutor?: Maybe<typeof SubscriptionExecutor>,
145150
): Promise<AsyncIterable<unknown> | ExecutionResult> {
146151
// If arguments are missing or incorrectly typed, this is an internal
147152
// developer mistake which should throw an early error.
@@ -164,7 +169,7 @@ export async function createSourceEventStream(
164169
return { errors: exeContext };
165170
}
166171

167-
const executor = new SubscriptionExecutor(exeContext);
172+
const executor = new (CustomExecutor ?? SubscriptionExecutor)(exeContext);
168173

169174
const eventStream = await executor.executeSubscription();
170175

0 commit comments

Comments
 (0)