declarative-migrations / dpm

The database is the schema. Diff it like one.

dpm is declarative, ORM-agnostic Postgres schema migration in Rust: introspect two states, diff the catalogs, emit reviewable SQL that converges one onto the other — then prove it converged, and let seven independent tools countersign the proof.

Install

# curl — prebuilt binary from the latest release (cargo fallback)
curl -fsSL https://raw.githubusercontent.com/declarative-migrations/declarative-postgres-migrate.rs/main/scripts/install.sh | bash

# Homebrew
brew install declarative-migrations/tap/dpm

# From source
cargo install --git https://github.com/declarative-migrations/declarative-postgres-migrate.rs

One idea, taken seriously

The Postgres system catalogs are the neutral interchange format. Whether a schema was authored by Prisma, Drizzle, SeaORM, ent, peewee, or raw SQL — once it exists, pg_catalog describes it canonically. dpm deparses both sides with the server itself (pg_get_constraintdef, pg_get_indexdef, format_type, … under search_path = ''), so comparison is exact string equality. No regex reconciliation of hand-written SQL against Postgres' re-serialized forms.

dpm diff   --source postgres://…/desired  --target postgres://…/live
dpm verify --source schema.sql --target "$DATABASE_URL" --shadow "$SHADOW_DATABASE_URL" --cross-check-all
dpm apply  --yes   # interactive confirm without --yes; destructive ops need explicit consent

Any source, any target

Live database ↔ catalog.json dump ↔ schema.sql (or a pg_dump -s dump, materialized on a shadow server). All nine pairings produce byte-identical migrations — asserted in CI.

Convergence, proven

dpm verify replays your target onto a throwaway replica, applies the migration, re-introspects, re-diffs. An empty plan is the proof. Exit codes made for CI.

Destructive = two consents

--allow-destructive-sql to generate drops (otherwise commented out), --allow-destructive-ops to execute them. Apply fails closed.

Supabase-aware

Managed schemas excluded by default, RLS + policies are first-class diffed objects, transaction-pooler connections detected and refused with a fix hint.

Seven tools countersign every migration

dpm never asks you to take its word for it. Six independent diff engines must see zero remaining difference after dpm migrates; flyway validates the script under a standard migration runner. The test matrix runs all of them against ten schema fixtures — and it has already caught real bugs (that's the point).

✓ migra✓ pgdiff✓ atlas✓ stripe/pg-schema-diff✓ liquibase✓ apgdiff✓ flyway
dpm verify … --cross-check-all            # every installed tool
dpm verify … --cross-check-with-migra --cross-check-with-atlas
dpm verify … --cross-check-with-ai        # AI discrepancy scan over all reports

The AI layer reviews migrations too — dpm review drives the Anthropic / OpenAI / Gemini HTTP APIs (or the claude / codex / gemini CLIs) with a fail-closed machine verdict: no parseable DPM_VERDICT, no approval.

ORM-agnostic by construction

You usedpm source
Drizzledrizzle-kit export → schema.sql
Prismaprisma migrate diff --from-empty --to-schema-datamodel --script
SeaORM / sqlx / ent / Django / Railsmigrations → scratch db → --source postgres://scratch
Raw SQLthe file is the source

“Diff my Drizzle app against the SeaORM service's database” is one command. dpm never parses ORM code.

How verify works

  1. 01Everything is reviewable SQL — dpm never executes without explicit consent.
  2. 02Constraint adds on existing tables use NOT VALID + VALIDATE; enum values are added outside the transaction; FKs land last.
  3. 03Manual-review items (enum label removal, partition-strategy changes) are surfaced as comments, never guessed at.