Skip to content

Postgres · MySQL · SQLite · SQL Server

A database change manager
for the modern era

Your schema lives in db/sql/, one file per table, as it stands today. A fresh database builds straight from those files. An existing one catches up through tracked changes you can revert.

Get started →Download

Write SQL. Skip the ORM. · one binary · Apache 2.0

✕ migrations/what changed
0001_create_party.sql
0002_create_sales_order.sql
0003_add_email_to_party.sql
0004_create_order_line.sql
…
0212_rename_total_to_amount.sql
0213_add_status_to_sales_order.sql
0214_backfill_sales_order_status.sql

To learn what sales_order looks like, read all 214.

✓ db/sql/01_tables/02_sales_order.sqlwhat exists
sql
CREATE TABLE sales_order (
    party_id   integer     NOT NULL,
    order_no   integer     NOT NULL,
    placed_at  timestamptz NOT NULL DEFAULT now(),
    status     text        NOT NULL DEFAULT 'open',
    CONSTRAINT sales_order_pk
        PRIMARY KEY (party_id, order_no),
    CONSTRAINT sales_order_isplacedby_party
        FOREIGN KEY (party_id) REFERENCES party (party_id)
);

What changes

The problems you stop having

A test database replays the whole migration history before the first test runs.

It builds from the current files in one command.

noorm run build

A stored procedure was hotfixed in a SQL editor, and nobody can say which version is live.

Procedures, views, and functions re-run through a change. History records who ran it, when, and the error if it failed.

noorm change history

A password or API key the database needs sits in a SQL file in git.

Store it encrypted with noorm. The template fills it in when the SQL runs, so the committed file holds only the reference.

{%~ $.quote($.secrets.GATEWAY_KEY) %}

Your tests mock the database because a real one is a chore to set up.

The SDK runs tests against a real database, and refuses any config not marked as a test one.

createContext({ requireTest: true })

You handed an AI agent a connection string and hope it reads before it writes.

Each config sets a role per caller: read-only for the agent on dev, invisible on prod.

noorm mcp serve

How it works

Two folders, two commands

Edit the table in db/sql/. Add a change for databases that already exist. Every environment ends up in the same state.

bash
# a new test database
$ noorm run build
• log  Build completed  status=success filesRun=11 filesSkipped=0 filesFailed=0 durationMs=96.417
bash
# staging, which already has data
$ noorm change ff -c staging
• log  Fast-forward success  executed=1 skipped=0 failed=0
• log    2026-10-20-order-status (success)

ignatius · data modeling

Agree on the data model
before anyone writes the SQL

ignatius turns a folder of markdown into an IDEF1X entity diagram, a searchable data dictionary, and data flow diagrams. It reads no database and writes no SQL. The model lives in your repo and changes in the same pull request as the schema.

DownloadExplore ignatius

one binary · works with or without noorm

Where your data comes from, and where it goes

A data flow diagram shows who sends data, including people outside the technical system, which process moves or transforms it, and which store keeps it. Most teams skip it and draw logic flowcharts, which show decisions, not data. ignatius draws data flow diagrams in the SSADM style.

logic flowchartwhat teams draw

What the code decides. No table, log file, or person appears in it.

data flow diagram · order to cashrendered by ignatius

Every place an order's data enters, rests, and leaves, including the gateway log that no table holds.

model/data/sales/sales_order.mdyou write this
yaml
---
entity: sales_order
group: sales
pk: [party_id, order_no]
columns:
  party_id:  { type: integer }
  order_no:  { type: integer }
  placed_at: { type: datetime, default: now }
  status:    { type: text, default: open }
examples:
  - { party_id: 2, order_no: 1, status: invoiced }
  - { party_id: 2, order_no: 2, status: open }
  - { party_id: 1, order_no: 1, status: paid }
relationships:
  - target: party
    on: { party_id: party_id }
    predicate:
      fwd: places
      rev: is placed by
---
ignatius draws this

Cardinality and identifying edges come from the keys, and subtype clusters from the subtypes: block. Nothing is drawn by hand, and saving a file reloads the open browser.

The ER diagram in your wiki stopped matching the database months ago.

The diagram renders from markdown in the repo, so it changes in the same pull request as the schema.

ignatius serve model/ -o

Nobody remembers why a table exists.

Each entity carries its purpose and example rows beside its keys, searchable in the dictionary.

examples:

The person who signs off on the design can't read DDL.

Export the whole model to one HTML file. They open it from their desktop, with nothing installed.

ignatius export -o model.html

You inherited a database nobody documented.

An agent reads it through noorm's read-only MCP server, and the ignatius-modeling skill writes the model.

/ignatius-modeling
bash
# check the model in CI
$ ignatius validate model/
✓ /home/you/shop/model: valid — 11 entities, no findings.
text
# model with an agent, in Claude Code
> /ignatius-modeling discover
  interviews you about how the business runs,
  then writes the entities and flows

FAQ

Questions developers ask first

Is this migrations with extra steps?

Your migrations become db/changes/: forward and revert files, tracked and checksummed. db/sql/ sits beside them as the current schema, so a fresh database builds from it without replaying history.

Does it replace my ORM?

It replaces the ORM's schema and migration layer. Query with the typed SDK (Kysely underneath), raw SQL, or the client you already use.

Which databases?

PostgreSQL, MySQL, SQLite, and SQL Server. The CLI is one binary with every driver built in.

What about stored procedures?

Call them from TypeScript with typed parameters and result rows, table-valued parameters included.

Do I need both tools?

No. ignatius reads markdown and never connects to a database, so it works with any stack. noorm runs your SQL whether or not a model exists.

Does ignatius generate the SQL?

No. It shows what you intend to build. Write the SQL yourself or hand the model to your agent; From model to SQL shows how each field maps.

If you write SQL by hand and ship it to more than one environment, start here.

bash
# noorm: build and change the database
curl -fsSL https://noorm.dev/install.sh | sh

# ignatius: model it first
curl -fsSL https://noorm.dev/ignatius.sh | sh