Skip to content

Commit 23421d1

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 0a0c2aa commit 23421d1

File tree

5 files changed

+51
-2
lines changed

5 files changed

+51
-2
lines changed

src/execution/__tests__/executor-test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,6 +1152,32 @@ describe('Execute: Handles basic execution tasks', () => {
11521152
expect(result).to.deep.equal({ data: { foo: null } });
11531153
});
11541154

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+
11551181
it('uses a custom field resolver', () => {
11561182
const schema = new GraphQLSchema({
11571183
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: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,18 @@ import { execute } from './execution/execute';
5656
* A type resolver function to use when none is provided by the schema.
5757
* If not provided, the default type resolver is used (which looks for a
5858
* `__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.
5971
*/
6072
export interface GraphQLArgs {
6173
schema: GraphQLSchema;
@@ -66,6 +78,7 @@ export interface GraphQLArgs {
6678
operationName?: Maybe<string>;
6779
fieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
6880
typeResolver?: Maybe<GraphQLTypeResolver<any, any>>;
81+
CustomExecutor?: Maybe<typeof Executor>;
6982
}
7083

7184
export function graphql(args: GraphQLArgs): Promise<ExecutionResult> {
@@ -100,6 +113,7 @@ function graphqlImpl(args: GraphQLArgs): PromiseOrValue<ExecutionResult> {
100113
operationName,
101114
fieldResolver,
102115
typeResolver,
116+
CustomExecutor,
103117
} = args;
104118

105119
// Validate Schema
@@ -132,5 +146,6 @@ function graphqlImpl(args: GraphQLArgs): PromiseOrValue<ExecutionResult> {
132146
operationName,
133147
fieldResolver,
134148
typeResolver,
149+
CustomExecutor,
135150
});
136151
}

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)