Skip to content
dbSDK
Esc
↑↓navigate↵open⌘Jpreview
On this page

Errors

One normalized DbError with codes, SQLSTATE passthrough, retryable, and the indeterminate flag for writes with unknown outcomes.

Every error that escapes the client is a DbError (or carries one as its cause), regardless of which adapter or transport produced it. Driver errors keep their information: the original error is attached as cause, and the PostgreSQL SQLSTATE code is passed through when the server reported one.

The shape

import { isDbError } from "dbsdk";

try {
  await db.sql`select * from does_not_exist`;
} catch (error) {
  if (isDbError(error)) {
    error.code;       // "SYNTAX" here (SQLSTATE 42P01)
    error.sqlstate;   // "42P01"
    error.retryable;  // false
    error.indeterminate; // false: nothing was written
    error.adapterId;  // "neon" | "supabase" | "postgres"
    error.cause;      // the underlying driver error, preserved
  }
}

Codes

Code Meaning Typical source
CAPABILITY The transport cannot do this operation transaction() on Neon HTTP, session-state statements on the transaction pooler
CONFIGURATION The adapter was configured inconsistently connection string contradicting the declared Supabase mode; undefined parameters
CONSTRAINT Constraint violation SQLSTATE class 23, for example unique violations (23505)
CONNECTION The connection failed or was closed dropped socket, operations after close(), Node transport errors like EPIPE or ECONNRESET
TIMEOUT A timeout fired SQLSTATE 57014, client statement timeouts
PERMISSION The server refused for permissions or row security SQLSTATE 42501, the common RLS denial
SYNTAX The server could not parse the SQL SQLSTATE class 42 except permission errors
TRANSACTION Transaction-state problems serialization failures (40001), deadlocks (40P01), invalid transaction state
DATA Data exceptions SQLSTATE class 22, for example invalid text representation
UNKNOWN Anything not yet classified driver-specific failures without an SQLSTATE

Classification runs on the SQLSTATE when there is one, so PostgreSQL’s own taxonomy is preserved. Code branching plus sqlstate gives you both a stable vocabulary and exact precision when you need it.

retryable

true on connection failures, timeouts, serialization failures (40001), and deadlocks (40P01). It means “retrying this exact statement is safe in principle”. It is information only: dbSDK never retries on your behalf.

indeterminate

true when a write may or may not have been committed and dbSDK cannot know which. Set when a connection or timeout error occurs:

  • during a statement whose first keyword is a write (insert, update, delete, merge, create, grant, and similar - note that all explain statements are included, because explain analyze executes its statement),
  • inside a batch that contains any write-shaped statement,
  • escaping a transaction() callback, where dbSDK cannot know whether your callback wrote before the failure,
  • in a multi-statement parameterless string, where everything after a semicolon also ran.

Top-level read-only statements are never marked on a transport failure. Inside a transaction, any error without a server SQLSTATE is marked, because dbSDK cannot know whether the callback wrote. Pre-dispatch failures (capability, configuration) are never marked, because nothing was sent.

What dbSDK does about it: nothing automatic. No retry, no replay, no failover. See Transactions for how to make retries safe with idempotent writes.

Errors that are not from the database

Your own exceptions are normalized too. If ordinary code inside transaction() throws (a business-rule error, for example), what reaches your catch is a DbError with code UNKNOWN, indeterminate: true (the transaction was rolled back, but dbSDK cannot prove nothing was written before the throw), your message, and the original error on cause. If you want clean handling of deliberate aborts, throw a DbError yourself: user-thrown DbErrors pass through unchanged, keeping their code and indeterminate: false.

That also matters for the retry pattern above: indeterminate: true on a transaction can be a business abort, not a network problem. Distinguish by error.code === 'UNKNOWN' and the cause chain, or by throwing typed errors for expected cases.

Reporting and diagnostics

DbError messages contain no credentials. Connection strings stay in your environment; configuration errors name only the host, port, and username pieces needed to diagnose a mode mismatch. The cause chain is preserved for logs, so log the full error, not just the message.

Was this page helpful?