UNPKG

@graphql-mesh/fusion-composition

Version:

Basic composition utility for Fusion spec

177 lines (176 loc) • 8.52 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.resolveToDirective = exports.resolveToSourceArgsScalar = void 0; exports.createEncapsulateTransform = createEncapsulateTransform; const graphql_1 = require("graphql"); const utils_1 = require("@graphql-tools/utils"); const federation_utils_js_1 = require("../federation-utils.js"); const filter_schema_js_1 = require("./filter-schema.js"); const OPERATION_TYPE_SUFFIX_MAP = { query: 'Query', mutation: 'Mutation', subscription: 'Subscription', }; const DEFAULT_APPLY_TO = { query: true, mutation: true, subscription: true, }; function createEncapsulateTransform(opts = {}) { return function encapsulateTransform(schema, subgraphConfig) { const groupName = opts.name || subgraphConfig.name; const applyToMap = { ...DEFAULT_APPLY_TO, ...(opts.applyTo || {}), }; const newRootTypes = {}; let inaccessibleDirectiveAdded = false; for (const opTypeString in applyToMap) { const operationType = opTypeString; const originalType = schema.getRootType(operationType); if (originalType && applyToMap[operationType]) { const originalTypeConfig = originalType.toConfig(); const wrappedTypeName = `${groupName}${OPERATION_TYPE_SUFFIX_MAP[operationType]}`; const originalFieldMapWithHidden = {}; const wrappedFieldMap = {}; for (const fieldName in originalTypeConfig.fields) { const originalFieldConfig = originalTypeConfig.fields[fieldName]; // Already-hidden copies from a previous encapsulate stay on the root type. if (fieldName.startsWith('_encapsulated_')) { originalFieldMapWithHidden[fieldName] = originalFieldConfig; continue; } const originalDirectives = (0, utils_1.getDirectiveExtensions)(originalFieldConfig) || {}; // Nested encapsulate: keep existing namespace fields as-is under the new group // instead of re-pointing them at an SDK field that does not exist (#4962). if (originalDirectives.resolveTo?.[0]?.sourceFieldName === '__typename') { wrappedFieldMap[fieldName] = originalFieldConfig; continue; } // Generate sourceArgs to forward all arguments const sourceArgs = {}; if (originalFieldConfig.args) { for (const argName in originalFieldConfig.args) { sourceArgs[argName] = `{args.${argName}}`; } } const wrappedFieldName = `_encapsulated_${groupName}_${fieldName}`; wrappedFieldMap[fieldName] = { ...originalFieldConfig, extensions: { ...originalFieldConfig.extensions, directives: { ...originalDirectives, resolveTo: [ { sourceName: subgraphConfig.name, sourceTypeName: originalType.name, sourceFieldName: wrappedFieldName, ...(Object.keys(sourceArgs).length > 0 && { sourceArgs }), }, ], }, }, }; const newOriginalFieldConfig = { ...originalFieldConfig, astNode: undefined, }; (0, filter_schema_js_1.addInaccessibleDirective)(newOriginalFieldConfig); inaccessibleDirectiveAdded = true; originalFieldMapWithHidden[wrappedFieldName] = newOriginalFieldConfig; } const wrappedType = new graphql_1.GraphQLObjectType({ name: wrappedTypeName, fields: wrappedFieldMap, }); newRootTypes[operationType] = new graphql_1.GraphQLObjectType({ ...originalTypeConfig, fields: { ...originalFieldMapWithHidden, [groupName]: { type: new graphql_1.GraphQLNonNull(wrappedType), extensions: { directives: { resolveTo: [ { sourceName: subgraphConfig.name, sourceTypeName: originalType.name, sourceFieldName: '__typename', }, ], }, }, }, }, }); } else { newRootTypes[operationType] = originalType; } } const schemaConfig = schema.toConfig(); const newDirectives = [...schemaConfig.directives]; if (!newDirectives.some(directive => directive.name === 'resolveTo')) { newDirectives.push(exports.resolveToDirective); } // `types: undefined` drops types that are not reachable as field return // types, including interface implementors (#8382). Keep only those extra // types so unused scalars (e.g. transport options) are not re-introduced. const reachableTypeMap = new graphql_1.GraphQLSchema({ ...schemaConfig, types: undefined, }).getTypeMap(); const typesToKeep = schemaConfig.types.filter(type => { if (type === schemaConfig.query || type === schemaConfig.mutation || type === schemaConfig.subscription || type.name.startsWith('__')) { return false; } if (reachableTypeMap[type.name]) { return false; } if ((0, graphql_1.isObjectType)(type) || (0, graphql_1.isInterfaceType)(type)) { return type.getInterfaces().length > 0; } return (0, graphql_1.isUnionType)(type); }); const newSchema = new graphql_1.GraphQLSchema({ ...schemaConfig, types: typesToKeep, directives: newDirectives, ...newRootTypes, }); const schemaLevelDirectives = (0, utils_1.getDirectiveExtensions)(newSchema); const importStatement = schemaLevelDirectives?.link?.find(linkDirectiveArgs => linkDirectiveArgs.url?.startsWith('https://specs.apollo.dev/federation/') && linkDirectiveArgs.import); if (importStatement && inaccessibleDirectiveAdded) { return (0, federation_utils_js_1.importFederationDirectives)(newSchema, ['@inaccessible']); } return newSchema; }; } exports.resolveToSourceArgsScalar = new graphql_1.GraphQLScalarType({ name: 'ResolveToSourceArgs', }); exports.resolveToDirective = new graphql_1.GraphQLDirective({ name: 'resolveTo', locations: [graphql_1.DirectiveLocation.FIELD_DEFINITION], args: { additionalArgs: { type: exports.resolveToSourceArgsScalar }, filterBy: { type: graphql_1.GraphQLString }, keyField: { type: graphql_1.GraphQLString }, keysArg: { type: graphql_1.GraphQLString }, pubsubTopic: { type: graphql_1.GraphQLString }, requiredSelectionSet: { type: graphql_1.GraphQLString }, result: { type: graphql_1.GraphQLString }, resultType: { type: graphql_1.GraphQLString }, sourceArgs: { type: exports.resolveToSourceArgsScalar }, sourceFieldName: { type: graphql_1.GraphQLString }, sourceName: { type: graphql_1.GraphQLString }, sourceSelectionSet: { type: graphql_1.GraphQLString }, sourceTypeName: { type: graphql_1.GraphQLString }, valueKeyField: { type: graphql_1.GraphQLString }, }, });