UNPKG

@graphql-mesh/fusion-execution

Version:
207 lines (206 loc) 9.45 kB
"use strict"; 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;