From harness-claude
Executes atomic database operations using Prisma $transaction: sequential arrays, interactive callbacks with dependent reads/writes, nested creates/updates, and batch ops with rollback.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Execute atomic operations with Prisma $transaction, interactive transactions, and nested writes
Executes atomic Drizzle ORM database operations with db.transaction(), nested transactions via savepoints, rollbacks, and batch updates for multi-query consistency.
Provides expert guidance on Prisma ORM schema design, migrations, query optimization, relations modeling, and database operations for PostgreSQL, MySQL, SQLite.
Manages database transactions for ACID compliance, concurrency control, isolation levels, explicit locking, and deadlock prevention in PostgreSQL and MySQL.
Share bugs, ideas, or general feedback.
Execute atomic operations with Prisma $transaction, interactive transactions, and nested writes
const [order, payment] = await prisma.$transaction([
prisma.order.create({ data: { userId, total: 99.99 } }),
prisma.payment.create({ data: { userId, amount: 99.99, status: 'PENDING' } }),
]);
const transfer = await prisma.$transaction(async (tx) => {
const sender = await tx.account.update({
where: { id: senderId },
data: { balance: { decrement: amount } },
});
if (sender.balance < 0) {
throw new Error('Insufficient funds');
}
const receiver = await tx.account.update({
where: { id: receiverId },
data: { balance: { increment: amount } },
});
return { sender, receiver };
});
Throwing inside the callback rolls back the entire transaction.
await prisma.$transaction(
async (tx) => {
/* ... */
},
{ isolationLevel: Prisma.TransactionIsolationLevel.Serializable }
);
await prisma.$transaction(
async (tx) => {
/* ... */
},
{ maxWait: 5000, timeout: 10000 }
);
maxWait — maximum time to wait for a connection from the pool (ms)timeout — maximum time the transaction can run before auto-rollback (ms)create/update with nested operations is already atomic:// This is already transactional — no $transaction wrapper needed
const user = await prisma.user.create({
data: {
email: 'user@example.com',
profile: { create: { bio: 'Hello' } },
posts: { create: [{ title: 'Post 1' }, { title: 'Post 2' }] },
},
});
Use the tx client inside interactive transactions, not the global prisma client. Queries on the global client run outside the transaction.
Batch operations — combine createMany, updateMany, and deleteMany in a sequential transaction for bulk operations:
await prisma.$transaction([
prisma.notification.deleteMany({ where: { read: true, createdAt: { lt: cutoff } } }),
prisma.auditLog.create({ data: { action: 'CLEANUP', count: deletedCount } }),
]);
Prisma supports two transaction modes: sequential (array) and interactive (callback). Both produce a real database transaction with ACID guarantees.
Sequential vs interactive: Sequential transactions execute all operations in order but you cannot read intermediate results. Interactive transactions give you a transaction-scoped client (tx) that lets you read and branch on results. Use sequential for independent writes; use interactive when operations depend on each other.
Isolation levels: ReadUncommitted, ReadCommitted, RepeatableRead, Serializable. The default varies by provider (PostgreSQL defaults to ReadCommitted). Higher isolation levels prevent more anomalies but increase lock contention and deadlock risk.
Interactive transaction pitfalls:
tx client is only valid inside the callback — do not pass it to background jobsNested writes vs explicit transactions: Nested writes (connect, create, connectOrCreate within a single operation) are cleaner and more performant than wrapping individual operations in $transaction. Use explicit transactions only when you need conditional logic between operations.
Deadlocks: If two transactions lock the same rows in different order, the database detects the deadlock and aborts one transaction. Prisma surfaces this as error code P2034. Retry the transaction in application code:
async function withRetry<T>(fn: () => Promise<T>, retries = 3): Promise<T> {
for (let i = 0; i < retries; i++) {
try {
return await fn();
} catch (e) {
if (e.code === 'P2034' && i < retries - 1) continue;
throw e;
}
}
throw new Error('Unreachable');
}
https://prisma.io/docs/orm/prisma-client/queries/transactions