1 | import DataLoader from 'dataloader';
|
2 | import { Kind, visit } from 'graphql';
|
3 | import { relocatedError } from '@graphql-tools/utils';
|
4 |
|
5 |
|
6 | function createPrefix(index) {
|
7 | return `graphqlTools${index}_`;
|
8 | }
|
9 | function parseKey(prefixedKey) {
|
10 | const match = /^graphqlTools([\d]+)_(.*)$/.exec(prefixedKey);
|
11 | if (match && match.length === 3 && !isNaN(Number(match[1])) && match[2]) {
|
12 | return { index: Number(match[1]), originalKey: match[2] };
|
13 | }
|
14 | throw new Error(`Key ${prefixedKey} is not correctly prefixed`);
|
15 | }
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 | function mergeRequests(requests, extensionsReducer) {
|
53 | const mergedVariables = Object.create(null);
|
54 | const mergedVariableDefinitions = [];
|
55 | const mergedSelections = [];
|
56 | const mergedFragmentDefinitions = [];
|
57 | let mergedExtensions = Object.create(null);
|
58 | for (const index in requests) {
|
59 | const request = requests[index];
|
60 | const prefixedRequests = prefixRequest(createPrefix(index), request);
|
61 | for (const def of prefixedRequests.document.definitions) {
|
62 | if (isOperationDefinition(def)) {
|
63 | mergedSelections.push(...def.selectionSet.selections);
|
64 | if (def.variableDefinitions) {
|
65 | mergedVariableDefinitions.push(...def.variableDefinitions);
|
66 | }
|
67 | }
|
68 | if (isFragmentDefinition(def)) {
|
69 | mergedFragmentDefinitions.push(def);
|
70 | }
|
71 | }
|
72 | Object.assign(mergedVariables, prefixedRequests.variables);
|
73 | mergedExtensions = extensionsReducer(mergedExtensions, request);
|
74 | }
|
75 | const mergedOperationDefinition = {
|
76 | kind: Kind.OPERATION_DEFINITION,
|
77 | operation: requests[0].operationType,
|
78 | variableDefinitions: mergedVariableDefinitions,
|
79 | selectionSet: {
|
80 | kind: Kind.SELECTION_SET,
|
81 | selections: mergedSelections,
|
82 | },
|
83 | };
|
84 | return {
|
85 | document: {
|
86 | kind: Kind.DOCUMENT,
|
87 | definitions: [mergedOperationDefinition, ...mergedFragmentDefinitions],
|
88 | },
|
89 | variables: mergedVariables,
|
90 | extensions: mergedExtensions,
|
91 | context: requests[0].context,
|
92 | info: requests[0].info,
|
93 | operationType: requests[0].operationType,
|
94 | };
|
95 | }
|
96 | function prefixRequest(prefix, request) {
|
97 | var _a;
|
98 | const executionVariables = (_a = request.variables) !== null && _a !== void 0 ? _a : {};
|
99 | function prefixNode(node) {
|
100 | return prefixNodeName(node, prefix);
|
101 | }
|
102 | let prefixedDocument = aliasTopLevelFields(prefix, request.document);
|
103 | const executionVariableNames = Object.keys(executionVariables);
|
104 | if (executionVariableNames.length > 0) {
|
105 | prefixedDocument = visit(prefixedDocument, {
|
106 | [Kind.VARIABLE]: prefixNode,
|
107 | [Kind.FRAGMENT_DEFINITION]: prefixNode,
|
108 | [Kind.FRAGMENT_SPREAD]: prefixNode,
|
109 | });
|
110 | }
|
111 | const prefixedVariables = {};
|
112 | for (const variableName of executionVariableNames) {
|
113 | prefixedVariables[prefix + variableName] = executionVariables[variableName];
|
114 | }
|
115 | return {
|
116 | document: prefixedDocument,
|
117 | variables: prefixedVariables,
|
118 | operationType: request.operationType,
|
119 | };
|
120 | }
|
121 |
|
122 |
|
123 |
|
124 |
|
125 |
|
126 | function aliasTopLevelFields(prefix, document) {
|
127 | const transformer = {
|
128 | [Kind.OPERATION_DEFINITION]: (def) => {
|
129 | const { selections } = def.selectionSet;
|
130 | return {
|
131 | ...def,
|
132 | selectionSet: {
|
133 | ...def.selectionSet,
|
134 | selections: aliasFieldsInSelection(prefix, selections, document),
|
135 | },
|
136 | };
|
137 | },
|
138 | };
|
139 | return visit(document, transformer, {
|
140 | [Kind.DOCUMENT]: [`definitions`],
|
141 | });
|
142 | }
|
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 |
|
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 |
|
155 |
|
156 |
|
157 |
|
158 |
|
159 |
|
160 |
|
161 |
|
162 |
|
163 | function aliasFieldsInSelection(prefix, selections, document) {
|
164 | return selections.map(selection => {
|
165 | switch (selection.kind) {
|
166 | case Kind.INLINE_FRAGMENT:
|
167 | return aliasFieldsInInlineFragment(prefix, selection, document);
|
168 | case Kind.FRAGMENT_SPREAD: {
|
169 | const inlineFragment = inlineFragmentSpread(selection, document);
|
170 | return aliasFieldsInInlineFragment(prefix, inlineFragment, document);
|
171 | }
|
172 | case Kind.FIELD:
|
173 | default:
|
174 | return aliasField(selection, prefix);
|
175 | }
|
176 | });
|
177 | }
|
178 |
|
179 |
|
180 |
|
181 |
|
182 |
|
183 |
|
184 |
|
185 |
|
186 |
|
187 | function aliasFieldsInInlineFragment(prefix, fragment, document) {
|
188 | const { selections } = fragment.selectionSet;
|
189 | return {
|
190 | ...fragment,
|
191 | selectionSet: {
|
192 | ...fragment.selectionSet,
|
193 | selections: aliasFieldsInSelection(prefix, selections, document),
|
194 | },
|
195 | };
|
196 | }
|
197 |
|
198 |
|
199 |
|
200 |
|
201 |
|
202 |
|
203 |
|
204 |
|
205 |
|
206 |
|
207 | function inlineFragmentSpread(spread, document) {
|
208 | const fragment = document.definitions.find(def => isFragmentDefinition(def) && def.name.value === spread.name.value);
|
209 | if (!fragment) {
|
210 | throw new Error(`Fragment ${spread.name.value} does not exist`);
|
211 | }
|
212 | const { typeCondition, selectionSet } = fragment;
|
213 | return {
|
214 | kind: Kind.INLINE_FRAGMENT,
|
215 | typeCondition,
|
216 | selectionSet,
|
217 | directives: spread.directives,
|
218 | };
|
219 | }
|
220 | function prefixNodeName(namedNode, prefix) {
|
221 | return {
|
222 | ...namedNode,
|
223 | name: {
|
224 | ...namedNode.name,
|
225 | value: prefix + namedNode.name.value,
|
226 | },
|
227 | };
|
228 | }
|
229 |
|
230 |
|
231 |
|
232 |
|
233 |
|
234 |
|
235 |
|
236 | function aliasField(field, aliasPrefix) {
|
237 | const aliasNode = field.alias ? field.alias : field.name;
|
238 | return {
|
239 | ...field,
|
240 | alias: {
|
241 | ...aliasNode,
|
242 | value: aliasPrefix + aliasNode.value,
|
243 | },
|
244 | };
|
245 | }
|
246 | function isOperationDefinition(def) {
|
247 | return def.kind === Kind.OPERATION_DEFINITION;
|
248 | }
|
249 | function isFragmentDefinition(def) {
|
250 | return def.kind === Kind.FRAGMENT_DEFINITION;
|
251 | }
|
252 |
|
253 |
|
254 |
|
255 |
|
256 |
|
257 | function splitResult({ data, errors }, numResults) {
|
258 | const splitResults = [];
|
259 | for (let i = 0; i < numResults; i++) {
|
260 | splitResults.push({});
|
261 | }
|
262 | if (data) {
|
263 | for (const prefixedKey in data) {
|
264 | const { index, originalKey } = parseKey(prefixedKey);
|
265 | const result = splitResults[index];
|
266 | if (result == null) {
|
267 | continue;
|
268 | }
|
269 | if (result.data == null) {
|
270 | result.data = { [originalKey]: data[prefixedKey] };
|
271 | }
|
272 | else {
|
273 | result.data[originalKey] = data[prefixedKey];
|
274 | }
|
275 | }
|
276 | }
|
277 | if (errors) {
|
278 | for (const error of errors) {
|
279 | if (error.path) {
|
280 | const parsedKey = parseKey(error.path[0]);
|
281 | const { index, originalKey } = parsedKey;
|
282 | const newError = relocatedError(error, [originalKey, ...error.path.slice(1)]);
|
283 | const errors = (splitResults[index].errors = (splitResults[index].errors || []));
|
284 | errors.push(newError);
|
285 | }
|
286 | }
|
287 | }
|
288 | return splitResults;
|
289 | }
|
290 |
|
291 | function createBatchingExecutor(executor, dataLoaderOptions, extensionsReducer = defaultExtensionsReducer) {
|
292 | const loader = new DataLoader(createLoadFn(executor, extensionsReducer), dataLoaderOptions);
|
293 | return (request) => {
|
294 | return request.operationType === 'subscription' ? executor(request) : loader.load(request);
|
295 | };
|
296 | }
|
297 | function createLoadFn(executor, extensionsReducer) {
|
298 | return async function batchExecuteLoadFn(requests) {
|
299 | const execBatches = [];
|
300 | let index = 0;
|
301 | const request = requests[index];
|
302 | let currentBatch = [request];
|
303 | execBatches.push(currentBatch);
|
304 | const operationType = request.operationType;
|
305 | while (++index < requests.length) {
|
306 | const currentOperationType = requests[index].operationType;
|
307 | if (operationType == null) {
|
308 | throw new Error('Could not identify operation type of document.');
|
309 | }
|
310 | if (operationType === currentOperationType) {
|
311 | currentBatch.push(requests[index]);
|
312 | }
|
313 | else {
|
314 | currentBatch = [requests[index]];
|
315 | execBatches.push(currentBatch);
|
316 | }
|
317 | }
|
318 | const results = await Promise.all(execBatches.map(async (execBatch) => {
|
319 | const mergedRequests = mergeRequests(execBatch, extensionsReducer);
|
320 | const resultBatches = (await executor(mergedRequests));
|
321 | return splitResult(resultBatches, execBatch.length);
|
322 | }));
|
323 | return results.flat();
|
324 | };
|
325 | }
|
326 | function defaultExtensionsReducer(mergedExtensions, request) {
|
327 | const newExtensions = request.extensions;
|
328 | if (newExtensions != null) {
|
329 | Object.assign(mergedExtensions, newExtensions);
|
330 | }
|
331 | return mergedExtensions;
|
332 | }
|
333 |
|
334 | function memoize2of4(fn) {
|
335 | let cache1;
|
336 | function memoized(a1, a2, a3, a4) {
|
337 | if (!cache1) {
|
338 | cache1 = new WeakMap();
|
339 | const cache2 = new WeakMap();
|
340 | cache1.set(a1, cache2);
|
341 | const newValue = fn(a1, a2, a3, a4);
|
342 | cache2.set(a2, newValue);
|
343 | return newValue;
|
344 | }
|
345 | let cache2 = cache1.get(a1);
|
346 | if (!cache2) {
|
347 | cache2 = new WeakMap();
|
348 | cache1.set(a1, cache2);
|
349 | const newValue = fn(a1, a2, a3, a4);
|
350 | cache2.set(a2, newValue);
|
351 | return newValue;
|
352 | }
|
353 | const cachedValue = cache2.get(a2);
|
354 | if (cachedValue === undefined) {
|
355 | const newValue = fn(a1, a2, a3, a4);
|
356 | cache2.set(a2, newValue);
|
357 | return newValue;
|
358 | }
|
359 | return cachedValue;
|
360 | }
|
361 | return memoized;
|
362 | }
|
363 |
|
364 | const getBatchingExecutor = memoize2of4(function getBatchingExecutor(_context, executor, dataLoaderOptions, extensionsReducer) {
|
365 | return createBatchingExecutor(executor, dataLoaderOptions, extensionsReducer);
|
366 | });
|
367 |
|
368 | export { createBatchingExecutor, getBatchingExecutor };
|