fix(labs): synchronous confirm transaction — async callback broke atomicity

bun-sqlite's Database.transaction is synchronous, but async callbacks return
a pending Promise at the first await, causing immediate COMMIT before the
entire callback completes. This allowed partial inserts with no rollback.

Fixed by:
- Remove async from transaction callback
- Add .run() to each insert/update to execute synchronously
- Add regression test proving atomicity: transaction that throws mid-loop
  rolls back all changes (both tables empty after failure)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
marcuspaico
2026-08-17 15:52:59 -07:00
parent 038c92af45
commit 96692163f1
2 changed files with 26 additions and 7 deletions

View File

@@ -87,19 +87,19 @@ export function labsRoutes(deps: LabsDeps) {
panel: m.panel, name: m.name, value: m.value, unit: m.unit,
referenceRange: m.referenceRange, flagged: m.flagged,
}));
await deps.db.transaction(async (tx) => {
await tx.insert(labDraws).values({
deps.db.transaction((tx) => {
tx.insert(labDraws).values({
id: drawId, collectedAt: body.data.collectedDate, labName: body.data.labName,
draftId: row.id, createdAt: Date.now(),
});
}).run();
for (const n of norm) {
await tx.insert(biomarkers).values({
tx.insert(biomarkers).values({
drawId, panel: n.panel, name: n.name, marker: n.marker, analyteKey: n.analyteKey,
value: n.value, valueNum: n.valueNum, unit: n.unit, referenceRange: n.referenceRange,
flagged: n.flagged ? 1 : 0, valueCanonical: n.valueCanonical, canonicalUnit: n.canonicalUnit,
});
}).run();
}
await tx.update(labDrafts).set({ status: "confirmed" }).where(eq(labDrafts.id, row.id));
tx.update(labDrafts).set({ status: "confirmed" }).where(eq(labDrafts.id, row.id)).run();
});
return c.json({ drawId }, 201);
});