kysely icon indicating copy to clipboard operation
kysely copied to clipboard

fix(Migrator): Enable Transactional Dry-Runs by supporting Transaction Instances with disableTransactions: true

Open jlucaso1 opened this issue 6 months ago • 1 comments

Hey team!

This PR addresses an issue where the Migrator would unexpectedly fail if initialized with a Kysely Transaction instance (trx) while disableTransactions was set to true. The Migrator was attempting to call trx.connection(), which isn't a supported operation on a Transaction object.

The Problem:

When disableTransactions is true, the Migrator falls back to using this.#props.db.connection().execute(...). If this.#props.db is a Transaction instance, this leads to: Error: calling the connection method for a Transaction is not supported

This prevented a useful pattern: performing a "transactional dry-run" of migrations, where migrations are executed within an outer transaction that is subsequently rolled back.

The Solution:

This change modifies the Migrator to check if the provided db instance isTransaction. If it is, and disableTransactions is also true, the Migrator will now directly use the provided Transaction instance (this.#props.db) to run the migration logic (run(this.#props.db)), bypassing the problematic .connection() call.

Use Case Example (Transactional Migration Validation):

This fix makes it much easier to validate migrations without permanently altering the database, which is super handy for pre-push hooks or CI checks:

import { db } from './your-db-setup'; // Your main Kysely instance
import { Migrator, FileMigrationProvider } from 'kysely';

async function validateMigrationsDryRun() {
  console.log('Attempting transactional dry-run of migrations...');
  try {
    await db.transaction().execute(async (trx) => {
      const migrator = new Migrator({
        db: trx, // Pass the transaction
        provider: new FileMigrationProvider(...),
        disableTransactions: true, // Tell Migrator not to manage its own TX
      });

      const { error, results } = await migrator.migrateToLatest();

      if (error) {
        console.error('Migration validation failed:', error);
        throw error; // This will cause the outer transaction to rollback
      }

      console.log('Migrations appear valid!');
      // IMPORTANT: We must throw an error to force the rollback for a dry run
      throw new Error('DRY_RUN_ROLLBACK');
    });
  } catch (e: any) {
    if (e.message === 'DRY_RUN_ROLLBACK') {
      console.log('Dry run successful, all changes rolled back.');
    } else {
      console.error('Migration validation encountered an error:', e);
      // Potentially exit with error for CI
    }
  }
}

Testing: I've added a test case to cover this specific scenario, ensuring the Migrator behaves as expected when used with a Transaction and disableTransactions: true.

jlucaso1 avatar Jun 05 '25 19:06 jlucaso1

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
kysely 🛑 Canceled (Inspect) Jul 9, 2025 6:10pm

vercel[bot] avatar Jun 05 '25 19:06 vercel[bot]

kysely_koa_example

npm i https://pkg.pr.new/kysely-org/kysely@1480

commit: d98b472

pkg-pr-new[bot] avatar Jun 29 '25 10:06 pkg-pr-new[bot]

Any updates on this?

tarqwara avatar Sep 01 '25 05:09 tarqwara