UNPKG

14.8 kBJavaScriptView Raw
1import { __spread, __assign, __awaiter, __generator } from 'tslib';
2import { Kind, visit, getOperationAST } from 'graphql';
3import isPromise from 'is-promise';
4import DataLoader from 'dataloader';
5import { relocatedError } from '@graphql-tools/utils/es5';
6
7// adapted from https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-graphql/src/batching/merge-queries.js
8function createPrefix(index) {
9 return "graphqlTools" + index + "_";
10}
11function parseKey(prefixedKey) {
12 var match = /^graphqlTools([\d]+)_(.*)$/.exec(prefixedKey);
13 if (match && match.length === 3 && !isNaN(Number(match[1])) && match[2]) {
14 return { index: Number(match[1]), originalKey: match[2] };
15 }
16 return null;
17}
18
19// adapted from https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-graphql/src/batching/merge-queries.js
20/**
21 * Merge multiple queries into a single query in such a way that query results
22 * can be split and transformed as if they were obtained by running original queries.
23 *
24 * Merging algorithm involves several transformations:
25 * 1. Replace top-level fragment spreads with inline fragments (... on Query {})
26 * 2. Add unique aliases to all top-level query fields (including those on inline fragments)
27 * 3. Prefix all variable definitions and variable usages
28 * 4. Prefix names (and spreads) of fragments
29 *
30 * i.e transform:
31 * [
32 * `query Foo($id: ID!) { foo, bar(id: $id), ...FooQuery }
33 * fragment FooQuery on Query { baz }`,
34 *
35 * `query Bar($id: ID!) { foo: baz, bar(id: $id), ... on Query { baz } }`
36 * ]
37 * to:
38 * query (
39 * $graphqlTools1_id: ID!
40 * $graphqlTools2_id: ID!
41 * ) {
42 * graphqlTools1_foo: foo,
43 * graphqlTools1_bar: bar(id: $graphqlTools1_id)
44 * ... on Query {
45 * graphqlTools1__baz: baz
46 * }
47 * graphqlTools1__foo: baz
48 * graphqlTools1__bar: bar(id: $graphqlTools1__id)
49 * ... on Query {
50 * graphqlTools1__baz: baz
51 * }
52 * }
53 */
54function mergeExecutionParams(execs, extensionsReducer) {
55 var mergedVariables = Object.create(null);
56 var mergedVariableDefinitions = [];
57 var mergedSelections = [];
58 var mergedFragmentDefinitions = [];
59 var mergedExtensions = Object.create(null);
60 var operation;
61 execs.forEach(function (executionParams, index) {
62 var prefixedExecutionParams = prefixExecutionParams(createPrefix(index), executionParams);
63 prefixedExecutionParams.document.definitions.forEach(function (def) {
64 var _a;
65 if (isOperationDefinition(def)) {
66 operation = def.operation;
67 mergedSelections.push.apply(mergedSelections, __spread(def.selectionSet.selections));
68 mergedVariableDefinitions.push.apply(mergedVariableDefinitions, __spread(((_a = def.variableDefinitions) !== null && _a !== void 0 ? _a : [])));
69 }
70 if (isFragmentDefinition(def)) {
71 mergedFragmentDefinitions.push(def);
72 }
73 });
74 Object.assign(mergedVariables, prefixedExecutionParams.variables);
75 mergedExtensions = extensionsReducer(mergedExtensions, executionParams);
76 });
77 var mergedOperationDefinition = {
78 kind: Kind.OPERATION_DEFINITION,
79 operation: operation,
80 variableDefinitions: mergedVariableDefinitions,
81 selectionSet: {
82 kind: Kind.SELECTION_SET,
83 selections: mergedSelections,
84 },
85 };
86 return {
87 document: {
88 kind: Kind.DOCUMENT,
89 definitions: __spread([mergedOperationDefinition], mergedFragmentDefinitions),
90 },
91 variables: mergedVariables,
92 extensions: mergedExtensions,
93 context: execs[0].context,
94 info: execs[0].info,
95 };
96}
97function prefixExecutionParams(prefix, executionParams) {
98 var _a;
99 var document = aliasTopLevelFields(prefix, executionParams.document);
100 var variableNames = Object.keys(executionParams.variables);
101 if (variableNames.length === 0) {
102 return __assign(__assign({}, executionParams), { document: document });
103 }
104 document = visit(document, (_a = {},
105 _a[Kind.VARIABLE] = function (node) { return prefixNodeName(node, prefix); },
106 _a[Kind.FRAGMENT_DEFINITION] = function (node) { return prefixNodeName(node, prefix); },
107 _a[Kind.FRAGMENT_SPREAD] = function (node) { return prefixNodeName(node, prefix); },
108 _a));
109 var prefixedVariables = variableNames.reduce(function (acc, name) {
110 acc[prefix + name] = executionParams.variables[name];
111 return acc;
112 }, Object.create(null));
113 return {
114 document: document,
115 variables: prefixedVariables,
116 };
117}
118/**
119 * Adds prefixed aliases to top-level fields of the query.
120 *
121 * @see aliasFieldsInSelection for implementation details
122 */
123function aliasTopLevelFields(prefix, document) {
124 var _a, _b;
125 var transformer = (_a = {},
126 _a[Kind.OPERATION_DEFINITION] = function (def) {
127 var selections = def.selectionSet.selections;
128 return __assign(__assign({}, def), { selectionSet: __assign(__assign({}, def.selectionSet), { selections: aliasFieldsInSelection(prefix, selections, document) }) });
129 },
130 _a);
131 return visit(document, transformer, (_b = {}, _b[Kind.DOCUMENT] = ["definitions"], _b));
132}
133/**
134 * Add aliases to fields of the selection, including top-level fields of inline fragments.
135 * Fragment spreads are converted to inline fragments and their top-level fields are also aliased.
136 *
137 * Note that this method is shallow. It adds aliases only to the top-level fields and doesn't
138 * descend to field sub-selections.
139 *
140 * For example, transforms:
141 * {
142 * foo
143 * ... on Query { foo }
144 * ...FragmentWithBarField
145 * }
146 * To:
147 * {
148 * graphqlTools1_foo: foo
149 * ... on Query { graphqlTools1_foo: foo }
150 * ... on Query { graphqlTools1_bar: bar }
151 * }
152 */
153function aliasFieldsInSelection(prefix, selections, document) {
154 return selections.map(function (selection) {
155 switch (selection.kind) {
156 case Kind.INLINE_FRAGMENT:
157 return aliasFieldsInInlineFragment(prefix, selection, document);
158 case Kind.FRAGMENT_SPREAD: {
159 var inlineFragment = inlineFragmentSpread(selection, document);
160 return aliasFieldsInInlineFragment(prefix, inlineFragment, document);
161 }
162 case Kind.FIELD:
163 default:
164 return aliasField(selection, prefix);
165 }
166 });
167}
168/**
169 * Add aliases to top-level fields of the inline fragment.
170 * Returns new inline fragment node.
171 *
172 * For Example, transforms:
173 * ... on Query { foo, ... on Query { bar: foo } }
174 * To
175 * ... on Query { graphqlTools1_foo: foo, ... on Query { graphqlTools1_bar: foo } }
176 */
177function aliasFieldsInInlineFragment(prefix, fragment, document) {
178 var selections = fragment.selectionSet.selections;
179 return __assign(__assign({}, fragment), { selectionSet: __assign(__assign({}, fragment.selectionSet), { selections: aliasFieldsInSelection(prefix, selections, document) }) });
180}
181/**
182 * Replaces fragment spread with inline fragment
183 *
184 * Example:
185 * query { ...Spread }
186 * fragment Spread on Query { bar }
187 *
188 * Transforms to:
189 * query { ... on Query { bar } }
190 */
191function inlineFragmentSpread(spread, document) {
192 var fragment = document.definitions.find(function (def) { return isFragmentDefinition(def) && def.name.value === spread.name.value; });
193 if (!fragment) {
194 throw new Error("Fragment " + spread.name.value + " does not exist");
195 }
196 var typeCondition = fragment.typeCondition, selectionSet = fragment.selectionSet;
197 return {
198 kind: Kind.INLINE_FRAGMENT,
199 typeCondition: typeCondition,
200 selectionSet: selectionSet,
201 directives: spread.directives,
202 };
203}
204function prefixNodeName(namedNode, prefix) {
205 return __assign(__assign({}, namedNode), { name: __assign(__assign({}, namedNode.name), { value: prefix + namedNode.name.value }) });
206}
207/**
208 * Returns a new FieldNode with prefixed alias
209 *
210 * Example. Given prefix === "graphqlTools1_" transforms:
211 * { foo } -> { graphqlTools1_foo: foo }
212 * { foo: bar } -> { graphqlTools1_foo: bar }
213 */
214function aliasField(field, aliasPrefix) {
215 var aliasNode = field.alias ? field.alias : field.name;
216 return __assign(__assign({}, field), { alias: __assign(__assign({}, aliasNode), { value: aliasPrefix + aliasNode.value }) });
217}
218function isOperationDefinition(def) {
219 return def.kind === Kind.OPERATION_DEFINITION;
220}
221function isFragmentDefinition(def) {
222 return def.kind === Kind.FRAGMENT_DEFINITION;
223}
224
225// adapted from https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-graphql/src/batching/merge-queries.js
226/**
227 * Split and transform result of the query produced by the `merge` function
228 */
229function splitResult(mergedResult, numResults) {
230 var splitResults = [];
231 for (var i = 0; i < numResults; i++) {
232 splitResults.push({});
233 }
234 var data = mergedResult.data;
235 if (data) {
236 Object.keys(data).forEach(function (prefixedKey) {
237 var _a;
238 var _b = parseKey(prefixedKey), index = _b.index, originalKey = _b.originalKey;
239 if (!splitResults[index].data) {
240 splitResults[index].data = (_a = {}, _a[originalKey] = data[prefixedKey], _a);
241 }
242 else {
243 splitResults[index].data[originalKey] = data[prefixedKey];
244 }
245 });
246 }
247 var errors = mergedResult.errors;
248 if (errors) {
249 var newErrors_1 = Object.create(null);
250 errors.forEach(function (error) {
251 if (error.path) {
252 var parsedKey = parseKey(error.path[0]);
253 if (parsedKey) {
254 var index = parsedKey.index, originalKey = parsedKey.originalKey;
255 var newError = relocatedError(error, __spread([originalKey], error.path.slice(1)));
256 if (!newErrors_1[index]) {
257 newErrors_1[index] = [newError];
258 }
259 else {
260 newErrors_1[index].push(newError);
261 }
262 return;
263 }
264 }
265 splitResults.forEach(function (_splitResult, index) {
266 if (!newErrors_1[index]) {
267 newErrors_1[index] = [error];
268 }
269 else {
270 newErrors_1[index].push(error);
271 }
272 });
273 });
274 Object.keys(newErrors_1).forEach(function (index) {
275 splitResults[index].errors = newErrors_1[index];
276 });
277 }
278 return splitResults;
279}
280
281function createBatchingExecutor(executor, dataLoaderOptions, extensionsReducer) {
282 var loader = new DataLoader(createLoadFn(executor, extensionsReducer !== null && extensionsReducer !== void 0 ? extensionsReducer : defaultExtensionsReducer), dataLoaderOptions);
283 return function (executionParams) { return loader.load(executionParams); };
284}
285function createLoadFn(executor, extensionsReducer) {
286 var _this = this;
287 return function (execs) { return __awaiter(_this, void 0, void 0, function () {
288 var execBatches, index, exec, currentBatch, operationType, currentOperationType, containsPromises, executionResults, results;
289 return __generator(this, function (_a) {
290 execBatches = [];
291 index = 0;
292 exec = execs[index];
293 currentBatch = [exec];
294 execBatches.push(currentBatch);
295 operationType = getOperationAST(exec.document, undefined).operation;
296 while (++index < execs.length) {
297 currentOperationType = getOperationAST(execs[index].document, undefined).operation;
298 if (operationType === currentOperationType) {
299 currentBatch.push(execs[index]);
300 }
301 else {
302 currentBatch = [execs[index]];
303 execBatches.push(currentBatch);
304 }
305 }
306 containsPromises = false;
307 executionResults = [];
308 execBatches.forEach(function (execBatch) {
309 var mergedExecutionParams = mergeExecutionParams(execBatch, extensionsReducer);
310 var executionResult = executor(mergedExecutionParams);
311 if (isPromise(executionResult)) {
312 containsPromises = true;
313 }
314 executionResults.push(executionResult);
315 });
316 if (containsPromises) {
317 return [2 /*return*/, Promise.all(executionResults).then(function (resultBatches) {
318 var results = [];
319 resultBatches.forEach(function (resultBatch, index) {
320 results = results.concat(splitResult(resultBatch, execBatches[index].length));
321 });
322 return results;
323 })];
324 }
325 results = [];
326 executionResults.forEach(function (resultBatch, index) {
327 results = results.concat(splitResult(resultBatch, execBatches[index].length));
328 });
329 return [2 /*return*/, results];
330 });
331 }); };
332}
333function defaultExtensionsReducer(mergedExtensions, executionParams) {
334 var newExtensions = executionParams.extensions;
335 if (newExtensions != null) {
336 Object.assign(mergedExtensions, newExtensions);
337 }
338 return mergedExtensions;
339}
340
341function memoize2of4(fn) {
342 var cache1;
343 function memoized(a1, a2, a3, a4) {
344 if (!cache1) {
345 cache1 = new WeakMap();
346 var cache2_1 = new WeakMap();
347 cache1.set(a1, cache2_1);
348 var newValue = fn(a1, a2, a3, a4);
349 cache2_1.set(a2, newValue);
350 return newValue;
351 }
352 var cache2 = cache1.get(a1);
353 if (!cache2) {
354 cache2 = new WeakMap();
355 cache1.set(a1, cache2);
356 var newValue = fn(a1, a2, a3, a4);
357 cache2.set(a2, newValue);
358 return newValue;
359 }
360 var cachedValue = cache2.get(a2);
361 if (cachedValue === undefined) {
362 var newValue = fn(a1, a2, a3, a4);
363 cache2.set(a2, newValue);
364 return newValue;
365 }
366 return cachedValue;
367 }
368 return memoized;
369}
370
371var getBatchingExecutor = memoize2of4(function (_context, executor, dataLoaderOptions, extensionsReducer) {
372 return createBatchingExecutor(executor, dataLoaderOptions, extensionsReducer);
373});
374
375export { createBatchingExecutor, getBatchingExecutor };
376//# sourceMappingURL=index.esm.js.map