Skip to content

Commit e7d772e

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 4175c22 commit e7d772e

File tree

6 files changed

+54
-4
lines changed

6 files changed

+54
-4
lines changed

src/execution/__tests__/executor-test.ts

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

21-
import { execute, executeSync } from '../execute';
21+
import { Executor, execute, executeSync } from '../execute';
2222

2323
describe('Execute: Handles basic execution tasks', () => {
2424
it('throws if no document is provided', () => {
@@ -1151,6 +1151,32 @@ describe('Execute: Handles basic execution tasks', () => {
11511151
expect(result).to.deep.equal({ data: { foo: null } });
11521152
});
11531153

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

src/execution/execute.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ export interface ExecutionArgs {
128128
operationName?: Maybe<string>;
129129
fieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
130130
typeResolver?: Maybe<GraphQLTypeResolver<any, any>>;
131+
CustomExecutor?: Maybe<typeof Executor>;
131132
}
132133

133134
/**
@@ -150,6 +151,7 @@ export function execute(args: ExecutionArgs): PromiseOrValue<ExecutionResult> {
150151
operationName,
151152
fieldResolver,
152153
typeResolver,
154+
CustomExecutor,
153155
} = args;
154156

155157
// If arguments are missing or incorrect, throw an error.
@@ -173,7 +175,7 @@ export function execute(args: ExecutionArgs): PromiseOrValue<ExecutionResult> {
173175
return { errors: exeContext };
174176
}
175177

176-
const executor = new Executor(exeContext);
178+
const executor = new (CustomExecutor ?? Executor)(exeContext);
177179

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

src/execution/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export { pathToArray as responsePathAsArray } from '../jsutils/Path';
22

33
export {
4+
Executor,
45
execute,
56
executeSync,
67
defaultFieldResolver,

src/graphql.ts

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

17-
import type { ExecutionResult } from './execution/execute';
17+
import type { ExecutionResult, Executor } from './execution/execute';
1818
import { execute } from './execution/execute';
1919

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

7083
export function graphql(args: GraphQLArgs): Promise<ExecutionResult> {
@@ -99,6 +112,7 @@ function graphqlImpl(args: GraphQLArgs): PromiseOrValue<ExecutionResult> {
99112
operationName,
100113
fieldResolver,
101114
typeResolver,
115+
CustomExecutor,
102116
} = args;
103117

104118
// Validate Schema
@@ -131,5 +145,6 @@ function graphqlImpl(args: GraphQLArgs): PromiseOrValue<ExecutionResult> {
131145
operationName,
132146
fieldResolver,
133147
typeResolver,
148+
CustomExecutor,
134149
});
135150
}

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
@@ -35,6 +35,7 @@ export interface SubscriptionArgs {
3535
operationName?: Maybe<string>;
3636
fieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
3737
subscribeFieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
38+
CustomExecutor?: Maybe<typeof SubscriptionExecutor>;
3839
}
3940

4041
/**
@@ -70,6 +71,7 @@ export async function subscribe(
7071
operationName,
7172
fieldResolver,
7273
subscribeFieldResolver,
74+
CustomExecutor,
7375
} = args;
7476

7577
const resultOrStream = await createSourceEventStream(
@@ -80,6 +82,7 @@ export async function subscribe(
8082
variableValues,
8183
operationName,
8284
subscribeFieldResolver,
85+
CustomExecutor,
8386
);
8487

8588
if (!isAsyncIterable(resultOrStream)) {
@@ -101,6 +104,7 @@ export async function subscribe(
101104
variableValues,
102105
operationName,
103106
fieldResolver,
107+
CustomExecutor,
104108
});
105109

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

168-
const executor = new SubscriptionExecutor(exeContext);
173+
const executor = new (CustomExecutor ?? SubscriptionExecutor)(exeContext);
169174

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

0 commit comments

Comments
 (0)