88 */
99
1010import invariant from '../jsutils/invariant' ;
11- import keyMap from '../jsutils/keyMap' ;
12- import objectValues from '../jsutils/objectValues' ;
1311import { ASTDefinitionBuilder } from './buildASTSchema' ;
12+ import { SchemaTransformer } from './transformSchema' ;
1413import { GraphQLError } from '../error/GraphQLError' ;
1514import { isSchema , GraphQLSchema } from '../type/schema' ;
16- import { isIntrospectionType } from '../type/introspection' ;
17-
1815import type { GraphQLSchemaValidationOptions } from '../type/schema' ;
19-
2016import {
2117 isObjectType ,
2218 isInterfaceType ,
23- isUnionType ,
24- isListType ,
25- isNonNullType ,
2619 GraphQLObjectType ,
2720 GraphQLInterfaceType ,
28- GraphQLUnionType ,
2921} from '../type/definition' ;
30- import { GraphQLList , GraphQLNonNull } from '../type/wrappers' ;
31-
32- import { GraphQLDirective } from '../type/directives' ;
33-
3422import { Kind } from '../language/kinds' ;
35-
36- import type { GraphQLType , GraphQLNamedType } from '../type/definition' ;
37-
3823import type {
3924 DocumentNode ,
4025 DirectiveDefinitionNode ,
@@ -195,10 +180,12 @@ export function extendSchema(
195180 typeDefinitionMap ,
196181 options ,
197182 typeRef => {
183+ invariant ( schemaTransformer ) ;
198184 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 ;
202189 }
203190
204191 throw new GraphQLError (
@@ -209,54 +196,49 @@ export function extendSchema(
209196 } ,
210197 ) ;
211198
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+ ) ;
248204
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+ } ,
258238 } ) ;
259239
240+ return schemaTransformer . transformSchema ( ) ;
241+
260242 function appendExtensionToTypeExtensions (
261243 extension : TypeExtensionNode ,
262244 existingTypeExtensions : ?Array < TypeExtensionNode > ,
@@ -268,155 +250,37 @@ export function extendSchema(
268250 return existingTypeExtensions;
269251 }
270252
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 (
366257 // Note: While this could make early assertions to get the correctly
367258 // typed values, that would throw immediately while type system
368259 // 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+ ) ;
375264 }
376265
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 } ;
391269
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 ;
409273
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+ }
419283 }
420- return getExtendedType ( typeDef ) ;
284+ return fieldMap ;
421285 }
422286}
0 commit comments