Static PostgreSQL migrations → Mermaid ERD — no database, no cgo.
📖 Documentation site — including a live ERD example generated by stratum itself on every deploy.
stratum is a Go CLI that statically parses PostgreSQL migration .sql
files and generates a Mermaid ERD embedded in a
Markdown document. It opens no database connection and makes no network
calls — every fact in the diagram is derived from the migration files
themselves, so it runs fully offline (e.g. in a GitHub Actions runner).
It works by replaying your migrations in order against an in-memory schema model and rendering the result. Parsing uses a small, hand-written pure-Go SQL DDL parser (no cgo, no third-party parser) that models the subset of PostgreSQL DDL an ERD needs and defensively skips everything else.
Relationship cardinality in the ERD reflects the constraints: a foreign key
renders as mandatory (}o--||) when its columns are NOT NULL and optional
(}o--o|) when nullable, and collapses to one-to-one (|o--…) when the FK
columns are covered by a unique constraint.
CREATE TABLE— columns, inline and table-level primary keys, foreign keys (REFERENCES),UNIQUE,CHECK,NOT NULL,DEFAULT; composite and self-referential keys;serial/bigserialandGENERATED … AS IDENTITYcolumns (treated asNOT NULL);COLLATE; multi-word types (double precision,character varying(n),timestamp with time zone, arrays)ALTER TABLE— add/drop/rename column, alter column type, set/drop default, add constraint (FK / UNIQUE / CHECK), drop constraint, rename tableCREATE [UNIQUE] INDEXandDROP INDEXDROP TABLEandDROP SCHEMA- Multiple schemas (
public.users, etc.); unqualified names default topublic
DML (INSERT/UPDATE/DELETE), procedural DO $$ … $$ blocks / function
bodies, comments, and other non-structural statements are skipped (their ;
inside dollar-quotes won't break parsing). Unrecognized statements are skipped,
never fatal; the parser never panics.
go build -o stratum ./cmd/stratumPure Go — no C compiler needed.
CGO_ENABLED=0produces a fully static binary.
stratum --migrations-path db/migrations --output SCHEMA.md| Flag | Default | Description |
|---|---|---|
--migrations-path |
(required) | Path to migrations directory |
--output |
SCHEMA.md |
Output file path |
--schema |
public |
Filter output to this schema name |
--recursive |
false |
Discover .sql files recursively |
--push |
false |
git add + commit + push the output file |
--commit-message |
chore: update schema docs |
Commit message used with --push |
--check |
false |
Verify the output file is up to date; exit non-zero if it would change (writes nothing) |
--verbose |
false |
Log each parsed statement |
The tool exits non-zero if any file fails to parse. It warns (non-fatal) on unknown statement types, dangling foreign keys, and empty migration files.
Use --check as a gate so a pull request fails until its schema docs are
regenerated (the volatile timestamp line is ignored, so only schema content
counts):
stratum --migrations-path db/migrations --output SCHEMA.md --checkThis is the read-only alternative to --push: no bot commits, the author
regenerates and commits SCHEMA.md themselves.
Files are ordered by filename, supporting both numeric prefixes (001_, 002_)
and 14-digit timestamp prefixes (20240101120000_). Numeric prefixes are
compared as numbers, so 2_ sorts before 10_.
To pin an explicit order, drop a migrations.yaml in the migrations directory:
migrations:
- 003_first.sql
- 001_second.sql
- 002_third.sqlWhen present, the manifest order is used verbatim.
- uses: actions/checkout@v4
- uses: zvdy/stratum@v1
with:
migrations-path: db/migrations
output: SCHEMA.md
schema: public
push: "true"See action.yml for all inputs.
docker build -t stratum .
docker run --rm -v "$PWD:/work" -w /work stratum \
--migrations-path db/migrations --output SCHEMA.mdgo test ./...Fixtures live under testdata/.
BSD 3-Clause © zvdy
