import { Settings } from 'graphile-migrate'; import { getFileMigrationRecords, getMigrationHistoryRecords, getMigrationsPath, } from './compare-migration-hashes-helpers'; import { CompareMigrationHashesErrorCallback, MigrationRecord } from './types'; /** * @param message Default Error handler for migration history comparison. * @param mismatchedRecords */ const defaultErrorCallback: CompareMigrationHashesErrorCallback = ( message: string, _mismatchedRecords?: MigrationRecord[], ): void => { throw new Error(message); }; /** * Compares hashes from migration files with migration records from the database. * Execution order: * 1. Load all migration files from the `committed` folder. * 2. Extract migration number, hash, and previous hash from each migration file. * 3. Load all migration records from `graphile_migrate.migrations` table. * 4. Compare file records with records from the database: * - Checks if migration history has more records than committed migration files. * - Checks for mismatches in hashes. A mismatch is considered a file record * where the migration file number exists in the database, but the * hash(or previous hash) recorded in the database doesn't match hash(or * previous hash) in file migration. */ export const compareMigrationHashes = async ( settings: Settings, errorCallback?: CompareMigrationHashesErrorCallback, ): Promise => { errorCallback = errorCallback ?? defaultErrorCallback; //retrieve 'committed' migrations const fileMigrationRecords = await getFileMigrationRecords( getMigrationsPath(settings), errorCallback, ); //retrieve all migrations from `database` const dbMigrationRecords = await getMigrationHistoryRecords( settings.connectionString, errorCallback, ); // database has more records, than files committed // also covers case, if db has records, but committed folder is empty if (dbMigrationRecords.length > fileMigrationRecords.length) { const mismatchRecords = dbMigrationRecords.filter((dbRecord) => { return !fileMigrationRecords.some((fileRecord) => { return dbRecord.filename === fileRecord.filename; }); }); errorCallback( `Migration history contains more records than committed migration files.`, mismatchRecords, ); return; } //compare migration file hash records with migration hash records from database const mismatchRecords = fileMigrationRecords.filter((fileRecord) => { return dbMigrationRecords.some((migrationRecord) => { return ( fileRecord.filename === migrationRecord.filename && (fileRecord.hash !== migrationRecord.hash || fileRecord.previousHash !== migrationRecord.previousHash) ); }); }); if (mismatchRecords.length) { errorCallback( `Found hash mismatches between committed migration files and migration history records.`, mismatchRecords, ); return; } };