// @ts-nocheck import { Knex } from 'knex'; import Bluebird from 'bluebird'; import { getTable, getTableName, getLockTableName } from './TableUtils'; import getMergedConfig from './SeederConfig'; import { listAllAndCompleted, getNewMigrations, listCompleted, ensureMigrationTables, } from './MigrateUtils'; import { MigrateItem, SeedMigrationContext, ISeederConfig } from './interfaces'; import { FsMigrations } from './FsMigrations'; export class SeedMigration { knex: Knex; config: ISeederConfig; migrationSource: FsMigrations; context: SeedMigrationContext; /** * Constructor method. * @param {Knex} knex - Knex instance. * @param {SeedMigrationContext} context - */ constructor(knex: Knex, context: SeedMigrationContext) { this.knex = knex; this.config = getMergedConfig(this.knex.client.config.seeds, undefined); this.migrationSource = this.config.migrationSource; this.context = context; } /** * Latest migration. * @returns {Promise} */ async latest(config = null): Promise { // Merges the configuration. this.config = getMergedConfig(config, this.config); // Ensure migration tables. await ensureMigrationTables(this.config.tableName, null, this.knex); // Retrieve all and completed migrations. const [all, completed] = await listAllAndCompleted(this.config, this.knex); // Retrieve the new migrations. const migrations = getNewMigrations(this.migrationSource, all, completed); // Run the latest migration on one batch. return this.knex.transaction((trx: Knex.Transaction) => { return this.runBatch(migrations, 'up', trx); }); } /** * Add migration lock flag. * @param {Knex.Transaction} trx * @returns */ private migrateLockTable(trx: Knex.Transaction) { const tableName = getLockTableName(this.config.tableName); return getTable(this.knex, tableName, this.config.schemaName) .transacting(trx) .where('is_locked', '=', 0) .update({ is_locked: 1 }) .then((rowCount) => { if (rowCount != 1) { throw new Error('Migration table is already locked'); } }); } /** * Add migration lock flag. * @param {Knex.Transaction} trx * @returns */ private migrationLock(trx: Knex.Transaction) { return this.migrateLockTable(trx); } /** * Free the migration lock flag. * @param {Knex.Transaction} trx * @returns */ private freeLock(trx = this.knex): Promise { const tableName = getLockTableName(this.config.tableName); return getTable(trx, tableName, this.config.schemaName).update({ is_locked: 0, }); } /** * Returns the latest batch number. * @param trx * @returns */ private latestBatchNumber(trx = this.knex): number { return trx .from(getTableName(this.config.tableName, this.config.schemaName)) .max('batch as max_batch') .then((obj) => obj[0].max_batch || 0); } /** * Runs a batch of `migrations` in a specified `direction`, saving the * appropriate database information as the migrations are run. * @param {number} batchNo * @param {MigrateItem[]} migrations * @param {string} direction * @param {Knex.Transaction} trx * @returns {Promise} */ private waterfallBatch( batchNo: number, migrations: MigrateItem[], direction: string, trx: Knex.Transaction, ): Promise { const { tableName } = this.config; return Bluebird.each(migrations, (migration) => { const name = this.migrationSource.getMigrationName(migration); return this.migrationSource .getMigration(migration) .then((migrationContent) => this.runMigrationContent(migrationContent.default, direction, trx), ) .then(() => { if (direction === 'up') { return trx.into(getTableName(tableName)).insert({ name, batch: batchNo, migration_time: new Date(), }); } if (direction === 'down') { return trx.from(getTableName(tableName)).where({ name }).del(); } }); }); } /** * Runs and builds the given migration class. */ private runMigrationContent(Migration, direction, trx) { const instance = new Migration(trx); if (this.context.i18n) { instance.setI18n(this.context.i18n); } instance.setTenant(this.context.tenant); return instance[direction](trx); } /** * Validates some migrations by requiring and checking for an `up` and `down`function. * @param {MigrateItem} migration * @returns {MigrateItem} */ async validateMigrationStructure(migration: MigrateItem): MigrateItem { const migrationName = this.migrationSource.getMigrationName(migration); // maybe promise const migrationContent = await this.migrationSource.getMigration(migration); if ( typeof migrationContent.up !== 'function' || typeof migrationContent.down !== 'function' ) { throw new Error( `Invalid migration: ${migrationName} must have both an up and down function`, ); } return migration; } /** * Run a batch of current migrations, in sequence. * @param {MigrateItem[]} migrations * @param {string} direction * @param {Knex.Transaction} trx * @returns {Promise} */ private async runBatch( migrations: MigrateItem[], direction: string, trx: Knex.Transaction, ): Promise { // Adds flag to migration lock. await this.migrationLock(trx); // When there is a wrapping transaction, some migrations // could have been done while waiting for the lock: const completed = await listCompleted( this.config.tableName, this.config.schemaName, trx, ); // Differentiate between all and completed to get new migrations. const newMigrations = getNewMigrations( this.config.migrationSource, migrations, completed, ); // Retrieve the latest batch number. const batchNo = await this.latestBatchNumber(trx); // Increment the next batch number. const newBatchNo = direction === 'up' ? batchNo + 1 : batchNo; // Run all migration files in waterfall. await this.waterfallBatch(newBatchNo, newMigrations, direction, trx); // Free the migration lock flag. await this.freeLock(trx); } }