db structure abstraction; async collection; update/insert queries; model saving
This commit is contained in:
parent
eddb4f1fbe
commit
e4f5da7ac6
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
||||
.idea*
|
||||
test*
|
||||
|
5
app/configs/app/app.config.ts
Normal file
5
app/configs/app/app.config.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { env } from '../../../lib/src/unit/Scaffolding.ts';
|
||||
|
||||
export default {
|
||||
name: env('APP_NAME', 'Daton'),
|
||||
}
|
4
app/configs/server.config.ts
Normal file
4
app/configs/server.config.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export default {
|
||||
port: 8080,
|
||||
use_ssl: false,
|
||||
}
|
5
app/http/controllers/Test.controller.ts
Normal file
5
app/http/controllers/Test.controller.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import Controller from "../../../lib/src/http/Controller.ts";
|
||||
|
||||
export default class TestController extends Controller {
|
||||
|
||||
}
|
@ -33,6 +33,16 @@ class Container {
|
||||
private factories: Collection<AbstractFactory> = new Collection<AbstractFactory>()
|
||||
private instances: Collection<InstanceRef> = new Collection<InstanceRef>()
|
||||
|
||||
constructor() {
|
||||
this.register(Container)
|
||||
this.instances.push({
|
||||
key: Container,
|
||||
value: this,
|
||||
})
|
||||
|
||||
this.register_singleton('injector', this)
|
||||
}
|
||||
|
||||
register(dependency: Instantiable<any>) {
|
||||
if ( this.resolve(dependency) )
|
||||
throw new DuplicateFactoryKeyError(dependency)
|
||||
@ -134,14 +144,22 @@ class Container {
|
||||
}
|
||||
|
||||
make(target: DependencyKey, ...parameters: any[]) {
|
||||
if ( this.has_key(target) ) {
|
||||
if ( this.has_key(target) )
|
||||
return this.resolve_and_create(target, ...parameters)
|
||||
}
|
||||
else if ( typeof target !== 'string' )
|
||||
return this.produce_factory(new Factory(target), parameters)
|
||||
else
|
||||
throw new TypeError(`Invalid or unknown make target: ${target}`)
|
||||
}
|
||||
|
||||
get_dependencies(target: DependencyKey): Collection<DependencyKey> {
|
||||
const factory = this.resolve(target)
|
||||
|
||||
if ( !factory )
|
||||
throw new InvalidDependencyKeyError(target)
|
||||
|
||||
return factory.get_dependency_keys().pluck('key')
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
|
26
lib/src/collection/ArrayIterable.ts
Normal file
26
lib/src/collection/ArrayIterable.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { Iterable } from './Iterable.ts'
|
||||
import { collect } from './Collection.ts'
|
||||
|
||||
export class ArrayIterable<T> extends Iterable<T> {
|
||||
constructor(
|
||||
protected items: T[],
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
async at_index(i: number) {
|
||||
return this.items[i]
|
||||
}
|
||||
|
||||
async from_range(start: number, end: number) {
|
||||
return collect(this.items.slice(start, end + 1))
|
||||
}
|
||||
|
||||
async count() {
|
||||
return this.items.length
|
||||
}
|
||||
|
||||
clone() {
|
||||
return new ArrayIterable([...this.items])
|
||||
}
|
||||
}
|
663
lib/src/collection/AsyncCollection.ts
Normal file
663
lib/src/collection/AsyncCollection.ts
Normal file
@ -0,0 +1,663 @@
|
||||
import {
|
||||
AssociatedCollectionItem,
|
||||
Collection, CollectionIndex,
|
||||
CollectionItem, ComparisonFunction,
|
||||
DeterminesEquality, KeyFunction,
|
||||
KeyOperator, KeyReducerFunction, MaybeCollectionIndex, MaybeCollectionItem
|
||||
} from './Collection.ts'
|
||||
import {Iterable, StopIteration} from './Iterable.ts'
|
||||
import {applyWhere, WhereOperator} from './Where.ts'
|
||||
type AsyncCollectionComparable<T> = CollectionItem<T>[] | Collection<T> | AsyncCollection<T>
|
||||
type AsyncKeyFunction<T, T2> = (item: CollectionItem<T>, index: number) => CollectionItem<T2> | Promise<CollectionItem<T2>>
|
||||
type AsyncCollectionFunction<T, T2> = (items: AsyncCollection<T>) => T2
|
||||
|
||||
export class AsyncCollection<T> {
|
||||
constructor(
|
||||
private _items: Iterable<T>,
|
||||
private _chunk_size: number = 1000, // TODO fix this. It's just for testing
|
||||
) {}
|
||||
|
||||
private async _chunk(callback: (items: Collection<T>) => any): Promise<void> {
|
||||
await this._items.chunk(this._chunk_size, async items => {
|
||||
await callback(items)
|
||||
})
|
||||
await this._items.reset()
|
||||
}
|
||||
|
||||
private async _chunk_all<T2>(key: KeyOperator<T, T2>, callback: (items: Collection<T2>) => any): Promise<void> {
|
||||
await this._items.chunk(this._chunk_size, async items => {
|
||||
await callback(items.pluck(key))
|
||||
})
|
||||
await this._items.reset()
|
||||
}
|
||||
|
||||
private async _chunk_all_numbers<T2>(key: KeyOperator<T, T2>, callback: (items: number[]) => any): Promise<void> {
|
||||
await this._items.chunk(this._chunk_size, async items => {
|
||||
await callback(items.pluck(key).map(x => Number(x)).all())
|
||||
})
|
||||
await this._items.reset()
|
||||
}
|
||||
|
||||
private async _chunk_all_associate<T2>(key: KeyOperator<T, T2>, callback: (items: AssociatedCollectionItem<T2, T>[]) => any): Promise<void> {
|
||||
await this._items.chunk(this._chunk_size, async items => {
|
||||
const assoc_items: AssociatedCollectionItem<T2, T>[] = []
|
||||
if ( typeof key === 'function' ) {
|
||||
items.map((item, index) => {
|
||||
const key_item = key(item, index)
|
||||
assoc_items.push({ key: key_item, item })
|
||||
})
|
||||
} else if ( typeof key === 'string' ) {
|
||||
items.map((item, index) => {
|
||||
assoc_items.push({ key: (<any>item)[key], item })
|
||||
})
|
||||
}
|
||||
|
||||
await callback(assoc_items)
|
||||
})
|
||||
await this._items.reset()
|
||||
}
|
||||
|
||||
async all(): Promise<CollectionItem<T>[]> {
|
||||
return (await this._items.from_range(0, await this._items.count())).all()
|
||||
}
|
||||
|
||||
async collect(): Promise<Collection<T>> {
|
||||
return this._items.from_range(0, await this._items.count())
|
||||
}
|
||||
|
||||
async average<T2>(key?: KeyOperator<T, T2>): Promise<number> {
|
||||
let running_total = 0
|
||||
let running_items = 0
|
||||
|
||||
const chunk_helper = (items: number[]) => {
|
||||
running_items += items.length
|
||||
running_total += items.reduce((prev, curr) => prev + curr)
|
||||
}
|
||||
|
||||
if ( key ) await this._chunk_all_numbers(key, chunk_helper)
|
||||
else await this._chunk((items) => {
|
||||
chunk_helper(items.map(x => Number(x)).all())
|
||||
})
|
||||
|
||||
return running_total / running_items
|
||||
}
|
||||
|
||||
async median<T2>(key?: KeyOperator<T, T2>): Promise<number> {
|
||||
let items: number[] = []
|
||||
|
||||
const chunk_helper = (next_items: number[]) => {
|
||||
items = items.concat(next_items)
|
||||
}
|
||||
|
||||
if ( key ) await this._chunk_all_numbers(key, chunk_helper)
|
||||
else await this._chunk(items => {
|
||||
chunk_helper(items.map(x => Number(x)).all())
|
||||
})
|
||||
|
||||
items = items.sort((a, b) => a - b)
|
||||
const middle = Math.floor((items.length - 1) / 2)
|
||||
if ( items.length % 2 ) return items[middle]
|
||||
else return (items[middle] + items[middle + 1]) / 2
|
||||
}
|
||||
|
||||
async mode<T2>(key?: KeyOperator<T, T2>): Promise<number> {
|
||||
let counts: any = {}
|
||||
|
||||
const chunk_helper = (items: number[]) => {
|
||||
for ( const item of items ) {
|
||||
if ( !counts[item] ) counts[item] = 1
|
||||
else counts[item] += 1
|
||||
}
|
||||
}
|
||||
|
||||
if ( key ) await this._chunk_all_numbers(key, chunk_helper)
|
||||
else await this._chunk(items => {
|
||||
chunk_helper(items.map(x => Number(x)).all())
|
||||
})
|
||||
|
||||
return Number(Object.keys(counts).reduce((a, b) => counts[a] > counts[b] ? a : b)[0])
|
||||
}
|
||||
|
||||
async collapse(): Promise<Collection<any>> {
|
||||
const items = await this.collect()
|
||||
return items.collapse() as Collection<any>
|
||||
}
|
||||
|
||||
async contains<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Promise<boolean> {
|
||||
let contains = false
|
||||
|
||||
await this._chunk_all_associate(key, (items: AssociatedCollectionItem<T2, T>[]) => {
|
||||
const matches = applyWhere(items, operator, operand)
|
||||
if ( matches.length > 0 ) {
|
||||
contains = true
|
||||
throw new StopIteration()
|
||||
}
|
||||
})
|
||||
|
||||
return contains
|
||||
}
|
||||
|
||||
async clone(): Promise<AsyncCollection<T>> {
|
||||
return new AsyncCollection<T>(await this._items.clone())
|
||||
}
|
||||
|
||||
async diff(items: AsyncCollectionComparable<T>): Promise<Collection<T>> {
|
||||
const matches: T[] = []
|
||||
|
||||
await this._chunk(async chunk => {
|
||||
for ( const item of chunk.all() ) {
|
||||
if ( !(await items.includes(item)) ) {
|
||||
matches.push(item)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return new Collection<T>(matches)
|
||||
}
|
||||
|
||||
async diffUsing(items: AsyncCollectionComparable<T>, compare: DeterminesEquality<T>): Promise<Collection<T>> {
|
||||
const matches: T[] = []
|
||||
|
||||
await this._chunk(async chunk => {
|
||||
for ( const item of chunk.all() ) {
|
||||
if ( !(await items.some(exc => compare(item, exc))) )
|
||||
matches.push(item)
|
||||
}
|
||||
})
|
||||
|
||||
return new Collection<T>(matches)
|
||||
}
|
||||
|
||||
async includes(item: CollectionItem<T>): Promise<boolean> {
|
||||
let contains = false
|
||||
|
||||
await this._chunk(items => {
|
||||
if ( items.includes(item) ) {
|
||||
contains = true
|
||||
throw new StopIteration()
|
||||
}
|
||||
})
|
||||
|
||||
return contains
|
||||
}
|
||||
|
||||
async some(operator: (item: T) => boolean): Promise<boolean> {
|
||||
let contains = false
|
||||
|
||||
await this._chunk(items => {
|
||||
for ( const item of items.all() ) {
|
||||
if ( operator(item) ) {
|
||||
contains = true
|
||||
throw new StopIteration()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return contains
|
||||
}
|
||||
|
||||
async each<T2>(func: AsyncKeyFunction<T, T2>): Promise<void> {
|
||||
let index = 0
|
||||
|
||||
await this._chunk(async items => {
|
||||
for ( const item of items.all() ) {
|
||||
await func(item, index)
|
||||
index += 1
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async map<T2>(func: AsyncKeyFunction<T, T2>): Promise<Collection<T2>> {
|
||||
const new_items: CollectionItem<T2>[] = []
|
||||
await this.each(async (item, index) => {
|
||||
new_items.push(await func(item, index))
|
||||
})
|
||||
return new Collection<T2>(new_items)
|
||||
}
|
||||
|
||||
async every<T2>(func: AsyncKeyFunction<T, T2>): Promise<boolean> {
|
||||
let pass = true
|
||||
let index = 0
|
||||
|
||||
await this._chunk(async items => {
|
||||
for ( const item of items.all() ) {
|
||||
if ( !(await func(item, index)) ) {
|
||||
pass = false
|
||||
throw new StopIteration()
|
||||
}
|
||||
|
||||
index += 1
|
||||
}
|
||||
})
|
||||
|
||||
return pass
|
||||
}
|
||||
|
||||
async everyWhere<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Promise<boolean> {
|
||||
let pass = true
|
||||
|
||||
await this._chunk(async items => {
|
||||
pass = pass && items.everyWhere(key, operator, operand)
|
||||
if ( !pass ) {
|
||||
throw new StopIteration()
|
||||
}
|
||||
})
|
||||
|
||||
return pass
|
||||
}
|
||||
|
||||
async filter<T2>(func: KeyFunction<T, T2>): Promise<Collection<T>> {
|
||||
let new_items: CollectionItem<T>[] = []
|
||||
|
||||
await this._chunk(async items => {
|
||||
new_items = new_items.concat(items.filter(func).all())
|
||||
})
|
||||
|
||||
return new Collection<T>(new_items)
|
||||
}
|
||||
|
||||
when<T2>(bool: boolean, then: AsyncCollectionFunction<T, T2>): AsyncCollection<T> {
|
||||
if ( bool ) then(this)
|
||||
return this
|
||||
}
|
||||
|
||||
unless<T2>(bool: boolean, then: AsyncCollectionFunction<T, T2>): AsyncCollection<T> {
|
||||
if ( !bool ) then(this)
|
||||
return this
|
||||
}
|
||||
|
||||
async where<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Promise<Collection<T>> {
|
||||
let new_items: CollectionItem<T>[] = []
|
||||
await this._chunk(async items => {
|
||||
new_items = new_items.concat(items.where(key, operator, operand).all())
|
||||
})
|
||||
return new Collection<T>(new_items)
|
||||
}
|
||||
|
||||
async whereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Promise<Collection<T>> {
|
||||
let new_items: CollectionItem<T>[] = []
|
||||
await this._chunk(async items => {
|
||||
new_items = new_items.concat(items.whereNot(key, operator, operand).all())
|
||||
})
|
||||
return new Collection<T>(new_items)
|
||||
}
|
||||
|
||||
async whereIn<T2>(key: KeyOperator<T, T2>, items: AsyncCollectionComparable<T2>): Promise<Collection<T>> {
|
||||
let new_items: CollectionItem<T>[] = []
|
||||
await this._chunk_all_associate(key,async chunk => {
|
||||
for ( const item of chunk ) {
|
||||
if ( await items.includes(item.key) ) {
|
||||
new_items.push(item.item)
|
||||
}
|
||||
}
|
||||
})
|
||||
return new Collection<T>(new_items)
|
||||
}
|
||||
|
||||
async whereNotIn<T2>(key: KeyOperator<T, T2>, items: AsyncCollectionComparable<T2>): Promise<Collection<T>> {
|
||||
let new_items: CollectionItem<T>[] = []
|
||||
await this._chunk_all_associate(key,async chunk => {
|
||||
for ( const item of chunk ) {
|
||||
if ( !(await items.includes(item.key)) ) {
|
||||
new_items.push(item.item)
|
||||
}
|
||||
}
|
||||
})
|
||||
return new Collection<T>(new_items)
|
||||
}
|
||||
|
||||
async first(): Promise<MaybeCollectionItem<T>> {
|
||||
if ( await this._items.count() > 0 ) {
|
||||
return this._items.at_index(0)
|
||||
}
|
||||
}
|
||||
|
||||
async firstWhere<T2>(key: KeyOperator<T, T2>, operator: WhereOperator = '=', operand: any = true): Promise<MaybeCollectionItem<T>> {
|
||||
let item = undefined
|
||||
await this._chunk_all_associate(key, async items => {
|
||||
const matches = applyWhere(items, operator, operand)
|
||||
if ( matches.length > 0 ) {
|
||||
item = matches[0]
|
||||
throw new StopIteration()
|
||||
}
|
||||
})
|
||||
return item
|
||||
}
|
||||
|
||||
async firstWhereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator = '=', operand: any = true): Promise<MaybeCollectionItem<T>> {
|
||||
let item: MaybeCollectionItem<T> = undefined
|
||||
await this._chunk(async items => {
|
||||
const matches = items.whereNot(key, operator, operand)
|
||||
if ( matches.length > 0 ) {
|
||||
item = matches.first()
|
||||
throw new StopIteration()
|
||||
}
|
||||
})
|
||||
return item
|
||||
}
|
||||
|
||||
async count() {
|
||||
return this._items.count()
|
||||
}
|
||||
|
||||
async length() {
|
||||
return this._items.count()
|
||||
}
|
||||
|
||||
async get(index: number, fallback?: any) {
|
||||
if ( (await this.count()) > index ) return this._items.at_index(index)
|
||||
else return fallback
|
||||
}
|
||||
|
||||
async at(index: number): Promise<MaybeCollectionItem<T>> {
|
||||
return this.get(index)
|
||||
}
|
||||
|
||||
async groupBy<T2>(key: KeyOperator<T, T2>): Promise<any> {
|
||||
return (await this.collect()).groupBy(key)
|
||||
}
|
||||
|
||||
async associate<T2>(key: KeyOperator<T, T2>): Promise<any> {
|
||||
return (await this.collect()).associate(key)
|
||||
}
|
||||
|
||||
async join(delimiter: string): Promise<string> {
|
||||
let running_strings: string[] = []
|
||||
|
||||
await this._chunk(async items => {
|
||||
running_strings.push(items.join(delimiter))
|
||||
})
|
||||
|
||||
return running_strings.join(delimiter)
|
||||
}
|
||||
|
||||
async implode(delimiter: string): Promise<string> {
|
||||
return this.join(delimiter)
|
||||
}
|
||||
|
||||
// TODO intersect
|
||||
|
||||
async isEmpty(): Promise<boolean> {
|
||||
return (await this._items.count()) < 1
|
||||
}
|
||||
|
||||
async isNotEmpty(): Promise<boolean> {
|
||||
return (await this._items.count()) > 0
|
||||
}
|
||||
|
||||
async last(): Promise<MaybeCollectionItem<T>> {
|
||||
const length = await this._items.count()
|
||||
if ( length > 0 ) return this._items.at_index(length - 1)
|
||||
}
|
||||
|
||||
async lastWhere<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Promise<MaybeCollectionItem<T>> {
|
||||
return (await this.where(key, operator, operand)).last()
|
||||
}
|
||||
|
||||
async lastWhereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Promise<MaybeCollectionItem<T>> {
|
||||
return (await this.whereNot(key, operator, operand)).last()
|
||||
}
|
||||
|
||||
async pluck<T2>(key: KeyOperator<T, T2>): Promise<Collection<T2>> {
|
||||
let new_items: CollectionItem<T2>[] = []
|
||||
|
||||
await this._chunk_all(key, async items => {
|
||||
new_items = new_items.concat(items.all())
|
||||
})
|
||||
|
||||
return new Collection<T2>(new_items)
|
||||
}
|
||||
|
||||
async max<T2>(key: KeyOperator<T, T2>): Promise<number> {
|
||||
let running_max: number
|
||||
|
||||
await this._chunk_all_numbers(key, async items => {
|
||||
const local_max = Math.max(...items)
|
||||
if ( typeof running_max === 'undefined' ) running_max = local_max
|
||||
else running_max = Math.max(running_max, local_max)
|
||||
})
|
||||
|
||||
// @ts-ignore
|
||||
return running_max
|
||||
}
|
||||
|
||||
async whereMax<T2>(key: KeyOperator<T, T2>): Promise<Collection<T>> {
|
||||
return this.where(key, '=', await this.max(key))
|
||||
}
|
||||
|
||||
async min<T2>(key: KeyOperator<T, T2>): Promise<number> {
|
||||
let running_min: number
|
||||
|
||||
await this._chunk_all_numbers(key, async items => {
|
||||
const local_min = Math.min(...items)
|
||||
if ( typeof running_min === 'undefined' ) running_min = local_min
|
||||
else running_min = Math.min(running_min, local_min)
|
||||
})
|
||||
|
||||
// @ts-ignore
|
||||
return running_min
|
||||
}
|
||||
|
||||
async whereMin<T2>(key: KeyOperator<T, T2>): Promise<Collection<T>> {
|
||||
return this.where(key, '=', await this.min(key))
|
||||
}
|
||||
|
||||
async merge<T2>(merge_with: AsyncCollectionComparable<T2>): Promise<Collection<T|T2>> {
|
||||
let items: T2[]
|
||||
if ( merge_with instanceof Collection ) items = await merge_with.all()
|
||||
else if ( merge_with instanceof AsyncCollection ) items = await merge_with.all()
|
||||
else if ( Array.isArray(merge_with) ) items = merge_with
|
||||
|
||||
// @ts-ignore
|
||||
return new Collection<T|T2>([...items, ...await this.all()])
|
||||
}
|
||||
|
||||
async nth(n: number): Promise<Collection<T>> {
|
||||
const matches: CollectionItem<T>[] = []
|
||||
let current = 1
|
||||
|
||||
await this._chunk(async chunk => {
|
||||
for ( const item of chunk.all() ) {
|
||||
if ( current === 1 ) matches.push(item)
|
||||
current += 1
|
||||
if ( current > n ) current = 1
|
||||
}
|
||||
})
|
||||
|
||||
return new Collection<T>(matches)
|
||||
}
|
||||
|
||||
async forPage(page: number, per_page: number): Promise<Collection<T>> {
|
||||
const start = page * per_page - per_page
|
||||
const end = page * per_page - 1
|
||||
return this._items.from_range(start, end)
|
||||
}
|
||||
|
||||
pipe<T2>(func: AsyncCollectionFunction<T, T2>): any {
|
||||
return func(this)
|
||||
}
|
||||
|
||||
/*async pop(): Promise<MaybeCollectionItem<T>> {
|
||||
const next_item = await this._items.next()
|
||||
if ( !next_item.done ) {
|
||||
return next_item.value
|
||||
}
|
||||
}*/ // TODO Fix this
|
||||
|
||||
async random(n: number): Promise<Collection<T>> {
|
||||
const random_items: CollectionItem<T>[] = []
|
||||
const fetched_indices: number[] = []
|
||||
|
||||
const max_n = await this._items.count()
|
||||
if ( n > max_n ) n = max_n
|
||||
|
||||
while ( random_items.length < n ) {
|
||||
const index = Math.floor(Math.random() * max_n)
|
||||
|
||||
if ( !fetched_indices.includes(index) ) {
|
||||
fetched_indices.push(index)
|
||||
random_items.push(await this._items.at_index(index))
|
||||
}
|
||||
}
|
||||
|
||||
return new Collection<T>(random_items)
|
||||
}
|
||||
|
||||
async reduce<T2>(reducer: KeyReducerFunction<T, T2>, initial_value?: T2): Promise<T2 | undefined> {
|
||||
let current_value = initial_value
|
||||
let index = 0
|
||||
|
||||
await this._chunk(async items => {
|
||||
for ( const item of items.all() ) {
|
||||
current_value = reducer(current_value, item, index)
|
||||
index += 1
|
||||
}
|
||||
})
|
||||
|
||||
return current_value
|
||||
}
|
||||
|
||||
async reject<T2>(truth_test: AsyncKeyFunction<T, T2>): Promise<Collection<T>> {
|
||||
let rejected: CollectionItem<T>[] = []
|
||||
|
||||
await this._chunk(async items => {
|
||||
rejected = rejected.concat(items.all().filter((item, index) => {
|
||||
return !truth_test(item, index)
|
||||
}))
|
||||
})
|
||||
|
||||
return new Collection<T>(rejected)
|
||||
}
|
||||
|
||||
async reverse(): Promise<Collection<T>> {
|
||||
return (await this.collect()).reverse()
|
||||
}
|
||||
|
||||
async search(item: CollectionItem<T>): Promise<MaybeCollectionIndex> {
|
||||
let found_index
|
||||
let index = 0
|
||||
|
||||
await this._chunk(async items => {
|
||||
items.some(possible_item => {
|
||||
if ( possible_item === item ) {
|
||||
found_index = index
|
||||
throw new StopIteration()
|
||||
}
|
||||
|
||||
index += 1
|
||||
return false
|
||||
})
|
||||
})
|
||||
|
||||
return found_index
|
||||
}
|
||||
|
||||
async shift(): Promise<MaybeCollectionItem<T>> {
|
||||
const next_item = await this._items.next()
|
||||
if ( !next_item.done ) {
|
||||
return next_item.value
|
||||
}
|
||||
}
|
||||
|
||||
async shuffle(): Promise<Collection<T>> {
|
||||
return (await this.collect()).shuffle()
|
||||
}
|
||||
|
||||
async slice(start: number, end: number): Promise<Collection<T>> {
|
||||
return this._items.from_range(start, end - 1)
|
||||
}
|
||||
|
||||
async sort(compare_func?: ComparisonFunction<T>): Promise<Collection<T>> {
|
||||
return (await this.collect()).sort(compare_func)
|
||||
}
|
||||
|
||||
async sortBy<T2>(key?: KeyOperator<T, T2>): Promise<Collection<T>> {
|
||||
return (await this.collect()).sortBy(key)
|
||||
}
|
||||
|
||||
async sortDesc(compare_func?: ComparisonFunction<T>): Promise<Collection<T>> {
|
||||
return (await this.collect()).sortDesc(compare_func)
|
||||
}
|
||||
|
||||
async sortByDesc<T2>(key?: KeyOperator<T, T2>): Promise<Collection<T>> {
|
||||
return (await this.collect()).sortByDesc(key)
|
||||
}
|
||||
|
||||
async splice(start: CollectionIndex, deleteCount?: number): Promise<Collection<T>> {
|
||||
return (await this.collect()).splice(start, deleteCount)
|
||||
}
|
||||
|
||||
async sum<T2>(key?: KeyOperator<T, T2>): Promise<number> {
|
||||
let running_sum: number = 0
|
||||
|
||||
const chunk_handler = (items: number[]) => {
|
||||
for ( const item of items ) {
|
||||
running_sum += item
|
||||
}
|
||||
}
|
||||
|
||||
if ( key ) await this._chunk_all_numbers(key, chunk_handler)
|
||||
else await this._chunk(async chunk => {
|
||||
chunk_handler(chunk.map(x => Number(x)).all())
|
||||
})
|
||||
|
||||
return running_sum
|
||||
}
|
||||
|
||||
async take(limit: number): Promise<Collection<T>> {
|
||||
if ( limit === 0 ) return new Collection<T>()
|
||||
else if ( limit > 0 ) {
|
||||
return this.slice(0, limit)
|
||||
} else {
|
||||
const cnt = await this._items.count()
|
||||
return this._items.from_range(cnt - (-1 * limit), cnt - 1)
|
||||
}
|
||||
}
|
||||
|
||||
async tap<T2>(func: AsyncCollectionFunction<T, T2>): Promise<AsyncCollection<T>> {
|
||||
await func(this)
|
||||
return this
|
||||
}
|
||||
|
||||
async unique<T2>(key?: KeyOperator<T, T2>): Promise<Collection<T|T2>> {
|
||||
const has: CollectionItem<T|T2>[] = []
|
||||
|
||||
if ( !key ) {
|
||||
await this._chunk(async items => {
|
||||
for ( const item of items.all() ) {
|
||||
if ( !has.includes(item) ) has.push(item)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
await this._chunk_all(key, async items => {
|
||||
for ( const item of items.all() ) {
|
||||
if ( !has.includes(item) ) has.push(item)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return new Collection<T|T2>(has)
|
||||
}
|
||||
|
||||
async toArray(): Promise<any[]> {
|
||||
const returns: any = []
|
||||
for ( const item of (await this.all()) ) {
|
||||
if ( item instanceof Collection ) returns.push(item.toArray())
|
||||
else if ( item instanceof AsyncCollection ) returns.push(await item.toArray())
|
||||
else returns.push(item)
|
||||
}
|
||||
return returns
|
||||
}
|
||||
|
||||
async toJSON(replacer = undefined, space = 4): Promise<string> {
|
||||
return JSON.stringify(this.toArray(), replacer, space)
|
||||
}
|
||||
|
||||
[Symbol.asyncIterator]() {
|
||||
return this._items.clone()
|
||||
}
|
||||
|
||||
iterator() {
|
||||
return this._items.clone()
|
||||
}
|
||||
}
|
@ -108,7 +108,7 @@ class Collection<T> {
|
||||
|
||||
const middle = Math.floor((items.length - 1) / 2)
|
||||
if ( items.length % 2 ) return items[middle]
|
||||
else return (items[middle] + items[middle + 1]) / 2;
|
||||
else return (items[middle] + items[middle + 1]) / 2
|
||||
}
|
||||
|
||||
mode<T2>(key?: KeyOperator<T, T2>): number {
|
||||
@ -121,7 +121,7 @@ class Collection<T> {
|
||||
counts[item] = (counts[item] ?? -1) + 1
|
||||
}
|
||||
|
||||
return Math.max(...Object.values(counts).map(Number))
|
||||
return Number(Object.keys(counts).reduce((a, b) => counts[a] > counts[b] ? a : b)[0])
|
||||
}
|
||||
|
||||
collapse(): Collection<any> {
|
||||
@ -153,6 +153,10 @@ class Collection<T> {
|
||||
|
||||
// TODO crossJoin
|
||||
|
||||
clone(): Collection<T> {
|
||||
return new Collection<T>(this._items)
|
||||
}
|
||||
|
||||
diff<T2>(items: CollectionComparable<T|T2>): Collection<T> {
|
||||
const exclude = items instanceof Collection ? items.all() : items
|
||||
const matches = []
|
||||
@ -172,12 +176,20 @@ class Collection<T> {
|
||||
return new Collection(matches)
|
||||
}
|
||||
|
||||
each<T2>(func: KeyFunction<T, T2>): Collection<T2> {
|
||||
return new Collection(this._items.map(func))
|
||||
some(func: (item: T) => boolean): boolean {
|
||||
return this._items.some(func)
|
||||
}
|
||||
|
||||
each<T2>(func: KeyFunction<T, T2>): void {
|
||||
this._items.map(func)
|
||||
}
|
||||
|
||||
map<T2>(func: KeyFunction<T, T2>): Collection<T2> {
|
||||
return this.each(func)
|
||||
const new_items: CollectionItem<T2>[] = []
|
||||
this.each(((item, index) => {
|
||||
new_items.push(func(item, index))
|
||||
}))
|
||||
return new Collection<T2>(new_items)
|
||||
}
|
||||
|
||||
every<T2>(func: KeyFunction<T, T2>): boolean {
|
||||
@ -396,16 +408,6 @@ class Collection<T> {
|
||||
return this
|
||||
}
|
||||
|
||||
pull(index: number, fallback: any): MaybeCollectionItem<T> {
|
||||
let value
|
||||
const new_items = []
|
||||
this._items.forEach((item, item_index) => {
|
||||
if ( item_index !== index ) new_items.push(item)
|
||||
else value = item
|
||||
})
|
||||
return value ?? fallback
|
||||
}
|
||||
|
||||
put(index: number, item: CollectionItem<T>): Collection<T> {
|
||||
const new_items = []
|
||||
let inserted = false
|
||||
@ -582,7 +584,7 @@ class Collection<T> {
|
||||
return {
|
||||
current_index: 0,
|
||||
next() {
|
||||
if ( items.length < 1 || this.current_index + 1 >= items.length ) {
|
||||
if ( items.length < 1 || this.current_index + 1 > items.length ) {
|
||||
return { done: true }
|
||||
}
|
||||
|
||||
|
62
lib/src/collection/Iterable.ts
Normal file
62
lib/src/collection/Iterable.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import {Collection} from './Collection.ts'
|
||||
|
||||
export type MaybeIterationItem<T> = { done: boolean, value?: T }
|
||||
export type ChunkCallback<T> = (items: Collection<T>) => any
|
||||
|
||||
export class StopIteration extends Error {}
|
||||
|
||||
export abstract class Iterable<T> {
|
||||
protected index = 0
|
||||
|
||||
abstract async at_index(i: number): Promise<T>
|
||||
abstract async from_range(start: number, end: number): Promise<Collection<T>>
|
||||
abstract async count(): Promise<number>
|
||||
abstract clone(): Iterable<T>
|
||||
|
||||
public async next(): Promise<MaybeIterationItem<T>> {
|
||||
const i = this.index
|
||||
|
||||
if ( i >= await this.count() ) {
|
||||
return { done: true }
|
||||
}
|
||||
|
||||
this.index = i + 1
|
||||
return { done: false, value: await this.at_index(i) }
|
||||
}
|
||||
|
||||
[Symbol.asyncIterator]() {
|
||||
return this
|
||||
}
|
||||
|
||||
public async chunk(size: number, callback: ChunkCallback<T>) {
|
||||
const total = await this.count()
|
||||
|
||||
while ( this.index < total ) {
|
||||
const items = await this.from_range(this.index, this.index + size - 1)
|
||||
|
||||
try {
|
||||
await callback(items)
|
||||
} catch ( error ) {
|
||||
if ( error instanceof StopIteration ) break
|
||||
else throw error
|
||||
}
|
||||
|
||||
this.index += size
|
||||
}
|
||||
}
|
||||
|
||||
public async seek(index: number) {
|
||||
if ( index < 0 ) throw new TypeError('Cannot seek to negative index.')
|
||||
else if ( index >= await this.count() ) throw new TypeError('Cannot seek past last item.')
|
||||
this.index = index
|
||||
}
|
||||
|
||||
public async peek(): Promise<T | undefined> {
|
||||
if ( this.index + 1 >= await this.count() ) return undefined
|
||||
else return this.at_index(this.index + 1)
|
||||
}
|
||||
|
||||
public async reset() {
|
||||
this.index = 0
|
||||
}
|
||||
}
|
@ -1,15 +1,17 @@
|
||||
const STATUS_STOPPED = Symbol('status stopped')
|
||||
const STATUS_STARTING = Symbol('status starting')
|
||||
const STATUS_RUNNING = Symbol('status running')
|
||||
const STATUS_STOPPING = Symbol('status stopping')
|
||||
const STATUS_ERROR = Symbol('status error')
|
||||
enum Status {
|
||||
Stopped = 'stopped',
|
||||
Starting = 'starting',
|
||||
Running = 'running',
|
||||
Stopping = 'stopping',
|
||||
Error = 'error',
|
||||
}
|
||||
|
||||
const isStatus = (something: any) => [
|
||||
STATUS_STOPPED,
|
||||
STATUS_STARTING,
|
||||
STATUS_RUNNING,
|
||||
STATUS_STOPPING,
|
||||
STATUS_ERROR,
|
||||
Status.Stopped,
|
||||
Status.Starting,
|
||||
Status.Running,
|
||||
Status.Stopping,
|
||||
Status.Error,
|
||||
].includes(something)
|
||||
|
||||
export { STATUS_STOPPED, STATUS_STARTING, STATUS_RUNNING, STATUS_STOPPING, STATUS_ERROR, isStatus }
|
||||
export { Status, isStatus }
|
||||
|
39
lib/src/error/RunLevelErrorHandler.ts
Normal file
39
lib/src/error/RunLevelErrorHandler.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { red, bgRed } from '../external/std.ts'
|
||||
import { Service } from '../../../di/src/decorator/Service.ts'
|
||||
import { Logging } from '../service/logging/Logging.ts'
|
||||
|
||||
@Service()
|
||||
export default class RunLevelErrorHandler {
|
||||
constructor(
|
||||
protected logger: Logging,
|
||||
) {}
|
||||
|
||||
get handle(): (e: Error) => void {
|
||||
return (e: Error) => {
|
||||
this.display(e)
|
||||
Deno.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
display(e: Error) {
|
||||
try {
|
||||
const error_string = `RunLevelErrorHandler invoked:
|
||||
|
||||
${bgRed(' ')}
|
||||
${bgRed(' UNCAUGHT TOP-LEVEL ERROR ')}
|
||||
${bgRed(' ')}
|
||||
|
||||
${e.constructor ? e.constructor.name : e.name}
|
||||
${red(`---------------------------------------------------`)}
|
||||
${e.stack}
|
||||
`
|
||||
this.logger.error(error_string, true)
|
||||
} catch (display_e) {
|
||||
// The error display encountered an error...
|
||||
// just throw the original so it makes it out
|
||||
console.error('RunLevelErrorHandler encountered an error:', display_e.message)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
}
|
1
lib/src/external/db.ts
vendored
Normal file
1
lib/src/external/db.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
export * from 'https://deno.land/x/postgres/mod.ts'
|
2
lib/src/external/http.ts
vendored
Normal file
2
lib/src/external/http.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
export * from 'https://deno.land/std@0.53.0/http/server.ts'
|
||||
export * from 'https://deno.land/std@0.53.0/http/cookie.ts'
|
1
lib/src/external/postgres.ts
vendored
Normal file
1
lib/src/external/postgres.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
export * from 'https://deno.land/x/postgres/mod.ts'
|
5
lib/src/external/std.ts
vendored
5
lib/src/external/std.ts
vendored
@ -1,3 +1,4 @@
|
||||
export { serve } from 'https://deno.land/std/http/server.ts'
|
||||
export * from 'https://deno.land/std/fmt/colors.ts'
|
||||
export * from 'https://deno.land/std@0.53.0/fmt/colors.ts'
|
||||
export { config as dotenv } from 'https://deno.land/x/dotenv/mod.ts'
|
||||
export * as path from 'https://deno.land/std@0.53.0/path/mod.ts'
|
||||
export * as fs from 'https://deno.land/std@0.53.0/fs/mod.ts'
|
||||
|
4
lib/src/http/Controller.ts
Normal file
4
lib/src/http/Controller.ts
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
export default class Controller {
|
||||
|
||||
}
|
69
lib/src/http/CookieJar.ts
Normal file
69
lib/src/http/CookieJar.ts
Normal file
@ -0,0 +1,69 @@
|
||||
import { Injectable } from '../../../di/src/decorator/Injection.ts'
|
||||
import { getCookies, setCookie, delCookie, ServerRequest } from '../external/http.ts'
|
||||
import { InMemCache } from '../support/InMemCache.ts'
|
||||
import { HTTPRequest } from './type/HTTPRequest.ts'
|
||||
|
||||
export interface Cookie {
|
||||
key: string,
|
||||
original_value: string,
|
||||
value: any,
|
||||
}
|
||||
|
||||
export type MaybeCookie = Cookie | undefined
|
||||
|
||||
// TODO cookie options (http only, expires, &c.)
|
||||
@Injectable()
|
||||
export class CookieJar {
|
||||
protected _parsed: { [key: string]: string } = {}
|
||||
protected _cache = new InMemCache()
|
||||
|
||||
constructor(
|
||||
protected request: HTTPRequest,
|
||||
) {
|
||||
this._parsed = getCookies(this.request.to_native)
|
||||
}
|
||||
|
||||
public async get(key: string): Promise<MaybeCookie> {
|
||||
// Try the cache
|
||||
if ( await this._cache.has(key) )
|
||||
return this._cache.fetch(key)
|
||||
|
||||
// If the cache missed, try to parse it and load in cache
|
||||
if ( key in this._parsed ) {
|
||||
let value = this._parsed[key]
|
||||
try {
|
||||
value = JSON.parse(atob(this._parsed[key]))
|
||||
} catch(e) {}
|
||||
|
||||
const cookie = {
|
||||
key,
|
||||
value,
|
||||
original_value: this._parsed[key],
|
||||
}
|
||||
|
||||
await this._cache.put(key, cookie)
|
||||
return cookie
|
||||
}
|
||||
}
|
||||
|
||||
public async set(key: string, value: any): Promise<void> {
|
||||
const original_value = btoa(JSON.stringify(value))
|
||||
const cookie = {
|
||||
key,
|
||||
value,
|
||||
original_value,
|
||||
}
|
||||
|
||||
await this._cache.put(key, value)
|
||||
setCookie(this.request.response, { name: key, value: original_value })
|
||||
}
|
||||
|
||||
public async has(key: string): Promise<boolean> {
|
||||
return (await this._cache.has(key)) || key in this._parsed
|
||||
}
|
||||
|
||||
public async delete(key: string): Promise<void> {
|
||||
await this._cache.drop(key)
|
||||
delCookie(this.request.response, key)
|
||||
}
|
||||
}
|
3
lib/src/http/Middleware.ts
Normal file
3
lib/src/http/Middleware.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export class Middleware {
|
||||
|
||||
}
|
115
lib/src/http/Request.ts
Normal file
115
lib/src/http/Request.ts
Normal file
@ -0,0 +1,115 @@
|
||||
import { ServerRequest } from '../external/http.ts'
|
||||
import { HTTPProtocol, HTTPRequest, RemoteHost } from './type/HTTPRequest.ts'
|
||||
import { Response } from './Response.ts'
|
||||
import { HTTPResponse } from './type/HTTPResponse.ts'
|
||||
import Utility from '../service/utility/Utility.ts'
|
||||
import { Injectable } from '../../../di/src/decorator/Injection.ts'
|
||||
|
||||
@Injectable()
|
||||
export class Request implements HTTPRequest {
|
||||
public readonly response: HTTPResponse
|
||||
private readonly _deno_req: ServerRequest
|
||||
private _body: any
|
||||
private _query: { [key: string]: any } = {}
|
||||
|
||||
public readonly url: string
|
||||
public readonly method: string
|
||||
public readonly protocol: HTTPProtocol
|
||||
public readonly connection: Deno.Conn
|
||||
public readonly secure: boolean = false
|
||||
|
||||
public get headers() {
|
||||
return this._deno_req.headers
|
||||
}
|
||||
|
||||
get to_native(): ServerRequest {
|
||||
return this._deno_req
|
||||
}
|
||||
|
||||
get cookies() {
|
||||
return this.response.cookies
|
||||
}
|
||||
|
||||
constructor(
|
||||
protected utility: Utility,
|
||||
from: ServerRequest
|
||||
) {
|
||||
this._deno_req = from
|
||||
this.url = this._deno_req.url
|
||||
this.method = this._deno_req.method.toLowerCase()
|
||||
this.protocol = {
|
||||
string: this._deno_req.proto,
|
||||
major: this._deno_req.protoMajor,
|
||||
minor: this._deno_req.protoMinor,
|
||||
}
|
||||
this.connection = this._deno_req.conn
|
||||
this.response = new Response(this)
|
||||
}
|
||||
|
||||
public async prepare() {
|
||||
this._body = await Deno.readAll(this._deno_req.body)
|
||||
|
||||
const url_params = new URLSearchParams(this.url.substr(1))
|
||||
const param_obj = Object.fromEntries(url_params)
|
||||
const params: any = {}
|
||||
for ( const key in param_obj ) {
|
||||
if ( !param_obj.hasOwnProperty(key) ) continue
|
||||
if ( param_obj[key] === '' ) params[key] = true
|
||||
else params[key] = this.utility.infer(param_obj[key])
|
||||
}
|
||||
|
||||
this._query = params
|
||||
}
|
||||
|
||||
respond(res: any) {
|
||||
return this._deno_req.respond(res)
|
||||
}
|
||||
|
||||
// public body: RequestBody = {}
|
||||
// public original_body: RequestBody = {}
|
||||
|
||||
get remote() {
|
||||
return this.connection.remoteAddr as RemoteHost
|
||||
}
|
||||
|
||||
get body() {
|
||||
return this._body
|
||||
}
|
||||
|
||||
get query() {
|
||||
return this._query
|
||||
}
|
||||
|
||||
get hostname() {
|
||||
return this.headers.get('host')?.split(':')[0]
|
||||
}
|
||||
|
||||
get path() {
|
||||
return this.url.split('?')[0]
|
||||
}
|
||||
|
||||
get xhr() {
|
||||
return this.headers.get('x-requested-with')?.toLowerCase() === 'xmlhttprequest'
|
||||
}
|
||||
|
||||
/*
|
||||
body
|
||||
fresh/stale - cache
|
||||
remote ips (proxy)
|
||||
params
|
||||
route?
|
||||
signedCookies
|
||||
*/
|
||||
|
||||
/*
|
||||
accepts content type
|
||||
accepts charsets
|
||||
accepts encodings
|
||||
accepts languages
|
||||
get header
|
||||
is content type
|
||||
get param with default value
|
||||
get input with default value
|
||||
range header parser
|
||||
*/
|
||||
}
|
30
lib/src/http/Response.ts
Normal file
30
lib/src/http/Response.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { HTTPResponse } from './type/HTTPResponse.ts'
|
||||
import { HTTPRequest } from './type/HTTPRequest.ts'
|
||||
import { ServerRequest } from '../external/http.ts'
|
||||
import {CookieJar} from "./CookieJar.ts";
|
||||
|
||||
export class Response implements HTTPResponse {
|
||||
public status = 200
|
||||
public headers = new Headers()
|
||||
public body = ''
|
||||
public readonly cookies: CookieJar
|
||||
|
||||
private readonly _deno_req: ServerRequest
|
||||
private readonly _request: HTTPRequest
|
||||
|
||||
private _sent = false
|
||||
get sent() {
|
||||
return this._sent
|
||||
}
|
||||
|
||||
constructor(to: HTTPRequest) {
|
||||
this._deno_req = to.to_native
|
||||
this._request = to
|
||||
this.cookies = new CookieJar(to)
|
||||
}
|
||||
|
||||
send() {
|
||||
this._sent = true
|
||||
return this._deno_req.respond(this)
|
||||
}
|
||||
}
|
6
lib/src/http/SecureRequest.ts
Normal file
6
lib/src/http/SecureRequest.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { HTTPRequest } from './type/HTTPRequest.ts'
|
||||
import { Request } from './Request.ts'
|
||||
|
||||
export default class SecureRequest extends Request implements HTTPRequest {
|
||||
public readonly secure: boolean = true
|
||||
}
|
30
lib/src/http/type/HTTPRequest.ts
Normal file
30
lib/src/http/type/HTTPRequest.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { ServerRequest } from '../../external/http.ts'
|
||||
import {HTTPResponse} from "./HTTPResponse.ts";
|
||||
|
||||
export interface HTTPProtocol {
|
||||
string: string,
|
||||
major: number,
|
||||
minor: number,
|
||||
}
|
||||
|
||||
export interface RemoteHost {
|
||||
hostname: string,
|
||||
port: number,
|
||||
transport: string,
|
||||
}
|
||||
|
||||
export interface HTTPRequest {
|
||||
url: string
|
||||
method: string
|
||||
protocol: HTTPProtocol
|
||||
headers: Headers
|
||||
connection: Deno.Conn
|
||||
response: HTTPResponse
|
||||
to_native: ServerRequest
|
||||
|
||||
remote: RemoteHost
|
||||
body: any
|
||||
query: any
|
||||
hostname: string | undefined
|
||||
secure: boolean
|
||||
}
|
12
lib/src/http/type/HTTPResponse.ts
Normal file
12
lib/src/http/type/HTTPResponse.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import {CookieJar} from '../CookieJar.ts'
|
||||
|
||||
export interface HTTPResponse {
|
||||
status: number
|
||||
headers: Headers
|
||||
body: Uint8Array | Deno.Reader | string
|
||||
trailers?: () => Promise<Headers> | Headers
|
||||
sent: boolean
|
||||
cookies: CookieJar,
|
||||
|
||||
send: () => void
|
||||
}
|
43
lib/src/lifecycle/Application.ts
Normal file
43
lib/src/lifecycle/Application.ts
Normal file
@ -0,0 +1,43 @@
|
||||
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 RunLevelErrorHandler from '../error/RunLevelErrorHandler.ts'
|
||||
|
||||
@Service()
|
||||
export default class Application {
|
||||
constructor(
|
||||
protected logger: Logging,
|
||||
protected rleh: RunLevelErrorHandler,
|
||||
protected units: Unit[],
|
||||
) {}
|
||||
|
||||
make(token: DependencyKey) {
|
||||
return make(token)
|
||||
}
|
||||
|
||||
container() {
|
||||
return container
|
||||
}
|
||||
|
||||
async up() {
|
||||
|
||||
}
|
||||
|
||||
async down() {
|
||||
|
||||
}
|
||||
|
||||
async run() {
|
||||
try {
|
||||
this.logger.info('Starting Daton...')
|
||||
} catch (e) {
|
||||
await this.app_error(e)
|
||||
}
|
||||
}
|
||||
|
||||
async app_error(e: Error) {
|
||||
this.rleh.handle(e)
|
||||
}
|
||||
}
|
@ -1,19 +1,42 @@
|
||||
import { STATUS_STOPPED, isStatus } from '../const/status.ts'
|
||||
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";
|
||||
|
||||
const isLifecycleUnit = (something: any): something is (typeof LifecycleUnit) => {
|
||||
return isInstantiable(something) && something.prototype instanceof LifecycleUnit
|
||||
}
|
||||
|
||||
export default abstract class LifecycleUnit {
|
||||
private _status = STATUS_STOPPED
|
||||
private _status = Status.Stopped
|
||||
|
||||
public get status() {
|
||||
return this._status
|
||||
}
|
||||
|
||||
public set status(status) {
|
||||
if ( !isStatus(status) )
|
||||
throw new TypeError('Invalid unit status: '+status.description)
|
||||
|
||||
this._status = status
|
||||
}
|
||||
|
||||
public async up(): Promise<void> {};
|
||||
public async down(): Promise<void> {};
|
||||
|
||||
public static get_dependencies(): Collection<typeof LifecycleUnit> {
|
||||
if ( isInstantiable(this) ) {
|
||||
const deps = new Collection<typeof LifecycleUnit>()
|
||||
for ( const dependency of container.get_dependencies(this) ) {
|
||||
if ( isLifecycleUnit(dependency) )
|
||||
deps.push(dependency)
|
||||
}
|
||||
|
||||
return deps
|
||||
}
|
||||
< |