npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@bydey/tusk

v1.0.0

Published

SQL-first PostgreSQL migrations for Node.js and Bun

Readme

Tusk

CI

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 up and down steps 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 Bun 1.3.8+
  • PostgreSQL 13+
  • ESM projects for programmatic use; the CLI works from any project type

Recommended for new projects:

  • Node.js 24
  • PostgreSQL 18

Install

Install Tusk with one PostgreSQL driver. With pg:

npm install @bydey/tusk pg
# or
bun add @bydey/tusk pg

With postgres.js:

npm install @bydey/tusk postgres
# or
bun add @bydey/tusk postgres

The package root contains the driver-neutral migration API. Import driver and framework integrations from their explicit subpaths:

import { runUp } from "@bydey/tusk";
import { createPgAdapter } from "@bydey/tusk/pg";
// or: import { createPostgresJsAdapter } from "@bydey/tusk/postgres";

The Elysia integration requires both elysia and pg:

bun add @bydey/tusk elysia pg

The CLI and MCP server discover the installed driver. If both are installed, pg is the default; set TUSK_DRIVER=postgres to select postgres.js.

Migration 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.sql is applied when you run up
  • *.down.sql is applied when you run down
  • executed migrations are tracked in the _migrations table
  • 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.

The _migrations table name and shape are part of Tusk's v1 compatibility contract. See Metadata table contract for the exact schema, search_path scope, and safety behavior.

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

This is the recommended first migration flow.

  1. Create a .env file. Tusk loads it automatically. Set either DATABASE_URL:
DATABASE_URL=postgresql://user:password@localhost:5432/app

or individual settings:

DB_HOST=localhost
DB_PORT=5432
DB_NAME=app
DB_USER=postgres
DB_PASSWORD=secret

Optional settings:

MIGRATIONS_PATH=./migrations
LOG_LEVEL=warn
TUSK_DRIVER=pg
TUSK_STATEMENT_TIMEOUT_MS=300000

TUSK_STATEMENT_TIMEOUT_MS applies to each migration transaction. Use 0 to leave PostgreSQL's statement timeout unchanged. See Transactions and timeouts.

  1. Initialise the project and create a migration pair:
npx tusk init
npx tusk create create_users

With Bun:

bunx tusk init
bunx tusk create create_users
  1. Edit both generated files under migrations/:
<timestamp>_create_users.up.sql
<timestamp>_create_users.down.sql
  1. Validate the files before connecting to the database:
npx tusk validate
  1. Run the read-only project and database preflight:
npx tusk doctor
  1. Review the ordered SQL without applying it:
npx tusk up --dry-run
  1. Apply the migration, then confirm status:
npx tusk up
npx tusk status

For CI and agents, add --json. Use tusk status --exit-code to exit 1 when migrations are pending, or tusk status --quiet for a single summary line.

Roll back migrations. down defaults to one rollback so an omitted argument cannot accidentally undo the full migration history:

npx tusk down
npx tusk down 1
npx tusk down --dry-run
npx tusk down 3
npx tusk down --all

Rollback contract:

  • tusk down and tusk down 1 roll back exactly the latest applied migration.
  • tusk down n rolls back the latest n applied migrations, newest first.
  • tusk down --all rolls back every applied migration, newest first.
  • An adopted 0000000000000_initial.up.sql baseline is protected from ordinary rollback. Tusk refuses before executing the batch unless the destructive --allow-baseline-rollback override is present.
  • If n is greater than the number of applied migrations, Tusk rolls back all available applied migrations and says how many were available.
  • If no migrations are applied, Tusk exits successfully with No applied migrations to roll back.
  • Rollbacks use only matching .down.sql files. If any required rollback file is missing, Tusk fails while planning before applying the rollback batch.

Starting From an Existing Database

For a new project, initialise the local migration directory first:

npx tusk init

This creates ./migrations when it is missing. Add matching .up.sql and .down.sql files there, then run tusk doctor and tusk up.

