From f7f90a71fc3186ef46054e75925552bf7fbf857f Mon Sep 17 00:00:00 2001 From: Flea Flicker <22+gb_flea@noreply.git.farh.net> Date: Thu, 6 Aug 2026 09:14:55 +0000 Subject: [PATCH] fix(GRO-2672): use drizzle-kit migrate in reset.ts to bypass HWM bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit drizzle-orm's migrate() has a high-water-mark (HWM) bug: on a fresh DB, migration 0000 sets the watermark to 2026-03-17. Migrations 0001, 0003, 0010, 0011 have stale 2025-era `when` timestamps and are silently skipped. Migration 0003 (recurring_series) is the blocker — its skip leaves `recurring_series`, `appointments.series_id`, and `appointments.series_index` missing. A downstream migration inside migrate()'s single Postgres transaction then fails, rolling back everything including 0000's `staff` and `services` tables. Replace the drizzle-orm migrate() call with `pnpm exec drizzle-kit migrate` (hash-based). drizzle-kit applies every unhashed migration regardless of `when` ordering, matching the K8s migrate Job exactly. --- packages/db/src/reset.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/db/src/reset.ts b/packages/db/src/reset.ts index fb88e20..7f12970 100644 --- a/packages/db/src/reset.ts +++ b/packages/db/src/reset.ts @@ -32,7 +32,7 @@ */ import postgres from "postgres"; import { drizzle } from "drizzle-orm/postgres-js"; -import { migrate } from "drizzle-orm/postgres-js/migrator"; +import { execSync } from "node:child_process"; import { fileURLToPath } from "node:url"; import { dirname, resolve } from "node:path"; import * as schema from "./schema.js"; @@ -46,7 +46,6 @@ import { const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); -const MIGRATIONS_FOLDER = resolve(__dirname, "../migrations"); async function reset() { const url = process.env.DATABASE_URL; @@ -73,7 +72,7 @@ async function reset() { // across processes regardless of how many connections the work uses — // it does NOT require the work to share the lock's session. // - // Therefore `max` must be ≥ 2: 1 reserved for the lock + ≥1 free for + // Therefore `max` must be >= 2: 1 reserved for the lock + >=1 free for // the work. `max: 1` would let `reserve()` consume the only connection // and every query inside the callback would block forever waiting for // a connection that never frees (connection-starvation deadlock). We @@ -122,7 +121,15 @@ async function reset() { console.log("✓ All tables and enums dropped\n"); console.log("Running migrations..."); - await migrate(db, { migrationsFolder: MIGRATIONS_FOLDER }); + // GRO-2672: drizzle-orm's migrate() has a high-water-mark bug that skips + // migrations with stale `when` timestamps (0001, 0003, 0010, 0011). Use + // drizzle-kit instead -- it applies migrations by hash, matching the K8s + // migrate Job behaviour exactly. + execSync("pnpm exec drizzle-kit migrate", { + stdio: "inherit", + env: { ...process.env }, + cwd: resolve(__dirname, ".."), + }); console.log("✓ Migrations applied\n"); console.log("Seeding database...");