1 | import {
|
2 | DocumentNode,
|
3 | SelectionSetNode,
|
4 | FieldNode,
|
5 | FragmentDefinitionNode,
|
6 | InlineFragmentNode,
|
7 | } from 'graphql';
|
8 |
|
9 | import {
|
10 | getMainDefinition,
|
11 | getFragmentDefinitions,
|
12 | createFragmentMap,
|
13 | shouldInclude,
|
14 | getDirectiveInfoFromField,
|
15 | isField,
|
16 | isInlineFragment,
|
17 | resultKeyNameFromField,
|
18 | argumentsObjectFromField,
|
19 | } from 'apollo-utilities';
|
20 |
|
21 | import {
|
22 | merge,
|
23 | Resolver,
|
24 | VariableMap,
|
25 | ExecContext,
|
26 | ExecInfo,
|
27 | ExecOptions,
|
28 | } from './graphql';
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 | export 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 |
|
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 |
|
80 | async 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 |
|
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 |
|
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 |
|
146 | async 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 |
|
166 | if (!field.selectionSet) {
|
167 | return result;
|
168 | }
|
169 |
|
170 |
|
171 |
|
172 | if (result == null) {
|
173 |
|
174 | return result;
|
175 | }
|
176 |
|
177 | if (Array.isArray(result)) {
|
178 | return executeSubSelectedArray(field, result, execContext);
|
179 | }
|
180 |
|
181 |
|
182 | return executeSelectionSet(field.selectionSet, result, execContext);
|
183 | }
|
184 |
|
185 | function executeSubSelectedArray(field, result, execContext) {
|
186 | return Promise.all(
|
187 | result.map(item => {
|
188 |
|
189 | if (item === null) {
|
190 | return null;
|
191 | }
|
192 |
|
193 |
|
194 | if (Array.isArray(item)) {
|
195 | return executeSubSelectedArray(field, item, execContext);
|
196 | }
|
197 |
|
198 |
|
199 | return executeSelectionSet(field.selectionSet, item, execContext);
|
200 | }),
|
201 | );
|
202 | }
|