UNPKG

@iarayan/ch-orm

Version:

A Developer-First ClickHouse ORM with Powerful CLI Tools

294 lines 11 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MigrationRunner = void 0; const Schema_1 = require("./Schema"); const MigrationRecord_1 = require("./models/MigrationRecord"); /** * MigrationRunner class for managing migration execution * Handles applying and rolling back migrations */ class MigrationRunner { /** * Create a new MigrationRunner instance * @param connection - ClickHouse connection */ constructor(connection) { /** * Name of the migrations table */ this.migrationsTable = "migrations"; /** * Array of migrations to be applied or rolled back */ this.migrations = []; this.connection = connection; this.schema = new Schema_1.Schema(connection); // Set the connection for the model MigrationRecord_1.MigrationRecord.setConnection(connection); } /** * Set the name of the migrations table * @param tableName - Migrations table name * @returns MigrationRunner instance for chaining */ setMigrationsTable(tableName) { this.migrationsTable = tableName; return this; } /** * Add a migration to the runner * @param migration - Migration instance * @returns MigrationRunner instance for chaining */ add(migration) { this.migrations.push(migration); return this; } /** * Add multiple migrations to the runner * @param migrations - Array of migration instances * @returns MigrationRunner instance for chaining */ addMultiple(migrations) { this.migrations = [...this.migrations, ...migrations]; return this; } /** * Set the migrations for the runner * @param migrations - Array of migration instances * @returns MigrationRunner instance for chaining */ setMigrations(migrations) { this.migrations = migrations; return this; } /** * Ensure the migrations table exists * @returns Promise that resolves when the migrations table is created (if needed) */ async ensureMigrationsTable() { try { // First check if the table exists using the Schema class const tableExists = await this.schema.hasTable(this.migrationsTable); if (!tableExists) { // If it doesn't exist, create it await this.createMigrationsTable(); } } catch (error) { // If there's an error, try to create the table await this.createMigrationsTable(); } } /** * Get all migration records from the database * @returns Promise that resolves to array of migration records */ async getMigrationRecords() { await this.ensureMigrationsTable(); // Use the MigrationRecord model to get all records const records = await MigrationRecord_1.MigrationRecord.query() .orderBy("created_at", "ASC") .get(); return records ? records : []; } /** * Get the last batch number from the migration records * @returns Promise that resolves to the last batch number or 0 if no migrations have been run */ async getLastBatchNumber() { var _a; // Use the MigrationRecord model to get the max batch number const result = await MigrationRecord_1.MigrationRecord.query().max("batch"); if (!result || !result.length) { return 0; } const maxBatch = (_a = result[0]) === null || _a === void 0 ? void 0 : _a["max(batch)"]; return maxBatch || 0; } /** * Save a migration record to the database * @param migration - Migration instance * @param batch - Batch number * @param executionTime - Execution time in seconds * @returns Promise that resolves when the record is saved */ async saveMigrationRecord(migration, batch, executionTime) { // Create a new MigrationRecord instance await MigrationRecord_1.MigrationRecord.createAndSave({ name: migration.getName(), batch, execution_time: executionTime, created_at: new Date(), }); } /** * Delete a migration record from the database * @param migration - Migration instance * @returns Promise that resolves when the record is deleted */ async deleteMigrationRecord(migration) { // Use the MigrationRecord model to delete the record await MigrationRecord_1.MigrationRecord.query().where("name", migration.getName()).delete(); } /** * Run pending migrations * @returns Promise that resolves to number of migrations applied */ async run() { await this.ensureMigrationsTable(); // Get existing migration records const existingRecords = await this.getMigrationRecords(); const existingNames = existingRecords.map((record) => record.name); // Get pending migrations const pendingMigrations = this.migrations.filter((migration) => !existingNames.includes(migration.getName())); if (pendingMigrations.length === 0) { return 0; } // Get the next batch number const nextBatch = (await this.getLastBatchNumber()) + 1; // Apply each pending migration let appliedCount = 0; for (const migration of pendingMigrations) { const startTime = process.hrtime(); // Apply the migration await migration.apply(); // Calculate execution time const [seconds, nanoseconds] = process.hrtime(startTime); const executionTime = seconds + nanoseconds / 1e9; // Save the migration record await this.saveMigrationRecord(migration, nextBatch, executionTime); // Update the applied count appliedCount++; } return appliedCount; } /** * Rollback the last batch of migrations * @returns Promise that resolves to number of migrations rolled back */ async rollback() { await this.ensureMigrationsTable(); // Get the last batch number const lastBatch = await this.getLastBatchNumber(); if (lastBatch === 0) { return 0; } // Get records from the last batch using the model const recordsToRollback = await MigrationRecord_1.MigrationRecord.query() .where("batch", lastBatch) .orderBy("created_at", "DESC") .get(); if (recordsToRollback.length === 0) { return 0; } // Find the corresponding migration instances const migrationsToRollback = recordsToRollback .map((record) => this.migrations.find((m) => m.getName() === record.name)) .filter((m) => m !== undefined); // Set these migrations as applied migrationsToRollback.forEach((migration) => migration.setApplied(true)); // Rollback each migration let rolledBackCount = 0; for (const migration of migrationsToRollback) { // Rollback the migration await migration.revert(); // Delete the migration record await this.deleteMigrationRecord(migration); // Update the rolled back count rolledBackCount++; } return rolledBackCount; } /** * Reset the database by rolling back all migrations * @returns Promise that resolves to number of migrations rolled back */ async reset() { await this.ensureMigrationsTable(); // Get all migration records const recordsToRollback = await MigrationRecord_1.MigrationRecord.query() .orderBy("created_at", "DESC") .get(); if (!recordsToRollback || recordsToRollback.length === 0) { return 0; } // Find the corresponding migration instances const migrationsToRollback = recordsToRollback .map((record) => this.migrations.find((m) => m.getName() === record.name)) .filter((m) => m !== undefined); // Set these migrations as applied migrationsToRollback.forEach((migration) => migration.setApplied(true)); // Rollback each migration let rolledBackCount = 0; for (const migration of migrationsToRollback) { // Rollback the migration await migration.revert(); // Delete the migration record await this.deleteMigrationRecord(migration); // Update the rolled back count rolledBackCount++; } return rolledBackCount; } /** * Refresh the database by rolling back all migrations and then running them again * @returns Promise that resolves to number of migrations applied */ async refresh() { // Reset the database await this.reset(); // Run all migrations return this.run(); } /** * Get array of pending migrations * @returns Promise that resolves to array of pending migration names */ async getPendingMigrations() { const existingRecords = await this.getMigrationRecords(); const existingNames = existingRecords.map((record) => record.name); return this.migrations .filter((migration) => !existingNames.includes(migration.getName())) .map((migration) => migration.getName()); } /** * Get array of all migration names * @returns Array of all migration names */ getMigrationNames() { return this.migrations.map((migration) => migration.getName()); } /** * Get the underlying connection instance * @returns Connection instance */ getConnection() { return this.connection; } /** * Create the migrations table if it doesn't exist * This is used to track which migrations have been applied * @returns Promise that resolves when the table is created */ async createMigrationsTable() { // Check if the table exists first const tableExists = await this.schema.hasTable(this.migrationsTable); if (!tableExists) { // Create the migrations table await this.schema.create(this.migrationsTable, (table) => { // Define columns matching the MigrationRecord model table.string("name"); table.int32("batch"); table.float64("execution_time", { nullable: true }); table.dateTime("created_at"); // Set the engine to ReplacingMergeTree table.replacingMergeTree(); // In ClickHouse, the ORDER BY key is effectively the primary key table.orderBy("name"); }); } } } exports.MigrationRunner = MigrationRunner; //# sourceMappingURL=MigrationRunner.js.map