feat: centralize application framework primitives
This commit is contained in:
@@ -63,6 +63,34 @@ export function withTransaction<T>(db: Db, callback: (tx: Db) => Promise<T>): Pr
|
||||
return db.tx(callback);
|
||||
}
|
||||
|
||||
/** Execute a parameterized compare-and-set update and report whether this caller won. */
|
||||
export async function compareAndSet(
|
||||
db: Db,
|
||||
options: {
|
||||
table: string;
|
||||
idColumn?: string;
|
||||
id: string | number;
|
||||
stateColumn?: string;
|
||||
from: string;
|
||||
to: string;
|
||||
extra?: Record<string, unknown>;
|
||||
},
|
||||
): Promise<boolean> {
|
||||
const table = identifier(options.table);
|
||||
const idColumn = identifier(options.idColumn ?? "id");
|
||||
const stateColumn = identifier(options.stateColumn ?? "status");
|
||||
const entries = Object.entries(options.extra ?? {});
|
||||
const assignments = [stateColumn, ...entries.map(([name]) => identifier(name))];
|
||||
const dialect = db.driver.dialect;
|
||||
const values = [options.to, ...entries.map(([, value]) => value), options.id, options.from];
|
||||
const sql = `UPDATE ${table} SET ${assignments
|
||||
.map((name, index) => `${name} = ${placeholder(dialect, index + 1)}`)
|
||||
.join(
|
||||
", ",
|
||||
)} WHERE ${idColumn} = ${placeholder(dialect, assignments.length + 1)} AND ${stateColumn} = ${placeholder(dialect, assignments.length + 2)}`;
|
||||
return (await db.exec(sql, values)).changes === 1;
|
||||
}
|
||||
|
||||
/** Conservative default classifier for deadlock/serialization retry errors. */
|
||||
export function isRetryableTransactionError(error: unknown): boolean {
|
||||
if (!error || typeof error !== "object") return false;
|
||||
|
||||
@@ -54,6 +54,7 @@ export {
|
||||
exists,
|
||||
countRows,
|
||||
withTransaction,
|
||||
compareAndSet,
|
||||
retryTransaction,
|
||||
batch,
|
||||
databaseHealth,
|
||||
|
||||
Reference in New Issue
Block a user