import {ITable, InsertableTable} from "../table"; import {SortDirection} from "../order"; import {IConnection} from "../execution"; import {IColumn} from "../column"; import {IJoinDeclaration} from "../join-declaration"; import {Row} from "../row"; import {TrackRow} from "../track-row"; import * as LogUtil from "./util"; /* Enables use of the audit log pattern/storing time-series data. We could be tracking a person's speed over time. In this case, the person would be the entity. And the speed would be what's tracked. ----- ### `table` The table we will be using to store time-series data. ### `entity` The table that "owns" the time-series data. ### `entityIdentifier` Uniquely identifies each entity. The entity's PK/CK. ### `latestOrder` The `entityIdentifier` + `latestOrder` must be a candidate key of the `table`. ### `tracked` Tracked columns indicate that these values change over time, and that we want to log them. Example, this lets us retrieve the latest speed of a person. ### `doNotCopy` Values we do not want to copy when adding a row to the log. When attempting to insert new data, values for these columns must always be provided. Consider the following table, |entityId|value|updatedAt|updatedBy| The `entityIdentifier` is `entityId` The `tracked` is `value` The `latestOrder` is `updatedAt` The `doNotCopy` is `updatedBy` If user X says, "Update X's value to 5", we add a row, |entityId|value|updatedAt|updatedBy| |X |5 |10:00AM |X | Then, when user Y says, "Update X's value to 5", we notice that all `tracked` column values have not changed. So, we do not add a row. Then, when user Z says, "Update X's value to 6", we add a row, |entityId|value|updatedAt|updatedBy| |X |6 |12:00PM |Z | So, we can change the value of `updatedBy`, but only if the value of whatever is being tracked changes. ### `copy` Columns not in `entityIdentifier`, `tracked`, `latestOrder`, and `doNotCopy` are implicitly copied over when new rows are added. ### `copyDefaultsDelegate` When invoked, returns default values of `copy` columns. Each entity may have different default values when no data is logged yet. ### `trackedDefaults` The default value of `tracked` columns. These values apply to all entities with no data logged yet. */ export interface LogData { readonly table : ITable; readonly entity : ITable|undefined; readonly joinDeclaration : IJoinDeclaration<{ //From `table` readonly fromTable : ITable, //To `entity` readonly toTable : ITable, readonly nullable : false, }>|undefined; //Must be a CK of `entity` readonly entityIdentifier : string[]|undefined; //(entityIdentifier, lastestOrder) must be a CK of `table` readonly latestOrder : [IColumn, SortDirection]|undefined; readonly tracked : string[]|undefined; readonly doNotCopy : string[]|undefined; readonly copy : string[]; readonly copyDefaultsDelegate : (( args : { entityIdentifier : any, connection : IConnection, } ) => Promise<{}>)|undefined; //A literal value; unchanging. readonly trackedDefaults : { readonly [columnName : string] : any; }|undefined; } export interface ILog { readonly table : DataT["table"]; readonly entity : DataT["entity"]; readonly entityIdentifier : DataT["entityIdentifier"]; readonly joinDeclaration : DataT["joinDeclaration"]; readonly latestOrder : DataT["latestOrder"]; readonly tracked : DataT["tracked"]; readonly doNotCopy : DataT["doNotCopy"]; readonly copy : DataT["copy"]; readonly copyDefaultsDelegate : DataT["copyDefaultsDelegate"]; readonly trackedDefaults : DataT["trackedDefaults"]; } export interface InsertableLog { readonly table : InsertableTable; readonly entity : ITable; readonly entityIdentifier : string[]; readonly joinDeclaration : IJoinDeclaration<{ //From `table` readonly fromTable : ITable, //To `entity` readonly toTable : ITable, readonly nullable : false, }>; readonly latestOrder : [IColumn, SortDirection]; readonly tracked : string[]; readonly doNotCopy : string[]; readonly copy : string[]; readonly copyDefaultsDelegate : ( args : { entityIdentifier : any, connection : IConnection, } ) => Promise<{}>; readonly trackedDefaults : { readonly [columnName : string] : any; }|undefined; } export interface LogNoTrackedDefaults { readonly table : ITable; readonly entity : ITable; readonly entityIdentifier : string[]; readonly joinDeclaration : IJoinDeclaration<{ //From `table` readonly fromTable : ITable, //To `entity` readonly toTable : ITable, readonly nullable : false, }>; readonly latestOrder : [IColumn, SortDirection]; readonly tracked : string[]; readonly doNotCopy : string[]; readonly copy : string[]; readonly copyDefaultsDelegate : ( args : { entityIdentifier : any, connection : IConnection, } ) => Promise<{}>; readonly trackedDefaults : { readonly [columnName : string] : any; }|undefined; } export interface CompletedLog { readonly table : ITable; readonly entity : ITable; readonly entityIdentifier : string[]; readonly joinDeclaration : IJoinDeclaration<{ //From `table` readonly fromTable : ITable, //To `entity` readonly toTable : ITable, readonly nullable : false, }>; readonly latestOrder : [IColumn, SortDirection]; readonly tracked : string[]; readonly doNotCopy : string[]; readonly copy : string[]; readonly copyDefaultsDelegate : ( args : { entityIdentifier : any, connection : IConnection, } ) => Promise<{}>; readonly trackedDefaults : { readonly [columnName : string] : any; }; } export type EntityIdentifier = ( { readonly [columnName in LogT["entityIdentifier"][number]] : ( ReturnType ) } ); export type PreviousRow = ( { readonly [columnName in ( LogT["entityIdentifier"][number] | LogT["tracked"][number] | LogT["copy"][number] )] : ReturnType } ) export class Log implements ILog { readonly table : DataT["table"]; readonly entity : DataT["entity"]; readonly entityIdentifier : DataT["entityIdentifier"]; readonly joinDeclaration : DataT["joinDeclaration"]; readonly latestOrder : DataT["latestOrder"]; readonly tracked : DataT["tracked"]; readonly doNotCopy : DataT["doNotCopy"]; readonly copy : DataT["copy"]; readonly copyDefaultsDelegate : DataT["copyDefaultsDelegate"]; readonly trackedDefaults : DataT["trackedDefaults"]; constructor (data : DataT) { this.table = data.table; this.entity = data.entity; this.entityIdentifier = data.entityIdentifier; this.joinDeclaration = data.joinDeclaration; this.latestOrder = data.latestOrder; this.tracked = data.tracked; this.doNotCopy = data.doNotCopy; this.copy = data.copy; this.copyDefaultsDelegate = data.copyDefaultsDelegate; this.trackedDefaults = data.trackedDefaults; } setEntity< EntityT extends ITable > ( this : Extract, entity : LogUtil.AssertValidEntity< Extract, EntityT > ) : ( LogUtil.SetEntity< Extract, EntityT > ) { return LogUtil.setEntity< Extract, EntityT >(this, entity); } setEntityIdentifier< DelegateT extends LogUtil.SetEntityIdentifierDelegate< Extract > > ( this : Extract, delegate : DelegateT ) : ( LogUtil.AssertValidSetEntityIdentifierDelegate_Hack< Extract, DelegateT, LogUtil.SetEntityIdentifier< Extract, DelegateT > > ) { return LogUtil.setEntityIdentifier(this, delegate); } setLatestOrder< DelegateT extends LogUtil.SetLatestOrderDelegate< Extract > > ( this : Extract, delegate : DelegateT ) : ( LogUtil.AssertValidSetLatestOrderDelegate_Hack< Extract, DelegateT, LogUtil.SetLatestOrder< Extract, DelegateT > > ) { return LogUtil.setLatestOrder( this, delegate ); } setTracked< DelegateT extends LogUtil.SetTrackedDelegate< Extract > > ( this : Extract, delegate : DelegateT ) : ( LogUtil.SetTracked< Extract, DelegateT > ) { return LogUtil.setTracked(this, delegate); } setDoNotCopy< DelegateT extends LogUtil.SetDoNotCopyDelegate< Extract > > ( this : Extract, delegate : DelegateT ) : ( LogUtil.SetDoNotCopy< Extract, DelegateT > ) { return LogUtil.setDoNotCopy(this, delegate); } setCopyDefaultsDelegate ( this : Extract, dynamicDefaultValueDelegate : LogUtil.CopyDefaultsDelegate< Extract > ) : ( LogUtil.SetCopyDefaultsDelegate< Extract > ) { return LogUtil.setCopyDefaultsDelegate(this, dynamicDefaultValueDelegate); } setTrackedDefaults< MapT extends LogUtil.TrackedDefaultsMap< Extract > > ( this : Extract, rawMap : MapT ) : ( LogUtil.SetTrackedDefaults< Extract, MapT > ) { return LogUtil.setTrackedDefaults(this, rawMap); } exists ( this : Extract, connection : IConnection, entityIdentifier : EntityIdentifier> ) : Promise { return LogUtil.exists(this, connection, entityIdentifier); } fetchDefault ( this : Extract, connection : IConnection, entityIdentifier : EntityIdentifier> ) : Promise>> { return LogUtil.fetchDefault(this, connection, entityIdentifier); } fetchLatestOrDefault ( this : Extract, connection : IConnection, entityIdentifier : EntityIdentifier> ) : Promise>> { return LogUtil.fetchLatestOrDefault(this, connection, entityIdentifier); } fetchLatestOrError ( this : Extract, connection : IConnection, entityIdentifier : EntityIdentifier> ) : Promise["table"]>> { return LogUtil.fetchLatestOrError(this, connection, entityIdentifier); } fetchLatestOrUndefined ( this : Extract, connection : IConnection, entityIdentifier : EntityIdentifier> ) : Promise["table"]>|undefined> { return LogUtil.fetchLatestOrUndefined(this, connection, entityIdentifier); } fetchLatestOrderOrError ( this : Extract, connection : IConnection, entityIdentifier : EntityIdentifier> ) : Promise< ReturnType< Extract["latestOrder"][0]["assertDelegate"] > > { return LogUtil.fetchLatestOrderOrError(this, connection, entityIdentifier); } fetchLatestOrderOrUndefined ( this : Extract, connection : IConnection, entityIdentifier : EntityIdentifier> ) : Promise< ReturnType< Extract["latestOrder"][0]["assertDelegate"] >| undefined > { return LogUtil.fetchLatestOrderOrUndefined(this, connection, entityIdentifier); } fetchLatestValueOrDefault< DelegateT extends LogUtil.LatestValueDelegate> > ( this : Extract, connection : IConnection, entityIdentifier : EntityIdentifier>, delegate : DelegateT ) : Promise< ReturnType< Extract["table"]["columns"][ ReturnType["name"] ]["assertDelegate"] > > { return LogUtil.fetchLatestValueOrDefault( this, connection, entityIdentifier, delegate ); } fetchLatestValueOrError< DelegateT extends LogUtil.LatestValueDelegate> > ( this : Extract, connection : IConnection, entityIdentifier : EntityIdentifier>, delegate : DelegateT ) : Promise< ReturnType< Extract["table"]["columns"][ ReturnType["name"] ]["assertDelegate"] > > { return LogUtil.fetchLatestValueOrError( this, connection, entityIdentifier, delegate ); } fetchLatestValueOrUndefined< DelegateT extends LogUtil.LatestValueDelegate> > ( this : Extract, connection : IConnection, entityIdentifier : EntityIdentifier>, delegate : DelegateT ) : Promise< ReturnType< Extract["table"]["columns"][ ReturnType["name"] ]["assertDelegate"] >| undefined > { return LogUtil.fetchLatestValueOrUndefined( this, connection, entityIdentifier, delegate ); } existsOfEntity ( this : Extract ) : ( LogUtil.ExistsOfEntity> ) { return LogUtil.existsOfEntity(this); } latestOfEntity ( this : Extract ) : ( LogUtil.LatestOfEntity> ) { return LogUtil.latestOfEntity(this); } latestOrderOfEntityOrNull ( this : Extract ) : ( LogUtil.LatestOrderOfEntityOrNull> ) { return LogUtil.latestOrderOfEntityOrNull(this); } latestValueOfEntityOrNull< DelegateT extends LogUtil.LatestValueOrNullDelegate< Extract > > ( this : Extract, delegate : DelegateT ) : ( LogUtil.LatestValueOfEntityOrNull< Extract, DelegateT > ) { return LogUtil.latestValueOfEntityOrNull(this, delegate); } latestValueOfEntity< DelegateT extends LogUtil.LatestValueDelegate< Extract > > ( this : Extract, delegate : DelegateT ) : ( LogUtil.LatestValueOfEntity< Extract, DelegateT > ) { return LogUtil.latestValueOfEntity(this, delegate); } trackOrError ( this : Extract, connection : IConnection, entityIdentifier : EntityIdentifier< Extract >, trackRow : TrackRow< Extract > ) : ( Promise< LogUtil.TrackResult< Extract > > ) { return LogUtil.trackOrError( this, connection, entityIdentifier, trackRow ); } track ( this : Extract, connection : IConnection, entityIdentifier : EntityIdentifier< Extract >, trackRow : TrackRow< Extract > ) : ( Promise< LogUtil.TrackResult< Extract > > ) { return LogUtil.track( this, connection, entityIdentifier, trackRow ); } latest ( this : Extract, entityIdentifier : EntityIdentifier> ) : ( LogUtil.Latest> ) { return LogUtil.latest(this, entityIdentifier); } }