Skip to content

Commit

Permalink
add fieldExecutor option to graphql, execute, and subscribe to specif…
Browse files Browse the repository at this point in the history
…y a custom field executor, export the default field executor for easy wrapping
  • Loading branch information
yaacovCR committed Jun 8, 2021
1 parent dc2a3eb commit d027428
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 12 deletions.
23 changes: 23 additions & 0 deletions src/execution/__tests__/executor-test.ts
Expand Up @@ -1151,6 +1151,29 @@ describe('Execute: Handles basic execution tasks', () => {
expect(result).to.deep.equal({ data: { foo: null } });
});

it('uses a custom field executor', () => {
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
foo: { type: GraphQLString },
},
}),
});
const document = parse('{ foo }');

const result = executeSync({
schema,
document,
fieldExecutor() {
// For the purposes of test, just return the name of the field!
return 'foo';
},
});

expect(result).to.deep.equal({ data: { foo: 'foo' } });
});

it('uses a custom field resolver', () => {
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
Expand Down
34 changes: 24 additions & 10 deletions src/execution/execute.ts
Expand Up @@ -102,9 +102,18 @@ export interface ExecutionContext {
variableValues: { [variable: string]: unknown };
fieldResolver: GraphQLFieldResolver<any, any>;
typeResolver: GraphQLTypeResolver<any, any>;
fieldExecutor: GraphQLFieldExecutor;
errors: Array<GraphQLError>;
}

export type GraphQLFieldExecutor = (
exeContext: ExecutionContext,
parentType: GraphQLObjectType,
source: unknown,
fieldNodes: ReadonlyArray<FieldNode>,
path: Path,
) => PromiseOrValue<unknown>;

/**
* The result of GraphQL execution.
*
Expand Down Expand Up @@ -139,6 +148,7 @@ export interface ExecutionArgs {
operationName?: Maybe<string>;
fieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
typeResolver?: Maybe<GraphQLTypeResolver<any, any>>;
fieldExecutor?: Maybe<GraphQLFieldExecutor>;
}

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

// If arguments are missing or incorrect, throw an error.
Expand All @@ -177,6 +188,7 @@ export function execute(args: ExecutionArgs): PromiseOrValue<ExecutionResult> {
operationName,
fieldResolver,
typeResolver,
fieldExecutor,
);

// Return early errors if execution context failed.
Expand Down Expand Up @@ -267,6 +279,7 @@ export function buildExecutionContext(
operationName: Maybe<string>,
fieldResolver: Maybe<GraphQLFieldResolver<unknown, unknown>>,
typeResolver?: Maybe<GraphQLTypeResolver<unknown, unknown>>,
fieldExecutor?: Maybe<GraphQLFieldExecutor>,
): ReadonlyArray<GraphQLError> | ExecutionContext {
let operation: OperationDefinitionNode | undefined;
const fragments: ObjMap<FragmentDefinitionNode> = Object.create(null);
Expand Down Expand Up @@ -322,6 +335,7 @@ export function buildExecutionContext(
variableValues: coercedVariableValues.coerced,
fieldResolver: fieldResolver ?? defaultFieldResolver,
typeResolver: typeResolver ?? defaultTypeResolver,
fieldExecutor: fieldExecutor ?? defaultFieldExecutor,
errors: [],
};
}
Expand Down Expand Up @@ -381,7 +395,7 @@ function executeFieldsSerially(
fields.entries(),
(results, [responseName, fieldNodes]) => {
const fieldPath = addPath(path, responseName, parentType.name);
const result = executeField(
const result = exeContext.fieldExecutor(
exeContext,
parentType,
sourceValue,
Expand Down Expand Up @@ -420,7 +434,7 @@ function executeFields(

for (const [responseName, fieldNodes] of fields.entries()) {
const fieldPath = addPath(path, responseName, parentType.name);
const result = executeField(
const result = exeContext.fieldExecutor(
exeContext,
parentType,
sourceValue,
Expand Down Expand Up @@ -588,13 +602,13 @@ function getFieldEntryKey(node: FieldNode): string {
* calling its resolve function, then calls completeValue to complete promises,
* serialize scalars, or execute the sub-selection-set for objects.
*/
function executeField(
exeContext: ExecutionContext,
parentType: GraphQLObjectType,
source: unknown,
fieldNodes: ReadonlyArray<FieldNode>,
path: Path,
): PromiseOrValue<unknown> {
export const defaultFieldExecutor: GraphQLFieldExecutor = (
exeContext,
parentType,
source,
fieldNodes,
path,
) => {
const fieldDef = getFieldDef(exeContext.schema, parentType, fieldNodes[0]);
if (!fieldDef) {
return;
Expand Down Expand Up @@ -658,7 +672,7 @@ function executeField(
const error = locatedError(rawError, fieldNodes, pathToArray(path));
return handleFieldError(error, returnType, exeContext);
}
}
};

/**
* @internal
Expand Down
3 changes: 3 additions & 0 deletions src/execution/index.ts
Expand Up @@ -3,14 +3,17 @@ export { pathToArray as responsePathAsArray } from '../jsutils/Path';
export {
execute,
executeSync,
defaultFieldExecutor,
defaultFieldResolver,
defaultTypeResolver,
} from './execute';

export type {
ExecutionArgs,
ExecutionContext,
ExecutionResult,
FormattedExecutionResult,
GraphQLFieldExecutor,
} from './execute';

export { getDirectiveValues } from './values';
13 changes: 12 additions & 1 deletion src/graphql.ts
Expand Up @@ -14,7 +14,10 @@ import type {
import type { GraphQLSchema } from './type/schema';
import { validateSchema } from './type/validate';

import type { ExecutionResult } from './execution/execute';
import type {
ExecutionResult,
GraphQLFieldExecutor,
} from './execution/execute';
import { execute } from './execution/execute';

/**
Expand Down Expand Up @@ -55,6 +58,11 @@ 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).
* fieldExecutor:
* An executor function to use when one is not provided by the schema.
* If not provided, the default field executor is used (which properly
* calls the field resolver and completes values according to the
* GraphQL spec).
*/
export interface GraphQLArgs {
schema: GraphQLSchema;
Expand All @@ -65,6 +73,7 @@ export interface GraphQLArgs {
operationName?: Maybe<string>;
fieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
typeResolver?: Maybe<GraphQLTypeResolver<any, any>>;
fieldExecutor?: Maybe<GraphQLFieldExecutor>;
}

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

// Validate Schema
Expand Down Expand Up @@ -131,5 +141,6 @@ function graphqlImpl(args: GraphQLArgs): PromiseOrValue<ExecutionResult> {
operationName,
fieldResolver,
typeResolver,
fieldExecutor,
});
}
3 changes: 3 additions & 0 deletions src/index.ts
Expand Up @@ -301,6 +301,7 @@ export type {
export {
execute,
executeSync,
defaultFieldExecutor,
defaultFieldResolver,
defaultTypeResolver,
responsePathAsArray,
Expand All @@ -309,8 +310,10 @@ export {

export type {
ExecutionArgs,
ExecutionContext,
ExecutionResult,
FormattedExecutionResult,
GraphQLFieldExecutor,
} from './execution/index';

export { subscribe, createSourceEventStream } from './subscription/index';
Expand Down
9 changes: 8 additions & 1 deletion src/subscription/subscribe.ts
Expand Up @@ -8,7 +8,11 @@ import { locatedError } from '../error/locatedError';

import type { DocumentNode } from '../language/ast';

import type { ExecutionResult, ExecutionContext } from '../execution/execute';
import type {
ExecutionResult,
ExecutionContext,
GraphQLFieldExecutor,
} from '../execution/execute';
import { getArgumentValues } from '../execution/values';
import {
assertValidExecutionArguments,
Expand All @@ -34,6 +38,7 @@ export interface SubscriptionArgs {
variableValues?: Maybe<{ readonly [variable: string]: unknown }>;
operationName?: Maybe<string>;
fieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
fieldExecutor?: Maybe<GraphQLFieldExecutor>;
subscribeFieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
}

Expand Down Expand Up @@ -69,6 +74,7 @@ export async function subscribe(
variableValues,
operationName,
fieldResolver,
fieldExecutor,
subscribeFieldResolver,
} = args;

Expand Down Expand Up @@ -101,6 +107,7 @@ export async function subscribe(
variableValues,
operationName,
fieldResolver,
fieldExecutor,
});

// Map every source value to a ExecutionResult value as described above.
Expand Down
4 changes: 4 additions & 0 deletions src/validation/rules/SingleFieldSubscriptionsRule.ts
Expand Up @@ -9,11 +9,14 @@ import type {
import { Kind } from '../../language/kinds';

import type { ValidationContext } from '../ValidationContext';

import type { ExecutionContext } from '../../execution/execute';

import {
collectFields,
defaultFieldResolver,
defaultTypeResolver,
defaultFieldExecutor,
} from '../../execution/execute';

/**
Expand Down Expand Up @@ -52,6 +55,7 @@ export function SingleFieldSubscriptionsRule(
variableValues,
fieldResolver: defaultFieldResolver,
typeResolver: defaultTypeResolver,
fieldExecutor: defaultFieldExecutor,
errors: [],
};
const fields = collectFields(
Expand Down

0 comments on commit d027428

Please sign in to comment.