@graphql-tools/batch-execute 9.0.6 → 9.0.7-alpha-63d80f9d08b5d8448f4bfb8b04aeff28b977dec8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +722 -0
- package/dist/index.cjs +323 -0
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +316 -0
- package/package.json +32 -43
- package/cjs/createBatchingExecutor.js +0 -51
- package/cjs/getBatchingExecutor.js +0 -8
- package/cjs/index.js +0 -5
- package/cjs/mergeRequests.js +0 -264
- package/cjs/package.json +0 -1
- package/cjs/prefix.js +0 -42
- package/cjs/splitResult.js +0 -49
- package/esm/createBatchingExecutor.js +0 -46
- package/esm/getBatchingExecutor.js +0 -5
- package/esm/index.js +0 -2
- package/esm/mergeRequests.js +0 -260
- package/esm/prefix.js +0 -36
- package/esm/splitResult.js +0 -45
- package/typings/createBatchingExecutor.d.cts +0 -3
- package/typings/createBatchingExecutor.d.ts +0 -3
- package/typings/getBatchingExecutor.d.cts +0 -3
- package/typings/getBatchingExecutor.d.ts +0 -3
- package/typings/index.d.cts +0 -2
- package/typings/index.d.ts +0 -2
- package/typings/mergeRequests.d.cts +0 -36
- package/typings/mergeRequests.d.ts +0 -36
- package/typings/prefix.d.cts +0 -9
- package/typings/prefix.d.ts +0 -9
- package/typings/splitResult.d.cts +0 -5
- package/typings/splitResult.d.ts +0 -5
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var utils = require('@graphql-tools/utils');
|
|
4
|
+
var DataLoader = require('dataloader');
|
|
5
|
+
var valueOrPromise = require('value-or-promise');
|
|
6
|
+
var graphql = require('graphql');
|
|
7
|
+
|
|
8
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
|
+
|
|
10
|
+
var DataLoader__default = /*#__PURE__*/_interopDefault(DataLoader);
|
|
11
|
+
|
|
12
|
+
function createPrefix(index) {
|
|
13
|
+
return `_${index}_`;
|
|
14
|
+
}
|
|
15
|
+
function matchKey(prefixedKey) {
|
|
16
|
+
const match = /^_(\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
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
function parseKey(prefixedKey) {
|
|
23
|
+
const match = matchKey(prefixedKey);
|
|
24
|
+
if (!match) {
|
|
25
|
+
throw new Error(`Key ${prefixedKey} is not correctly prefixed`);
|
|
26
|
+
}
|
|
27
|
+
return match;
|
|
28
|
+
}
|
|
29
|
+
function parseKeyFromPath(path) {
|
|
30
|
+
let keyOffset = 0;
|
|
31
|
+
let match = null;
|
|
32
|
+
for (; !match && keyOffset < path.length; keyOffset++) {
|
|
33
|
+
const pathKey = path[keyOffset];
|
|
34
|
+
if (typeof pathKey === "string") {
|
|
35
|
+
match = matchKey(pathKey);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (!match) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`Path ${path.join(".")} does not contain correctly prefixed key`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
...match,
|
|
45
|
+
keyOffset
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function mergeRequests(requests, extensionsReducer) {
|
|
50
|
+
const mergedVariables = /* @__PURE__ */ Object.create(null);
|
|
51
|
+
const mergedVariableDefinitions = [];
|
|
52
|
+
const mergedSelections = [];
|
|
53
|
+
const mergedFragmentDefinitions = [];
|
|
54
|
+
let mergedExtensions = /* @__PURE__ */ Object.create(null);
|
|
55
|
+
for (let index = 0; index < requests.length; index++) {
|
|
56
|
+
const request = requests[index];
|
|
57
|
+
if (request) {
|
|
58
|
+
const prefixedRequests = prefixRequest(createPrefix(index), request);
|
|
59
|
+
for (const def of prefixedRequests.document.definitions) {
|
|
60
|
+
if (isOperationDefinition(def)) {
|
|
61
|
+
mergedSelections.push(...def.selectionSet.selections);
|
|
62
|
+
if (def.variableDefinitions) {
|
|
63
|
+
mergedVariableDefinitions.push(...def.variableDefinitions);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (isFragmentDefinition(def)) {
|
|
67
|
+
mergedFragmentDefinitions.push(def);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
Object.assign(mergedVariables, prefixedRequests.variables);
|
|
71
|
+
mergedExtensions = extensionsReducer(mergedExtensions, request);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const firstRequest = requests[0];
|
|
75
|
+
if (!firstRequest) {
|
|
76
|
+
throw new Error("At least one request is required");
|
|
77
|
+
}
|
|
78
|
+
const operationType = firstRequest.operationType ?? utils.getOperationASTFromRequest(firstRequest).operation;
|
|
79
|
+
const mergedOperationDefinition = {
|
|
80
|
+
kind: graphql.Kind.OPERATION_DEFINITION,
|
|
81
|
+
operation: operationType,
|
|
82
|
+
variableDefinitions: mergedVariableDefinitions,
|
|
83
|
+
selectionSet: {
|
|
84
|
+
kind: graphql.Kind.SELECTION_SET,
|
|
85
|
+
selections: mergedSelections
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const operationName = firstRequest.operationName ?? firstRequest.info?.operation?.name?.value;
|
|
89
|
+
if (operationName) {
|
|
90
|
+
mergedOperationDefinition.name = {
|
|
91
|
+
kind: graphql.Kind.NAME,
|
|
92
|
+
value: operationName
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
document: {
|
|
97
|
+
kind: graphql.Kind.DOCUMENT,
|
|
98
|
+
definitions: [mergedOperationDefinition, ...mergedFragmentDefinitions]
|
|
99
|
+
},
|
|
100
|
+
variables: mergedVariables,
|
|
101
|
+
extensions: mergedExtensions,
|
|
102
|
+
context: firstRequest.context,
|
|
103
|
+
info: firstRequest.info,
|
|
104
|
+
operationType,
|
|
105
|
+
rootValue: firstRequest.rootValue
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function prefixRequest(prefix, request) {
|
|
109
|
+
const executionVariables = request.variables ?? {};
|
|
110
|
+
function prefixNode(node) {
|
|
111
|
+
return prefixNodeName(node, prefix);
|
|
112
|
+
}
|
|
113
|
+
let prefixedDocument = aliasTopLevelFields(prefix, request.document);
|
|
114
|
+
const executionVariableNames = Object.keys(executionVariables);
|
|
115
|
+
const hasFragmentDefinitions = request.document.definitions.some(
|
|
116
|
+
(def) => isFragmentDefinition(def)
|
|
117
|
+
);
|
|
118
|
+
const fragmentSpreadImpl = {};
|
|
119
|
+
if (executionVariableNames.length > 0 || hasFragmentDefinitions) {
|
|
120
|
+
prefixedDocument = graphql.visit(prefixedDocument, {
|
|
121
|
+
[graphql.Kind.VARIABLE]: prefixNode,
|
|
122
|
+
[graphql.Kind.FRAGMENT_DEFINITION]: prefixNode,
|
|
123
|
+
[graphql.Kind.FRAGMENT_SPREAD]: (node) => {
|
|
124
|
+
node = prefixNodeName(node, prefix);
|
|
125
|
+
fragmentSpreadImpl[node.name.value] = true;
|
|
126
|
+
return node;
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
const prefixedVariables = {};
|
|
131
|
+
for (const variableName of executionVariableNames) {
|
|
132
|
+
prefixedVariables[prefix + variableName] = executionVariables[variableName];
|
|
133
|
+
}
|
|
134
|
+
if (hasFragmentDefinitions) {
|
|
135
|
+
prefixedDocument = {
|
|
136
|
+
...prefixedDocument,
|
|
137
|
+
definitions: prefixedDocument.definitions.filter((def) => {
|
|
138
|
+
return !isFragmentDefinition(def) || fragmentSpreadImpl[def.name.value];
|
|
139
|
+
})
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
document: prefixedDocument,
|
|
144
|
+
variables: prefixedVariables
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function aliasTopLevelFields(prefix, document) {
|
|
148
|
+
const transformer = {
|
|
149
|
+
[graphql.Kind.OPERATION_DEFINITION]: (def) => {
|
|
150
|
+
const { selections } = def.selectionSet;
|
|
151
|
+
return {
|
|
152
|
+
...def,
|
|
153
|
+
selectionSet: {
|
|
154
|
+
...def.selectionSet,
|
|
155
|
+
selections: aliasFieldsInSelection(prefix, selections, document)
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
return graphql.visit(document, transformer, {
|
|
161
|
+
[graphql.Kind.DOCUMENT]: [`definitions`]
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
function aliasFieldsInSelection(prefix, selections, document) {
|
|
165
|
+
return selections.map((selection) => {
|
|
166
|
+
switch (selection.kind) {
|
|
167
|
+
case graphql.Kind.INLINE_FRAGMENT:
|
|
168
|
+
return aliasFieldsInInlineFragment(prefix, selection, document);
|
|
169
|
+
case graphql.Kind.FRAGMENT_SPREAD: {
|
|
170
|
+
const inlineFragment = inlineFragmentSpread(selection, document);
|
|
171
|
+
return aliasFieldsInInlineFragment(prefix, inlineFragment, document);
|
|
172
|
+
}
|
|
173
|
+
case graphql.Kind.FIELD:
|
|
174
|
+
default:
|
|
175
|
+
return aliasField(selection, prefix);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
function aliasFieldsInInlineFragment(prefix, fragment, document) {
|
|
180
|
+
const { selections } = fragment.selectionSet;
|
|
181
|
+
return {
|
|
182
|
+
...fragment,
|
|
183
|
+
selectionSet: {
|
|
184
|
+
...fragment.selectionSet,
|
|
185
|
+
selections: aliasFieldsInSelection(prefix, selections, document)
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
function inlineFragmentSpread(spread, document) {
|
|
190
|
+
const fragment = document.definitions.find(
|
|
191
|
+
(def) => isFragmentDefinition(def) && def.name.value === spread.name.value
|
|
192
|
+
);
|
|
193
|
+
if (!fragment) {
|
|
194
|
+
throw new Error(`Fragment ${spread.name.value} does not exist`);
|
|
195
|
+
}
|
|
196
|
+
const { typeCondition, selectionSet } = fragment;
|
|
197
|
+
return {
|
|
198
|
+
kind: graphql.Kind.INLINE_FRAGMENT,
|
|
199
|
+
typeCondition,
|
|
200
|
+
selectionSet,
|
|
201
|
+
directives: spread.directives
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function prefixNodeName(namedNode, prefix) {
|
|
205
|
+
return {
|
|
206
|
+
...namedNode,
|
|
207
|
+
name: {
|
|
208
|
+
...namedNode.name,
|
|
209
|
+
value: prefix + namedNode.name.value
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function aliasField(field, aliasPrefix) {
|
|
214
|
+
const aliasNode = field.alias ? field.alias : field.name;
|
|
215
|
+
return {
|
|
216
|
+
...field,
|
|
217
|
+
alias: {
|
|
218
|
+
...aliasNode,
|
|
219
|
+
value: aliasPrefix + aliasNode.value
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function isOperationDefinition(def) {
|
|
224
|
+
return def.kind === graphql.Kind.OPERATION_DEFINITION;
|
|
225
|
+
}
|
|
226
|
+
function isFragmentDefinition(def) {
|
|
227
|
+
return def.kind === graphql.Kind.FRAGMENT_DEFINITION;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function splitResult({ data, errors }, numResults) {
|
|
231
|
+
const splitResults = [];
|
|
232
|
+
for (let i = 0; i < numResults; i++) {
|
|
233
|
+
splitResults.push({});
|
|
234
|
+
}
|
|
235
|
+
if (data) {
|
|
236
|
+
for (const prefixedKey in data) {
|
|
237
|
+
const { index, originalKey } = parseKey(prefixedKey);
|
|
238
|
+
const result = splitResults[index];
|
|
239
|
+
if (result == null) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (result.data == null) {
|
|
243
|
+
result.data = { [originalKey]: data[prefixedKey] };
|
|
244
|
+
} else {
|
|
245
|
+
result.data[originalKey] = data[prefixedKey];
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (errors) {
|
|
250
|
+
for (const error of errors) {
|
|
251
|
+
if (error.path) {
|
|
252
|
+
const { index, originalKey, keyOffset } = parseKeyFromPath(error.path);
|
|
253
|
+
const newError = utils.relocatedError(error, [
|
|
254
|
+
originalKey,
|
|
255
|
+
...error.path.slice(keyOffset)
|
|
256
|
+
]);
|
|
257
|
+
const splittedResult = splitResults[index];
|
|
258
|
+
if (splittedResult) {
|
|
259
|
+
const resultErrors = splittedResult.errors ||= [];
|
|
260
|
+
resultErrors.push(newError);
|
|
261
|
+
}
|
|
262
|
+
} else {
|
|
263
|
+
splitResults.forEach((result) => {
|
|
264
|
+
const resultErrors = result.errors ||= [];
|
|
265
|
+
resultErrors.push(error);
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return splitResults;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function createBatchingExecutor(executor, dataLoaderOptions, extensionsReducer = defaultExtensionsReducer) {
|
|
274
|
+
const loadFn = createLoadFn(executor, extensionsReducer);
|
|
275
|
+
const queryLoader = new DataLoader__default.default(loadFn, dataLoaderOptions);
|
|
276
|
+
const mutationLoader = new DataLoader__default.default(loadFn, dataLoaderOptions);
|
|
277
|
+
return function batchingExecutor(request) {
|
|
278
|
+
const operationType = request.operationType ?? utils.getOperationASTFromRequest(request)?.operation;
|
|
279
|
+
switch (operationType) {
|
|
280
|
+
case "query":
|
|
281
|
+
return queryLoader.load(request);
|
|
282
|
+
case "mutation":
|
|
283
|
+
return mutationLoader.load(request);
|
|
284
|
+
case "subscription":
|
|
285
|
+
return executor(request);
|
|
286
|
+
default:
|
|
287
|
+
throw new Error(`Invalid operation type "${operationType}"`);
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
function createLoadFn(executor, extensionsReducer) {
|
|
292
|
+
return function batchExecuteLoadFn(requests) {
|
|
293
|
+
if (requests.length === 1 && requests[0]) {
|
|
294
|
+
const request = requests[0];
|
|
295
|
+
return new valueOrPromise.ValueOrPromise(() => executor(request)).then((result) => [result]).catch((err) => [err]);
|
|
296
|
+
}
|
|
297
|
+
const mergedRequests = mergeRequests(requests, extensionsReducer);
|
|
298
|
+
return new valueOrPromise.ValueOrPromise(() => executor(mergedRequests)).then(
|
|
299
|
+
(resultBatches) => {
|
|
300
|
+
if (utils.isAsyncIterable(resultBatches)) {
|
|
301
|
+
throw new Error(
|
|
302
|
+
"Executor must not return incremental results for batching"
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
return splitResult(resultBatches, requests.length);
|
|
306
|
+
}
|
|
307
|
+
);
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
function defaultExtensionsReducer(mergedExtensions, request) {
|
|
311
|
+
const newExtensions = request.extensions;
|
|
312
|
+
if (newExtensions != null) {
|
|
313
|
+
Object.assign(mergedExtensions, newExtensions);
|
|
314
|
+
}
|
|
315
|
+
return mergedExtensions;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const getBatchingExecutor = utils.memoize2of4(function getBatchingExecutor2(_context, executor, dataLoaderOptions, extensionsReducer) {
|
|
319
|
+
return createBatchingExecutor(executor, dataLoaderOptions, extensionsReducer);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
exports.createBatchingExecutor = createBatchingExecutor;
|
|
323
|
+
exports.getBatchingExecutor = getBatchingExecutor;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Executor, ExecutionRequest } from '@graphql-tools/utils';
|
|
2
|
+
import DataLoader from 'dataloader';
|
|
3
|
+
|
|
4
|
+
declare function createBatchingExecutor(executor: Executor, dataLoaderOptions?: DataLoader.Options<any, any, any>, extensionsReducer?: (mergedExtensions: Record<string, any>, request: ExecutionRequest) => Record<string, any>): Executor;
|
|
5
|
+
|
|
6
|
+
declare const getBatchingExecutor: (_context: Record<string, any>, executor: Executor, dataLoaderOptions?: DataLoader.Options<any, any, any> | undefined, extensionsReducer?: undefined | ((mergedExtensions: Record<string, any>, request: ExecutionRequest) => Record<string, any>)) => Executor;
|
|
7
|
+
|
|
8
|
+
export { createBatchingExecutor, getBatchingExecutor };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Executor, ExecutionRequest } from '@graphql-tools/utils';
|
|
2
|
+
import DataLoader from 'dataloader';
|
|
3
|
+
|
|
4
|
+
declare function createBatchingExecutor(executor: Executor, dataLoaderOptions?: DataLoader.Options<any, any, any>, extensionsReducer?: (mergedExtensions: Record<string, any>, request: ExecutionRequest) => Record<string, any>): Executor;
|
|
5
|
+
|
|
6
|
+
declare const getBatchingExecutor: (_context: Record<string, any>, executor: Executor, dataLoaderOptions?: DataLoader.Options<any, any, any> | undefined, extensionsReducer?: undefined | ((mergedExtensions: Record<string, any>, request: ExecutionRequest) => Record<string, any>)) => Executor;
|
|
7
|
+
|
|
8
|
+
export { createBatchingExecutor, getBatchingExecutor };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { getOperationASTFromRequest, relocatedError, isAsyncIterable, memoize2of4 } from '@graphql-tools/utils';
|
|
2
|
+
import DataLoader from 'dataloader';
|
|
3
|
+
import { ValueOrPromise } from 'value-or-promise';
|
|
4
|
+
import { Kind, visit } from 'graphql';
|
|
5
|
+
|
|
6
|
+
function createPrefix(index) {
|
|
7
|
+
return `_${index}_`;
|
|
8
|
+
}
|
|
9
|
+
function matchKey(prefixedKey) {
|
|
10
|
+
const match = /^_(\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
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
function parseKey(prefixedKey) {
|
|
17
|
+
const match = matchKey(prefixedKey);
|
|
18
|
+
if (!match) {
|
|
19
|
+
throw new Error(`Key ${prefixedKey} is not correctly prefixed`);
|
|
20
|
+
}
|
|
21
|
+
return match;
|
|
22
|
+
}
|
|
23
|
+
function parseKeyFromPath(path) {
|
|
24
|
+
let keyOffset = 0;
|
|
25
|
+
let match = null;
|
|
26
|
+
for (; !match && keyOffset < path.length; keyOffset++) {
|
|
27
|
+
const pathKey = path[keyOffset];
|
|
28
|
+
if (typeof pathKey === "string") {
|
|
29
|
+
match = matchKey(pathKey);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (!match) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`Path ${path.join(".")} does not contain correctly prefixed key`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
...match,
|
|
39
|
+
keyOffset
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function mergeRequests(requests, extensionsReducer) {
|
|
44
|
+
const mergedVariables = /* @__PURE__ */ Object.create(null);
|
|
45
|
+
const mergedVariableDefinitions = [];
|
|
46
|
+
const mergedSelections = [];
|
|
47
|
+
const mergedFragmentDefinitions = [];
|
|
48
|
+
let mergedExtensions = /* @__PURE__ */ Object.create(null);
|
|
49
|
+
for (let index = 0; index < requests.length; index++) {
|
|
50
|
+
const request = requests[index];
|
|
51
|
+
if (request) {
|
|
52
|
+
const prefixedRequests = prefixRequest(createPrefix(index), request);
|
|
53
|
+
for (const def of prefixedRequests.document.definitions) {
|
|
54
|
+
if (isOperationDefinition(def)) {
|
|
55
|
+
mergedSelections.push(...def.selectionSet.selections);
|
|
56
|
+
if (def.variableDefinitions) {
|
|
57
|
+
mergedVariableDefinitions.push(...def.variableDefinitions);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (isFragmentDefinition(def)) {
|
|
61
|
+
mergedFragmentDefinitions.push(def);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
Object.assign(mergedVariables, prefixedRequests.variables);
|
|
65
|
+
mergedExtensions = extensionsReducer(mergedExtensions, request);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const firstRequest = requests[0];
|
|
69
|
+
if (!firstRequest) {
|
|
70
|
+
throw new Error("At least one request is required");
|
|
71
|
+
}
|
|
72
|
+
const operationType = firstRequest.operationType ?? getOperationASTFromRequest(firstRequest).operation;
|
|
73
|
+
const mergedOperationDefinition = {
|
|
74
|
+
kind: Kind.OPERATION_DEFINITION,
|
|
75
|
+
operation: operationType,
|
|
76
|
+
variableDefinitions: mergedVariableDefinitions,
|
|
77
|
+
selectionSet: {
|
|
78
|
+
kind: Kind.SELECTION_SET,
|
|
79
|
+
selections: mergedSelections
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const operationName = firstRequest.operationName ?? firstRequest.info?.operation?.name?.value;
|
|
83
|
+
if (operationName) {
|
|
84
|
+
mergedOperationDefinition.name = {
|
|
85
|
+
kind: Kind.NAME,
|
|
86
|
+
value: operationName
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
document: {
|
|
91
|
+
kind: Kind.DOCUMENT,
|
|
92
|
+
definitions: [mergedOperationDefinition, ...mergedFragmentDefinitions]
|
|
93
|
+
},
|
|
94
|
+
variables: mergedVariables,
|
|
95
|
+
extensions: mergedExtensions,
|
|
96
|
+
context: firstRequest.context,
|
|
97
|
+
info: firstRequest.info,
|
|
98
|
+
operationType,
|
|
99
|
+
rootValue: firstRequest.rootValue
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function prefixRequest(prefix, request) {
|
|
103
|
+
const executionVariables = request.variables ?? {};
|
|
104
|
+
function prefixNode(node) {
|
|
105
|
+
return prefixNodeName(node, prefix);
|
|
106
|
+
}
|
|
107
|
+
let prefixedDocument = aliasTopLevelFields(prefix, request.document);
|
|
108
|
+
const executionVariableNames = Object.keys(executionVariables);
|
|
109
|
+
const hasFragmentDefinitions = request.document.definitions.some(
|
|
110
|
+
(def) => isFragmentDefinition(def)
|
|
111
|
+
);
|
|
112
|
+
const fragmentSpreadImpl = {};
|
|
113
|
+
if (executionVariableNames.length > 0 || hasFragmentDefinitions) {
|
|
114
|
+
prefixedDocument = visit(prefixedDocument, {
|
|
115
|
+
[Kind.VARIABLE]: prefixNode,
|
|
116
|
+
[Kind.FRAGMENT_DEFINITION]: prefixNode,
|
|
117
|
+
[Kind.FRAGMENT_SPREAD]: (node) => {
|
|
118
|
+
node = prefixNodeName(node, prefix);
|
|
119
|
+
fragmentSpreadImpl[node.name.value] = true;
|
|
120
|
+
return node;
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
const prefixedVariables = {};
|
|
125
|
+
for (const variableName of executionVariableNames) {
|
|
126
|
+
prefixedVariables[prefix + variableName] = executionVariables[variableName];
|
|
127
|
+
}
|
|
128
|
+
if (hasFragmentDefinitions) {
|
|
129
|
+
prefixedDocument = {
|
|
130
|
+
...prefixedDocument,
|
|
131
|
+
definitions: prefixedDocument.definitions.filter((def) => {
|
|
132
|
+
return !isFragmentDefinition(def) || fragmentSpreadImpl[def.name.value];
|
|
133
|
+
})
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
document: prefixedDocument,
|
|
138
|
+
variables: prefixedVariables
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function aliasTopLevelFields(prefix, document) {
|
|
142
|
+
const transformer = {
|
|
143
|
+
[Kind.OPERATION_DEFINITION]: (def) => {
|
|
144
|
+
const { selections } = def.selectionSet;
|
|
145
|
+
return {
|
|
146
|
+
...def,
|
|
147
|
+
selectionSet: {
|
|
148
|
+
...def.selectionSet,
|
|
149
|
+
selections: aliasFieldsInSelection(prefix, selections, document)
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
return visit(document, transformer, {
|
|
155
|
+
[Kind.DOCUMENT]: [`definitions`]
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
function aliasFieldsInSelection(prefix, selections, document) {
|
|
159
|
+
return selections.map((selection) => {
|
|
160
|
+
switch (selection.kind) {
|
|
161
|
+
case Kind.INLINE_FRAGMENT:
|
|
162
|
+
return aliasFieldsInInlineFragment(prefix, selection, document);
|
|
163
|
+
case Kind.FRAGMENT_SPREAD: {
|
|
164
|
+
const inlineFragment = inlineFragmentSpread(selection, document);
|
|
165
|
+
return aliasFieldsInInlineFragment(prefix, inlineFragment, document);
|
|
166
|
+
}
|
|
167
|
+
case Kind.FIELD:
|
|
168
|
+
default:
|
|
169
|
+
return aliasField(selection, prefix);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
function aliasFieldsInInlineFragment(prefix, fragment, document) {
|
|
174
|
+
const { selections } = fragment.selectionSet;
|
|
175
|
+
return {
|
|
176
|
+
...fragment,
|
|
177
|
+
selectionSet: {
|
|
178
|
+
...fragment.selectionSet,
|
|
179
|
+
selections: aliasFieldsInSelection(prefix, selections, document)
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function inlineFragmentSpread(spread, document) {
|
|
184
|
+
const fragment = document.definitions.find(
|
|
185
|
+
(def) => isFragmentDefinition(def) && def.name.value === spread.name.value
|
|
186
|
+
);
|
|
187
|
+
if (!fragment) {
|
|
188
|
+
throw new Error(`Fragment ${spread.name.value} does not exist`);
|
|
189
|
+
}
|
|
190
|
+
const { typeCondition, selectionSet } = fragment;
|
|
191
|
+
return {
|
|
192
|
+
kind: Kind.INLINE_FRAGMENT,
|
|
193
|
+
typeCondition,
|
|
194
|
+
selectionSet,
|
|
195
|
+
directives: spread.directives
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function prefixNodeName(namedNode, prefix) {
|
|
199
|
+
return {
|
|
200
|
+
...namedNode,
|
|
201
|
+
name: {
|
|
202
|
+
...namedNode.name,
|
|
203
|
+
value: prefix + namedNode.name.value
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function aliasField(field, aliasPrefix) {
|
|
208
|
+
const aliasNode = field.alias ? field.alias : field.name;
|
|
209
|
+
return {
|
|
210
|
+
...field,
|
|
211
|
+
alias: {
|
|
212
|
+
...aliasNode,
|
|
213
|
+
value: aliasPrefix + aliasNode.value
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function isOperationDefinition(def) {
|
|
218
|
+
return def.kind === Kind.OPERATION_DEFINITION;
|
|
219
|
+
}
|
|
220
|
+
function isFragmentDefinition(def) {
|
|
221
|
+
return def.kind === Kind.FRAGMENT_DEFINITION;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function splitResult({ data, errors }, numResults) {
|
|
225
|
+
const splitResults = [];
|
|
226
|
+
for (let i = 0; i < numResults; i++) {
|
|
227
|
+
splitResults.push({});
|
|
228
|
+
}
|
|
229
|
+
if (data) {
|
|
230
|
+
for (const prefixedKey in data) {
|
|
231
|
+
const { index, originalKey } = parseKey(prefixedKey);
|
|
232
|
+
const result = splitResults[index];
|
|
233
|
+
if (result == null) {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (result.data == null) {
|
|
237
|
+
result.data = { [originalKey]: data[prefixedKey] };
|
|
238
|
+
} else {
|
|
239
|
+
result.data[originalKey] = data[prefixedKey];
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (errors) {
|
|
244
|
+
for (const error of errors) {
|
|
245
|
+
if (error.path) {
|
|
246
|
+
const { index, originalKey, keyOffset } = parseKeyFromPath(error.path);
|
|
247
|
+
const newError = relocatedError(error, [
|
|
248
|
+
originalKey,
|
|
249
|
+
...error.path.slice(keyOffset)
|
|
250
|
+
]);
|
|
251
|
+
const splittedResult = splitResults[index];
|
|
252
|
+
if (splittedResult) {
|
|
253
|
+
const resultErrors = splittedResult.errors ||= [];
|
|
254
|
+
resultErrors.push(newError);
|
|
255
|
+
}
|
|
256
|
+
} else {
|
|
257
|
+
splitResults.forEach((result) => {
|
|
258
|
+
const resultErrors = result.errors ||= [];
|
|
259
|
+
resultErrors.push(error);
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return splitResults;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function createBatchingExecutor(executor, dataLoaderOptions, extensionsReducer = defaultExtensionsReducer) {
|
|
268
|
+
const loadFn = createLoadFn(executor, extensionsReducer);
|
|
269
|
+
const queryLoader = new DataLoader(loadFn, dataLoaderOptions);
|
|
270
|
+
const mutationLoader = new DataLoader(loadFn, dataLoaderOptions);
|
|
271
|
+
return function batchingExecutor(request) {
|
|
272
|
+
const operationType = request.operationType ?? getOperationASTFromRequest(request)?.operation;
|
|
273
|
+
switch (operationType) {
|
|
274
|
+
case "query":
|
|
275
|
+
return queryLoader.load(request);
|
|
276
|
+
case "mutation":
|
|
277
|
+
return mutationLoader.load(request);
|
|
278
|
+
case "subscription":
|
|
279
|
+
return executor(request);
|
|
280
|
+
default:
|
|
281
|
+
throw new Error(`Invalid operation type "${operationType}"`);
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function createLoadFn(executor, extensionsReducer) {
|
|
286
|
+
return function batchExecuteLoadFn(requests) {
|
|
287
|
+
if (requests.length === 1 && requests[0]) {
|
|
288
|
+
const request = requests[0];
|
|
289
|
+
return new ValueOrPromise(() => executor(request)).then((result) => [result]).catch((err) => [err]);
|
|
290
|
+
}
|
|
291
|
+
const mergedRequests = mergeRequests(requests, extensionsReducer);
|
|
292
|
+
return new ValueOrPromise(() => executor(mergedRequests)).then(
|
|
293
|
+
(resultBatches) => {
|
|
294
|
+
if (isAsyncIterable(resultBatches)) {
|
|
295
|
+
throw new Error(
|
|
296
|
+
"Executor must not return incremental results for batching"
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
return splitResult(resultBatches, requests.length);
|
|
300
|
+
}
|
|
301
|
+
);
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
function defaultExtensionsReducer(mergedExtensions, request) {
|
|
305
|
+
const newExtensions = request.extensions;
|
|
306
|
+
if (newExtensions != null) {
|
|
307
|
+
Object.assign(mergedExtensions, newExtensions);
|
|
308
|
+
}
|
|
309
|
+
return mergedExtensions;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const getBatchingExecutor = memoize2of4(function getBatchingExecutor2(_context, executor, dataLoaderOptions, extensionsReducer) {
|
|
313
|
+
return createBatchingExecutor(executor, dataLoaderOptions, extensionsReducer);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
export { createBatchingExecutor, getBatchingExecutor };
|