chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
import { Prisma, type PrismaClient, isPrismaKnownError } from "@trigger.dev/database";
|
||||
import { logger } from "~/services/logger.server";
|
||||
|
||||
// Minimal structural logger so this stays decoupled from the concrete Logger
|
||||
// (and lets tests pass a capturing logger).
|
||||
type ErrorLogger = { error: (message: string, fields?: Record<string, unknown>) => void };
|
||||
|
||||
// Prisma connectivity / infrastructure error codes — engine- and
|
||||
// connection-level failures, not query- or validation-level ones. When the
|
||||
// database is unreachable, Prisma 6.x throws a PrismaClientKnownRequestError
|
||||
// carrying one of these codes (e.g. P1001 "Can't reach database server").
|
||||
const INFRASTRUCTURE_PRISMA_CODES = new Set([
|
||||
"P1001", // Can't reach database server
|
||||
"P1002", // Database server reached but timed out
|
||||
"P1008", // Operations timed out
|
||||
"P1017", // Server has closed the connection
|
||||
]);
|
||||
|
||||
/**
|
||||
* True when `error` is a Prisma infrastructure/connectivity failure (DB
|
||||
* unreachable, timed out, connection dropped) rather than a query- or
|
||||
* validation-level error.
|
||||
*
|
||||
* These errors carry internal infrastructure detail (e.g. the database
|
||||
* hostname) in their `.message`, so they must never be surfaced to API
|
||||
* clients — callers should let them propagate to the generic 5xx handler
|
||||
* (which both scrubs the message and is retryable by the SDK) instead of
|
||||
* folding `.message` into a client-facing error.
|
||||
*/
|
||||
export function isInfrastructureError(error: unknown): boolean {
|
||||
if (
|
||||
error instanceof Prisma.PrismaClientInitializationError ||
|
||||
error instanceof Prisma.PrismaClientRustPanicError ||
|
||||
error instanceof Prisma.PrismaClientUnknownRequestError
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
return INFRASTRUCTURE_PRISMA_CODES.has(error.code);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// One-shot marker so a single infra error is logged exactly once: the client
|
||||
// extension (statement level) tags it, and the $transaction-boundary loggers
|
||||
// skip a tagged error rather than logging the same failure a second time.
|
||||
const INFRA_ERROR_LOGGED: unique symbol = Symbol("prismaInfraErrorLogged");
|
||||
|
||||
function markInfraErrorLogged(error: unknown): void {
|
||||
if (typeof error !== "object" || error === null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// Non-enumerable so error-spreads/serializers can't copy the marker onto a
|
||||
// different error; try/catch so a frozen error object can't make this throw
|
||||
// and mask the original error as it propagates out of the catch.
|
||||
Object.defineProperty(error, INFRA_ERROR_LOGGED, {
|
||||
value: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
});
|
||||
} catch {
|
||||
// best-effort: a sealed/frozen error simply won't be deduped.
|
||||
}
|
||||
}
|
||||
|
||||
export function infraErrorAlreadyLogged(error: unknown): boolean {
|
||||
return (
|
||||
typeof error === "object" &&
|
||||
error !== null &&
|
||||
(error as Record<symbol, unknown>)[INFRA_ERROR_LOGGED] === true
|
||||
);
|
||||
}
|
||||
|
||||
// Logs infrastructure failures (P1xxx-class, see isInfrastructureError) and
|
||||
// rethrows the ORIGINAL error: callers branch on error.code, and this fires
|
||||
// per-statement inside transactions, so converting it would break that.
|
||||
export function captureInfrastructureErrors<T extends PrismaClient>(
|
||||
client: T,
|
||||
log: ErrorLogger = logger
|
||||
): T {
|
||||
return client.$extends({
|
||||
name: "infrastructure-error-capture",
|
||||
query: {
|
||||
$allOperations: async ({ model, operation, args, query }) => {
|
||||
try {
|
||||
return await query(args);
|
||||
} catch (error) {
|
||||
if (isInfrastructureError(error)) {
|
||||
log.error("prisma infrastructure error", {
|
||||
model,
|
||||
operation,
|
||||
code: error instanceof Prisma.PrismaClientKnownRequestError ? error.code : undefined,
|
||||
meta: error instanceof Prisma.PrismaClientKnownRequestError ? error.meta : undefined,
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
});
|
||||
markInfraErrorLogged(error);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
},
|
||||
}) as unknown as T;
|
||||
}
|
||||
|
||||
// Logs infrastructure errors that reach the $transaction boundary WITHOUT a
|
||||
// Prisma error code (e.g. PrismaClientInitializationError). Coded errors there
|
||||
// are already logged by transac()'s callback, and errors that bubbled up from a
|
||||
// statement were already logged (and tagged) by the client extension — both are
|
||||
// skipped here to avoid double-logging. Returns whether it logged.
|
||||
export function logTransactionInfrastructureError(
|
||||
error: unknown,
|
||||
log: ErrorLogger = logger
|
||||
): boolean {
|
||||
if (
|
||||
!isInfrastructureError(error) ||
|
||||
isPrismaKnownError(error) ||
|
||||
infraErrorAlreadyLogged(error)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
log.error("prisma.$transaction infrastructure error", {
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
name: error instanceof Error ? error.name : undefined,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Replaces a Prisma infrastructure error's message (which carries the DB
|
||||
// hostname) with a generic one before it reaches an API client. Any other
|
||||
// error's message is returned unchanged. Status codes/headers are unaffected.
|
||||
export function clientSafeErrorMessage(error: Error): string {
|
||||
return isInfrastructureError(error) ? "Internal Server Error" : error.message;
|
||||
}
|
||||
Reference in New Issue
Block a user