@graphql-mesh/fusion-runtime
Version:
Runtime for GraphQL Mesh Fusion Supergraph
1,374 lines (1,369 loc) • 53.6 kB
JavaScript
'use strict';
var transportCommon = require('@graphql-mesh/transport-common');
var utils = require('@graphql-mesh/utils');
var utils$1 = require('@graphql-tools/utils');
var constantCase = require('constant-case');
var disposablestack = require('@whatwg-node/disposablestack');
var graphql = require('graphql');
var federation = require('@graphql-tools/federation');
var merge = require('@graphql-tools/merge');
var stitch = require('@graphql-tools/stitch');
var stitchingDirectives = require('@graphql-tools/stitching-directives');
var wrap = require('@graphql-tools/wrap');
function defaultTransportsGetter(kind) {
const moduleName = `@graphql-mesh/transport-${kind}`;
return utils$1.mapMaybePromise(require(moduleName), (transport) => {
if (typeof transport !== "object") {
throw new Error(`${moduleName} module does not export an object`);
}
if (transport?.default?.getSubgraphExecutor) {
transport = transport.default;
}
if (!transport?.getSubgraphExecutor) {
throw new Error(
`${moduleName} module does not export "getSubgraphExecutor"`
);
}
if (typeof transport?.getSubgraphExecutor !== "function") {
throw new Error(
`${moduleName} module's export "getSubgraphExecutor" is not a function`
);
}
return transport;
});
}
function getTransportExecutor({
transportContext,
transportEntry,
subgraphName = "",
subgraph,
transports = defaultTransportsGetter,
getDisposeReason
}) {
const kind = transportEntry?.kind || "";
let logger = transportContext?.logger;
if (logger) {
if (subgraphName) {
logger = logger.child(subgraphName);
}
logger?.debug(`Loading transport "${kind}"`);
}
return utils$1.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(transportEntry2) {
return getTransportExecutor({
transportContext,
transportEntry: transportEntry2,
subgraphName,
subgraph,
transports,
getDisposeReason
});
},
getDisposeReason,
...transportContext
});
}
);
}
const subgraphNameByExecutionRequest = /* @__PURE__ */ new WeakMap();
function getOnSubgraphExecute({
onSubgraphExecuteHooks,
transportContext = {},
transportEntryMap,
getSubgraphSchema,
transportExecutorStack,
transports,
getDisposeReason
}) {
const subgraphExecutorMap = /* @__PURE__ */ new Map();
return function onSubgraphExecute(subgraphName, executionRequest) {
subgraphNameByExecutionRequest.set(executionRequest, subgraphName);
let executor = subgraphExecutorMap.get(subgraphName);
if (executor == null) {
let logger = transportContext?.logger;
if (logger) {
const requestId = utils.requestIdByRequest.get(
executionRequest.context?.request
);
if (requestId) {
logger = logger.child(requestId);
}
if (subgraphName) {
logger = logger.child(subgraphName);
}
logger.debug(`Initializing executor`);
}
executor = function lazyExecutor(subgraphExecReq) {
return utils$1.mapMaybePromise(
// Gets the transport executor for the given subgraph
getTransportExecutor({
transportContext,
subgraphName,
get subgraph() {
return getSubgraphSchema(subgraphName);
},
get transportEntry() {
return transportEntryMap[subgraphName];
},
transports,
getDisposeReason
}),
(executor_) => {
if (utils.isDisposable(executor_)) {
transportExecutorStack.use(executor_);
}
executor = wrapExecutorWithHooks({
executor: executor_,
onSubgraphExecuteHooks,
subgraphName,
transportEntryMap,
transportContext,
getSubgraphSchema
});
subgraphExecutorMap.set(subgraphName, executor);
return executor(subgraphExecReq);
}
);
};
subgraphExecutorMap.set(subgraphName, executor);
}
return executor(executionRequest);
};
}
function wrapExecutorWithHooks({
executor: baseExecutor,
onSubgraphExecuteHooks,
subgraphName,
transportEntryMap,
getSubgraphSchema,
transportContext
}) {
return function executorWithHooks(baseExecutionRequest) {
baseExecutionRequest.info = baseExecutionRequest.info || {};
baseExecutionRequest.info.executionRequest = baseExecutionRequest;
const requestId = baseExecutionRequest.context?.request && utils.requestIdByRequest.get(baseExecutionRequest.context.request);
let execReqLogger = transportContext?.logger;
if (execReqLogger) {
if (requestId) {
execReqLogger = execReqLogger.child(requestId);
}
utils.loggerForExecutionRequest.set(baseExecutionRequest, execReqLogger);
}
execReqLogger = execReqLogger?.child?.(subgraphName);
if (onSubgraphExecuteHooks.length === 0) {
return baseExecutor(baseExecutionRequest);
}
let executor = baseExecutor;
let executionRequest = baseExecutionRequest;
const onSubgraphExecuteDoneHooks = [];
return utils$1.mapMaybePromise(
utils.iterateAsync(
onSubgraphExecuteHooks,
(onSubgraphExecuteHook) => onSubgraphExecuteHook({
get subgraph() {
return getSubgraphSchema(subgraphName);
},
subgraphName,
get transportEntry() {
return transportEntryMap?.[subgraphName];
},
executionRequest,
setExecutionRequest(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 utils$1.mapMaybePromise(executor(executionRequest), (currentResult) => {
const executeDoneResults = [];
return utils$1.mapMaybePromise(
utils.iterateAsync(
onSubgraphExecuteDoneHooks,
(onSubgraphExecuteDoneHook) => onSubgraphExecuteDoneHook({
result: currentResult,
setResult(newResult) {
execReqLogger?.debug("overriding result with: ", newResult);
currentResult = newResult;
}
}),
executeDoneResults
),
() => {
if (!utils$1.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 utils$1.mapAsyncIterator(
currentResult,
(currentResult2) => utils$1.mapMaybePromise(
utils.iterateAsync(
onNextHooks,
(onNext) => onNext({
result: currentResult2,
setResult: (res) => {
execReqLogger?.debug("overriding result with: ", res);
currentResult2 = res;
}
})
),
() => currentResult2
),
undefined,
() => onEndHooks.length === 0 ? undefined : utils.iterateAsync(onEndHooks, (onEnd) => onEnd())
);
}
);
});
}
);
};
}
function compareSchemas(a, b) {
let aStr;
if (typeof a === "string") {
aStr = a;
} else if (utils$1.isDocumentNode(a)) {
aStr = transportCommon.defaultPrintFn(a);
} else {
aStr = utils$1.printSchemaWithDirectives(a);
}
let bStr;
if (typeof b === "string") {
bStr = b;
} else if (utils$1.isDocumentNode(b)) {
bStr = transportCommon.defaultPrintFn(b);
} else {
bStr = utils$1.printSchemaWithDirectives(b);
}
return aStr === bStr;
}
function compareSubgraphNames(name1, name2) {
return constantCase.constantCase(name1) === constantCase.constantCase(name2);
}
function wrapMergedTypeResolver(originalResolver, typeName, onDelegationStageExecuteHooks, baseLogger) {
return (object, context, info, subschema, selectionSet, key, type) => {
let logger = baseLogger;
let requestId;
if (logger && context["request"]) {
requestId = utils.requestIdByRequest.get(context["request"]);
if (requestId) {
logger = logger.child(requestId);
}
}
if (subschema.name) {
logger = logger?.child(subschema.name);
}
let resolver = originalResolver;
function setResolver(newResolver) {
resolver = newResolver;
}
const onDelegationStageExecuteDoneHooks = [];
for (const onDelegationStageExecute of onDelegationStageExecuteHooks) {
const onDelegationStageExecuteDone = onDelegationStageExecute({
object,
context,
info,
subgraph: subschema.name,
subschema,
selectionSet,
key,
typeName,
type,
requestId,
logger,
resolver,
setResolver
});
if (onDelegationStageExecuteDone) {
onDelegationStageExecuteDoneHooks.push(onDelegationStageExecuteDone);
}
}
return utils$1.mapMaybePromise(
resolver(
object,
context,
info,
subschema,
selectionSet,
key,
type
),
(result) => {
function setResult(newResult) {
result = newResult;
}
for (const onDelegationStageExecuteDone of onDelegationStageExecuteDoneHooks) {
onDelegationStageExecuteDone({
result,
setResult
});
}
return result;
}
);
};
}
function millisecondsToStr(milliseconds) {
function numberEnding(number) {
return number > 1 ? "s" : "";
}
let temp = Math.floor(milliseconds / 1e3);
const days = Math.floor((temp %= 31536e3) / 86400);
if (days) {
return days + " day" + numberEnding(days);
}
const hours = Math.floor((temp %= 86400) / 3600);
if (hours) {
return hours + " hour" + numberEnding(hours);
}
const minutes = Math.floor((temp %= 3600) / 60);
if (minutes) {
return minutes + " minute" + numberEnding(minutes);
}
const seconds = temp % 60;
if (seconds) {
return seconds + " second" + numberEnding(seconds);
}
return "less than a second";
}
function handleFederationSubschema({
subschemaConfig,
realSubgraphNameMap,
schemaDirectives,
transportEntryMap,
additionalTypeDefs,
stitchingDirectivesTransformer,
onSubgraphExecute
}) {
const subgraphName = (subschemaConfig.name = realSubgraphNameMap?.get(subschemaConfig.name || "") || subschemaConfig.name) || "";
const subgraphDirectives = utils$1.getDirectiveExtensions(subschemaConfig.schema);
const directivesToLook = schemaDirectives || subgraphDirectives;
for (const directiveName in directivesToLook) {
if (!subgraphDirectives[directiveName]?.length && schemaDirectives?.[directiveName]?.length) {
const directives = schemaDirectives[directiveName];
for (const directive of directives) {
if (directive.subgraph && directive.subgraph !== subgraphName) {
continue;
}
subgraphDirectives[directiveName] ||= [];
subgraphDirectives[directiveName].push(directive);
}
}
}
const subgraphExtensions = subschemaConfig.schema.extensions ||= {};
subgraphExtensions["directives"] = subgraphDirectives;
const transportDirectives = subgraphDirectives.transport ||= [];
const transportDirective = transportDirectives[0];
if (transportDirective) {
transportEntryMap[subgraphName] = transportDirective;
} else {
transportEntryMap[subgraphName] = {
kind: "http",
subgraph: subgraphName,
location: subschemaConfig.endpoint
};
}
const renameTypeNames = {};
const renameTypeNamesReversed = {};
const renameFieldByObjectTypeNames = {};
const renameFieldByInputTypeNames = {};
const renameFieldByInterfaceTypeNames = {};
const renameEnumValueByEnumTypeNames = {};
const renameFieldByTypeNamesReversed = {};
const renameArgByFieldByTypeNames = {};
const transforms = subschemaConfig.transforms ||= [];
let mergeDirectiveUsed = false;
subschemaConfig.schema = utils$1.mapSchema(subschemaConfig.schema, {
[utils$1.MapperKind.TYPE]: (type) => {
const typeDirectives = utils$1.getDirectiveExtensions(type);
const sourceDirectives = typeDirectives.source;
const sourceDirective = sourceDirectives?.find(
(directive) => compareSubgraphNames(directive.subgraph, subgraphName)
);
if (sourceDirective != null) {
const realName = sourceDirective.name || type.name;
if (type.name !== realName) {
renameTypeNames[realName] = type.name;
renameTypeNamesReversed[type.name] = realName;
return new (Object.getPrototypeOf(type)).constructor({
...type.toConfig(),
name: realName
});
}
}
},
[utils$1.MapperKind.OBJECT_FIELD]: (fieldConfig, fieldName, typeName, schema) => {
const fieldDirectives = utils$1.getDirectiveExtensions(fieldConfig);
if (fieldDirectives.merge?.length) {
mergeDirectiveUsed = true;
}
const resolveToDirectives = fieldDirectives.resolveTo;
if (resolveToDirectives?.length) {
const type = schema.getType(typeName);
if (!graphql.isObjectType(type)) {
throw new Error(
`Type ${typeName} for field ${fieldName} is not an object type`
);
}
const fieldMap = type.getFields();
const field = fieldMap[fieldName];
if (!field) {
throw new Error(`Field ${typeName}.${fieldName} not found`);
}
additionalTypeDefs.push({
kind: graphql.Kind.DOCUMENT,
definitions: [
{
kind: graphql.Kind.OBJECT_TYPE_DEFINITION,
name: { kind: graphql.Kind.NAME, value: typeName },
fields: [utils$1.astFromField(field, schema)]
}
]
});
}
const additionalFieldDirectives = fieldDirectives.additionalField;
if (additionalFieldDirectives?.length) {
return null;
}
const sourceDirectives = fieldDirectives.source;
const sourceDirective = sourceDirectives?.find(
(directive) => compareSubgraphNames(directive.subgraph, subgraphName)
);
const realTypeName = renameTypeNamesReversed[typeName] ?? typeName;
const realName = sourceDirective?.name ?? fieldName;
if (fieldName !== realName) {
if (!renameFieldByObjectTypeNames[realTypeName]) {
renameFieldByObjectTypeNames[realTypeName] = {};
}
renameFieldByObjectTypeNames[realTypeName][realName] = fieldName;
if (!renameFieldByTypeNamesReversed[realTypeName]) {
renameFieldByTypeNamesReversed[realTypeName] = {};
}
renameFieldByTypeNamesReversed[realTypeName][fieldName] = realName;
}
const hoistDirectives = fieldDirectives.hoist;
if (hoistDirectives?.length) {
for (const hoistDirective of hoistDirectives) {
if (hoistDirective.subgraph === subgraphName) {
const pathConfig = hoistDirective.pathConfig.map((annotation) => {
if (typeof annotation === "string") {
return {
fieldName: annotation,
argFilter: () => true
};
}
return {
fieldName: annotation.fieldName,
argFilter: annotation.filterArgs ? (arg) => !annotation.filterArgs.includes(arg.name) : () => true
};
});
transforms.push(
new wrap.HoistField(realTypeName, pathConfig, fieldName),
new wrap.PruneSchema()
);
}
}
}
const newArgs = {};
if (fieldConfig.args) {
for (const argName in fieldConfig.args) {
const argConfig = fieldConfig.args[argName];
const argDirectives = utils$1.getDirectiveExtensions(argConfig);
const argSourceDirectives = argDirectives.source;
const argSourceDirective = argSourceDirectives?.find(
(directive) => compareSubgraphNames(directive.subgraph, subgraphName)
);
if (argSourceDirective != null) {
const realArgName = argSourceDirective.name ?? argName;
newArgs[realArgName] = argConfig;
if (realArgName !== argName) {
if (!renameArgByFieldByTypeNames[realTypeName]) {
renameArgByFieldByTypeNames[realTypeName] = {};
}
if (!renameArgByFieldByTypeNames[realTypeName][realName]) {
renameArgByFieldByTypeNames[realTypeName][realName] = {};
}
renameArgByFieldByTypeNames[realTypeName][realName][realArgName] = argName;
}
} else {
newArgs[argName] = argConfig;
}
}
}
let fieldType = fieldConfig.type;
if (sourceDirective?.type) {
const fieldTypeNode = parseTypeNodeWithRenames(
sourceDirective.type,
renameTypeNames
);
const newType = graphql.typeFromAST(subschemaConfig.schema, fieldTypeNode);
if (!newType) {
throw new Error(
`Type ${sourceDirective.type} for field ${typeName}.${fieldName} is not defined in the schema`
);
}
if (!graphql.isOutputType(newType)) {
throw new Error(
`Type ${sourceDirective.type} for field ${typeName}.${fieldName} is not an output type`
);
}
fieldType = newType;
}
return [
realName,
{
...fieldConfig,
type: fieldType,
args: newArgs
}
];
},
[utils$1.MapperKind.INPUT_OBJECT_FIELD]: (fieldConfig, fieldName, typeName) => {
const fieldDirectives = utils$1.getDirectiveExtensions(fieldConfig);
const sourceDirectives = fieldDirectives.source;
const sourceDirective = sourceDirectives?.find(
(directive) => compareSubgraphNames(directive.subgraph, subgraphName)
);
if (sourceDirective != null) {
const realTypeName = renameTypeNamesReversed[typeName] ?? typeName;
const realName = sourceDirective.name ?? fieldName;
if (fieldName !== realName) {
if (!renameFieldByInputTypeNames[realTypeName]) {
renameFieldByInputTypeNames[realTypeName] = {};
}
renameFieldByInputTypeNames[realTypeName][realName] = fieldName;
}
return [realName, fieldConfig];
}
const additionalFieldDirectives = fieldDirectives.additionalField;
if (additionalFieldDirectives?.length) {
return null;
}
return undefined;
},
[utils$1.MapperKind.INTERFACE_FIELD]: (fieldConfig, fieldName, typeName, schema) => {
const fieldDirectives = utils$1.getDirectiveExtensions(fieldConfig);
const resolveToDirectives = fieldDirectives.resolveTo;
if (resolveToDirectives?.length) {
const type = schema.getType(typeName);
if (!graphql.isInterfaceType(type)) {
throw new Error(
`Type ${typeName} for field ${fieldName} is not an object type`
);
}
const fieldMap = type.getFields();
const field = fieldMap[fieldName];
if (!field) {
throw new Error(`Field ${typeName}.${fieldName} not found`);
}
additionalTypeDefs.push({
kind: graphql.Kind.DOCUMENT,
definitions: [
{
kind: graphql.Kind.INTERFACE_TYPE_DEFINITION,
name: { kind: graphql.Kind.NAME, value: typeName },
fields: [utils$1.astFromField(field, schema)]
}
]
});
}
const additionalFieldDirectives = fieldDirectives.additionalField;
if (additionalFieldDirectives?.length) {
return null;
}
const sourceDirectives = fieldDirectives.source;
const sourceDirective = sourceDirectives?.find(
(directive) => compareSubgraphNames(directive.subgraph, subgraphName)
);
if (sourceDirective != null) {
const realTypeName = renameTypeNamesReversed[typeName] ?? typeName;
const realName = sourceDirective.name ?? fieldName;
if (fieldName !== realName) {
if (!renameFieldByInterfaceTypeNames[realTypeName]) {
renameFieldByInterfaceTypeNames[realTypeName] = {};
}
renameFieldByInterfaceTypeNames[realTypeName][realName] = fieldName;
}
return [realName, fieldConfig];
}
return undefined;
},
[utils$1.MapperKind.ENUM_VALUE]: (enumValueConfig, typeName, _schema, externalValue) => {
const enumDirectives = utils$1.getDirectiveExtensions(enumValueConfig);
const sourceDirectives = enumDirectives.source;
const sourceDirective = sourceDirectives?.find(
(directive) => compareSubgraphNames(directive.subgraph, subgraphName)
);
if (sourceDirective != null) {
const realValue = sourceDirective.name ?? externalValue;
const realTypeName = renameTypeNamesReversed[typeName] ?? typeName;
if (externalValue !== realValue) {
if (!renameEnumValueByEnumTypeNames[realTypeName]) {
renameEnumValueByEnumTypeNames[realTypeName] = {};
}
renameEnumValueByEnumTypeNames[realTypeName][realValue] = externalValue;
}
return [
realValue,
{
...enumValueConfig,
value: realValue
}
];
}
return undefined;
}
});
if (Object.keys(renameTypeNames).length > 0) {
transforms.push(
new wrap.RenameTypes((typeName) => renameTypeNames[typeName] || typeName)
);
}
if (Object.keys(renameFieldByObjectTypeNames).length > 0) {
transforms.push(
new wrap.RenameObjectFields((typeName, fieldName, _fieldConfig) => {
const realTypeName = renameTypeNamesReversed[typeName] ?? typeName;
return renameFieldByObjectTypeNames[realTypeName]?.[fieldName] ?? fieldName;
})
);
}
if (Object.keys(renameFieldByInputTypeNames).length > 0) {
transforms.push(
new wrap.RenameInputObjectFields((typeName, fieldName, _fieldConfig) => {
const realTypeName = renameTypeNamesReversed[typeName] ?? typeName;
return renameFieldByInputTypeNames[realTypeName]?.[fieldName] ?? fieldName;
})
);
}
if (Object.keys(renameFieldByInterfaceTypeNames).length > 0) {
transforms.push(
new wrap.RenameInterfaceFields((typeName, fieldName, _fieldConfig) => {
const realTypeName = renameTypeNamesReversed[typeName] ?? typeName;
return renameFieldByInterfaceTypeNames[realTypeName]?.[fieldName] ?? fieldName;
})
);
}
if (Object.keys(renameEnumValueByEnumTypeNames).length > 0) {
transforms.push(
new wrap.TransformEnumValues((typeName, externalValue, enumValueConfig) => {
const realTypeName = renameTypeNamesReversed[typeName] ?? typeName;
const realValue = renameEnumValueByEnumTypeNames[realTypeName]?.[enumValueConfig.value || externalValue] ?? enumValueConfig.value;
return [
realValue,
{
...enumValueConfig,
value: realValue
}
];
})
);
}
if (Object.keys(renameArgByFieldByTypeNames).length > 0) {
transforms.push(
new wrap.RenameObjectFieldArguments((typeName, fieldName, argName) => {
const realTypeName = renameTypeNamesReversed[typeName] ?? typeName;
const realFieldName = renameFieldByTypeNamesReversed[realTypeName]?.[fieldName] ?? fieldName;
return renameArgByFieldByTypeNames[realTypeName]?.[realFieldName]?.[argName] ?? argName;
})
);
}
if (mergeDirectiveUsed) {
const existingMergeConfig = subschemaConfig.merge || {};
subschemaConfig.merge = {};
const subgraphSchemaConfig = subschemaConfig.schema.toConfig();
subschemaConfig.schema = new graphql.GraphQLSchema({
...subgraphSchemaConfig,
directives: [...subgraphSchemaConfig.directives, mergeDirective],
assumeValid: true
});
subschemaConfig.merge = Object.assign(
existingMergeConfig,
stitchingDirectivesTransformer(subschemaConfig).merge
);
const queryType = subschemaConfig.schema.getQueryType();
if (!queryType) {
throw new Error("Query type is required");
}
if (transforms.length && subschemaConfig.merge) {
const subschemaConfigMerge = subschemaConfig.merge;
const mergeConfig = {};
for (const realTypeName in subschemaConfig.merge) {
const renamedTypeName = renameTypeNames[realTypeName] ?? realTypeName;
mergeConfig[renamedTypeName] = subschemaConfigMerge[realTypeName];
const realQueryFieldName = mergeConfig[renamedTypeName].fieldName;
if (realQueryFieldName) {
mergeConfig[renamedTypeName].fieldName = renameFieldByObjectTypeNames[queryType.name]?.[realQueryFieldName] ?? realQueryFieldName;
}
mergeConfig[renamedTypeName].entryPoints = subschemaConfigMerge[realTypeName]?.entryPoints?.map((entryPoint) => ({
...entryPoint,
fieldName: entryPoint.fieldName && (renameFieldByObjectTypeNames[queryType.name]?.[entryPoint.fieldName] ?? entryPoint.fieldName)
}));
}
subschemaConfig.merge = mergeConfig;
}
}
subschemaConfig.executor = function subschemaExecutor(req) {
return onSubgraphExecute(subgraphName, req);
};
return subschemaConfig;
}
const mergeDirective = new graphql.GraphQLDirective({
name: "merge",
isRepeatable: true,
locations: [graphql.DirectiveLocation.FIELD],
args: {
subgraph: {
type: graphql.GraphQLString
},
key: {
type: graphql.GraphQLString
},
keyField: {
type: graphql.GraphQLString
},
keyArg: {
type: graphql.GraphQLString
},
argsExpr: {
type: graphql.GraphQLString
}
}
});
function parseTypeNodeWithRenames(typeString, renameTypeNames) {
const typeNode = graphql.parseType(typeString);
return graphql.visit(typeNode, {
NamedType: (node) => {
const realName = renameTypeNames[node.name.value] ?? node.name.value;
return {
...node,
name: {
...node.name,
value: realName
}
};
}
});
}
const restoreExtraDirectives = utils$1.memoize1(function restoreExtraDirectives2(schema) {
const queryType = schema.getQueryType();
if (!queryType) {
throw new Error("Query type is required");
}
const queryTypeExtensions = utils$1.getDirectiveExtensions(queryType);
const extraSchemaDefinitionDirectives = queryTypeExtensions?.extraSchemaDefinitionDirective;
if (extraSchemaDefinitionDirectives?.length) {
schema = utils$1.mapSchema(schema, {
[utils$1.MapperKind.TYPE]: (type) => {
const typeDirectiveExtensions = utils$1.getDirectiveExtensions(type) || {};
const TypeCtor = Object.getPrototypeOf(type).constructor;
if (type.name === queryType.name) {
const typeConfig = type.toConfig();
return new TypeCtor({
...typeConfig,
extensions: {
...type.extensions || {},
directives: {
...typeDirectiveExtensions,
extraSchemaDefinitionDirective: []
}
},
// Cleanup ASTNode to prevent conflicts
astNode: undefined
});
}
}
});
if (extraSchemaDefinitionDirectives?.length) {
const schemaDirectives = utils$1.getDirectiveExtensions(schema);
for (const extensionObj of extraSchemaDefinitionDirectives) {
if (extensionObj != null) {
const { directives } = extensionObj;
for (const directiveName in directives) {
const directiveObjects = directives[directiveName];
if (Array.isArray(directiveObjects)) {
schemaDirectives[directiveName] ||= [];
schemaDirectives[directiveName].push(...directiveObjects);
}
}
}
}
const schemaExtensions = schema.extensions ||= {};
schemaExtensions["directives"] = schemaDirectives;
}
}
return schema;
});
function getStitchingDirectivesTransformerForSubschema() {
const { stitchingDirectivesTransformer } = stitchingDirectives.stitchingDirectives({
keyDirectiveName: "stitch__key",
computedDirectiveName: "stitch__computed",
mergeDirectiveName: "merge",
canonicalDirectiveName: "stitch__canonical"
});
return stitchingDirectivesTransformer;
}
function handleResolveToDirectives(typeDefsOpt, additionalTypeDefs, additionalResolvers) {
const mergedTypeDefs = merge.mergeTypeDefs([typeDefsOpt, additionalTypeDefs]);
graphql.visit(mergedTypeDefs, {
[graphql.Kind.FIELD_DEFINITION](field, _key, _parent, _path, ancestors) {
const fieldDirectives = utils$1.getDirectiveExtensions({ astNode: field });
const resolveToDirectives = fieldDirectives?.resolveTo;
if (resolveToDirectives?.length) {
const targetTypeName = ancestors[ancestors.length - 1].name.value;
const targetFieldName = field.name.value;
for (const resolveToDirective of resolveToDirectives) {
additionalResolvers.push(
utils.resolveAdditionalResolversWithoutImport({
...resolveToDirective,
targetTypeName,
targetFieldName
})
);
}
}
}
});
return mergedTypeDefs;
}
const handleFederationSupergraph = function({
unifiedGraph,
onSubgraphExecute,
onDelegationStageExecuteHooks,
additionalTypeDefs: additionalTypeDefsFromConfig = [],
additionalResolvers: additionalResolversFromConfig = [],
transportEntryAdditions,
batch = true,
logger
}) {
const additionalTypeDefs = [...utils$1.asArray(additionalTypeDefsFromConfig)];
const additionalResolvers = [...utils$1.asArray(additionalResolversFromConfig)];
const transportEntryMap = {};
let subschemas = [];
const stitchingDirectivesTransformer = getStitchingDirectivesTransformerForSubschema();
unifiedGraph = restoreExtraDirectives(unifiedGraph);
const schemaDirectives = utils$1.getDirectiveExtensions(unifiedGraph);
const realSubgraphNameMap = /* @__PURE__ */ new Map();
const joinGraphType = unifiedGraph.getType("join__Graph");
if (graphql.isEnumType(joinGraphType)) {
for (const enumValue of joinGraphType.getValues()) {
const enumValueDirectives = utils$1.getDirectiveExtensions(enumValue);
const joinGraphDirectives = enumValueDirectives?.join__graph;
if (joinGraphDirectives?.length) {
for (const joinGraphDirective of joinGraphDirectives) {
if (joinGraphDirective) {
realSubgraphNameMap.set(enumValue.name, joinGraphDirective.name);
}
}
}
}
}
let executableUnifiedGraph = federation.getStitchedSchemaFromSupergraphSdl({
supergraphSdl: utils$1.getDocumentNodeFromSchema(unifiedGraph),
/**
* This visits over the subgraph schema to get;
* - Extra Type Defs and Resolvers (additionalTypeDefs & additionalResolvers)
* - Transport Entries (transportEntryMap)
* - Type Merging Configuration for the subgraph (subschemaConfig.merge)
* - Set the executor for the subschema (subschemaConfig.executor)
*/
onSubschemaConfig: (subschemaConfig) => handleFederationSubschema({
subschemaConfig,
realSubgraphNameMap,
schemaDirectives,
transportEntryMap,
additionalTypeDefs,
stitchingDirectivesTransformer,
onSubgraphExecute
}),
batch,
onStitchingOptions(opts) {
subschemas = opts.subschemas;
opts.typeDefs = handleResolveToDirectives(
opts.typeDefs,
additionalTypeDefs,
additionalResolvers
);
opts.resolvers = additionalResolvers;
opts.inheritResolversFromInterfaces = true;
if (onDelegationStageExecuteHooks?.length) {
for (const subschema of subschemas) {
if (subschema.merge) {
for (const typeName in subschema.merge) {
const mergedTypeConfig = subschema.merge[typeName];
if (mergedTypeConfig) {
const originalResolver = stitch.createMergedTypeResolver(
mergedTypeConfig,
typeName
);
if (originalResolver) {
mergedTypeConfig.resolve = wrapMergedTypeResolver(
originalResolver,
typeName,
onDelegationStageExecuteHooks,
logger
);
}
}
}
}
}
}
},
onSubgraphAST(_name, subgraphAST) {
return graphql.visit(subgraphAST, {
[graphql.Kind.OBJECT_TYPE_DEFINITION](node) {
const typeName = node.name.value;
return {
...node,
fields: node.fields?.filter((fieldNode) => {
const fieldDirectives = utils$1.getDirectiveExtensions({ astNode: fieldNode });
const resolveToDirectives = fieldDirectives.resolveTo;
if (resolveToDirectives?.length) {
additionalTypeDefs.push({
kind: graphql.Kind.DOCUMENT,
definitions: [
{
kind: graphql.Kind.OBJECT_TYPE_DEFINITION,
name: { kind: graphql.Kind.NAME, value: typeName },
fields: [fieldNode]
}
]
});
}
const additionalFieldDirectives = fieldDirectives.additionalField;
if (additionalFieldDirectives?.length) {
return false;
}
return true;
})
};
}
});
}
});
if (transportEntryAdditions) {
const wildcardTransportOptions = transportEntryAdditions["*"];
for (const subgraphName in transportEntryMap) {
const toBeMerged = [];
const transportEntry = transportEntryMap[subgraphName];
if (transportEntry) {
toBeMerged.push(transportEntry);
}
const transportOptionBySubgraph = transportEntryAdditions[subgraphName];
if (transportOptionBySubgraph) {
toBeMerged.push(transportOptionBySubgraph);
}
const transportOptionByKind = transportEntryAdditions["*." + transportEntry?.kind];
if (transportOptionByKind) {
toBeMerged.push(transportOptionByKind);
}
if (wildcardTransportOptions) {
toBeMerged.push(wildcardTransportOptions);
}
transportEntryMap[subgraphName] = utils$1.mergeDeep(toBeMerged);
}
}
return {
unifiedGraph: executableUnifiedGraph,
subschemas,
transportEntryMap,
additionalResolvers
};
};
function ensureSchema(source) {
if (graphql.isSchema(source)) {
return source;
}
if (typeof source === "string") {
return graphql.buildSchema(source, { assumeValid: true, assumeValidSDL: true });
}
if (utils$1.isDocumentNode(source)) {
return graphql.buildASTSchema(source, { assumeValid: true, assumeValidSDL: true });
}
return source;
}
const UNIFIEDGRAPH_CACHE_KEY = "hive-gateway:supergraph";
class UnifiedGraphManager {
constructor(opts) {
this.opts = opts;
this.batch = opts.batch ?? true;
this.handleUnifiedGraph = opts.handleUnifiedGraph || handleFederationSupergraph;
this.onSubgraphExecuteHooks = opts?.onSubgraphExecuteHooks || [];
this.onDelegationPlanHooks = opts?.onDelegationPlanHooks || [];
this.onDelegationStageExecuteHooks = opts?.onDelegationStageExecuteHooks || [];
if (opts.pollingInterval != null) {
opts.transportContext?.logger?.debug(
`Starting polling to Supergraph with interval ${millisecondsToStr(opts.pollingInterval)}`
);
}
}
batch;
handleUnifiedGraph;
unifiedGraph;
lastLoadedUnifiedGraph;
onSubgraphExecuteHooks;
onDelegationPlanHooks;
onDelegationStageExecuteHooks;
inContextSDK;
initialUnifiedGraph$;
polling$;
_transportEntryMap;
_transportExecutorStack;
lastLoadTime;
cleanup() {
this.unifiedGraph = undefined;
this.lastLoadedUnifiedGraph = undefined;
this.inContextSDK = undefined;
this.initialUnifiedGraph$ = undefined;
this.lastLoadTime = undefined;
this.polling$ = undefined;
}
ensureUnifiedGraph() {
if (this.polling$ == null && this.opts?.pollingInterval != null && this.lastLoadTime != null && Date.now() - this.lastLoadTime >= this.opts.pollingInterval) {
this.opts?.transportContext?.logger?.debug(`Polling Supergraph`);
this.polling$ = utils$1.mapMaybePromise(this.getAndSetUnifiedGraph(), () => {
this.polling$ = undefined;
});
}
if (!this.unifiedGraph) {
if (!this.initialUnifiedGraph$) {
this.opts?.transportContext?.logger?.debug(
"Fetching the initial Supergraph"
);
if (this.opts.transportContext?.cache) {
this.opts.transportContext?.logger?.debug(
`Searching for Supergraph in cache under key "${UNIFIEDGRAPH_CACHE_KEY}"...`
);
this.initialUnifiedGraph$ = utils$1.mapMaybePromise(
this.opts.transportContext.cache.get(UNIFIEDGRAPH_CACHE_KEY),
(cachedUnifiedGraph) => {
if (cachedUnifiedGraph) {
this.opts.transportContext?.logger?.debug(
"Found Supergraph in cache"
);
return this.handleLoadedUnifiedGraph(cachedUnifiedGraph, true);
}
return this.getAndSetUnifiedGraph();
},
() => {
return this.getAndSetUnifiedGraph();
}
);
} else {
this.initialUnifiedGraph$ = this.getAndSetUnifiedGraph();
}
this.initialUnifiedGraph$ = utils$1.mapMaybePromise(
this.initialUnifiedGraph$,
(v) => {
this.initialUnifiedGraph$ = undefined;
this.opts.transportContext?.logger?.debug(
"Initial Supergraph fetched"
);
return v;
}
);
}
return this.initialUnifiedGraph$ || this.unifiedGraph;
}
return this.unifiedGraph;
}
disposeReason;
handleLoadedUnifiedGraph(loadedUnifiedGraph, doNotCache) {
if (loadedUnifiedGraph != null && this.lastLoadedUnifiedGraph != null && compareSchemas(loadedUnifiedGraph, this.lastLoadedUnifiedGraph)) {
this.opts.transportContext?.logger?.debug(
"Supergraph has not been changed, skipping..."
);
this.lastLoadTime = Date.now();
if (!this.unifiedGraph) {
throw new Error(`This should not happen!`);
}
return this.unifiedGraph;
}
if (this.lastLoadedUnifiedGraph != null) {
this.disposeReason = utils$1.createGraphQLError(
"operation has been aborted due to a schema reload",
{
extensions: {
code: "SCHEMA_RELOAD",
http: {
status: 503
}
}
}
);
this.opts.transportContext?.logger?.debug(
"Supergraph has been changed, updating..."
);
}
if (!doNotCache && this.opts.transportContext?.cache) {
let serializedUnifiedGraph;
if (typeof loadedUnifiedGraph === "string") {
serializedUnifiedGraph = loadedUnifiedGraph;
} else if (graphql.isSchema(loadedUnifiedGraph)) {
serializedUnifiedGraph = utils$1.printSchemaWithDirectives(loadedUnifiedGraph);
} else if (utils$1.isDocumentNode(loadedUnifiedGraph)) {
serializedUnifiedGraph = graphql.print(loadedUnifiedGraph);
}
if (serializedUnifiedGraph != null) {
try {
const ttl = this.opts.pollingInterval ? this.opts.pollingInterval * 1e-3 : (
// if no polling interval (cache TTL) is configured, default to
// 60 seconds making sure the unifiedgraph is not kept forever
// NOTE: we default to 60s because Cloudflare KV TTL does not accept anything less
60
);
this.opts.transportContext.logger?.debug(
`Caching Supergraph with TTL ${ttl}s`
);
const logCacheSetError = (e) => {
this.opts.transportContext?.logger?.debug(
`Unable to store Supergraph in cache under key "${UNIFIEDGRAPH_CACHE_KEY}" with TTL ${ttl}s`,
e
);
};
try {
const cacheSet$ = this.opts.transportContext.cache.set(
UNIFIEDGRAPH_CACHE_KEY,
serializedUnifiedGraph,
{ ttl }
);
if (utils$1.isPromise(cacheSet$)) {
cacheSet$.then(() => {
}, logCacheSetError);
this._transportExecutorStack?.defer(() => cacheSet$);
}
} catch (e) {
logCacheSetError(e);
}
} catch (e) {
this.opts.transportContext.logger?.error(
"Failed to initiate caching of Supergraph",
e
);
}
}
}
return utils$1.mapMaybePromise(
this._transportExecutorStack?.disposeAsync?.(),
() => {
this.disposeReason = undefined;
this._transportExecutorStack = new disposablestack.AsyncDisposableStack();
this._transportExecutorStack.defer(() => {
this.cleanup();
});
this.lastLoadedUnifiedGraph = loadedUnifiedGraph;
this.unifiedGraph = ensureSchema(loadedUnifiedGraph);
const {
unifiedGraph: newUnifiedGraph,
transportEntryMap,
subschemas,
additionalResolvers
} = this.handleUnifiedGraph({
unifiedGraph: this.unifiedGraph,
additionalTypeDefs: this.opts.additionalTypeDefs,
additionalResolvers: this.opts.additionalResolvers,
onSubgraphExecute(subgraphName, execReq) {
return onSubgraphExecute(subgraphName, execReq);
},
onDelegationStageExecuteHooks: this.onDelegationStageExecuteHooks,
transportEntryAdditions: this.opts.transportEntryAdditions,
batch: this.batch,
logger: this.opts.transportContext?.logger
});
this.unifiedGraph = newUnifiedGraph;
const onSubgraphExecute = getOnSubgraphExecute({
onSubgraphExecuteHooks: this.onSubgraphExecuteHooks,
transports: this.opts.transports,
transportContext: this.opts.transportContext,
transportEntryMap,
getSubgraphSchema(subgraphName) {
const subgraph = subschemas.find(
(s) => s.name && compareSubgraphNames(s.name, subgraphName)
);
if (!subgraph) {
throw new Error(`Subgraph ${subgraphName} not found`);
}
return subgraph.schema;
},
transportExecutorStack: this._transportExecutorStack,
getDisposeReason: () => this.disposeReason
});
if (this.opts.additionalResolvers || additionalResolvers.length) {
this.inContextSDK = utils.getInContextSDK(
this.unifiedGraph,
// @ts-expect-error Legacy Mesh RawSource is not compatible with new Mesh
subschemas,
this.opts.transportContext?.logger,
this.opts.onDelegateHooks || []
);
}
this.lastLoadTime = Date.now();
this._transportEntryMap = transportEntryMap;
const stitchingInfo = this.unifiedGraph?.extensions?.["stitchingInfo"];
if (stitchingInfo && this.onDelegationPlanHooks?.length) {
for (const typeName in stitchingInfo.mergedTypes) {
const mergedTypeInfo = stitchingInfo.mergedTypes[typeName];
if (mergedTypeInfo) {
const originalDelegationPlanBuilder = mergedTypeInfo.nonMemoizedDelegationPlanBuilder;
mergedTypeInfo.nonMemoizedDelegationPlanBuilder = (supergraph, sourceSubschema, variables, fragments, fieldNodes, context, info) => {
let delegationPlanBuilder = originalDelegationPlanBuilder;
function setDelegationPlanBuilder(newDelegationPlanBuilder) {
delegationPlanBuilder = newDelegationPlanBuilder;
}
const onDelegationPlanDoneHooks = [];
let logger = this.opts.transportContext?.logger;
let requestId;
if (context?.request) {
requestId = utils.requestIdByRequest.get(context.request);
if (requestId) {
logger = logger?.child(requestId);
}
}
if (sourceSubschema.name) {
logger = logger?.child(sourceSubschema.name);
}
for (const onDelegationPlan of this.onDelegationPlanHooks) {
const onDelegationPlanDone = onDelegationPlan({
supergraph,
subgraph: sourceSubschema.name,
sourceSubschema,
typeName: mergedTypeInfo.typeName,
variables,
fragments,
fieldNodes,
logger,
context,
info,
delegationPlanBuilder,
setDelegationPlanBuilder
});
if (onDelegationPlanDone) {
onDelegationPlanDoneHooks.push(onDelegationPlanDone);
}
}
let delegationPlan = delegationPlanBuilder(
supergraph,
sourceSubschema,
variables,
fragments,
fieldNodes,
context,
info
);
function setDelegationPlan(newDelegationPlan) {
delegationPlan = newDelegationPlan;
}
for (const onDelegationPlanDone of onDelegationPlanDoneHooks) {
onDelegationPlanDone({
delegationPlan,
setDelegationPlan
});
}
return delegationPlan;
};
}
}
}
return this.unifiedGraph;
}
);
}
getAndSetUnifiedGraph() {
try {
return utils$1.mapMaybePromise(
this.opts.getUnifiedGraph(this.opts.transportContext || {}),
(loadedUnifiedGraph) => this.handleLoadedUnifiedGraph(loadedUnifiedGraph),
(err) => {
this.opts.transportContext?.logger?.error(
"Failed to load Supergraph",
err
);
this.lastLoadTime = Date.now();
if (!this.unifiedGraph) {
throw err;
}
return this.unifiedGraph;
}
);
} catch (e) {
this.opts.transportContext?.logger?.error("Failed to load Supergraph", e);
this.lastLoadTime = Date.now();
if (!this.unifiedGraph) {
throw e;
}
return this.unifiedGraph;
}
}
getUnifiedGraph() {
return utils$1.mapMaybePromise(this.ensureUnifiedGraph(), () => {
if (!this.unifiedGraph) {
throw new Error(`This should not happen!`);
}
return this.unifiedGraph;
});
}
getContext(base = {}) {
return utils$1.mapMaybePromise(this.ensureUnifiedGraph(), () => {
if (this.inContextSDK) {
Object.assign(base, this.inContextSDK);
}
Object.assign(base, this.opts.transportContext);
return base;
});
}
getTransportEntryMap() {
return utils$1.mapMaybePromise(t