fix(polls): canonicalize transaction lock order

This commit is contained in:
Simo committed 2026-09-02 19:09:50 +02:00
1 parent e9680775f9
commit de7cc42ddf
4 files changed
+275 -38

No files matched your search

@@ -101,6 +101,28 @@ describe("Content production mutation adapter", () => {
);
});
it("uses read committed isolation only for poll mutations", async () => {
const pollDeps = dependencies();
await pollDeps.adapter.execute(
"poll.change",
{ action: "delete", id: 7 },
mutationContext,
);
expect(pollDeps.transaction).toHaveBeenCalledWith(expect.any(Function), {
isolationLevel: "read committed",
});
const articleDeps = dependencies();
await articleDeps.adapter.execute(
"article.change",
{ action: "update", id: 7 },
mutationContext,
);
expect(articleDeps.transaction).toHaveBeenCalledWith(
expect.any(Function),
undefined,
);
});
it("persists correlated intent and truthful outcome around external storage writes", async () => {
const deps = dependencies();
await deps.adapter.execute(
@@ -46,9 +46,14 @@ export const CONTENT_MIXED_OPERATIONS = [
type TransactionToken = unknown;
interface ContentTransactionOptions {
isolationLevel: "read committed";
}
export interface ContentProductionMutationDependencies {
transaction<T>(
run: (transaction: TransactionToken) => Promise<T>,
options?: ContentTransactionOptions,
): Promise<T>;
writeAudit(entry: AuditEntry, transaction?: TransactionToken): Promise<void>;
executeOperation(
@@ -126,6 +131,10 @@ export function createContentProductionMutationAdapter(
return {
async execute(operation, input, context) {
if (includesOperation(CONTENT_DATABASE_OPERATIONS, operation)) {
const transactionOptions =
operation === "poll.change"
? ({ isolationLevel: "read committed" } as const)
: undefined;
return dependencies.transaction(async (transaction) => {
const snapshot = await dependencies.executeOperation(
operation,
@@ -138,7 +147,7 @@ export function createContentProductionMutationAdapter(
transaction,
);
return snapshot;
});
}, transactionOptions);
}
await dependencies.writeAudit(auditEntry(operation, context, "intent"));
@@ -192,9 +201,9 @@ export function createContentProductionMutationAdapter(
export const contentProductionMutationAdapter =
createContentProductionMutationAdapter({
async transaction(run) {
async transaction(run, options) {
const { db } = await import("@/lib/db");
return db.transaction((transaction) => run(transaction));
return db.transaction((transaction) => run(transaction), options);
},
async writeAudit(entry, transaction) {
const { logAudit } = await import("@/lib/services/audit");