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

db-migrate-ts-monorepo

v1.0.0

Published

Type-safe database migrations powered by Zod and TypeScript — Monorepo root

Downloads

143

Readme

db-migrate-ts

Type-safe database migrations powered by Zod and TypeScript

npm version TypeScript License: MIT Bundle Size


The Problem

Every back-end developer knows this pain: you write a migration, deploy to production, and the app crashes because you wrote user_name instead of username, or articls instead of articles. This type of error doesn't show up at write-time — it shows up at the worst possible moment: runtime, on the client's environment.

Existing tools each solve part of the problem:

  • Prisma Migrate — excellent type safety, but forces the entire Prisma ecosystem (ORM, schema format, generate step)
  • Knex.js — lightweight and flexible, but no type safety on table/column names — you're working with plain strings
  • TypeORM — Decorator-based approach that's confusing and adds overhead

db-migrate-ts fills the gap: as lightweight as Knex, as safe as Prisma, as simple as raw SQL — but with TypeScript in every corner.


The Solution

// ❌ BEFORE: Runtime crash on production
migrate.renameTable("poasts", "articles"); // Typo → runtime error

// ✅ AFTER: TypeScript error at write-time
migrate.renameTable("poasts", "articles");
//                  ^^^^^^^
// TypeScript Error: Argument of type '"poasts"' is not assignable to
// parameter of type '"users" | "posts" | "comments"'

Features

| Feature | Description | |---------|-------------| | 🔒 Type-safe table names | Typos in table names → TypeScript compile errors | | 🔒 Type-safe column names | Typos in column names → TypeScript compile errors | | 🧩 Zod integration | Define columns using Zod schemas — type inference is automatic | | 🗄️ Multi-dialect | PostgreSQL, MySQL, SQLite | | ⚡ Lightweight | ~12KB gzipped, zero mandatory runtime dependencies | | 🔄 Rollback support | Full down() migration support with step count or target name | | ✅ Checksum validation | Detects tampered migrations after execution | | 📸 SQL snapshots | Save generated SQL to files for review and auditing | | 🖥️ CLI | Full-featured command-line tool | | 🔍 Dry-run mode | Preview SQL without touching the database | | 🏃 Transaction-wrapped | Each migration runs in a transaction (auto-rollback on failure) |


Quick Start

1. Install

npm install db-migrate-ts zod
# With TypeScript (required)
npm install -D typescript

# Add your database driver:
npm install pg          # PostgreSQL
npm install mysql2      # MySQL
npm install better-sqlite3  # SQLite

2. Create config file

// db-migrate.config.ts
import { defineConfig, createPostgresAdapter } from "db-migrate-ts";
import { z } from "zod";

export default defineConfig({
  adapter: "postgres",
  connection: await createPostgresAdapter(process.env.DATABASE_URL!),
  migrationsDir: "./migrations",

  // Define your schema for type-safe operations
  schema: {
    users: {
      id:    { schema: z.string().uuid(), primaryKey: true },
      email: { schema: z.string().email(), unique: true },
      name:  { schema: z.string().max(100) },
    },
    posts: {
      id:       { schema: z.number().int(), primaryKey: true },
      title:    { schema: z.string().max(255) },
      authorId: {
        schema: z.string().uuid(),
        references: { table: "users", column: "id", onDelete: "CASCADE" },
      },
    },
  },
});

3. Create your first migration

npx db-migrate-ts generate create_users_table --template table

This creates migrations/20241215143025_create_users_table.ts.

4. Edit the migration

// migrations/20241215143025_create_users_table.ts
import type { Migration } from "db-migrate-ts";
import { z } from "zod";

export default {
  name: "20241215143025_create_users_table",
  timestamp: 20241215143025,

  up: async (migrate) => {
    migrate.createTable("users", {
      id: {
        schema: z.string().uuid(),
        primaryKey: true,
        default: "gen_random_uuid()",
      },
      email: {
        schema: z.string().email().max(254),
        unique: true,
      },
      name: {
        schema: z.string().max(100),
      },
      createdAt: {
        schema: z.date(),
        default: "NOW()",
      },
    });

    migrate.createIndex("users", ["email"], {
      name: "idx_users_email",
      unique: true,
    });
  },

  down: async (migrate) => {
    migrate.dropIndex("idx_users_email");
    migrate.dropTable("users");
  },
} satisfies Migration;

