mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
refactor contract handling to support single route per contract and improve reference generation logic
This commit is contained in:
@@ -20,3 +20,6 @@ export * from "./repositories/base.repository";
|
||||
|
||||
// Services
|
||||
export * from "./services/exchange";
|
||||
|
||||
// Utils
|
||||
export * from "./utils/reference-sequence";
|
||||
|
||||
50
packages/api-common/src/utils/reference-sequence.ts
Normal file
50
packages/api-common/src/utils/reference-sequence.ts
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user