git clone https://github.com/Intense-Visions/harness-engineering
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/codex/drizzle-transactions" ~/.claude/skills/intense-visions-harness-engineering-drizzle-transactions-f6362b && rm -rf "$T"
agents/skills/codex/drizzle-transactions/SKILL.mdDrizzle Transactions
Execute atomic Drizzle operations with db.transaction(), nested transactions, and rollback semantics
When to Use
- Performing multiple database operations that must succeed or fail atomically
- Implementing business logic with read-then-write patterns
- Ensuring consistency when modifying related records
- Using savepoints for partial rollback within a larger transaction
Instructions
- Basic transaction — pass an async callback:
const result = await db.transaction(async (tx) => { const [sender] = await tx .update(accounts) .set({ balance: sql`${accounts.balance} - ${amount}` }) .where(eq(accounts.id, senderId)) .returning(); if (sender.balance < 0) { tx.rollback(); // Rolls back the entire transaction } const [receiver] = await tx .update(accounts) .set({ balance: sql`${accounts.balance} + ${amount}` }) .where(eq(accounts.id, receiverId)) .returning(); return { sender, receiver }; });
-
Use
for all queries inside the callback. Usingtx
instead ofdb
runs queries outside the transaction.tx -
Rollback — call
or throw an error:tx.rollback()
await db.transaction(async (tx) => { await tx.insert(orders).values({ userId, total }); const inventory = await tx.query.products.findFirst({ where: eq(products.id, productId), }); if (!inventory || inventory.stock < quantity) { throw new Error('Insufficient stock'); // Triggers rollback } await tx .update(products) .set({ stock: sql`${products.stock} - ${quantity}` }) .where(eq(products.id, productId)); });
- Nested transactions (savepoints):
await db.transaction(async (tx) => { await tx.insert(orders).values(orderData); try { await tx.transaction(async (tx2) => { await tx2.insert(notifications).values(notifData); // If this fails, only the inner savepoint rolls back }); } catch { // Order still committed; notification failed silently } });
- Transaction configuration (PostgreSQL):
await db.transaction( async (tx) => { // ... }, { isolationLevel: 'serializable', accessMode: 'read write', deferrable: true, } );
- Batch operations in a transaction:
await db.transaction(async (tx) => { for (const item of items) { await tx .update(inventory) .set({ stock: sql`${inventory.stock} - ${item.qty}` }) .where(eq(inventory.sku, item.sku)); } });
Details
Drizzle transactions use the underlying database driver's transaction support. The
tx object is a transaction-scoped Drizzle client with the same query API as db.
: This throws a special error that Drizzle catches to trigger rollback. Do not catch this error inside the transaction callback. The transaction function returns tx.rollback()
never after tx.rollback().
Nested transactions use savepoints. On PostgreSQL and MySQL, nested
tx.transaction() calls translate to SAVEPOINT / ROLLBACK TO SAVEPOINT. SQLite does not support savepoints in all drivers.
Isolation levels (PostgreSQL):
— rarely used, same asread uncommitted
on PostgreSQLread committed
(default) — each statement sees committed data as of statement startread committed
— all statements see committed data as of transaction startrepeatable read
— full serializability, detects and aborts conflicting transactionsserializable
Connection handling: A transaction holds one database connection for its entire duration. Keep transactions short to avoid connection pool exhaustion. Avoid external HTTP calls, file I/O, or long computations inside transactions.
Trade-offs:
uses exceptions for control flow, which can be surprising — do not wrap it in try/catch inside the transactiontx.rollback()- Serializable isolation prevents anomalies but may abort transactions under contention — implement retry logic
- Nested transactions (savepoints) add complexity — use them sparingly for optional side effects
Source
https://orm.drizzle.team/docs/transactions
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.