UNPKG

@graphql-mesh/fusion-runtime

Version:

Runtime for GraphQL Mesh Fusion Supergraph

340 lines (339 loc) • 15.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.useFusiongraph = exports.getExecutorForFusiongraph = exports.getTransportExecutor = exports.createTransportGetter = exports.defaultTransportsOption = exports.getMemoizedExecutionPlanForOperation = exports.getSubgraphTransportMapFromFusiongraph = void 0; const graphql_1 = require("graphql"); const fusion_execution_1 = require("@graphql-mesh/fusion-execution"); // eslint-disable-next-line import/no-extraneous-dependencies const runtime_1 = require("@graphql-mesh/runtime"); // eslint-disable-next-line import/no-extraneous-dependencies const utils_1 = require("@graphql-mesh/utils"); const stitch_1 = require("@graphql-tools/stitch"); const utils_2 = require("@graphql-tools/utils"); function getTransportDirectives(fusiongraph) { const transportDirectives = (0, utils_2.getDirective)(fusiongraph, fusiongraph, 'transport'); if (transportDirectives?.length) { return transportDirectives; } const astNode = fusiongraph.astNode; if (astNode?.directives?.length) { return astNode.directives .filter(directive => directive.name.value === 'transport') .map(transportDirective => Object.fromEntries(transportDirective.arguments?.map(argument => [ argument.name.value, (0, graphql_1.valueFromASTUntyped)(argument.value), ]))); } return []; } function getSubgraphTransportMapFromFusiongraph(fusiongraph) { const subgraphTransportEntryMap = {}; const transportDirectives = getTransportDirectives(fusiongraph); for (const { kind, subgraph, location, headers, ...options } of transportDirectives) { subgraphTransportEntryMap[subgraph] = { kind, location, headers, options, subgraph, }; } return subgraphTransportEntryMap; } exports.getSubgraphTransportMapFromFusiongraph = getSubgraphTransportMapFromFusiongraph; exports.getMemoizedExecutionPlanForOperation = (0, utils_2.memoize2of4)(function getMemoizedExecutionPlanForOperation(fusiongraph, document, operationName, _random) { return (0, fusion_execution_1.createExecutablePlanForOperation)({ fusiongraph, document, operationName, }); }); function defaultTransportsOption(transportKind) { return Promise.resolve(`${`@graphql-mesh/transport-${transportKind}`}`).then(s => __importStar(require(s))).catch(err => { console.error(err); throw new Error(`No transport found for ${transportKind}. Please install @graphql-mesh/transport-${transportKind}`); }); } exports.defaultTransportsOption = defaultTransportsOption; function createTransportGetter(transports) { if (typeof transports === 'function') { return transports; } return function getTransport(transportKind) { const transport = transports[transportKind]; if (!transport) { throw new Error(`No transport found for ${transportKind}`); } return transport; }; } exports.createTransportGetter = createTransportGetter; function getTransportExecutor(transportGetter, transportContext) { transportContext.logger?.info(`Loading transport ${transportContext.transportEntry?.kind}`); const transport$ = transportGetter(transportContext.transportEntry?.kind); if ((0, utils_2.isPromise)(transport$)) { return transport$.then(transport => transport.getSubgraphExecutor(transportContext)); } return transport$.getSubgraphExecutor(transportContext); } exports.getTransportExecutor = getTransportExecutor; function getExecutorForFusiongraph({ fusiongraph, transports = defaultTransportsOption, plugins, ...transportBaseContext }) { const onSubgraphExecuteHooks = []; if (plugins) { for (const plugin of plugins) { if (plugin.onSubgraphExecute) { onSubgraphExecuteHooks.push(plugin.onSubgraphExecute); } } } const transportEntryMap = getSubgraphTransportMapFromFusiongraph(fusiongraph); const subgraphExecutorMap = {}; const transportGetter = createTransportGetter(transports); function onSubgraphExecute(subgraphName, document, variables, context) { let executor = subgraphExecutorMap[subgraphName]; if (executor == null) { transportBaseContext?.logger?.info(`Initializing executor for subgraph ${subgraphName}`); const transportEntry = transportEntryMap[subgraphName]; // eslint-disable-next-line no-inner-declarations function wrapExecutorWithHooks(currentExecutor) { if (onSubgraphExecuteHooks.length) { return function executorWithHooks(subgraphExecReq) { const onSubgraphExecuteDoneHooks = []; const onSubgraphExecuteHooksRes$ = (0, utils_1.iterateAsync)(onSubgraphExecuteHooks, onSubgraphExecuteHook => onSubgraphExecuteHook({ fusiongraph, subgraphName, transportKind: transportEntry?.kind, transportLocation: transportEntry?.location, transportHeaders: transportEntry?.headers, transportOptions: transportEntry?.options, executionRequest: subgraphExecReq, executor: currentExecutor, setExecutor(newExecutor) { currentExecutor = newExecutor; }, }), onSubgraphExecuteDoneHooks); function handleOnSubgraphExecuteHooksResult() { if (onSubgraphExecuteDoneHooks.length) { // eslint-disable-next-line no-inner-declarations function handleExecutorResWithHooks(currentResult) { const onSubgraphExecuteDoneHooksRes$ = (0, utils_1.iterateAsync)(onSubgraphExecuteDoneHooks, onSubgraphExecuteDoneHook => onSubgraphExecuteDoneHook({ result: currentResult, setResult(newResult) { currentResult = newResult; }, })); if ((0, utils_2.isPromise)(onSubgraphExecuteDoneHooksRes$)) { return onSubgraphExecuteDoneHooksRes$.then(() => currentResult); } return currentResult; } const executorRes$ = currentExecutor(subgraphExecReq); if ((0, utils_2.isPromise)(executorRes$)) { return executorRes$.then(handleExecutorResWithHooks); } if ((0, utils_2.isAsyncIterable)(executorRes$)) { return (0, utils_2.mapAsyncIterator)(executorRes$, handleExecutorResWithHooks); } return handleExecutorResWithHooks(executorRes$); } return currentExecutor(subgraphExecReq); } if ((0, utils_2.isPromise)(onSubgraphExecuteHooksRes$)) { return onSubgraphExecuteHooksRes$.then(handleOnSubgraphExecuteHooksResult); } return handleOnSubgraphExecuteHooksResult(); }; } return currentExecutor; } executor = function lazyExecutor(subgraphExecReq) { function getSubgraph() { return (0, fusion_execution_1.extractSubgraphFromFusiongraph)(subgraphName, fusiongraph); } const executor$ = getTransportExecutor(transportGetter, transportBaseContext ? { ...transportBaseContext, subgraphName, getSubgraph, transportEntry, } : { getSubgraph, transportEntry, subgraphName }); if ((0, utils_2.isPromise)(executor$)) { return executor$.then(executor_ => { executor = wrapExecutorWithHooks(executor_); subgraphExecutorMap[subgraphName] = executor; return executor(subgraphExecReq); }); } executor = wrapExecutorWithHooks(executor$); subgraphExecutorMap[subgraphName] = executor; return executor(subgraphExecReq); }; } return executor({ document, variables, context }); } function fusiongraphExecutor(execReq) { if (execReq.operationName === 'IntrospectionQuery') { return { data: (0, graphql_1.introspectionFromSchema)(fusiongraph), }; } const executablePlan = (0, exports.getMemoizedExecutionPlanForOperation)(fusiongraph, execReq.document, execReq.operationName); return (0, fusion_execution_1.executeOperationPlan)({ executablePlan, onExecute: onSubgraphExecute, variables: execReq.variables, context: execReq.context, }); } return { fusiongraphExecutor, transportEntryMap, onSubgraphExecute, }; } exports.getExecutorForFusiongraph = getExecutorForFusiongraph; function ensureSchema(source) { if ((0, graphql_1.isSchema)(source)) { return source; } if (typeof source === 'string') { return (0, graphql_1.buildSchema)(source, { noLocation: true, assumeValidSDL: true, assumeValid: true }); } return (0, graphql_1.buildASTSchema)(source, { assumeValidSDL: true, assumeValid: true }); } function getExecuteFnFromExecutor(executor) { return function executeFnFromExecutor({ document, variableValues, contextValue, rootValue, operationName, }) { return executor({ document, variables: variableValues, context: contextValue, operationName, rootValue, }); }; } function useFusiongraph({ getFusiongraph, transports, additionalResolvers, polling, transportBaseContext, }) { let fusiongraph; let lastLoadedFusiongraph; let executeFn; let executor; let yoga; // TODO: We need to figure this out in a better way let inContextSDK; function handleLoadedFusiongraph(loadedFusiongraph) { // If the fusiongraph is the same, we don't need to do anything if (lastLoadedFusiongraph != null && lastLoadedFusiongraph === loadedFusiongraph) { return; } lastLoadedFusiongraph = loadedFusiongraph; fusiongraph = ensureSchema(loadedFusiongraph); const { fusiongraphExecutor, onSubgraphExecute, transportEntryMap } = getExecutorForFusiongraph({ fusiongraph, transports, plugins: yoga.getEnveloped._plugins, ...transportBaseContext, }); executor = fusiongraphExecutor; if (additionalResolvers != null) { fusiongraph = (0, stitch_1.stitchSchemas)({ subschemas: [ { schema: fusiongraph, executor, }, ], resolvers: additionalResolvers, }); const subgraphsForInContextSdk = []; for (const subgraphName in transportEntryMap) { subgraphsForInContextSdk.push({ name: subgraphName, schema: (0, fusion_execution_1.extractSubgraphFromFusiongraph)(subgraphName, fusiongraph), executor(execReq) { return onSubgraphExecute(subgraphName, execReq.document, execReq.variables, execReq.context); }, }); } inContextSDK = (0, runtime_1.getInContextSDK)(fusiongraph, subgraphsForInContextSdk, transportBaseContext.logger, []); } else { executeFn = getExecuteFnFromExecutor(executor); } } function getAndSetFusiongraph() { const fusiongraph$ = getFusiongraph(transportBaseContext); if ((0, utils_2.isPromise)(fusiongraph$)) { return fusiongraph$.then(handleLoadedFusiongraph); } else { return handleLoadedFusiongraph(fusiongraph$); } } if (polling) { setInterval(getAndSetFusiongraph, polling); } let initialFusiongraph$; let initiated = false; return { onYogaInit(payload) { yoga = payload.yoga; }, onRequestParse() { return { onRequestParseDone() { if (!initiated) { initialFusiongraph$ = getAndSetFusiongraph(); } initiated = true; return initialFusiongraph$; }, }; }, onEnveloped({ setSchema }) { setSchema(fusiongraph); }, onContextBuilding({ extendContext }) { if (inContextSDK) { extendContext(inContextSDK); } extendContext(transportBaseContext); }, onExecute({ setExecuteFn }) { if (executeFn) { setExecuteFn(executeFn); } }, onSubscribe({ setSubscribeFn }) { if (executeFn) { setSubscribeFn(executeFn); } }, invalidateUnifiedGraph() { return getAndSetFusiongraph(); }, }; } exports.useFusiongraph = useFusiongraph;