import { eq, sql } from 'drizzle-orm'; import type { PgTable, PgColumn } from 'drizzle-orm/pg-core'; import { db } from './index'; /** * Drizzle transaction client type — the argument shape `db.transaction`'s * callback receives. Exported so service helpers that take a `tx` * parameter can spell the type instead of falling back to `any`. */ export type Tx = Parameters[0]>[0]; /** * Wraps a database operation in a transaction. * Rolls back automatically on error. * * @example * const result = await withTransaction(async (tx) => { * await tx.insert(clients).values({ ... }); * await tx.insert(interests).values({ ... }); * return result; * }); */ export async function withTransaction(callback: (tx: typeof db) => Promise): Promise { return db.transaction(callback as unknown as Parameters[0]) as Promise; } /** * Soft-deletes a record by setting `archivedAt` to now. * The table must have an `archivedAt` JS property mapping to the * `archived_at` column. * * @example * await softDelete(clients, clients.id, clientId); */ export async function softDelete( table: TTable, idColumn: PgColumn, id: string, ): Promise { // Drizzle's `.set()` API expects the JS property name (`archivedAt`), // not the snake-case column name. The previous version passed // `{ archived_at: ... }` which silently produced an empty SET clause // (Drizzle skipped the unknown property) → `UPDATE ... where ...` // syntax error from Postgres. Caught by QA: archive an interest // with a berth attached → 500. Same fix in restore() below. await db .update(table) .set({ archivedAt: sql`now()` } as Record) .where(eq(idColumn, id)); } /** * Restores a soft-deleted record by clearing `archivedAt`. * The table must have an `archivedAt` JS property mapping to the * `archived_at` column. * * @example * await restore(clients, clients.id, clientId); */ export async function restore( table: TTable, idColumn: PgColumn, id: string, ): Promise { await db .update(table) .set({ archivedAt: null } as Record) .where(eq(idColumn, id)); }