mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 08:32:54 +00:00
51 lines
1.9 KiB
TypeScript
51 lines
1.9 KiB
TypeScript
import { QueryFailedError } from "typeorm";
|
|
|
|
/** Postgres unique-violation SQLSTATE. */
|
|
const PG_UNIQUE_VIOLATION = "23505";
|
|
|
|
/**
|
|
* True when `error` is a Postgres unique-constraint violation. Used to detect a
|
|
* reference-number collision from a concurrent insert so the caller can retry
|
|
* with a freshly-computed number instead of surfacing a 500.
|
|
*/
|
|
export function isUniqueViolation(error: unknown): boolean {
|
|
if (!(error instanceof QueryFailedError)) return false;
|
|
const driver = (
|
|
error as QueryFailedError & { driverError?: { code?: string } }
|
|
).driverError;
|
|
return driver?.code === PG_UNIQUE_VIOLATION;
|
|
}
|
|
|
|
/**
|
|
* Run `insert(reference)` under a "generate → try → retry on collision" loop.
|
|
*
|
|
* `MAX(sequence) + 1` alone is not concurrency-safe: two requests can read the
|
|
* same max and derive the same reference, and one insert then hits the unique
|
|
* index. On that collision we recompute the reference and try again, so the
|
|
* sequence advances under load instead of throwing. Non-collision errors (and
|
|
* exhausting the attempt budget) propagate unchanged.
|
|
*
|
|
* @param generate Async producer of the next reference (e.g. `CTR-2026-00033`).
|
|
* Re-invoked on each attempt so it re-reads the current max.
|
|
* @param insert Performs the insert with the given reference; its result is
|
|
* returned on success.
|
|
* @param attempts Maximum tries before giving up (default 5).
|
|
*/
|
|
export async function insertWithGeneratedReference<T>(
|
|
generate: () => Promise<string>,
|
|
insert: (reference: string) => Promise<T>,
|
|
attempts = 5,
|
|
): Promise<T> {
|
|
let lastError: unknown;
|
|
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
const reference = await generate();
|
|
try {
|
|
return await insert(reference);
|
|
} catch (error) {
|
|
if (!isUniqueViolation(error)) throw error;
|
|
lastError = error;
|
|
}
|
|
}
|
|
throw lastError;
|
|
}
|