import { AnyClass , BoosterConfig , Class , CommandEnvelope , EventFilter , EventSearchRequest , EventSearchResponse , Logger , ReadModelByIdRequestArgs , ReadModelInterface , ReadModelRequestArgs , ReadModelRequestEnvelope , ReadModelRequestProperties , TimeKey , } from '@boostercloud/framework-types' ; import { GraphQLFieldResolver , GraphQLResolveInfo , GraphQLSchema } from 'graphql' ; import { pluralize } from 'inflected' ; import { BoosterCommandDispatcher } from '../../booster-command-dispatcher' ; import { BoosterEventsReader } from '../../booster-events-reader' ; import { BoosterReadModelsReader } from '../../booster-read-models-reader' ; import { GraphQLResolverContext } from './common' ; import { GraphQLMutationGenerator } from './graphql-mutation-generator' ; import { GraphQLQueryGenerator } from './graphql-query-generator' ; import { GraphQLSubscriptionGenerator } from './graphql-subcriptions-generator' ; import { GraphQLTypeInformer } from './graphql-type-informer' ; export class GraphQLGenerator { private static commandsDispatcher : BoosterCommandDispatcher ; private static readModelsReader : BoosterReadModelsReader ; private static eventsReader : BoosterEventsReader ; private static schema : GraphQLSchema ; public static generateSchema ( config , logger ) { if ( ! this . schema ) { this . commandsDispatcher = new BoosterCommandDispatcher ( config , logger ) ; this . readModelsReader = new BoosterReadModelsReader ( config , logger ) ; this . eventsReader = new BoosterEventsReader ( config , logger ) ; const typeInformer = new GraphQLTypeInformer ( { ... config . readModels , ... config . commandHandlers , } ) ; const queryGenerator = new GraphQLQueryGenerator ( config , config . readModels , typeInformer , this . readModelByIDResolverBuilder . bind ( this , config ) , this . readModelResolverBuilder . bind ( this ) , this . eventResolver . bind ( this ) ) ; const mutationGenerator = new GraphQLMutationGenerator ( config . commandHandlers , typeInformer , this . commandResolverBuilder . bind ( this ) ) ; const subscriptionGenerator = new GraphQLSubscriptionGenerator ( config . readModels , typeInformer , queryGenerator , this . subscriptionByIDResolverBuilder . bind ( this , config ) , this . subscriptionResolverBuilder . bind ( this , config ) ) ; this . schema = new GraphQLSchema ( { query : queryGenerator . generate ( ) , mutation : mutationGenerator . generate ( ) , subscription : subscriptionGenerator . generate ( ) , } ) ; } return this . schema ; } public static readModelResolverBuilder ( readModelClass ) { return ( parent , args , context , info ) => { let isPaginated = false ; if ( info ?. fieldName === `List${ pluralize ( readModelClass . name ) }` ) { isPaginated = true ; } const readModelEnvelope = toReadModelRequestEnvelope ( readModelClass , args , context , isPaginated ) ; return this . readModelsReader . search ( readModelEnvelope ) ; } ; } public static readModelByIDResolverBuilder ( config , readModelClass ) { const sequenceKeyName = config . readModelSequenceKeys [ readModelClass . name ] ; return async ( parent , args , context , info ) => { const readModelRequestEnvelope = this . toReadModelByIdRequestEnvelope ( readModelClass , args , context , sequenceKeyName ) ; return await this . readModelsReader . findById ( readModelRequestEnvelope ) ; } ; } public static eventResolver ( parent , args , context , info ) { const eventsRequestEnvelope = toEventSearchRequest ( args , context ) ; return this . eventsReader . fetch ( eventsRequestEnvelope ) ; } public static commandResolverBuilder ( commandClass ) { return async ( parent , args , context , info ) => { const commandEnvelope = toCommandEnvelope ( commandClass . name , args . input , context ) ; const result = await this . commandsDispatcher . dispatchCommand ( commandEnvelope ) ; return result ?? true ; } ; } public static subscriptionByIDResolverBuilder ( config , readModelClass ) { return async ( parent , args , context , info ) => { const filterArgs = { filter : { id : { eq : args . id } } } ; return this . subscriptionResolverBuilder ( config , readModelClass ) ( parent , filterArgs , context , info ) ; } ; } public static subscriptionResolverBuilder ( config , readModelClass ) { return async ( parent , args , context , info ) => { if ( ! context . connectionID ) { throw new Error ( 'Missing "connectionID". It is required for subscriptions' ) ; } const readModelRequestEnvelope = toReadModelRequestEnvelope ( readModelClass , args , context ) ; if ( context . storeSubscriptions ) { await this . readModelsReader . subscribe ( context . connectionID , readModelRequestEnvelope , context . operation ) ; } return context . pubSub . asyncIterator ( readModelRequestEnvelope , config ) ; } ; } private static toReadModelByIdRequestEnvelope ( readModelClass , args , context , sequenceKeyName ? ) { const key = sequenceKeyName ? { id : args . id , sequenceKey : { name : sequenceKeyName , value : args [ sequenceKeyName ] as TimeKey , } , } : { id : args . id } ; return { currentUser : context . user , requestID : context . requestID , class : readModelClass , className : readModelClass . name , key , version : 1 , filters : { } , } ; } } function toReadModelRequestEnvelope ( readModelClass , args , context , paginatedVersion = false ) { return { requestID : context . requestID , currentUser : context . user , class : readModelClass , className : readModelClass . name , filters : args . filter ?? { } , limit : args . limit , afterCursor : args . afterCursor , paginatedVersion , version : 1 , } ; } function toEventSearchRequest ( args , context ) { return { requestID : context . requestID , currentUser : context . user , filters : args , } ; } function toCommandEnvelope ( commandName , value , context ) { return { requestID : context . requestID , currentUser : context . user , typeName : commandName , value , version : 1 , } ; }