refactor contract handling to support single route per contract and improve reference generation logic

This commit is contained in:
Marshal
2026-07-06 08:36:11 +00:00
parent 544cd4620c
commit 1b92c57e23
15 changed files with 179 additions and 230 deletions

View File

@@ -20,3 +20,6 @@ export * from "./repositories/base.repository";
// Services
export * from "./services/exchange";
// Utils
export * from "./utils/reference-sequence";

View File

@@ -0,0 +1,50 @@
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;
}