Postgres · MySQL · SQLite · SQL Server
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.
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.
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
A test database replays the whole migration history before the first test runs.
It builds from the current files in one command.
noorm run buildA 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 historyA 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 serveHow it works
Edit the table in db/sql/. Add a change for databases that already exist. Every environment ends up in the same state.
fresh database db/sql/ ── noorm run build ──▶ current existing database db/changes/ ── noorm change ff ──▶ current
# a new test database
$ noorm run build
• log Build completed status=success filesRun=11 filesSkipped=0 filesFailed=0 durationMs=96.417# 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
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.
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.
┌─────────────┐
│ charge card │
└──────┬──────┘
│
approved?
┌───┴───┐
yes│ │no
┌──────▼───┐ ┌─▼──────────┐
│ mark paid│ │ show error │
└──────────┘ └────────────┘What the code decides. No table, log file, or person appears in it.
---
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
---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/ -oNobody 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.htmlYou 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# check the model in CI
$ ignatius validate model/
✓ /home/you/shop/model: valid — 11 entities, no findings.# model with an agent, in Claude Code
> /ignatius-modeling discover
interviews you about how the business runs,
then writes the entities and flowsFAQ
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.
It replaces the ORM's schema and migration layer. Query with the typed SDK (Kysely underneath), raw SQL, or the client you already use.
PostgreSQL, MySQL, SQLite, and SQL Server. The CLI is one binary with every driver built in.
Call them from TypeScript with typed parameters and result rows, table-valued parameters included.
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.
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.
# 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