Results and errors
Every call returns a Result with a typed, serializable DbError.
Repository methods never throw for database errors. They return an
AsyncResult, which you can await for a Result or unwrap:
const result = await db.customers.findById(id);
if (!result.ok) {
if (result.error.kind === "not_found") return notFound();
throw result.error;
}
result.data.name;
// Or throw a DbException on error:
const customer = await db.customers.findById(id).orThrow();
// Or chain:
const name = await db.customers
.findById(id)
.map((row) => row.name)
.unwrapOr("Unknown");AsyncResult has orThrow, unwrapOr, map, mapError and andThen.
mapError here rewrites the DbError inside one result; betterSupabase.mapError()
(below) changes what .orThrow() throws.
Error kinds
DbError is plain data, safe to return from server actions and RPC handlers.
| kind | status | From |
|---|---|---|
not_found | 404 | findById, update, delete with no row, PGRST116, P0002 (no_data_found) |
unauthorized | 401 | Missing or expired JWT |
forbidden | 403 | 42501, RLS WITH CHECK failures; guards with aal set required (MFA); an authorizer refusal sets permission, and approval with code: "APPROVAL_REQUIRED" |
conflict | 409 | 23505 unique violation, with constraint and columns |
foreign_key | 409 | 23503 |
check | 422 | 23514, with constraint; a jsonb-schemas check is validation instead |
not_null | 422 | 23502, with column |
exclusion | 409 | 23P01 |
invalid_input | 400 | 22* data exceptions |
invalid_value | 500 | A stored value the app type can't hold, such as infinity in a Temporal timestamp, with column |
raised | 400 | RAISE EXCEPTION (P0001); hint carries your app code |
timeout | 504 | 57014 statement timeout, or a call past its timeout option |
serialization | 409 | 40001, safe to retry |
network | 503 | Fetch failures, 08* |
aborted | 499 | The AbortSignal fired |
invalid_request | 400 | A query the backend cannot express |
validation | 422 | Standard Schema issues from the validation plugin, or the jsonb-schemas errors |
multiple_rows | 409 | A single-row call matched several rows |
stale | 412 | update(..., { expect }) found a newer row |
max_affected | 400 | A write matched more rows than its maxAffected, PGRST124; maxAffected is the limit |
rate_limited | 429 | Over a write rate limit; retryAfter is the seconds to wait |
quota_exceeded | 429 | A usage quota is used up; meter, limit and retryAfter (seconds until the period resets) |
unsupported | 501 | The executor can't run the operation, such as an include on SQLite |
unexpected | 500 | Anything else |
The status field feeds the RFC 9457 problem details
mapping in the server adapters.
Custom kinds and mappers
Add mappers for your own RAISE codes:
const betterSupabase = defineSupabase(schema, {
errors: [
(raw, fallback) =>
raw.hint === "quota_exceeded"
? dbError("forbidden", fallback.message)
: undefined,
],
});Plugins can also contribute a mapError. These mappers turn a raw
Postgres or PostgREST error into a DbError; the next section turns a
DbError into your own error.
Domain errors for .orThrow()
betterSupabase.mapError(fn) sets the error .orThrow() throws for every db it
connects, instead of a DbException:
export class AppError extends Error {
constructor(readonly error: DbError) {
super(error.message, { cause: error });
}
}
export const toAppError = (error: DbError) => new AppError(error);
export const betterSupabase = defineSupabase(schema).mapError(toAppError);
await db.customers.findById(id).orThrow(); // throws AppErrorResults themselves keep their DbError, so they stay serializable. The
mapper survives map, mapError and andThen; a factory passed to
.orThrow(factory) takes precedence.
Set the DbError as the error's cause. The server, Next.js, Hono and
oRPC adapters then still answer with Problem Details, and isConflict,
isCheck and isForeignKey still match. TanStack Query hooks always see a
DbException, whatever the mapper.
better-result
To return better-result values
instead, wrap the db with withBetterResult or convert single results with
toBetterResult. See better-result.
Last updated on