feat(FN-193): rewrite migrate.ts with hash-based runner (+4 more)
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled
Commits merged: - docs(FN-193): update migration runner docs - fix(FN-193): lint/typecheck fixes - test(FN-193): rewrite migrate tests for runMigrations - feat(FN-193): make baseline migration idempotent (IF NOT EXISTS) - feat(FN-193): rewrite migrate.ts with hash-based runner Files changed: apps/api/drizzle/0000_brief_guardian.sql | 364 ++++++++++++------------ apps/api/src/database/__tests__/migrate.spec.ts | 354 ++++++++++++----------- apps/api/src/database/migrate.ts | 173 +++++------ docs/INDEX.md | 17 +- 4 files changed, 466 insertions(+), 442 deletions(-) Fusion-Task-Id: FN-193
This commit is contained in:
@@ -356,7 +356,20 @@ catalogVehicles
|
||||
|
||||
### Migration Workflow
|
||||
|
||||
**Strategy:** Formal Drizzle SQL migrations with automated startup.
|
||||
**Strategy:** Formal Drizzle SQL migrations with a custom hash-based runner.
|
||||
|
||||
The migration runner at `apps/api/src/database/migrate.ts` uses a **custom hash-based runner** instead of Drizzle's built-in `migrate()`. It tracks applied migrations by SHA256 of SQL file content rather than by timestamp comparison.
|
||||
|
||||
**Why custom?** Drizzle's built-in `migrate()` compares `folderMillis` timestamps from `drizzle/meta/_journal.json`. When `drizzle-kit generate` produces incorrect timestamps (clock skew bug), new migrations can be silently skipped. Hash-based tracking is immune to clock skew and provides deterministic idempotency.
|
||||
|
||||
**How it works:**
|
||||
1. Reads all previously-applied migration hashes from `drizzle.__drizzle_migrations`
|
||||
2. Reads `drizzle/meta/_journal.json` for the list of migration files in index order
|
||||
3. For each migration file, computes SHA256 of the SQL content
|
||||
4. If the hash already exists in `__drizzle_migrations` → skip
|
||||
5. If the hash is new → execute all statements in a single transaction, then record the hash
|
||||
|
||||
**Idempotent baseline:** `0000_brief_guardian.sql` uses `IF NOT EXISTS` for all `CREATE TABLE`, `CREATE INDEX`, and `CREATE UNIQUE INDEX` statements. `ALTER TABLE ADD CONSTRAINT` statements are wrapped in `DO $$ BEGIN ... EXCEPTION WHEN duplicate_object ... END $$` blocks. This allows the baseline to be safely re-applied on existing databases where `__drizzle_migrations` was bootstrapped to a different hash.
|
||||
|
||||
**Schema change workflow:**
|
||||
1. Edit schema files in `apps/api/src/database/schema/`
|
||||
@@ -366,7 +379,7 @@ catalogVehicles
|
||||
|
||||
**Container startup:** The API server runs `apps/api/start.sh` which executes `node dist/database/migrate.js` BEFORE starting the NestJS server. This ensures all pending migrations are applied before any code runs.
|
||||
|
||||
**Bootstrap note:** Existing databases with no migration history (`__drizzle_migrations` table missing) are automatically bootstrapped on first run. The bootstrap creates the tracking table and marks all baseline migrations as already applied (no SQL is executed against the live DB). This is safe for databases previously managed via `pnpm db:push`.
|
||||
**Bootstrap note:** Existing databases with no migration history (`__drizzle_migrations` table empty or missing) are handled automatically. The runner creates the tracking table and applies the baseline migration idempotently (all statements use `IF NOT EXISTS` guards, so no errors on re-application). This is safe for databases previously managed via `pnpm db:push`.
|
||||
|
||||
**Manual commands:**
|
||||
- `pnpm db:migrate` — Run migrations in dev (uses `tsx` for direct TS execution)
|
||||
|
||||
Reference in New Issue
Block a user