@shirudo/base-error
v8.0.0
Published
A cross-environment base error class for TypeScript applications, designed for seamless use across Node.js, browsers, and edge runtimes.
Maintainers
Readme
@shirudo/base-error
A cross-environment base error class for TypeScript targeting Node.js, modern browsers, and edge runtimes (Cloudflare Workers, Deno Deploy, Vercel Edge). A purely technical core, plus a public-error pipeline that produces safe, localized client-facing output. The core has no client serializer, so it never leaks internal state by default. Zero runtime dependencies.
Features
- 🌐 Cross-platform: Node.js, browsers, edge; rich stack traces, preserved cause chains.
- 🔒 Safe by default: the core has no public serializer; client output is produced only by the public-error pipeline's explicit allowlist.
- 🧱 Structured errors: typed
code/category/retryable/details. - 🎯 Exhaustive
matchError: compile-time-checked dispatch oncode. - 🗂️ Exhaustive class sets: reusable
defineErrorClassSetdefinitions with complete, precisely typed handler tables. - 🧩 Open-world
matchThrown: fluent constructor and guard matching for arbitrary caught values. - 🧭 General error guards: narrow native, Node.js-style, and custom errors without casts.
- 📒 Error catalog:
defineErrorsprovides namespaced factories, immutable metadata, provenance guards, and catalog-level redaction. - ✅ Validation aggregate: collect field issues (Standard Schema compatible) into one error.
- 🔁 Wire round-trip:
toLogObject/fromJSONfor same-context reconstruction & log replay. - 🌍 Public error pipeline:
@shirudo/base-error/public-errorturns an error into a curated view, an optional localized variant, and an RFC 9457application/problem+jsonbody, all from one descriptor per public code. - 🛡️ PII redaction: opt-in, sticky log-path redaction (
redact/redactAllow/partialMask).
Installation
npm install @shirudo/base-errorQuick start
import { StructuredError, matchError } from "@shirudo/base-error";
class UserNotFoundError extends StructuredError<"USER_NOT_FOUND", "NOT_FOUND"> {
constructor(userId: string) {
super({
code: "USER_NOT_FOUND",
category: "NOT_FOUND",
retryable: false,
message: `User ${userId} not found in primary db`, // technical (for logs)
});
}
}
const err = new UserNotFoundError("123");
// The technical truth goes to your logger:
logger.error(err.toLogObject()); // message, stack, cause, details
// Exhaustive handling on the stable code:
const status = matchError(err, {
USER_NOT_FOUND: () => 404,
_: () => 500,
});For safe, client-facing output, use the public-error pipeline
(@shirudo/base-error/public-error): register your public errors in a catalog,
then project an error to a curated view, optionally localize it, and map it to
an RFC 9457 body with toProblem. See the public-error guide.
Main types
| Type | Layer | What it is |
| --- | --- | --- |
| BaseError | Core | Cross-runtime base error: preserved cause chain, rich stack, timestamps. |
| StructuredError | Core | The technical error you throw and log: code, category, retryable, details. |
| PublicError | Boundary | The safe, message-free view of an error; what crosses to the client. |
| LocalizedPublicError | Boundary | PublicError plus message + locale, only when the backend localizes. |
| ProblemDetails | Boundary | The RFC 9457 application/problem+json HTTP body. |
Core is what you throw and log. The three Boundary types are successive
shapes of the same error on its way out (curate, then optionally localize, then
RFC 9457), not alternatives. The @shirudo/base-error/public-error subpath drives
that flow from one descriptor per public code.
Documentation
The full guide lives in docs/guide/
(run it locally with pnpm docs:dev):
Introduction
Core
- BaseError
- StructuredError
- Error catalog (
defineErrors) - Validation errors
- Matching errors (
matchError) - Cause chains
- Type guards & assertions
Boundaries
Reference
TypeScript
Ships ESM + CommonJS + type declarations. Requires TypeScript 5.x with strict
mode for the full type-safety story.
