Skip to main content

Transactions

Business writes that were seen calling prisma.$transaction include order command, marketplace ingestion, stock command, fulfillment workflow, returns, goods receipt, stock transfer, stock count, packing manifest, NDR, shipments, wallet ledger, settlement loader, and the outbox claim.

Rules that follow from that​

WriteAtomic with
Aggregate plus outboxWriter.enqueue(tx, ...)The outbox row
Aggregate plus eventEmitter.emitNothing. The emit is after or outside the transaction semantics of the database
Courier ship() and the local shipment rowNot the same transaction
Bull queue.add and the business rowOnly if you enqueue after commit, or accept a job for a rolled-back row if you enqueue inside the transaction before commit

Enqueue Bull work after commit unless you have a reconciliation path. The outbox exists so side effects can commit with the row and run later.

Isolation​

The isolation level passed to $transaction was not surveyed. Prisma uses the database default unless an option is set.

Outbox claim​

The dispatcher claims with FOR UPDATE SKIP LOCKED. A PROCESSING lock older than 5 minutes returns to PENDING without incrementing attempts. Two workers can run the dispatcher only if you run two worker processes. Whether production runs one worker replica is unknown.

Idempotency tools inside a transaction​

IdempotentEventHandlerService wraps a consumer in a transaction, locks the order when orderId is set, and inserts ProcessedEvent. It is opt-in per listener.

StockMovement uniqueness includes createdAt, so it does not by itself reject a second movement with the same idempotency key and a new timestamp.

Optimistic version columns were not surveyed across all models.