@graphql-mesh/fusion-runtime
Version:
Runtime for GraphQL Mesh Fusion Supergraph
1,376 lines (1,371 loc) • 59.6 kB
JavaScript
import { getInstrumented } from '@envelop/instrumentation';
import { defaultPrintFn, createDefaultExecutor } from '@graphql-mesh/transport-common';
import { resolveAdditionalResolversWithoutImport, getInContextSDK, requestIdByRequest, isDisposable, loggerForExecutionRequest, iterateAsync, dispose } from '@graphql-mesh/utils';
import { getBatchingExecutor } from '@graphql-tools/batch-execute';
import { getDirectiveExtensions, mapSchema, MapperKind, astFromField, memoize1, asArray, getDocumentNodeFromSchema, isAsyncIterable, isDocumentNode, printSchemaWithDirectives, mergeDeep, createGraphQLError } from '@graphql-tools/utils';
import { handleMaybePromise, mapAsyncIterator, isPromise } from '@whatwg-node/promise-helpers';
import { isInterfaceType, Kind, isObjectType, typeFromAST, isOutputType, GraphQLSchema, GraphQLDirective, parseType, visit, GraphQLString, DirectiveLocation, isEnumType, isSchema, buildSchema, buildASTSchema, print } from 'graphql';
import { getStitchedSchemaFromSupergraphSdl } from '@graphql-tools/federation';
import { mergeTypeDefs } from '@graphql-tools/merge';
import { createMergedTypeResolver } from '@graphql-tools/stitch';
import { stitchingDirectives } from '@graphql-tools/stitching-directives';
import { HoistField, PruneSchema, RenameTypes, RenameObjectFields, RenameInputObjectFields, RenameInterfaceFields, TransformEnumValues, RenameObjectFieldArguments } from '@graphql-tools/wrap';
import { AsyncDisposableStack, DisposableSymbols } from '@whatwg-node/disposablestack';
// Regexps involved with splitting words in various case formats.
const SPLIT_LOWER_UPPER_RE = /([\p{Ll}\d])(\p{Lu})/gu;
const SPLIT_UPPER_UPPER_RE = /(\p{Lu})([\p{Lu}][\p{Ll}])/gu;
// Used to iterate over the initial split result and separate numbers.
const SPLIT_SEPARATE_NUMBER_RE = /(\d)\p{Ll}|(\p{L})\d/u;
// Regexp involved with stripping non-word characters from the result.
const DEFAULT_STRIP_REGEXP = /[^\p{L}\d]+/giu;
// The replacement value for splits.
const SPLIT_REPLACE_VALUE = "$1\0$2";
// The default characters to keep after transforming case.
const DEFAULT_PREFIX_SUFFIX_CHARACTERS = "";
/**
* Split any cased input strings into an array of words.
*/
function split(value) {
let result = value.trim();
result = result
.replace(SPLIT_LOWER_UPPER_RE, SPLIT_REPLACE_VALUE)
.replace(SPLIT_UPPER_UPPER_RE, SPLIT_REPLACE_VALUE);
result = result.replace(DEFAULT_STRIP_REGEXP, "\0");
let start = 0;
let end = result.length;
// Trim the delimiter from around the output string.
while (result.charAt(start) === "\0")
start++;
if (start === end)
return [];
while (result.charAt(end - 1) === "\0")
end--;
return result.slice(start, end).split(/\0/g);
}
/**
* Split the input string into an array of words, separating numbers.
*/
function splitSeparateNumbers(value) {
const words = split(value);
for (let i = 0; i < words.length; i++) {
const word = words[i];
const match = SPLIT_SEPARATE_NUMBER_RE.exec(word);
if (match) {
const offset = match.index + (match[1] ?? match[2]).length;
words.splice(i, 1, word.slice(0, offset), word.slice(offset));
}
}
return words;
}
/**
* Convert a string to constant case (`FOO_BAR`).
*/
function constantCase(input, options) {
const [prefix, words, suffix] = splitPrefixSuffix(input, options);
return (prefix +
words.map(upperFactory(options?.locale)).join("_") +
suffix);
}
function upperFactory(locale) {
return locale === false
? (input) => input.toUpperCase()
: (input) => input.toLocaleUpperCase(locale);
}
function splitPrefixSuffix(input, options = {}) {
const splitFn = options.split ?? (options.separateNumbers ? splitSeparateNumbers : split);
const prefixCharacters = options.prefixCharacters ?? DEFAULT_PREFIX_SUFFIX_CHARACTERS;
const suffixCharacters = options.suffixCharacters ?? DEFAULT_PREFIX_SUFFIX_CHARACTERS;
let prefixIndex = 0;
let suffixIndex = input.length;
while (prefixIndex < input.length) {
const char = input.charAt(prefixIndex);
if (!prefixCharacters.includes(char))
break;
prefixIndex++;
}
while (suffixIndex > prefixIndex) {
const index = suffixIndex - 1;
const char = input.charAt(index);
if (!suffixCharacters.includes(char))
break;
suffixIndex = index;
}
return [
input.slice(0, prefixIndex),
splitFn(input.slice(prefixIndex, suffixIndex)),
input.slice(suffixIndex),
];
}
function handleFederationSubschema({
subschemaConfig,
unifiedGraphDirectives,
realSubgraphNameMap,
additionalTypeDefs,
stitchingDirectivesTransformer,
onSubgraphExecute
}) {
const subgraphName = (subschemaConfig.name = realSubgraphNameMap?.get(subschemaConfig.name || "") || subschemaConfig.name) || "";
const subgraphDirectives = getDirectiveExtensions(subschemaConfig.schema);
const directivesToLook = unifiedGraphDirectives || subgraphDirectives;
for (const directiveName in directivesToLook) {
if (!subgraphDirectives[directiveName]?.length && unifiedGraphDirectives?.[directiveName]?.length) {
const directives = unifiedGraphDirectives[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 renameTypeNames = {};
const renameTypeNamesReversed = {};
const renameFieldByObjectTypeNames = {};
const renameFieldByInputTypeNames = {};
const renameFieldByInterfaceTypeNames = {};
const renameEnumValueByEnumTypeNames = {};
const renameFieldByTypeNamesReversed = {};
const renameArgByFieldByTypeNames = {};
const transforms = subschemaConfig.transforms ||= [];
let mergeDirectiveUsed = false;
subschemaConfig.schema = mapSchema(subschemaConfig.schema, {
[MapperKind.TYPE]: (type) => {
const typeDirectives = 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
});
}
}
},
[MapperKind.OBJECT_FIELD]: (fieldConfig, fieldName, typeName, schema) => {
const fieldDirectives = getDirectiveExtensions(fieldConfig);
if (fieldDirectives.merge?.length) {
mergeDirectiveUsed = true;
}
const resolveToDirectives = fieldDirectives.resolveTo;
if (resolveToDirectives?.length) {
const type = schema.getType(typeName);
if (!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: Kind.DOCUMENT,
definitions: [
{
kind: Kind.OBJECT_TYPE_DEFINITION,
name: { kind: Kind.NAME, value: typeName },
fields: [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 HoistField(realTypeName, pathConfig, fieldName),
new PruneSchema()
);
}
}
}
const newArgs = {};
if (fieldConfig.args) {
for (const argName in fieldConfig.args) {
const argConfig = fieldConfig.args[argName];
const argDirectives = 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 = typeFromAST(subschemaConfig.schema, fieldTypeNode);
if (!newType) {
throw new Error(
`Type ${sourceDirective.type} for field ${typeName}.${fieldName} is not defined in the schema`
);
}
if (!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
}
];
},
[MapperKind.INPUT_OBJECT_FIELD]: (fieldConfig, fieldName, typeName) => {
const fieldDirectives = 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 void 0;
},
[MapperKind.INTERFACE_FIELD]: (fieldConfig, fieldName, typeName, schema) => {
const fieldDirectives = getDirectiveExtensions(fieldConfig);
const resolveToDirectives = fieldDirectives.resolveTo;
if (resolveToDirectives?.length) {
const type = schema.getType(typeName);
if (!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: Kind.DOCUMENT,
definitions: [
{
kind: Kind.INTERFACE_TYPE_DEFINITION,
name: { kind: Kind.NAME, value: typeName },
fields: [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 void 0;
},
[MapperKind.ENUM_VALUE]: (enumValueConfig, typeName, _schema, externalValue) => {
const enumDirectives = 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 void 0;
}
});
if (Object.keys(renameTypeNames).length > 0) {
transforms.push(
new RenameTypes((typeName) => renameTypeNames[typeName] || typeName)
);
}
if (Object.keys(renameFieldByObjectTypeNames).length > 0) {
transforms.push(
new RenameObjectFields((typeName, fieldName, _fieldConfig) => {
const realTypeName = renameTypeNamesReversed[typeName] ?? typeName;
return renameFieldByObjectTypeNames[realTypeName]?.[fieldName] ?? fieldName;
})
);
}
if (Object.keys(renameFieldByInputTypeNames).length > 0) {
transforms.push(
new RenameInputObjectFields((typeName, fieldName, _fieldConfig) => {
const realTypeName = renameTypeNamesReversed[typeName] ?? typeName;
return renameFieldByInputTypeNames[realTypeName]?.[fieldName] ?? fieldName;
})
);
}
if (Object.keys(renameFieldByInterfaceTypeNames).length > 0) {
transforms.push(
new RenameInterfaceFields((typeName, fieldName, _fieldConfig) => {
const realTypeName = renameTypeNamesReversed[typeName] ?? typeName;
return renameFieldByInterfaceTypeNames[realTypeName]?.[fieldName] ?? fieldName;
})
);
}
if (Object.keys(renameEnumValueByEnumTypeNames).length > 0) {
transforms.push(
new 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 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 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 GraphQLDirective({
name: "merge",
isRepeatable: true,
locations: [DirectiveLocation.FIELD],
args: {
subgraph: {
type: GraphQLString
},
key: {
type: GraphQLString
},
keyField: {
type: GraphQLString
},
keyArg: {
type: GraphQLString
},
argsExpr: {
type: GraphQLString
}
}
});
function parseTypeNodeWithRenames(typeString, renameTypeNames) {
const typeNode = parseType(typeString);
return visit(typeNode, {
NamedType: (node) => {
const realName = renameTypeNames[node.name.value] ?? node.name.value;
return {
...node,
name: {
...node.name,
value: realName
}
};
}
});
}
const restoreExtraDirectives = memoize1(function restoreExtraDirectives2(schema) {
const queryType = schema.getQueryType();
if (!queryType) {
throw new Error("Query type is required");
}
const queryTypeExtensions = getDirectiveExtensions(queryType);
const extraSchemaDefinitionDirectives = queryTypeExtensions?.extraSchemaDefinitionDirective;
if (extraSchemaDefinitionDirectives?.length) {
schema = mapSchema(schema, {
[MapperKind.TYPE]: (type) => {
const typeDirectiveExtensions = 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: void 0
});
}
}
});
if (extraSchemaDefinitionDirectives?.length) {
const schemaDirectives = 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({
keyDirectiveName: "stitch__key",
computedDirectiveName: "stitch__computed",
mergeDirectiveName: "merge",
canonicalDirectiveName: "stitch__canonical"
});
return stitchingDirectivesTransformer;
}
function handleResolveToDirectives(typeDefsOpt, additionalTypeDefs, additionalResolvers) {
const mergedTypeDefs = mergeTypeDefs([typeDefsOpt, additionalTypeDefs]);
visit(mergedTypeDefs, {
[Kind.FIELD_DEFINITION](field, _key, _parent, _path, ancestors) {
const fieldDirectives = 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(
resolveAdditionalResolversWithoutImport({
...resolveToDirective,
targetTypeName,
targetFieldName
})
);
}
}
}
});
return mergedTypeDefs;
}
const handleFederationSupergraph = function({
unifiedGraph,
onSubgraphExecute,
onDelegationPlanHooks,
onDelegationStageExecuteHooks,
onDelegateHooks,
additionalTypeDefs: additionalTypeDefsFromConfig = [],
additionalResolvers: additionalResolversFromConfig = [],
logger
}) {
const additionalTypeDefs = [...asArray(additionalTypeDefsFromConfig)];
const additionalResolvers = [...asArray(additionalResolversFromConfig)];
let subschemas = [];
const stitchingDirectivesTransformer = getStitchingDirectivesTransformerForSubschema();
const realSubgraphNameMap = /* @__PURE__ */ new Map();
const joinGraphType = unifiedGraph.getType("join__Graph");
if (isEnumType(joinGraphType)) {
for (const enumValue of joinGraphType.getValues()) {
const enumValueDirectives = getDirectiveExtensions(enumValue);
const joinGraphDirectives = enumValueDirectives?.join__graph;
if (joinGraphDirectives?.length) {
for (const joinGraphDirective of joinGraphDirectives) {
if (joinGraphDirective) {
realSubgraphNameMap.set(enumValue.name, joinGraphDirective.name);
}
}
}
}
}
const unifiedGraphDirectives = getDirectiveExtensions(unifiedGraph);
let executableUnifiedGraph = getStitchedSchemaFromSupergraphSdl({
supergraphSdl: 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,
unifiedGraphDirectives,
realSubgraphNameMap,
additionalTypeDefs,
stitchingDirectivesTransformer,
onSubgraphExecute
}),
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 = createMergedTypeResolver(
mergedTypeConfig,
typeName
);
if (originalResolver) {
mergedTypeConfig.resolve = wrapMergedTypeResolver(
originalResolver,
typeName,
onDelegationStageExecuteHooks,
logger
);
}
}
}
}
}
}
},
onSubgraphAST(_name, subgraphAST) {
return visit(subgraphAST, {
[Kind.OBJECT_TYPE_DEFINITION](node) {
const typeName = node.name.value;
return {
...node,
fields: node.fields?.filter((fieldNode) => {
const fieldDirectives = getDirectiveExtensions({ astNode: fieldNode });
const resolveToDirectives = fieldDirectives.resolveTo;
if (resolveToDirectives?.length) {
additionalTypeDefs.push({
kind: Kind.DOCUMENT,
definitions: [
{
kind: Kind.OBJECT_TYPE_DEFINITION,
name: { kind: Kind.NAME, value: typeName },
fields: [fieldNode]
}
]
});
}
const additionalFieldDirectives = fieldDirectives.additionalField;
if (additionalFieldDirectives?.length) {
return false;
}
return true;
})
};
}
});
}
});
const inContextSDK = getInContextSDK(
executableUnifiedGraph,
// @ts-expect-error Legacy Mesh RawSource is not compatible with new Mesh
subschemas,
logger,
onDelegateHooks || []
);
const stitchingInfo = executableUnifiedGraph.extensions?.["stitchingInfo"];
if (stitchingInfo && 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 currentLogger = logger;
let requestId;
if (context?.request) {
requestId = requestIdByRequest.get(context.request);
if (requestId) {
currentLogger = currentLogger?.child({ requestId });
}
}
if (sourceSubschema.name) {
currentLogger = currentLogger?.child({
subgraph: sourceSubschema.name
});
}
for (const onDelegationPlan of onDelegationPlanHooks) {
const onDelegationPlanDone = onDelegationPlan({
supergraph,
subgraph: sourceSubschema.name,
sourceSubschema,
typeName: mergedTypeInfo.typeName,
variables,
fragments,
fieldNodes,
logger: currentLogger,
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 {
unifiedGraph: executableUnifiedGraph,
inContextSDK,
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;
}
};
};
function defaultTransportsGetter(kind) {
const moduleName = `@graphql-mesh/transport-${kind}`;
return handleMaybePromise(
() => import(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({ subgraph: subgraphName });
}
logger?.debug(`Loading transport "${kind}"`);
}
return handleMaybePromise(
() => 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,
batch = true,
instrumentation
}) {
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 = requestIdByRequest.get(
executionRequest.context?.request
);
if (requestId) {
logger = logger.child({ requestId });
}
if (subgraphName) {
logger = logger.child({ subgraph: subgraphName });
}
logger.debug(`Initializing executor`);
}
executor = function lazyExecutor(subgraphExecReq) {
return handleMaybePromise(
() => (
// 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 (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);
}
if (batch) {
executor = getBatchingExecutor(
executionRequest.context || subgraphExecutorMap,
executor
);
}
const originalExecutor = executor;
executor = (executionRequest2) => {
const subgraphInstrumentation = instrumentation()?.subgraphExecute;
return getInstrumented({ executionRequest: executionRequest2 }).asyncFn(
subgraphInstrumentation,
originalExecutor
)(executionRequest2);
};
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 && requestIdByRequest.get(baseExecutionRequest.context.request);
let execReqLogger = transportContext?.logger;
if (execReqLogger) {
if (requestId) {
execReqLogger = execReqLogger.child({ requestId });
}
loggerForExecutionRequest.set(baseExecutionRequest, execReqLogger);
}
execReqLogger = execReqLogger?.child?.({ subgraph: subgraphName });
if (onSubgraphExecuteHooks.length === 0) {
return baseExecutor(baseExecutionRequest);
}
let executor = baseExecutor;
let executionRequest = baseExecutionRequest;
const onSubgraphExecuteDoneHooks = [];
return handleMaybePromise(
() => 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 handleMaybePromise(
() => executor(executionRequest),
(currentResult) => {
const executeDoneResults = [];
return handleMaybePromise(
() => 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,
(currentResult2) => handleMaybePromise(
() => iterateAsync(
onNextHooks,
(onNext) => onNext({
result: currentResult2,
setResult: (res) => {
execReqLogger?.debug(
"overriding result with: ",
res
);
currentResult2 = res;
}
})
),
() => currentResult2
),
void 0,
() => onEndHooks.length === 0 ? void 0 : iterateAsync(onEndHooks, (onEnd) => onEnd())
);
}
);
}
);
}
);
};
}
function compareSchemas(a, b) {
let aStr;
if (typeof a === "string") {
aStr = a;
} else if (isDocumentNode(a)) {
aStr = defaultPrintFn(a);
} else {
aStr = printSchemaWithDirectives(a);
}
let bStr;
if (typeof b === "string") {
bStr = b;
} else if (isDocumentNode(b)) {
bStr = defaultPrintFn(b);
} else {
bStr = printSchemaWithDirectives(b);
}
return aStr === bStr;
}
function compareSubgraphNames(name1, name2) {
return constantCase(name1) === 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 = requestIdByRequest.get(context["request"]);
if (requestId) {
logger = logger.child({ requestId });
}
}
if (subschema.name) {
logger = logger?.child({ subgraph: 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 handleMaybePromise(
() => 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 getTransportEntryMapUsingFusionAndFederationDirectives(unifiedGraph, transportEntryAdditions) {
unifiedGraph = restoreExtraDirectives(unifiedGraph);
const transportEntryMap = {};
const joinGraph = unifiedGraph.getType("join__Graph");
const schemaDirectives = getDirectiveExtensions(unifiedGraph);
if (isEnumType(joinGraph)) {
for (const enumValue of joinGraph.getValues()) {
const enumValueDirectives = getDirectiveExtensions(enumValue);
if (enumValueDirectives?.join__graph?.length) {
for (const joinGraphDirective of enumValueDirectives.join__graph) {
if (joinGraphDirective.url) {
transportEntryMap[joinGraphDirective.name] = {
subgraph: joinGraphDirective.name,
kind: "http",
location: joinGraphDirective.url
};
}
}
}
}
}
if (schemaDirectives?.transport?.length) {
for (const transportDirective of schemaDirectives.transport) {
transportEntryMap[transportDirective.subgraph] = transportDirective;
}
}
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] = mergeDeep(toBeMerged);
}
}
const schemaExtensions = unifiedGraph.extensions ||= {};
const directivesInExtensions = schemaExtensions.directives ||= {};
const transportEntriesInExtensions = directivesInExtensions.transport = [];
for (const subgraphName in transportEntryMap) {
const transportEntry = transportEntryMap[subgraphName];
if (transportEntry) {
transportEntriesInExtensions.push(transportEntry);
}
}
return transportEntryMap;
}
function ensureSchema(source) {
if (isSchema(source)) {
return source;
}
if (typeof source === "string") {
return buildSchema(source, { assumeValid: true, assumeValidSDL: true });
}
if (isDocumentNode(source)) {
return 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.instrumentation = opts.instrumentation ?? (() => void 0);
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;
executor;
instrumentation;
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$ = handleMaybePromise(
() => this.getAndSetUnifiedGraph(),
() => {
this.polling$ = void 0;
},
(err) => {
this.opts.transportContext?.logger?.error(
"Failed to poll Supergraph",
err
);
this.polling$ = void 0;
}
);
}
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$ = handleMaybePromise(
() => 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$ = handleMaybePromise(
() => this.initialUnifiedGraph$,
(v) => {
this.initialUnifiedGraph$ = void 0;
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 (!doNotCache && this.opts.transportContext?.cache) {
let serializedUnifiedGraph;
if (typeof loadedUnifiedGraph === "string") {
serializedUnifiedGraph = loadedUnifiedGraph;
} else if (isSchema(loadedUnifiedGraph)) {
serializedUnifiedGraph = printSchemaWithDirectives(loadedUnifiedGraph);
} else if (isDocumentNode(loadedUnifiedGraph)) {
serializedUnifiedGraph = print(loadedUnifiedGraph);
}
if (serializedUnifiedGraph != null) {
try {
const ttl = this.opts.pollingInterval ? this.opts.pollingInterval * 1e-3 : (
// if no polling interval (cache TTL) is confi