UNPKG

@graphql-mesh/fusion-runtime

Version:

Runtime for GraphQL Mesh Fusion Supergraph

236 lines (235 loc) • 10.3 kB
import { constantCase } from 'change-case'; import { print } from 'graphql'; import { isDisposable, iterateAsync, loggerForExecutionRequest, mapMaybePromise, requestIdByRequest, } from '@graphql-mesh/utils'; import { isAsyncIterable, isDocumentNode, mapAsyncIterator, printSchemaWithDirectives, } from '@graphql-tools/utils'; async function defaultTransportsGetter(kind) { try { let transport = await import(`@graphql-mesh/transport-${kind}`); if (!transport) { throw new Error(`@graphql-mesh/transport-${kind} module does not export anything`); } if (typeof transport !== 'object') { throw new Error(`@graphql-mesh/transport-${kind} module does not export an object`); } if (transport.default?.getSubgraphExecutor) { transport = transport.default; } if (!transport.getSubgraphExecutor) { throw new Error(`@graphql-mesh/transport-${kind} module does not export "getSubgraphExecutor"`); } if (typeof transport.getSubgraphExecutor !== 'function') { throw new Error(`@graphql-mesh/transport-${kind} module's export "getSubgraphExecutor" is not a function`); } return transport; } catch (e) { if (e.code === 'MODULE_NOT_FOUND') { throw new Error(`No transport found for ${kind}. Please make sure you have installed @graphql-mesh/transport-${kind} or defined the transport config in "mesh.config.ts"`); } else { throw e; // bubble non-module_not_found errors } } } function getTransportExecutor({ transportContext, transportEntry, subgraphName = '', subgraph, transports = defaultTransportsGetter, }) { const kind = transportEntry.kind; transportContext?.logger?.debug(`Loading transport "${kind}" for subgraph ${subgraphName}`); return mapMaybePromise(typeof transports === 'function' ? transports(kind) : transports[kind], transport => { if (!transport) { throw new Error(`Transport "${kind}" is empty`); } if (typeof transport !== 'object') { throw new Error(`Transport "${kind}" is not an object`); } let getSubgraphExecutor; if ('default' in transport) { getSubgraphExecutor = transport.default?.getSubgraphExecutor; } else { getSubgraphExecutor = transport.getSubgraphExecutor; } if (!getSubgraphExecutor) { throw new Error(`Transport "${kind}" does not have "getSubgraphExecutor"`); } if (typeof getSubgraphExecutor !== 'function') { throw new Error(`Transport "${kind}" "getSubgraphExecutor" is not a function`); } return getSubgraphExecutor({ subgraphName, subgraph, transportEntry, getTransportExecutor(transportEntry) { return getTransportExecutor({ transportContext, transportEntry, subgraphName, subgraph, transports, }); }, ...transportContext, }); }); } export const subgraphNameByExecutionRequest = new WeakMap(); /** * This function creates a executor factory that uses the transport packages, * and wraps them with the hooks */ export function getOnSubgraphExecute({ onSubgraphExecuteHooks, transportContext, transportEntryMap, getSubgraphSchema, transportExecutorStack, transports, }) { const subgraphExecutorMap = new Map(); return function onSubgraphExecute(subgraphName, executionRequest) { subgraphNameByExecutionRequest.set(executionRequest, subgraphName); let executor = subgraphExecutorMap.get(subgraphName); // If the executor is not initialized yet, initialize it if (executor == null) { transportContext?.logger?.debug(`Initializing executor for subgraph ${subgraphName}`); // Lazy executor that loads transport executor on demand executor = function lazyExecutor(subgraphExecReq) { return mapMaybePromise( // Gets the transport executor for the given subgraph getTransportExecutor({ transportContext, subgraphName, get subgraph() { return getSubgraphSchema(subgraphName); }, get transportEntry() { return transportEntryMap?.[subgraphName]; }, transports, }), executor_ => { if (isDisposable(executor_)) { transportExecutorStack.use(executor_); } // Wraps the transport executor with hooks executor = wrapExecutorWithHooks({ executor: executor_, onSubgraphExecuteHooks, subgraphName, transportEntryMap, transportContext, getSubgraphSchema, }); // Caches the executor for future use subgraphExecutorMap.set(subgraphName, executor); return executor(subgraphExecReq); }); }; // Caches the lazy executor to prevent race conditions subgraphExecutorMap.set(subgraphName, executor); } return executor(executionRequest); }; } /** * This function wraps the executor created by the transport package * with `onSubgraphExecuteHooks` to hook into the execution phase of subgraphs */ export function wrapExecutorWithHooks({ executor, onSubgraphExecuteHooks, subgraphName, transportEntryMap, getSubgraphSchema, transportContext, }) { return function executorWithHooks(executionRequest) { executionRequest.info = executionRequest.info || {}; executionRequest.info.executionRequest = executionRequest; const requestId = executionRequest.context?.request && requestIdByRequest.get(executionRequest.context.request); let execReqLogger = transportContext?.logger?.child?.(subgraphName); if (execReqLogger) { if (requestId) { execReqLogger = execReqLogger.child(requestId); } loggerForExecutionRequest.set(executionRequest, execReqLogger); } if (onSubgraphExecuteHooks.length === 0) { return executor(executionRequest); } const onSubgraphExecuteDoneHooks = []; return mapMaybePromise(iterateAsync(onSubgraphExecuteHooks, onSubgraphExecuteHook => onSubgraphExecuteHook({ get subgraph() { return getSubgraphSchema(subgraphName); }, subgraphName, get transportEntry() { return transportEntryMap?.[subgraphName]; }, executionRequest, setExecutionRequest(newExecutionRequest) { execReqLogger.debug('Updating execution request to: ', newExecutionRequest); executionRequest = newExecutionRequest; }, executor, setExecutor(newExecutor) { execReqLogger.debug('executor has been updated'); executor = newExecutor; }, requestId, logger: execReqLogger, }), onSubgraphExecuteDoneHooks), () => { if (onSubgraphExecuteDoneHooks.length === 0) { return executor(executionRequest); } return mapMaybePromise(executor(executionRequest), currentResult => { const executeDoneResults = []; return mapMaybePromise(iterateAsync(onSubgraphExecuteDoneHooks, onSubgraphExecuteDoneHook => onSubgraphExecuteDoneHook({ result: currentResult, setResult(newResult) { execReqLogger.debug('overriding result with: ', newResult); currentResult = newResult; }, }), executeDoneResults), () => { if (!isAsyncIterable(currentResult)) { return currentResult; } if (executeDoneResults.length === 0) { return currentResult; } const onNextHooks = []; const onEndHooks = []; for (const executeDoneResult of executeDoneResults) { if (executeDoneResult.onNext) { onNextHooks.push(executeDoneResult.onNext); } if (executeDoneResult.onEnd) { onEndHooks.push(executeDoneResult.onEnd); } } if (onNextHooks.length === 0 && onEndHooks.length === 0) { return currentResult; } return mapAsyncIterator(currentResult, currentResult => mapMaybePromise(iterateAsync(onNextHooks, onNext => onNext({ result: currentResult, setResult: res => { execReqLogger.debug('overriding result with: ', res); currentResult = res; }, })), () => currentResult), undefined, () => onEndHooks.length === 0 ? undefined : iterateAsync(onEndHooks, onEnd => onEnd())); }); }); }); }; } export function compareSchemas(a, b) { let aStr; if (typeof a === 'string') { aStr = a; } else if (isDocumentNode(a)) { aStr = print(a); } else { aStr = printSchemaWithDirectives(a); } let bStr; if (typeof b === 'string') { bStr = b; } else if (isDocumentNode(b)) { bStr = print(b); } else { bStr = printSchemaWithDirectives(b); } return aStr === bStr; } // TODO: Fix this in GraphQL Tools export function compareSubgraphNames(name1, name2) { return constantCase(name1) === constantCase(name2); }