diff --git a/app/bundle/daton.ts b/app/bundle/daton.ts new file mode 100644 index 0000000..9b07d55 --- /dev/null +++ b/app/bundle/daton.ts @@ -0,0 +1,2 @@ +export * from '../../lib/src/module.ts' +export * from '../../di/module.ts' \ No newline at end of file diff --git a/app/bundle/daton_units.ts b/app/bundle/daton_units.ts new file mode 100644 index 0000000..f052352 --- /dev/null +++ b/app/bundle/daton_units.ts @@ -0,0 +1,4 @@ +export { default as ConfigUnit } from '../../lib/src/unit/Config.ts' +export { DatabaseUnit } from '../../orm/src/DatabaseUnit.ts' +export { default as ControllerUnit } from '../../lib/src/unit/Controllers.ts' +export { default as MiddlewareUnit } from '../../lib/src/unit/Middlewares.ts' diff --git a/app/configs/app.config.ts b/app/configs/app.config.ts new file mode 100644 index 0000000..a56cc2a --- /dev/null +++ b/app/configs/app.config.ts @@ -0,0 +1,5 @@ +import { env } from '../../lib/src/unit/Scaffolding.ts'; + +export default { + name: env('APP_NAME', 'Daton'), +} diff --git a/app/configs/app/app.config.ts b/app/configs/app/app.config.ts deleted file mode 100644 index 050f56e..0000000 --- a/app/configs/app/app.config.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { env } from '../../../lib/src/unit/Scaffolding.ts'; - -export default { - name: env('APP_NAME', 'Daton'), -} diff --git a/app/configs/db.config.ts b/app/configs/db.config.ts new file mode 100644 index 0000000..983fa12 --- /dev/null +++ b/app/configs/db.config.ts @@ -0,0 +1,16 @@ +import {env} from "../../lib/src/unit/Scaffolding.ts"; + +export default { + + connections: { + default: { + type: env('DB_TYPE', 'postgres'), + user: env('DB_USERNAME', 'daton'), + password: env('DB_PASSWORD'), + database: env('DB_DATABASE', 'daton'), + hostname: env('DB_HOSTNAME', 'localhost'), + port: env('DB_PORT', 5432), + } + } + +} diff --git a/app/index.ts b/app/index.ts new file mode 100755 index 0000000..2ed0d40 --- /dev/null +++ b/app/index.ts @@ -0,0 +1,20 @@ +#!/usr/bin/env -S deno run -c ./tsconfig.json --unstable --allow-read --allow-env --allow-net +/* Main executable for the Daton app. */ + +import { make, Scaffolding, Application } from './bundle/daton.ts' +import units from './units.ts' + +/* + * Let's get up and running. The scaffolding provides the bare minimum + * amount of support required to get Daton up and running. The app handles + * the rest. + */ +const scaffolding = make(Scaffolding) +await scaffolding.up() + +/* + * Now, import the units and start the application. The units define each + * modular piece of functionality that is managed by the Daton app. + */ +const app = make(Application, units) +await app.run() diff --git a/app/tsconfig.json b/app/tsconfig.json new file mode 100755 index 0000000..04073ec --- /dev/null +++ b/app/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "experimentalDecorators": true, + "emitDecoratorMetadata": true + } +} + + diff --git a/app/units.ts b/app/units.ts new file mode 100644 index 0000000..1bb5f37 --- /dev/null +++ b/app/units.ts @@ -0,0 +1,8 @@ +import {ConfigUnit, DatabaseUnit, ControllerUnit, MiddlewareUnit} from './bundle/daton_units.ts' + +export default [ + ConfigUnit, + DatabaseUnit, + MiddlewareUnit, + ControllerUnit, +] diff --git a/lib/src/lifecycle/Application.ts b/lib/src/lifecycle/Application.ts index 6a27be2..c7749c3 100644 --- a/lib/src/lifecycle/Application.ts +++ b/lib/src/lifecycle/Application.ts @@ -1,16 +1,21 @@ -import { Service } from '../../../di/src/decorator/Service.ts' -import { Logging } from '../service/logging/Logging.ts' -import Unit from './Unit.ts' -import { container, make } from '../../../di/src/global.ts' -import { DependencyKey } from '../../../di/src/type/DependencyKey.ts' +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"; @Service() export default class Application { + protected instantiated_units: Collection = new Collection() + constructor( protected logger: Logging, protected rleh: RunLevelErrorHandler, - protected units: Unit[], + protected units: (Instantiable)[], ) {} make(token: DependencyKey) { @@ -22,7 +27,12 @@ export default class Application { } 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) + } } async down() { @@ -31,7 +41,8 @@ export default class Application { async run() { try { - this.logger.info('Starting Daton...') + await this.up() + await this.down() } catch (e) { await this.app_error(e) } @@ -40,4 +51,19 @@ export default class Application { async app_error(e: Error) { this.rleh.handle(e) } + + 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) + } + } } diff --git a/lib/src/lifecycle/Unit.ts b/lib/src/lifecycle/Unit.ts index 10abec4..c17c13a 100644 --- a/lib/src/lifecycle/Unit.ts +++ b/lib/src/lifecycle/Unit.ts @@ -1,9 +1,8 @@ import { Status, isStatus } from '../const/status.ts' -import { Unit } from './decorators.ts' import { Collection } from '../collection/Collection.ts' import {container, make} from '../../../di/src/global.ts' -import {DependencyKey} from "../../../di/src/type/DependencyKey.ts"; -import Instantiable, {isInstantiable} from "../../../di/src/type/Instantiable.ts"; +import {DependencyKey} from '../../../di/src/type/DependencyKey.ts' +import Instantiable, {isInstantiable} from '../../../di/src/type/Instantiable.ts' const isLifecycleUnit = (something: any): something is (typeof LifecycleUnit) => { return isInstantiable(something) && something.prototype instanceof LifecycleUnit diff --git a/lib/src/module.ts b/lib/src/module.ts new file mode 100644 index 0000000..cd932e1 --- /dev/null +++ b/lib/src/module.ts @@ -0,0 +1,2 @@ +export { default as Scaffolding } from './unit/Scaffolding.ts' +export { default as Application } from './lifecycle/Application.ts' diff --git a/lib/src/support/Cache.ts b/lib/src/support/Cache.ts index 5ee1d40..2bf442c 100644 --- a/lib/src/support/Cache.ts +++ b/lib/src/support/Cache.ts @@ -1,6 +1,6 @@ export default abstract class Cache { public abstract async fetch(key: string): Promise; - public abstract async put(key: string, value: any): Promise; + public abstract async put(key: string, value: string): Promise; public abstract async has(key: string): Promise; public abstract async drop(key: string): Promise; } diff --git a/lib/src/support/InMemCache.ts b/lib/src/support/InMemCache.ts index cf8a663..54c9ebc 100644 --- a/lib/src/support/InMemCache.ts +++ b/lib/src/support/InMemCache.ts @@ -3,7 +3,7 @@ import { Collection } from '../collection/Collection.ts' export interface InMemCacheItem { key: string, - item: any, + item: string, } export class InMemCache extends Cache { @@ -14,7 +14,7 @@ export class InMemCache extends Cache { if ( item ) return item.item } - public async put(key: string, item: any) { + public async put(key: string, item: string) { const existing = this.items.firstWhere('key', '=', key) if ( existing ) existing.item = item else this.items.push({ key, item }) diff --git a/lib/src/unit/Canonical.ts b/lib/src/unit/Canonical.ts index 6c32ab1..c6cc68e 100644 --- a/lib/src/unit/Canonical.ts +++ b/lib/src/unit/Canonical.ts @@ -8,11 +8,11 @@ export interface CanonicalDefinition { imported: any, } -export class Canonical extends LifecycleUnit { +export class Canonical extends LifecycleUnit { protected base_path: string = '.' protected suffix: string = '.ts' protected canonical_item: string = '' - protected _items: { [key: string]: any } = {} + protected _items: { [key: string]: T } = {} public get path(): string { return path.resolve(this.base_path) @@ -32,7 +32,7 @@ export class Canonical extends LifecycleUnit { this.make(Canon).register_resource(this.canonical_items, (key: string) => this.get(key)) } - public async init_canonical_item(definition: CanonicalDefinition): Promise { + public async init_canonical_item(definition: CanonicalDefinition): Promise { return definition.imported.default } @@ -47,12 +47,7 @@ export class Canonical extends LifecycleUnit { return { canonical_name, original_name, imported } } - public get(key: string): any { - const key_parts = key.split('.') - let desc_value = this._items - key_parts.forEach(part => { - desc_value = desc_value[part] - }) - return desc_value + public get(key: string): T | undefined { + return this._items[key] } } diff --git a/lib/src/unit/Config.ts b/lib/src/unit/Config.ts index f52242c..b702314 100644 --- a/lib/src/unit/Config.ts +++ b/lib/src/unit/Config.ts @@ -1,8 +1,8 @@ -import {Canonical} from './Canonical.ts' import { Unit } from '../lifecycle/decorators.ts' +import {RecursiveCanonical} from './RecursiveCanonical.ts' @Unit() -export default class Config extends Canonical { +export default class Config extends RecursiveCanonical { protected base_path = './app/configs' protected suffix = '.config.ts' protected canonical_item = 'config' diff --git a/lib/src/unit/Controllers.ts b/lib/src/unit/Controllers.ts index 9bc6bd1..ad780b2 100644 --- a/lib/src/unit/Controllers.ts +++ b/lib/src/unit/Controllers.ts @@ -4,7 +4,7 @@ import Controller from '../http/Controller.ts' import { Unit } from '../lifecycle/decorators.ts' @Unit() -export default class Controllers extends InstantiableCanonical { +export default class Controllers extends InstantiableCanonical { protected base_path = './app/http/controllers' protected canonical_item = 'controller' protected suffix = '.controller.ts' diff --git a/lib/src/unit/InstantiableCanonical.ts b/lib/src/unit/InstantiableCanonical.ts index b3836ad..dd423b1 100644 --- a/lib/src/unit/InstantiableCanonical.ts +++ b/lib/src/unit/InstantiableCanonical.ts @@ -1,5 +1,5 @@ import {Canonical, CanonicalDefinition} from './Canonical.ts' -import {isInstantiable} from '../../../di/src/type/Instantiable.ts' +import Instantiable, {isInstantiable} from '../../../di/src/type/Instantiable.ts' export class InvalidCanonicalExportError extends Error { constructor(name: string) { @@ -7,8 +7,8 @@ export class InvalidCanonicalExportError extends Error { } } -export class InstantiableCanonical extends Canonical { - public async init_canonical_item(def: CanonicalDefinition) { +export class InstantiableCanonical extends Canonical> { + public async init_canonical_item(def: CanonicalDefinition): Promise> { if ( isInstantiable(def.imported.default) ) { return this.make(def.imported.default) } diff --git a/lib/src/unit/Middlewares.ts b/lib/src/unit/Middlewares.ts index a1e59fa..3dd1bdc 100644 --- a/lib/src/unit/Middlewares.ts +++ b/lib/src/unit/Middlewares.ts @@ -4,7 +4,7 @@ import { Middleware } from '../http/Middleware.ts' import { Unit } from '../lifecycle/decorators.ts' @Unit() -export default class Middlewares extends InstantiableCanonical { +export default class Middlewares extends InstantiableCanonical { protected base_path = './app/http/middleware' protected canonical_item = 'middleware' protected suffix = '.middleware.ts' diff --git a/lib/src/unit/RecursiveCanonical.ts b/lib/src/unit/RecursiveCanonical.ts new file mode 100644 index 0000000..f46106d --- /dev/null +++ b/lib/src/unit/RecursiveCanonical.ts @@ -0,0 +1,12 @@ +import {Canonical} from './Canonical.ts' + +export class RecursiveCanonical extends Canonical { + public get(key: string): any | undefined { + const parts = key.split('.') + let current_value = this._items + for ( const part of parts ) { + current_value = current_value?.[part] + } + return current_value + } +} diff --git a/lib/src/unit/Scaffolding.ts b/lib/src/unit/Scaffolding.ts index 36e73a8..dc9d924 100644 --- a/lib/src/unit/Scaffolding.ts +++ b/lib/src/unit/Scaffolding.ts @@ -10,7 +10,7 @@ import { Container } from '../../../di/src/Container.ts' import { Inject } from '../../../di/src/decorator/Injection.ts' import CacheFactory from "../support/CacheFactory.ts"; -const env = (name: string, fallback: any) => { +const env = (name: string, fallback?: any) => { const scaffolding = make(Scaffolding) return scaffolding.env(name) ?? fallback } @@ -31,6 +31,9 @@ export default class Scaffolding extends LifecycleUnit { public async up() { this.setup_logging() + + this.logger.verbose('Adding the cache production factory to the container...') + this.injector.register_factory(new CacheFactory()) } public setup_logging() { @@ -48,8 +51,5 @@ export default class Scaffolding extends LifecycleUnit { } catch (e) {} this.logger.info('Logging initialized.', true) - - this.logger.verbose('Adding the cache production factory to the container...') - this.injector.register_factory(new CacheFactory()) } } diff --git a/orm/src/DatabaseUnit.ts b/orm/src/DatabaseUnit.ts new file mode 100644 index 0000000..d50197b --- /dev/null +++ b/orm/src/DatabaseUnit.ts @@ -0,0 +1,31 @@ +import LifecycleUnit from '../../lib/src/lifecycle/Unit.ts' +import Config from '../../lib/src/unit/Config.ts' +import {Unit} from '../../lib/src/lifecycle/decorators.ts' +import Database from './service/Database.ts' + +@Unit() +export class DatabaseUnit extends LifecycleUnit { + constructor( + protected config: Config, + protected db: Database, + ) { + super() + } + + public async up() { + const connections: { [key: string]: any } = this.config.get('db.connections') + + for ( const conn_name in connections ) { + if ( !connections.hasOwnProperty(conn_name) ) continue + const config = connections[conn_name] + + switch(config.type) { + case 'postgres': + await this.db.postgres(conn_name, config) + break + default: + throw new TypeError(`Invalid database driver type: ${config.type}`) + } + } + } +} diff --git a/test.ts b/test.ts deleted file mode 100644 index eefcac8..0000000 --- a/test.ts +++ /dev/null @@ -1,70 +0,0 @@ -import {make} from "./di/src/global.ts"; -import Application from "./lib/src/lifecycle/Application.ts"; -import Scaffolding from "./lib/src/unit/Scaffolding.ts"; -import {Logging} from "./lib/src/service/logging/Logging.ts"; -import Database from "./orm/src/service/Database.ts"; -import { Model } from './orm/src/model/Model.ts' -import {Field} from "./orm/src/model/Field.ts"; -import {QueryRow, Type} from './orm/src/db/types.ts'; -import {Builder} from "./orm/src/builder/Builder.ts"; -import ObjectResultOperator from "./orm/src/builder/type/result/ObjectResultOperator.ts"; -import {BehaviorSubject} from "./lib/src/support/BehaviorSubject.ts"; - -// TODO enum bit fields -// TODO JSON field support - -;(async () => { - const scaf = make(Scaffolding) - await scaf.up() - - const logger = make(Logging) - - const app = make(Application) - await app.run() - - const db = make(Database) - - await db.postgres('garrettmills', { - user: 'garrettmills', - password: 'garrettmills', - database: 'garrettmills', - hostname: 'localhost', - port: 5432, - }) - - class User extends Model { - protected static connection = 'garrettmills' - protected static table = 'daton_users' - protected static key = 'user_id' - - protected static appends: string[] = ['display_name'] - protected static masks: string[] = ['active'] - - @Field(Type.int) - public user_id!: number - - @Field(Type.varchar) - public username!: string - - @Field(Type.varchar) - public first_name!: string - - @Field(Type.varchar) - public last_name!: string - - @Field(Type.boolean) - public active!: boolean - - @Field(Type.timestamp) - public updated_at!: Date - - @Field(Type.timestamp) - public created_at!: Date - - get display_name(): string { - return `${this.last_name}, ${this.first_name}` - } - } - - const u = await User.find_one({ username: 'garrettmills' }) -})()