Migrations
The schema is reconciled with the live store on every DO boot, inside a storage
transaction. There is no separate migration step — you edit the schema and redeploy.
A schema hash in _pramen_meta skips the work entirely when nothing changed.
Two passes
Additive (no data loss):
- a new table →
CREATE TABLE - a new column →
ALTER TABLE ADD COLUMN(nullable; SQLite can't addNOT NULLto a populated table)
Destructive (auto-applied, can lose data — by design, since pramen is WIP with no backward-compat constraints):
- a column the schema no longer declares is dropped
- a column whose type changed is rebuilt (with a
CAST) - a table absent from the schema is dropped
Drops and type changes use the standard SQLite table-rebuild (create a new table, copy the data, drop the old, rename) — preserving rows and ids.
Renames
A rename can't be inferred from a diff: a removed column plus an added column is
ambiguous. Declare it explicitly with renamedFrom, and the migrator copies the old
column's data during the rebuild:
import { Entity, renamedFrom } from "pramen/sdk/schema";
notes: Entity((t) => ({
id: t.id(),
title: t.text(),
content: renamedFrom(t.text(), "body"), // column `body` -> `content`, data preserved
}));
Without the hint, the change is applied as drop + add and the old column's data is lost.
Inspecting changes
The CLI's schema diff compares your schema against a snapshot baseline and flags
each change as additive or destructive:
bun run pramen schema snapshot # baseline in .pramen/schema.json
bun run pramen schema diff # additive vs destructive (all auto-applied on boot)
bun run pramen schema status --tenant acme # is a deployed tenant caught up?