Skip to content

Commit 53f7970

Browse files
committed
feat(payments): add unique violation detection in createPaymentIdempotent function
1 parent 4878d92 commit 53f7970

1 file changed

Lines changed: 12 additions & 4 deletions

File tree

src/lib/payments.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,15 @@ export const isValidIdempotencyKey = (k: unknown): k is string =>
130130

131131
export type Payment = typeof payments.$inferSelect
132132

133+
/** Detecta violación de UNIQUE recorriendo la cadena de causas del error. */
134+
function isUniqueViolation(e: unknown): boolean {
135+
for (let err = e, depth = 0; err && depth < 5; err = (err as { cause?: unknown }).cause, depth++) {
136+
const { message, code } = err as { message?: string; code?: string }
137+
if (/unique|constraint/i.test(message ?? '') || /CONSTRAINT/i.test(code ?? '')) return true
138+
}
139+
return false
140+
}
141+
133142
/**
134143
* Crea el pago o devuelve el existente si la clave de idempotencia ya se usó.
135144
* A prueba de carreras: si dos requests simultáneos pasan el SELECT previo,
@@ -161,10 +170,9 @@ export async function createPaymentIdempotent(
161170
return { payment: row, replayed: false }
162171
} catch (e) {
163172
// Carrera perdida: otro request insertó la misma clave entre el SELECT y el INSERT.
164-
// libsql reporta el UNIQUE en e.code (SQLITE_CONSTRAINT_UNIQUE), no siempre en el mensaje.
165-
const msg = e instanceof Error ? e.message : ''
166-
const code = (e as { code?: string })?.code ?? ''
167-
if (/unique|constraint/i.test(msg) || /CONSTRAINT/i.test(code)) {
173+
// Drizzle envuelve el error de libsql (código SQLITE_CONSTRAINT_UNIQUE) en
174+
// DrizzleQueryError con la causa original en `cause`: hay que mirar toda la cadena.
175+
if (isUniqueViolation(e)) {
168176
const [row] = await db.select().from(payments).where(eq(payments.idempotencyKey, input.idempotencyKey))
169177
if (row) return { payment: row, replayed: true }
170178
}

0 commit comments

Comments
 (0)