Migrations
Forward-only SQL files per feature, applied and tracked by the norns CLI.
bun run migrate create notes/add_pinned # scaffold migrations/notes/<timestamp>_add_pinned.sql
bun run migrate up # apply pending migrations
bun run migrate status # list applied and pending("migrate": "norns migrate" in package.json.)
#Layout
<project>/migrations/<feature>/<timestamp>_<slug>.sqlMigrations live at the project root, outside src/, one folder per feature. They are operational artefacts read by tooling, not application code, and keeping them out of src/lib keeps the bundler away from them. Files apply in file-name order across features, so the timestamp prefix gives a global chronology; the feature name breaks ties.
create does not check that the feature exists, so nested layouts such as src/lib/<group>/<feature>/ work too. A typo simply produces an orphan folder under migrations/ that is easy to spot.
#Tracking
Applied migrations are recorded in a norns_migrations table (id TEXT PRIMARY KEY, applied_at INTEGER). The id is <feature>/<file-without-.sql>. Each migration runs in a transaction together with its tracking insert, so a failing file leaves nothing half-applied.
#Target database
The CLI reads DATABASE_URL and supports SQLite only in v1: file:<path>, defaulting to file:./data/app.db. Any other scheme fails with a message pointing at the alternatives.
The SQLite backend is chosen at runtime: bun:sqlite under Bun (no native build, works on Alpine) and better-sqlite3 under Node, where the consumer app must have it installed.
DATABASE_URL=file:./data/notes.db bun run migrate up#Cloudflare D1
norns migrate does not talk to D1. Point wrangler at the same folder and use its migration commands; see Cloudflare D1. Postgres and libSQL through the CLI are planned.