zeroant-factory
Version:
Factory modules for zeroant
329 lines (289 loc) • 9.79 kB
text/typescript
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)
}
}