import {Service} from '../../../di/src/decorator/Service.ts' import {Logging} from '../service/logging/Logging.ts' import LifecycleUnit from './Unit.ts' import {container, make} from '../../../di/src/global.ts' import {DependencyKey} from '../../../di/src/type/DependencyKey.ts' import RunLevelErrorHandler from '../error/RunLevelErrorHandler.ts' import {Status} from '../const/status.ts' import Instantiable from '../../../di/src/type/Instantiable.ts' import {Collection} from '../collection/Collection.ts' import {path} from '../external/std.ts' import Scaffolding from '../unit/Scaffolding.ts' import {Container} from '../../../di/src/Container.ts' /** * Central class for Daton applications. */ @Service() export default class Application { /** * Collection of LifecycleUnits instantiated by this application. * @type Collection */ protected instantiated_units: Collection = new Collection() constructor( protected logger: Logging, protected injector: Container, protected rleh: RunLevelErrorHandler, /** * Array of unit classes to run for this application. * @type Array> */ protected units: (Instantiable)[], ) {} /** * Use the IoC container to instantiate the given dependency key. * @param {DependencyKey} token */ make(token: DependencyKey) { return make(token) } /** * Get the IoC container. * @return Container */ container() { return container } /** * Launch the application. * @return Promise */ async up() { this.logger.info('Starting Daton...', true) for ( const unit_class of this.units ) { const unit = this.make(unit_class) this.instantiated_units.push(unit) await this.start_unit(unit) } } /** * Stop the application. * @return Promise */ async down() { } /** * Run the application. * @return Promise */ async run() { try { await this.up() await this.down() } catch (e) { await this.app_error(e) } } /** * Pass an error to the top-level error handler. * @param {Error} e */ async app_error(e: Error) { this.rleh.handle(e) } /** * Launch the given lifecycle unit. * @param {LifecycleUnit} unit */ protected async start_unit(unit: LifecycleUnit) { try { unit.status = Status.Starting this.logger.info(`Starting ${unit.constructor.name}...`) await unit.up() this.logger.verbose(`Successfully started ${unit.constructor.name}`) unit.status = Status.Running } catch (e) { unit.status = Status.Error this.logger.error(`Error encountered while starting ${unit.constructor.name}. Will attempt to proceed.`) this.logger.debug(e.message) this.logger.verbose(e) } } /** * Get the root directory of the application. * @type string */ get root() { return this.injector.make(Scaffolding).base_dir } /** * Get the root directory of application class definitions. * @type string */ get app_root() { let base_dir = this.injector.make(Scaffolding).base_dir if ( base_dir.startsWith('file://') ) { base_dir = base_dir.slice(7) } const resolved = path.resolve(base_dir, 'app') if ( resolved.startsWith('/') ) { return `file://${resolved}` } else { return resolved } } /** * Resolve the given path within the application's root. * @param {...string} parts * @return string */ path(...parts: string[]) { let root = this.root if ( root.startsWith('file://') ) { root = root.slice(7) } const resolved = path.resolve(root, ...parts) if ( resolved.startsWith('/') ) { return `file://${resolved}` } else { return resolved } } /** * Resolve the given path within the application's class definition root. * @param {...string} parts * @return string */ app_path(...parts: string[]) { let app_root = this.app_root if ( app_root.startsWith('file://') ) { app_root = app_root.slice(7) } console.log('app path', {parts, app_root}) const resolved = path.resolve(app_root, ...parts) if ( resolved.startsWith('/') ) { return `file://${resolved}` } else { return resolved } } }