Files
SnapOtter/apps/api/src/db/migrate.ts
T
Siddharth Kumar Sah ce51065243 fix: handle migration race condition in concurrent test workers
Drizzle's migrate() throws when multiple vitest workers race to apply
migrations on the same temp database. The DrizzleError wraps a
SqliteError ("table already exists") in its cause chain. Add a
same-process guard and a catch that checks both the outer message and
cause for "already exists" so the second worker continues safely.
2026-03-28 11:45:54 +08:00

43 lines
1.4 KiB
TypeScript

import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
import { db } from "./index.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Resolve migrations folder relative to this file, not the working directory
const migrationsFolder = join(__dirname, "../../drizzle");
function isAlreadyExistsError(err: unknown): boolean {
if (err instanceof Error) {
if (err.message.includes("already exists")) return true;
// DrizzleError wraps the real SqliteError in .cause
if ("cause" in err && err.cause instanceof Error) {
return err.cause.message.includes("already exists");
}
}
return false;
}
let migrated = false;
export function runMigrations() {
if (migrated) return;
try {
migrate(db, { migrationsFolder });
} catch (err: unknown) {
// In test / multi-process environments, concurrent workers may race to
// apply migrations on the same database file. If a table already exists,
// the schema is in place and we can safely continue.
// Drizzle wraps the SqliteError in a DrizzleError, so check both the
// outer message and the cause chain.
if (isAlreadyExistsError(err)) {
// Tables created by another process — DB is ready
} else {
throw err;
}
}
migrated = true;
}