Migration Runtime
db.migrate() applies pending migrations from your schema in order.
What db.migrate() does
- Ensures
__tsql_migrationsexists. - Reads already-applied migration names.
- Compiles schema SQL from
schema.migrations. - Runs each pending migration inside its own transaction.
- Records migration name and timestamp after success.
If a migration fails, that migration transaction is rolled back and execution stops.
If the connection provides executeTransaction(statements), db.migrate() compiles each pending migration into CompiledQuery[] and sends it through that batch transaction primitive. This supports runtimes such as Cloudflare D1, where transactions are D1Database.batch() calls rather than SQL-level interactive begin / commit sessions.
If executeTransaction is not provided, db.migrate() uses db.begin() and the interactive transaction path.
Operational usage
- Preferred: run in CI/deploy bootstrap step.
- Monolith: run once during startup before serving traffic.
- Safe to call repeatedly; applied migrations are skipped.
Example bootstrap script
import { db } from "../src/db";
await db.migrate();
DML in migrations
Use migration queries directly in the migration callback:
export const schema = defineDatabase((db) =>
db
.migration("001_create_users", (schemaBuilder) =>
schemaBuilder.createTable("users", (t) => ({
id: t.text().primaryKey(),
email: t.text().notNull(),
})),
)
.migration("002_uppercase_user_ids", (migration) =>
migration
.update("users")
.set(({ users }) => ({
id: users.id.upper(),
}))
.execute(),
),
);
The same callback can mix schema and data steps in sequence.
DDL and DML execute in the order they are chained. This permits typed table rebuilds: create a
replacement table, copy rows with insert(...).from(select), drop the old table, and rename the
replacement without falling back to raw SQL.
Each migration runs in a transaction, so failures roll back cleanly; rerunning db.migrate() resumes from the first migration that has not been recorded yet.
Compile one migration
Use compileMigrationQueries(schema, dialect, migrationIndex) to compile one migration body into CompiledQuery[] without the migration tracking insert:
const statements = await compileMigrationQueries(schema, sqliteDialect(), 1);
In batch compile mode, query results are unavailable. If migration logic reads query results to build later SQL, tsql throws: connection supports batched transactions only; migration query results unavailable.