Zero-dependency PostgreSQL DDL migration supervisor & linter for Node.js / TypeScript.

Eliminates lock queues, connection pool exhaustion, and deploy deadlocks across Prisma, Drizzle, and raw SQL migrations.

Drop ddlforge wrap directly in front of your ORM migration commands:

# Supervise Prisma deployments

npx ddlforge wrap -- npx prisma migrate deploy

# Supervise Drizzle migrations

npx ddlforge wrap -- npx drizzle-kit migrate- Auto-detects migration folders: Discovers prisma/migrationsordrizzledirectory layouts (or uses--diroverride).

- Filters pending migrations: Compares against database schema history tables (_prisma_migrationsor__drizzle_migrationswhen--db/DATABASE_URLis accessible), or verifies all candidate files if--dbis omitted.

- Pre-flight safety interception: Evaluates all pending migrations with zero-downtime rules. If any BLOCKERhazard is detected, it outputs rich diagnostics with remediation recipes and aborts deployment before running the child process (exit code1).

- Execution delegation: When pre-flight safety checks pass, it spawns your migration command with inherited stdio (stdio: 'inherit') and cleanly propagates the child process's exit code.

In PostgreSQL, DDL commands take table-level locks. Even a sub-millisecond migration can cause a catastrophic production outage:

┌───────────────────────────── The Migration Lock Trap ──────────────────────────────┐

│ │

│ 1. Long SELECT holds ACCESS SHARE lock on "users" │

│ 2. ALTER TABLE requests ACCESS EXCLUSIVE lock │

│ └─ BLOCKED: Waits behind the SELECT │

│ 3. Subsequent web queries (SELECT, INSERT, UPDATE) arrive │

│ └─ BLOCKED: Queue behind the waiting ALTER TABLE! │

│ 4. Connection pool saturates within 5–15 seconds │

│ └─ Outage: All application endpoints return 504 Gateway Timeout │

│ │

└────────────────────────────────────────────────────────────────────────────────────┘

ddlforge eliminates this risk at every phase:

- In CI: ddlforge checkfails pull requests before dangerous DDL reachesmain.

- In ORM deployments: ddlforge wrapintercepts pending migrations and aborts before running unvalidated changes.

- In direct execution: ddlforge applyinjects statement lock-timeouts, retries with full jitter, and runs a background avalanche-breaker.

Supervises external migration commands by running pre-flight safety checks against pending migration files before delegating execution.

ddlforge wrap [options] -- <command...>

ARGUMENTS:

<command...> Migration command to execute if safety checks pass

FLAGS:

--dir <path> Migration directory (auto-detects prisma/migrations, drizzle, or ./migrations)

--db <url> Database URL for pending migration comparison (falls back to DATABASE_URL)

--allow-blockers Warn on blockers instead of aborting the command

--help, -h Print wrap help and exit

# Supervise standard Prisma deployment

npx ddlforge wrap -- npx prisma migrate deploy

# Supervise Drizzle deployment with explicit directory

npx ddlforge wrap --dir=./drizzle -- npx drizzle-kit migrate

# Allow blockers in emergency deploys (warns without aborting)

npx ddlforge wrap --allow-blockers -- npx prisma migrate deployStatically parses SQL files with a zero-dependency, single-pass lexer and AST analyzer. Emits human-readable terminal output, JSON, GitHub Markdown, or SARIF 2.1.0.

ddlforge check [paths...] [flags]

ARGUMENTS:

paths Migration files or directories (e.g. ./prisma/migrations, ./drizzle)

FLAGS:

--format <type> Output format: pretty | terminal | json | markdown | sarif (default: pretty)

--output <path> Write report directly to a file (ideal for SARIF or Markdown step summaries)

--pg <version> Target PostgreSQL version (default: 16)

--quiet, -q Show blockers only; suppress warnings and advisories

--changed-only Use git diff to lint only files modified in this branch / PR

--version, -v Print ddlforge version and exit

--help, -h Print check help and exit

# Scan Prisma migrations directory

npx ddlforge check ./prisma/migrations

# Lint only changed files in current PR

npx ddlforge check --changed-only

# Generate SARIF 2.1.0 report for GitHub Code Scanning

npx ddlforge check "prisma/migrations/**/*.sql" --format=sarif --output=ddlforge.sarif

# Export JSON summary for custom CI tooling

npx ddlforge check ./drizzle --format=json --output=report.jsonExecutes raw SQL migration files against live databases with automated safeguards:

