@shirudo/base-error
Version:
A cross-environment base error class for TypeScript applications, designed for seamless use across Node.js, browsers, and edge runtimes.
122 lines (91 loc) • 6.01 kB
Markdown
# @shirudo/base-error
[](LICENSE)
[](https://www.npmjs.com/package/@shirudo/base-error)
[](https://bundlephobia.com/package/@shirudo/base-error)
[](https://github.com/shi-rudo/base-error-ts/actions/workflows/tests.yml)
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 on `code`.
- 🗂️ **Exhaustive class sets**: reusable `defineErrorClassSet` definitions 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**: `defineErrors` provides 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` / `fromJSON` for same-context reconstruction & log replay.
- 🌍 **Public error pipeline**: `@shirudo/base-error/public-error` turns an error into a curated view, an optional localized variant, and an RFC 9457 `application/problem+json` body, all from one descriptor per public code.
- 🛡️ **PII redaction**: opt-in, sticky log-path redaction (`redact` / `redactAllow` / `partialMask`).
## Installation
```bash
npm install @shirudo/base-error
```
## Quick start
```ts
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](docs/guide/public-error.md).
## 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/`](https://github.com/shi-rudo/base-error-ts/tree/main/docs/guide)
(run it locally with `pnpm docs:dev`):
**Introduction**
- [Getting started](https://github.com/shi-rudo/base-error-ts/blob/main/docs/guide/getting-started.md)
- [Why safe by default](https://github.com/shi-rudo/base-error-ts/blob/main/docs/guide/safe-by-default.md)
- [Pitfalls](https://github.com/shi-rudo/base-error-ts/blob/main/docs/guide/pitfalls.md)
**Core**
- [BaseError](https://github.com/shi-rudo/base-error-ts/blob/main/docs/guide/base-error.md)
- [StructuredError](https://github.com/shi-rudo/base-error-ts/blob/main/docs/guide/structured-error.md)
- [Error catalog (`defineErrors`)](https://github.com/shi-rudo/base-error-ts/blob/main/docs/guide/catalog.md)
- [Validation errors](https://github.com/shi-rudo/base-error-ts/blob/main/docs/guide/validation.md)
- [Matching errors (`matchError`)](https://github.com/shi-rudo/base-error-ts/blob/main/docs/guide/matching.md)
- [Cause chains](https://github.com/shi-rudo/base-error-ts/blob/main/docs/guide/cause-chains.md)
- [Type guards & assertions](https://github.com/shi-rudo/base-error-ts/blob/main/docs/guide/guards.md)
**Boundaries**
- [Public error pipeline](https://github.com/shi-rudo/base-error-ts/blob/main/docs/guide/public-error.md)
- [Observability & logging (incl. PII redaction & `fromJSON`)](https://github.com/shi-rudo/base-error-ts/blob/main/docs/guide/observability.md)
**Reference**
- [Migration](https://github.com/shi-rudo/base-error-ts/blob/main/docs/guide/migration.md)
- [Changelog](CHANGELOG.md)
## TypeScript
Ships ESM + CommonJS + type declarations. Requires TypeScript 5.x with `strict`
mode for the full type-safety story.
## License
[MIT](LICENSE)