// Prisma custom transaction handler // I'm just add supported transactions options // Please read more: // https://github.com/prisma/prisma/issues/22309 // https://github.com/prisma/prisma-client-extensions/tree/main/callback-free-itx // https://github.com/prisma/prisma-client-extensions/pull/52 import { Prisma, PrismaClient } from '@prisma/client'; const prisma = new PrismaClient({ errorFormat: 'pretty', log: [ { emit: 'event', level: 'query', }, { emit: 'stdout', level: 'error', }, { emit: 'stdout', level: 'info', }, { emit: 'stdout', level: 'warn', }, ], }); const green = '\x1b[32m'; const reset = '\x1b[0m'; prisma.$on('query', (e) => { console.log(`Query: \n${green} ${e.query} ${reset}`); console.log(`Params: ${e.params}`); console.log(`Duration: ${e.duration} ms`); console.log(`Timestamp: ${e.timestamp}\n`); }); type PrismaFlatTransactionClient = Prisma.TransactionClient & { $commit: () => Promise; $rollback: () => Promise; }; type PrismaTransactionOptions = { maxWait?: number; timeout?: number; isolationLevel?: Prisma.TransactionIsolationLevel; }; const ROLLBACK = { [Symbol.for('prisma.client.extension.rollback')]: true }; const xprisma = prisma.$extends({ client: { async $begin(options?: PrismaTransactionOptions) { const prisma = Prisma.getExtensionContext(this); let setTxClient: (txClient: Prisma.TransactionClient) => void; let commit: () => void; let rollback: () => void; // a promise for getting the tx inner client const txClient = new Promise((res) => { setTxClient = (txClient) => res(txClient); }); // a promise for controlling the transaction const txPromise = new Promise((_res, _rej) => { commit = () => { return _res(undefined); }; rollback = () => { return _rej(ROLLBACK); }; }); // opening a transaction to control externally if ( '$transaction' in prisma && typeof prisma.$transaction === 'function' ) { const tx = prisma .$transaction((txClient) => { setTxClient( txClient as unknown as Prisma.TransactionClient, ); return txPromise; }, options) .catch((e) => { if (e === ROLLBACK) return; throw e; }); // return a proxy TransactionClient with `$commit` and `$rollback` methods return new Proxy(await txClient, { get(target, prop) { if (prop === '$commit') { return () => { commit(); return tx; }; } if (prop === '$rollback') { return () => { rollback(); return tx; }; } if (prop === '$transaction') { return async ( fn: ( client: Prisma.TransactionClient, // biome-ignore lint/suspicious/noExplicitAny: ) => Promise, ) => { return fn(target); }; } return target[prop as keyof typeof target]; }, }) as PrismaFlatTransactionClient; } throw new Error('Transactions are not supported by this client'); }, }, }); const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); async function exampleTransaction() { const tx = await xprisma.$begin({ timeout: 10 * 1000, // 10s }); // sleep 5s await sleep(5 * 1000); let user: { id: string; email: string; } | null; try { user = await tx.user.findFirst({ where: { email: 'test@test.com' } }); if (!user) { user = await tx.user.create({ data: { email: 'test@test.com' } }); } else { await tx.user.delete({ where: { id: user.id } }); } await tx.$commit(); } catch (error) { await tx.$rollback(); throw error; } return { user: user }; } const result = await exampleTransaction(); console.log(result);