Last active
January 24, 2025 07:21
-
-
Save staciax/ed73a38a9dc10870501af0013aeebe2b to your computer and use it in GitHub Desktop.
Prisma Custom Transaction Handler
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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<void>; | |
$rollback: () => Promise<void>; | |
}; | |
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<Prisma.TransactionClient>((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: <explanation> | |
) => Promise<any>, | |
) => { | |
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: '[email protected]' } }); | |
if (!user) { | |
user = await tx.user.create({ data: { email: '[email protected]' } }); | |
} 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); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment