DeesseJS Errors

API Reference

Complete API reference for all exports from @deessejs/errors.

This page documents all public exports from @deessejs/errors.

error()

Creates an error factory function for defining typed, structured errors.

factory.ts
const errorFactory = error<TFields>(config)

There are two public overloads:

  • No-schema overload: error<T>({ name, message?, inherits? }). The call signature accepts no arguments (or an empty object) when T is the empty shape; otherwise the input argument is required.
  • Schema overload: error({ name, fields, message, inherits? }). The factory's call signature requires the schema's InferInput as the argument. A function-form message is required; a string message is not accepted alongside a schema.

Parameters

ParameterTypeDescription
config.namestringError name identifier (required)
config.messagestring or functionString template (legacy, {field} placeholders) or (data) => string taking the validated (post-transform) fields. Required to be a function when a schema is supplied.
config.fieldsStandardSchemaV1Field validation schema (Zod, Valibot, ArkType, etc.). The schema's output must be a non-null, non-array object — see Fields and Schema.
config.inheritsErrorFactory or ErrorFactory[]Parent error(s) to inherit from. The leaf's InferOutput must be assignable to every parent's InferOutput (enforced at the call site by the contravariance witness).

When fields is supplied, the input is validated against the schema at every call. Invalid inputs throw an ArgsValidationError. When message is a function, it receives the parsed (post-transform) data as its argument.

Returns

An ErrorFactory function that creates error instances. The factory has these properties:

PropertyTypeDescription
namestringThe error name
inheritsErrorFactory or ErrorFactory[] or undefinedParent error types
schemaStandardSchemaV1 or undefinedThe validation schema (exposed via the factory)
rawMessagestring or function or undefinedOriginal message template (string) or function, set when message is supplied

Example

factory.ts
import { z } from "zod";
import { error } from "@deessejs/errors";

const AppError = error({ name: "AppError" });

const ValidationError = error({
  name: "ValidationError",
  fields: z.object({ field: z.string() }),
  message: (data) => `Field "${data.field}" is invalid`,
  inherits: AppError,
});

const err = ValidationError({ field: "email" });

raise()

Throws an error instance. The primary mechanism for throwing errors in @deessejs/errors.

raise.ts
raise(error: ErrorInstance): never

Parameters

ParameterTypeDescription
errorErrorInstanceThe error to throw

Returns

never - this function always throws.

Example

raise.ts
import { error, raise, is } from "@deessejs/errors";

const ValidationError = error({ name: "ValidationError" });

try {
  // The no-schema factory takes no arguments.
  raise(ValidationError());
} catch (err) {
  console.log(is(err, ValidationError)); // -> true
}

is()

Type guard function to check if an error is an instance of a specific error type. Two overloads are exposed:

  • Factory overload: is(error, factory) narrows the input to ErrorInstance<ExtractFactoryFields<typeof factory>>. The walk traverses the factory's inherits graph (depth-first, with cycle detection) and consults the package-private INSTANCE_REGISTRY to reject foreign objects that imitate the factory marker.
  • Native constructor overload: is(error, NativeErrorClass) narrows the input to InstanceType<typeof NativeErrorClass>. A native Error subclass carries no .fields, .notes, .from(), or .addNote() extensions.

is() is per-package-load: an instance created by a second copy of @deessejs/errors (a duplicate in node_modules, a separate bundle, a CommonJS/ESM dual load) is registered in that copy's internal registry, not ours. Holding a direct reference to its factory is not sufficient — the registry check fails, and is() returns false. To recognize cross-load instances, the consumer must call is() from the same load that produced the instance.

is.ts
const result = is(error: unknown, ErrorType): error is ErrorInstance

Parameters

ParameterTypeDescription
errorunknownThe error to check
ErrorTypeErrorFactory or ErrorClassThe error type to check against

Returns

boolean - true if the error matches or inherits from the type. Narrows the input type for TypeScript.

Example

is.ts
import { error, is } from "@deessejs/errors";

const AppError = error({ name: "AppError" });
const ValidationError = error({
  name: "ValidationError",
  inherits: AppError,
});

// The no-schema factory has an empty input shape.
const err = ValidationError();

if (is(err, ValidationError)) {
  console.log("ValidationError matched");
}
if (is(err, AppError)) {
  console.log("AppError matched via inheritance");
}

// Native error discrimination works too.
try {
  JSON.parse("invalid");
} catch (e) {
  if (is(e, SyntaxError)) {
    console.log("SyntaxError caught");
  }
}

causes()

Returns all causes in an error chain, ordered immediate cause first, root cause last. The walk follows each cause's own .cause link with cycle detection.

causes.ts
const chain = causes(error: unknown): Error[]

Parameters

ParameterTypeDescription
errorunknownThe error to get causes from

Returns

Error[] - array of errors in the cause chain. Returns an empty array for null, undefined, or errors without causes. The returned array is a fresh copy; mutations do not affect the underlying error.

Example

causes.ts
import { error, causes } from "@deessejs/errors";

const AppError = error({ name: "AppError" });
const ValidationError = error({ name: "ValidationError" });

// Build the chain by attaching each cause to the previous one.
// The .from() call replaces the previous direct cause.
const root = new Error("Connection timeout");
const middle = ValidationError().from(root);
const appErr = AppError().from(middle);

const chain = causes(appErr);
console.log(chain.length); // 2 — [middle, root]

