9
9
10
10
import invariant from '../jsutils/invariant' ;
11
11
import keyMap from '../jsutils/keyMap' ;
12
+ import objectValues from '../jsutils/objectValues' ;
12
13
import { ASTDefinitionBuilder } from './buildASTSchema' ;
13
14
import { GraphQLError } from '../error/GraphQLError' ;
14
15
import { isSchema , GraphQLSchema } from '../type/schema' ;
16
+ import { isIntrospectionType } from '../type/introspection' ;
15
17
16
18
import type { GraphQLSchemaValidationOptions } from '../type/schema' ;
17
19
@@ -189,56 +191,52 @@ export function extendSchema(
189
191
return schema ;
190
192
}
191
193
192
- const definitionBuilder = new ASTDefinitionBuilder (
194
+ const astBuilder = new ASTDefinitionBuilder (
193
195
typeDefinitionMap ,
194
196
options ,
195
- ( typeName , node ) => {
197
+ typeRef => {
198
+ const typeName = typeRef . name . value ;
196
199
const existingType = schema . getType ( typeName ) ;
197
200
if ( existingType ) {
198
- return extendType ( existingType ) ;
201
+ return getExtendedType ( existingType ) ;
199
202
}
200
203
201
- if ( node ) {
202
- throw new GraphQLError (
203
- `Unknown type: "${ typeName } ". Ensure that this type exists ` +
204
- 'either in the original schema, or is added in a type definition.' ,
205
- [ node ] ,
206
- ) ;
207
- }
208
- throw GraphQLError ( 'Missing type from schema' ) ;
204
+ throw new GraphQLError (
205
+ `Unknown type: "${ typeName } ". Ensure that this type exists ` +
206
+ 'either in the original schema, or is added in a type definition.' ,
207
+ [ typeRef ] ,
208
+ ) ;
209
209
} ,
210
210
) ;
211
211
212
+ const extendTypeCache = Object . create ( null ) ;
213
+
212
214
// Get the root Query, Mutation, and Subscription object types.
213
215
// Note: While this could make early assertions to get the correctly
214
216
// typed values below, that would throw immediately while type system
215
217
// validation with validateSchema() will produce more actionable results.
216
218
const existingQueryType = schema . getQueryType ( ) ;
217
219
const queryType = existingQueryType
218
- ? ( definitionBuilder . buildType ( existingQueryType . name ) : any )
220
+ ? getExtendedType ( existingQueryType )
219
221
: null ;
220
222
221
223
const existingMutationType = schema . getMutationType ( ) ;
222
224
const mutationType = existingMutationType
223
- ? ( definitionBuilder . buildType ( existingMutationType . name ) : any )
225
+ ? getExtendedType ( existingMutationType )
224
226
: null ;
225
227
226
228
const existingSubscriptionType = schema . getSubscriptionType ( ) ;
227
229
const subscriptionType = existingSubscriptionType
228
- ? ( definitionBuilder . buildType ( existingSubscriptionType . name ) : any )
230
+ ? getExtendedType ( existingSubscriptionType )
229
231
: null ;
230
232
231
- // Iterate through all types, getting the type definition for each, ensuring
232
- // that any type not directly referenced by a field will get created.
233
- const typeMap = schema . getTypeMap ( ) ;
234
- const types = Object . keys ( typeMap ) . map ( typeName =>
235
- definitionBuilder . buildType ( typeName ) ,
236
- ) ;
237
-
238
- // Do the same with new types, appending to the list of defined types.
239
- Object . keys ( typeDefinitionMap ) . forEach ( typeName => {
240
- types . push ( definitionBuilder . buildType ( typeName ) ) ;
241
- } ) ;
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
+ ] ;
242
240
243
241
// Support both original legacy names and extended legacy names.
244
242
const schemaAllowedLegacyNames = schema . __allowedLegacyNames ;
@@ -277,20 +275,24 @@ export function extendSchema(
277
275
const existingDirectives = schema . getDirectives ( ) ;
278
276
invariant ( existingDirectives , 'schema must have default directives' ) ;
279
277
280
- const newDirectives = directiveDefinitions . map ( directiveNode =>
281
- definitionBuilder . buildDirective ( directiveNode ) ,
278
+ return existingDirectives . concat (
279
+ directiveDefinitions . map ( node => astBuilder . buildDirective ( node ) ) ,
282
280
) ;
283
- return existingDirectives . concat ( newDirectives ) ;
284
281
}
285
282
286
- function getTypeFromDef < T : GraphQLNamedType > (typeDef: T): T {
287
- const type = definitionBuilder . buildType ( typeDef . name ) ;
288
- return ( type : any ) ;
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);
289
288
}
290
289
291
- // Given a type's introspection result, construct the correct
292
- // GraphQLType instance.
293
- function extendType(type: GraphQLNamedType): GraphQLNamedType {
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
+ }
294
296
if ( isObjectType ( type ) ) {
295
297
return extendObjectType ( type ) ;
296
298
}
@@ -300,6 +302,7 @@ export function extendSchema(
300
302
if ( isUnionType ( type ) ) {
301
303
return extendUnionType ( type ) ;
302
304
}
305
+ // This type is not yet extendable.
303
306
return type ;
304
307
}
305
308
@@ -344,7 +347,7 @@ export function extendSchema(
344
347
return new GraphQLUnionType ( {
345
348
name : type . name ,
346
349
description : type . description ,
347
- types : type . getTypes ( ) . map ( getTypeFromDef ) ,
350
+ types : type . getTypes ( ) . map ( getExtendedType ) ,
348
351
astNode : type . astNode ,
349
352
resolveType : type . resolveType ,
350
353
} ) ;
@@ -353,7 +356,7 @@ export function extendSchema(
353
356
function extendImplementedInterfaces (
354
357
type : GraphQLObjectType ,
355
358
) : Array < GraphQLInterfaceType > {
356
- const interfaces = type . getInterfaces ( ) . map ( getTypeFromDef ) ;
359
+ const interfaces = type . getInterfaces ( ) . map ( getExtendedType ) ;
357
360
358
361
// If there are any extensions to the interfaces, apply those here.
359
362
const extensions = typeExtensionsMap [ type . name ] ;
@@ -363,7 +366,7 @@ export function extendSchema(
363
366
// Note: While this could make early assertions to get the correctly
364
367
// typed values, that would throw immediately while type system
365
368
// validation with validateSchema() will produce more actionable results.
366
- interfaces . push ( ( definitionBuilder . buildType ( namedType ) : any ) ) ;
369
+ interfaces . push ( ( astBuilder . buildType ( namedType ) : any ) ) ;
367
370
} ) ;
368
371
} ) ;
369
372
}
@@ -399,7 +402,7 @@ export function extendSchema(
399
402
[ field ] ,
400
403
) ;
401
404
}
402
- newFieldMap [ fieldName ] = definitionBuilder . buildField ( field ) ;
405
+ newFieldMap [ fieldName ] = astBuilder . buildField ( field ) ;
403
406
} ) ;
404
407
} ) ;
405
408
}
@@ -414,6 +417,6 @@ export function extendSchema(
414
417
if ( isNonNullType ( typeDef ) ) {
415
418
return ( GraphQLNonNull ( extendFieldType ( typeDef . ofType ) ) : any ) ;
416
419
}
417
- return getTypeFromDef (typeDef);
420
+ return getExtendedType ( typeDef ) ;
418
421
}
419
422
}
0 commit comments