- Per-statement timeout injection: Automatically injects session lock_timeoutandstatement_timeout.

- Non-transactional CONCURRENTLYsplitting: Automatically detects statements forbidden inside transactions (CREATE/DROP INDEX CONCURRENTLY,REINDEX CONCURRENTLY,VACUUM) and runs them outsideBEGIN...COMMITwith session-levelSET/RESETtimeouts.

- Exponential backoff with full jitter: Randomizes retry intervals on lock errors (55P03/57014) to prevent retry stampedes.

- Background lock-queue monitor: Continuously polls pg_lockson an isolated connection and executespg_cancel_backendif queue depth reaches threshold.

ddlforge apply <file.sql> --db <DATABASE_URL> [flags]

ARGUMENTS:

<file.sql> SQL migration file to execute

FLAGS:

--db <url> PostgreSQL connection URL (required, or set DATABASE_URL)

--lock-timeout <ms> Per-statement lock_timeout in ms (default: 3000ms)

--statement-timeout <ms> Per-statement statement_timeout in ms (default: 30000ms)

--max-retries <n> Max retry attempts on lock timeout (default: 5)

--dry-run Parse and display statements without executing

--lock-queue-threshold <n> Blocked queries behind migration before aborting (default: 1)

--monitor-poll-ms <ms> Lock-monitor polling interval in ms (default: 500)

--help, -h Print apply help and exit

npx ddlforge apply ./migrations/001_add_index.sql \

--db "$DATABASE_URL" \

--lock-timeout 2500 \

--statement-timeout 60000 \

--max-retries 5 \

--lock-queue-threshold 1-- ❌ Dangerous: Table scan under ACCESS EXCLUSIVE blocks all queries

ALTER TABLE orders ADD CONSTRAINT check_amt_positive CHECK (amount > 0);

-- ✅ Safe: Two-phase non-blocking validation

ALTER TABLE orders ADD CONSTRAINT check_amt_positive CHECK (amount > 0) NOT VALID;

ALTER TABLE orders VALIDATE CONSTRAINT check_amt_positive;-- ❌ Dangerous: SHARE lock blocks write traffic during index creation

ALTER TABLE users ADD CONSTRAINT uq_users_email UNIQUE (email);

-- ✅ Safe: Build index concurrently first, then attach instantly

CREATE UNIQUE INDEX CONCURRENTLY uq_users_email_idx ON users (email);

ALTER TABLE users ADD CONSTRAINT uq_users_email UNIQUE USING INDEX uq_users_email_idx;-- ❌ Dangerous: Session locks leak across PgBouncer connection pools

SELECT pg_advisory_lock(98765);

-- ✅ Safe: Transaction-scoped lock automatically releases on COMMIT or ROLLBACK

SELECT pg_advisory_xact_lock(98765);-- ❌ Dangerous: Prisma creates DROP + ADD on field renames, wiping data

ALTER TABLE "users" DROP COLUMN "full_name";

ALTER TABLE "users" ADD COLUMN "name" TEXT NOT NULL;

-- ✅ Safe: Rename in place without data loss

ALTER TABLE "users" RENAME COLUMN "full_name" TO "name";Fails the PR if any lock blockers are introduced, and posts the report to the GitHub Step Summary.

name: Migration Safety Gate

on:

pull_request:

paths:

- '**/migrations/**'

- '**/*.sql'

jobs:

ddlforge-lint:

runs-on: ubuntu-latest

steps:

- uses: actions/checkout@v4

with:

fetch-depth: 0

- uses: actions/setup-node@v4

with:

node-version: 20

- name: Lint changed migrations

run: npx ddlforge check --changed-only --format=terminal

- name: Post Markdown summary to PR

if: always()

run: npx ddlforge check --changed-only --format=markdown >> $GITHUB_STEP_SUMMARYGenerates SARIF 2.1.0 results and uploads them to GitHub Code Scanning for inline pull request annotations.

name: Migration Code Scanning

on:

pull_request:

paths:

- '**/migrations/**'

- '**/*.sql'

push:

branches: [main]

jobs:

ddlforge-sarif:

runs-on: ubuntu-latest

permissions:

security-events: write

contents: read

steps:

- uses: actions/checkout@v4

- uses: actions/setup-node@v4

with:

node-version: 20

- name: Run ddlforge SARIF inspection

run: npx ddlforge check "prisma/migrations/**/*.sql" --format=sarif --output=ddlforge.sarif

