UNPKG

@graphql-mesh/fusion-composition

Version:

Basic composition utility for Fusion spec

253 lines (252 loc) • 13.2 kB
import { DirectiveLocation, getNamedType, GraphQLDirective, GraphQLList, GraphQLNonNull, GraphQLSchema, GraphQLString, OperationTypeNode, } from 'graphql'; import { asArray, getDirectiveExtensions, getRootTypeMap, MapperKind, mapSchema, } from '@graphql-tools/utils'; import { addFederation2DirectivesToSubgraph, importFederationDirectives, importMeshDirectives, } from '../federation-utils.js'; import { TransformValidationError } from './utils.js'; const federationDirectiveNames = [ 'key', 'interfaceObject', 'extends', 'shareable', 'inaccessible', 'override', 'authenticated', 'requiresScopes', 'policy', 'external', 'provides', 'requires', ]; export 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 TransformValidationError(`Federation Transform: Type ${typeName} not found in schema for the coordinate: ${coordinate}`); } if (fieldName) { if (!('getFields' in typeInSchema)) { throw new 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 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([...getRootTypeMap(subgraphSchema)].map(([operationType, type]) => [ type.name, operationType, ])); const usedFederationDirectives = new Set(); let mergeDirectiveUsed = false; subgraphSchema = mapSchema(subgraphSchema, { [MapperKind.TYPE]: type => { const configByType = configurationByType.get(type.name); if (configByType) { const directiveExtensions = getDirectiveExtensions(type) || {}; for (const directiveName in configByType) { const directiveConfigs = 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 || 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, }, }); } }, [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 = getDirectiveExtensions(fieldConfig) || {}; const fieldTransformConfig = configurationByField.get(typeName)?.get(fieldName); if (fieldTransformConfig) { for (const directiveName in fieldTransformConfig) { const directiveConfigs = 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 = getNamedType(fieldConfig.type); if ('getFields' in returnNamedType) { const returnFields = returnNamedType.getFields(); for (const argName in fieldConfig.args) { const arg = fieldConfig.args[argName]; const argType = getNamedType(arg.type); const returnField = returnFields[argName]; if (returnField) { const returnFieldType = 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, }, }; } }, [MapperKind.FIELD]: (fieldConfig, fieldName, typeName) => { const fieldTransformConfig = configurationByField.get(typeName)?.get(fieldName); if (fieldTransformConfig) { const fieldDirectives = getDirectiveExtensions(fieldConfig) || {}; for (const directiveName in fieldTransformConfig) { const directiveConfigs = 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 = addFederation2DirectivesToSubgraph(subgraphSchema); subgraphSchema = importFederationDirectives(subgraphSchema, [...usedFederationDirectives]); if (mergeDirectiveUsed) { subgraphSchema = importMeshDirectives(subgraphSchema, ['@merge']); const schemaConfig = subgraphSchema.toConfig(); subgraphSchema = new GraphQLSchema({ ...schemaConfig, directives: [ ...schemaConfig.directives, new GraphQLDirective({ name: 'merge', locations: [DirectiveLocation.FIELD_DEFINITION], args: { subgraph: { type: new GraphQLNonNull(GraphQLString), }, argsExpr: { type: GraphQLString, }, keyArg: { type: GraphQLString, }, keyField: { type: GraphQLString, }, key: { type: new GraphQLList(GraphQLString), }, additionalArgs: { type: GraphQLString, }, }, }), ], }); } return subgraphSchema; }; }