Prisma Interactive Transaction Retry

Use Prisma interactive transactions with serialization failure detection, bounded retry loops, and rollback on invariant violations.

TypescriptNodeData Integrity

Code

snippet
import { PrismaClient, Prisma } from '@prisma/client';

const prisma = new PrismaClient({ log: ['warn', 'error'] });

async function adjustBalance(userId: number, delta: number) {
  for (let attempt = 1; attempt <= 4; attempt++) {
    try {
      return await prisma.$transaction(async (tx) => {
        const wallet = await tx.wallet.findUniqueOrThrow({
          where: { userId },
        });

        const next = wallet.balanceCents + delta;
        if (next < 0) {
          throw new Error('balance would go negative');
        }

        return tx.wallet.update({
          where: { userId },
          data: { balanceCents: next },
        });
      }, { isolationLevel: Prisma.TransactionIsolationLevel.Serializable });
    } catch (err) {
      const conflict =
        err instanceof Prisma.PrismaClientKnownRequestError
        && ['P2034'].includes(err.code);

      if (conflict && attempt < 4) {
        await new Promise((r) => setTimeout(r, 50 * 2 ** attempt));
        continue;
      }
      throw err;
    }
  }
  throw new Error('unreachable');
}

const wallet = await adjustBalance(1, -250);
console.log('balance now:', wallet.balanceCents);
await prisma.$disconnect();