continue-on-error: true # Upload SARIF annotations even when blockers are present

- name: Upload SARIF report to GitHub Security

uses: github/codeql-action/upload-sarif@v3

with:

sarif_file: ddlforge.sarif

category: ddlforge-migrationsSupervises production migration execution with automatic pre-flight verification:

name: Production Deployment

on:

push:

branches: [main]

jobs:

deploy:

runs-on: ubuntu-latest

environment: production

steps:

- uses: actions/checkout@v4

- uses: actions/setup-node@v4

with:

node-version: 20

- name: Install dependencies

run: npm ci

- name: Supervised Prisma Deployment

env:

DATABASE_URL: ${{ secrets.DATABASE_URL }}

run: npx ddlforge wrap -- npx prisma migrate deploySuppress specific rules on individual statements when intentional:

-- ddlforge-ignore require-concurrent-index

CREATE INDEX idx_scratch ON scratch_buffer (temp_id);

-- ddlforge-ignore unbatched-dml

UPDATE app_config SET maintenance_mode = true;Disable all checks for a specific statement:

-- ddlforge-ignore

ALTER TABLE legacy_import ADD COLUMN meta_data JSONB;Disable Prisma implicit transaction wrapper (file-level):

-- prisma:no-transaction

CREATE INDEX CONCURRENTLY "users_email_idx" ON "users"("email");import { analyzeSql, formatTerminal, formatSarif } from 'ddlforge';

const result = analyzeSql(`

ALTER TABLE users ADD CONSTRAINT uq_email UNIQUE (email);

`, {

filePath: 'migrations/001.sql',

pgVersion: 16,

});

if (result.hasBlockers) {

console.log(formatTerminal([result]));

process.exit(1);

}import { orchestrateWrap } from 'ddlforge';

const result = await orchestrateWrap({

dir: './prisma/migrations',

databaseUrl: process.env.DATABASE_URL,

allowBlockers: false,

command: ['npx', 'prisma', 'migrate', 'deploy'],

});

process.exit(result.exitCode);ddlforge/

├── bin/

│ └── ddlforge.ts # Global executable CLI entrypoint

├── src/

│ ├── lexer/

│ │ ├── sqlTokenizer.ts # Zero-dependency single-pass SQL lexer

│ │ └── tokens.ts # Token & Statement definitions

│ ├── engine/

│ │ ├── analyzer.ts # Static rule pipeline orchestrator

│ │ └── locks.ts # Postgres lock levels & conflict taxonomy

│ ├── rules/ # 10 zero-downtime safety rules

│ │ ├── indexConcurrently.ts

│ │ ├── transactionTrap.ts

│ │ ├── addColumnNotNull.ts

│ │ ├── foreignKeyNotValid.ts

│ │ ├── checkConstraintNotValid.ts

│ │ ├── uniqueConstraintUsingIndex.ts

│ │ ├── prismaRenameDropAdd.ts

│ │ ├── setNotNullFullScan.ts

│ │ ├── unbatchedBackfill.ts

│ │ └── sessionAdvisoryLock.ts

│ ├── wrapper/ # wrap — ORM supervisor orchestrator

│ │ └── orchestrator.ts # Pre-flight interceptor & child delegation

│ ├── runner/ # apply — runtime execution supervisor

│ │ ├── backoff.ts # Exponential backoff with full jitter

│ │ ├── locksMonitor.ts # pg_locks background queue-avalanche breaker

│ │ └── executor.ts # Non-transactional branching & timeouts

│ ├── reporters/

│ │ ├── terminal.ts # Colorized human-readable report

│ │ ├── json.ts # Machine-readable summary & findings

│ │ ├── markdown.ts # GitHub Step Summary & PR comments

│ │ └── sarif.ts # SARIF 2.1.0 for GitHub Code Scanning

│ └── cli.ts # CLI argument parser, check/apply/wrap dispatch

└── test/

├── analyzer.test.ts # Lexer, core rules & CLI tests

├── linter.test.ts # v0.3.0 rules & fixture verification tests

├── reporter.test.ts # SARIF 2.1.0 schema & reporter tests

├── runner.test.ts # Backoff, lock monitor, executor tests

└── wrapper.test.ts # ORM layout detection, pre-flight abort tests

npm test✔ 132 tests passed across 31 suites (0 failures)

✔ analyzes 100 migrations in under 15 milliseconds

MIT © x7sss