5. Run migrations

npx db-migrate-ts up
⚡ db-migrate-ts — running migrations

  → 20241215143025_create_users_table  ✓ (48ms)

✅ 1 migration applied successfully.

CLI Commands

# Run all pending migrations
db-migrate-ts up

# Preview without executing
db-migrate-ts up --dry-run

# Roll back last migration
db-migrate-ts down

# Roll back last 3 migrations
db-migrate-ts down --steps 3

# Roll back to specific migration
db-migrate-ts down --to 20241215120000_create_users_table

# Show status table
db-migrate-ts status

# Show status as JSON (CI/CD friendly)
db-migrate-ts status --json

# Generate new migration file
db-migrate-ts generate add_age_to_users

# Generate with table template
db-migrate-ts generate create_products --template table

# Validate all migrations without running them
db-migrate-ts validate

Type-Safe Migration API

All builder operations validate table and column names against your schema at compile time:

// ✅ Valid — "users" and "email" exist in the schema
migrate.dropColumn("users", "email");

// ❌ TypeScript Error — "poasts" doesn't exist
migrate.renameTable("poasts", "articles");
// Error: Argument of type '"poasts"' is not assignable to
// parameter of type '"users" | "posts"'

// ❌ TypeScript Error — "usr_email" is not a column of "users"  
migrate.alterColumn("users", "usr_email", { schema: z.string() });
// Error: Argument of type '"usr_email"' is not assignable to
// parameter of type '"id" | "email" | "name" | "createdAt"'

Full Builder API

// Table operations
migrate.createTable(tableName, schema)
migrate.dropTable(tableName, { ifExists?, cascade? })
migrate.renameTable(from, to)
migrate.truncateTable(tableName)

// Column operations
migrate.addColumn(tableName, columnName, definition)
migrate.dropColumn(tableName, columnName)       // ← columnName is type-safe!
migrate.renameColumn(tableName, from, to)        // ← from is type-safe!
migrate.alterColumn(tableName, columnName, def)  // ← columnName is type-safe!
migrate.setNotNull(tableName, columnName)
migrate.dropNotNull(tableName, columnName)
migrate.setDefault(tableName, columnName, value)
migrate.dropDefault(tableName, columnName)

// Index operations
migrate.createIndex(tableName, columns, { unique?, where?, using?, name? })
migrate.dropIndex(indexName, { ifExists?, cascade? })

// Constraint operations
migrate.addForeignKey(tableName, columnName, references)
migrate.dropForeignKey(tableName, constraintName)
migrate.addCheck(tableName, constraintName, expression)
migrate.dropCheck(tableName, constraintName)

// Escape hatch for complex SQL
migrate.raw(sql, bindings?)

Zod → SQL Type Mapping

| Zod Schema | PostgreSQL | MySQL | SQLite | |------------|-----------|-------|--------| | z.string() | TEXT | TEXT | TEXT | | z.string().max(100) | VARCHAR(100) | VARCHAR(100) | VARCHAR(100) | | z.string().uuid() | UUID | VARCHAR(36) | VARCHAR(36) | | z.string().email() | VARCHAR(254) | VARCHAR(254) | VARCHAR(254) | | z.number().int() | INTEGER | INT | INTEGER | | z.number() | DOUBLE PRECISION | DOUBLE | REAL | | z.bigint() | BIGINT | BIGINT | BIGINT | | z.boolean() | BOOLEAN | TINYINT(1) | INTEGER | | z.date() | TIMESTAMPTZ | DATETIME | TEXT | | z.object({...}) | JSONB | JSON | TEXT | | z.array(...) | JSONB | JSON | TEXT | | z.enum([...]) | TEXT | VARCHAR(100) | TEXT | | z.string().optional() | TEXT (nullable) | TEXT (nullable) | TEXT (nullable) |


Database Adapters

// PostgreSQL
import { createPostgresAdapter } from "db-migrate-ts";
const adapter = await createPostgresAdapter(process.env.DATABASE_URL);

// MySQL
import { createMySQLAdapter } from "db-migrate-ts";
const adapter = await createMySQLAdapter(process.env.DATABASE_URL);

// SQLite
import { createSQLiteAdapter } from "db-migrate-ts";
const adapter = await createSQLiteAdapter("./app.db", { wal: true });

