-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make 'ASTDefinitionBuilder' responsible only for build types from AST #1230
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,9 +9,11 @@ | |
|
||
import invariant from '../jsutils/invariant'; | ||
import keyMap from '../jsutils/keyMap'; | ||
import objectValues from '../jsutils/objectValues'; | ||
import { ASTDefinitionBuilder } from './buildASTSchema'; | ||
import { GraphQLError } from '../error/GraphQLError'; | ||
import { isSchema, GraphQLSchema } from '../type/schema'; | ||
import { isIntrospectionType } from '../type/introspection'; | ||
|
||
import { | ||
isObjectType, | ||
|
@@ -194,56 +196,52 @@ export function extendSchema( | |
return schema; | ||
} | ||
|
||
const definitionBuilder = new ASTDefinitionBuilder( | ||
const astBuilder = new ASTDefinitionBuilder( | ||
typeDefinitionMap, | ||
options, | ||
(typeName, node) => { | ||
typeRef => { | ||
const typeName = typeRef.name.value; | ||
const existingType = schema.getType(typeName); | ||
if (existingType) { | ||
return extendType(existingType); | ||
return getExtendedType(existingType); | ||
} | ||
|
||
if (node) { | ||
throw new GraphQLError( | ||
`Unknown type: "${typeName}". Ensure that this type exists ` + | ||
'either in the original schema, or is added in a type definition.', | ||
[node], | ||
); | ||
} | ||
throw GraphQLError('Missing type from schema'); | ||
throw new GraphQLError( | ||
`Unknown type: "${typeName}". Ensure that this type exists ` + | ||
'either in the original schema, or is added in a type definition.', | ||
[typeRef], | ||
); | ||
}, | ||
); | ||
|
||
const extendTypeCache = Object.create(null); | ||
|
||
// Get the root Query, Mutation, and Subscription object types. | ||
// Note: While this could make early assertions to get the correctly | ||
// typed values below, that would throw immediately while type system | ||
// validation with validateSchema() will produce more actionable results. | ||
const existingQueryType = schema.getQueryType(); | ||
const queryType = existingQueryType | ||
? (definitionBuilder.buildType(existingQueryType.name): any) | ||
? getExtendedType(existingQueryType) | ||
: null; | ||
|
||
const existingMutationType = schema.getMutationType(); | ||
const mutationType = existingMutationType | ||
? (definitionBuilder.buildType(existingMutationType.name): any) | ||
? getExtendedType(existingMutationType) | ||
: null; | ||
|
||
const existingSubscriptionType = schema.getSubscriptionType(); | ||
const subscriptionType = existingSubscriptionType | ||
? (definitionBuilder.buildType(existingSubscriptionType.name): any) | ||
? getExtendedType(existingSubscriptionType) | ||
: null; | ||
|
||
// Iterate through all types, getting the type definition for each, ensuring | ||
// that any type not directly referenced by a field will get created. | ||
const typeMap = schema.getTypeMap(); | ||
const types = Object.keys(typeMap).map(typeName => | ||
definitionBuilder.buildType(typeName), | ||
); | ||
|
||
// Do the same with new types, appending to the list of defined types. | ||
Object.keys(typeDefinitionMap).forEach(typeName => { | ||
types.push(definitionBuilder.buildType(typeName)); | ||
}); | ||
const types = [ | ||
// Iterate through all types, getting the type definition for each, ensuring | ||
// that any type not directly referenced by a field will get created. | ||
...objectValues(schema.getTypeMap()).map(type => getExtendedType(type)), | ||
// Do the same with new types. | ||
...objectValues(typeDefinitionMap).map(type => astBuilder.buildType(type)), | ||
]; | ||
|
||
// Then produce and return a Schema with these types. | ||
return new GraphQLSchema({ | ||
|
@@ -275,30 +273,32 @@ export function extendSchema( | |
const existingDirectives = schema.getDirectives(); | ||
invariant(existingDirectives, 'schema must have default directives'); | ||
|
||
const newDirectives = directiveDefinitions.map(directiveNode => | ||
definitionBuilder.buildDirective(directiveNode), | ||
return existingDirectives.concat( | ||
directiveDefinitions.map(node => astBuilder.buildDirective(node)), | ||
); | ||
return existingDirectives.concat(newDirectives); | ||
} | ||
|
||
function getTypeFromDef<T: GraphQLNamedType>(typeDef: T): T { | ||
const type = definitionBuilder.buildType(typeDef.name); | ||
return (type: any); | ||
function getExtendedType<T: GraphQLNamedType>(type: T): T { | ||
if (!extendTypeCache[type.name]) { | ||
extendTypeCache[type.name] = extendType(type); | ||
} | ||
return (extendTypeCache[type.name]: any); | ||
} | ||
|
||
// Given a type's introspection result, construct the correct | ||
// GraphQLType instance. | ||
function extendType(type: GraphQLNamedType): GraphQLNamedType { | ||
if (isObjectType(type)) { | ||
return extendObjectType(type); | ||
} | ||
if (isInterfaceType(type)) { | ||
return extendInterfaceType(type); | ||
} | ||
if (isUnionType(type)) { | ||
return extendUnionType(type); | ||
// Should be called only once per type so only getExtendedType should call it. | ||
function extendType<T: GraphQLNamedType>(type: T): T { | ||
let extendedType = type; | ||
if (!isIntrospectionType(type)) { | ||
if (isObjectType(type)) { | ||
extendedType = extendObjectType(type); | ||
} else if (isInterfaceType(type)) { | ||
extendedType = extendInterfaceType(type); | ||
} else if (isUnionType(type)) { | ||
extendedType = extendUnionType(type); | ||
} | ||
} | ||
return type; | ||
// Workaround: Flow should figure out correct type, but it doesn't. | ||
return (extendedType: any); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rather than mutating a variable in each branch, just return the value in each branch. Flow can't figure out what the type of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @leebyron I did that initially but it forces me to do typecast in every branch. Plus explaining workaround is more complicated.
Flow has exactly the same problem figuring types both with or without mutations. You can see it in CI logs for your commit and in Flow playground: |
||
} | ||
|
||
function extendObjectType(type: GraphQLObjectType): GraphQLObjectType { | ||
|
@@ -342,7 +342,7 @@ export function extendSchema( | |
return new GraphQLUnionType({ | ||
name: type.name, | ||
description: type.description, | ||
types: type.getTypes().map(getTypeFromDef), | ||
types: type.getTypes().map(getExtendedType), | ||
astNode: type.astNode, | ||
resolveType: type.resolveType, | ||
}); | ||
|
@@ -351,7 +351,7 @@ export function extendSchema( | |
function extendImplementedInterfaces( | ||
type: GraphQLObjectType, | ||
): Array<GraphQLInterfaceType> { | ||
const interfaces = type.getInterfaces().map(getTypeFromDef); | ||
const interfaces = type.getInterfaces().map(getExtendedType); | ||
|
||
// If there are any extensions to the interfaces, apply those here. | ||
const extensions = typeExtensionsMap[type.name]; | ||
|
@@ -361,7 +361,7 @@ export function extendSchema( | |
// Note: While this could make early assertions to get the correctly | ||
// typed values, that would throw immediately while type system | ||
// validation with validateSchema() will produce more actionable results. | ||
interfaces.push((definitionBuilder.buildType(namedType): any)); | ||
interfaces.push((astBuilder.buildType(namedType): any)); | ||
}); | ||
}); | ||
} | ||
|
@@ -397,7 +397,7 @@ export function extendSchema( | |
[field], | ||
); | ||
} | ||
newFieldMap[fieldName] = definitionBuilder.buildField(field); | ||
newFieldMap[fieldName] = astBuilder.buildField(field); | ||
}); | ||
}); | ||
} | ||
|
@@ -412,6 +412,6 @@ export function extendSchema( | |
if (isNonNullType(typeDef)) { | ||
return (GraphQLNonNull(extendFieldType(typeDef.ofType)): any); | ||
} | ||
return getTypeFromDef(typeDef); | ||
return getExtendedType(typeDef); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest factoring this into two functions,
getExtendedType
andextendType
, the first one performing caching (with a comment that extendType need only be called once per type). That should allow you to remove or at least further isolate theany
cast.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@leebyron Great idea 👍 I will make the change in a few minutes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@leebyron Sadly there is some kind of bug with Flow which prevents the suggested change:

You can test it here online but demo broken for
0.65
at the moment so you need to switch it to0.64
. But I tested0.65
locally and the result is the same.What should I do?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cast through any internally while keeping the correct function definition, and we'll report it to the flow team
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@leebyron Done. Can you please review?