/** * Shared PostgreSQL error extraction and user-friendly message formatting. * * Drizzle wraps native PG errors in a `.cause` chain. These utilities * unwrap that chain to get the real PostgreSQL error (identified by a * 5-character alphanumeric `code` such as `42P01`) and translate it into * a message that is safe and helpful to show to end-users. */ /** Shape of PostgreSQL errors with diagnostic metadata. */ export interface PostgresError extends Error { code?: string; detail?: string; hint?: string; constraint?: string; column?: string; table?: string; dataType?: string; cause?: unknown; } /** * Extract the underlying PostgreSQL error from a Drizzle wrapper. * Drizzle wraps PG errors in a `cause` property — this function * recursively walks the chain until it finds an object with a PG * error code (5-char alphanumeric, e.g. `42P01`). */ export declare function extractPgError(error: unknown): PostgresError | null; /** * Whether the failure came back from Postgres rather than from building the * query — which decides whether a fallback query is worth issuing. * * Reads here run inside a transaction (that is where `SET LOCAL ROLE` binds * RLS). Once a statement raises, that transaction is aborted, and every later * statement on it returns `25P02` — "current transaction is aborted, commands * ignored until end of transaction block". So a retry after a database error * cannot succeed, and it replaces a precise diagnosis ("invalid input syntax * for type uuid") with a generic one. Rethrow instead. * * A query the driver could not even build — a missing reciprocal relation, say * — never reached Postgres, leaves the transaction usable, and is exactly what * the fallback paths exist for. */ export declare function reachedDatabase(error: unknown): boolean; /** * Walk the error cause chain and return the deepest meaningful message. */ export declare function extractCauseMessage(error: unknown): string | null; export interface ConnectFailure { /** True when retrying cannot help: the connection string itself is wrong. */ fatal: boolean; /** The deepest message available — the Postgres one where there is one. */ reason: string; /** The `SQLSTATE`, when the failure came from Postgres rather than the socket. */ code?: string; } /** * Describe a failed connection attempt in terms a developer can act on. * * The error a caller catches is Drizzle's wrapper: its message is * `Failed query: SELECT 1` and its stack runs through drizzle internals, while * the sentence that says what is actually wrong — "password authentication * failed for user …", "database … does not exist" — sits in `.cause`. Logging * the wrapper, as the bootstrapper used to, tells a developer with a typo in * their `DATABASE_URL` nothing at all. */ export declare function classifyConnectFailure(error: unknown): ConnectFailure; /** * Detect whether an error is specifically a role-switching permission failure * (e.g. "permission denied to set role" or "must be member of role"), * as opposed to a table-level permission denial. * * This is used by the backend driver to auto-disable role switching when the * connection user lacks SET ROLE privileges, rather than surfacing a confusing * error to the Studio SQL Editor user. */ export declare function isRoleSwitchingPermissionError(error: unknown): boolean; /** * Was this `42501` the *caller* being refused by a policy, rather than the * server lacking a privilege? * * Both arrive as `insufficient_privilege`, and they are opposite kinds of * problem. A row-level-security refusal is a working access-control system * doing its job: the caller asked for something their policies do not permit, * which is a 403 and nobody's bug. A missing `GRANT` is the deployment being * wrong — the connection role cannot touch the table at all, no policy is * involved, and nothing the caller changes about the request will help. * * Postgres distinguishes them in the message, so this does too: * * new row violates row-level security policy for table "notes" → the caller * permission denied for table notes → the server * * Only writes reach this. A read that RLS excludes is not an error — the rows * are filtered and the caller gets an empty page — so the erroring case is * specifically an `INSERT`/`UPDATE` whose row fails a policy's `WITH CHECK`. * * Matched on the message because that is the only thing carrying the * distinction; the SQLSTATE is identical either way. Narrow by design: anything * not naming row-level security stays the server's problem, since reporting a * genuine privilege misconfiguration as "forbidden" would send an operator * hunting for a policy bug that does not exist. */ export declare function isRowLevelSecurityDenial(error: unknown): boolean; /** * Translate a raw PostgreSQL error into a user-friendly message. * * @param pgError - The extracted PostgreSQL error (from {@link extractPgError}) * @param context - A human-readable context string (e.g. collection slug or path) * @returns An object with a `message` safe for the client and the PG `code`. */ export declare function pgErrorToFriendlyMessage(pgError: PostgresError, context: string): { message: string; code: string; }; /** * Sanitize any error into a message safe and helpful for the client. * * A deliberate 4xx (`ApiError`) passes through untouched — the server already * decided what the client should read. Otherwise the PG error is extracted * from the Drizzle cause chain, falling back to a generic message that * doesn't leak SQL. * * @param error - The raw caught error * @param context - A human-readable context string (e.g. collection path) * @returns An object with `message` (user-friendly) and optional `code` * (the `ApiError` code, or the PG SQLSTATE). */ export declare function sanitizeErrorForClient(error: unknown, context: string): { message: string; code?: string; };