Skip to content

Commit ed02120

Browse files
committed
Reuse transformSchema inside extendSchema
1 parent 6b051ab commit ed02120

File tree

3 files changed

+155
-266
lines changed

3 files changed

+155
-266
lines changed

src/utilities/extendSchema.js

+70-206
Original file line numberDiff line numberDiff line change
@@ -8,33 +8,18 @@
88
*/
99

1010
import invariant from '../jsutils/invariant';
11-
import keyMap from '../jsutils/keyMap';
12-
import objectValues from '../jsutils/objectValues';
1311
import { ASTDefinitionBuilder } from './buildASTSchema';
12+
import { SchemaTransformer } from './transformSchema';
1413
import { GraphQLError } from '../error/GraphQLError';
1514
import { isSchema, GraphQLSchema } from '../type/schema';
16-
import { isIntrospectionType } from '../type/introspection';
17-
1815
import type { GraphQLSchemaValidationOptions } from '../type/schema';
19-
2016
import {
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-
3422
import { Kind } from '../language/kinds';
35-
36-
import type { GraphQLType, GraphQLNamedType } from '../type/definition';
37-
3823
import 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
}

src/utilities/lexicographicSortSchema.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ import {
1818
GraphQLEnumType,
1919
GraphQLInputObjectType,
2020
} from '../type/definition';
21-
import { transformSchema } from './transformSchema';
21+
import { SchemaTransformer } from './transformSchema';
2222

2323
/**
2424
* Sort GraphQLSchema.
2525
*/
2626
export function lexicographicSortSchema(schema: GraphQLSchema): GraphQLSchema {
27-
return transformSchema(schema, {
27+
const transformer = new SchemaTransformer(schema, {
2828
Schema(config) {
2929
return new GraphQLSchema({
3030
...config,
@@ -74,6 +74,8 @@ export function lexicographicSortSchema(schema: GraphQLSchema): GraphQLSchema {
7474
});
7575
},
7676
});
77+
78+
return transformer.transformSchema();
7779
}
7880

7981
function sortFields(fields) {

0 commit comments

Comments
 (0)