Skip to content

Commit 33761d6

Browse files
committed
refactor: move execution context related functionality to new file
1 parent 9fc1d93 commit 33761d6

File tree

3 files changed

+214
-170
lines changed

3 files changed

+214
-170
lines changed

src/execution/compiledDocument.ts

+207
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
import { isObjectLike } from '../jsutils/isObjectLike';
2+
import { isPromise } from '../jsutils/isPromise';
3+
import type { ObjMap } from '../jsutils/ObjMap';
4+
5+
import { GraphQLError } from '../error/GraphQLError';
6+
7+
import type {
8+
FragmentDefinitionNode,
9+
OperationDefinitionNode,
10+
} from '../language/ast';
11+
import { Kind } from '../language/kinds';
12+
13+
import type {
14+
GraphQLFieldResolver,
15+
GraphQLTypeResolver,
16+
} from '../type/definition';
17+
import type { GraphQLSchema } from '../type/schema';
18+
import { assertValidSchema } from '../type/validate';
19+
20+
import type { ExecutionArgs } from './execute';
21+
import { getVariableValues } from './values';
22+
23+
/**
24+
* Data that must be available at all points during query execution.
25+
*
26+
* Namely, schema of the type system that is currently executing,
27+
* and the fragments defined in the query document
28+
*/
29+
export interface ExecutionContext {
30+
schema: GraphQLSchema;
31+
fragments: ObjMap<FragmentDefinitionNode>;
32+
rootValue: unknown;
33+
contextValue: unknown;
34+
operation: OperationDefinitionNode;
35+
variableValues: { [variable: string]: unknown };
36+
fieldResolver: GraphQLFieldResolver<any, any>;
37+
typeResolver: GraphQLTypeResolver<any, any>;
38+
subscribeFieldResolver: GraphQLFieldResolver<any, any>;
39+
errors: Array<GraphQLError>;
40+
}
41+
42+
/**
43+
* Constructs a ExecutionContext object from the arguments passed to
44+
* execute, which we will pass throughout the other execution methods.
45+
*
46+
* Throws a GraphQLError if a valid execution context cannot be created.
47+
*
48+
* TODO: consider no longer exporting this function
49+
* @internal
50+
*/
51+
export function buildExecutionContext(
52+
args: ExecutionArgs,
53+
): ReadonlyArray<GraphQLError> | ExecutionContext {
54+
const {
55+
schema,
56+
document,
57+
rootValue,
58+
contextValue,
59+
variableValues: rawVariableValues,
60+
operationName,
61+
fieldResolver,
62+
typeResolver,
63+
subscribeFieldResolver,
64+
} = args;
65+
66+
// If the schema used for execution is invalid, throw an error.
67+
assertValidSchema(schema);
68+
69+
let operation: OperationDefinitionNode | undefined;
70+
const fragments: ObjMap<FragmentDefinitionNode> = Object.create(null);
71+
for (const definition of document.definitions) {
72+
switch (definition.kind) {
73+
case Kind.OPERATION_DEFINITION:
74+
if (operationName == null) {
75+
if (operation !== undefined) {
76+
return [
77+
new GraphQLError(
78+
'Must provide operation name if query contains multiple operations.',
79+
),
80+
];
81+
}
82+
operation = definition;
83+
} else if (definition.name?.value === operationName) {
84+
operation = definition;
85+
}
86+
break;
87+
case Kind.FRAGMENT_DEFINITION:
88+
fragments[definition.name.value] = definition;
89+
break;
90+
default:
91+
// ignore non-executable definitions
92+
}
93+
}
94+
95+
if (!operation) {
96+
if (operationName != null) {
97+
return [new GraphQLError(`Unknown operation named "${operationName}".`)];
98+
}
99+
return [new GraphQLError('Must provide an operation.')];
100+
}
101+
102+
// FIXME: https://github.com/graphql/graphql-js/issues/2203
103+
/* c8 ignore next */
104+
const variableDefinitions = operation.variableDefinitions ?? [];
105+
106+
const coercedVariableValues = getVariableValues(
107+
schema,
108+
variableDefinitions,
109+
rawVariableValues ?? {},
110+
{ maxErrors: 50 },
111+
);
112+
113+
if (coercedVariableValues.errors) {
114+
return coercedVariableValues.errors;
115+
}
116+
117+
return {
118+
schema,
119+
fragments,
120+
rootValue,
121+
contextValue,
122+
operation,
123+
variableValues: coercedVariableValues.coerced,
124+
fieldResolver: fieldResolver ?? defaultFieldResolver,
125+
typeResolver: typeResolver ?? defaultTypeResolver,
126+
subscribeFieldResolver: subscribeFieldResolver ?? defaultFieldResolver,
127+
errors: [],
128+
};
129+
}
130+
131+
/**
132+
* @internal
133+
*/
134+
export function buildPerEventExecutionContext(
135+
exeContext: ExecutionContext,
136+
payload: unknown,
137+
): ExecutionContext {
138+
return {
139+
...exeContext,
140+
rootValue: payload,
141+
errors: [],
142+
};
143+
}
144+
145+
/**
146+
* If a resolveType function is not given, then a default resolve behavior is
147+
* used which attempts two strategies:
148+
*
149+
* First, See if the provided value has a `__typename` field defined, if so, use
150+
* that value as name of the resolved type.
151+
*
152+
* Otherwise, test each possible type for the abstract type by calling
153+
* isTypeOf for the object being coerced, returning the first type that matches.
154+
*/
155+
export const defaultTypeResolver: GraphQLTypeResolver<unknown, unknown> =
156+
function (value, contextValue, info, abstractType) {
157+
// First, look for `__typename`.
158+
if (isObjectLike(value) && typeof value.__typename === 'string') {
159+
return value.__typename;
160+
}
161+
162+
// Otherwise, test each possible type.
163+
const possibleTypes = info.schema.getPossibleTypes(abstractType);
164+
const promisedIsTypeOfResults = [];
165+
166+
for (let i = 0; i < possibleTypes.length; i++) {
167+
const type = possibleTypes[i];
168+
169+
if (type.isTypeOf) {
170+
const isTypeOfResult = type.isTypeOf(value, contextValue, info);
171+
172+
if (isPromise(isTypeOfResult)) {
173+
promisedIsTypeOfResults[i] = isTypeOfResult;
174+
} else if (isTypeOfResult) {
175+
return type.name;
176+
}
177+
}
178+
}
179+
180+
if (promisedIsTypeOfResults.length) {
181+
return Promise.all(promisedIsTypeOfResults).then((isTypeOfResults) => {
182+
for (let i = 0; i < isTypeOfResults.length; i++) {
183+
if (isTypeOfResults[i]) {
184+
return possibleTypes[i].name;
185+
}
186+
}
187+
});
188+
}
189+
};
190+
191+
/**
192+
* If a resolve function is not given, then a default resolve behavior is used
193+
* which takes the property of the source object of the same name as the field
194+
* and returns it as the result, or if it's a function, returns the result
195+
* of calling that function while passing along args and context value.
196+
*/
197+
export const defaultFieldResolver: GraphQLFieldResolver<unknown, unknown> =
198+
function (source: any, args, contextValue, info) {
199+
// ensure source is a value for which property access is acceptable.
200+
if (isObjectLike(source) || typeof source === 'function') {
201+
const property = source[info.fieldName];
202+
if (typeof property === 'function') {
203+
return source[info.fieldName](args, contextValue, info);
204+
}
205+
return property;
206+
}
207+
};

0 commit comments

Comments
 (0)