diff --git a/CHANGELOG.md b/CHANGELOG.md index bd132d08fcc..db7bba32b8b 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`'s `typeDefs` parameter now accepts arrays of `DocumentNode`s (i.e. type definitions wrapped in `gql`) and `resolvers` to make the migration from a single service into a federated service easier for teams previously utilizing this pattern. [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..f55ef310754 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,126 @@ 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..16481e0902b 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,43 @@ 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 shapedModulesOrSDL: (GraphQLSchemaModule | DocumentNode)[] | DocumentNode; + if ('typeDefs' in modulesOrSDL) { + const { typeDefs, resolvers } = modulesOrSDL; + const augmentedTypeDefs = Array.isArray(typeDefs) ? typeDefs : [typeDefs]; + shapedModulesOrSDL = 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 { + shapedModulesOrSDL = modulesOrSDL; + } + + const modules = modulesFromSDL(shapedModulesOrSDL); let schema = buildSchemaFromSDL( modules,