|
8 | 8 | */
|
9 | 9 |
|
10 | 10 | import invariant from '../jsutils/invariant';
|
11 |
| -import keyMap from '../jsutils/keyMap'; |
12 | 11 | import { ASTDefinitionBuilder } from './buildASTSchema';
|
| 12 | +import { SchemaTransformer } from './transformSchema'; |
13 | 13 | import { GraphQLError } from '../error/GraphQLError';
|
14 | 14 | import { isSchema, GraphQLSchema } from '../type/schema';
|
15 |
| - |
16 |
| -import { |
17 |
| - isObjectType, |
18 |
| - isInterfaceType, |
19 |
| - isUnionType, |
20 |
| - isListType, |
21 |
| - isNonNullType, |
22 |
| - GraphQLObjectType, |
23 |
| - GraphQLInterfaceType, |
24 |
| - GraphQLUnionType, |
25 |
| -} from '../type/definition'; |
26 |
| -import { GraphQLList, GraphQLNonNull } from '../type/wrappers'; |
27 |
| - |
28 |
| -import { GraphQLDirective } from '../type/directives'; |
29 |
| - |
| 15 | +import { isObjectType, GraphQLObjectType } from '../type/definition'; |
30 | 16 | import * as Kind from '../language/kinds';
|
31 |
| - |
32 |
| -import type { GraphQLType, GraphQLNamedType } from '../type/definition'; |
33 |
| - |
34 | 17 | import type { DocumentNode, DirectiveDefinitionNode } from '../language/ast';
|
35 | 18 |
|
36 | 19 | type Options = {|
|
@@ -171,204 +154,87 @@ export function extendSchema(
|
171 | 154 | return schema;
|
172 | 155 | }
|
173 | 156 |
|
174 |
| - const definitionBuilder = new ASTDefinitionBuilder( |
| 157 | + const astBuilder = new ASTDefinitionBuilder( |
175 | 158 | typeDefinitionMap,
|
176 | 159 | options,
|
177 |
| - (typeName, node) => { |
178 |
| - const existingType = schema.getType(typeName); |
179 |
| - if (existingType) { |
180 |
| - return extendType(existingType); |
181 |
| - } |
| 160 | + typeRef => { |
| 161 | + invariant(schemaTransformer); |
| 162 | + const typeName = typeRef.name.value; |
| 163 | + const type = schemaTransformer.transformType(typeName); |
182 | 164 |
|
183 |
| - if (node) { |
| 165 | + if (!type) { |
184 | 166 | throw new GraphQLError(
|
185 | 167 | `Unknown type: "${typeName}". Ensure that this type exists ` +
|
186 | 168 | 'either in the original schema, or is added in a type definition.',
|
187 |
| - [node], |
| 169 | + [typeRef], |
188 | 170 | );
|
189 | 171 | }
|
190 |
| - throw GraphQLError('Missing type from schema'); |
| 172 | + return type; |
191 | 173 | },
|
192 | 174 | );
|
193 | 175 |
|
194 |
| - // Get the root Query, Mutation, and Subscription object types. |
195 |
| - // Note: While this could make early assertions to get the correctly |
196 |
| - // typed values below, that would throw immediately while type system |
197 |
| - // validation with validateSchema() will produce more actionable results. |
198 |
| - const existingQueryType = schema.getQueryType(); |
199 |
| - const queryType = existingQueryType |
200 |
| - ? (definitionBuilder.buildType(existingQueryType.name): any) |
201 |
| - : null; |
| 176 | + const schemaTransformer = new SchemaTransformer(schema, { |
| 177 | + Schema(config) { |
| 178 | + const newDirectives = directiveDefinitions.map(node => |
| 179 | + astBuilder.buildDirective(node), |
| 180 | + ); |
202 | 181 |
|
203 |
| - const existingMutationType = schema.getMutationType(); |
204 |
| - const mutationType = existingMutationType |
205 |
| - ? (definitionBuilder.buildType(existingMutationType.name): any) |
206 |
| - : null; |
207 |
| - |
208 |
| - const existingSubscriptionType = schema.getSubscriptionType(); |
209 |
| - const subscriptionType = existingSubscriptionType |
210 |
| - ? (definitionBuilder.buildType(existingSubscriptionType.name): any) |
211 |
| - : null; |
212 |
| - |
213 |
| - // Iterate through all types, getting the type definition for each, ensuring |
214 |
| - // that any type not directly referenced by a field will get created. |
215 |
| - const typeMap = schema.getTypeMap(); |
216 |
| - const types = Object.keys(typeMap).map(typeName => |
217 |
| - definitionBuilder.buildType(typeName), |
218 |
| - ); |
219 |
| - |
220 |
| - // Do the same with new types, appending to the list of defined types. |
221 |
| - Object.keys(typeDefinitionMap).forEach(typeName => { |
222 |
| - types.push(definitionBuilder.buildType(typeName)); |
223 |
| - }); |
| 182 | + const newTypes = []; |
| 183 | + Object.keys(typeDefinitionMap).forEach(typeName => { |
| 184 | + const def = typeDefinitionMap[typeName]; |
| 185 | + newTypes.push(astBuilder.buildType(def)); |
| 186 | + }); |
224 | 187 |
|
225 |
| - // Then produce and return a Schema with these types. |
226 |
| - return new GraphQLSchema({ |
227 |
| - query: queryType, |
228 |
| - mutation: mutationType, |
229 |
| - subscription: subscriptionType, |
230 |
| - types, |
231 |
| - directives: getMergedDirectives(), |
232 |
| - astNode: schema.astNode, |
| 188 | + return new GraphQLSchema({ |
| 189 | + ...config, |
| 190 | + types: config.types.concat(newTypes), |
| 191 | + directives: config.directives.concat(newDirectives), |
| 192 | + }); |
| 193 | + }, |
| 194 | + ObjectType(config) { |
| 195 | + const extensions = typeExtensionsMap[config.name] || []; |
| 196 | + return new GraphQLObjectType({ |
| 197 | + ...config, |
| 198 | + interfaces: () => extendImplementedInterfaces(config, extensions), |
| 199 | + fields: () => extendFieldMap(config, extensions), |
| 200 | + extensionASTNodes: config.extensionASTNodes.concat(extensions), |
| 201 | + }); |
| 202 | + }, |
233 | 203 | });
|
234 | 204 |
|
235 |
| - // Below are functions used for producing this schema that have closed over |
236 |
| - // this scope and have access to the schema, cache, and newly defined types. |
237 |
| - |
238 |
| - function getMergedDirectives(): Array<GraphQLDirective> { |
239 |
| - const existingDirectives = schema.getDirectives(); |
240 |
| - invariant(existingDirectives, 'schema must have default directives'); |
241 |
| - |
242 |
| - const newDirectives = directiveDefinitions.map(directiveNode => |
243 |
| - definitionBuilder.buildDirective(directiveNode), |
244 |
| - ); |
245 |
| - return existingDirectives.concat(newDirectives); |
246 |
| - } |
247 |
| - |
248 |
| - function getTypeFromDef<T: GraphQLNamedType>(typeDef: T): T { |
249 |
| - const type = definitionBuilder.buildType(typeDef.name); |
250 |
| - return (type: any); |
251 |
| - } |
| 205 | + return schemaTransformer.transformSchema(); |
252 | 206 |
|
253 |
| - // Given a type's introspection result, construct the correct |
254 |
| - // GraphQLType instance. |
255 |
| - function extendType(type: GraphQLNamedType): GraphQLNamedType { |
256 |
| - if (isObjectType(type)) { |
257 |
| - return extendObjectType(type); |
258 |
| - } |
259 |
| - if (isInterfaceType(type)) { |
260 |
| - return extendInterfaceType(type); |
261 |
| - } |
262 |
| - if (isUnionType(type)) { |
263 |
| - return extendUnionType(type); |
264 |
| - } |
265 |
| - return type; |
266 |
| - } |
267 |
| - |
268 |
| - function extendObjectType(type: GraphQLObjectType): GraphQLObjectType { |
269 |
| - const name = type.name; |
270 |
| - const extensionASTNodes = typeExtensionsMap[name] |
271 |
| - ? type.extensionASTNodes |
272 |
| - ? type.extensionASTNodes.concat(typeExtensionsMap[name]) |
273 |
| - : typeExtensionsMap[name] |
274 |
| - : type.extensionASTNodes; |
275 |
| - return new GraphQLObjectType({ |
276 |
| - name, |
277 |
| - description: type.description, |
278 |
| - interfaces: () => extendImplementedInterfaces(type), |
279 |
| - fields: () => extendFieldMap(type), |
280 |
| - astNode: type.astNode, |
281 |
| - extensionASTNodes, |
282 |
| - isTypeOf: type.isTypeOf, |
283 |
| - }); |
284 |
| - } |
285 |
| - |
286 |
| - function extendInterfaceType( |
287 |
| - type: GraphQLInterfaceType, |
288 |
| - ): GraphQLInterfaceType { |
289 |
| - return new GraphQLInterfaceType({ |
290 |
| - name: type.name, |
291 |
| - description: type.description, |
292 |
| - fields: () => extendFieldMap(type), |
293 |
| - astNode: type.astNode, |
294 |
| - resolveType: type.resolveType, |
295 |
| - }); |
296 |
| - } |
297 |
| - |
298 |
| - function extendUnionType(type: GraphQLUnionType): GraphQLUnionType { |
299 |
| - return new GraphQLUnionType({ |
300 |
| - name: type.name, |
301 |
| - description: type.description, |
302 |
| - types: type.getTypes().map(getTypeFromDef), |
303 |
| - astNode: type.astNode, |
304 |
| - resolveType: type.resolveType, |
305 |
| - }); |
306 |
| - } |
307 |
| - |
308 |
| - function extendImplementedInterfaces( |
309 |
| - type: GraphQLObjectType, |
310 |
| - ): Array<GraphQLInterfaceType> { |
311 |
| - const interfaces = type.getInterfaces().map(getTypeFromDef); |
312 |
| - |
313 |
| - // If there are any extensions to the interfaces, apply those here. |
314 |
| - const extensions = typeExtensionsMap[type.name]; |
315 |
| - if (extensions) { |
316 |
| - extensions.forEach(extension => { |
317 |
| - extension.interfaces.forEach(namedType => { |
| 207 | + function extendImplementedInterfaces(config, extensions) { |
| 208 | + return config.interfaces().concat( |
| 209 | + ...extensions.map(extension => |
| 210 | + extension.interfaces.map( |
318 | 211 | // Note: While this could make early assertions to get the correctly
|
319 | 212 | // typed values, that would throw immediately while type system
|
320 | 213 | // validation with validateSchema() will produce more actionable results.
|
321 |
| - interfaces.push((definitionBuilder.buildType(namedType): any)); |
322 |
| - }); |
323 |
| - }); |
324 |
| - } |
325 |
| - |
326 |
| - return interfaces; |
| 214 | + type => (astBuilder.buildType(type): any), |
| 215 | + ), |
| 216 | + ), |
| 217 | + ); |
327 | 218 | }
|
328 | 219 |
|
329 |
| - function extendFieldMap(type: GraphQLObjectType | GraphQLInterfaceType) { |
330 |
| - const newFieldMap = Object.create(null); |
331 |
| - const oldFieldMap = type.getFields(); |
332 |
| - Object.keys(oldFieldMap).forEach(fieldName => { |
333 |
| - const field = oldFieldMap[fieldName]; |
334 |
| - newFieldMap[fieldName] = { |
335 |
| - description: field.description, |
336 |
| - deprecationReason: field.deprecationReason, |
337 |
| - type: extendFieldType(field.type), |
338 |
| - args: keyMap(field.args, arg => arg.name), |
339 |
| - astNode: field.astNode, |
340 |
| - resolve: field.resolve, |
341 |
| - }; |
342 |
| - }); |
343 |
| - |
344 |
| - // If there are any extensions to the fields, apply those here. |
345 |
| - const extensions = typeExtensionsMap[type.name]; |
346 |
| - if (extensions) { |
347 |
| - extensions.forEach(extension => { |
348 |
| - extension.fields.forEach(field => { |
349 |
| - const fieldName = field.name.value; |
350 |
| - if (oldFieldMap[fieldName]) { |
351 |
| - throw new GraphQLError( |
352 |
| - `Field "${type.name}.${fieldName}" already exists in the ` + |
353 |
| - 'schema. It cannot also be defined in this type extension.', |
354 |
| - [field], |
355 |
| - ); |
356 |
| - } |
357 |
| - newFieldMap[fieldName] = definitionBuilder.buildField(field); |
358 |
| - }); |
359 |
| - }); |
360 |
| - } |
| 220 | + function extendFieldMap(config, extensions) { |
| 221 | + const oldFields = config.fields(); |
| 222 | + const fieldMap = { ...oldFields }; |
361 | 223 |
|
362 |
| - return newFieldMap; |
363 |
| - } |
| 224 | + for (const extension of extensions) { |
| 225 | + for (const field of extension.fields) { |
| 226 | + const fieldName = field.name.value; |
364 | 227 |
|
365 |
| - function extendFieldType<T: GraphQLType>(typeDef: T): T { |
366 |
| - if (isListType(typeDef)) { |
367 |
| - return (GraphQLList(extendFieldType(typeDef.ofType)): any); |
368 |
| - } |
369 |
| - if (isNonNullType(typeDef)) { |
370 |
| - return (GraphQLNonNull(extendFieldType(typeDef.ofType)): any); |
| 228 | + if (oldFields[fieldName]) { |
| 229 | + throw new GraphQLError( |
| 230 | + `Field "${config.name}.${fieldName}" already exists in the ` + |
| 231 | + 'schema. It cannot also be defined in this type extension.', |
| 232 | + [field], |
| 233 | + ); |
| 234 | + } |
| 235 | + fieldMap[fieldName] = astBuilder.buildField(field); |
| 236 | + } |
371 | 237 | }
|
372 |
| - return getTypeFromDef(typeDef); |
| 238 | + return fieldMap; |
373 | 239 | }
|
374 | 240 | }
|
0 commit comments