@bydey/tusk
v0.5.0
Published
SQL-first PostgreSQL migrations for Node.js and Bun
Maintainers
Readme
Tusk
Tusk is a SQL-first PostgreSQL migration tool for Node.js and Bun.
It is explicit and low abstraction:
- write plain SQL, not a migration DSL
- define
upanddownsteps directly
It handles execution for you:
- transactional migrations with advisory locking
- one migration contract across
pg,postgres.js, and the CLI
Tusk favors control and embeddability over schema builders and generated migrations.
Requirements
- Node.js
18+or Bun1+ - PostgreSQL
13+
Recommended for new projects:
- Node.js
24 - PostgreSQL
18
Install
With pg:
npm install @bydey/tusk pg
# or
bun add @bydey/tusk pgWith postgres.js:
npm install @bydey/tusk postgres
# or
bun add @bydey/tusk postgresMigration Model
Tusk uses timestamped SQL files with explicit directionality.
Tusk reads SQL files from a migrations directory:
migrations/
1728123456789_create_users.up.sql
1728123456789_create_users.down.sql*.up.sqlis applied when you runup*.down.sqlis applied when you rundown- executed migrations are tracked in the
_migrationstable - migrations are protected by a Postgres advisory lock so two runners do not apply them at the same time
Tusk does not provide a migration DSL or schema abstraction layer.
Example:
-- migrations/1728123456789_create_users.up.sql
CREATE TABLE users (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
email TEXT NOT NULL UNIQUE
);-- migrations/1728123456789_create_users.down.sql
DROP TABLE IF EXISTS users;Quick Start
Set your database connection with either DATABASE_URL or individual variables:
DATABASE_URL=postgresql://user:password@localhost:5432/app
# or
DB_HOST=localhost
DB_PORT=5432
DB_NAME=app
DB_USER=postgres
DB_PASSWORD=secret
MIGRATIONS_PATH=./migrations
LOG_LEVEL=infoSet MIGRATIONS_PATH to use a different migration directory.
Create your first migration:
npx tusk create create_users
# or
bunx tusk create create_usersRun pending migrations:
npx tusk upCheck status:
npx tusk status
npx tusk status --exit-code
npx tusk status --json
npx tusk status --quietValidate migration files before applying them:
npx tusk validate
npx tusk validate --json
npx tusk validate --db --jsonCheck whether the project and database are ready for Tusk:
npx tusk doctor
npx tusk doctor --jsonPreview exactly what would run:
npx tusk up --dry-run
npx tusk up --dry-run --jsonRoll back migrations. down defaults to one rollback so an omitted
argument cannot accidentally undo the full migration history:
npx tusk down
npx tusk down --dry-run
npx tusk down 3
npx tusk down --allStarting From an Existing Database
If your schema already exists, Tusk can derive a baseline migration from the live database:
npx tusk initThis creates 0000000000000_initial.up.sql and 0000000000000_initial.down.sql so future schema changes can be managed through normal migrations.
The generated initial migration is also recorded in _migrations as already applied. That makes takeover explicit: tusk up will skip the baseline, new migrations can run normally, and tusk down 1 can roll back the baseline if it is the latest applied migration.
Programmatic Use
With pg:
import { Pool } from "pg";
import { createPgAdapter, runUp } from "@bydey/tusk";
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const adapter = createPgAdapter(pool);
await runUp(adapter, "./migrations");With postgres.js:
import postgres from "postgres";
import { createPostgresJsAdapter, runUp } from "@bydey/tusk";
const sql = postgres(process.env.DATABASE_URL!);
const adapter = createPostgresJsAdapter(sql);
await runUp(adapter, "./migrations");Generate an initial migration programmatically:
import { Pool } from "pg";
import { createInitialMigration, createPgAdapter } from "@bydey/tusk";
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const adapter = createPgAdapter(pool);
await createInitialMigration(adapter, "./migrations");Elysia
import { Elysia } from "elysia";
import { migrate } from "@bydey/tusk";
new Elysia()
.use(
migrate({
connectionString: process.env.DATABASE_URL,
migrationsPath: "./migrations",
})
)
.listen(3000);By default the plugin runs up on startup.
CLI Commands
tusk create <name>
tusk init
tusk up
tusk down [count]
tusk down --all
tusk status
tusk validate
tusk doctor
tusk versiontusk down rolls back one migration by default. This is intentionally narrow:
the safest rollback is the latest migration only, and broader rollback should be
explicit. Use tusk down <count> to roll back several migrations, or
tusk down --all to roll back every applied migration.
tusk up --dry-run, tusk down --dry-run,
tusk down <count> --dry-run, and tusk down --all --dry-run print the
ordered migration SQL without applying it.
tusk status --exit-code exits with status 1 when migrations are pending and 0 when the schema is clean.
tusk status --quiet suppresses the detailed sections and prints only the summary line, which is useful for scripts and CI logs.
tusk status --json prints machine-readable status data with ok, command, executed, pending, and summary fields. It can be combined with --exit-code, but not with --quiet.
tusk validate checks migration filenames, pairs, executable SQL, duplicate timestamps, and transaction-control statements. Add --db to check executed migration checksums against the configured database without modifying migration state.
tusk doctor runs a read-only health check over the migration directory, database configuration, connection, PostgreSQL compatibility, migration metadata, checksum drift, status readability, and advisory lock support. It exits 0 when there are no failing checks and 1 when action is needed. Use tusk doctor --json for automation.
--json is supported by create, init, up, down, status, validate, and doctor for machine-readable automation output.
Agent and MCP Use
For AI agents and automation, prefer the safe loop in Agent workflow: doctor --json, validate --json, validate --db --json, up --dry-run --json, then apply only after the plan is reviewed.
Tusk also includes a stdio MCP server:
tusk-mcpIt exposes tools for validation, status, dry-run planning, and migration file creation.
Support Policy
Tusk keeps a wide support floor for teams working on older projects while still treating the current stack as the primary development lane.
- Supported floor: Node.js
18+, Bun1+, PostgreSQL13+ - Recommended stack: Node.js
24, PostgreSQL18 - Required PR CI checks:
Verify (Node 24, PostgreSQL 18)runs the full build and test suiteMinimum Support (Node 18, PostgreSQL 13)runs the packaged smoke test against the oldest supported runtime/database pair
- Scheduled compatibility coverage:
- the
Compatibility Matrixworkflow exercises packaged smoke tests across multiple supported Node.js and PostgreSQL versions
- the
If a supported floor version stops passing CI, it is a regression and should be treated as a bug.
More
License
MIT
