Error Domains
Declare typed error schemas with defineNiceError and the err() helper.
An error domain is a named group of related errors. Each entry in its schema ties an error id to a message, an HTTP status, and (optionally) some typed extra data called context.
The words we use
Section titled “The words we use”- Domain — a named group of related errors (
err_auth,err_payment). - Schema — the list that maps each error id to its message, HTTP status, and optional context.
- Error id — a short name for what went wrong. One error can have several ids active at once.
- Context — extra data attached to an id (e.g.
{ username: string }). - Hydration — when an error has travelled as JSON, hydration turns its context back into the real typed values it started as.
Defining a domain
Section titled “Defining a domain”import { defineNiceError, err } from "@nice-code/error";
export const err_auth = defineNiceError({ domain: "err_auth", schema: { // No context — second arg to fromId is not accepted account_locked: err({ message: "Account is locked", httpStatusCode: 403, }),
// Required context — second arg to fromId is required invalid_credentials: err<{ username: string }>({ message: ({ username }) => `Invalid credentials for: ${username}`, httpStatusCode: 401, context: { required: true }, }),
// Optional context rate_limited: err<{ retryAfter: number }>({ message: (ctx) => (ctx ? `Retry after ${ctx.retryAfter}s` : "Rate limited"), httpStatusCode: 429, context: {}, }), },});The err() helper
Section titled “The err() helper”err() describes one error in the schema. How you call it decides whether context is allowed — or required — when you later create the error:
| How you declare it | fromId("id") | fromId("id", ctx) |
|---|---|---|
err() / err({ message, httpStatusCode }) — no context | ✓ | ✗ |
err<C>({ context: {} }) — optional context | ✓ optional | ✓ optional |
err<C>({ context: { required: true } }) — required context | ✗ | ✓ required |
message and httpStatusCode can each be a fixed value, or a function that builds the value from the context.
Sending context that isn’t plain JSON
Section titled “Sending context that isn’t plain JSON”If a context value isn’t something JSON can carry on its own (an Error, a Date, a Map), give it a pair of functions — one to pack it, one to unpack it — so it survives the trip:
fs_error: err<{ cause: NodeJS.ErrnoException }>({ message: ({ cause }) => `FS error: ${cause.message}`, context: { required: true, serialization: { toJsonSerializable: ({ cause }) => ({ code: cause.code, message: cause.message }), fromJsonSerializable: (obj) => ({ cause: Object.assign(new Error(obj.message), obj) }), }, },}),The packed form is what travels over the network; hydration unpacks it back into the real value on the other side.
Utility types
Section titled “Utility types”import type { InferNiceError, InferNiceErrorHydrated } from "@nice-code/error";
type TAuthError = InferNiceError<typeof err_auth>;type TAuthErrorHydrated = InferNiceErrorHydrated<typeof err_auth>;