@ooneex/migrations component is a database migration runner for Bun’s native SQL client. Each migration is a class implementing IMigration with up and down methods, a timestamp-based version, and an optional dependency list. Migrations are registered with a decorator, sorted by version, and run inside a transaction — applied versions are tracked in a database table so each one runs exactly once.
Why this component
- Versioned and ordered. Every migration carries a timestamp version (
YYYYMMDDHHMMSSMMM); the runner sorts by version and applies them in order. - Up and down.
up()applies the change,down()reverses it — so schema changes stay symmetric and reviewable. - Transaction-safe. Each migration runs inside
sql.begin(); a failure rolls back the whole migration and stops the run. - Run-once tracking. Applied versions are recorded in a tracking table (default
migrations); already-applied migrations are skipped. - Dependency-aware. A migration can declare other migrations as dependencies, and they run first.
- Container-managed. Register a migration class with a decorator and the runner resolves it from the container.
How it works
You write a migration class that implementsIMigration and register it with @decorator.migration(). The up() runner loads every registered migration, sorts it by version, and applies each pending one inside a transaction, recording the version when it succeeds.
| Member | Purpose |
|---|---|
up(tx, sql) | Apply the schema change. Runs inside a transaction (tx); sql is the pooled client. |
down(tx, sql) | Reverse the schema change — drop exactly what up added. |
getVersion() | Return the timestamp version string; used for ordering and tracking. |
getDependencies() | Return the migration classes that must run before this one. |
up() runner drives the lifecycle:
| Function | Purpose |
|---|---|
up(config?) | Run all pending migrations in version order, each in a transaction. |
migrationCreate(config?) | Generate a new migration file (and its test) from the template. |
getMigrations() | Return every registered migration instance, sorted by version. |
generateMigrationVersion() | Produce a YYYYMMDDHHMMSSMMM timestamp version string. |
createMigrationTable(sql, tableName) | Create the tracking table (id VARCHAR(20) PRIMARY KEY) if missing. |
id; if present, the migration is skipped. Passing --drop to the runner first drops and recreates the public schema — destructive, for development only.
Environment variables
| Variable | Required | Purpose |
|---|---|---|
DATABASE_URL | Yes (unless databaseUrl is passed in config) | Postgres connection string used by the runner, e.g. postgres://user:pass@localhost:5432/mydb. |
Usage
A migration is a class implementingIMigration, registered with the decorator. up() applies the change and down() reverses it:
up(). It reads DATABASE_URL by default, or takes an explicit connection string and tracking table name:
Best practices
- Make
down()reverseup()exactly. Drop precisely whatupadds; an asymmetric or irreversibledown()is a bug. - Never edit an applied migration. On a shared database, add a new corrective migration instead of rewriting one that already ran.
- Index what you query. Add indexes for foreign keys,
WHERE/ORDER BYcolumns, and unique constraints inup(), and drop them indown()before the table or column they cover. - Match the entity. Keep column types, nullability, and lengths in sync with the entity definition — a non-nullable column must be
NOT NULLin the migration. - Reserve
--dropfor development. It wipes the whole schema; never run it against a shared or production database. - Use dependencies for cross-migration ordering. When one change must precede another regardless of timestamps, declare it in
getDependencies().
CLI command
Scaffold a migration with the generator.ooneex migration:create writes a timestamped migration class under modules/<module>/src/migrations/, a matching test under modules/<module>/tests/migrations/, refreshes the migrations.ts barrel export, and creates the module’s bin/migration/up.ts runner if it is missing.
| Option | Description | Default |
|---|---|---|
--module | Target module the migration is generated into. | shared |
--name option. Capture the schema change when you implement up() and down().
The generated class is a ready-to-fill stub:
ooneex migration:up:
| Option | Description | Default |
|---|---|---|
--drop | Drop and recreate the public schema before running migrations. | false |
Use with Claude and Codex
The generator ships a matchingmigration:create skill. It runs the scaffold and then guides your AI agent through completing the migration — implementing up() with the schema change, down() with the reverse, and the indexes your queries need. Initialize the skills once for your agent:
- Claude
- Codex
Prompt
migration:create, then implements up() to add the avatar column and down() to drop it.