import {Container} from '@extollo/di'; import { ErrorWithContext, globalRegistry, infer, isLoggingLevel, PathLike, StandardLogger, universalPath, UniversalPath } from '@extollo/util'; import {Logging} from '../service/Logging'; import {RunLevelErrorHandler} from "./RunLevelErrorHandler"; import {Unit, UnitStatus} from "./Unit"; import * as dotenv from 'dotenv'; import {CacheFactory} from "../support/cache/CacheFactory"; export function env(key: string, defaultValue?: any): any { return Application.getApplication().env(key, defaultValue) } export class Application extends Container { public static getContainer(): Container { const existing = globalRegistry.getGlobal('extollo/injector') if ( !existing ) { const container = new Application() globalRegistry.setGlobal('extollo/injector', container) return container } return existing as Container } public static getApplication(): Application { const existing = globalRegistry.getGlobal('extollo/injector') if ( existing instanceof Application ) { return existing } else if ( existing ) { const app = new Application() existing.cloneTo(app) globalRegistry.setGlobal('extollo/injector', app) return app } else { const app = new Application() globalRegistry.setGlobal('extollo/injector', app) return app } } protected baseDir!: string protected basePath!: UniversalPath protected applicationUnits: (typeof Unit)[] = [] protected instantiatedUnits: Unit[] = [] constructor() { super() if ( !this.hasKey(Application) ) { this.register(Application) this.instances.push({ key: Application, value: this, }) } if ( !this.hasKey('app') ) { this.registerSingleton('app', this) } } get root() { return this.basePath.concat() } get appRoot() { return this.basePath.concat('app') } path(...parts: PathLike[]) { return this.basePath.concat(...parts) } appPath(...parts: PathLike[]) { return this.basePath.concat('app', ...parts) } get errorHandler() { const rleh: RunLevelErrorHandler = this.make(RunLevelErrorHandler) return rleh.handle } errorWrapContext(e: Error, context: {[key: string]: any}): ErrorWithContext { const rleh: RunLevelErrorHandler = this.make(RunLevelErrorHandler) return rleh.wrapContext(e, context) } scaffold(absolutePathToApplicationRoot: string, applicationUnits: (typeof Unit)[]) { this.baseDir = absolutePathToApplicationRoot this.basePath = universalPath(absolutePathToApplicationRoot) this.applicationUnits = applicationUnits this.bootstrapEnvironment() this.setupLogging() this.registerFactory(new CacheFactory()) // FIXME move this somewhere else? this.make(Logging).debug(`Application root: ${this.baseDir}`) } protected setupLogging() { const standard: StandardLogger = this.make(StandardLogger) const logging: Logging = this.make(Logging) logging.registerLogger(standard) try { logging.verbose('Attempting to load logging level from the environment...') const envLevel = this.env('EXTOLLO_LOGGING_LEVEL') logging.verbose(`Read logging level: ${envLevel}`) if ( isLoggingLevel(envLevel) ) { logging.verbose('Logging level is valid.') logging.level = envLevel logging.debug(`Set logging level from environment: ${envLevel}`) } } catch(e) {} } protected bootstrapEnvironment() { dotenv.config({ path: this.basePath.concat('.env').toLocal }) } public env(key: string, defaultValue?: any): any { return infer(process.env[key] ?? '') ?? defaultValue } async run() { try { await this.up() await this.down() } catch (e) { this.errorHandler(e) } } async up() { const logging: Logging = this.make(Logging) logging.info('Starting Extollo...', true) for ( const unitClass of this.applicationUnits ) { const unit: Unit = this.make(unitClass) this.instantiatedUnits.push(unit) await this.startUnit(unit) } } async down() { const logging: Logging = this.make(Logging) logging.info('Stopping Extollo...', true) for ( const unit of this.instantiatedUnits ) { if ( !unit ) continue await this.stopUnit(unit) } } protected async startUnit(unit: Unit) { const logging: Logging = this.make(Logging) try { logging.debug(`Starting ${unit.constructor.name}...`) unit.status = UnitStatus.Starting await unit.up() unit.status = UnitStatus.Started logging.info(`Started ${unit.constructor.name}.`) } catch (e) { unit.status = UnitStatus.Error console.log(e) throw this.errorWrapContext(e, {unit_name: unit.constructor.name}) } } protected async stopUnit(unit: Unit) { const logging: Logging = this.make(Logging) try { logging.debug(`Stopping ${unit.constructor.name}...`) unit.status = UnitStatus.Stopping await unit.down() unit.status = UnitStatus.Stopped logging.info(`Stopped ${unit.constructor.name}.`) } catch (e) { unit.status = UnitStatus.Error throw this.errorWrapContext(e, {unit_name: unit.constructor.name}) } } }