drizzle-kit generate has been broken since migration 0008 (stale snapshots). The actual workflow is hand-written SQL + manual _journal.json registration. Updated CLAUDE.md and database-migrations.md to reflect reality and prevent future migrations from silently failing to apply.
81 lines
3.6 KiB
Markdown
81 lines
3.6 KiB
Markdown
# Database Migrations
|
|
|
|
This project uses [drizzle-orm](https://orm.drizzle.team/) for database schema management with **hand-written SQL migrations**.
|
|
|
|
## Overview
|
|
|
|
- **Schema definition:** `apps/server/db/schema.ts` (drizzle-orm table definitions)
|
|
- **Migration output:** `apps/server/drizzle/` directory (SQL files + `meta/_journal.json`)
|
|
- **Config:** `drizzle.config.ts`
|
|
- **Runtime migrator:** `apps/server/db/ensure-schema.ts` (calls `drizzle-orm/better-sqlite3/migrator`)
|
|
|
|
## How It Works
|
|
|
|
On every server startup, `ensureSchema(db)` runs all pending migrations from the `apps/server/drizzle/` folder. Drizzle tracks applied migrations in a `__drizzle_migrations` table so only new migrations are applied. This is safe to call repeatedly.
|
|
|
|
The migrator discovers migrations via `apps/server/drizzle/meta/_journal.json` — **not** by scanning the filesystem. A migration SQL file that isn't registered in the journal will never be applied.
|
|
|
|
## Workflow
|
|
|
|
### Making schema changes
|
|
|
|
**Do NOT use `drizzle-kit generate`** — the snapshots in `meta/` have been stale since migration 0008 and `drizzle-kit generate` will produce incorrect interactive prompts. All migrations since 0008 are hand-written.
|
|
|
|
1. Edit `apps/server/db/schema.ts` with your table/column changes
|
|
2. Create a new SQL migration file: `apps/server/drizzle/NNNN_descriptive_name.sql`
|
|
- Number it sequentially (check the last migration number)
|
|
- Write the SQL (ALTER TABLE, CREATE INDEX, etc.)
|
|
3. **Register it in the journal**: edit `apps/server/drizzle/meta/_journal.json`
|
|
- Add a new entry at the end of the `entries` array:
|
|
```json
|
|
{
|
|
"idx": <next_number>,
|
|
"version": "6",
|
|
"when": <unix_timestamp_ms>,
|
|
"tag": "NNNN_descriptive_name",
|
|
"breakpoints": true
|
|
}
|
|
```
|
|
- `idx`: sequential (previous entry's idx + 1)
|
|
- `tag`: migration filename **without `.sql` extension**
|
|
- `when`: any timestamp in milliseconds (e.g., previous + 86400000)
|
|
4. Commit **both** the SQL file and the updated `_journal.json` together
|
|
5. Run `npm run build && npm link` to pick up the changes
|
|
|
|
### Example
|
|
|
|
Adding a column to an existing table:
|
|
|
|
```sql
|
|
-- apps/server/drizzle/0032_add_comment_threading.sql
|
|
ALTER TABLE review_comments ADD COLUMN parent_comment_id TEXT REFERENCES review_comments(id) ON DELETE CASCADE;
|
|
CREATE INDEX review_comments_parent_id_idx ON review_comments(parent_comment_id);
|
|
```
|
|
|
|
```json
|
|
// In meta/_journal.json entries array:
|
|
{
|
|
"idx": 32,
|
|
"version": "6",
|
|
"when": 1772323200000,
|
|
"tag": "0032_add_comment_threading",
|
|
"breakpoints": true
|
|
}
|
|
```
|
|
|
|
### Applying migrations
|
|
|
|
Migrations are applied automatically on server startup. No manual step needed.
|
|
|
|
For tests, the same `ensureSchema()` function is called on in-memory SQLite databases in `apps/server/db/repositories/drizzle/test-helpers.ts`.
|
|
|
|
## Rules
|
|
|
|
- **Always hand-write migration SQL.** Do not use `drizzle-kit generate` (stale snapshots).
|
|
- **Always register in `_journal.json`.** The migrator reads the journal, not the filesystem.
|
|
- **Commit SQL + journal together.** A migration file without a journal entry is invisible to the migrator.
|
|
- **Never use raw CREATE TABLE statements** for schema initialization. The migration system handles this.
|
|
- **Migration files are immutable.** Once committed, never edit them. Make a new migration instead.
|
|
- **Test with `npm test`** after creating migrations to verify they work with in-memory databases.
|
|
- **Keep schema.ts in sync.** The schema file is the source of truth for TypeScript types; migrations are the source of truth for database DDL. Both must reflect the same structure.
|