UNPKG

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