import * as Knex from 'knex' ; import { Migrator } from './Migrator' ; export class MigrationTable { type { Knex } private connection : Knex ; type { string } private tableName : string ; type { string } private lockTableName : string ; param { Knex } connection param { string } tableName param { string } lockTableName public constructor ( connection , tableName , lockTableName ) { this . connection = connection ; this . tableName = tableName ; this . lockTableName = lockTableName ; } returns { Promise < any > } public getLock ( ) { return this . ensureMigrationTables ( ) . then ( ( ) => { return this . connection . transaction ( transaction => { return this . isLocked ( transaction ) . then ( isLocked => { if ( isLocked ) { throw new Error ( 'Migration table is already locked' ) ; } } ) . then ( ( ) => this . lockMigrations ( transaction ) ) ; } ) ; } ) ; } returns { QueryBuilder } public freeLock ( ) { return Promise . resolve ( this . connection ( this . lockTableName ) . update ( { locked : 0 } ) ) ; } returns { Promise < number | null > } public getLastRunId ( ) { const lastRunId = this . connection ( this . tableName ) . select ( 'run' ) . limit ( 1 ) . orderBy ( 'run' , 'desc' ) . then ( result => result [ 0 ] ? result [ 0 ] . run : null ) ; return Promise . resolve ( lastRunId ) ; } returns { Promise < string | null > } public getLastMigrationName ( ) { return this . ensureMigrationTables ( ) . then ( ( ) => { return this . connection ( this . tableName ) . select ( 'name' ) . limit ( 1 ) . orderBy ( 'id' , 'desc' ) . then ( result => result [ 0 ] ? result [ 0 ] . name : null ) ; } ) ; } returns { Promise < Array < string > | null > } public getLastRun ( ) { return this . getLastRunId ( ) . then ( lastRun => { if ( lastRun === null ) { return null ; } const connection = this . connection ( this . tableName ) . select ( 'name' ) . where ( 'run' , lastRun ) . orderBy ( 'id' , 'desc' ) ; return connection . then ( results => results . map ( result => result . name ) ) ; } ) ; } returns { Promise < Array < string > | null > } public getAllRun ( ) { return this . ensureMigrationTables ( ) . then ( ( ) => { return this . connection ( this . tableName ) . orderBy ( 'id' , 'desc' ) ; } ) ; } param { string } direction param { string [ ] } migrations returns { Promise } public saveRun ( direction , migrations ) { if ( direction === Migrator . DIRECTION_DOWN ) { return Promise . resolve ( this . connection ( this . tableName ) . whereIn ( 'name' , migrations ) . del ( ) ) ; } return this . getLastRunId ( ) . then ( lastRun => { return this . connection ( this . tableName ) . insert ( migrations . map ( name => { return { name , run : ( lastRun + 1 ) } ; } ) ) ; } ) ; } param { Knex . Transaction } transaction returns { Promise < boolean > } private isLocked ( transaction ) { const isLocked = this . connection ( this . lockTableName ) . transacting ( transaction ) . forUpdate ( ) . select ( '*' ) . then ( data => ! ! data [ 0 ] && ! ! data [ 0 ] . locked ) ; return Promise . resolve ( isLocked ) ; } param { Knex . Transaction } transaction returns { QueryBuilder } private lockMigrations ( transaction ) { return Promise . resolve ( this . connection ( this . lockTableName ) . transacting ( transaction ) . update ( { locked : 1 } ) ) ; } returns { Promise < any > } private async ensureMigrationTables ( ) { const connection = this . connection ; const migrationTableExists = await connection . schema . hasTable ( this . tableName ) ; if ( migrationTableExists ) { return ; } await connection . schema . createTable ( this . tableName , t => { t . increments ( ) ; t . string ( 'name' ) ; t . integer ( 'run' ) ; t . timestamp ( 'migration_time' ) . defaultTo ( connection . fn . now ( ) ) ; t . index ( [ 'run' ] ) ; t . index ( [ 'migration_time' ] ) ; } ) ; const lockTableExists = await connection . schema . hasTable ( this . lockTableName ) ; if ( lockTableExists ) { return ; } await connection . schema . createTable ( this . lockTableName , t => t . boolean ( 'locked' ) ) ; return connection . schema ; } }