Skip to content

Instantly share code, notes, and snippets.

@staciax
Last active January 24, 2025 07:21
Show Gist options
  • Save staciax/ed73a38a9dc10870501af0013aeebe2b to your computer and use it in GitHub Desktop.
Save staciax/ed73a38a9dc10870501af0013aeebe2b to your computer and use it in GitHub Desktop.
Prisma Custom Transaction Handler
// 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