Skip to content

Commit

Permalink
feat: add CustomExecutor option to graphql, execute, and subscribe
Browse files Browse the repository at this point in the history
allows customization of the execution algorithm by overriding any of the
protected members of the now exported internal Executor class.

Reference:

graphql#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.
  • Loading branch information
yaacovCR committed Jun 16, 2021
1 parent e8228c5 commit e580f62
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 4 deletions.
28 changes: 27 additions & 1 deletion src/execution/__tests__/executor-test.ts
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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({
Expand Down
12 changes: 11 additions & 1 deletion src/execution/execute.ts
Expand Up @@ -128,6 +128,7 @@ export interface ExecutionArgs {
operationName?: Maybe<string>;
fieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
typeResolver?: Maybe<GraphQLTypeResolver<any, any>>;
CustomExecutor?: Maybe<typeof Executor>;
}

/**
Expand All @@ -150,6 +151,7 @@ export function execute(args: ExecutionArgs): PromiseOrValue<ExecutionResult> {
operationName,
fieldResolver,
typeResolver,
CustomExecutor,
} = args;

// If arguments are missing or incorrect, throw an error.
Expand All @@ -173,7 +175,7 @@ export function execute(args: ExecutionArgs): PromiseOrValue<ExecutionResult> {
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.
Expand Down Expand Up @@ -302,6 +304,14 @@ export function buildExecutionContext(
}

/**
* 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.
*
* @internal
*/
export class Executor {
Expand Down
1 change: 1 addition & 0 deletions src/execution/index.ts
@@ -1,6 +1,7 @@
export { pathToArray as responsePathAsArray } from '../jsutils/Path';

export {
Executor,
execute,
executeSync,
defaultFieldResolver,
Expand Down
17 changes: 16 additions & 1 deletion src/graphql.ts
Expand Up @@ -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';

/**
Expand Down Expand Up @@ -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;
Expand All @@ -65,6 +77,7 @@ export interface GraphQLArgs {
operationName?: Maybe<string>;
fieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
typeResolver?: Maybe<GraphQLTypeResolver<any, any>>;
CustomExecutor?: Maybe<typeof Executor>;
}

export function graphql(args: GraphQLArgs): Promise<ExecutionResult> {
Expand Down Expand Up @@ -99,6 +112,7 @@ function graphqlImpl(args: GraphQLArgs): PromiseOrValue<ExecutionResult> {
operationName,
fieldResolver,
typeResolver,
CustomExecutor,
} = args;

// Validate Schema
Expand Down Expand Up @@ -131,5 +145,6 @@ function graphqlImpl(args: GraphQLArgs): PromiseOrValue<ExecutionResult> {
operationName,
fieldResolver,
typeResolver,
CustomExecutor,
});
}
1 change: 1 addition & 0 deletions src/index.ts
Expand Up @@ -299,6 +299,7 @@ export type {

/** Execute GraphQL queries. */
export {
Executor,
execute,
executeSync,
defaultFieldResolver,
Expand Down
7 changes: 6 additions & 1 deletion src/subscription/subscribe.ts
Expand Up @@ -35,6 +35,7 @@ export interface SubscriptionArgs {
operationName?: Maybe<string>;
fieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
subscribeFieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
CustomExecutor?: Maybe<typeof SubscriptionExecutor>;
}

/**
Expand Down Expand Up @@ -70,6 +71,7 @@ export async function subscribe(
operationName,
fieldResolver,
subscribeFieldResolver,
CustomExecutor,
} = args;

const resultOrStream = await createSourceEventStream(
Expand All @@ -80,6 +82,7 @@ export async function subscribe(
variableValues,
operationName,
subscribeFieldResolver,
CustomExecutor,
);

if (!isAsyncIterable(resultOrStream)) {
Expand All @@ -101,6 +104,7 @@ export async function subscribe(
variableValues,
operationName,
fieldResolver,
CustomExecutor,
});

// Map every source value to a ExecutionResult value as described above.
Expand Down Expand Up @@ -143,6 +147,7 @@ export async function createSourceEventStream(
variableValues?: Maybe<{ readonly [variable: string]: unknown }>,
operationName?: Maybe<string>,
fieldResolver?: Maybe<GraphQLFieldResolver<any, any>>,
CustomExecutor?: Maybe<typeof SubscriptionExecutor>,
): Promise<AsyncIterable<unknown> | ExecutionResult> {
// If arguments are missing or incorrectly typed, this is an internal
// developer mistake which should throw an early error.
Expand All @@ -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();

Expand Down

0 comments on commit e580f62

Please sign in to comment.