|
|
@ -1,18 +1,26 @@
|
|
|
|
import {ModelKey, QueryRow, QuerySource} from '../types'
|
|
|
|
import {ModelKey, QueryRow, QuerySource} from '../types'
|
|
|
|
import {Container, Inject} from '../../di'
|
|
|
|
import {Container, Inject, StaticClass} from '../../di'
|
|
|
|
import {DatabaseService} from '../DatabaseService'
|
|
|
|
import {DatabaseService} from '../DatabaseService'
|
|
|
|
import {ModelBuilder} from './ModelBuilder'
|
|
|
|
import {ModelBuilder} from './ModelBuilder'
|
|
|
|
import {getFieldsMeta, ModelField} from './Field'
|
|
|
|
import {getFieldsMeta, ModelField} from './Field'
|
|
|
|
import {deepCopy, BehaviorSubject, Pipe, Collection} from '../../util'
|
|
|
|
import {deepCopy, Pipe, Collection, Awaitable, uuid4} from '../../util'
|
|
|
|
import {EscapeValueObject} from '../dialect/SQLDialect'
|
|
|
|
import {EscapeValueObject} from '../dialect/SQLDialect'
|
|
|
|
import {AppClass} from '../../lifecycle/AppClass'
|
|
|
|
import {AppClass} from '../../lifecycle/AppClass'
|
|
|
|
import {Logging} from '../../service/Logging'
|
|
|
|
import {Logging} from '../../service/Logging'
|
|
|
|
import {Connection} from '../connection/Connection'
|
|
|
|
import {Connection} from '../connection/Connection'
|
|
|
|
|
|
|
|
import {Bus, Dispatchable, EventSubscriber, EventSubscriberEntry, EventSubscription} from '../../event/types'
|
|
|
|
|
|
|
|
import {ModelRetrievedEvent} from './events/ModelRetrievedEvent'
|
|
|
|
|
|
|
|
import {ModelSavingEvent} from './events/ModelSavingEvent'
|
|
|
|
|
|
|
|
import {ModelSavedEvent} from './events/ModelSavedEvent'
|
|
|
|
|
|
|
|
import {ModelUpdatingEvent} from './events/ModelUpdatingEvent'
|
|
|
|
|
|
|
|
import {ModelUpdatedEvent} from './events/ModelUpdatedEvent'
|
|
|
|
|
|
|
|
import {ModelCreatingEvent} from './events/ModelCreatingEvent'
|
|
|
|
|
|
|
|
import {ModelCreatedEvent} from './events/ModelCreatedEvent'
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Base for classes that are mapped to tables in a database.
|
|
|
|
* Base for classes that are mapped to tables in a database.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
export abstract class Model<T extends Model<T>> extends AppClass {
|
|
|
|
export abstract class Model<T extends Model<T>> extends AppClass implements Bus {
|
|
|
|
@Inject()
|
|
|
|
@Inject()
|
|
|
|
protected readonly logging!: Logging;
|
|
|
|
protected readonly logging!: Logging;
|
|
|
|
|
|
|
|
|
|
|
@ -78,49 +86,10 @@ export abstract class Model<T extends Model<T>> extends AppClass {
|
|
|
|
protected originalSourceRow?: QueryRow
|
|
|
|
protected originalSourceRow?: QueryRow
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Behavior subject that fires after the model is populated.
|
|
|
|
* Collection of event subscribers, by their events.
|
|
|
|
*/
|
|
|
|
* @protected
|
|
|
|
protected retrieved$ = new BehaviorSubject<Model<T>>()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Behavior subject that fires right before the model is saved.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
protected saving$ = new BehaviorSubject<Model<T>>()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Behavior subject that fires right after the model is saved.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
protected saved$ = new BehaviorSubject<Model<T>>()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Behavior subject that fires right before the model is updated.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
protected updating$ = new BehaviorSubject<Model<T>>()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Behavior subject that fires right after the model is updated.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
protected updated$ = new BehaviorSubject<Model<T>>()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Behavior subject that fires right before the model is inserted.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
protected creating$ = new BehaviorSubject<Model<T>>()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Behavior subject that fires right after the model is inserted.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
protected created$ = new BehaviorSubject<Model<T>>()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Behavior subject that fires right before the model is deleted.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
protected deleting$ = new BehaviorSubject<Model<T>>()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Behavior subject that fires right after the model is deleted.
|
|
|
|
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
protected deleted$ = new BehaviorSubject<Model<T>>()
|
|
|
|
protected modelEventBusSubscribers: Collection<EventSubscriberEntry<any>> = new Collection<EventSubscriberEntry<any>>()
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Get the table name for this model.
|
|
|
|
* Get the table name for this model.
|
|
|
@ -193,9 +162,16 @@ export abstract class Model<T extends Model<T>> extends AppClass {
|
|
|
|
values?: {[key: string]: any},
|
|
|
|
values?: {[key: string]: any},
|
|
|
|
) {
|
|
|
|
) {
|
|
|
|
super()
|
|
|
|
super()
|
|
|
|
|
|
|
|
this.initialize()
|
|
|
|
this.boot(values)
|
|
|
|
this.boot(values)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Called when the model is instantiated. Use for any setup of events, &c.
|
|
|
|
|
|
|
|
* @protected
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
protected initialize(): void {} // eslint-disable-line @typescript-eslint/no-empty-function
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Initialize the model's properties from the given values and do any other initial setup.
|
|
|
|
* Initialize the model's properties from the given values and do any other initial setup.
|
|
|
|
*
|
|
|
|
*
|
|
|
@ -228,7 +204,7 @@ export abstract class Model<T extends Model<T>> extends AppClass {
|
|
|
|
this.setFieldFromObject(field.modelKey, field.databaseKey, row)
|
|
|
|
this.setFieldFromObject(field.modelKey, field.databaseKey, row)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
await this.retrieved$.next(this)
|
|
|
|
await this.dispatch(new ModelRetrievedEvent<T>(this as any))
|
|
|
|
return this
|
|
|
|
return this
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -592,11 +568,11 @@ export abstract class Model<T extends Model<T>> extends AppClass {
|
|
|
|
* @param withoutTimestamps
|
|
|
|
* @param withoutTimestamps
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
public async save({ withoutTimestamps = false } = {}): Promise<Model<T>> {
|
|
|
|
public async save({ withoutTimestamps = false } = {}): Promise<Model<T>> {
|
|
|
|
await this.saving$.next(this)
|
|
|
|
await this.dispatch(new ModelSavingEvent<T>(this as any))
|
|
|
|
const ctor = this.constructor as typeof Model
|
|
|
|
const ctor = this.constructor as typeof Model
|
|
|
|
|
|
|
|
|
|
|
|
if ( this.exists() && this.isDirty() ) {
|
|
|
|
if ( this.exists() && this.isDirty() ) {
|
|
|
|
await this.updating$.next(this)
|
|
|
|
await this.dispatch(new ModelUpdatingEvent<T>(this as any))
|
|
|
|
|
|
|
|
|
|
|
|
if ( !withoutTimestamps && ctor.timestamps && ctor.UPDATED_AT ) {
|
|
|
|
if ( !withoutTimestamps && ctor.timestamps && ctor.UPDATED_AT ) {
|
|
|
|
(this as any)[ctor.UPDATED_AT] = new Date()
|
|
|
|
(this as any)[ctor.UPDATED_AT] = new Date()
|
|
|
@ -617,9 +593,9 @@ export abstract class Model<T extends Model<T>> extends AppClass {
|
|
|
|
await this.assumeFromSource(data)
|
|
|
|
await this.assumeFromSource(data)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await this.updated$.next(this)
|
|
|
|
await this.dispatch(new ModelUpdatedEvent<T>(this as any))
|
|
|
|
} else if ( !this.exists() ) {
|
|
|
|
} else if ( !this.exists() ) {
|
|
|
|
await this.creating$.next(this)
|
|
|
|
await this.dispatch(new ModelCreatingEvent<T>(this as any))
|
|
|
|
|
|
|
|
|
|
|
|
if ( !withoutTimestamps ) {
|
|
|
|
if ( !withoutTimestamps ) {
|
|
|
|
if ( ctor.timestamps && ctor.CREATED_AT ) {
|
|
|
|
if ( ctor.timestamps && ctor.CREATED_AT ) {
|
|
|
@ -647,10 +623,11 @@ export abstract class Model<T extends Model<T>> extends AppClass {
|
|
|
|
if ( data ) {
|
|
|
|
if ( data ) {
|
|
|
|
await this.assumeFromSource(result)
|
|
|
|
await this.assumeFromSource(result)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
await this.created$.next(this)
|
|
|
|
|
|
|
|
|
|
|
|
await this.dispatch(new ModelCreatedEvent<T>(this as any))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await this.saved$.next(this)
|
|
|
|
await this.dispatch(new ModelSavedEvent<T>(this as any))
|
|
|
|
return this
|
|
|
|
return this
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -822,4 +799,42 @@ export abstract class Model<T extends Model<T>> extends AppClass {
|
|
|
|
protected setFieldFromObject(thisFieldName: string | symbol, objectFieldName: string, object: QueryRow): void {
|
|
|
|
protected setFieldFromObject(thisFieldName: string | symbol, objectFieldName: string, object: QueryRow): void {
|
|
|
|
(this as any)[thisFieldName] = object[objectFieldName]
|
|
|
|
(this as any)[thisFieldName] = object[objectFieldName]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
subscribe<EventT extends Dispatchable>(event: StaticClass<EventT, EventT>, subscriber: EventSubscriber<EventT>): Awaitable<EventSubscription> {
|
|
|
|
|
|
|
|
const entry: EventSubscriberEntry<EventT> = {
|
|
|
|
|
|
|
|
id: uuid4(),
|
|
|
|
|
|
|
|
event,
|
|
|
|
|
|
|
|
subscriber,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.modelEventBusSubscribers.push(entry)
|
|
|
|
|
|
|
|
return this.buildSubscription(entry.id)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
unsubscribe<EventT extends Dispatchable>(subscriber: EventSubscriber<EventT>): Awaitable<void> {
|
|
|
|
|
|
|
|
this.modelEventBusSubscribers = this.modelEventBusSubscribers.where('subscriber', '!=', subscriber)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async dispatch(event: Dispatchable): Promise<void> {
|
|
|
|
|
|
|
|
const eventClass: StaticClass<typeof event, typeof event> = event.constructor as StaticClass<Dispatchable, Dispatchable>
|
|
|
|
|
|
|
|
await this.modelEventBusSubscribers.where('event', '=', eventClass)
|
|
|
|
|
|
|
|
.promiseMap(entry => entry.subscriber(event))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Build an EventSubscription object for the subscriber of the given ID.
|
|
|
|
|
|
|
|
* @param id
|
|
|
|
|
|
|
|
* @protected
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
protected buildSubscription(id: string): EventSubscription {
|
|
|
|
|
|
|
|
let subscribed = true
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
unsubscribe: (): Awaitable<void> => {
|
|
|
|
|
|
|
|
if ( subscribed ) {
|
|
|
|
|
|
|
|
this.modelEventBusSubscribers = this.modelEventBusSubscribers.where('id', '!=', id)
|
|
|
|
|
|
|
|
subscribed = false
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|