diff --git a/packages/apollo/lib/factories/graphql-federation-definitions.factory.ts b/packages/apollo/lib/factories/graphql-federation-definitions.factory.ts new file mode 100644 index 000000000..c05854a1e --- /dev/null +++ b/packages/apollo/lib/factories/graphql-federation-definitions.factory.ts @@ -0,0 +1,54 @@ +import { loadPackage } from '@nestjs/common/utils/load-package.util'; +import { + DefinitionsGeneratorOptions, + GraphQLDefinitionsFactory, +} from '@nestjs/graphql-experimental'; +import { extend } from '@nestjs/graphql-experimental/utils'; +import { gql } from 'graphql-tag'; + +export class GraphQLFederationDefinitionsFactory extends GraphQLDefinitionsFactory { + protected async exploreAndEmit( + typePaths: string[], + path: string, + outputAs: 'class' | 'interface', + isDebugEnabled: boolean, + definitionsGeneratorOptions: DefinitionsGeneratorOptions, + typeDefs?: string | string[], + ) { + const typePathDefs = await this.gqlTypesLoader.mergeTypesByPaths(typePaths); + const mergedTypeDefs = extend(typePathDefs, typeDefs); + + const { buildSubgraphSchema }: typeof import('@apollo/subgraph') = + loadPackage('@apollo/subgraph', 'ApolloFederation', () => + require('@apollo/subgraph'), + ); + + const { printSubgraphSchema } = loadPackage( + '@apollo/subgraph', + 'ApolloFederation', + () => require('@apollo/subgraph'), + ); + + const schema = buildSubgraphSchema([ + { + typeDefs: gql` + ${mergedTypeDefs} + `, + resolvers: {}, + }, + ]); + const tsFile = await this.gqlAstExplorer.explore( + gql` + ${printSubgraphSchema(schema)} + `, + path, + outputAs, + definitionsGeneratorOptions, + ); + await tsFile.save(); + this.printMessage( + `[${new Date().toLocaleTimeString()}] The definitions have been updated.`, + isDebugEnabled, + ); + } +} diff --git a/packages/apollo/lib/factories/graphql-federation.factory.ts b/packages/apollo/lib/factories/graphql-federation.factory.ts index f8164d10e..369562033 100644 --- a/packages/apollo/lib/factories/graphql-federation.factory.ts +++ b/packages/apollo/lib/factories/graphql-federation.factory.ts @@ -8,7 +8,6 @@ import { ScalarsExplorerService, } from '@nestjs/graphql-experimental/services'; import { extend } from '@nestjs/graphql-experimental/utils'; -import { transformSchema } from '@nestjs/graphql-experimental/utils/transform-schema.util'; import { GraphQLAbstractType, GraphQLField, @@ -26,11 +25,13 @@ import { isObjectType, isScalarType, isUnionType, + specifiedDirectives, } from 'graphql'; import { gql } from 'graphql-tag'; import { forEach, isEmpty } from 'lodash'; import { ApolloAdapterOptions } from '../interfaces'; import { PluginsExplorerService } from '../services/plugins-explorer.service'; +import { transformSchema } from '../utils/transform-schema.util'; export class GraphQLFederationFactory { constructor( @@ -95,12 +96,11 @@ export class GraphQLFederationFactory { () => require('@apollo/subgraph'), ); - const autoGeneratedSchema: GraphQLSchema = - await this.gqlSchemaBuilder.buildFederatedSchema( - options.autoSchemaFile, - options, - this.resolversExplorerService.getAllCtors(), - ); + const autoGeneratedSchema: GraphQLSchema = await this.buildFederatedSchema( + options.autoSchemaFile, + options, + this.resolversExplorerService.getAllCtors(), + ); let executableSchema: GraphQLSchema = buildSubgraphSchema({ typeDefs: gql(printSubgraphSchema(autoGeneratedSchema)), resolvers: this.getResolvers(options.resolvers), @@ -269,4 +269,46 @@ export class GraphQLFederationFactory { }; return typeInFederatedSchema; } + + async buildFederatedSchema( + autoSchemaFile: string | boolean, + options: ApolloAdapterOptions, + resolvers: Function[], + ) { + const scalarsMap = this.scalarsExplorerService.getScalarsMap(); + try { + const buildSchemaOptions = options.buildSchemaOptions || {}; + return await this.gqlSchemaBuilder.generateSchema( + resolvers, + autoSchemaFile, + { + ...buildSchemaOptions, + directives: [ + ...specifiedDirectives, + ...this.loadFederationDirectives(), + ...((buildSchemaOptions && buildSchemaOptions.directives) || []), + ], + scalarsMap, + schemaDirectives: options.schemaDirectives, + skipCheck: true, + }, + options.sortSchema, + options.transformAutoSchemaFile && options.transformSchema, + ); + } catch (err) { + if (err && err.details) { + console.error(err.details); + } + throw err; + } + } + + private loadFederationDirectives() { + const { federationDirectives } = loadPackage( + '@apollo/subgraph/dist/directives', + 'SchemaBuilder', + () => require('@apollo/subgraph/dist/directives'), + ); + return federationDirectives; + } } diff --git a/packages/apollo/lib/factories/index.ts b/packages/apollo/lib/factories/index.ts new file mode 100644 index 000000000..ba6cc53bb --- /dev/null +++ b/packages/apollo/lib/factories/index.ts @@ -0,0 +1 @@ +export * from './graphql-federation-definitions.factory'; diff --git a/packages/apollo/lib/index.ts b/packages/apollo/lib/index.ts index d2c8fa644..db18401b4 100644 --- a/packages/apollo/lib/index.ts +++ b/packages/apollo/lib/index.ts @@ -1,4 +1,5 @@ export * from './adapters'; export * from './decorators'; +export * from './factories'; export * from './interfaces'; export * from './utils'; diff --git a/packages/apollo/lib/interfaces/adapter-options.interface.ts b/packages/apollo/lib/interfaces/adapter-options.interface.ts index 375224bb6..083008713 100644 --- a/packages/apollo/lib/interfaces/adapter-options.interface.ts +++ b/packages/apollo/lib/interfaces/adapter-options.interface.ts @@ -79,7 +79,5 @@ export interface ApolloAdapterOptions export type ApolloAdapterOptionsFactory = GqlOptionsFactory; -export type ApolloAdapterAsyncOptions = GqlModuleAsyncOptions< - ApolloAdapterOptions, - ApolloAdapterOptionsFactory ->; +export type ApolloAdapterAsyncOptions = + GqlModuleAsyncOptions; diff --git a/packages/apollo/lib/interfaces/gateway-adapter-options.interface.ts b/packages/apollo/lib/interfaces/gateway-adapter-options.interface.ts index 420a6de50..9602e50bc 100644 --- a/packages/apollo/lib/interfaces/gateway-adapter-options.interface.ts +++ b/packages/apollo/lib/interfaces/gateway-adapter-options.interface.ts @@ -29,7 +29,5 @@ export interface ApolloGatewayAdapterOptions { export type GatewayOptionsFactory = GqlOptionsFactory; -export type GatewayAsyncOptions = GqlModuleAsyncOptions< - ApolloGatewayAdapterOptions, - GatewayOptionsFactory ->; +export type GatewayAsyncOptions = + GqlModuleAsyncOptions; diff --git a/packages/graphql/lib/utils/transform-schema.util.ts b/packages/apollo/lib/utils/transform-schema.util.ts similarity index 100% rename from packages/graphql/lib/utils/transform-schema.util.ts rename to packages/apollo/lib/utils/transform-schema.util.ts diff --git a/packages/apollo/tests/e2e/generated-definitions.spec.ts b/packages/apollo/tests/e2e/generated-definitions.spec.ts index 9f3e23974..46608f063 100644 --- a/packages/apollo/tests/e2e/generated-definitions.spec.ts +++ b/packages/apollo/tests/e2e/generated-definitions.spec.ts @@ -1,12 +1,10 @@ import { INestApplication } from '@nestjs/common'; -import { - GraphQLDefinitionsFactory, - GraphQLFactory, -} from '@nestjs/graphql-experimental'; +import { GraphQLFactory } from '@nestjs/graphql-experimental'; import { Test } from '@nestjs/testing'; import * as fs from 'fs'; import * as path from 'path'; import * as util from 'util'; +import { GraphQLFederationDefinitionsFactory } from '../../lib'; import { ApplicationModule } from '../code-first/app.module'; const readFile = util.promisify(fs.readFile); @@ -282,12 +280,11 @@ describe('Generated Definitions', () => { it('should generate for a federated graph', async () => { const outputFile = generatedDefinitions('federation.test-definitions.ts'); - const factory = new GraphQLDefinitionsFactory(); + const factory = new GraphQLFederationDefinitionsFactory(); await factory.generate({ typePaths: [generatedDefinitions('federation.graphql')], path: outputFile, outputAs: 'class', - federation: true, }); expect( @@ -299,7 +296,7 @@ describe('Generated Definitions', () => { const outputFile = generatedDefinitions( 'federation-typedef.test-definitions.ts', ); - const factory = new GraphQLDefinitionsFactory(); + const factory = new GraphQLFederationDefinitionsFactory(); await factory.generate({ typePaths: [generatedDefinitions('federation-typedef.graphql')], typeDefs: `enum Animal { @@ -308,7 +305,6 @@ describe('Generated Definitions', () => { }`, path: outputFile, outputAs: 'class', - federation: true, }); expect( diff --git a/packages/graphql/lib/graphql-definitions.factory.ts b/packages/graphql/lib/graphql-definitions.factory.ts index 0535fc09e..30e8b2c3b 100644 --- a/packages/graphql/lib/graphql-definitions.factory.ts +++ b/packages/graphql/lib/graphql-definitions.factory.ts @@ -1,5 +1,4 @@ import { makeExecutableSchema } from '@graphql-tools/schema'; -import { loadPackage } from '@nestjs/common/utils/load-package.util'; import { isEmpty } from '@nestjs/common/utils/shared.utils'; import * as chokidar from 'chokidar'; import { printSchema } from 'graphql'; @@ -11,28 +10,26 @@ import { import { GraphQLTypesLoader } from './graphql-types.loader'; import { extend, removeTempField } from './utils'; +export type GenerateOptions = DefinitionsGeneratorOptions & { + typePaths: string[]; + path: string; + outputAs?: 'class' | 'interface'; + watch?: boolean; + debug?: boolean; + typeDefs?: string | string[]; +}; + export class GraphQLDefinitionsFactory { - private readonly gqlAstExplorer = new GraphQLAstExplorer(); - private readonly gqlTypesLoader = new GraphQLTypesLoader(); + protected readonly gqlAstExplorer = new GraphQLAstExplorer(); + protected readonly gqlTypesLoader = new GraphQLTypesLoader(); - async generate( - options: { - typePaths: string[]; - path: string; - outputAs?: 'class' | 'interface'; - watch?: boolean; - debug?: boolean; - federation?: boolean; - typeDefs?: string | string[]; - } & DefinitionsGeneratorOptions, - ) { + async generate(options: GenerateOptions) { const isDebugEnabled = !(options && options.debug === false); const typePathsExists = options.typePaths && !isEmpty(options.typePaths); if (!typePathsExists) { throw new Error(`"typePaths" property cannot be empty.`); } - const isFederation = options && options.federation; const definitionsGeneratorOptions: DefinitionsGeneratorOptions = { emitTypenameField: options.emitTypenameField, skipResolverArgs: options.skipResolverArgs, @@ -58,7 +55,6 @@ export class GraphQLDefinitionsFactory { options.typePaths, options.path, options.outputAs, - isFederation, isDebugEnabled, definitionsGeneratorOptions, options.typeDefs, @@ -69,88 +65,13 @@ export class GraphQLDefinitionsFactory { options.typePaths, options.path, options.outputAs, - isFederation, isDebugEnabled, definitionsGeneratorOptions, options.typeDefs, ); } - private async exploreAndEmit( - typePaths: string[], - path: string, - outputAs: 'class' | 'interface', - isFederation: boolean, - isDebugEnabled: boolean, - definitionsGeneratorOptions: DefinitionsGeneratorOptions = {}, - typeDefs?: string | string[], - ) { - if (isFederation) { - return this.exploreAndEmitFederation( - typePaths, - path, - outputAs, - isDebugEnabled, - definitionsGeneratorOptions, - typeDefs, - ); - } - return this.exploreAndEmitRegular( - typePaths, - path, - outputAs, - isDebugEnabled, - definitionsGeneratorOptions, - typeDefs, - ); - } - - private async exploreAndEmitFederation( - typePaths: string[], - path: string, - outputAs: 'class' | 'interface', - isDebugEnabled: boolean, - definitionsGeneratorOptions: DefinitionsGeneratorOptions, - typeDefs?: string | string[], - ) { - const typePathDefs = await this.gqlTypesLoader.mergeTypesByPaths(typePaths); - const mergedTypeDefs = extend(typePathDefs, typeDefs); - - const { buildSubgraphSchema }: typeof import('@apollo/subgraph') = - loadPackage('@apollo/subgraph', 'ApolloFederation', () => - require('@apollo/subgraph'), - ); - - const { printSubgraphSchema } = loadPackage( - '@apollo/subgraph', - 'ApolloFederation', - () => require('@apollo/subgraph'), - ); - - const schema = buildSubgraphSchema([ - { - typeDefs: gql` - ${mergedTypeDefs} - `, - resolvers: {}, - }, - ]); - const tsFile = await this.gqlAstExplorer.explore( - gql` - ${printSubgraphSchema(schema)} - `, - path, - outputAs, - definitionsGeneratorOptions, - ); - await tsFile.save(); - this.printMessage( - `[${new Date().toLocaleTimeString()}] The definitions have been updated.`, - isDebugEnabled, - ); - } - - private async exploreAndEmitRegular( + protected async exploreAndEmit( typePaths: string[], path: string, outputAs: 'class' | 'interface', @@ -185,7 +106,7 @@ export class GraphQLDefinitionsFactory { ); } - private printMessage(text: string, isEnabled: boolean) { + protected printMessage(text: string, isEnabled: boolean) { isEnabled && console.log(text); } } diff --git a/packages/graphql/lib/graphql-schema.builder.ts b/packages/graphql/lib/graphql-schema.builder.ts index b0b6d6d38..dd52b3130 100644 --- a/packages/graphql/lib/graphql-schema.builder.ts +++ b/packages/graphql/lib/graphql-schema.builder.ts @@ -1,12 +1,6 @@ import { Injectable } from '@nestjs/common'; -import { loadPackage } from '@nestjs/common/utils/load-package.util'; import { isString } from '@nestjs/common/utils/shared.utils'; -import { - GraphQLSchema, - lexicographicSortSchema, - printSchema, - specifiedDirectives, -} from 'graphql'; +import { GraphQLSchema, lexicographicSortSchema, printSchema } from 'graphql'; import { resolve } from 'path'; import { GRAPHQL_SDL_FILE_HEADER } from './graphql.constants'; import { GqlModuleOptions } from './interfaces'; @@ -23,7 +17,7 @@ export class GraphQLSchemaBuilder { private readonly fileSystemHelper: FileSystemHelper, ) {} - async build( + public async build( autoSchemaFile: string | boolean, options: GqlModuleOptions, resolvers: Function[], @@ -31,7 +25,7 @@ export class GraphQLSchemaBuilder { const scalarsMap = this.scalarsExplorerService.getScalarsMap(); try { const buildSchemaOptions = options.buildSchemaOptions || {}; - return await this.buildSchema( + return await this.generateSchema( resolvers, autoSchemaFile, { @@ -51,40 +45,7 @@ export class GraphQLSchemaBuilder { } } - async buildFederatedSchema( - autoSchemaFile: string | boolean, - options: GqlModuleOptions, - resolvers: Function[], - ) { - const scalarsMap = this.scalarsExplorerService.getScalarsMap(); - try { - const buildSchemaOptions = options.buildSchemaOptions || {}; - return await this.buildSchema( - resolvers, - autoSchemaFile, - { - ...buildSchemaOptions, - directives: [ - ...specifiedDirectives, - ...this.loadFederationDirectives(), - ...((buildSchemaOptions && buildSchemaOptions.directives) || []), - ], - scalarsMap, - schemaDirectives: options.schemaDirectives, - skipCheck: true, - }, - options.sortSchema, - options.transformAutoSchemaFile && options.transformSchema, - ); - } catch (err) { - if (err && err.details) { - console.error(err.details); - } - throw err; - } - } - - private async buildSchema( + public async generateSchema( resolvers: Function[], autoSchemaFile: boolean | string, options: BuildSchemaOptions = {}, @@ -113,13 +74,4 @@ export class GraphQLSchemaBuilder { } return schema; } - - private loadFederationDirectives() { - const { federationDirectives } = loadPackage( - '@apollo/subgraph/dist/directives', - 'SchemaBuilder', - () => require('@apollo/subgraph/dist/directives'), - ); - return federationDirectives; - } } diff --git a/packages/graphql/lib/interfaces/gql-module-options.interface.ts b/packages/graphql/lib/interfaces/gql-module-options.interface.ts index 29fa99a82..f6bf5d7fc 100644 --- a/packages/graphql/lib/interfaces/gql-module-options.interface.ts +++ b/packages/graphql/lib/interfaces/gql-module-options.interface.ts @@ -120,7 +120,7 @@ export interface GqlOptionsFactory< export interface GqlModuleAsyncOptions< TOptions extends Record = GqlModuleOptions, - TFactory = GqlOptionsFactory, + TFactory = GqlOptionsFactory, > extends Pick { /** * GraphQL server adapter