UNPKG

@graphql-mesh/transform-federation

Version:
267 lines (266 loc) • 12.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const dset_1 = require("dset"); const graphql_1 = require("graphql"); const types_js_1 = require("@apollo/subgraph/dist/types.js"); const string_interpolation_1 = require("@graphql-mesh/string-interpolation"); const utils_1 = require("@graphql-mesh/utils"); const utils_2 = require("@graphql-tools/utils"); const federationDirectives = [ 'link', 'key', 'interfaceObject', 'extends', 'shareable', 'inaccessible', 'override', 'external', 'provides', 'requires', 'tag', 'composeDirective', ]; class FederationTransform { constructor({ apiName, baseDir, config, importFn, }) { this.noWrap = true; this.apiName = apiName; this.config = config; this.baseDir = baseDir; this.importFn = importFn; } transformSchema(schema, rawSource) { rawSource.merge = {}; if (this.config?.types) { const queryType = schema.getQueryType(); const queryTypeFields = queryType.getFields(); for (const type of this.config.types) { rawSource.merge[type.name] = {}; const typeObj = schema.getType(type.name); typeObj.extensions = typeObj.extensions || {}; const typeDirectivesObj = (typeObj.extensions.directives = typeObj.extensions.directives || {}); if (type.config?.key) { typeDirectivesObj.key = type.config.key; } if (type.config?.shareable) { typeDirectivesObj.shareable = type.config.shareable; } if (type.config?.extends) { typeDirectivesObj.extends = type.config.extends; } const typeFieldObjs = typeObj.getFields(); if (type.config?.fields) { for (const field of type.config.fields) { const typeField = typeFieldObjs[field.name]; if (typeField) { typeField.extensions = typeField.extensions || {}; const directivesObj = (typeField.extensions.directives = typeField.extensions.directives || {}); Object.assign(directivesObj, field.config); } rawSource.merge[type.name].fields = rawSource.merge[type.name].fields || {}; rawSource.merge[type.name].fields[field.name] = rawSource.merge[type.name].fields[field.name] || {}; if (field.config.requires) { rawSource.merge[type.name].fields[field.name].computed = true; rawSource.merge[type.name].fields[field.name].selectionSet = `{ ${field.config.requires} }`; } } } // If a field is a key field, it should be GraphQLID if (type.config?.key) { let selectionSetContent = ''; for (const keyField of type.config.key) { selectionSetContent += '\n'; selectionSetContent += keyField.fields || ''; } if (selectionSetContent) { rawSource.merge[type.name].selectionSet = `{ ${selectionSetContent} }`; } } let resolveReference; if (type.config?.resolveReference) { const resolveReferenceConfig = type.config.resolveReference; if (typeof resolveReferenceConfig === 'string') { const fn$ = (0, utils_1.loadFromModuleExportExpression)(resolveReferenceConfig, { cwd: this.baseDir, defaultExportName: 'default', importFn: this.importFn, }); resolveReference = (...args) => fn$.then(fn => fn(...args)); } else if (typeof resolveReferenceConfig === 'function') { resolveReference = resolveReferenceConfig; } else { const queryField = queryTypeFields[resolveReferenceConfig.queryFieldName]; resolveReference = async (root, context, info) => { const args = {}; for (const argName in resolveReferenceConfig.args) { const argVal = string_interpolation_1.stringInterpolator.parse(resolveReferenceConfig.args[argName], { root, args, context, info, env: process.env, }); if (argVal) { (0, dset_1.dset)(args, argName, argVal); } } const result = await context[this.apiName].Query[queryField.name]({ root, args, context, info, }); return { ...root, ...result, }; }; } rawSource.merge[type.name].resolve = resolveReference; } } } const entityTypes = []; for (const typeName in rawSource.merge || {}) { const type = schema.getType(typeName); if ((0, graphql_1.isObjectType)(type)) { entityTypes.push(type); } (0, dset_1.dset)(type, 'extensions.apollo.subgraph.resolveReference', rawSource.merge[typeName].resolve); } const schemaWithFederationQueryType = (0, utils_2.mapSchema)(schema, { [utils_2.MapperKind.QUERY]: type => { const config = type.toConfig(); return new graphql_1.GraphQLObjectType({ ...config, fields: { ...config.fields, _entities: types_js_1.entitiesField, _service: { ...types_js_1.serviceField, resolve: (root, args, context, info) => ({ sdl: (0, utils_2.printSchemaWithDirectives)(info.schema), }), }, }, }); }, }); const schemaWithUnionType = (0, utils_2.mapSchema)(schemaWithFederationQueryType, { [utils_2.MapperKind.UNION_TYPE]: type => { if (type.name === types_js_1.EntityType.name) { return new graphql_1.GraphQLUnionType({ ...types_js_1.EntityType.toConfig(), types: entityTypes, }); } return type; }, }); schemaWithUnionType.extensions = schemaWithUnionType.extensions || {}; const directivesObj = (schemaWithUnionType.extensions.directives = schemaWithUnionType.extensions.directives || {}); const existingDirectives = schemaWithUnionType.getDirectives(); const filteredDirectives = existingDirectives.filter(directive => federationDirectives.includes(directive.name)); directivesObj.link = { url: 'https://specs.apollo.dev/federation/' + (this.config.version || 'v2.0'), import: filteredDirectives .filter(({ name }) => name !== 'link') .map(dirName => `@${dirName.name}`), }; if (existingDirectives.length === filteredDirectives.length) { return schemaWithUnionType; } return (0, utils_2.mapSchema)(schemaWithUnionType, { [utils_2.MapperKind.DIRECTIVE]: directive => { if (federationDirectives.includes(directive.name)) { return directive; } return null; }, [utils_2.MapperKind.OBJECT_TYPE]: type => { return new graphql_1.GraphQLObjectType({ ...type.toConfig(), astNode: type.astNode && { ...type.astNode, directives: type.astNode.directives?.filter(directive => federationDirectives.includes(directive.name.value)), }, extensions: { ...type.extensions, directives: Object.fromEntries(Object.entries(type.extensions?.directives || {}).filter(([key]) => federationDirectives.includes(key))), }, }); }, [utils_2.MapperKind.INTERFACE_TYPE]: type => { return new graphql_1.GraphQLInterfaceType({ ...type.toConfig(), astNode: type.astNode && { ...type.astNode, directives: type.astNode.directives?.filter(directive => federationDirectives.includes(directive.name.value)), }, extensions: { ...type.extensions, directives: Object.fromEntries(Object.entries(type.extensions?.directives || {}).filter(([key]) => federationDirectives.includes(key))), }, }); }, [utils_2.MapperKind.COMPOSITE_FIELD]: fieldConfig => { return { ...fieldConfig, astNode: fieldConfig.astNode && { ...fieldConfig.astNode, directives: fieldConfig.astNode.directives?.filter(directive => federationDirectives.includes(directive.name.value)), }, extensions: { ...fieldConfig.extensions, directives: Object.fromEntries(Object.entries(fieldConfig.extensions?.directives || {}).filter(([key]) => federationDirectives.includes(key))), }, }; }, [utils_2.MapperKind.SCALAR_TYPE]: type => { if ((0, graphql_1.isSpecifiedScalarType)(type)) { return type; } return new graphql_1.GraphQLScalarType({ ...type.toConfig(), astNode: type.astNode && { ...type.astNode, directives: type.astNode.directives?.filter(directive => federationDirectives.includes(directive.name.value)), }, extensions: { ...type.extensions, directives: Object.fromEntries(Object.entries(type.extensions?.directives || {}).filter(([key]) => federationDirectives.includes(key))), }, }); }, [utils_2.MapperKind.ENUM_TYPE]: type => new graphql_1.GraphQLEnumType({ ...type.toConfig(), astNode: type.astNode && { ...type.astNode, directives: type.astNode.directives?.filter(directive => federationDirectives.includes(directive.name.value)), }, extensions: { ...type.extensions, directives: Object.fromEntries(Object.entries(type.extensions?.directives || {}).filter(([key]) => federationDirectives.includes(key))), }, }), [utils_2.MapperKind.ENUM_VALUE]: enumValueConfig => ({ ...enumValueConfig, astNode: enumValueConfig.astNode && { ...enumValueConfig.astNode, directives: enumValueConfig.astNode.directives?.filter(directive => federationDirectives.includes(directive.name.value)), }, extensions: { ...enumValueConfig.extensions, directives: Object.fromEntries(Object.entries(enumValueConfig.extensions?.directives || {}).filter(([key]) => federationDirectives.includes(key))), }, }), }); } } exports.default = FederationTransform;