UNPKG

@graphql-mesh/fusion-runtime

Version:

Runtime for GraphQL Mesh Fusion Supergraph

357 lines (356 loc) • 17.7 kB
"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, }, }; }, }); }