If your schema already exists, Tusk can create and record a starting baseline with the explicit adoption flag:

npx tusk init --from-db

This 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 recorded in _migrations as already applied, so tusk up skips it and new migrations run normally. Ordinary rollback will not remove this baseline. The explicit --allow-baseline-rollback override can drop every table represented by the baseline and should be treated as destructive.

Adoption is not a complete PostgreSQL backup or schema-dump replacement. Tusk fails before writing or recording a baseline when it detects schema features it cannot reproduce safely, including custom types, arrays, views, routines, triggers, policies, generated columns, check/exclusion constraints, partitions, inheritance, and unowned sequences. See Existing database adoption for the exact boundary.

Programmatic Use

With pg:

import { Pool } from "pg";
import { runUp } from "@bydey/tusk";
import { createPgAdapter } from "@bydey/tusk/pg";

const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const adapter = createPgAdapter(pool);

try {
  await runUp(adapter, "./migrations");
} finally {
  await pool.end();
}

With postgres.js:

import postgres from "postgres";
import { runUp } from "@bydey/tusk";
import { createPostgresJsAdapter } from "@bydey/tusk/postgres";

const sql = postgres(process.env.DATABASE_URL!);
const adapter = createPostgresJsAdapter(sql);

try {
  await runUp(adapter, "./migrations");
} finally {
  await sql.end();
}

Generate an initial migration programmatically:

import { Pool } from "pg";
import { createInitialMigration } from "@bydey/tusk";
import { createPgAdapter } from "@bydey/tusk/pg";

const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const adapter = createPgAdapter(pool);

try {
  await createInitialMigration(adapter, "./migrations");
} finally {
  await pool.end();
}

Custom database clients only need to implement the exported MigrationAdapter contract (query, transaction, and migration lock methods) to use planning and execution APIs. DatabaseAdapter extends that contract with introspection and SQL generation used only by createInitialMigration. See the custom adapter contract before implementing one.

Elysia

import { Elysia } from "elysia";
import { migrate } from "@bydey/tusk/elysia";

new Elysia()
  .use(
    migrate({
      connectionString: process.env.DATABASE_URL,
      migrationsPath: "./migrations",
    }),
  )
  .listen(3000);

By default the plugin runs up on startup. The Elysia integration requires Bun 1.3.8+ or Node.js 20+; the rest of Tusk supports Node.js 18+.

CLI Commands

tusk create <name>
tusk init
tusk init --from-db
tusk up
tusk down [count]
tusk down --all
tusk down --allow-baseline-rollback
tusk status
tusk validate
tusk doctor
tusk version

tusk down and tusk down 1 roll 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. Rollback batches run newest first and use only matching .down.sql files. If a requested count is larger than the applied migration count, Tusk rolls back all available applied migrations and reports the smaller available count.

Adopted baselines are protected. If a selected rollback would include 0000000000000_initial.up.sql, Tusk refuses the whole batch unless --allow-baseline-rollback is supplied. Programmatic callers must opt in with a rollback target such as { count: 1, allowBaselineRollback: true }.

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. See the JSON output contracts for stable fields and compatibility rules.

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-mcp

It 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+, Bun 1.3.8+, PostgreSQL 13+
  • Recommended stack: Node.js 24, PostgreSQL 18
  • Required PR CI checks:
    • Verify (Node 24, PostgreSQL 18) requires the full build/test/quality lane and the globally aggregated exhaustive mutation suite
    • Minimum Support (Node 18, PostgreSQL 13) runs the packaged smoke test against the oldest supported runtime/database pair
    • Package smoke (macos-latest) and Package smoke (windows-latest) verify the installed artifact on both desktop platforms
  • Scheduled compatibility coverage:
    • the Compatibility Matrix workflow exercises packaged smoke tests across multiple supported Node.js and PostgreSQL versions

If a supported floor version stops passing CI, it is a regression and should be treated as a bug.

More

License

MIT