-
Notifications
You must be signed in to change notification settings - Fork 2k
/
Copy pathbuildFederatedSchema.ts
134 lines (119 loc) · 4.2 KB
/
buildFederatedSchema.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import {
DocumentNode,
GraphQLSchema,
isObjectType,
isUnionType,
GraphQLUnionType,
GraphQLObjectType,
specifiedDirectives,
} from 'graphql';
import {
buildSchemaFromSDL,
transformSchema,
GraphQLSchemaModule,
modulesFromSDL,
addResolversToSchema,
GraphQLResolverMap,
} from 'apollo-graphql';
import federationDirectives, { typeIncludesDirective } from '../directives';
import { serviceField, entitiesField, EntityType } from '../types';
import { printSchema } from './printFederatedSchema';
import 'apollo-server-env';
type LegacySchemaModule = {
typeDefs: DocumentNode | DocumentNode[];
resolvers?: GraphQLResolverMap<any>;
};
export function buildFederatedSchema(
modulesOrSDL:
| (GraphQLSchemaModule | DocumentNode)[]
| DocumentNode
| LegacySchemaModule,
): GraphQLSchema {
// 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,
new GraphQLSchema({
query: undefined,
directives: [...specifiedDirectives, ...federationDirectives],
}),
);
// At this point in time, we have a schema to be printed into SDL which is
// representative of what the user defined for their schema. This is before
// we process any of the federation directives and add custom federation types
// so its the right place to create our service definition sdl.
//
// We have to use a modified printSchema from graphql-js which includes
// support for preserving the *uses* of federation directives while removing
// their *definitions* from the sdl.
const sdl = printSchema(schema);
// Add an empty query root type if none has been defined
if (!schema.getQueryType()) {
schema = new GraphQLSchema({
...schema.toConfig(),
query: new GraphQLObjectType({
name: 'Query',
fields: {},
}),
});
}
const entityTypes = Object.values(schema.getTypeMap()).filter(
type => isObjectType(type) && typeIncludesDirective(type, 'key'),
);
const hasEntities = entityTypes.length > 0;
schema = transformSchema(schema, type => {
// Add `_entities` and `_service` fields to query root type
if (isObjectType(type) && type === schema.getQueryType()) {
const config = type.toConfig();
return new GraphQLObjectType({
...config,
fields: {
...(hasEntities && { _entities: entitiesField }),
_service: {
...serviceField,
resolve: () => ({ sdl }),
},
...config.fields,
},
});
}
return undefined;
});
schema = transformSchema(schema, type => {
if (hasEntities && isUnionType(type) && type.name === EntityType.name) {
return new GraphQLUnionType({
...EntityType.toConfig(),
types: entityTypes.filter(isObjectType),
});
}
return undefined;
});
for (const module of modules) {
if (!module.resolvers) continue;
addResolversToSchema(schema, module.resolvers);
}
return schema;
}