@graphql-mesh/fusion-runtime
Version:
Runtime for GraphQL Mesh Fusion Supergraph
236 lines (235 loc) • 10.3 kB
JavaScript
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);
}