import invariant from '../jsutils/invariant' ; import keyValMap from '../jsutils/keyValMap' ; import { valueFromAST } from 'graphql/utilities/valueFromAST' ; import { getArgumentValues } from '../execution/values' ; import { getDescription , getDeprecationReason , getNamedTypeNode , locationForDirectiveDefinition , } from '../utilities' ; import { TypeResolverMap , FieldResolverMap , ScalarResolverMap , } from '../utilities/ResolverMap' ; import { FactoryMiddleware , } from './FactoryMiddleware' ; import { Map } from 'immutable' ; import { GraphQLFieldResolver , parse , Kind , } from 'graphql' ; import { LIST_TYPE , NON_NULL_TYPE , SCALAR_TYPE_DEFINITION , OBJECT_TYPE_DEFINITION , INTERFACE_TYPE_DEFINITION , ENUM_TYPE_DEFINITION , UNION_TYPE_DEFINITION , INPUT_OBJECT_TYPE_DEFINITION , SCHEMA_DEFINITION , DIRECTIVE_DEFINITION , TYPE_EXTENSION_DEFINITION , } from 'graphql/language/kinds' ; import { DirectiveNode , TypeNode , SchemaDefinitionNode , TypeDefinitionNode , ScalarTypeDefinitionNode , ObjectTypeDefinitionNode , InputValueDefinitionNode , InterfaceTypeDefinitionNode , UnionTypeDefinitionNode , EnumTypeDefinitionNode , InputObjectTypeDefinitionNode , DirectiveDefinitionNode , FieldDefinitionNode , } from 'graphql/language/ast' ; import { GraphQLSchema } from 'graphql/type/schema' ; import { GraphQLString , GraphQLInt , GraphQLFloat , GraphQLBoolean , GraphQLID , } from 'graphql/type/scalars' ; import { GraphQLScalarType , GraphQLObjectType , GraphQLInterfaceType , GraphQLUnionType , GraphQLEnumType , GraphQLInputObjectType , GraphQLList , GraphQLNonNull , isInputType , isOutputType , GraphQLTypeResolver , } from 'graphql/type/definition' ; import { GraphQLObjectTypeExt } from '../type/object' ; import { GraphQLType , GraphQLNamedType , GraphQLInputType , GraphQLOutputType , GraphQLInputFieldConfigMap , GraphQLFieldConfigArgumentMap , } from 'graphql/type/definition' ; import { GraphQLDirective , GraphQLSkipDirective , GraphQLIncludeDirective , GraphQLDeprecatedDirective , DirectiveLocation , } from 'graphql/type/directives' ; import { GraphQLFieldConfigMapExt , GraphQLFieldConfigExt , } from '../type/object' ; import { __Schema , __Directive , __DirectiveLocation , __Type , __Field , __InputValue , __EnumValue , __TypeKind , } from 'graphql/type/introspection' ; import { GraphQLDirectiveValue , } from '../type/directives' ; const DEFAULT_DIRECTIVES = { deprecated : GraphQLDeprecatedDirective , include : GraphQLIncludeDirective , skip : GraphQLSkipDirective , } ; function buildWrappedType ( innerType , inputTypeNode ) { if ( inputTypeNode . kind === LIST_TYPE ) { return new GraphQLList ( buildWrappedType ( innerType , inputTypeNode . type ) ) ; } if ( inputTypeNode . kind === NON_NULL_TYPE ) { const wrappedType = buildWrappedType ( innerType , inputTypeNode . type ) ; invariant ( ! ( wrappedType instanceof GraphQLNonNull ) , 'No nesting nonnull.' ) ; return new GraphQLNonNull ( wrappedType ) ; } return innerType ; } function keywordForDefinition ( node ) { switch ( node . kind ) { case SCALAR_TYPE_DEFINITION : return 'scalar' ; case OBJECT_TYPE_DEFINITION : return 'object' ; case INTERFACE_TYPE_DEFINITION : return 'scalar' ; case ENUM_TYPE_DEFINITION : return 'enum' ; case UNION_TYPE_DEFINITION : return 'union' ; case INPUT_OBJECT_TYPE_DEFINITION : return 'input' ; default : return 'unknown' ; } } function keywordForTypeKind ( type ) { if ( type instanceof GraphQLObjectType ) { return 'object' ; } else if ( type instanceof GraphQLScalarType ) { return 'scalar' ; } else if ( type instanceof GraphQLInterfaceType ) { return 'interface' ; } else if ( type instanceof GraphQLEnumType ) { return 'enum' ; } else if ( type instanceof GraphQLUnionType ) { return 'union' ; } else if ( type instanceof GraphQLInputObjectType ) { return 'input' ; } return 'unknown' ; } function validateDirectiveLocations ( directive ) { for ( let location of directive . locations ) { if ( ! DirectiveLocation [ location . value ] ) { throw new Error ( `The directive '${ directive . name . value }' has an invalid location '${ location . value }'.\n` + `Try one of [ ${ Object . keys ( DirectiveLocation ) . join ( ', ' ) } ].` ) ; } } } export interface SchemaFactoryConfig { middleware ? : FactoryMiddleware ; onResolverCollision ? : ( oldFn : FieldResolverMap , newFn : FieldResolverMap , key : string ) => FieldResolverMap ; onTypeCollision ? : ( oldType : GraphQLNamedType , newType : GraphQLNamedType , key : string ) => GraphQLNamedType ; } /**
 * A base class that handles managing and building GraphQL schemas
 */ export class SchemaFactory { protected schemaDef : SchemaDefinitionNode | null ; protected typeMap : Map < string , GraphQLNamedType > ; protected nodeMap : Map < string , TypeDefinitionNode > ; protected resolverMap : Map < string , FieldResolverMap > ; protected typeResolverMap : Map < string , GraphQLTypeResolver < mixed , mixed > > ; protected directiveMap : Map < string , GraphQLDirective > ; protected scalarResolvers : Map < string , ScalarResolverMap < mixed , mixed > > ; private config : SchemaFactoryConfig ; private middleware : FactoryMiddleware ; constructor ( config = { } ) { this . config = config ; this . middleware = config . middleware ? config . middleware : new FactoryMiddleware ( ) ; this . nodeMap = Map < string , TypeDefinitionNode > ( ) ; this . typeResolverMap = Map < string , GraphQLTypeResolver < mixed , mixed > > ( ) ; this . typeMap = Map < string , GraphQLNamedType > ( { String : GraphQLString , Int : GraphQLInt , Float : GraphQLFloat , Boolean : GraphQLBoolean , ID : GraphQLID , __Schema , __Directive , __DirectiveLocation , __Type , __Field , __InputValue , __EnumValue , __TypeKind , } ) ; this . schemaDef = null ; this . directiveMap = Map < string , GraphQLDirective > ( DEFAULT_DIRECTIVES ) ; this . resolverMap = Map < string , FieldResolverMap > ( ) ; this . middleware . onInit ( this ) ; } /**
     * Public API
     */ private buildSchema ( ) { this . middleware . beforeBuild ( this ) ; let queryTypeName = null ; let mutationTypeName = null ; let subscriptionTypeName = null ; if ( this . schemaDef ) { this . schemaDef . operationTypes . forEach ( operationType => { const typeName = operationType . type . name . value ; if ( operationType . operation === 'query' ) { if ( queryTypeName ) { throw new Error ( 'Must provide only one query type in schema.' ) ; } if ( ! this . nodeMap . get ( typeName ) ) { throw new Error ( `Specified query type "${ typeName }" not found in document.` ) ; } queryTypeName = typeName ; } else if ( operationType . operation === 'mutation' ) { if ( mutationTypeName ) { throw new Error ( 'Must provide only one mutation type in schema.' ) ; } if ( ! this . nodeMap . get ( typeName ) ) { throw new Error ( `Specified mutation type "${ typeName }" not found in document.` ) ; } mutationTypeName = typeName ; } else if ( operationType . operation === 'subscription' ) { if ( subscriptionTypeName ) { throw new Error ( 'Must provide only one subscription type in schema.' ) ; } if ( ! this . nodeMap . get ( typeName ) ) { throw new Error ( `Specified subscription type "${ typeName }" not found in document.` ) ; } subscriptionTypeName = typeName ; } } ) ; } else { if ( this . nodeMap . has ( 'Query' ) || this . typeMap . has ( 'Query' ) ) { queryTypeName = 'Query' ; } if ( this . nodeMap . has ( 'Mutation' ) || this . typeMap . has ( 'Mutation' ) ) { mutationTypeName = 'Mutation' ; } if ( this . nodeMap . has ( 'Subscription' ) || this . typeMap . has ( 'Subscription' ) ) { subscriptionTypeName = 'Subscription' ; } } if ( ! queryTypeName ) { throw new Error ( 'Must provide schema definition with query type or a type named Query.' ) ; } const types = this . nodeMap . valueSeq ( ) . map ( def => this . typeDefNamed ( def ! . name . value ) ) . toArray ( ) ; let directives = this . directiveMap . toArray ( ) ; return this . middleware . afterBuild ( new GraphQLSchema ( { query : this . getObjectTypeNamed ( queryTypeName ) , mutation : mutationTypeName ? this . getObjectTypeNamed ( mutationTypeName ) : undefined , subscription : subscriptionTypeName ? this . getObjectTypeNamed ( subscriptionTypeName ) : undefined , types , directives , } ) ) ; } public getSchema ( ) { return this . buildSchema ( ) ; } public getType ( name ) { return this . typeDefNamed ( name ) ; } public hasType ( name ) { return this . typeMap . has ( name ) ; } public hasASTNode ( name ) { return this . nodeMap . has ( name ) ; } public getASTNode ( name ) { return this . nodeMap . get ( name ) ; } /**
     * Getters
     */ get nodes ( ) : Array < TypeDefinitionNode > { return this . nodeMap . valueSeq ( ) . toArray ( ) ; } get types ( ) : Array < GraphQLNamedType > { return this . typeMap . valueSeq ( ) . toArray ( ) ; } /**
     * Add resolvers to the resolver map. These resolvers will get merged into types
     * when you call getSchema().
     */ public addResolveFunctionsToSchema ( resolvers = { } ) { this . resolverMap = this . resolverMap . mergeWith ( ( oldVal , newVal , key ) => { if ( oldVal && newVal ) { if ( this . config . onResolverCollision ) { return this . config . onResolverCollision ( oldVal , newVal , key ) ; } return { ... oldVal , ... newVal , } ; } return newVal as FieldResolverMap ; } , resolvers ) ; return this ; } param spec param resolvers public createType ( spec , resolvers = { } ) { const def = parse ( spec ) ; invariant ( def && def . definitions . length === 1 , 'Factory.createType expects exactly one definition' ) ; const definition = def . definitions [ 0 ] ; invariant ( definition . kind === Kind . OBJECT_TYPE_DEFINITION , `Factory.createType expects a single definition of kind ${ Kind . OBJECT_TYPE_DEFINITION }` ) ; const objectDef = ( definition as ObjectTypeDefinitionNode ) ; this . assertIsNewType ( objectDef . name . value ) ; this . nodeMap = this . nodeMap . set ( objectDef . name . value , objectDef ) ; this . resolverMap = this . resolverMap . set ( objectDef . name . value , resolvers ) ; return this ; } param spec param resolveType public createInterface ( spec , resolveType ) { const def = parse ( spec ) ; invariant ( def && def . definitions . length === 1 , 'Factory.createInterface expects exactly one definition' ) ; const definition = def . definitions [ 0 ] ; invariant ( definition . kind === Kind . INTERFACE_TYPE_DEFINITION , `Factory.createInterface expects a single definition of kind ${ Kind . INTERFACE_TYPE_DEFINITION }` ) ; const iDef = ( definition as InterfaceTypeDefinitionNode ) ; this . assertIsNewType ( iDef . name . value ) ; this . nodeMap = this . nodeMap . set ( iDef . name . value , iDef ) ; this . typeResolverMap = this . typeResolverMap . set ( iDef . name . value , resolveType ) ; return this ; } param spec public createEnum ( spec ) { const def = parse ( spec ) ; invariant ( def && def . definitions . length === 1 , 'Factory.createEnum expects exactly one definition' ) ; const definition = def . definitions [ 0 ] ; invariant ( definition . kind === Kind . ENUM_TYPE_DEFINITION , `Factory.createEnum expects a single definition of kind ${ Kind . ENUM_TYPE_DEFINITION }` ) ; const eDef = ( definition as EnumTypeDefinitionNode ) ; this . assertIsNewType ( eDef . name . value ) ; this . nodeMap = this . nodeMap . set ( eDef . name . value , eDef ) ; return this ; } param spec param resolveType public createUnion ( spec , resolveType ) { const def = parse ( spec ) ; invariant ( def && def . definitions . length === 1 , 'Factory.createEnum expects exactly one definition' ) ; const definition = def . definitions [ 0 ] ; invariant ( definition . kind === Kind . ENUM_TYPE_DEFINITION , `Factory.createEnum expects a single definition of kind ${ Kind . ENUM_TYPE_DEFINITION }` ) ; const uDef = ( definition as EnumTypeDefinitionNode ) ; this . assertIsNewType ( uDef . name . value ) ; this . nodeMap = this . nodeMap . set ( uDef . name . value , uDef ) ; this . typeResolverMap = this . typeResolverMap . set ( uDef . name . value , resolveType ) ; return this ; } param types public extendWithTypes ( types ) { const that = this ; const keyedByName = keyValMap ( types , type => type . name , type => type ) ; this . typeMap = this . typeMap . mergeWith ( ( oldType , newType , key ) => { if ( that . config . onTypeCollision && oldType && newType && key ) { return that . config . onTypeCollision ( oldType , newType , key ) ; } throw new Error ( `A type with name ${ key } already exists. ` + 'You can extend this type using the extend keyword with Factory.extendWithSpec()' ) ; } , keyedByName ) ; return that ; } param spec public extendWithSpec ( spec , resolvers = { } ) { const def = parse ( spec ) ; if ( ! def ) { throw new Error ( 'GraphQL spec must have atleast one definition' ) ; } def . definitions . forEach ( d => { switch ( d . kind ) { case SCHEMA_DEFINITION : if ( this . schemaDef ) { throw new Error ( 'Must provide only one schema definition.' ) ; } this . schemaDef = d as SchemaDefinitionNode ; break ; case SCALAR_TYPE_DEFINITION : case OBJECT_TYPE_DEFINITION : case INTERFACE_TYPE_DEFINITION : case ENUM_TYPE_DEFINITION : case UNION_TYPE_DEFINITION : case INPUT_OBJECT_TYPE_DEFINITION : this . assertIsNewType ( d . name . value ) ; this . nodeMap = this . nodeMap . set ( ( d as TypeDefinitionNode ) . name . value , d as TypeDefinitionNode ) ; break ; case DIRECTIVE_DEFINITION : this . directiveMap = this . directiveMap . set ( ( d as DirectiveDefinitionNode ) . name . value , this . getDirective ( d ) ) ; break ; case TYPE_EXTENSION_DEFINITION : break ; default : break ; } } ) ; this . addResolveFunctionsToSchema ( resolvers ) ; return this ; } param name private assertIsNewType ( name ) { if ( this . nodeMap . has ( name ) ) { const keyword = keywordForDefinition ( this . nodeMap . get ( name ) ) ; throw new Error ( `A ${ keyword } with name ${ name } already exists. ` + `You can extend this ${ keyword } using the extend keyword with Factory.extendWithSpec()` ) ; } else if ( this . typeMap . has ( name ) ) { const tKeyword = keywordForTypeKind ( this . typeMap . get ( name ) ) ; throw new Error ( `A ${ tKeyword } with name ${ name } already exists. ` + `You can extend this ${ tKeyword } using the extend keyword with Factory.extendWithSpec()` ) ; } } param directiveNode protected getDirective ( directiveNode ) { validateDirectiveLocations ( directiveNode ) ; return new GraphQLDirective ( { name : directiveNode . name . value , description : getDescription ( directiveNode ) , locations : directiveNode . locations . map ( node => node . value ) , args : directiveNode . arguments && this . makeInputValues ( directiveNode . arguments ) as GraphQLFieldConfigArgumentMap , } ) ; } protected getObjectTypeNamed ( name ) { const type = this . typeDefNamed ( name ) ; invariant ( type instanceof GraphQLObjectType , 'AST must provide object type.' ) ; return type as GraphQLObjectType ; } protected getObjectType ( typeNode ) { const type = this . typeDefNamed ( typeNode . name . value ) ; invariant ( type instanceof GraphQLObjectType , 'AST must provide object type.' ) ; return type as GraphQLObjectType ; } protected produceType ( typeNode ) { const typeName = getNamedTypeNode ( typeNode ) . name . value ; const typeDef = this . typeDefNamed ( typeName ) ; return buildWrappedType ( typeDef , typeNode ) ; } protected produceInputType ( typeNode ) { const type = this . produceType ( typeNode ) ; invariant ( isInputType ( type ) , 'Expected Input type.' ) ; return type as GraphQLInputType ; } protected produceOutputType ( typeNode ) { const type = this . produceType ( typeNode ) ; invariant ( isOutputType ( type ) , 'Expected Output type.' ) ; return type as GraphQLOutputType ; } protected produceObjectType ( typeNode ) { const type = this . produceType ( typeNode ) ; invariant ( type instanceof GraphQLObjectType , 'Expected Object type.' ) ; return type as GraphQLObjectType ; } protected produceInterfaceType ( typeNode ) { const type = this . produceType ( typeNode ) ; invariant ( type instanceof GraphQLInterfaceType , 'Expected Interface type.' ) ; return type as GraphQLInterfaceType ; } protected produceDirectiveValue ( def , directiveNode ) { const directive = this . directiveMap . get ( directiveNode . name . value ) ; if ( ! directive ) { throw new Error ( `Unrecognized directive ${ directiveNode . name . value } found on schema.` ) ; } return new GraphQLDirectiveValue ( { name : directiveNode . name . value , args : getArgumentValues ( directive , directiveNode ) , location : locationForDirectiveDefinition ( directive , def ) , } ) ; } protected typeDefNamed ( typeName ) { if ( this . typeMap . has ( typeName ) ) { return this . typeMap . get ( typeName ) ; } if ( ! this . nodeMap . has ( typeName ) ) { throw new Error ( `Type "${ typeName }" not found in document.` ) ; } const innerTypeDef = this . makeSchemaDef ( this . nodeMap . get ( typeName ) ) ; if ( ! innerTypeDef ) { throw new Error ( `Nothing constructed for "${ typeName }".` ) ; } this . typeMap = this . typeMap . set ( typeName , innerTypeDef ) ; return innerTypeDef ; } protected makeSchemaDef ( def ) { if ( ! def ) { throw new Error ( 'def must be defined' ) ; } switch ( def . kind ) { case OBJECT_TYPE_DEFINITION : return this . makeTypeDef ( def ) ; case INTERFACE_TYPE_DEFINITION : return this . makeInterfaceDef ( def ) ; case ENUM_TYPE_DEFINITION : return this . makeEnumDef ( def ) ; case UNION_TYPE_DEFINITION : return this . makeUnionDef ( def ) ; case SCALAR_TYPE_DEFINITION : return this . makeScalarDef ( def ) ; case INPUT_OBJECT_TYPE_DEFINITION : return this . makeInputObjectDef ( def ) ; default : throw new Error ( `Type kind "${ def }" not supported.` ) ; } } protected makeTypeDef ( def ) { const typeName = def . name . value ; return this . middleware . wrapObjectType ( this , def , new GraphQLObjectTypeExt ( { name : typeName , description : getDescription ( def ) , fields : ( ) => this . makeObjectFieldDefMap ( def ) , interfaces : ( ) => this . makeImplementedInterfaces ( def ) , directives : ( ) => this . makeDirectiveValues ( def ) , } ) ) ; } protected getResolver ( type , field ) { const fieldResolver = this . resolverMap . getIn ( [ type . name . value , field . name . value ] ) ; if ( fieldResolver ) { return fieldResolver ; } return ; } protected makeObjectFieldDefMap ( def ) { return keyValMap < FieldDefinitionNode , GraphQLFieldConfigExt < mixed , mixed > > ( def . fields , field => field . name . value , field => ( this . middleware . wrapObjectField ( this , def , field , { type : this . produceOutputType ( field . type ) , description : getDescription ( field ) , args : this . makeInputValues ( field . arguments ) as GraphQLFieldConfigArgumentMap , deprecationReason : getDeprecationReason ( field . directives ) , directives : this . makeDirectiveValues ( field ) , resolve : this . getResolver ( def , field ) , } ) ) ) ; } protected makeInterfaceFieldDefMap ( def ) { return keyValMap < FieldDefinitionNode , GraphQLFieldConfigExt < mixed , mixed > > ( def . fields , field => field . name . value , field => ( this . middleware . wrapInterfaceField ( this , def , field , { type : this . produceOutputType ( field . type ) , description : getDescription ( field ) , args : this . makeInputValues ( field . arguments ) as GraphQLFieldConfigArgumentMap , deprecationReason : getDeprecationReason ( field . directives ) , directives : this . makeDirectiveValues ( field ) , } ) ) ) ; } protected makeImplementedInterfaces ( def ) { return def . interfaces ? def . interfaces . map ( iface => this . produceInterfaceType ( iface ) ) : [ ] ; } protected makeDirectiveValues ( def ) { return def . directives ? def . directives . map ( dir => this . produceDirectiveValue ( def , dir ) ) : [ ] ; } protected makeInputValues ( values ) { return keyValMap ( values , value => value . name . value , value => { const type = this . produceInputType ( value . type ) ; return { type , description : getDescription ( value ) , defaultValue : valueFromAST ( value . defaultValue ! , type ) , } ; } ) ; } protected makeInterfaceDef ( def ) { const typeName = def . name . value ; return this . middleware . wrapInterfaceType ( this , def , new GraphQLInterfaceType ( { name : typeName , description : getDescription ( def ) , fields : ( ) => this . makeInterfaceFieldDefMap ( def ) , resolveType : cannotExecuteSchema , } ) ) ; } protected makeEnumDef ( def ) { return this . middleware . wrapEnumType ( this , def , new GraphQLEnumType ( { name : def . name . value , description : getDescription ( def ) , values : keyValMap ( def . values , enumValue => enumValue . name . value , enumValue => ( { description : getDescription ( enumValue ) , deprecationReason : getDeprecationReason ( enumValue . directives ) , } ) ) , } ) ) ; } protected makeUnionDef ( def ) { return this . middleware . wrapUnionType ( this , def , new GraphQLUnionType ( { name : def . name . value , description : getDescription ( def ) , types : def . types . map ( t => this . produceObjectType ( t ) ) , resolveType : cannotExecuteSchema , } ) ) ; } protected makeScalarDef ( def ) { return this . middleware . wrapScalarType ( this , def , new GraphQLScalarType ( { name : def . name . value , description : getDescription ( def ) , serialize : ( ) => null , parseValue : ( ) => false , parseLiteral : ( ) => false , } ) ) ; } protected makeInputObjectDef ( def ) { return this . middleware . wrapInputType ( this , def , new GraphQLInputObjectType ( { name : def . name . value , description : getDescription ( def ) , fields : ( ) => this . makeInputValues ( def . fields ) as GraphQLInputFieldConfigMap , } ) ) ; } } const cannotExecuteSchema = ( ) => { throw new Error ( 'Generated Schema cannot use Interface or Union types for execution.' ) ; } ;