UNPKG

5.99 kBPlain TextView Raw
1import {
2 DocumentNode,
3 SelectionSetNode,
4 FieldNode,
5 FragmentDefinitionNode,
6 InlineFragmentNode,
7} from 'graphql';
8
9import {
10 getMainDefinition,
11 getFragmentDefinitions,
12 createFragmentMap,
13 FragmentMap,
14 DirectiveInfo,
15 shouldInclude,
16 getDirectiveInfoFromField,
17 isField,
18 isInlineFragment,
19 resultKeyNameFromField,
20 argumentsObjectFromField,
21} from 'apollo-utilities';
22
23export type Resolver = (
24 fieldName: string,
25 rootValue: any,
26 args: any,
27 context: any,
28 info: ExecInfo,
29) => any;
30
31export type VariableMap = { [name: string]: any };
32
33export type ResultMapper = (
34 values: { [fieldName: string]: any },
35 rootValue: any,
36) => any;
37export type FragmentMatcher = (
38 rootValue: any,
39 typeCondition: string,
40 context: any,
41) => boolean;
42
43export type ExecContext = {
44 fragmentMap: FragmentMap;
45 contextValue: any;
46 variableValues: VariableMap;
47 resultMapper: ResultMapper;
48 resolver: Resolver;
49 fragmentMatcher: FragmentMatcher;
50};
51
52export type ExecInfo = {
53 isLeaf: boolean;
54 resultKey: string;
55 directives: DirectiveInfo;
56 field: FieldNode;
57};
58
59export type ExecOptions = {
60 resultMapper?: ResultMapper;
61 fragmentMatcher?: FragmentMatcher;
62};
63
64/* Based on graphql function from graphql-js:
65 *
66 * graphql(
67 * schema: GraphQLSchema,
68 * requestString: string,
69 * rootValue?: ?any,
70 * contextValue?: ?any,
71 * variableValues?: ?{[key: string]: any},
72 * operationName?: ?string
73 * ): Promise<GraphQLResult>
74 *
75 * The default export as of graphql-anywhere is sync as of 4.0,
76 * but below is an exported alternative that is async.
77 * In the 5.0 version, this will be the only export again
78 * and it will be async
79 */
80export function graphql(
81 resolver: Resolver,
82 document: DocumentNode,
83 rootValue?: any,
84 contextValue?: any,
85 variableValues: VariableMap = {},
86 execOptions: ExecOptions = {},
87) {
88 const mainDefinition = getMainDefinition(document);
89
90 const fragments = getFragmentDefinitions(document);
91 const fragmentMap = createFragmentMap(fragments);
92
93 const resultMapper = execOptions.resultMapper;
94
95 // Default matcher always matches all fragments
96 const fragmentMatcher = execOptions.fragmentMatcher || (() => true);
97
98 const execContext: ExecContext = {
99 fragmentMap,
100 contextValue,
101 variableValues,
102 resultMapper,
103 resolver,
104 fragmentMatcher,
105 };
106
107 return executeSelectionSet(
108 mainDefinition.selectionSet,
109 rootValue,
110 execContext,
111 );
112}
113
114function executeSelectionSet(
115 selectionSet: SelectionSetNode,
116 rootValue: any,
117 execContext: ExecContext,
118) {
119 const { fragmentMap, contextValue, variableValues: variables } = execContext;
120
121 const result = {};
122
123 selectionSet.selections.forEach(selection => {
124 if (variables && !shouldInclude(selection, variables)) {
125 // Skip selection sets which we're able to determine should not be run
126 return;
127 }
128
129 if (isField(selection)) {
130 const fieldResult = executeField(selection, rootValue, execContext);
131
132 const resultFieldKey = resultKeyNameFromField(selection);
133
134 if (fieldResult !== undefined) {
135 if (result[resultFieldKey] === undefined) {
136 result[resultFieldKey] = fieldResult;
137 } else {
138 merge(result[resultFieldKey], fieldResult);
139 }
140 }
141 } else {
142 let fragment: InlineFragmentNode | FragmentDefinitionNode;
143
144 if (isInlineFragment(selection)) {
145 fragment = selection;
146 } else {
147 // This is a named fragment
148 fragment = fragmentMap[selection.name.value];
149
150 if (!fragment) {
151 throw new Error(`No fragment named ${selection.name.value}`);
152 }
153 }
154
155 const typeCondition = fragment.typeCondition.name.value;
156
157 if (execContext.fragmentMatcher(rootValue, typeCondition, contextValue)) {
158 const fragmentResult = executeSelectionSet(
159 fragment.selectionSet,
160 rootValue,
161 execContext,
162 );
163
164 merge(result, fragmentResult);
165 }
166 }
167 });
168
169 if (execContext.resultMapper) {
170 return execContext.resultMapper(result, rootValue);
171 }
172
173 return result;
174}
175
176function executeField(
177 field: FieldNode,
178 rootValue: any,
179 execContext: ExecContext,
180): any {
181 const { variableValues: variables, contextValue, resolver } = execContext;
182
183 const fieldName = field.name.value;
184 const args = argumentsObjectFromField(field, variables);
185
186 const info: ExecInfo = {
187 isLeaf: !field.selectionSet,
188 resultKey: resultKeyNameFromField(field),
189 directives: getDirectiveInfoFromField(field, variables),
190 field,
191 };
192
193 const result = resolver(fieldName, rootValue, args, contextValue, info);
194
195 // Handle all scalar types here
196 if (!field.selectionSet) {
197 return result;
198 }
199
200 // From here down, the field has a selection set, which means it's trying to
201 // query a GraphQLObjectType
202 if (result == null) {
203 // Basically any field in a GraphQL response can be null, or missing
204 return result;
205 }
206
207 if (Array.isArray(result)) {
208 return executeSubSelectedArray(field, result, execContext);
209 }
210
211 // Returned value is an object, and the query has a sub-selection. Recurse.
212 return executeSelectionSet(field.selectionSet, result, execContext);
213}
214
215function executeSubSelectedArray(field, result, execContext) {
216 return result.map(item => {
217 // null value in array
218 if (item === null) {
219 return null;
220 }
221
222 // This is a nested array, recurse
223 if (Array.isArray(item)) {
224 return executeSubSelectedArray(field, item, execContext);
225 }
226
227 // This is an object, run the selection set on it
228 return executeSelectionSet(field.selectionSet, item, execContext);
229 });
230}
231
232const hasOwn = Object.prototype.hasOwnProperty;
233
234export function merge(dest, src) {
235 if (src !== null && typeof src === 'object') {
236 Object.keys(src).forEach(key => {
237 const srcVal = src[key];
238 if (!hasOwn.call(dest, key)) {
239 dest[key] = srcVal;
240 } else {
241 merge(dest[key], srcVal);
242 }
243 });
244 }
245}
246
\No newline at end of file