diff --git a/packages/api/src/graphql/GraphqlSequencerModule.ts b/packages/api/src/graphql/GraphqlSequencerModule.ts index 2aaadf248..5098c760d 100644 --- a/packages/api/src/graphql/GraphqlSequencerModule.ts +++ b/packages/api/src/graphql/GraphqlSequencerModule.ts @@ -1,68 +1,39 @@ -import { buildSchemaSync, NonEmptyArray } from "type-graphql"; +import assert from "node:assert"; + import { Closeable, closeable, SequencerModule } from "@proto-kit/sequencer"; import { ChildContainerProvider, Configurable, CombinedModuleContainerConfig, + dependencyFactory, log, ModuleContainer, ModulesRecord, TypedClass, } from "@proto-kit/common"; -import { GraphQLSchema } from "graphql/type"; -import { stitchSchemas } from "@graphql-tools/stitch"; -import { createYoga } from "graphql-yoga"; -import Koa from "koa"; import { GraphqlModule, ResolverFactoryGraphqlModule, SchemaGeneratingGraphqlModule, } from "./GraphqlModule"; +import { GraphqlServer, GraphqlServerOptions } from "./GraphqlServer"; export type GraphqlModulesRecord = ModulesRecord< TypedClass> >; -export interface GraphqlServerConfig { - host: string; - port: number; - graphiql: boolean; -} - export type GraphqlSequencerModuleConfig< GraphQLModules extends GraphqlModulesRecord, -> = CombinedModuleContainerConfig; - -type Server = ReturnType; - -function assertArrayIsNotEmpty( - array: readonly T[], - errorMessage: string -): asserts array is NonEmptyArray { - if (array.length === 0) { - throw new Error(errorMessage); - } -} +> = CombinedModuleContainerConfig; @closeable() +@dependencyFactory() export class GraphqlSequencerModule - extends ModuleContainer + extends ModuleContainer implements Configurable, SequencerModule, Closeable { - private readonly modules: TypedClass>[] = []; - - private readonly schemas: GraphQLSchema[] = []; - - private resolvers: NonEmptyArray | undefined; - - private server?: Server; - - private context: {} = {}; - - public get serverConfig(): GraphqlServerConfig { - return this.containerConfig; - } + private graphqlServer?: GraphqlServer; public static from( definition: GraphQLModules @@ -74,28 +45,35 @@ export class GraphqlSequencerModule }; } - public constructor(definition: GraphQLModules) { - super(definition); + public static dependencies() { + return { + graphqlServer: { + useClass: GraphqlServer, + }, + }; } - public setContext(newContext: {}) { - this.context = newContext; + public constructor(definition: GraphQLModules) { + super(definition); } - public registerResolvers(resolvers: NonEmptyArray) { - if (this.resolvers === undefined) { - this.resolvers = resolvers; - } else { - this.resolvers = [...this.resolvers, ...resolvers]; + private getGraphqlConfig(graphqlServer: GraphqlServer): GraphqlServerOptions { + try { + return graphqlServer.config; + } catch { + return this.containerConfig; } } public create(childContainerProvider: ChildContainerProvider) { super.create(childContainerProvider); - this.container.register("GraphqlServer", { useValue: this }); + this.graphqlServer = this.container.resolve("GraphqlServer"); } public async start(): Promise { + assert(this.graphqlServer !== undefined); + this.graphqlServer.setContainer(this.container); + this.graphqlServer.config = this.getGraphqlConfig(this.graphqlServer); // eslint-disable-next-line guard-for-in for (const moduleName in this.definition) { const moduleClass = this.definition[moduleName]; @@ -112,9 +90,9 @@ export class GraphqlSequencerModule moduleName ) as ResolverFactoryGraphqlModule; // eslint-disable-next-line no-await-in-loop - this.registerResolvers(await module.resolvers()); + this.graphqlServer.registerResolvers(await module.resolvers()); } else { - this.modules.push(moduleClass); + this.graphqlServer.registerModule(moduleClass); if ( Object.prototype.isPrototypeOf.call( @@ -127,91 +105,16 @@ export class GraphqlSequencerModule const module = this.resolve( moduleName ) as SchemaGeneratingGraphqlModule; - this.schemas.push(module.generateSchema()); + this.graphqlServer.registerSchema(module.generateSchema()); } } } - await this.startServer(); - } - - // Server logic - - private async startServer() { - const { modules, container: dependencyContainer } = this; - - const resolvers = [...modules, ...(this.resolvers || [])]; - - assertArrayIsNotEmpty( - resolvers, - "At least one module has to be provided to GraphqlServer" - ); - - // Building schema - const resolverSchema = buildSchemaSync({ - resolvers, - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - container: { get: (cls) => dependencyContainer.resolve(cls) }, - validate: { - enableDebugMessages: true, - }, - }); - - // Instantiate all modules at startup - modules.forEach((module) => { - dependencyContainer.resolve(module); - }); - - const schema = [resolverSchema, ...this.schemas].reduce( - (schema1, schema2) => - stitchSchemas({ - subschemas: [{ schema: schema1 }, { schema: schema2 }], - }) - ); - - const app = new Koa(); - - const { graphiql, port, host } = this.serverConfig; - - const yoga = createYoga({ - schema, - graphiql, - context: this.context, - }); - - // Bind GraphQL Yoga to `/graphql` endpoint - app.use(async (ctx) => { - // Second parameter adds Koa's context into GraphQL Context - const response = await yoga.handleNodeRequest(ctx.req, ctx); - - // Set status code - ctx.status = response.status; - - // Set headers - response.headers.forEach((value, key) => { - ctx.append(key, value); - }); - - // Converts ReadableStream to a NodeJS Stream - ctx.body = response.body; - }); - - this.server = app.listen({ port, host }, () => { - log.info(`GraphQL Server listening on ${host}:${port}`); - }); + await this.graphqlServer.startServer(); } public async close() { - if (this.server !== undefined) { - const { server } = this; - - await new Promise((res) => { - server.close((error) => { - if (error !== undefined) { - log.error(error); - } - res(); - }); - }); + if (this.graphqlServer !== undefined) { + await this.graphqlServer.close(); } } } diff --git a/packages/api/src/graphql/GraphqlServer.ts b/packages/api/src/graphql/GraphqlServer.ts index 0a5658540..338239727 100644 --- a/packages/api/src/graphql/GraphqlServer.ts +++ b/packages/api/src/graphql/GraphqlServer.ts @@ -1,6 +1,6 @@ import { buildSchemaSync, NonEmptyArray } from "type-graphql"; import { DependencyContainer, injectable } from "tsyringe"; -import { SequencerModule } from "@proto-kit/sequencer"; +import { closeable, SequencerModule } from "@proto-kit/sequencer"; import { log, noop, TypedClass } from "@proto-kit/common"; import { GraphQLSchema } from "graphql/type"; import { stitchSchemas } from "@graphql-tools/stitch"; @@ -11,7 +11,7 @@ import type { GraphqlModule } from "./GraphqlModule"; type Server = ReturnType; -interface GraphqlServerOptions { +export interface GraphqlServerOptions { host: string; port: number; graphiql: boolean; @@ -27,6 +27,7 @@ function assertArrayIsNotEmpty( } @injectable() +@closeable() export class GraphqlServer extends SequencerModule { private readonly modules: TypedClass>[] = []; diff --git a/packages/indexer/src/api/GeneratedResolverFactoryGraphqlModule.ts b/packages/indexer/src/api/GeneratedResolverFactoryGraphqlModule.ts index fd31b1814..1fd90050b 100644 --- a/packages/indexer/src/api/GeneratedResolverFactoryGraphqlModule.ts +++ b/packages/indexer/src/api/GeneratedResolverFactoryGraphqlModule.ts @@ -1,6 +1,5 @@ import { - GraphqlModulesRecord, - GraphqlSequencerModule, + GraphqlServer, ResolverFactoryGraphqlModule, graphqlModule, } from "@proto-kit/api"; @@ -99,7 +98,7 @@ export function ValidateTakeArg() { export class GeneratedResolverFactoryGraphqlModule extends ResolverFactoryGraphqlModule { public constructor( @inject("GraphqlServer") - public graphqlServer: GraphqlSequencerModule + public graphqlServer: GraphqlServer ) { super(); } diff --git a/packages/processor/src/api/ResolverFactoryGraphqlModule.ts b/packages/processor/src/api/ResolverFactoryGraphqlModule.ts index 3ac532073..a110b0be6 100644 --- a/packages/processor/src/api/ResolverFactoryGraphqlModule.ts +++ b/packages/processor/src/api/ResolverFactoryGraphqlModule.ts @@ -1,6 +1,5 @@ import { - GraphqlModulesRecord, - GraphqlSequencerModule, + GraphqlServer, ResolverFactoryGraphqlModule as BaseResolverFactoryGraphqlModule, graphqlModule, } from "@proto-kit/api"; @@ -62,9 +61,7 @@ export class ResolverFactoryGraphqlModule< public database: PrismaDatabaseConnection | undefined; - public constructor( - public graphqlServer: GraphqlSequencerModule - ) { + public constructor(public graphqlServer: GraphqlServer) { super(); }