mirror of
https://github.com/eliasstepanik/core.git
synced 2026-01-12 03:08:28 +00:00
132 lines
3.4 KiB
TypeScript
132 lines
3.4 KiB
TypeScript
import { Prisma, PrismaClient } from "@prisma/client";
|
|
|
|
export type PrismaTransactionClient = Omit<
|
|
PrismaClient,
|
|
"$connect" | "$disconnect" | "$on" | "$transaction" | "$use" | "$extends"
|
|
>;
|
|
|
|
export type PrismaClientOrTransaction = PrismaClient | PrismaTransactionClient;
|
|
|
|
export type PrismaReplicaClient = Omit<PrismaClient, "$transaction">;
|
|
|
|
function isTransactionClient(prisma: PrismaClientOrTransaction): prisma is PrismaTransactionClient {
|
|
return !("$transaction" in prisma);
|
|
}
|
|
|
|
export function isPrismaKnownError(error: unknown): error is Prisma.PrismaClientKnownRequestError {
|
|
return (
|
|
typeof error === "object" && error !== null && "code" in error && typeof error.code === "string"
|
|
);
|
|
}
|
|
|
|
/*
|
|
• P2024: Connection timeout errors
|
|
• P2028: Transaction timeout errors
|
|
• P2034: Transaction deadlock/conflict errors
|
|
*/
|
|
const retryCodes = ["P2024", "P2028", "P2034"];
|
|
|
|
export function isPrismaRetriableError(error: unknown): boolean {
|
|
if (!isPrismaKnownError(error)) {
|
|
return false;
|
|
}
|
|
|
|
return retryCodes.includes(error.code);
|
|
}
|
|
|
|
/*
|
|
• P2025: Record not found errors (in race conditions) [not included for now]
|
|
*/
|
|
export function isPrismaRaceConditionError(error: unknown): boolean {
|
|
if (!isPrismaKnownError(error)) {
|
|
return false;
|
|
}
|
|
|
|
return error.code === "P2025";
|
|
}
|
|
|
|
export type PrismaTransactionOptions = {
|
|
/** The maximum amount of time (in ms) Prisma Client will wait to acquire a transaction from the database. The default value is 2000ms. */
|
|
maxWait?: number;
|
|
|
|
/** The maximum amount of time (in ms) the interactive transaction can run before being canceled and rolled back. The default value is 5000ms. */
|
|
timeout?: number;
|
|
|
|
/** Sets the transaction isolation level. By default this is set to the value currently configured in your database. */
|
|
isolationLevel?: Prisma.TransactionIsolationLevel;
|
|
|
|
swallowPrismaErrors?: boolean;
|
|
|
|
/**
|
|
* The maximum number of times the transaction will be retried in case of a serialization failure. The default value is 0.
|
|
*
|
|
* See https://www.prisma.io/docs/orm/prisma-client/queries/transactions#transaction-timing-issues
|
|
*/
|
|
maxRetries?: number;
|
|
};
|
|
|
|
export async function $transaction<R>(
|
|
prisma: PrismaClientOrTransaction,
|
|
fn: (prisma: PrismaTransactionClient) => Promise<R>,
|
|
prismaError: (error: Prisma.PrismaClientKnownRequestError) => void,
|
|
options?: PrismaTransactionOptions,
|
|
attempt = 0
|
|
): Promise<R | undefined> {
|
|
if (isTransactionClient(prisma)) {
|
|
return fn(prisma);
|
|
}
|
|
|
|
try {
|
|
return await (prisma as PrismaClient).$transaction(fn, options);
|
|
} catch (error) {
|
|
if (isPrismaKnownError(error)) {
|
|
if (
|
|
retryCodes.includes(error.code) &&
|
|
typeof options?.maxRetries === "number" &&
|
|
attempt < options.maxRetries
|
|
) {
|
|
return $transaction(prisma, fn, prismaError, options, attempt + 1);
|
|
}
|
|
|
|
prismaError(error);
|
|
|
|
if (options?.swallowPrismaErrors) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export function isUniqueConstraintError<T extends readonly string[]>(
|
|
error: unknown,
|
|
columns: T
|
|
): boolean {
|
|
if (!isPrismaKnownError(error)) {
|
|
return false;
|
|
}
|
|
|
|
if (error.code !== "P2002") {
|
|
return false;
|
|
}
|
|
|
|
const target = error.meta?.target;
|
|
|
|
if (!Array.isArray(target)) {
|
|
return false;
|
|
}
|
|
|
|
if (target.length !== columns.length) {
|
|
return false;
|
|
}
|
|
|
|
for (let i = 0; i < columns.length; i++) {
|
|
if (target[i] !== columns[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|