UNPKG

@nestjs/graphql

Version:

Nest - modern, fast, powerful node.js web framework (@graphql)

254 lines (253 loc) 12.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.GraphQLFederationFactory = void 0; const tslib_1 = require("tslib"); const schema_1 = require("@graphql-tools/schema"); const utils_1 = require("@graphql-tools/utils"); const common_1 = require("@nestjs/common"); const load_package_util_1 = require("@nestjs/common/utils/load-package.util"); const shared_utils_1 = require("@nestjs/common/utils/shared.utils"); const graphql_1 = require("graphql"); const graphql_tag_1 = require("graphql-tag"); const lodash_1 = require("lodash"); const graphql_schema_builder_1 = require("../graphql-schema.builder"); const services_1 = require("../services"); const utils_2 = require("../utils"); const transform_schema_util_1 = require("../utils/transform-schema.util"); const type_defs_decorator_factory_1 = require("./type-defs-decorator.factory"); const DEFAULT_FEDERATION_VERSION = 1; let GraphQLFederationFactory = exports.GraphQLFederationFactory = class GraphQLFederationFactory { constructor(resolversExplorerService, scalarsExplorerService, gqlSchemaBuilder, typeDefsDecoratorFactory) { this.resolversExplorerService = resolversExplorerService; this.scalarsExplorerService = scalarsExplorerService; this.gqlSchemaBuilder = gqlSchemaBuilder; this.typeDefsDecoratorFactory = typeDefsDecoratorFactory; } async generateSchema(options = {}, buildFederatedSchema) { const transformSchema = options.transformSchema ?? ((schema) => schema); let schema; if (options.autoSchemaFile) { schema = await this.generateSchemaFromCodeFirst(options, buildFederatedSchema); } else if ((0, lodash_1.isEmpty)(options.typeDefs)) { schema = options.schema; } else { schema = this.buildSchemaFromTypeDefs(options); } return await transformSchema(schema); } buildSchemaFromTypeDefs(options) { const { buildSubgraphSchema } = (0, load_package_util_1.loadPackage)('@apollo/subgraph', 'ApolloFederation', () => require('@apollo/subgraph')); const resolvers = this.getResolvers(options.resolvers); return (0, schema_1.addResolversToSchema)({ inheritResolversFromInterfaces: options.inheritResolversFromInterfaces, resolvers, schema: buildSubgraphSchema([ { typeDefs: (0, graphql_tag_1.gql) ` ${options.typeDefs} `, resolvers, }, ]), }); } async generateSchemaFromCodeFirst(options, buildFederatedSchema) { const apolloSubgraph = (0, load_package_util_1.loadPackage)('@apollo/subgraph', 'ApolloFederation', () => require('@apollo/subgraph')); const apolloSubgraphVersion = (await Promise.resolve().then(() => require('@apollo/subgraph/package.json'))).version; const apolloSubgraphMajorVersion = Number(apolloSubgraphVersion.split('.')[0]); const printSubgraphSchema = apolloSubgraph.printSubgraphSchema; if (!buildFederatedSchema) { buildFederatedSchema = apolloSubgraph.buildSubgraphSchema; } const autoGeneratedSchema = await this.buildFederatedSchema(options.autoSchemaFile, options, this.resolversExplorerService.getAllCtors()); let typeDefs = apolloSubgraphMajorVersion >= 2 ? (0, utils_1.printSchemaWithDirectives)(autoGeneratedSchema) : printSubgraphSchema(autoGeneratedSchema); const [federationVersion, federationOptions] = this.getFederationVersionAndConfig(options.autoSchemaFile); const typeDefsDecorator = this.typeDefsDecoratorFactory.create(federationVersion, apolloSubgraphMajorVersion); if (typeDefsDecorator) { typeDefs = typeDefsDecorator.decorate(typeDefs, federationOptions); } let executableSchema = buildFederatedSchema({ typeDefs: (0, graphql_tag_1.gql)(typeDefs), resolvers: this.getResolvers(options.resolvers), }); executableSchema = this.overrideOrExtendResolvers(executableSchema, autoGeneratedSchema, printSubgraphSchema); const schema = options.schema ? (0, schema_1.mergeSchemas)({ schemas: [options.schema, executableSchema], }) : executableSchema; return schema; } getResolvers(optionResolvers) { optionResolvers = Array.isArray(optionResolvers) ? optionResolvers : [optionResolvers]; return this.extendResolvers([ this.resolversExplorerService.explore(), ...this.scalarsExplorerService.explore(), ...optionResolvers, ]); } extendResolvers(resolvers) { return resolvers.reduce((prev, curr) => (0, utils_2.extend)(prev, curr), {}); } overrideOrExtendResolvers(executableSchema, autoGeneratedSchema, printSchema) { return (0, transform_schema_util_1.transformSchema)(executableSchema, (type) => { if ((0, graphql_1.isUnionType)(type) && type.name !== '_Entity') { return this.overrideFederatedResolveType(type, autoGeneratedSchema); } else if ((0, graphql_1.isInterfaceType)(type)) { return this.overrideFederatedResolveType(type, autoGeneratedSchema); } else if ((0, graphql_1.isEnumType)(type)) { return autoGeneratedSchema.getType(type.name); } else if ((0, graphql_1.isInputObjectType)(type)) { const autoGeneratedInputType = autoGeneratedSchema.getType(type.name); if (!autoGeneratedInputType) { return type; } const fields = type.getFields(); (0, lodash_1.forEach)(fields, (value, key) => { const field = autoGeneratedInputType.getFields()[key]; if (!field) { return; } value.extensions = field.extensions; value.astNode = field.astNode; }); type.extensions = autoGeneratedInputType.extensions; return type; } else if ((0, graphql_1.isObjectType)(type)) { const autoGeneratedObjectType = autoGeneratedSchema.getType(type.name); if (!autoGeneratedObjectType) { return type; } const fields = type.getFields(); (0, lodash_1.forEach)(fields, (value, key) => { const field = autoGeneratedObjectType.getFields()[key]; if (!field) { return; } value.extensions = field.extensions; value.astNode = field.astNode; if (!value.resolve) { value.resolve = field.resolve; } }); if (autoGeneratedObjectType.astNode) { type.astNode = { ...type.astNode, ...autoGeneratedObjectType.astNode, }; } type.extensions = { ...type.extensions, ...autoGeneratedObjectType.extensions, }; return type; } else if ((0, graphql_1.isScalarType)(type) && type.name === 'DateTime') { const autoGeneratedScalar = autoGeneratedSchema.getType(type.name); if (!autoGeneratedScalar) { return type; } type.parseLiteral = autoGeneratedScalar.parseLiteral; type.parseValue = autoGeneratedScalar.parseValue; return type; } return type; }); } /** * Ensures that the resolveType method for unions and interfaces in the federated schema * is properly set from the one in the autoGeneratedSchema. */ overrideFederatedResolveType(typeInFederatedSchema, autoGeneratedSchema) { // Get the matching type from the auto generated schema const autoGeneratedType = autoGeneratedSchema.getType(typeInFederatedSchema.name); // Bail if inconsistent with original schema if (!autoGeneratedType || !(autoGeneratedType instanceof graphql_1.GraphQLUnionType || autoGeneratedType instanceof graphql_1.GraphQLInterfaceType) || !autoGeneratedType.resolveType) { return typeInFederatedSchema; } typeInFederatedSchema.resolveType = async (value, context, info, abstractType) => { const resultFromAutogenSchema = await autoGeneratedType.resolveType(value, context, info, abstractType); // If the result is not a GraphQLObjectType we're fine if (!resultFromAutogenSchema || (0, shared_utils_1.isString)(resultFromAutogenSchema)) { return resultFromAutogenSchema; } // We now have a GraphQLObjectType from the original union in the autogenerated schema. // But we can't return that without the additional federation property apollo adds to object // types (see node_modules/@apollo/federation/src/composition/types.ts:47). // Without that property, Apollo will ignore the returned type and the // union value will resolve to null. So we need to return the type with // the same name from the federated schema const resultFromFederatedSchema = info.schema.getType(resultFromAutogenSchema.name); if (resultFromFederatedSchema && resultFromFederatedSchema instanceof graphql_1.GraphQLObjectType) { return resultFromFederatedSchema; } // If we couldn't find a match in the federated schema, return just the // name of the type and hope apollo works it out return resultFromAutogenSchema; }; return typeInFederatedSchema; } async buildFederatedSchema(autoSchemaFile, options, resolvers) { const scalarsMap = this.scalarsExplorerService.getScalarsMap(); try { const buildSchemaOptions = options.buildSchemaOptions || {}; const directives = [...graphql_1.specifiedDirectives]; const [federationVersion] = this.getFederationVersionAndConfig(autoSchemaFile); if (federationVersion < 2) { directives.push(...this.loadFederationDirectives()); } if (buildSchemaOptions?.directives) { directives.push(...buildSchemaOptions.directives); } return await this.gqlSchemaBuilder.generateSchema(resolvers, autoSchemaFile, { ...buildSchemaOptions, directives, scalarsMap, skipCheck: true, }, options.sortSchema, options.transformAutoSchemaFile && options.transformSchema); } catch (err) { if (err && err.details) { console.error(err.details); } throw err; } } getFederationVersionAndConfig(autoSchemaFile) { if (!autoSchemaFile || typeof autoSchemaFile !== 'object') { return [DEFAULT_FEDERATION_VERSION]; } if (typeof autoSchemaFile.federation !== 'object') { return [autoSchemaFile.federation ?? DEFAULT_FEDERATION_VERSION]; } return [ autoSchemaFile.federation?.version ?? DEFAULT_FEDERATION_VERSION, autoSchemaFile.federation, ]; } loadFederationDirectives() { const { federationDirectives, directivesWithNoDefinitionNeeded } = (0, load_package_util_1.loadPackage)('@apollo/subgraph/dist/directives', 'SchemaBuilder', () => require('@apollo/subgraph/dist/directives')); return federationDirectives ?? directivesWithNoDefinitionNeeded; } }; exports.GraphQLFederationFactory = GraphQLFederationFactory = tslib_1.__decorate([ (0, common_1.Injectable)(), tslib_1.__metadata("design:paramtypes", [services_1.ResolversExplorerService, services_1.ScalarsExplorerService, graphql_schema_builder_1.GraphQLSchemaBuilder, type_defs_decorator_factory_1.TypeDefsDecoratorFactory]) ], GraphQLFederationFactory);