UNPKG

@graphql-mesh/fusion-composition

Version:

Basic composition utility for Fusion spec

256 lines (255 loc) • 13.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createFederationTransform = createFederationTransform; const graphql_1 = require("graphql"); const utils_1 = require("@graphql-tools/utils"); const federation_utils_js_1 = require("../federation-utils.js"); const utils_js_1 = require("./utils.js"); const federationDirectiveNames = [ 'key', 'interfaceObject', 'extends', 'shareable', 'inaccessible', 'override', 'authenticated', 'requiresScopes', 'policy', 'external', 'provides', 'requires', ]; function createFederationTransform(config) { return function (subgraphSchema, subgraphConfig) { const configurationByType = new Map(); const configurationByField = new Map(); for (const coordinate in config) { const [typeName, fieldName] = coordinate.split('.'); const typeInSchema = subgraphSchema.getType(typeName); if (!typeInSchema) { throw new utils_js_1.TransformValidationError(`Federation Transform: Type ${typeName} not found in schema for the coordinate: ${coordinate}`); } if (fieldName) { if (!('getFields' in typeInSchema)) { throw new utils_js_1.TransformValidationError(`Federation Transform: Type ${typeName} is not an object type in schema for the coordinate: ${coordinate}`); } const fieldsInType = typeInSchema.getFields(); if (!fieldsInType[fieldName]) { throw new utils_js_1.TransformValidationError(`Federation Transform: Field ${fieldName} not found in type ${typeName} for the coordinate: ${coordinate}`); } const fieldConfig = config[coordinate]; let fieldMap = configurationByField.get(typeName); if (!fieldMap) { fieldMap = new Map(); configurationByField.set(typeName, fieldMap); } fieldMap.set(fieldName, fieldConfig); } else { configurationByType.set(typeName, config[coordinate]); } } const mergeDirectiveConfigMap = new Map(); const rootTypeNameOperationMap = new Map([...(0, utils_1.getRootTypeMap)(subgraphSchema)].map(([operationType, type]) => [ type.name, operationType, ])); const usedFederationDirectives = new Set(); let mergeDirectiveUsed = false; subgraphSchema = (0, utils_1.mapSchema)(subgraphSchema, { [utils_1.MapperKind.TYPE]: type => { const configByType = configurationByType.get(type.name); if (configByType) { const directiveExtensions = (0, utils_1.getDirectiveExtensions)(type) || {}; for (const directiveName in configByType) { const directiveConfigs = (0, utils_1.asArray)(configByType[directiveName]); for (const directiveConfig of directiveConfigs) { const specificDirectiveExtensions = (directiveExtensions[directiveName] ||= []); switch (directiveName) { case 'key': { const keyConfig = directiveConfig; specificDirectiveExtensions.push({ fields: keyConfig.fields, }); if (keyConfig.resolveReference) { const operation = keyConfig.resolveReference.operation || graphql_1.OperationTypeNode.QUERY; let operationMergeDirectiveConfig = mergeDirectiveConfigMap.get(operation); if (!operationMergeDirectiveConfig) { operationMergeDirectiveConfig = new Map(); mergeDirectiveConfigMap.set(operation, operationMergeDirectiveConfig); } operationMergeDirectiveConfig.set(keyConfig.resolveReference.fieldName, { keyField: keyConfig.fields, ...keyConfig.resolveReference, }); } break; } default: { if (directiveConfig) { specificDirectiveExtensions.push(directiveConfig === true ? {} : directiveConfig); } break; } } usedFederationDirectives.add(`@${directiveName}`); } } for (const directiveName of federationDirectiveNames) { if (directiveExtensions[directiveName]?.length && federationDirectiveNames.includes(directiveName)) { usedFederationDirectives.add(`@${directiveName}`); } } // Existing directives return new (Object.getPrototypeOf(type).constructor)({ ...type.toConfig(), astNode: undefined, extensions: { ...(type.extensions || {}), directives: directiveExtensions, }, }); } }, [utils_1.MapperKind.ROOT_FIELD]: (fieldConfig, fieldName, typeName) => { const operation = rootTypeNameOperationMap.get(typeName); if (!operation) { throw new Error(`Unexpected root field ${fieldName} in type ${typeName}`); } const mergeDirectiveConfigForOperation = mergeDirectiveConfigMap.get(operation); const mergeDirectiveConfig = mergeDirectiveConfigForOperation?.get(fieldName); const fieldDirectives = (0, utils_1.getDirectiveExtensions)(fieldConfig) || {}; const fieldTransformConfig = configurationByField.get(typeName)?.get(fieldName); if (fieldTransformConfig) { for (const directiveName in fieldTransformConfig) { const directiveConfigs = (0, utils_1.asArray)(fieldTransformConfig[directiveName]); const specificDirectiveExtensions = (fieldDirectives[directiveName] ||= []); for (let directiveConfig of directiveConfigs) { if (typeof directiveConfig === 'boolean') { directiveConfig = {}; } specificDirectiveExtensions.push(directiveConfig); usedFederationDirectives.add(`@${directiveName}`); } } } if (mergeDirectiveConfig) { const mergeDirectiveExtensions = (fieldDirectives.merge ||= []); let argsExpr = mergeDirectiveConfig.argsExpr; if (!argsExpr && fieldConfig.args && !mergeDirectiveConfig.keyArg) { const argsExprElems = []; const returnNamedType = (0, graphql_1.getNamedType)(fieldConfig.type); if ('getFields' in returnNamedType) { const returnFields = returnNamedType.getFields(); for (const argName in fieldConfig.args) { const arg = fieldConfig.args[argName]; const argType = (0, graphql_1.getNamedType)(arg.type); const returnField = returnFields[argName]; if (returnField) { const returnFieldType = (0, graphql_1.getNamedType)(returnField.type); if (argType.name === returnFieldType.name) { argsExprElems.push(`${argName}: $key.${argName}`); } } } argsExpr = argsExprElems.join(', '); } } mergeDirectiveExtensions.push({ subgraph: subgraphConfig.name, key: mergeDirectiveConfig.key, keyField: mergeDirectiveConfig.keyField, keyArg: mergeDirectiveConfig.keyArg, argsExpr, }); mergeDirectiveUsed = true; } for (const directiveName of federationDirectiveNames) { if (fieldDirectives[directiveName]?.length && federationDirectiveNames.includes(directiveName)) { usedFederationDirectives.add(`@${directiveName}`); } } if (fieldTransformConfig || mergeDirectiveConfig) { return { ...fieldConfig, astNode: undefined, extensions: { ...(fieldConfig.extensions || {}), directives: fieldDirectives, }, }; } }, [utils_1.MapperKind.FIELD]: (fieldConfig, fieldName, typeName) => { const fieldTransformConfig = configurationByField.get(typeName)?.get(fieldName); if (fieldTransformConfig) { const fieldDirectives = (0, utils_1.getDirectiveExtensions)(fieldConfig) || {}; for (const directiveName in fieldTransformConfig) { const directiveConfigs = (0, utils_1.asArray)(fieldTransformConfig[directiveName]); const specificDirectiveExtensions = (fieldDirectives[directiveName] ||= []); for (let directiveConfig of directiveConfigs) { if (typeof directiveConfig === 'boolean') { directiveConfig = {}; } specificDirectiveExtensions.push(directiveConfig); usedFederationDirectives.add(`@${directiveName}`); } } for (const directiveName of federationDirectiveNames) { if (fieldDirectives[directiveName]?.length && federationDirectiveNames.includes(directiveName)) { usedFederationDirectives.add(`@${directiveName}`); } } return { ...fieldConfig, astNode: undefined, extensions: { ...(fieldConfig.extensions || {}), directives: fieldDirectives, }, }; } }, }); subgraphSchema = (0, federation_utils_js_1.addFederation2DirectivesToSubgraph)(subgraphSchema); subgraphSchema = (0, federation_utils_js_1.importFederationDirectives)(subgraphSchema, [...usedFederationDirectives]); if (mergeDirectiveUsed) { subgraphSchema = (0, federation_utils_js_1.importMeshDirectives)(subgraphSchema, ['@merge']); const schemaConfig = subgraphSchema.toConfig(); subgraphSchema = new graphql_1.GraphQLSchema({ ...schemaConfig, directives: [ ...schemaConfig.directives, new graphql_1.GraphQLDirective({ name: 'merge', locations: [graphql_1.DirectiveLocation.FIELD_DEFINITION], args: { subgraph: { type: new graphql_1.GraphQLNonNull(graphql_1.GraphQLString), }, argsExpr: { type: graphql_1.GraphQLString, }, keyArg: { type: graphql_1.GraphQLString, }, keyField: { type: graphql_1.GraphQLString, }, key: { type: new graphql_1.GraphQLList(graphql_1.GraphQLString), }, additionalArgs: { type: graphql_1.GraphQLString, }, }, }), ], }); } return subgraphSchema; }; }