UNPKG

9.49 kBJavaScriptView Raw
1"use strict";
2// adapted from https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-graphql/src/batching/merge-queries.js
3Object.defineProperty(exports, "__esModule", { value: true });
4exports.mergeRequests = void 0;
5const graphql_1 = require("graphql");
6const utils_1 = require("@graphql-tools/utils");
7const prefix_js_1 = require("./prefix.js");
8/**
9 * Merge multiple queries into a single query in such a way that query results
10 * can be split and transformed as if they were obtained by running original queries.
11 *
12 * Merging algorithm involves several transformations:
13 * 1. Replace top-level fragment spreads with inline fragments (... on Query {})
14 * 2. Add unique aliases to all top-level query fields (including those on inline fragments)
15 * 3. Prefix all variable definitions and variable usages
16 * 4. Prefix names (and spreads) of fragments
17 *
18 * i.e transform:
19 * [
20 * `query Foo($id: ID!) { foo, bar(id: $id), ...FooQuery }
21 * fragment FooQuery on Query { baz }`,
22 *
23 * `query Bar($id: ID!) { foo: baz, bar(id: $id), ... on Query { baz } }`
24 * ]
25 * to:
26 * query (
27 * $graphqlTools1_id: ID!
28 * $graphqlTools2_id: ID!
29 * ) {
30 * graphqlTools1_foo: foo,
31 * graphqlTools1_bar: bar(id: $graphqlTools1_id)
32 * ... on Query {
33 * graphqlTools1__baz: baz
34 * }
35 * graphqlTools1__foo: baz
36 * graphqlTools1__bar: bar(id: $graphqlTools1__id)
37 * ... on Query {
38 * graphqlTools1__baz: baz
39 * }
40 * }
41 */
42function mergeRequests(requests, extensionsReducer) {
43 var _a, _b, _c, _d, _e;
44 const mergedVariables = Object.create(null);
45 const mergedVariableDefinitions = [];
46 const mergedSelections = [];
47 const mergedFragmentDefinitions = [];
48 let mergedExtensions = Object.create(null);
49 for (const index in requests) {
50 const request = requests[index];
51 const prefixedRequests = prefixRequest((0, prefix_js_1.createPrefix)(index), request);
52 for (const def of prefixedRequests.document.definitions) {
53 if (isOperationDefinition(def)) {
54 mergedSelections.push(...def.selectionSet.selections);
55 if (def.variableDefinitions) {
56 mergedVariableDefinitions.push(...def.variableDefinitions);
57 }
58 }
59 if (isFragmentDefinition(def)) {
60 mergedFragmentDefinitions.push(def);
61 }
62 }
63 Object.assign(mergedVariables, prefixedRequests.variables);
64 mergedExtensions = extensionsReducer(mergedExtensions, request);
65 }
66 const firstRequest = requests[0];
67 const operationType = (_a = firstRequest.operationType) !== null && _a !== void 0 ? _a : (0, utils_1.getOperationASTFromRequest)(firstRequest).operation;
68 const mergedOperationDefinition = {
69 kind: graphql_1.Kind.OPERATION_DEFINITION,
70 operation: operationType,
71 variableDefinitions: mergedVariableDefinitions,
72 selectionSet: {
73 kind: graphql_1.Kind.SELECTION_SET,
74 selections: mergedSelections,
75 },
76 };
77 const operationName = (_b = firstRequest.operationName) !== null && _b !== void 0 ? _b : (_e = (_d = (_c = firstRequest.info) === null || _c === void 0 ? void 0 : _c.operation) === null || _d === void 0 ? void 0 : _d.name) === null || _e === void 0 ? void 0 : _e.value;
78 if (operationName) {
79 mergedOperationDefinition.name = {
80 kind: graphql_1.Kind.NAME,
81 value: operationName,
82 };
83 }
84 return {
85 document: {
86 kind: graphql_1.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,
94 };
95}
96exports.mergeRequests = mergeRequests;
97function prefixRequest(prefix, request) {
98 var _a;
99 const executionVariables = (_a = request.variables) !== null && _a !== void 0 ? _a : {};
100 function prefixNode(node) {
101 return prefixNodeName(node, prefix);
102 }
103 let prefixedDocument = aliasTopLevelFields(prefix, request.document);
104 const executionVariableNames = Object.keys(executionVariables);
105 const hasFragmentDefinitions = request.document.definitions.some(def => isFragmentDefinition(def));
106 const fragmentSpreadImpl = {};
107 if (executionVariableNames.length > 0 || hasFragmentDefinitions) {
108 prefixedDocument = (0, graphql_1.visit)(prefixedDocument, {
109 [graphql_1.Kind.VARIABLE]: prefixNode,
110 [graphql_1.Kind.FRAGMENT_DEFINITION]: prefixNode,
111 [graphql_1.Kind.FRAGMENT_SPREAD]: node => {
112 node = prefixNodeName(node, prefix);
113 fragmentSpreadImpl[node.name.value] = true;
114 return node;
115 },
116 });
117 }
118 const prefixedVariables = {};
119 for (const variableName of executionVariableNames) {
120 prefixedVariables[prefix + variableName] = executionVariables[variableName];
121 }
122 if (hasFragmentDefinitions) {
123 prefixedDocument = {
124 ...prefixedDocument,
125 definitions: prefixedDocument.definitions.filter(def => {
126 return !isFragmentDefinition(def) || fragmentSpreadImpl[def.name.value];
127 }),
128 };
129 }
130 return {
131 document: prefixedDocument,
132 variables: prefixedVariables,
133 };
134}
135/**
136 * Adds prefixed aliases to top-level fields of the query.
137 *
138 * @see aliasFieldsInSelection for implementation details
139 */
140function aliasTopLevelFields(prefix, document) {
141 const transformer = {
142 [graphql_1.Kind.OPERATION_DEFINITION]: (def) => {
143 const { selections } = def.selectionSet;
144 return {
145 ...def,
146 selectionSet: {
147 ...def.selectionSet,
148 selections: aliasFieldsInSelection(prefix, selections, document),
149 },
150 };
151 },
152 };
153 return (0, graphql_1.visit)(document, transformer, {
154 [graphql_1.Kind.DOCUMENT]: [`definitions`],
155 });
156}
157/**
158 * Add aliases to fields of the selection, including top-level fields of inline fragments.
159 * Fragment spreads are converted to inline fragments and their top-level fields are also aliased.
160 *
161 * Note that this method is shallow. It adds aliases only to the top-level fields and doesn't
162 * descend to field sub-selections.
163 *
164 * For example, transforms:
165 * {
166 * foo
167 * ... on Query { foo }
168 * ...FragmentWithBarField
169 * }
170 * To:
171 * {
172 * graphqlTools1_foo: foo
173 * ... on Query { graphqlTools1_foo: foo }
174 * ... on Query { graphqlTools1_bar: bar }
175 * }
176 */
177function aliasFieldsInSelection(prefix, selections, document) {
178 return selections.map(selection => {
179 switch (selection.kind) {
180 case graphql_1.Kind.INLINE_FRAGMENT:
181 return aliasFieldsInInlineFragment(prefix, selection, document);
182 case graphql_1.Kind.FRAGMENT_SPREAD: {
183 const inlineFragment = inlineFragmentSpread(selection, document);
184 return aliasFieldsInInlineFragment(prefix, inlineFragment, document);
185 }
186 case graphql_1.Kind.FIELD:
187 default:
188 return aliasField(selection, prefix);
189 }
190 });
191}
192/**
193 * Add aliases to top-level fields of the inline fragment.
194 * Returns new inline fragment node.
195 *
196 * For Example, transforms:
197 * ... on Query { foo, ... on Query { bar: foo } }
198 * To
199 * ... on Query { graphqlTools1_foo: foo, ... on Query { graphqlTools1_bar: foo } }
200 */
201function aliasFieldsInInlineFragment(prefix, fragment, document) {
202 const { selections } = fragment.selectionSet;
203 return {
204 ...fragment,
205 selectionSet: {
206 ...fragment.selectionSet,
207 selections: aliasFieldsInSelection(prefix, selections, document),
208 },
209 };
210}
211/**
212 * Replaces fragment spread with inline fragment
213 *
214 * Example:
215 * query { ...Spread }
216 * fragment Spread on Query { bar }
217 *
218 * Transforms to:
219 * query { ... on Query { bar } }
220 */
221function inlineFragmentSpread(spread, document) {
222 const fragment = document.definitions.find(def => isFragmentDefinition(def) && def.name.value === spread.name.value);
223 if (!fragment) {
224 throw new Error(`Fragment ${spread.name.value} does not exist`);
225 }
226 const { typeCondition, selectionSet } = fragment;
227 return {
228 kind: graphql_1.Kind.INLINE_FRAGMENT,
229 typeCondition,
230 selectionSet,
231 directives: spread.directives,
232 };
233}
234function prefixNodeName(namedNode, prefix) {
235 return {
236 ...namedNode,
237 name: {
238 ...namedNode.name,
239 value: prefix + namedNode.name.value,
240 },
241 };
242}
243/**
244 * Returns a new FieldNode with prefixed alias
245 *
246 * Example. Given prefix === "graphqlTools1_" transforms:
247 * { foo } -> { graphqlTools1_foo: foo }
248 * { foo: bar } -> { graphqlTools1_foo: bar }
249 */
250function aliasField(field, aliasPrefix) {
251 const aliasNode = field.alias ? field.alias : field.name;
252 return {
253 ...field,
254 alias: {
255 ...aliasNode,
256 value: aliasPrefix + aliasNode.value,
257 },
258 };
259}
260function isOperationDefinition(def) {
261 return def.kind === graphql_1.Kind.OPERATION_DEFINITION;
262}
263function isFragmentDefinition(def) {
264 return def.kind === graphql_1.Kind.FRAGMENT_DEFINITION;
265}