import { printSchemaWithDirectives } from '@graphql-tools/utils' ; import { Binding , BindingFromClassOptions , BindingKey , BindingScope , config , Constructor , Context , ContextTags , createBindingFromClass , filterByTag , inject , lifeCycleObserver , Server , } from '@loopback/core' ; import { HttpServer } from '@loopback/http-server' ; import { ContextFunction } from 'apollo-server-core' ; import { ApolloServer , ApolloServerExpressConfig , PubSub , PubSubEngine , } from 'apollo-server-express' ; import { ExpressContext } from 'apollo-server-express/dist/ApolloServer' ; import express from 'express' ; import fs from 'fs' ; import { lexicographicSortSchema } from 'graphql' ; import { AuthChecker , buildSchema , BuildSchemaOptions as TypeGrahpQLBuildSchemaOptions , NonEmptyArray , ResolverInterface , } from 'type-graphql' ; import { Middleware } from 'type-graphql/dist/interfaces/Middleware' ; import { LoopBackContainer } from './graphql.container' ; import { GraphQLBindings , GraphQLTags } from './keys' ; import { GraphQLServerOptions } from './types' ; /**
 * GraphQL Server
 */ @ lifeCycleObserver ( 'server' , { scope : BindingScope . SINGLETON , tags : { [ ContextTags . KEY ] : GraphQLBindings . GRAPHQL_SERVER } , } ) export class GraphQLServer extends Context implements Server { readonly httpServer ? : HttpServer ; readonly expressApp : express . Application ; constructor ( @ config ( ) private options = { } , @ inject . context ( ) parent ? ) { super ( parent , 'graphql-server' ) ; this . scope = BindingScope . SERVER ; this . expressApp = express ( ) ; if ( options . expressSettings ) { for ( const p in options . expressSettings ) { this . expressApp . set ( p , options . expressSettings [ p ] ) ; } } if ( ! options . asMiddlewareOnly ) { this . httpServer = new HttpServer ( this . expressApp , this . options ) ; } } /**
     * Get a list of resolver classes
     */ getResolverClasses ( ) { const view = this . createView ( filterByTag ( GraphQLTags . RESOLVER ) ) ; return view . bindings . filter ( b => b . valueConstructor != null ) . map ( b => b . valueConstructor as Constructor < ResolverInterface < object > > ) ; } /**
     * Get a list of middleware
     */ async getMiddlewareList ( ) { const view = this . createView < Middleware < unknown > > ( filterByTag ( GraphQLTags . MIDDLEWARE ) ) ; return view . values ( ) ; } param middleware middleware < T = unknown > ( middleware ) { return this . bind < Middleware < T > > ( BindingKey . generate ( `graphql.middleware` ) ) . to ( middleware ) . tag ( GraphQLTags . MIDDLEWARE ) ; } param resolverClass param nameOrOptions resolver ( resolverClass , nameOrOptions ? ) { return registerResolver ( this , resolverClass , nameOrOptions ) ; } private async _setupSchema ( ) { const resolverClasses = this . getResolverClasses ( ) as unknown as NonEmptyArray < Function > ; const authChecker = ( await this . get ( GraphQLBindings . GRAPHQL_AUTH_CHECKER , { optional : true , } ) ) ?? ( ( resolverData , roles ) => true ) ; const pubSub = ( await this . get ( GraphQLBindings . PUB_SUB_ENGINE , { optional : true , } ) ) ?? new PubSub ( ) ; const buildSchemaOptions = { validate : false , resolvers : resolverClasses , container : new LoopBackContainer ( this ) , authChecker , pubSub , globalMiddlewares : await this . getMiddlewareList ( ) , } ; return buildSchema ( buildSchemaOptions ) ; } async start ( ) { const schema = await this . _setupSchema ( ) ; const graphqlContextResolver = ( await this . get ( GraphQLBindings . GRAPHQL_CONTEXT_RESOLVER , { optional : true , } ) ) ?? ( context => context ) ; const serverConfig = { playground : true , context : graphqlContextResolver , subscriptions : false , ... this . options . apollo , schema , } ; const graphQLServer = new ApolloServer ( serverConfig ) ; graphQLServer . applyMiddleware ( { app : this . expressApp , ... this . options . middlewareOptions , } ) ; if ( this . httpServer && serverConfig . subscriptions ) { graphQLServer . installSubscriptionHandlers ( this . httpServer ?. server ) ; } await this . httpServer ?. start ( ) ; } async exportGraphQLSchema ( outFile = '' , log = console . log ) { const schema = await this . _setupSchema ( ) ; const schemaFileContent = printSchemaWithDirectives ( lexicographicSortSchema ( schema ) ) ; if ( outFile === '-' || outFile === '' ) { log ( '%s' , schemaFileContent ) ; } else { fs . writeFileSync ( outFile , schemaFileContent , 'utf-8' ) ; } } async stop ( ) { await this . httpServer ?. stop ( ) ; } /**
     * Is the GraphQL listening
     */ get listening ( ) { return ! ! this . httpServer ?. listening ; } } param ctx param resolverClass param nameOrOptions export function registerResolver ( ctx , resolverClass , nameOrOptions ? ) { const binding = createBindingFromClass ( resolverClass , { namespace : GraphQLBindings . RESOLVERS , ... toOptions ( nameOrOptions ) , } ) . tag ( GraphQLTags . RESOLVER ) ; ctx . add ( binding ) ; return binding ; } param nameOrOptions function toOptions ( nameOrOptions ? ) { if ( typeof nameOrOptions === 'string' ) { return { name : nameOrOptions } ; } return nameOrOptions ?? { } ; }