Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Comment thread
jbaxleyiii marked this conversation as resolved.
Outdated

### v2.8.2

> [See complete versioning details.](https://github.com/apollographql/apollo-server/commit/99f78c6782bce170186ba6ef311182a8c9f281b7)
Expand Down
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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`,
);
});
});
40 changes: 38 additions & 2 deletions packages/apollo-federation/src/service/buildFederatedSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
GraphQLSchemaModule,
modulesFromSDL,
addResolversToSchema,
GraphQLResolverMap,
} from 'apollo-graphql';
import federationDirectives, { typeIncludesDirective } from '../directives';

Expand All @@ -22,10 +23,45 @@ import { printSchema } from './printFederatedSchema';

import 'apollo-server-env';

type LegacySchemaModule = {
typeDefs: DocumentNode | DocumentNode[];
resolvers?: GraphQLResolverMap<any>;
};

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,
Expand Down