UNPKG

4.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 shouldInclude,
14 getDirectiveInfoFromField,
15 isField,
16 isInlineFragment,
17 resultKeyNameFromField,
18 argumentsObjectFromField,
19} from 'apollo-utilities';
20
21import {
22 merge,
23 Resolver,
24 VariableMap,
25 ExecContext,
26 ExecInfo,
27 ExecOptions,
28} from './graphql';
29
30/* Based on graphql function from graphql-js:
31 *
32 * graphql(
33 * schema: GraphQLSchema,
34 * requestString: string,
35 * rootValue?: ?any,
36 * contextValue?: ?any,
37 * variableValues?: ?{[key: string]: any},
38 * operationName?: ?string
39 * ): Promise<GraphQLResult>
40 *
41 * The default export as of graphql-anywhere is sync as of 4.0,
42 * but below is an exported alternative that is async.
43 * In the 5.0 version, this will be the only export again
44 * and it will be async
45 */
46export function graphql(
47 resolver: Resolver,
48 document: DocumentNode,
49 rootValue?: any,
50 contextValue?: any,
51 variableValues?: VariableMap,
52 execOptions: ExecOptions = {},
53): Promise<null | Object> {
54 const mainDefinition = getMainDefinition(document);
55
56 const fragments = getFragmentDefinitions(document);
57 const fragmentMap = createFragmentMap(fragments);
58
59 const resultMapper = execOptions.resultMapper;
60
61 // Default matcher always matches all fragments
62 const fragmentMatcher = execOptions.fragmentMatcher || (() => true);
63
64 const execContext: ExecContext = {
65 fragmentMap,
66 contextValue,
67 variableValues,
68 resultMapper,
69 resolver,
70 fragmentMatcher,
71 };
72
73 return executeSelectionSet(
74 mainDefinition.selectionSet,
75 rootValue,
76 execContext,
77 );
78}
79
80async function executeSelectionSet(
81 selectionSet: SelectionSetNode,
82 rootValue: any,
83 execContext: ExecContext,
84) {
85 const { fragmentMap, contextValue, variableValues: variables } = execContext;
86
87 const result = {};
88
89 const execute = async selection => {
90 if (!shouldInclude(selection, variables)) {
91 // Skip this entirely
92 return;
93 }
94
95 if (isField(selection)) {
96 const fieldResult = await executeField(selection, rootValue, execContext);
97
98 const resultFieldKey = resultKeyNameFromField(selection);
99
100 if (fieldResult !== undefined) {
101 if (result[resultFieldKey] === undefined) {
102 result[resultFieldKey] = fieldResult;
103 } else {
104 merge(result[resultFieldKey], fieldResult);
105 }
106 }
107
108 return;
109 }
110
111 let fragment: InlineFragmentNode | FragmentDefinitionNode;
112
113 if (isInlineFragment(selection)) {
114 fragment = selection;
115 } else {
116 // This is a named fragment
117 fragment = fragmentMap[selection.name.value];
118
119 if (!fragment) {
120 throw new Error(`No fragment named ${selection.name.value}`);
121 }
122 }
123
124 const typeCondition = fragment.typeCondition.name.value;
125
126 if (execContext.fragmentMatcher(rootValue, typeCondition, contextValue)) {
127 const fragmentResult = await executeSelectionSet(
128 fragment.selectionSet,
129 rootValue,
130 execContext,
131 );
132
133 merge(result, fragmentResult);
134 }
135 };
136
137 await Promise.all(selectionSet.selections.map(execute));
138
139 if (execContext.resultMapper) {
140 return execContext.resultMapper(result, rootValue);
141 }
142
143 return result;
144}
145
146async function executeField(
147 field: FieldNode,
148 rootValue: any,
149 execContext: ExecContext,
150): Promise<null | Object> {
151 const { variableValues: variables, contextValue, resolver } = execContext;
152
153 const fieldName = field.name.value;
154 const args = argumentsObjectFromField(field, variables);
155
156 const info: ExecInfo = {
157 isLeaf: !field.selectionSet,
158 resultKey: resultKeyNameFromField(field),
159 directives: getDirectiveInfoFromField(field, variables),
160 field,
161 };
162
163 const result = await resolver(fieldName, rootValue, args, contextValue, info);
164
165 // Handle all scalar types here
166 if (!field.selectionSet) {
167 return result;
168 }
169
170 // From here down, the field has a selection set, which means it's trying to
171 // query a GraphQLObjectType
172 if (result == null) {
173 // Basically any field in a GraphQL response can be null, or missing
174 return result;
175 }
176
177 if (Array.isArray(result)) {
178 return executeSubSelectedArray(field, result, execContext);
179 }
180
181 // Returned value is an object, and the query has a sub-selection. Recurse.
182 return executeSelectionSet(field.selectionSet, result, execContext);
183}
184
185function executeSubSelectedArray(field, result, execContext) {
186 return Promise.all(
187 result.map(item => {
188 // null value in array
189 if (item === null) {
190 return null;
191 }
192
193 // This is a nested array, recurse
194 if (Array.isArray(item)) {
195 return executeSubSelectedArray(field, item, execContext);
196 }
197
198 // This is an object, run the selection set on it
199 return executeSelectionSet(field.selectionSet, item, execContext);
200 }),
201 );
202}