UNPKG

@graphql-mesh/fusion-runtime

Version:

Runtime for GraphQL Mesh Fusion Supergraph

190 lines (189 loc) • 9.32 kB
import { isEnumType, Kind, visit, } from 'graphql'; import { resolveAdditionalResolversWithoutImport } from '@graphql-mesh/utils'; import { getStitchedSchemaFromSupergraphSdl } from '@graphql-tools/federation'; import { mergeTypeDefs } from '@graphql-tools/merge'; import { stitchingDirectives } from '@graphql-tools/stitching-directives'; import { asArray, getDirectiveExtensions, getDocumentNodeFromSchema, MapperKind, mapSchema, memoize1, mergeDeep, } from '@graphql-tools/utils'; import { filterHiddenPartsInSchema } from '../filterHiddenPartsInSchema.js'; import { handleFederationSubschema } from './subgraph.js'; // Memoize to avoid re-parsing the same schema AST // Workaround for unsupported directives on composition: restore extra directives export const restoreExtraDirectives = memoize1(function restoreExtraDirectives(schema) { const queryType = schema.getQueryType(); 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(); // Cleanup extra directives on Query type return new TypeCtor({ ...typeConfig, extensions: { ...(type.extensions || {}), directives: { ...typeDirectiveExtensions, extraSchemaDefinitionDirective: [], }, }, // Cleanup ASTNode to prevent conflicts astNode: undefined, }); } }, }); if (extraSchemaDefinitionDirectives?.length) { const schemaDirectives = getDirectiveExtensions(schema); for (const { directives } of extraSchemaDefinitionDirectives) { for (const directiveName in directives) { schemaDirectives[directiveName] ||= []; schemaDirectives[directiveName].push(...directives[directiveName]); } } const schemaExtensions = (schema.extensions ||= {}); schemaExtensions.directives = schemaDirectives; } } return schema; }); export function getStitchingDirectivesTransformerForSubschema() { const { stitchingDirectivesTransformer } = stitchingDirectives({ keyDirectiveName: 'stitch__key', computedDirectiveName: 'stitch__computed', mergeDirectiveName: 'merge', canonicalDirectiveName: 'stitch__canonical', }); return stitchingDirectivesTransformer; } export const handleFederationSupergraph = function ({ unifiedGraph, onSubgraphExecute, additionalTypeDefs: additionalTypeDefsFromConfig = [], additionalResolvers: additionalResolversFromConfig = [], transportEntryAdditions, batch = true, }) { const additionalTypeDefs = [...asArray(additionalTypeDefsFromConfig)]; const additionalResolvers = [...asArray(additionalResolversFromConfig)]; const transportEntryMap = {}; let subschemas = []; const stitchingDirectivesTransformer = getStitchingDirectivesTransformerForSubschema(); unifiedGraph = restoreExtraDirectives(unifiedGraph); // Get Transport Information from Schema Directives const schemaDirectives = getDirectiveExtensions(unifiedGraph); // Workaround to get the real name of the subschema const realSubgraphNameMap = 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) { realSubgraphNameMap.set(enumValue.name, joinGraphDirective.name); } } } } 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, realSubgraphNameMap, schemaDirectives, transportEntryMap, additionalTypeDefs, stitchingDirectivesTransformer, onSubgraphExecute, }), batch, onStitchingOptions(opts) { subschemas = opts.subschemas; const mergedTypeDefs = mergeTypeDefs([opts.typeDefs, 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({ targetTypeName, targetFieldName, ...resolveToDirective, })); } } }, }); opts.typeDefs = mergedTypeDefs; opts.resolvers = additionalResolvers; }, 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 > 0) { 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 > 0) { return false; } return true; }), }; }, }); }, }); // Filter hidden elements with @hidden directive executableUnifiedGraph = filterHiddenPartsInSchema(executableUnifiedGraph); 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); } } return { unifiedGraph: executableUnifiedGraph, subschemas, transportEntryMap, additionalResolvers, }; };