UNPKG

@graphql-mesh/fusion-runtime

Version:

Runtime for GraphQL Mesh Fusion Supergraph

1,374 lines (1,369 loc) • 53.6 kB
'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