run method

Future<void> run()

Runs all pending migrations that haven't been applied yet.

Each migration is executed within its own transaction. If a migration fails, the transaction is rolled back and no further migrations are run.

Implementation

Future<void> run() async {
  await _ensureMigrationsTable();

  // Acquire advisory lock to prevent concurrent migration runs
  final locked = await _acquireLock();
  if (!locked) {
    Logger.staticWarning('⚠️ Another migration process is running. Skipping.');
    return;
  }

  try {
    final applied = await _getAppliedMigrations();
    final pending = _registry.where((e) => !applied.contains(e.name)).toList();

    if (pending.isEmpty) {
      Logger.staticInfo('✅ Nothing to migrate.');
      return;
    }

    // Calculate the next batch number ONCE for the entire run
    final batch = await _getNextBatchNumber();
    var successCount = 0;

    for (final entry in pending) {
      Logger.staticInfo('🚀 Migrating: ${entry.name}');

      try {
        await _db.transaction((tx) async {
          await entry.migration.up(tx);
          await tx.query(
            'INSERT INTO migrations (name, batch) VALUES (@name, @batch)',
            {'name': entry.name, 'batch': batch},
          );
        });
        successCount++;
      } catch (e) {
        Logger.staticWarning(
          '❌ Migration "${entry.name}" failed: $e\n'
          '   Transaction rolled back. $successCount migration(s) were applied before this failure.'
        );
        return; // Stop running further migrations
      }
    }

    Logger.staticInfo('✨ Successfully applied $successCount migration(s) in batch $batch.');
  } finally {
    await _releaseLock();
  }
}