import pluralize from 'pluralize' ; import { graphql as executeGraphQL , GraphQLObjectType , GraphQLID , GraphQLInt , GraphQLString , GraphQLList , GraphQLSchema , GraphQLFieldConfigMap , GraphQLInputObjectType , GraphQLInputFieldConfigMap , GraphQLBoolean , GraphQLInputType , GraphQLScalarType , GraphQLFieldConfigArgumentMap , } from 'graphql' ; import { GraphQLHandler , graphql } from 'msw' ; import { ModelAPI , ModelDefinition , ModelDictionary } from '../glossary' ; import { PrimaryKey } from '../primaryKey' ; import { capitalize } from '../utils/capitalize' ; import { QueryToComparator } from '../query/queryTypes' ; import { booleanComparators } from '../comparators/boolean' ; import { stringComparators } from '../comparators/string' ; import { numberComparators } from '../comparators/number' ; interface GraphQLFieldsMap { fields : GraphQLFieldConfigMap < any , any > ; inputFields : GraphQLInputFieldConfigMap ; queryInputFields : GraphQLInputFieldConfigMap ; } /**
 * Derive a GraphQL scalar type from a variable.
 */ export function getGraphQLType ( value ) { const resolvedValue = typeof value === 'function' ? value ( ) : value ; switch ( resolvedValue . constructor . name ) { case 'Number' : return GraphQLInt ; case 'Boolean' : return GraphQLBoolean ; default : return GraphQLString ; } } /**
 * Create a GraphQLInputObjectType from a given comparator function.
 */ function createComparatorGraphQLInputType ( name , comparators , type ) { return new GraphQLInputObjectType ( { name , fields : Object . keys ( comparators ) . reduce < GraphQLInputFieldConfigMap > ( ( fields , comparatorFn ) => { const fieldType = [ 'between' , 'notBetween' , 'in' , 'notIn' ] . includes ( comparatorFn ) ? GraphQLList ( type ) : type ; fields [ comparatorFn ] = { type : fieldType } ; return fields ; } , { } ) } ) ; } export const comparatorTypes = { IdQueryType : createComparatorGraphQLInputType ( 'IdQueryType' , stringComparators , GraphQLID ) , StringQueryType : createComparatorGraphQLInputType ( 'StringQueryType' , stringComparators , GraphQLString ) , IntQueryType : createComparatorGraphQLInputType ( 'IntQueryType' , numberComparators , GraphQLInt ) , BooleanQueryType : createComparatorGraphQLInputType ( 'BooleanQueryType' , booleanComparators , GraphQLBoolean ) } ; export function getQueryTypeByValueType ( valueType ) { switch ( valueType . name ) { case 'ID' : return comparatorTypes . IdQueryType ; case 'Int' : return comparatorTypes . IntQueryType ; case 'Boolean' : return comparatorTypes . BooleanQueryType ; default : return comparatorTypes . StringQueryType ; } } export function definitionToFields ( definition ) { return Object . entries ( definition ) . reduce < GraphQLFieldsMap > ( ( types , [ key , value ] ) => { const valueType = value instanceof PrimaryKey ? GraphQLID : getGraphQLType ( value ) ; const queryType = getQueryTypeByValueType ( valueType ) ; types . fields [ key ] = { type : valueType } ; types . inputFields [ key ] = { type : valueType } ; types . queryInputFields [ key ] = { type : queryType } ; return types ; } , { fields : { } , inputFields : { } , queryInputFields : { } } as GraphQLFieldsMap ) ; } export function generateGraphQLSchema < Dictionary extends ModelDictionary , ModelName extends string > ( modelName , definition , model ) { const pluralModelName = pluralize ( modelName ) ; const capitalModelName = capitalize ( modelName ) ; const { fields , inputFields , queryInputFields } = definitionToFields ( definition ) ; const EntityType = new GraphQLObjectType ( { name : capitalModelName , fields } ) ; const EntityInputType = new GraphQLInputObjectType ( { name : `${ capitalModelName }Input` , fields : inputFields } ) ; const EntityQueryInputType = new GraphQLInputObjectType ( { name : `${ capitalModelName }QueryInput` , fields : queryInputFields } ) ; const paginationArgs = { take : { type : GraphQLInt } , skip : { type : GraphQLInt } , cursor : { type : GraphQLID } } ; const objectSchema = new GraphQLSchema ( { query : new GraphQLObjectType ( { name : 'Query' , fields : { [ modelName ] : { type : EntityType , args : { where : { type : EntityQueryInputType } } , resolve ( _ , args ) { return model . findFirst ( { where : args . where } ) ; } } , [ pluralModelName ] : { type : new GraphQLList ( EntityType ) , args : { ... paginationArgs , where : { type : EntityQueryInputType } } , resolve ( _ , args ) { const shouldQuery = Object . keys ( args ) . length > 0 ; return shouldQuery ? model . findMany ( { where : args . where , skip : args . skip , take : args . take , cursor : args . cursor } ) : model . getAll ( ) ; } } } } ) , mutation : new GraphQLObjectType ( { name : 'Mutation' , fields : { [ `create${ capitalModelName }` ] : { type : EntityType , args : { data : { type : EntityInputType } } , resolve ( _ , args ) { return model . create ( args . data ) ; } } , [ `update${ capitalModelName }` ] : { type : EntityType , args : { where : { type : EntityQueryInputType } , data : { type : EntityInputType } } , resolve ( _ , args ) { return model . update ( { where : args . where , data : args . data } ) ; } } , [ `update${ capitalize ( pluralModelName ) }` ] : { type : new GraphQLList ( EntityType ) , args : { where : { type : EntityQueryInputType } , data : { type : EntityInputType } } , resolve ( _ , args ) { return model . updateMany ( { where : args . where , data : args . data } ) ; } } , [ `delete${ capitalModelName }` ] : { type : EntityType , args : { where : { type : EntityQueryInputType } } , resolve ( _ , args ) { return model . delete ( { where : args . where } ) ; } } , [ `delete${ capitalize ( pluralModelName ) }` ] : { type : new GraphQLList ( EntityType ) , args : { where : { type : EntityQueryInputType } } , resolve ( _ , args ) { return model . deleteMany ( { where : args . where } ) ; } } } } ) } ) ; return objectSchema ; } export function generateGraphQLHandlers < Dictionary extends ModelDictionary , ModelName extends string > ( modelName , definition , model , baseUrl = '' ) { const target = baseUrl ? graphql . link ( baseUrl ) : graphql ; const objectSchema = generateGraphQLSchema ( modelName , definition , model ) ; return [ target . operation ( async ( req , res , ctx ) => { if ( ! req . body ) { return ; } const result = await executeGraphQL ( { schema : objectSchema , source : req . body ?. query , variableValues : req . variables } ) ; return res ( ctx . data ( result . data ! ) , ctx . errors ( result . errors ) ) ; } ) , ] ; }