@absoluteui/helpers/error
Error classes with stable codes, Result values, safe serialization, and field-error maps for forms.
import { ... } from '@absoluteui/helpers/error';How to use
Section titled “How to use”Typed errors with stable code and kind, a Result type for code that should not throw, and converters that turn anything thrown into form messages, log records, or worker messages. Every helper in the package throws these classes, so one catch handles them all. The Helpers and services guide explains the error-code conventions.
How do I turn thrown errors into form field messages?
Section titled “How do I turn thrown errors into form field messages?”Collect the errors from a save and pass them to fieldErrors: fields from validation (and HTTP 422) errors map to their inputs, anything else becomes one form-level message under _form.
import { fieldErrors, HttpError, ValidationError } from '@absoluteui/helpers/error';
const clientCheck = new ValidationError('HRM_EMP_INVALID', 'Fix the highlighted fields.', { fields: { email: 'Enter a valid email address.' },});const serverReply = new HttpError('HRM_HTTP_422', 'The request was rejected.', { status: 422, fields: { startDate: 'The start date is in a closed payroll period.' },});const messages = fieldErrors([clientCheck, serverReply, new Error('Server unavailable.')]);messages; // => { email: 'Enter a valid email address.', startDate: 'The start date is in a closed payroll period.', _form: 'Server unavailable.' }How do I decide what to show for a failed call?
Section titled “How do I decide what to show for a failed call?”Branch on kind or code, never on the message. isHelperError narrows an unknown catch value; HttpError adds category and retryable.
import { HttpError, isHelperError, NotFoundError, toError } from '@absoluteui/helpers/error';
function userMessage(caught: unknown): string { if (caught instanceof HttpError && caught.retryable) return 'The service is busy. Try again shortly.'; if (isHelperError(caught)) { if (caught.kind === 'not-found') return 'This employee no longer exists.'; if (caught.kind === 'conflict') return 'Someone else changed this record. Reload and try again.'; } return toError(caught).message;}userMessage(new NotFoundError('HRM_EMP_NOT_FOUND', 'Employee not found.')); // => 'This employee no longer exists.'userMessage(new HttpError('HRM_HTTP_503', 'Service unavailable.', { status: 503 })); // => 'The service is busy. Try again shortly.'userMessage('boom'); // => 'A non-error value was thrown.'How do I validate rows without try/catch everywhere?
Section titled “How do I validate rows without try/catch everywhere?”Return a Result with ok/err, wrap throwing code with tryCatch, and use combineResults to stop at the first bad row.
import { combineResults, err, ok, ValidationError, type Result } from '@absoluteui/helpers/error';
function parseHours(row: string, line: number): Result<number, ValidationError> { const hours = Number(row); return Number.isFinite(hours) && hours >= 0 && hours <= 24 ? ok(hours) : err(new ValidationError('HRM_TIMESHEET_HOURS', `Line ${line}: hours must be 0 to 24.`));}combineResults(['8', '7.5'].map((r, i) => parseHours(r, i + 1))); // => { ok: true, value: [8, 7.5] }const bad = combineResults(['8', '30', 'x'].map((r, i) => parseHours(r, i + 1)));bad.ok ? '' : bad.error.message; // => 'Line 2: hours must be 0 to 24.'How do I send an error to a log or across a worker boundary?
Section titled “How do I send an error to a log or across a worker boundary?”serializeError produces a JSON-safe object (with the cause chain, without stacks by default); deserializeError rebuilds a typed error on the other side.
import { ConflictError, deserializeError, serializeError } from '@absoluteui/helpers/error';
const original = new ConflictError('HRM_EMP_VERSION', 'The record was changed by someone else.');const wire = JSON.stringify(serializeError(original));const restored = deserializeError(JSON.parse(wire));[restored.name, restored.code, restored.kind]; // => ['ConflictError', 'HRM_EMP_VERSION', 'conflict']What goes wrong?
Section titled “What goes wrong?”Messages are shown to users and written to logs, so never put secrets or raw input in them; put structured, non-sensitive context in details. invariant guards internal assumptions and throws HLP_ASSERT:
import { invariant, unwrap, err, NotFoundError } from '@absoluteui/helpers/error';
const leaveBalance = -2;invariant(leaveBalance >= 0, 'Leave balance must not be negative.'); // => throws HLP_ASSERTunwrap(err(new NotFoundError('HRM_EMP_NOT_FOUND', 'Employee not found.'))); // => throws HRM_EMP_NOT_FOUNDAPI reference
Section titled “API reference”Functions
Section titled “Functions”assertNever
Exhaustiveness check for switch statements.
function assertNever(value: never, message?: string): never;Example:
type Shift = 'day' | 'night';function label(shift: Shift): string { switch (shift) { case 'day': return 'Day shift'; case 'night': return 'Night shift'; default: return assertNever(shift); // compile error if a Shift is not handled }}label('night'); // => 'Night shift'combineResults
Collects results: all values, or the first error.
function combineResults<T, E>(results: readonly Result<T, E>[]): Result<T[], E>;Example:
combineResults([ok(1), ok(2)]); // => { ok: true, value: [1, 2] }combineResults([ok(1), err('E_ROW_2'), err('E_ROW_3')]); // => { ok: false, error: 'E_ROW_2' }deserializeError
Rebuilds a HelperError from serializeError output (for example, received from a worker).
Unknown shapes become HLP_INVALID_SERIALIZED_ERROR.
function deserializeError(value: unknown): HelperError;Example:
const e = deserializeError({ name: 'NotFoundError', message: 'Employee not found.', code: 'HRM_EMP_NOT_FOUND', kind: 'not-found' });[e.name, e.code, e.kind]; // => ['NotFoundError', 'HRM_EMP_NOT_FOUND', 'not-found']deserializeError(42).code; // => 'HLP_INVALID_SERIALIZED_ERROR'err
function err<E>(error: E): Err<E>;Example:
const r = err(new ValidationError('HRM_LEAVE_RANGE', 'The end date is before the start date.'));r.ok; // => falser.error.code; // => 'HRM_LEAVE_RANGE'fieldErrors
Field-error map for form error summaries (error-summary, form-field).
Collects fields from ValidationErrors; other errors go under formKey.
function fieldErrors(errors: readonly unknown[], formKey?: string): Record<string, string>;Example:
const errors = [new ValidationError('HLP_V', 'Invalid.', { fields: { email: 'Enter a valid email address.' } }), new Error('Server unavailable.')];fieldErrors(errors); // => { email: 'Enter a valid email address.', _form: 'Server unavailable.' }httpFailureCategory
Failure category of a status: specific statuses first, then client (4xx) or server (5xx).
function httpFailureCategory(status: number): HttpFailureCategory;Example:
httpFailureCategory(422) // => 'validation'Example:
httpFailureCategory(503) // => 'unavailable'Example:
httpFailureCategory(418) // => 'client'invariant
Throws a HelperError with code HLP_ASSERT when condition is falsy.
function invariant(condition: unknown, message?: string, code?: string): asserts condition;Example:
const budget = -5;invariant(budget >= 0, 'Budget must not be negative.'); // => throws HLP_ASSERTisAbortError
True for any abort, from AbortController, DOM, or this package.
function isAbortError(value: unknown): boolean;Example:
isAbortError(new DOMException('Stopped.', 'AbortError')); // => trueisAbortError(new TimeoutError()); // => falseisHelperError
True when value is a HelperError, optionally with the given code.
function isHelperError(value: unknown, code?: string): value is HelperError;Example:
const e = new ConflictError('HRM_EMP_VERSION', 'The record was changed by someone else.');isHelperError(e, 'HRM_EMP_VERSION'); // => trueisHelperError(new Error('Plain.')); // => falsemapError
Maps the error value.
function mapError<T, E, F>(result: Result<T, E>, fn: (error: E) => F): Result<T, F>;Example:
mapError(err(404), (status) => `HTTP ${status}`); // => { ok: false, error: 'HTTP 404' }mapResult
Maps the success value.
function mapResult<T, U, E>(result: Result<T, E>, fn: (value: T) => U): Result<U, E>;Example:
mapResult(ok(20), (days) => days * 8); // => { ok: true, value: 160 }ok
function ok<T>(value: T): Ok<T>;Example:
const r = ok(42); // => { ok: true, value: 42 }serializeError
Converts an error (and its cause chain) into a JSON-safe object.
function serializeError(value: unknown, options?: SerializeErrorOptions): SerializedError;Example:
const e = new HttpError('HLP_HTTP_503', 'Service unavailable.', { status: 503 });serializeError(e); // => { name: 'HttpError', message: 'Service unavailable.', code: 'HLP_HTTP_503', kind: 'http', status: 503 }toError
Normalizes anything thrown into an Error. Non-error values become a HelperError
with code HLP_NON_ERROR_THROWN and the value kept only as cause.
function toError(value: unknown): Error;Example:
toError(new RangeError('Out of range.')).message; // => 'Out of range.'isHelperError(toError('boom'), 'HLP_NON_ERROR_THROWN'); // => truetryCatch
Runs fn, capturing a throw as Err.
function tryCatch<T>(fn: () => T): Result<T>;Example:
const r = tryCatch(() => JSON.parse('{"id":1}')); // => { ok: true, value: { id: 1 } }tryCatch(() => JSON.parse('{')).ok; // => falsetryCatchAsync
Awaits fn, capturing a rejection as Err.
function tryCatchAsync<T>(fn: () => Promise<T> | T): Promise<Result<T>>;Example:
const r = await tryCatchAsync(async () => { throw new TimeoutError(); });r.ok; // => falseunwrap
Returns the value or throws the error.
function unwrap<T, E>(result: Result<T, E>): T;Example:
unwrap(ok(5)); // => 5unwrap(err(new NotFoundError('HRM_EMP_NOT_FOUND', 'Employee not found.'))); // => throws HRM_EMP_NOT_FOUNDunwrapOr
Returns the value or fallback.
function unwrapOr<T, E>(result: Result<T, E>, fallback: T): T;Example:
unwrapOr(tryCatch(() => JSON.parse('{')), {}); // => {}Classes
Section titled “Classes”AbortError
class AbortError extends HelperError { name: string; constructor(code?: string, message?: string, options?: HelperErrorOptions);}Example:
const e = new AbortError();e.code; // => 'HLP_ABORTED'isAbortError(e); // => trueConflictError
class ConflictError extends HelperError { name: string; constructor(code: string, message: string, options?: HelperErrorOptions);}Example:
new ConflictError('HRM_EMP_VERSION', 'The record was changed by someone else.').kind; // => 'conflict'HelperError
Base error with a stable machine-readable code.
class HelperError extends Error { name: string; readonly code: string; readonly kind: ErrorKind; readonly details: Readonly<Record<string, unknown>>; readonly fields: Readonly<Record<string, string>>; constructor(code: string, message: string, options?: HelperErrorOptions & { readonly kind?: ErrorKind; });}Example:
const e = new HelperError('HLP_JSON_TOO_DEEP', 'JSON nesting exceeds the limit.', { kind: 'limit' });e.code; // => 'HLP_JSON_TOO_DEEP'e.kind; // => 'limit'e instanceof Error; // => trueHttpError
A failed HTTP call: an unsuccessful status, or no response at all (status 0 with category
network, offline, timeout, or cancelled). message is the user-facing message.
class HttpError extends HelperError { name: string; readonly status: number; readonly statusText: string; readonly problem: Readonly<Record<string, unknown>> | undefined; readonly method: string | undefined; readonly url: string | undefined; readonly category: HttpFailureCategory; readonly retryable: boolean; readonly retryAfterMs: number | undefined; readonly requestId: string | undefined; readonly body: unknown; constructor(code: string, message: string, options: HttpErrorOptions); toJSON(): HttpErrorJson;}Example:
const e = new HttpError('HLP_HTTP_404', 'Not found.', { status: 404, method: 'GET', url: '/api/employees/42' });[e.status, e.kind, e.category, e.retryable]; // => [404, 'not-found', 'not-found', false]Example:
const e = new HttpError('SVC_HTTP_STATUS', 'Too many requests.', { status: 429, retryAfterMs: 2000, requestId: 'req-7' });[e.category, e.retryable, e.retryAfterMs]; // => ['rate-limited', true, 2000]JSON.parse(JSON.stringify(e)).requestId; // => 'req-7'LimitError
A limit (size, depth, count, quota) was exceeded.
class LimitError extends HelperError { name: string; constructor(code: string, message: string, options?: HelperErrorOptions);}Example:
const e = new LimitError('HRM_IMPORT_TOO_LARGE', 'The import has too many rows.', { details: { max: 10000 } });[e.kind, e.details]; // => ['limit', { max: 10000 }]NotFoundError
class NotFoundError extends HelperError { name: string; constructor(code: string, message: string, options?: HelperErrorOptions);}Example:
new NotFoundError('HRM_EMP_NOT_FOUND', 'Employee not found.').kind; // => 'not-found'TimeoutError
class TimeoutError extends HelperError { name: string; constructor(code?: string, message?: string, options?: HelperErrorOptions);}Example:
const e = new TimeoutError();[e.code, e.kind, e.message]; // => ['HLP_TIMEOUT', 'timeout', 'The operation timed out.']ValidationError
Input failed validation. fields maps field paths to messages.
class ValidationError extends HelperError { name: string; constructor(code: string, message: string, options?: HelperErrorOptions);}Example:
const e = new ValidationError('HRM_EMP_INVALID', 'Fix the highlighted fields.', { fields: { email: 'Enter a valid email address.' } });e.kind; // => 'validation'e.fields; // => { email: 'Enter a valid email address.' }Interfaces
Section titled “Interfaces”Err
interface Err<E> { readonly ok: false; readonly error: E;}HelperErrorOptions
interface HelperErrorOptions { readonly cause?: unknown; readonly details?: Readonly<Record<string, unknown>>; readonly fields?: Readonly<Record<string, string>>;}HttpErrorJson
Log-safe summary of an HttpError (JSON.stringify uses it).
interface HttpErrorJson { readonly name: string; readonly code: string; readonly message: string; readonly status: number; readonly category: HttpFailureCategory; readonly retryable: boolean; readonly method: string | undefined; readonly url: string | undefined; readonly requestId: string | undefined; readonly retryAfterMs: number | undefined; readonly fields: Readonly<Record<string, string>>; readonly problemType: string | undefined;}HttpErrorOptions
interface HttpErrorOptions extends HelperErrorOptions { readonly status: number; readonly statusText?: string; readonly problem?: Readonly<Record<string, unknown>>; readonly method?: string; readonly url?: string; readonly category?: HttpFailureCategory; readonly retryable?: boolean; readonly retryAfterMs?: number; readonly requestId?: string; readonly body?: unknown;}Ok
interface Ok<T> { readonly ok: true; readonly value: T;}SerializedError
Plain, JSON-safe representation of an error.
interface SerializedError { readonly name: string; readonly message: string; readonly code?: string; readonly kind?: ErrorKind; readonly status?: number; readonly details?: Readonly<Record<string, unknown>>; readonly fields?: Readonly<Record<string, string>>; readonly stack?: string; readonly cause?: SerializedError;}SerializeErrorOptions
interface SerializeErrorOptions { readonly includeStack?: boolean; readonly maxDepth?: number;}ErrorKind
Stable error category used for mapping errors to UI and HTTP responses.
type ErrorKind = 'validation' | 'not-found' | 'conflict' | 'http' | 'timeout' | 'aborted' | 'unauthorized' | 'forbidden' | 'unsupported' | 'limit' | 'internal';HttpFailureCategory
Why an HTTP call failed, for choosing a message, a retry, or a sign-in flow. client covers
the 4xx statuses without a more specific category.
type HttpFailureCategory = 'network' | 'offline' | 'timeout' | 'cancelled' | 'unauthorized' | 'forbidden' | 'not-found' | 'conflict' | 'validation' | 'rate-limited' | 'client' | 'server' | 'unavailable' | 'bad-response' | 'too-large';Result
Discriminated success/failure value for code paths that must not throw.
type Result<T, E = Error> = Ok<T> | Err<E>;