Refactor units to be generic; start bundles; start app index.ts
This commit is contained in:
parent
c0777f77b5
commit
60bb9afa29
2
app/bundle/daton.ts
Normal file
2
app/bundle/daton.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from '../../lib/src/module.ts'
|
||||||
|
export * from '../../di/module.ts'
|
4
app/bundle/daton_units.ts
Normal file
4
app/bundle/daton_units.ts
Normal file
@ -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'
|
5
app/configs/app.config.ts
Normal file
5
app/configs/app.config.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { env } from '../../lib/src/unit/Scaffolding.ts';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: env('APP_NAME', 'Daton'),
|
||||||
|
}
|
@ -1,5 +0,0 @@
|
|||||||
import { env } from '../../../lib/src/unit/Scaffolding.ts';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: env('APP_NAME', 'Daton'),
|
|
||||||
}
|
|
16
app/configs/db.config.ts
Normal file
16
app/configs/db.config.ts
Normal file
@ -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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
20
app/index.ts
Executable file
20
app/index.ts
Executable file
@ -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()
|
8
app/tsconfig.json
Executable file
8
app/tsconfig.json
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"emitDecoratorMetadata": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
8
app/units.ts
Normal file
8
app/units.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import {ConfigUnit, DatabaseUnit, ControllerUnit, MiddlewareUnit} from './bundle/daton_units.ts'
|
||||||
|
|
||||||
|
export default [
|
||||||
|
ConfigUnit,
|
||||||
|
DatabaseUnit,
|
||||||
|
MiddlewareUnit,
|
||||||
|
ControllerUnit,
|
||||||
|
]
|
@ -1,16 +1,21 @@
|
|||||||
import {Service} from '../../../di/src/decorator/Service.ts'
|
import {Service} from '../../../di/src/decorator/Service.ts'
|
||||||
import {Logging} from '../service/logging/Logging.ts'
|
import {Logging} from '../service/logging/Logging.ts'
|
||||||
import Unit from './Unit.ts'
|
import LifecycleUnit from './Unit.ts'
|
||||||
import {container, make} from '../../../di/src/global.ts'
|
import {container, make} from '../../../di/src/global.ts'
|
||||||
import {DependencyKey} from '../../../di/src/type/DependencyKey.ts'
|
import {DependencyKey} from '../../../di/src/type/DependencyKey.ts'
|
||||||
import RunLevelErrorHandler from '../error/RunLevelErrorHandler.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()
|
@Service()
|
||||||
export default class Application {
|
export default class Application {
|
||||||
|
protected instantiated_units: Collection<LifecycleUnit> = new Collection<LifecycleUnit>()
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected logger: Logging,
|
protected logger: Logging,
|
||||||
protected rleh: RunLevelErrorHandler,
|
protected rleh: RunLevelErrorHandler,
|
||||||
protected units: Unit[],
|
protected units: (Instantiable<LifecycleUnit>)[],
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
make(token: DependencyKey) {
|
make(token: DependencyKey) {
|
||||||
@ -22,7 +27,12 @@ export default class Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async up() {
|
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() {
|
async down() {
|
||||||
@ -31,7 +41,8 @@ export default class Application {
|
|||||||
|
|
||||||
async run() {
|
async run() {
|
||||||
try {
|
try {
|
||||||
this.logger.info('Starting Daton...')
|
await this.up()
|
||||||
|
await this.down()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await this.app_error(e)
|
await this.app_error(e)
|
||||||
}
|
}
|
||||||
@ -40,4 +51,19 @@ export default class Application {
|
|||||||
async app_error(e: Error) {
|
async app_error(e: Error) {
|
||||||
this.rleh.handle(e)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import { Status, isStatus } from '../const/status.ts'
|
import { Status, isStatus } from '../const/status.ts'
|
||||||
import { Unit } from './decorators.ts'
|
|
||||||
import { Collection } from '../collection/Collection.ts'
|
import { Collection } from '../collection/Collection.ts'
|
||||||
import {container, make} from '../../../di/src/global.ts'
|
import {container, make} from '../../../di/src/global.ts'
|
||||||
import {DependencyKey} from "../../../di/src/type/DependencyKey.ts";
|
import {DependencyKey} from '../../../di/src/type/DependencyKey.ts'
|
||||||
import Instantiable, {isInstantiable} from "../../../di/src/type/Instantiable.ts";
|
import Instantiable, {isInstantiable} from '../../../di/src/type/Instantiable.ts'
|
||||||
|
|
||||||
const isLifecycleUnit = (something: any): something is (typeof LifecycleUnit) => {
|
const isLifecycleUnit = (something: any): something is (typeof LifecycleUnit) => {
|
||||||
return isInstantiable(something) && something.prototype instanceof LifecycleUnit
|
return isInstantiable(something) && something.prototype instanceof LifecycleUnit
|
||||||
|
2
lib/src/module.ts
Normal file
2
lib/src/module.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export { default as Scaffolding } from './unit/Scaffolding.ts'
|
||||||
|
export { default as Application } from './lifecycle/Application.ts'
|
@ -1,6 +1,6 @@
|
|||||||
export default abstract class Cache {
|
export default abstract class Cache {
|
||||||
public abstract async fetch(key: string): Promise<any>;
|
public abstract async fetch(key: string): Promise<any>;
|
||||||
public abstract async put(key: string, value: any): Promise<void>;
|
public abstract async put(key: string, value: string): Promise<void>;
|
||||||
public abstract async has(key: string): Promise<boolean>;
|
public abstract async has(key: string): Promise<boolean>;
|
||||||
public abstract async drop(key: string): Promise<void>;
|
public abstract async drop(key: string): Promise<void>;
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import { Collection } from '../collection/Collection.ts'
|
|||||||
|
|
||||||
export interface InMemCacheItem {
|
export interface InMemCacheItem {
|
||||||
key: string,
|
key: string,
|
||||||
item: any,
|
item: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
export class InMemCache extends Cache {
|
export class InMemCache extends Cache {
|
||||||
@ -14,7 +14,7 @@ export class InMemCache extends Cache {
|
|||||||
if ( item ) return item.item
|
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)
|
const existing = this.items.firstWhere('key', '=', key)
|
||||||
if ( existing ) existing.item = item
|
if ( existing ) existing.item = item
|
||||||
else this.items.push({ key, item })
|
else this.items.push({ key, item })
|
||||||
|
@ -8,11 +8,11 @@ export interface CanonicalDefinition {
|
|||||||
imported: any,
|
imported: any,
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Canonical extends LifecycleUnit {
|
export class Canonical<T> extends LifecycleUnit {
|
||||||
protected base_path: string = '.'
|
protected base_path: string = '.'
|
||||||
protected suffix: string = '.ts'
|
protected suffix: string = '.ts'
|
||||||
protected canonical_item: string = ''
|
protected canonical_item: string = ''
|
||||||
protected _items: { [key: string]: any } = {}
|
protected _items: { [key: string]: T } = {}
|
||||||
|
|
||||||
public get path(): string {
|
public get path(): string {
|
||||||
return path.resolve(this.base_path)
|
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))
|
this.make(Canon).register_resource(this.canonical_items, (key: string) => this.get(key))
|
||||||
}
|
}
|
||||||
|
|
||||||
public async init_canonical_item(definition: CanonicalDefinition): Promise<any> {
|
public async init_canonical_item(definition: CanonicalDefinition): Promise<T> {
|
||||||
return definition.imported.default
|
return definition.imported.default
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,12 +47,7 @@ export class Canonical extends LifecycleUnit {
|
|||||||
return { canonical_name, original_name, imported }
|
return { canonical_name, original_name, imported }
|
||||||
}
|
}
|
||||||
|
|
||||||
public get(key: string): any {
|
public get(key: string): T | undefined {
|
||||||
const key_parts = key.split('.')
|
return this._items[key]
|
||||||
let desc_value = this._items
|
|
||||||
key_parts.forEach(part => {
|
|
||||||
desc_value = desc_value[part]
|
|
||||||
})
|
|
||||||
return desc_value
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import {Canonical} from './Canonical.ts'
|
|
||||||
import { Unit } from '../lifecycle/decorators.ts'
|
import { Unit } from '../lifecycle/decorators.ts'
|
||||||
|
import {RecursiveCanonical} from './RecursiveCanonical.ts'
|
||||||
|
|
||||||
@Unit()
|
@Unit()
|
||||||
export default class Config extends Canonical {
|
export default class Config extends RecursiveCanonical {
|
||||||
protected base_path = './app/configs'
|
protected base_path = './app/configs'
|
||||||
protected suffix = '.config.ts'
|
protected suffix = '.config.ts'
|
||||||
protected canonical_item = 'config'
|
protected canonical_item = 'config'
|
||||||
|
@ -4,7 +4,7 @@ import Controller from '../http/Controller.ts'
|
|||||||
import { Unit } from '../lifecycle/decorators.ts'
|
import { Unit } from '../lifecycle/decorators.ts'
|
||||||
|
|
||||||
@Unit()
|
@Unit()
|
||||||
export default class Controllers extends InstantiableCanonical {
|
export default class Controllers extends InstantiableCanonical<Controller> {
|
||||||
protected base_path = './app/http/controllers'
|
protected base_path = './app/http/controllers'
|
||||||
protected canonical_item = 'controller'
|
protected canonical_item = 'controller'
|
||||||
protected suffix = '.controller.ts'
|
protected suffix = '.controller.ts'
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {Canonical, CanonicalDefinition} from './Canonical.ts'
|
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 {
|
export class InvalidCanonicalExportError extends Error {
|
||||||
constructor(name: string) {
|
constructor(name: string) {
|
||||||
@ -7,8 +7,8 @@ export class InvalidCanonicalExportError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class InstantiableCanonical extends Canonical {
|
export class InstantiableCanonical<T> extends Canonical<Instantiable<T>> {
|
||||||
public async init_canonical_item(def: CanonicalDefinition) {
|
public async init_canonical_item(def: CanonicalDefinition): Promise<Instantiable<T>> {
|
||||||
if ( isInstantiable(def.imported.default) ) {
|
if ( isInstantiable(def.imported.default) ) {
|
||||||
return this.make(def.imported.default)
|
return this.make(def.imported.default)
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import { Middleware } from '../http/Middleware.ts'
|
|||||||
import { Unit } from '../lifecycle/decorators.ts'
|
import { Unit } from '../lifecycle/decorators.ts'
|
||||||
|
|
||||||
@Unit()
|
@Unit()
|
||||||
export default class Middlewares extends InstantiableCanonical {
|
export default class Middlewares extends InstantiableCanonical<Middleware> {
|
||||||
protected base_path = './app/http/middleware'
|
protected base_path = './app/http/middleware'
|
||||||
protected canonical_item = 'middleware'
|
protected canonical_item = 'middleware'
|
||||||
protected suffix = '.middleware.ts'
|
protected suffix = '.middleware.ts'
|
||||||
|
12
lib/src/unit/RecursiveCanonical.ts
Normal file
12
lib/src/unit/RecursiveCanonical.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import {Canonical} from './Canonical.ts'
|
||||||
|
|
||||||
|
export class RecursiveCanonical extends Canonical<any> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -10,7 +10,7 @@ import { Container } from '../../../di/src/Container.ts'
|
|||||||
import { Inject } from '../../../di/src/decorator/Injection.ts'
|
import { Inject } from '../../../di/src/decorator/Injection.ts'
|
||||||
import CacheFactory from "../support/CacheFactory.ts";
|
import CacheFactory from "../support/CacheFactory.ts";
|
||||||
|
|
||||||
const env = (name: string, fallback: any) => {
|
const env = (name: string, fallback?: any) => {
|
||||||
const scaffolding = make(Scaffolding)
|
const scaffolding = make(Scaffolding)
|
||||||
return scaffolding.env(name) ?? fallback
|
return scaffolding.env(name) ?? fallback
|
||||||
}
|
}
|
||||||
@ -31,6 +31,9 @@ export default class Scaffolding extends LifecycleUnit {
|
|||||||
|
|
||||||
public async up() {
|
public async up() {
|
||||||
this.setup_logging()
|
this.setup_logging()
|
||||||
|
|
||||||
|
this.logger.verbose('Adding the cache production factory to the container...')
|
||||||
|
this.injector.register_factory(new CacheFactory())
|
||||||
}
|
}
|
||||||
|
|
||||||
public setup_logging() {
|
public setup_logging() {
|
||||||
@ -48,8 +51,5 @@ export default class Scaffolding extends LifecycleUnit {
|
|||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
this.logger.info('Logging initialized.', true)
|
this.logger.info('Logging initialized.', true)
|
||||||
|
|
||||||
this.logger.verbose('Adding the cache production factory to the container...')
|
|
||||||
this.injector.register_factory(new CacheFactory())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
31
orm/src/DatabaseUnit.ts
Normal file
31
orm/src/DatabaseUnit.ts
Normal file
@ -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}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
70
test.ts
70
test.ts
@ -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<User> {
|
|
||||||
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' })
|
|
||||||
})()
|
|
Loading…
Reference in New Issue
Block a user