// In-memory SQLite (testing)
const adapter = await createSQLiteAdapter(":memory:");

Advanced Usage

Programmatic API

import { MigrationRunner, createPostgresAdapter, loadAllMigrations } from "db-migrate-ts";

const adapter = await createPostgresAdapter(process.env.DATABASE_URL!);
const migrations = await loadAllMigrations("./migrations");

const runner = new MigrationRunner(adapter, {
  dialect: "postgres",
  validateChecksums: true,
  saveSQLSnapshots: true,
  snapshotsDir: "./migrations/snapshots",
});

// Apply all pending
await runner.up(migrations);

// Roll back 2 steps
await runner.down(migrations, 2);

// Get status
const status = await runner.status(migrations);
console.table(status);

await adapter.close();

Advanced Rollback

import { RollbackManager } from "db-migrate-ts";

const manager = new RollbackManager(adapter, migrations, "postgres");

// Roll back everything after a specific migration
await manager.rollbackTo("20241215120000_create_users_table");

// Roll back all (full reset)
await manager.rollbackAll();

// Preview without executing
const plan = await manager.preview("20241215120000_create_users_table");
console.log("Would roll back:", plan.map(m => m.name));

Schema Registry (Type Inference)

import { createRegistry } from "db-migrate-ts";
import type { InferTableType } from "db-migrate-ts";

const registry = createRegistry(mySchema);

// Infer the TypeScript type of a table row
type UserRow = InferTableType<typeof mySchema.users>;
// → { id: string; email: string; name: string; createdAt: Date }

// Insert type (required fields only, optional fields... optional)
type InsertUser = InsertType<typeof mySchema.users>;

// Query type-safety
const users: UserRow[] = await db.query("SELECT * FROM users");
users[0].email; // ✅ TypeScript knows this is string

Project Structure

your-project/
├── db-migrate.config.ts   ← Configuration
├── migrations/
│   ├── 20241215120000_create_users.ts
│   ├── 20241216090000_create_posts.ts
│   └── snapshots/          ← Generated SQL snapshots
└── package.json

Architecture

db-migrate-ts/
├── packages/
│   ├── core/               ← Main library (MIT)
│   │   ├── src/types/      ← Type system (column, table, migration, dialect)
│   │   ├── src/schema/     ← Zod→SQL converter, registry, differ, validator
│   │   ├── src/migration/  ← Builder, runner, tracker, rollback manager
│   │   ├── src/sql/        ← SQL builder, formatter, sanitizer
│   │   └── src/dialects/   ← PostgreSQL, MySQL, SQLite adapters
│   ├── cli/                ← CLI tool (MIT)
│   └── gui/                ← Desktop GUI (Pro — $39 one-time)
└── examples/
    ├── with-postgres/
    ├── with-mysql/
    └── with-nextjs/

Pro GUI ($39 — one-time license)

The Pro GUI is a desktop application (Electron + React) with:

  • Visual Schema Diff — side-by-side comparison of old vs new schema
  • SQL Preview — see the exact SQL before executing any migration
  • One-click Rollback — roll back any migration with a single click
  • Migration Timeline — visual history graph of all migrations
  • Live Connection — connects to your database and shows live table state
  • Checksum Alerts — instantly warns when a migration has been tampered with

Comparison

| Feature | db-migrate-ts | Prisma Migrate | Knex.js | TypeORM | |---------|:---:|:---:|:---:|:---:| | Type-safe table names | ✅ | ✅ | ❌ | ⚠️ | | Type-safe column names | ✅ | ✅ | ❌ | ⚠️ | | Zod integration | ✅ | ❌ | ❌ | ❌ | | ORM-free | ✅ | ❌ | ✅ | ❌ | | Multi-dialect | ✅ | ✅ | ✅ | ✅ | | Rollback | ✅ | ⚠️ | ✅ | ✅ | | Bundle size | ~12KB | ~5MB | ~800KB | ~2MB | | License | MIT | Apache-2 | MIT | MIT |


License

Core & CLI: MIT — free forever.
GUI: Commercial ($39 one-time license).


Contributing

See CONTRIBUTING.md for development setup and guidelines.

git clone https://github.com/Brah-Timo/db-migrate-ts
cd db-migrate-ts
pnpm install
pnpm build
pnpm test