ArgsValidationError

Thrown when input data fails a Standard Schema validator attached to fields. The error carries the original validator failure information, branded as an @deessejs/errors instance.

args-validation.ts
class ArgsValidationError extends Error

Constructor

ParameterTypeDescription
sourcestringName of the originating factory
issuesReadonlyArray<StandardSchemaV1.Issue>Read-only array of validator issues (per Standard Schema spec)
vendorstringVendor identifier (e.g. zod, valibot, arktype)

Instance properties

PropertyTypeDescription
sourcestringThe originating factory name
issuesReadonlyArray<StandardSchemaV1.Issue>Validator issues
vendorstringVendor identifier

Message format

Argument validation failed for "<source>": see .issues

Example

args-validation.ts
import { z } from "zod";
import { error, ArgsValidationError } from "@deessejs/errors";

const ValidationError = error({
  name: "ValidationError",
  fields: z.object({ field: z.string().min(1) }),
  message: (data) => "Field " + data.field,
});

try {
  ValidationError({ field: "" });
} catch (err) {
  if (err instanceof ArgsValidationError) {
    console.log(err.vendor);
    console.log(err.issues);
  }
}

ErrorInstance

The type of object returned by error factories. It extends the native Error type.

Properties

PropertyTypeDescription
namestringError name identifier
messagestringHuman-readable error message
stackstringStack trace string
fieldsTValidated (post-transform) fields, or the empty record for the no-schema form
notesstring[]Additional notes (PEP 678)
causeError or nullDirect cause of this error. Each .from() call replaces the previous cause; the previous cause is reachable through its own .cause link. Use the top-level causes() function to walk the full chain.
contextRecord<string, unknown> or nullInjected context data
inheritsErrorFactory or ErrorFactory[] or undefinedParent error factories (frozen snapshot taken at construction)

Methods

MethodDescription
from(cause: Error or ErrorInstance)Chains a cause error to this error. Replaces the previous direct cause. Returns this for chaining.
addNote(note: string)Appends a runtime context note (PEP 678). Returns this for chaining.

See Also


Constraints

The package's contracts depend on a few consumer-side guarantees. Each is enforced at the boundary that owns it; understanding them helps avoid footguns.

TypeScript

strictFunctionTypes is required. The contravariance witness that enforces inheritance compatibility relies on function parameters being checked contravariantly. Without strictFunctionTypes, the inheritance constraint is bypassed at compile time. strictFunctionTypes is enabled by strict, which is on in this project's tsconfig.

TypeScript 5.4 or later. The public error() overloads use NoInfer<T>, which was introduced in TypeScript 5.4. Earlier versions compile but lose the NoInfer semantics on the T parameter.

Schemas

Output must be a non-null, non-array object. A schema whose InferOutput is null, an unknown[], a primitive, or undefined is rejected at compile time via the fields: S & (IsObjectOutput<InferOutput<S>> extends true ? unknown : never) intersection, and at runtime via the isObjectFields guard that throws ArgsValidationError if the schema's value is malformed.

Schemas must be synchronous. A schema whose validate returns a Promise is rejected with ArgsValidationError. The Promise is still attached and a no-op .catch is added so a late rejection cannot surface as an unhandledRejection.

Input is required when a schema is supplied. A factory declared with a Standard Schema refuses a no-arg call at compile time. The runtime also throws a localized TypeError for the schema-bearing path so consumers who bypass the type-checker still get a clear message. This applies even when the schema accepts the empty shape (z.object({})).

.fields carries the post-transform output. For schemas that transform (e.g. z.coerce, z.default, z.transform), the leaf's .fields is the schema's InferOutput, not the InferInput. The factory's message function receives the post-transform output.

is() and the instance registry

is() is per-package-load. An instance created by a second copy of @deessejs/errors (a duplicate in node_modules, a separate bundle, a CommonJS/ESM dual load, two copies in a micro-frontend) is registered in that copy's internal INSTANCE_REGISTRY (a WeakSet), not ours. Holding a direct reference to its factory is not sufficient: the marker slot is read and matches, but the registry check fails, and is() returns false. To recognize cross-load instances, the consumer must call is() from the same load that produced the instance.

The instance marker is non-writable on real instances. Set via Object.defineProperty with writable: false. A foreign object that imitates the marker is rejected by the registry check; a hostile object that re-defines the marker on a real instance is rejected in strict mode (the assignment throws) and silently fails in sloppy mode (the marker is unchanged).

Inheritance

The factory's inherits slot is a frozen snapshot. Built once at construction, before the closure, and used for both the factory's metadata and every produced instance's inherits. The caller's array is not mutated in place; classification is preserved across caller-side mutations on the original list.

Each factory runs its own schema. Inheritance is a static type-level relationship plus a recognition walk for is(). The parent schema does not validate the child's data at instantiation. Consumers who relied on the parent-schema cascade to coerce a child field through a parent's transform must compose the schema themselves.

Legacy deprecation

The string-template form (error({ name, message: 'Hello {name}' })) is deprecated in 2.0.0 and emits a one-time warning per call site, only when the message is a string with {field} placeholders, and only when process.env.DEESSEJS_ERRORS_LEGACY_TEMPLATES is not '1'. A plain string without placeholders and a function-form message do not warn. The warning is suppressed in browser bundles (process is read only when defined).

On this page