diff --git a/TODO.txt b/TODO.txt index 6a5b1db..ff18279 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,12 +1,12 @@ TLS support internationalization -uploads & universal path!! +uploads CLI - view routes, template generation, start server, directives, output, args nicer error and home pages favicon utility - is_windows, is_linux, is_mac authentication - user/session, oauth, jwt, &c. -orm enum/bit fields, json handlers, scopes +orm enum/bit fields, json handlers, scopes, migrations, schema redis - redis client, redis rehydrated classes, redis sessions less/scss notifications - gotify/push/other mechanisms diff --git a/cli/src/CLIAppUnit.ts b/cli/src/CLIAppUnit.ts new file mode 100644 index 0000000..11c7f59 --- /dev/null +++ b/cli/src/CLIAppUnit.ts @@ -0,0 +1,18 @@ +import LifecycleUnit from '../../lib/src/lifecycle/Unit.ts' +import {CLIService} from './service/CLI.service.ts' +import {Unit} from '../../lib/src/lifecycle/decorators.ts' +import {Logging} from '../../lib/src/service/logging/Logging.ts' + +@Unit() +export default class CLIAppUnit extends LifecycleUnit { + constructor( + protected readonly cli: CLIService, + protected readonly logger: Logging, + ) { super() } + + public async up() { + this.logger.verbose(`Handling CLI invocation...`) + const args = Deno.args + console.log('args', {args}) + } +} diff --git a/cli/src/CLIUnit.ts b/cli/src/CLIUnit.ts new file mode 100644 index 0000000..f536a12 --- /dev/null +++ b/cli/src/CLIUnit.ts @@ -0,0 +1,15 @@ +import LifecycleUnit from '../../lib/src/lifecycle/Unit.ts' +import {CLIService} from './service/CLI.service.ts' +import {Unit} from '../../lib/src/lifecycle/decorators.ts' +import {UsageDirective} from './directive/UsageDirective.ts' + +@Unit() +export default class CLIUnit extends LifecycleUnit { + constructor( + protected readonly cli: CLIService, + ) { super() } + + public async up() { + this.cli.register_directive(this.make(UsageDirective)) + } +} diff --git a/cli/src/directive/Directive.ts b/cli/src/directive/Directive.ts new file mode 100644 index 0000000..cebafa9 --- /dev/null +++ b/cli/src/directive/Directive.ts @@ -0,0 +1,37 @@ +import AppClass from '../../../lib/src/lifecycle/AppClass.ts' +import {Logging} from '../../../lib/src/service/logging/Logging.ts' + +export abstract class Directive extends AppClass { + public abstract readonly keyword: string + public abstract readonly help: string + + static options() { + return [] + } + + public abstract invoke(): any + + success(message: any) { + this.make(Logging).success(message, true) + } + + error(message: any) { + this.make(Logging).error(message, true) + } + + warn(message: any) { + this.make(Logging).warn(message, true) + } + + info(message: any) { + this.make(Logging).info(message, true) + } + + debug(message: any) { + this.make(Logging).debug(message, true) + } + + verbose(message: any) { + this.make(Logging).verbose(message, true) + } +} diff --git a/cli/src/directive/UsageDirective.ts b/cli/src/directive/UsageDirective.ts new file mode 100644 index 0000000..615f9c4 --- /dev/null +++ b/cli/src/directive/UsageDirective.ts @@ -0,0 +1,10 @@ +import {Directive} from './Directive.ts' + +export class UsageDirective extends Directive { + public readonly keyword = 'help' + public readonly help = 'Display usage information' + + public async invoke() { + console.log('Hello, from Daton CLI.') + } +} diff --git a/cli/src/mod.ts b/cli/src/mod.ts new file mode 100644 index 0000000..e69de29 diff --git a/cli/src/service/CLI.service.ts b/cli/src/service/CLI.service.ts new file mode 100644 index 0000000..b481891 --- /dev/null +++ b/cli/src/service/CLI.service.ts @@ -0,0 +1,60 @@ +import AppClass from '../../../lib/src/lifecycle/AppClass.ts' +import {Service} from '../../../di/src/decorator/Service.ts' +import {Collection} from '../../../lib/src/collection/Collection.ts' +import {Directive} from '../directive/Directive.ts' +import {Logging} from '../../../lib/src/service/logging/Logging.ts' + +/** + * Error thrown when a directive registered with the same keyword as + * an existing directive. + * @extends Error + */ +export class DuplicateCLIDirectiveError extends Error { + constructor(keyword: string) { + super(`A CLI directive with the keyword "${keyword}" already exists.`) + } +} + +/** + * Service for registering and managing CLI directives. + * @extends AppClass + */ +@Service() +export class CLIService extends AppClass { + protected directives: Collection = new Collection() + + constructor( + protected readonly logger: Logging, + ) { super() } + + + /** + * Find a registered directive using its keyword, if one exists. + * @param {string} keyword + * @return Directive | undefined + */ + public get_directive_by_keyword(keyword: string): Directive | undefined { + return this.directives.firstWhere('keyword', '=', keyword) + } + + /** + * Get a collection of all registered directives. + * @return Collection + */ + public get_directives() { + return this.directives.clone() + } + + /** + * Register a directive with the service. + * @param {Directive} directive + */ + public register_directive(directive: Directive) { + if ( this.get_directive_by_keyword(directive.keyword) ) { + throw new DuplicateCLIDirectiveError(directive.keyword) + } + + this.logger.verbose(`Registering CLI directive with keyword: ${directive.keyword}`) + this.directives.push(directive) + } +} diff --git a/lib/src/error/ErrorWithContext.ts b/lib/src/error/ErrorWithContext.ts new file mode 100644 index 0000000..6508319 --- /dev/null +++ b/lib/src/error/ErrorWithContext.ts @@ -0,0 +1,4 @@ + +export default class ErrorWithContext extends Error { + +} diff --git a/lib/src/lifecycle/Application.ts b/lib/src/lifecycle/Application.ts index ab8ac3e..86b8901 100644 --- a/lib/src/lifecycle/Application.ts +++ b/lib/src/lifecycle/Application.ts @@ -68,7 +68,16 @@ export default class Application { * @return Promise */ async down() { + this.logger.info('Stopping Daton...', true) + for ( const unit of this.instantiated_units ) { + if ( !unit ) continue + await this.stop_unit(unit) + } + setTimeout(() => { + this.logger.warn(`Force exiting...`) + Deno.exit() + }, 2000) } /** @@ -106,8 +115,25 @@ export default class Application { } 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) + this.logger.debug(e) + } + } + + /** + * Shut down the given lifecycle unit. + * @param {LifecycleUnit} unit + */ + protected async stop_unit(unit: LifecycleUnit) { + try { + unit.status = Status.Stopping + this.logger.info(`Stopping ${unit.constructor.name}...`) + await unit.down() + this.logger.verbose(`Successfully stopped ${unit.constructor.name}`) + unit.status = Status.Stopped + } catch (e) { + unit.status = Status.Error + this.logger.error(`Error encountered while stopping ${unit.constructor.name}. Will attempt to proceed.`) + this.logger.debug(e) } } diff --git a/lib/src/unit/HttpServer.ts b/lib/src/unit/HttpServer.ts index cc9e3e7..0ab4f3d 100644 --- a/lib/src/unit/HttpServer.ts +++ b/lib/src/unit/HttpServer.ts @@ -31,6 +31,11 @@ export default class HttpServer extends LifecycleUnit { const request_timeout: number = this.config.get('server.request_timeout', 15000) + Deno.signal(Deno.Signal.SIGINT).then(() => { + this.logger.info('Closing server...', true) + this._server.close() + }) + for await ( const native_request of this._server ) { let req: Request = this.make(Request, native_request)