Skip to content

Commit d027428

Browse files
committed
add fieldExecutor option to graphql, execute, and subscribe to specify a custom field executor, export the default field executor for easy wrapping
1 parent dc2a3eb commit d027428

File tree

7 files changed

+77
-12
lines changed

7 files changed

+77
-12
lines changed

src/execution/__tests__/executor-test.ts

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

1154+
it('uses a custom field 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+
const result = executeSync({
1166+
schema,
1167+
document,
1168+
fieldExecutor() {
1169+
// For the purposes of test, just return the name of the field!
1170+
return 'foo';
1171+
},
1172+
});
1173+
1174+
expect(result).to.deep.equal({ data: { foo: 'foo' } });
1175+
});
1176+
11541177
it('uses a custom field resolver', () => {
11551178
const schema = new GraphQLSchema({
11561179
query: new GraphQLObjectType({

src/execution/execute.ts

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,18 @@ export interface ExecutionContext {
102102
variableValues: { [variable: string]: unknown };
103103
fieldResolver: GraphQLFieldResolver<any, any>;
104104
typeResolver: GraphQLTypeResolver<any, any>;
105+
fieldExecutor: GraphQLFieldExecutor;
105106
errors: Array<GraphQLError>;
106107
}
107108

109+
export type GraphQLFieldExecutor = (
110+
exeContext: ExecutionContext,
111+
parentType: GraphQLObjectType,
112+
source: unknown,
113+
fieldNodes: ReadonlyArray<FieldNode>,
114+
path: Path,
115+
) => PromiseOrValue<unknown>;
116+
108117
/**
109118
* The result of GraphQL execution.
110119
*
@@ -139,6 +148,7 @@ export interface ExecutionArgs {
139148
operationName?: Maybe<string>;
140149
fieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
141150
typeResolver?: Maybe<GraphQLTypeResolver<any, any>>;
151+
fieldExecutor?: Maybe<GraphQLFieldExecutor>;
142152
}
143153

144154
/**
@@ -161,6 +171,7 @@ export function execute(args: ExecutionArgs): PromiseOrValue<ExecutionResult> {
161171
operationName,
162172
fieldResolver,
163173
typeResolver,
174+
fieldExecutor,
164175
} = args;
165176

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

182194
// Return early errors if execution context failed.
@@ -267,6 +279,7 @@ export function buildExecutionContext(
267279
operationName: Maybe<string>,
268280
fieldResolver: Maybe<GraphQLFieldResolver<unknown, unknown>>,
269281
typeResolver?: Maybe<GraphQLTypeResolver<unknown, unknown>>,
282+
fieldExecutor?: Maybe<GraphQLFieldExecutor>,
270283
): ReadonlyArray<GraphQLError> | ExecutionContext {
271284
let operation: OperationDefinitionNode | undefined;
272285
const fragments: ObjMap<FragmentDefinitionNode> = Object.create(null);
@@ -322,6 +335,7 @@ export function buildExecutionContext(
322335
variableValues: coercedVariableValues.coerced,
323336
fieldResolver: fieldResolver ?? defaultFieldResolver,
324337
typeResolver: typeResolver ?? defaultTypeResolver,
338+
fieldExecutor: fieldExecutor ?? defaultFieldExecutor,
325339
errors: [],
326340
};
327341
}
@@ -381,7 +395,7 @@ function executeFieldsSerially(
381395
fields.entries(),
382396
(results, [responseName, fieldNodes]) => {
383397
const fieldPath = addPath(path, responseName, parentType.name);
384-
const result = executeField(
398+
const result = exeContext.fieldExecutor(
385399
exeContext,
386400
parentType,
387401
sourceValue,
@@ -420,7 +434,7 @@ function executeFields(
420434

421435
for (const [responseName, fieldNodes] of fields.entries()) {
422436
const fieldPath = addPath(path, responseName, parentType.name);
423-
const result = executeField(
437+
const result = exeContext.fieldExecutor(
424438
exeContext,
425439
parentType,
426440
sourceValue,
@@ -588,13 +602,13 @@ function getFieldEntryKey(node: FieldNode): string {
588602
* calling its resolve function, then calls completeValue to complete promises,
589603
* serialize scalars, or execute the sub-selection-set for objects.
590604
*/
591-
function executeField(
592-
exeContext: ExecutionContext,
593-
parentType: GraphQLObjectType,
594-
source: unknown,
595-
fieldNodes: ReadonlyArray<FieldNode>,
596-
path: Path,
597-
): PromiseOrValue<unknown> {
605+
export const defaultFieldExecutor: GraphQLFieldExecutor = (
606+
exeContext,
607+
parentType,
608+
source,
609+
fieldNodes,
610+
path,
611+
) => {
598612
const fieldDef = getFieldDef(exeContext.schema, parentType, fieldNodes[0]);
599613
if (!fieldDef) {
600614
return;
@@ -658,7 +672,7 @@ function executeField(
658672
const error = locatedError(rawError, fieldNodes, pathToArray(path));
659673
return handleFieldError(error, returnType, exeContext);
660674
}
661-
}
675+
};
662676

663677
/**
664678
* @internal

src/execution/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@ export { pathToArray as responsePathAsArray } from '../jsutils/Path';
33
export {
44
execute,
55
executeSync,
6+
defaultFieldExecutor,
67
defaultFieldResolver,
78
defaultTypeResolver,
89
} from './execute';
910

1011
export type {
1112
ExecutionArgs,
13+
ExecutionContext,
1214
ExecutionResult,
1315
FormattedExecutionResult,
16+
GraphQLFieldExecutor,
1417
} from './execute';
1518

1619
export { getDirectiveValues } from './values';

src/graphql.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ 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 {
18+
ExecutionResult,
19+
GraphQLFieldExecutor,
20+
} from './execution/execute';
1821
import { execute } from './execution/execute';
1922

2023
/**
@@ -55,6 +58,11 @@ import { execute } from './execution/execute';
5558
* A type resolver function to use when none is provided by the schema.
5659
* If not provided, the default type resolver is used (which looks for a
5760
* `__typename` field or alternatively calls the `isTypeOf` method).
61+
* fieldExecutor:
62+
* An executor function to use when one is not provided by the schema.
63+
* If not provided, the default field executor is used (which properly
64+
* calls the field resolver and completes values according to the
65+
* GraphQL spec).
5866
*/
5967
export interface GraphQLArgs {
6068
schema: GraphQLSchema;
@@ -65,6 +73,7 @@ export interface GraphQLArgs {
6573
operationName?: Maybe<string>;
6674
fieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
6775
typeResolver?: Maybe<GraphQLTypeResolver<any, any>>;
76+
fieldExecutor?: Maybe<GraphQLFieldExecutor>;
6877
}
6978

7079
export function graphql(args: GraphQLArgs): Promise<ExecutionResult> {
@@ -99,6 +108,7 @@ function graphqlImpl(args: GraphQLArgs): PromiseOrValue<ExecutionResult> {
99108
operationName,
100109
fieldResolver,
101110
typeResolver,
111+
fieldExecutor,
102112
} = args;
103113

104114
// Validate Schema
@@ -131,5 +141,6 @@ function graphqlImpl(args: GraphQLArgs): PromiseOrValue<ExecutionResult> {
131141
operationName,
132142
fieldResolver,
133143
typeResolver,
144+
fieldExecutor,
134145
});
135146
}

src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ export type {
301301
export {
302302
execute,
303303
executeSync,
304+
defaultFieldExecutor,
304305
defaultFieldResolver,
305306
defaultTypeResolver,
306307
responsePathAsArray,
@@ -309,8 +310,10 @@ export {
309310

310311
export type {
311312
ExecutionArgs,
313+
ExecutionContext,
312314
ExecutionResult,
313315
FormattedExecutionResult,
316+
GraphQLFieldExecutor,
314317
} from './execution/index';
315318

316319
export { subscribe, createSourceEventStream } from './subscription/index';

src/subscription/subscribe.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ import { locatedError } from '../error/locatedError';
88

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

11-
import type { ExecutionResult, ExecutionContext } from '../execution/execute';
11+
import type {
12+
ExecutionResult,
13+
ExecutionContext,
14+
GraphQLFieldExecutor,
15+
} from '../execution/execute';
1216
import { getArgumentValues } from '../execution/values';
1317
import {
1418
assertValidExecutionArguments,
@@ -34,6 +38,7 @@ export interface SubscriptionArgs {
3438
variableValues?: Maybe<{ readonly [variable: string]: unknown }>;
3539
operationName?: Maybe<string>;
3640
fieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
41+
fieldExecutor?: Maybe<GraphQLFieldExecutor>;
3742
subscribeFieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
3843
}
3944

@@ -69,6 +74,7 @@ export async function subscribe(
6974
variableValues,
7075
operationName,
7176
fieldResolver,
77+
fieldExecutor,
7278
subscribeFieldResolver,
7379
} = args;
7480

@@ -101,6 +107,7 @@ export async function subscribe(
101107
variableValues,
102108
operationName,
103109
fieldResolver,
110+
fieldExecutor,
104111
});
105112

106113
// Map every source value to a ExecutionResult value as described above.

src/validation/rules/SingleFieldSubscriptionsRule.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@ import type {
99
import { Kind } from '../../language/kinds';
1010

1111
import type { ValidationContext } from '../ValidationContext';
12+
1213
import type { ExecutionContext } from '../../execution/execute';
14+
1315
import {
1416
collectFields,
1517
defaultFieldResolver,
1618
defaultTypeResolver,
19+
defaultFieldExecutor,
1720
} from '../../execution/execute';
1821

1922
/**
@@ -52,6 +55,7 @@ export function SingleFieldSubscriptionsRule(
5255
variableValues,
5356
fieldResolver: defaultFieldResolver,
5457
typeResolver: defaultTypeResolver,
58+
fieldExecutor: defaultFieldExecutor,
5559
errors: [],
5660
};
5761
const fields = collectFields(

0 commit comments

Comments
 (0)