UNPKG

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