UNPKG

zeroant-factory

Version:
329 lines (289 loc) 9.79 kB
import { createServer, type Server } from 'http' import { InternalServerError } from 'zeroant-response/serverErrors/internalServerError.serverError' import { ErrorCode, ErrorDescription } from 'zeroant-constant/response.enum' import { ZeroantEvent } from 'zeroant-constant' import { type ServerFactoryConstructor, type ServerFactory } from './server.factory.js' import { type ConfigFactory } from './config.factory.js' import { type Plugin } from './plugin.factory.js' import { EventEmitter } from 'events' import { type WorkerFactoryConstructor, type WorkerFactory } from './worker.factory.js' import { type AddonPlugin, type AddonPluginConstructor } from './addon.plugin.js' import { type Logger } from 'winston' import type RegistryFactory from 'registry.factory.js' export class ZeroantContext<Config extends ConfigFactory> { static PORT = 8080 static HOSTNAME = '127.0.0.1' protected _server: Server protected _port: number protected _hostname: string #state = 'idle' #store = new Map() #workers = new Map<string, WorkerFactory<any, any>>() #event = new EventEmitter() #registry: RegistryFactory _servers: ServerFactory[] = [] constructor(private readonly Config: new (config: any) => Config) {} delay = async (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) initWorkers(workers: Array<WorkerFactoryConstructor<WorkerFactory<any, any>>>) { for (const Worker of workers) { const worker = new Worker(this) this.#workers.set(worker.name, worker) } } getWorkerByName<T extends WorkerFactory<any, any>>(workerName: string): T | undefined { return (this.#workers.get(workerName) as T) ?? null } getWorkerNames(): IterableIterator<string> { return this.#workers.keys() } get workers() { return { get: <T extends WorkerFactory<any, any>>(Worker: WorkerFactoryConstructor<T>) => this.getWorker<T>(Worker) } } getWorkers(): Array<WorkerFactory<any, any>> { const workers = [] for (const worker of this.#workers.values()) { workers.push(worker) } return workers } getWorker<T extends WorkerFactory<any, any>>(Worker: WorkerFactoryConstructor<T>): T { for (const worker of this.#workers.values()) { if (worker instanceof Worker) { return worker } } throw new InternalServerError( ErrorCode.UNIMPLEMENTED_EXCEPTION, ErrorDescription.UNIMPLEMENTED_EXCEPTION, `Worker ${Worker.name} not registered check common/registry.ts for more information` ) } listen(callback?: () => void): void { this.beforeStart() const config = this.getConfig() this._port = (config as ConfigFactory).serverPort ?? ZeroantContext.PORT this._hostname = (config as ConfigFactory).serverHostname ?? ZeroantContext.HOSTNAME const callbacks = this._servers.map((server) => server.callback()) this._server = createServer((req, res): void => { void (async (req, res) => { for (const callback of callbacks) { await callback(req, res) } })(req, res) }) this._server.listen(this._port, this._hostname, () => { this.onStart() if (typeof callback === 'function') { callback() } }) } onStart() { this.#event.emit(ZeroantEvent.START) for (const plugin of this.plugin.values()) { plugin.onStart() } for (const server of this._servers) { server.onStart() } ;(this.config as ConfigFactory).logging('info', () => { console.info(new Date(), '[ZeroantContext]: Running On Port', this._port) }) } beforeStart() { this.#event.emit(ZeroantEvent.BEFORE_START) for (const plugin of this.plugin.values()) { plugin.beforeStart() } for (const server of this._servers) { server.beforeStart() } } has(key: string) { return this.#store.has(key) } #exiting = false async safeExit(code?: number, signal?: NodeJS.Signals | 'exit' | 'beforeExit' | 'uncaughtException', ts?: number): Promise<void> { this.#state = 'exiting' if (this.#exiting) { return } this.#exiting = true if (signal !== undefined) console.info(new Date(), '[ZeroantContext]:', `Received ${signal}.`) await this.close(ts) process.exit(code) } async close(ts?: number) { this.#state = 'closing' this.#event.emit(ZeroantEvent.CLOSE) const wait: Array<Promise<any>> = [this.delay(ts ?? 0)] for (const plugin of this.plugin.values()) { wait.push( Promise.resolve().then(async () => { await plugin.close() }) ) } for (const server of this._servers) { wait.push( Promise.resolve().then(async () => { await server.close() }) ) } wait.push( new Promise<void>((resolve) => { // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (!this._server) { resolve() return } this._server.close((err) => { if (err != null) { console.error(err.message) } resolve() }) }) ) await Promise.all(wait) this.#state = 'closed' this.config.logging('info', () => { console.info(new Date(), '[ZeroantContext]: Stopped') }) } bootstrap(registry: RegistryFactory) { if (this.hasRegistry) { throw new InternalServerError( ErrorCode.SERVER_EXCEPTION, ErrorDescription.SERVER_EXCEPTION, `${new Date().toISOString()} Registry already bootstrap for zeroant and it can't be overridden` ) } this.#registry = registry registry.bootstrap(this) this.#event.emit(ZeroantEvent.BOOTSTRAP, this) } get hasRegistry() { return ![null, undefined].includes(this.#registry as never) } get registry(): RegistryFactory { if (!this.hasRegistry) { throw new InternalServerError( ErrorCode.SERVER_EXCEPTION, ErrorDescription.SERVER_EXCEPTION, `${new Date().toISOString()} Registry not register for zeroant yet, please bootstrap before using registry` ) } return this.#registry } ready() { this.#event.emit(ZeroantEvent.READY, this) this.#registry.ready(this) } initServer(Server: ServerFactoryConstructor<ServerFactory>, registry: RegistryFactory) { const server = new Server(this) server.initialize(registry) this._servers.push(server) this.#store.set(`server:${Server.name}`, server) } getServer<T extends ServerFactory>(Server: ServerFactoryConstructor<T>): T { const server = this.#store.get(`server:${Server.name}`) if (server === null || server === undefined) { throw new InternalServerError(ErrorCode.SERVER_EXCEPTION, ErrorDescription.SERVER_EXCEPTION, `${Server.name} Server Not Init`) } return server } async initPlugin(plugin: Plugin) { this.#store.set('plugin', plugin) await plugin.initialize() } getPlugins() { const plugin: Plugin = this.#store.get('plugin') if (plugin === null || plugin === undefined) { throw new InternalServerError(ErrorCode.SERVER_EXCEPTION, ErrorDescription.SERVER_EXCEPTION, 'Plugin Not Init') } return plugin } async initConfig(config: Config) { this.#store.set('config', config) } async initLogger(logger: Logger) { this.#store.set('logger', logger) } get<T>(name: string): T { const inst = this.#store.get(name) if (inst === null || inst === undefined) { throw new InternalServerError(ErrorCode.SERVER_EXCEPTION, ErrorDescription.SERVER_EXCEPTION, `Resources ${name} Not found`) } return inst } set<T>(name: string, value: T): this { if (this.has(name)) { throw new InternalServerError(ErrorCode.SERVER_EXCEPTION, ErrorDescription.SERVER_EXCEPTION, `Can't Override Resources ${name}`) } this.#store.set(name, value) return this } getLogger<T extends Logger>(): T { const logger = this.#store.get('logger') if (logger === null || logger === undefined) { throw new InternalServerError(ErrorCode.SERVER_EXCEPTION, ErrorDescription.SERVER_EXCEPTION, 'Logger Not Init') } return logger as T } getConfig(): Config { const config = this.#store.get('config') if (config === null || config === undefined) { throw new InternalServerError(ErrorCode.SERVER_EXCEPTION, ErrorDescription.SERVER_EXCEPTION, 'Config Not Init') } return config as Config } getPlugin<T extends AddonPlugin>(addon: AddonPluginConstructor<T>): T { return this.plugin.get<T>(addon) } get log(): Logger { return this.getLogger() } get server(): Server { return this._server } get state(): string { return this.#state } get plugin(): Plugin { return this.getPlugins() } get config(): Config { return this.getConfig() } on(eventName: ZeroantEvent, listener: (...args: any[]) => void): this { this.#event.on(eventName, listener) return this } once(eventName: ZeroantEvent, listener: (...args: any[]) => void): this { this.#event.once(eventName, listener) return this } off(eventName: ZeroantEvent, listener: (...args: any[]) => void): this { this.#event.off(eventName, listener) return this } removeListener(eventName: ZeroantEvent, listener: (...args: any[]) => void): this { this.#event.removeListener(eventName, listener) return this } removeAllListeners(eventName: ZeroantEvent): this { this.#event.removeAllListeners(eventName) return this } rawListeners(eventName: ZeroantEvent): this { this.#event.rawListeners(eventName) return this } emit(eventName: ZeroantEvent, ...args: any[]): boolean { return this.#event.emit(eventName, ...args) } }