8
8
*/
9
9
10
10
import invariant from '../jsutils/invariant' ;
11
- import keyMap from '../jsutils/keyMap' ;
12
- import objectValues from '../jsutils/objectValues' ;
13
11
import { ASTDefinitionBuilder } from './buildASTSchema' ;
12
+ import { SchemaTransformer } from './transformSchema' ;
14
13
import { GraphQLError } from '../error/GraphQLError' ;
15
14
import { isSchema , GraphQLSchema } from '../type/schema' ;
16
- import { isIntrospectionType } from '../type/introspection' ;
17
-
18
15
import type { GraphQLSchemaValidationOptions } from '../type/schema' ;
19
-
20
16
import {
21
17
isObjectType ,
22
18
isInterfaceType ,
23
- isUnionType ,
24
- isListType ,
25
- isNonNullType ,
26
19
GraphQLObjectType ,
27
20
GraphQLInterfaceType ,
28
- GraphQLUnionType ,
29
21
} from '../type/definition' ;
30
- import { GraphQLList , GraphQLNonNull } from '../type/wrappers' ;
31
-
32
- import { GraphQLDirective } from '../type/directives' ;
33
-
34
22
import { Kind } from '../language/kinds' ;
35
-
36
- import type { GraphQLType , GraphQLNamedType } from '../type/definition' ;
37
-
38
23
import type {
39
24
DocumentNode ,
40
25
DirectiveDefinitionNode ,
@@ -195,10 +180,12 @@ export function extendSchema(
195
180
typeDefinitionMap ,
196
181
options ,
197
182
typeRef => {
183
+ invariant ( schemaTransformer ) ;
198
184
const typeName = typeRef . name . value ;
199
- const existingType = schema . getType ( typeName ) ;
200
- if ( existingType ) {
201
- return getExtendedType ( existingType ) ;
185
+ const type = schemaTransformer . transformType ( typeName ) ;
186
+
187
+ if ( type ) {
188
+ return type ;
202
189
}
203
190
204
191
throw new GraphQLError (
@@ -209,54 +196,49 @@ export function extendSchema(
209
196
} ,
210
197
) ;
211
198
212
- const extendTypeCache = Object . create ( null ) ;
213
-
214
- // Get the root Query, Mutation, and Subscription object types.
215
- // Note: While this could make early assertions to get the correctly
216
- // typed values below, that would throw immediately while type system
217
- // validation with validateSchema() will produce more actionable results.
218
- const existingQueryType = schema . getQueryType ( ) ;
219
- const queryType = existingQueryType
220
- ? getExtendedType ( existingQueryType )
221
- : null ;
222
-
223
- const existingMutationType = schema . getMutationType ( ) ;
224
- const mutationType = existingMutationType
225
- ? getExtendedType ( existingMutationType )
226
- : null ;
227
-
228
- const existingSubscriptionType = schema . getSubscriptionType ( ) ;
229
- const subscriptionType = existingSubscriptionType
230
- ? getExtendedType ( existingSubscriptionType )
231
- : null ;
232
-
233
- const types = [
234
- // Iterate through all types, getting the type definition for each, ensuring
235
- // that any type not directly referenced by a field will get created.
236
- ...objectValues ( schema . getTypeMap ( ) ) . map ( type => getExtendedType ( type ) ) ,
237
- // Do the same with new types.
238
- ...objectValues ( typeDefinitionMap ) . map ( type => astBuilder . buildType ( type ) ) ,
239
- ] ;
240
-
241
- // Support both original legacy names and extended legacy names.
242
- const schemaAllowedLegacyNames = schema . __allowedLegacyNames ;
243
- const extendAllowedLegacyNames = options && options . allowedLegacyNames ;
244
- const allowedLegacyNames =
245
- schemaAllowedLegacyNames && extendAllowedLegacyNames
246
- ? schemaAllowedLegacyNames . concat ( extendAllowedLegacyNames )
247
- : schemaAllowedLegacyNames || extendAllowedLegacyNames ;
199
+ const schemaTransformer = new SchemaTransformer ( schema , {
200
+ Schema ( config ) {
201
+ const newDirectives = directiveDefinitions . map ( node =>
202
+ astBuilder . buildDirective ( node ) ,
203
+ ) ;
248
204
249
- // Then produce and return a Schema with these types.
250
- return new GraphQLSchema ( {
251
- query : queryType ,
252
- mutation : mutationType ,
253
- subscription : subscriptionType ,
254
- types,
255
- directives : getMergedDirectives ( ) ,
256
- astNode : schema . astNode ,
257
- allowedLegacyNames,
205
+ const newTypes = [ ] ;
206
+ Object . keys ( typeDefinitionMap ) . forEach ( typeName => {
207
+ const def = typeDefinitionMap [ typeName ] ;
208
+ newTypes . push ( astBuilder . buildType ( def ) ) ;
209
+ } ) ;
210
+ const extendAllowedLegacyNames = options && options . allowedLegacyNames ;
211
+
212
+ return new GraphQLSchema ( {
213
+ ...config ,
214
+ types : config . types . concat ( newTypes ) ,
215
+ directives : config . directives . concat ( newDirectives ) ,
216
+ allowedLegacyNames : extendAllowedLegacyNames
217
+ ? config . allowedLegacyNames . concat ( extendAllowedLegacyNames )
218
+ : config . allowedLegacyNames ,
219
+ } ) ;
220
+ } ,
221
+ ObjectType ( config ) {
222
+ const extensions = typeExtensionsMap [ config . name ] || [ ] ;
223
+ return new GraphQLObjectType ( {
224
+ ...config ,
225
+ interfaces : ( ) => extendImplementedInterfaces ( config , extensions ) ,
226
+ fields : ( ) => extendFieldMap ( config , extensions ) ,
227
+ extensionASTNodes : config . extensionASTNodes . concat ( extensions ) ,
228
+ } ) ;
229
+ } ,
230
+ InterfaceType ( config ) {
231
+ const extensions = typeExtensionsMap [ config . name ] || [ ] ;
232
+ return new GraphQLInterfaceType ( {
233
+ ...config ,
234
+ fields : ( ) => extendFieldMap ( config , extensions ) ,
235
+ extensionASTNodes : config . extensionASTNodes . concat ( extensions ) ,
236
+ } ) ;
237
+ } ,
258
238
} ) ;
259
239
240
+ return schemaTransformer . transformSchema ( ) ;
241
+
260
242
function appendExtensionToTypeExtensions (
261
243
extension : TypeExtensionNode ,
262
244
existingTypeExtensions : ?Array < TypeExtensionNode > ,
@@ -268,155 +250,37 @@ export function extendSchema(
268
250
return existingTypeExtensions;
269
251
}
270
252
271
- // Below are functions used for producing this schema that have closed over
272
- // this scope and have access to the schema, cache, and newly defined types.
273
-
274
- function getMergedDirectives ( ) : Array < GraphQLDirective > {
275
- const existingDirectives = schema . getDirectives ( ) ;
276
- invariant ( existingDirectives , 'schema must have default directives' ) ;
277
-
278
- return existingDirectives . concat (
279
- directiveDefinitions . map ( node => astBuilder . buildDirective ( node ) ) ,
280
- ) ;
281
- }
282
-
283
- function getExtendedType < T : GraphQLNamedType > (type: T): T {
284
- if ( ! extendTypeCache [ type . name ] ) {
285
- extendTypeCache [ type . name ] = extendType ( type ) ;
286
- }
287
- return (extendTypeCache[type.name]: any);
288
- }
289
-
290
- // To be called at most once per type. Only getExtendedType should call this.
291
- function extendType ( type ) {
292
- if ( isIntrospectionType ( type ) ) {
293
- // Introspection types are not extended.
294
- return type ;
295
- }
296
- if ( isObjectType ( type ) ) {
297
- return extendObjectType ( type ) ;
298
- }
299
- if ( isInterfaceType ( type ) ) {
300
- return extendInterfaceType ( type ) ;
301
- }
302
- if ( isUnionType ( type ) ) {
303
- return extendUnionType ( type ) ;
304
- }
305
- // This type is not yet extendable.
306
- return type ;
307
- }
308
-
309
- function extendObjectType ( type : GraphQLObjectType ) : GraphQLObjectType {
310
- const name = type . name ;
311
- const extensionASTNodes = typeExtensionsMap [ name ]
312
- ? type . extensionASTNodes
313
- ? type . extensionASTNodes . concat ( typeExtensionsMap [ name ] )
314
- : typeExtensionsMap [ name ]
315
- : type . extensionASTNodes ;
316
- return new GraphQLObjectType ( {
317
- name,
318
- description : type . description ,
319
- interfaces : ( ) => extendImplementedInterfaces ( type ) ,
320
- fields : ( ) => extendFieldMap ( type ) ,
321
- astNode : type . astNode ,
322
- extensionASTNodes,
323
- isTypeOf : type . isTypeOf ,
324
- } ) ;
325
- }
326
-
327
- function extendInterfaceType (
328
- type : GraphQLInterfaceType ,
329
- ) : GraphQLInterfaceType {
330
- const name = type . name ;
331
- const extensionASTNodes = typeExtensionsMap [ name ]
332
- ? type . extensionASTNodes
333
- ? type . extensionASTNodes . concat ( typeExtensionsMap [ name ] )
334
- : typeExtensionsMap [ name ]
335
- : type . extensionASTNodes ;
336
- return new GraphQLInterfaceType ( {
337
- name : type . name ,
338
- description : type . description ,
339
- fields : ( ) => extendFieldMap ( type ) ,
340
- astNode : type . astNode ,
341
- extensionASTNodes,
342
- resolveType : type . resolveType ,
343
- } ) ;
344
- }
345
-
346
- function extendUnionType ( type : GraphQLUnionType ) : GraphQLUnionType {
347
- return new GraphQLUnionType ( {
348
- name : type . name ,
349
- description : type . description ,
350
- types : type . getTypes ( ) . map ( getExtendedType ) ,
351
- astNode : type . astNode ,
352
- resolveType : type . resolveType ,
353
- } ) ;
354
- }
355
-
356
- function extendImplementedInterfaces (
357
- type : GraphQLObjectType ,
358
- ) : Array < GraphQLInterfaceType > {
359
- const interfaces = type . getInterfaces ( ) . map ( getExtendedType ) ;
360
-
361
- // If there are any extensions to the interfaces, apply those here.
362
- const extensions = typeExtensionsMap [ type . name ] ;
363
- if ( extensions ) {
364
- extensions . forEach ( extension => {
365
- extension . interfaces . forEach ( namedType => {
253
+ function extendImplementedInterfaces ( config , extensions ) {
254
+ return config . interfaces ( ) . concat (
255
+ ...extensions . map ( extension =>
256
+ extension . interfaces . map (
366
257
// Note: While this could make early assertions to get the correctly
367
258
// typed values, that would throw immediately while type system
368
259
// validation with validateSchema() will produce more actionable results.
369
- interfaces . push ( ( astBuilder . buildType ( namedType ) : any ) ) ;
370
- } ) ;
371
- } ) ;
372
- }
373
-
374
- return interfaces ;
260
+ type => ( astBuilder . buildType ( type ) : any ) ,
261
+ ) ,
262
+ ) ,
263
+ ) ;
375
264
}
376
265
377
- function extendFieldMap ( type : GraphQLObjectType | GraphQLInterfaceType ) {
378
- const newFieldMap = Object . create ( null ) ;
379
- const oldFieldMap = type . getFields ( ) ;
380
- Object . keys ( oldFieldMap ) . forEach ( fieldName => {
381
- const field = oldFieldMap [ fieldName ] ;
382
- newFieldMap [ fieldName ] = {
383
- description : field . description ,
384
- deprecationReason : field . deprecationReason ,
385
- type : extendFieldType ( field . type ) ,
386
- args : keyMap ( field . args , arg => arg . name ) ,
387
- astNode : field . astNode ,
388
- resolve : field . resolve ,
389
- } ;
390
- } ) ;
266
+ function extendFieldMap ( config , extensions ) {
267
+ const oldFields = config . fields ( ) ;
268
+ const fieldMap = { ...oldFields } ;
391
269
392
- // If there are any extensions to the fields, apply those here.
393
- const extensions = typeExtensionsMap [ type . name ] ;
394
- if ( extensions ) {
395
- extensions . forEach ( extension => {
396
- extension . fields . forEach ( field => {
397
- const fieldName = field . name . value ;
398
- if ( oldFieldMap [ fieldName ] ) {
399
- throw new GraphQLError (
400
- `Field "${ type . name } .${ fieldName } " already exists in the ` +
401
- 'schema. It cannot also be defined in this type extension.' ,
402
- [ field ] ,
403
- ) ;
404
- }
405
- newFieldMap [ fieldName ] = astBuilder . buildField ( field ) ;
406
- } ) ;
407
- } ) ;
408
- }
270
+ for ( const extension of extensions ) {
271
+ for ( const field of extension . fields ) {
272
+ const fieldName = field . name . value ;
409
273
410
- return newFieldMap ;
411
- }
412
-
413
- function extendFieldType < T : GraphQLType > ( typeDef : T ) : T {
414
- if ( isListType ( typeDef ) ) {
415
- return ( GraphQLList ( extendFieldType ( typeDef . ofType ) ) : any ) ;
416
- }
417
- if ( isNonNullType ( typeDef ) ) {
418
- return ( GraphQLNonNull ( extendFieldType ( typeDef . ofType ) ) : any ) ;
274
+ if ( oldFields [ fieldName ] ) {
275
+ throw new GraphQLError (
276
+ `Field " ${ config . name } . ${ fieldName } " already exists in the ` +
277
+ 'schema. It cannot also be defined in this type extension.' ,
278
+ [ field ] ,
279
+ ) ;
280
+ }
281
+ fieldMap [ fieldName ] = astBuilder . buildField ( field ) ;
282
+ }
419
283
}
420
- return getExtendedType ( typeDef ) ;
284
+ return fieldMap ;
421
285
}
422
286
}
0 commit comments