400 lines
14 KiB
JavaScript
400 lines
14 KiB
JavaScript
'use strict';
|
|
|
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
|
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
|
|
|
|
const graphql = require('graphql');
|
|
const isPromise = _interopDefault(require('is-promise'));
|
|
const DataLoader = _interopDefault(require('dataloader'));
|
|
const utils = require('@graphql-tools/utils');
|
|
|
|
// adapted from https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-graphql/src/batching/merge-queries.js
|
|
function createPrefix(index) {
|
|
return `graphqlTools${index}_`;
|
|
}
|
|
function parseKey(prefixedKey) {
|
|
const match = /^graphqlTools([\d]+)_(.*)$/.exec(prefixedKey);
|
|
if (match && match.length === 3 && !isNaN(Number(match[1])) && match[2]) {
|
|
return { index: Number(match[1]), originalKey: match[2] };
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// adapted from https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-graphql/src/batching/merge-queries.js
|
|
/**
|
|
* Merge multiple queries into a single query in such a way that query results
|
|
* can be split and transformed as if they were obtained by running original queries.
|
|
*
|
|
* Merging algorithm involves several transformations:
|
|
* 1. Replace top-level fragment spreads with inline fragments (... on Query {})
|
|
* 2. Add unique aliases to all top-level query fields (including those on inline fragments)
|
|
* 3. Prefix all variable definitions and variable usages
|
|
* 4. Prefix names (and spreads) of fragments
|
|
*
|
|
* i.e transform:
|
|
* [
|
|
* `query Foo($id: ID!) { foo, bar(id: $id), ...FooQuery }
|
|
* fragment FooQuery on Query { baz }`,
|
|
*
|
|
* `query Bar($id: ID!) { foo: baz, bar(id: $id), ... on Query { baz } }`
|
|
* ]
|
|
* to:
|
|
* query (
|
|
* $graphqlTools1_id: ID!
|
|
* $graphqlTools2_id: ID!
|
|
* ) {
|
|
* graphqlTools1_foo: foo,
|
|
* graphqlTools1_bar: bar(id: $graphqlTools1_id)
|
|
* ... on Query {
|
|
* graphqlTools1__baz: baz
|
|
* }
|
|
* graphqlTools1__foo: baz
|
|
* graphqlTools1__bar: bar(id: $graphqlTools1__id)
|
|
* ... on Query {
|
|
* graphqlTools1__baz: baz
|
|
* }
|
|
* }
|
|
*/
|
|
function mergeExecutionParams(execs, extensionsReducer) {
|
|
const mergedVariables = Object.create(null);
|
|
const mergedVariableDefinitions = [];
|
|
const mergedSelections = [];
|
|
const mergedFragmentDefinitions = [];
|
|
let mergedExtensions = Object.create(null);
|
|
let operation;
|
|
execs.forEach((executionParams, index) => {
|
|
const prefixedExecutionParams = prefixExecutionParams(createPrefix(index), executionParams);
|
|
prefixedExecutionParams.document.definitions.forEach(def => {
|
|
var _a;
|
|
if (isOperationDefinition(def)) {
|
|
operation = def.operation;
|
|
mergedSelections.push(...def.selectionSet.selections);
|
|
mergedVariableDefinitions.push(...((_a = def.variableDefinitions) !== null && _a !== void 0 ? _a : []));
|
|
}
|
|
if (isFragmentDefinition(def)) {
|
|
mergedFragmentDefinitions.push(def);
|
|
}
|
|
});
|
|
Object.assign(mergedVariables, prefixedExecutionParams.variables);
|
|
mergedExtensions = extensionsReducer(mergedExtensions, executionParams);
|
|
});
|
|
const mergedOperationDefinition = {
|
|
kind: graphql.Kind.OPERATION_DEFINITION,
|
|
operation,
|
|
variableDefinitions: mergedVariableDefinitions,
|
|
selectionSet: {
|
|
kind: graphql.Kind.SELECTION_SET,
|
|
selections: mergedSelections,
|
|
},
|
|
};
|
|
return {
|
|
document: {
|
|
kind: graphql.Kind.DOCUMENT,
|
|
definitions: [mergedOperationDefinition, ...mergedFragmentDefinitions],
|
|
},
|
|
variables: mergedVariables,
|
|
extensions: mergedExtensions,
|
|
context: execs[0].context,
|
|
info: execs[0].info,
|
|
};
|
|
}
|
|
function prefixExecutionParams(prefix, executionParams) {
|
|
let document = aliasTopLevelFields(prefix, executionParams.document);
|
|
const variableNames = Object.keys(executionParams.variables);
|
|
if (variableNames.length === 0) {
|
|
return { ...executionParams, document };
|
|
}
|
|
document = graphql.visit(document, {
|
|
[graphql.Kind.VARIABLE]: (node) => prefixNodeName(node, prefix),
|
|
[graphql.Kind.FRAGMENT_DEFINITION]: (node) => prefixNodeName(node, prefix),
|
|
[graphql.Kind.FRAGMENT_SPREAD]: (node) => prefixNodeName(node, prefix),
|
|
});
|
|
const prefixedVariables = variableNames.reduce((acc, name) => {
|
|
acc[prefix + name] = executionParams.variables[name];
|
|
return acc;
|
|
}, Object.create(null));
|
|
return {
|
|
document,
|
|
variables: prefixedVariables,
|
|
};
|
|
}
|
|
/**
|
|
* Adds prefixed aliases to top-level fields of the query.
|
|
*
|
|
* @see aliasFieldsInSelection for implementation details
|
|
*/
|
|
function aliasTopLevelFields(prefix, document) {
|
|
const transformer = {
|
|
[graphql.Kind.OPERATION_DEFINITION]: (def) => {
|
|
const { selections } = def.selectionSet;
|
|
return {
|
|
...def,
|
|
selectionSet: {
|
|
...def.selectionSet,
|
|
selections: aliasFieldsInSelection(prefix, selections, document),
|
|
},
|
|
};
|
|
},
|
|
};
|
|
return graphql.visit(document, transformer, { [graphql.Kind.DOCUMENT]: [`definitions`] });
|
|
}
|
|
/**
|
|
* Add aliases to fields of the selection, including top-level fields of inline fragments.
|
|
* Fragment spreads are converted to inline fragments and their top-level fields are also aliased.
|
|
*
|
|
* Note that this method is shallow. It adds aliases only to the top-level fields and doesn't
|
|
* descend to field sub-selections.
|
|
*
|
|
* For example, transforms:
|
|
* {
|
|
* foo
|
|
* ... on Query { foo }
|
|
* ...FragmentWithBarField
|
|
* }
|
|
* To:
|
|
* {
|
|
* graphqlTools1_foo: foo
|
|
* ... on Query { graphqlTools1_foo: foo }
|
|
* ... on Query { graphqlTools1_bar: bar }
|
|
* }
|
|
*/
|
|
function aliasFieldsInSelection(prefix, selections, document) {
|
|
return selections.map(selection => {
|
|
switch (selection.kind) {
|
|
case graphql.Kind.INLINE_FRAGMENT:
|
|
return aliasFieldsInInlineFragment(prefix, selection, document);
|
|
case graphql.Kind.FRAGMENT_SPREAD: {
|
|
const inlineFragment = inlineFragmentSpread(selection, document);
|
|
return aliasFieldsInInlineFragment(prefix, inlineFragment, document);
|
|
}
|
|
case graphql.Kind.FIELD:
|
|
default:
|
|
return aliasField(selection, prefix);
|
|
}
|
|
});
|
|
}
|
|
/**
|
|
* Add aliases to top-level fields of the inline fragment.
|
|
* Returns new inline fragment node.
|
|
*
|
|
* For Example, transforms:
|
|
* ... on Query { foo, ... on Query { bar: foo } }
|
|
* To
|
|
* ... on Query { graphqlTools1_foo: foo, ... on Query { graphqlTools1_bar: foo } }
|
|
*/
|
|
function aliasFieldsInInlineFragment(prefix, fragment, document) {
|
|
const { selections } = fragment.selectionSet;
|
|
return {
|
|
...fragment,
|
|
selectionSet: {
|
|
...fragment.selectionSet,
|
|
selections: aliasFieldsInSelection(prefix, selections, document),
|
|
},
|
|
};
|
|
}
|
|
/**
|
|
* Replaces fragment spread with inline fragment
|
|
*
|
|
* Example:
|
|
* query { ...Spread }
|
|
* fragment Spread on Query { bar }
|
|
*
|
|
* Transforms to:
|
|
* query { ... on Query { bar } }
|
|
*/
|
|
function inlineFragmentSpread(spread, document) {
|
|
const fragment = document.definitions.find(def => isFragmentDefinition(def) && def.name.value === spread.name.value);
|
|
if (!fragment) {
|
|
throw new Error(`Fragment ${spread.name.value} does not exist`);
|
|
}
|
|
const { typeCondition, selectionSet } = fragment;
|
|
return {
|
|
kind: graphql.Kind.INLINE_FRAGMENT,
|
|
typeCondition,
|
|
selectionSet,
|
|
directives: spread.directives,
|
|
};
|
|
}
|
|
function prefixNodeName(namedNode, prefix) {
|
|
return {
|
|
...namedNode,
|
|
name: {
|
|
...namedNode.name,
|
|
value: prefix + namedNode.name.value,
|
|
},
|
|
};
|
|
}
|
|
/**
|
|
* Returns a new FieldNode with prefixed alias
|
|
*
|
|
* Example. Given prefix === "graphqlTools1_" transforms:
|
|
* { foo } -> { graphqlTools1_foo: foo }
|
|
* { foo: bar } -> { graphqlTools1_foo: bar }
|
|
*/
|
|
function aliasField(field, aliasPrefix) {
|
|
const aliasNode = field.alias ? field.alias : field.name;
|
|
return {
|
|
...field,
|
|
alias: {
|
|
...aliasNode,
|
|
value: aliasPrefix + aliasNode.value,
|
|
},
|
|
};
|
|
}
|
|
function isOperationDefinition(def) {
|
|
return def.kind === graphql.Kind.OPERATION_DEFINITION;
|
|
}
|
|
function isFragmentDefinition(def) {
|
|
return def.kind === graphql.Kind.FRAGMENT_DEFINITION;
|
|
}
|
|
|
|
// adapted from https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-graphql/src/batching/merge-queries.js
|
|
/**
|
|
* Split and transform result of the query produced by the `merge` function
|
|
*/
|
|
function splitResult(mergedResult, numResults) {
|
|
const splitResults = [];
|
|
for (let i = 0; i < numResults; i++) {
|
|
splitResults.push({});
|
|
}
|
|
const data = mergedResult.data;
|
|
if (data) {
|
|
Object.keys(data).forEach(prefixedKey => {
|
|
const { index, originalKey } = parseKey(prefixedKey);
|
|
if (!splitResults[index].data) {
|
|
splitResults[index].data = { [originalKey]: data[prefixedKey] };
|
|
}
|
|
else {
|
|
splitResults[index].data[originalKey] = data[prefixedKey];
|
|
}
|
|
});
|
|
}
|
|
const errors = mergedResult.errors;
|
|
if (errors) {
|
|
const newErrors = Object.create(null);
|
|
errors.forEach(error => {
|
|
if (error.path) {
|
|
const parsedKey = parseKey(error.path[0]);
|
|
if (parsedKey) {
|
|
const { index, originalKey } = parsedKey;
|
|
const newError = utils.relocatedError(error, [originalKey, ...error.path.slice(1)]);
|
|
if (!newErrors[index]) {
|
|
newErrors[index] = [newError];
|
|
}
|
|
else {
|
|
newErrors[index].push(newError);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
splitResults.forEach((_splitResult, index) => {
|
|
if (!newErrors[index]) {
|
|
newErrors[index] = [error];
|
|
}
|
|
else {
|
|
newErrors[index].push(error);
|
|
}
|
|
});
|
|
});
|
|
Object.keys(newErrors).forEach(index => {
|
|
splitResults[index].errors = newErrors[index];
|
|
});
|
|
}
|
|
return splitResults;
|
|
}
|
|
|
|
function createBatchingExecutor(executor, dataLoaderOptions, extensionsReducer) {
|
|
const loader = new DataLoader(createLoadFn(executor, extensionsReducer !== null && extensionsReducer !== void 0 ? extensionsReducer : defaultExtensionsReducer), dataLoaderOptions);
|
|
return (executionParams) => loader.load(executionParams);
|
|
}
|
|
function createLoadFn(executor, extensionsReducer) {
|
|
return async (execs) => {
|
|
const execBatches = [];
|
|
let index = 0;
|
|
const exec = execs[index];
|
|
let currentBatch = [exec];
|
|
execBatches.push(currentBatch);
|
|
const operationType = graphql.getOperationAST(exec.document, undefined).operation;
|
|
while (++index < execs.length) {
|
|
const currentOperationType = graphql.getOperationAST(execs[index].document, undefined).operation;
|
|
if (operationType === currentOperationType) {
|
|
currentBatch.push(execs[index]);
|
|
}
|
|
else {
|
|
currentBatch = [execs[index]];
|
|
execBatches.push(currentBatch);
|
|
}
|
|
}
|
|
let containsPromises = false;
|
|
const executionResults = [];
|
|
execBatches.forEach(execBatch => {
|
|
const mergedExecutionParams = mergeExecutionParams(execBatch, extensionsReducer);
|
|
const executionResult = executor(mergedExecutionParams);
|
|
if (isPromise(executionResult)) {
|
|
containsPromises = true;
|
|
}
|
|
executionResults.push(executionResult);
|
|
});
|
|
if (containsPromises) {
|
|
return Promise.all(executionResults).then(resultBatches => {
|
|
let results = [];
|
|
resultBatches.forEach((resultBatch, index) => {
|
|
results = results.concat(splitResult(resultBatch, execBatches[index].length));
|
|
});
|
|
return results;
|
|
});
|
|
}
|
|
let results = [];
|
|
executionResults.forEach((resultBatch, index) => {
|
|
results = results.concat(splitResult(resultBatch, execBatches[index].length));
|
|
});
|
|
return results;
|
|
};
|
|
}
|
|
function defaultExtensionsReducer(mergedExtensions, executionParams) {
|
|
const newExtensions = executionParams.extensions;
|
|
if (newExtensions != null) {
|
|
Object.assign(mergedExtensions, newExtensions);
|
|
}
|
|
return mergedExtensions;
|
|
}
|
|
|
|
function memoize2of4(fn) {
|
|
let cache1;
|
|
function memoized(a1, a2, a3, a4) {
|
|
if (!cache1) {
|
|
cache1 = new WeakMap();
|
|
const cache2 = new WeakMap();
|
|
cache1.set(a1, cache2);
|
|
const newValue = fn(a1, a2, a3, a4);
|
|
cache2.set(a2, newValue);
|
|
return newValue;
|
|
}
|
|
let cache2 = cache1.get(a1);
|
|
if (!cache2) {
|
|
cache2 = new WeakMap();
|
|
cache1.set(a1, cache2);
|
|
const newValue = fn(a1, a2, a3, a4);
|
|
cache2.set(a2, newValue);
|
|
return newValue;
|
|
}
|
|
const cachedValue = cache2.get(a2);
|
|
if (cachedValue === undefined) {
|
|
const newValue = fn(a1, a2, a3, a4);
|
|
cache2.set(a2, newValue);
|
|
return newValue;
|
|
}
|
|
return cachedValue;
|
|
}
|
|
return memoized;
|
|
}
|
|
|
|
const getBatchingExecutor = memoize2of4(function (_context, executor, dataLoaderOptions, extensionsReducer) {
|
|
return createBatchingExecutor(executor, dataLoaderOptions, extensionsReducer);
|
|
});
|
|
|
|
exports.createBatchingExecutor = createBatchingExecutor;
|
|
exports.getBatchingExecutor = getBatchingExecutor;
|
|
//# sourceMappingURL=index.cjs.js.map
|