@graphql-mesh/fusion-runtime
Version:
Runtime for GraphQL Mesh Fusion Supergraph
357 lines (356 loc) • 17.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.handleFederationSubschema = handleFederationSubschema;
const graphql_1 = require("graphql");
const utils_1 = require("@graphql-tools/utils");
const wrap_1 = require("@graphql-tools/wrap");
const utils_js_1 = require("../utils.js");
function handleFederationSubschema({ subschemaConfig, realSubgraphNameMap, schemaDirectives, transportEntryMap, additionalTypeDefs, stitchingDirectivesTransformer, onSubgraphExecute, }) {
// Fix name
const subgraphName = (subschemaConfig.name =
realSubgraphNameMap?.get(subschemaConfig.name) || subschemaConfig.name);
const subgraphDirectives = (0, utils_1.getDirectiveExtensions)(subschemaConfig.schema);
for (const directiveName in schemaDirectives || subgraphDirectives) {
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 ||= []);
if (transportDirectives.length) {
transportEntryMap[subgraphName] = transportDirectives[0];
}
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 = (0, utils_1.mapSchema)(subschemaConfig.schema, {
[utils_1.MapperKind.TYPE]: type => {
const typeDirectives = (0, utils_1.getDirectiveExtensions)(type);
const sourceDirectives = typeDirectives.source;
const sourceDirective = sourceDirectives?.find(directive => (0, utils_js_1.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 = (0, utils_1.getDirectiveExtensions)(fieldConfig);
if (fieldDirectives.merge?.length) {
mergeDirectiveUsed = true;
}
const resolveToDirectives = fieldDirectives.resolveTo;
if (resolveToDirectives?.length > 0) {
const type = schema.getType(typeName);
if (!(0, graphql_1.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_1.Kind.DOCUMENT,
definitions: [
{
kind: graphql_1.Kind.OBJECT_TYPE_DEFINITION,
name: { kind: graphql_1.Kind.NAME, value: typeName },
fields: [(0, utils_1.astFromField)(field, schema)],
},
],
});
}
const additionalFieldDirectives = fieldDirectives.additionalField;
if (additionalFieldDirectives?.length > 0) {
return null;
}
const sourceDirectives = fieldDirectives.source;
const sourceDirective = sourceDirectives?.find(directive => (0, utils_js_1.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 > 0) {
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_1.HoistField(realTypeName, pathConfig, fieldName), new wrap_1.PruneSchema());
}
}
}
const newArgs = {};
if (fieldConfig.args) {
for (const argName in fieldConfig.args) {
const argConfig = fieldConfig.args[argName];
const argDirectives = (0, utils_1.getDirectiveExtensions)(argConfig);
const argSourceDirectives = argDirectives.source;
const argSourceDirective = argSourceDirectives?.find(directive => (0, utils_js_1.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 = (0, graphql_1.typeFromAST)(subschemaConfig.schema, fieldTypeNode);
if (!newType) {
throw new Error(`Type ${sourceDirective.type} for field ${typeName}.${fieldName} is not defined in the schema`);
}
if (!(0, graphql_1.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 = (0, utils_1.getDirectiveExtensions)(fieldConfig);
const sourceDirectives = fieldDirectives.source;
const sourceDirective = sourceDirectives?.find(directive => (0, utils_js_1.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 > 0) {
return null;
}
},
[utils_1.MapperKind.INTERFACE_FIELD]: (fieldConfig, fieldName, typeName) => {
const fieldDirectives = (0, utils_1.getDirectiveExtensions)(fieldConfig);
const sourceDirectives = fieldDirectives.source;
const sourceDirective = sourceDirectives?.find(directive => (0, utils_js_1.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];
}
const additionalFieldDirectives = fieldDirectives.additionalField;
if (additionalFieldDirectives?.length > 0) {
return null;
}
},
[utils_1.MapperKind.ENUM_VALUE]: (enumValueConfig, typeName, _schema, externalValue) => {
const enumDirectives = (0, utils_1.getDirectiveExtensions)(enumValueConfig);
const sourceDirectives = enumDirectives.source;
const sourceDirective = sourceDirectives?.find(directive => (0, utils_js_1.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,
},
];
}
},
});
if (Object.keys(renameTypeNames).length > 0) {
transforms.push(new wrap_1.RenameTypes(typeName => renameTypeNames[typeName] || typeName));
}
if (Object.keys(renameFieldByObjectTypeNames).length > 0) {
transforms.push(new wrap_1.RenameObjectFields((typeName, fieldName, _fieldConfig) => {
const realTypeName = renameTypeNamesReversed[typeName] ?? typeName;
return renameFieldByObjectTypeNames[realTypeName]?.[fieldName] ?? fieldName;
}));
}
if (Object.keys(renameFieldByInputTypeNames).length > 0) {
transforms.push(new wrap_1.RenameInputObjectFields((typeName, fieldName, _fieldConfig) => {
const realTypeName = renameTypeNamesReversed[typeName] ?? typeName;
return renameFieldByInputTypeNames[realTypeName]?.[fieldName] ?? fieldName;
}));
}
if (Object.keys(renameFieldByInterfaceTypeNames).length > 0) {
transforms.push(new wrap_1.RenameInterfaceFields((typeName, fieldName, _fieldConfig) => {
const realTypeName = renameTypeNamesReversed[typeName] ?? typeName;
return renameFieldByInterfaceTypeNames[realTypeName]?.[fieldName] ?? fieldName;
}));
}
if (Object.keys(renameEnumValueByEnumTypeNames).length > 0) {
transforms.push(new wrap_1.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_1.RenameObjectFieldArguments((typeName, fieldName, argName) => {
const realTypeName = renameTypeNamesReversed[typeName] ?? typeName;
const realFieldName = renameFieldByTypeNamesReversed[realTypeName]?.[fieldName] ?? fieldName;
return renameArgByFieldByTypeNames[realTypeName]?.[realFieldName]?.[argName] ?? argName;
}));
}
if (mergeDirectiveUsed) {
subschemaConfig.merge = {};
// Workaround because transformer needs the directive definition itself
const subgraphSchemaConfig = subschemaConfig.schema.toConfig();
subschemaConfig.schema = new graphql_1.GraphQLSchema({
...subgraphSchemaConfig,
directives: [...subgraphSchemaConfig.directives, mergeDirective],
assumeValid: true,
});
subschemaConfig.merge = stitchingDirectivesTransformer(subschemaConfig).merge;
const queryType = subschemaConfig.schema.getQueryType();
// Transformer doesn't respect transforms
if (transforms.length && subschemaConfig.merge) {
const mergeConfig = {};
for (const realTypeName in subschemaConfig.merge) {
const renamedTypeName = renameTypeNames[realTypeName] ?? realTypeName;
mergeConfig[renamedTypeName] = subschemaConfig.merge[realTypeName];
const realQueryFieldName = mergeConfig[renamedTypeName].fieldName;
if (realQueryFieldName) {
mergeConfig[renamedTypeName].fieldName =
renameFieldByObjectTypeNames[queryType.name]?.[realQueryFieldName] ??
realQueryFieldName;
}
mergeConfig[renamedTypeName].entryPoints = subschemaConfig.merge[realTypeName].entryPoints?.map(entryPoint => ({
...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_1.GraphQLDirective({
name: 'merge',
isRepeatable: true,
locations: [graphql_1.DirectiveLocation.FIELD],
args: {
subgraph: {
type: graphql_1.GraphQLString,
},
key: {
type: graphql_1.GraphQLString,
},
keyField: {
type: graphql_1.GraphQLString,
},
keyArg: {
type: graphql_1.GraphQLString,
},
argsExpr: {
type: graphql_1.GraphQLString,
},
},
});
function parseTypeNodeWithRenames(typeString, renameTypeNames) {
const typeNode = (0, graphql_1.parseType)(typeString);
return (0, graphql_1.visit)(typeNode, {
NamedType: node => {
const realName = renameTypeNames[node.name.value] ?? node.name.value;
return {
...node,
name: {
...node.name,
value: realName,
},
};
},
});
}