/** * Helper transaksi Serializable + retry. Single source of truth supaya semua * operasi yang rawan race (join/cancel trip, refund, payment) konsisten: * - Isolation level Serializable → Postgres SSI mendeteksi konflik. * - Retry otomatis saat serialization failure (Prisma error code `P2034`). */ import { Prisma } from "@/app/generated/prisma/client"; import { prisma } from "@/lib/prisma"; const SERIAL_TX_ATTEMPTS = 6; export function isSerializationConflict(err: unknown): boolean { return ( typeof err === "object" && err !== null && "code" in err && (err as { code: string }).code === "P2034" ); } /** * Jalankan `fn` dalam transaksi Serializable. Kalau gagal karena konflik * serialisasi, ulang sampai `SERIAL_TX_ATTEMPTS` kali. Error bisnis (non-P2034) * langsung dilempar tanpa retry. */ export async function runSerializable( fn: (tx: Prisma.TransactionClient) => Promise, fallbackMessage = "Operasi sedang ramai. Coba lagi sebentar." ): Promise { let lastErr: unknown; for (let attempt = 0; attempt < SERIAL_TX_ATTEMPTS; attempt++) { try { return await prisma.$transaction(fn, { isolationLevel: Prisma.TransactionIsolationLevel.Serializable, maxWait: 5000, timeout: 15000, }); } catch (e) { lastErr = e; if (isSerializationConflict(e) && attempt < SERIAL_TX_ATTEMPTS - 1) { continue; } throw e; } } throw lastErr instanceof Error ? lastErr : new Error(fallbackMessage); }