diff --git a/CHANGELOG.md b/CHANGELOG.md index bd132d08fcc..b001c70b1e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ The version headers in this history reflect the versions of Apollo Server itself - _Nothing yet!_ +- `@apollo/federation`: `buildFederatedSchema` now accepts the same possible interface combination as `new ApolloServer()` for `typeDefs` and `resolvers` to make the migration from a single service into a federated service easier for teams with existing `typeDefs` arrays already built up [PR #3188](https://github.com/apollographql/apollo-server/pull/3188) + ### v2.8.2 > [See complete versioning details.](https://github.com/apollographql/apollo-server/commit/99f78c6782bce170186ba6ef311182a8c9f281b7) diff --git a/packages/apollo-federation/src/service/__tests__/buildFederatedSchema.test.ts b/packages/apollo-federation/src/service/__tests__/buildFederatedSchema.test.ts index d09f250d900..a5d9ae3e00c 100644 --- a/packages/apollo-federation/src/service/__tests__/buildFederatedSchema.test.ts +++ b/packages/apollo-federation/src/service/__tests__/buildFederatedSchema.test.ts @@ -1,5 +1,5 @@ import gql from 'graphql-tag'; -import { Kind, graphql } from 'graphql'; +import { Kind, graphql, DocumentNode, execute } from 'graphql'; import { buildFederatedSchema } from '../buildFederatedSchema'; import { typeSerializer } from '../../snapshotSerializers'; @@ -478,3 +478,102 @@ extend type User @key(fields: "email") { }); }); }); + +describe('legacy interface', () => { + const resolvers = { + Query: { + product: () => ({}) + }, + Product: { + upc: () => '1234', + price: () => 10 + } + } + const typeDefs: DocumentNode[] = [ + gql` + type Query { + product: Product + } + type Product @key(fields: "upc") { + upc: String! + name: String + } + `, + gql` + extend type Product { + price: Int + } + ` + ] + it('allows legacy schema module interface as an input with an array of typeDefs and resolvers', async () => { + const schema = buildFederatedSchema({ typeDefs, resolvers }); + expect(schema.getType('_Entity')).toMatchInlineSnapshot( + `union _Entity = Product`, + ); + expect(await execute(schema, gql`{ product { price upc }}`)).toEqual({ + data: { + product: { upc: '1234', price: 10 } + } + }) + }) + it('allows legacy schema module interface as a single module', async () => { + const schema = buildFederatedSchema({ + typeDefs: gql` + type Query { + product: Product + } + type Product @key(fields: "upc") { + upc: String! + name: String + price: Int + } + `, + resolvers + }); + expect(schema.getType('_Entity')).toMatchInlineSnapshot( + `union _Entity = Product`, + ); + expect(await execute(schema, gql`{ product { price upc }}`)).toEqual({ + data: { + product: { upc: '1234', price: 10 } + } + }) + }) + it('allows legacy schema module interface as a single module without resolvers', async () => { + const schema = buildFederatedSchema({ + typeDefs: gql` + type Query { + product: Product + } + type Product @key(fields: "upc") { + upc: String! + name: String + price: Int + } + ` + }); + expect(schema.getType('Product')).toMatchInlineSnapshot(` +type Product { + upc: String! + name: String + price: Int +} +`); + expect(schema.getType('_Entity')).toMatchInlineSnapshot( + `union _Entity = Product`, + ); + }) + it('allows legacy schema module interface as a simple array of documents', async () => { + const schema = buildFederatedSchema({ typeDefs }); + expect(schema.getType('Product')).toMatchInlineSnapshot(` +type Product { + upc: String! + name: String + price: Int +} +`); + expect(schema.getType('_Entity')).toMatchInlineSnapshot( + `union _Entity = Product`, + ); + }) +}) diff --git a/packages/apollo-federation/src/service/buildFederatedSchema.ts b/packages/apollo-federation/src/service/buildFederatedSchema.ts index 40454297668..31e3501b567 100644 --- a/packages/apollo-federation/src/service/buildFederatedSchema.ts +++ b/packages/apollo-federation/src/service/buildFederatedSchema.ts @@ -13,6 +13,7 @@ import { GraphQLSchemaModule, modulesFromSDL, addResolversToSchema, + GraphQLResolverMap, } from 'apollo-graphql'; import federationDirectives, { typeIncludesDirective } from '../directives'; @@ -22,10 +23,40 @@ import { printSchema } from './printFederatedSchema'; import 'apollo-server-env'; +type LegacySchemaModule = { + typeDefs: DocumentNode | DocumentNode[], + resolvers?: GraphQLResolverMap; +} + export function buildFederatedSchema( - modulesOrSDL: (GraphQLSchemaModule | DocumentNode)[] | DocumentNode, + modulesOrSDL: (GraphQLSchemaModule | DocumentNode)[] | DocumentNode | LegacySchemaModule, ): GraphQLSchema { - const modules = modulesFromSDL(modulesOrSDL); + // ApolloServer supports passing an array of DocumentNode along with a single + // map of resolvers to build a schema. Long term we don't want to support this + // style anymore as we move towards a more structured approach to modules, + // however, it has tripped several teams up to not support this signature + // in buildFederatedSchema. Especially as teams migrate from + // `new ApolloServer({ typeDefs: DocumentNode[], resolvers })` to + // `new ApolloServer({ schema: buildFederatedSchema({ typeDefs: DocumentNode[], resolvers }) })` + // + // The last type in the union for `modulesOrSDL` supports this "legacy" input + // style in a simple manner (by just adding the resolvers to the first typeDefs entry) + // + let shappedModulesOrSDL: (GraphQLSchemaModule | DocumentNode)[] | DocumentNode; + if ('typeDefs' in modulesOrSDL) { + const { typeDefs, resolvers } = modulesOrSDL; + const augmentedTypeDefs = Array.isArray(typeDefs) ? typeDefs : [typeDefs]; + shappedModulesOrSDL = augmentedTypeDefs.map((typeDefs, i) => { + const module: GraphQLSchemaModule = { typeDefs } + // add the resolvers to the first "module" in the array + if (i === 0 && resolvers) module.resolvers = resolvers + return module; + }); + } else { + shappedModulesOrSDL = modulesOrSDL; + } + + const modules = modulesFromSDL(shappedModulesOrSDL); let schema = buildSchemaFromSDL( modules,