@graphql-mesh/fusion-execution
Version:
Runtime for Fusion Supergraph
207 lines (206 loc) • 9.45 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.serializeExecutableOperationPlan = exports.executeOperationPlan = exports.executeOperationPlanWithPatches = exports.createExecutablePlanForOperation = exports.executeOperationWithPatches = exports.executeOperation = exports.planOperation = void 0;
const tslib_1 = require("tslib");
const fast_json_patch_1 = tslib_1.__importDefault(require("fast-json-patch"));
const graphql_1 = require("graphql");
const utils_1 = require("@graphql-tools/utils");
const repeater_1 = require("@repeaterjs/repeater");
const execution_js_1 = require("./execution.js");
const flattenSelections_js_1 = require("./flattenSelections.js");
const query_planning_js_1 = require("./query-planning.js");
const serialization_js_1 = require("./serialization.js");
function planOperation(fusiongraph, document, operationName) {
let operationAst;
const fragments = Object.create(null);
for (const definition of document.definitions) {
if (definition.kind === 'OperationDefinition') {
if (!operationName && operationAst) {
throw new Error('Must provide operation name if query contains multiple operations');
}
if (!operationName || definition.name?.value === operationName) {
operationAst = definition;
}
}
else if (definition.kind === 'FragmentDefinition') {
fragments[definition.name.value] = definition;
}
}
if (!operationAst) {
throw new Error(`No operation found with name ${operationName}`);
}
const defaultVariables = Object.create(null);
operationAst.variableDefinitions?.forEach(variableDefinition => {
if (variableDefinition.defaultValue) {
defaultVariables[variableDefinition.variable.name.value] = (0, graphql_1.valueFromASTUntyped)(variableDefinition.defaultValue, defaultVariables);
}
});
const flattenedFakeFieldNode = {
kind: graphql_1.Kind.FIELD,
name: {
kind: graphql_1.Kind.NAME,
value: '__fake',
},
arguments: operationAst.variableDefinitions?.map(variableDefinition => ({
kind: graphql_1.Kind.ARGUMENT,
name: variableDefinition.variable.name,
value: {
kind: graphql_1.Kind.VARIABLE,
name: variableDefinition.variable.name,
},
})),
selectionSet: {
kind: graphql_1.Kind.SELECTION_SET,
selections: (0, flattenSelections_js_1.flattenSelections)(operationAst.selectionSet.selections, fragments),
},
};
const rootTypeMap = (0, utils_1.getRootTypeMap)(fusiongraph);
const operationType = operationAst.operation;
const rootType = rootTypeMap.get(operationType);
if (!rootType) {
throw new Error(`No root type found for operation type ${operationType}`);
}
const rootVariableMap = new Map();
for (const variableDefinition of operationAst.variableDefinitions || []) {
rootVariableMap.set(variableDefinition.variable.name.value, variableDefinition);
}
const planForFakeFieldNode = (0, query_planning_js_1.visitFieldNodeForTypeResolvers)('ROOT', flattenedFakeFieldNode, rootType, fusiongraph, {
currentVariableIndex: 0,
rootVariableMap,
});
return {
resolverOperationNodes: planForFakeFieldNode.resolverOperationNodes,
resolverDependencyFieldMap: planForFakeFieldNode.resolverDependencyFieldMap,
defaultVariables,
};
}
exports.planOperation = planOperation;
function executeOperation({ fusiongraph, onExecute, document, operationName, variables = {}, context = {}, }) {
const executablePlan = createExecutablePlanForOperation({ fusiongraph, document, operationName });
return executeOperationPlan({ executablePlan, onExecute, variables, context });
}
exports.executeOperation = executeOperation;
function executeOperationWithPatches({ fusiongraph, onExecute, document, operationName, variables = {}, context = {}, }) {
const executablePlan = createExecutablePlanForOperation({ fusiongraph, document, operationName });
return executeOperationPlanWithPatches({ executablePlan, onExecute, variables, context });
}
exports.executeOperationWithPatches = executeOperationWithPatches;
function createExecutablePlanForOperation({ fusiongraph, document, operationName, }) {
const plan = planOperation(fusiongraph, document, operationName);
const executablePlan = (0, execution_js_1.createExecutableResolverOperationNodesWithDependencyMap)(plan.resolverOperationNodes, plan.resolverDependencyFieldMap, 0);
return {
resolverOperationNodes: executablePlan.resolverOperationNodes,
resolverDependencyFieldMap: executablePlan.resolverDependencyFieldMap,
defaultVariables: plan.defaultVariables,
};
}
exports.createExecutablePlanForOperation = createExecutablePlanForOperation;
function removeInternalFieldsFromResponse(response) {
if (Array.isArray(response)) {
return response.map(removeInternalFieldsFromResponse);
}
else if (typeof response === 'object' &&
response != null &&
(!response.constructor || response.constructor.name === 'Object')) {
return Object.fromEntries(Object.entries(response)
.filter(([key]) => !key.startsWith('__variable'))
.map(([key, value]) => [key, removeInternalFieldsFromResponse(value)]));
}
else {
return response;
}
}
function prepareExecutionResult(planExecutionResult, errors, executionPlan) {
return {
data: planExecutionResult?.exported != null
? removeInternalFieldsFromResponse(planExecutionResult.exported)
: undefined,
errors: errors.length > 0 ? errors : undefined,
extensions: globalThis.process?.env?.DEBUG
? {
executionPlan: serializeExecutableOperationPlan(executionPlan),
outputVariables: Object.fromEntries(planExecutionResult?.outputVariableMap || []),
}
: undefined,
};
}
function executeOperationPlanWithPatches(params) {
return new repeater_1.Repeater(async function (push, stop) {
try {
const resMaybeAsyncIter = await executeOperationPlan(params);
if ((0, utils_1.isAsyncIterable)(resMaybeAsyncIter)) {
let lastRes;
for await (const res of resMaybeAsyncIter) {
if (!lastRes) {
await push({
...res,
hasNext: true,
});
}
else {
const patches = fast_json_patch_1.default.compare(lastRes, res);
for (const patch of patches) {
if (patch.op === 'add' || patch.op === 'replace') {
const [, ...pathForGraphQL] = patch.path.split('/');
await push({
...res,
path: pathForGraphQL,
data: patch.value,
hasNext: true,
});
}
}
}
lastRes = res;
}
await push({
hasNext: false,
});
}
else {
await push({
...resMaybeAsyncIter,
hasNext: false,
});
}
await stop();
}
catch (e) {
await stop(e);
}
});
}
exports.executeOperationPlanWithPatches = executeOperationPlanWithPatches;
function executeOperationPlan({ executablePlan, onExecute, variables = {}, context, }) {
const errors = [];
const res$ = (0, execution_js_1.executeResolverOperationNodesWithDependenciesInParallel)({
context,
resolverOperationNodes: executablePlan.resolverOperationNodes,
fieldDependencyMap: executablePlan.resolverDependencyFieldMap,
inputVariableMap: new Map([
...Object.entries(executablePlan.defaultVariables),
...Object.entries(variables),
]),
onExecute,
path: [],
errors,
});
if ((0, utils_1.isPromise)(res$)) {
return res$.then(res => prepareExecutionResult(res, errors, executablePlan));
}
if ((0, utils_1.isAsyncIterable)(res$)) {
return (0, utils_1.mapAsyncIterator)(res$, res => prepareExecutionResult(res, errors, executablePlan));
}
return prepareExecutionResult(res$, errors, executablePlan);
}
exports.executeOperationPlan = executeOperationPlan;
function serializeExecutableOperationPlan(executablePlan) {
return {
resolverOperationNodes: executablePlan.resolverOperationNodes.map(node => (0, serialization_js_1.serializeExecutableResolverOperationNode)(node)),
resolverDependencyFieldMap: Object.fromEntries([...executablePlan.resolverDependencyFieldMap.entries()].map(([key, value]) => [
key,
value.map(serialization_js_1.serializeExecutableResolverOperationNode),
])),
};
}
exports.serializeExecutableOperationPlan = serializeExecutableOperationPlan;
;