npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@shirudo/base-error

v10.0.0

Published

A cross-environment base error class for TypeScript applications, designed for seamless use across Node.js, browsers, and edge runtimes.

Readme

@shirudo/base-error

License npm JSR Bundle Size Tests

Structured errors for TypeScript: the full error for your logs, a safe answer for your clients.

@shirudo/base-error gives each error a stable code, a category and a retryable flag. Your logs get the whole error, with every cause. A client gets only the answer that you registered for that code.

The package runs on Node.js, Bun, Deno, in browsers and on edge runtimes such as Cloudflare Workers. It has no runtime dependencies.

Why

A plain Error carries only text. In a service, that causes three problems:

  • You cannot handle it safely. Code that tests err.message.includes("not found") breaks when somebody changes the text.
  • Your logs lose it. JSON.stringify(new Error("failed")) gives {}. A logger that serializes an error this way loses the message, the stack and every cause.
  • Your clients can see too much. An error handler that sends the error to the client also sends its stack and its database message. That is a known vulnerability (CWE-209).

This library removes the three problems:

| Problem | With @shirudo/base-error | | ------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | Handling by text | You match on a typed code. The compiler tells you when a code has no handler. | | Errors lost in logs | toLogObject() writes the message, the stack, every cause and the details. You can mask fields that hold personal data. | | Leaks to clients | The core cannot serialize an error for a client. A separate pipeline builds the response only from the codes that you registered. |

How it works

An error that you throw takes one of two paths:

                      ┌─▶ toLogObject() ──────────────────▶ your logger
                      │     message, stack, causes, details
throw StructuredError ┤
                      │
                      └─▶ project() ─▶ toProblem() ───────▶ HTTP response
                            registered public codes only (RFC 9457)

Each path has its own import:

| Import | Use it to | | ---------------------------------- | ------------------------------------------------------------------------ | | @shirudo/base-error | Throw, match, inspect and log errors. | | @shirudo/base-error/public-error | Build the response for a client. This is the only path to client output. |

Installation

npm install @shirudo/base-error

The package is also on JSR: deno add jsr:@shirudo/base-error, or npx jsr add @shirudo/base-error.

Quick start

1. Define an error

import { StructuredError } from "@shirudo/base-error";

export class UserNotFoundError extends StructuredError<
  "USER_NOT_FOUND",
  "NOT_FOUND"
> {
  constructor(userId: string, cause?: unknown) {
    super({
      code: "USER_NOT_FOUND",
      category: "NOT_FOUND",
      retryable: false,
      message: `No user ${userId} in the primary database`, // for the log
      details: { userId },
      cause,
    });
  }
}

The message is technical text for the log. The library never sends it to a client, but the default error handler of a framework can. Fastify 5 sends it, and Express 5 sends it when NODE_ENV is not production. Install your own error handler, as step 4 shows.

2. Log and handle it

import { matchErrorCode } from "@shirudo/base-error";

const error = new UserNotFoundError("123", dbError);

logger.error(error.toLogObject());
// { name, message, timestamp, timestampIso, stack,
//   cause: { name: "Error", message: "connection reset by peer", ... },
//   code: "USER_NOT_FOUND", category: "NOT_FOUND", retryable: false,
//   details: { userId: "123" } }

const user = matchErrorCode(error, {
  USER_NOT_FOUND: () => null, // a missing user is "no result" here
  _: (other) => {
    throw other;
  },
}); // null

toLogObject() copies the error into plain data. JSON.stringify(error) gives the same result, so a logger that serializes the error gets the full content. If a caught value has the type unknown, toStructuredError(value) turns it into a StructuredError.

3. Mask personal data in the log (optional)

error.redact(["userId"]);
// toLogObject() and JSON.stringify(error) now write details.userId as "[REDACTED]".

The policy stays on the error. It also applies when another error logs this one as its cause. redactAllow works the other way: it masks every data field that you did not list. console.log(error) prints the error itself, not the log object, so it shows the raw value. Log logObjectOf(error) instead.

4. Answer the client

import {
  definePublicErrors,
  project,
  toProblem,
} from "@shirudo/base-error/public-error";

// Register once, at startup. Only a registered code reaches a client.
const publicErrors = definePublicErrors({
  fallback: { publicCode: "internal_error", status: 500 },
}).registerByCode("USER_NOT_FOUND", {
  publicCode: "user_not_found",
  status: 404,
});

// In the error handler of your HTTP server:
const view = project(publicErrors, error);
const { status, headers, body } = toProblem(publicErrors, view);
// status:  404
// headers: { "content-type": "application/problem+json" }
// body:    { "status": 404, "code": "user_not_found" }

The technical message, the stack and the details do not cross. An error that you did not register gets the fallback: 500 with the code internal_error. The optional localize stage adds a message for the end user in the language of the request.

Features

Define errors

  • StructuredError carries a typed code, a category, a retryable flag and details.
  • defineErrors defines many codes in one place, with typed factories and metadata per code. With a prefix, a catalog derives dotted codes such as billing.card_declined, and its factories need no brackets.
  • StructuredAggregateError collects many failures in one error, for example from Promise.allSettled.
  • ValidationError collects field issues. The issue format is compatible with Standard Schema, and publicIssues() gives the subset that is safe for a client.

Handle errors

  • matchErrorCode dispatches on code. The compiler makes sure that every code has a handler.
  • defineErrorClassSet does the same for a set of error classes.
  • matchThrown matches any caught value by class or by guard.
  • Guards such as isError, isStructuredError, hasErrorCode and isRetryable narrow a caught value without a cast. They also work for an error from a worker or from a second copy of the package.
  • The cause-chain helpers (getRootCause, findInCauseChain, isChainRetryable and more) follow the cause links, also into the members of an AggregateError.

Log errors

  • toLogObject() writes the complete error as plain JSON data: the message, the stack, every cause, the members of an aggregate, and the details.
  • logObjectOf(value) writes the log form of any thrown value: an error of this library, a native Error, or a value that is not an error.
  • The subpaths @shirudo/base-error/pino and @shirudo/base-error/sentry keep the policy of an error in a pino line and in a Sentry event. They add no dependency.
  • redact, redactAllow, partialMask and redactWith mask personal data in the log.
  • StructuredError.fromJSON rebuilds a typed error from a log or from a worker message. structuredClone drops the members of an AggregateError, but this round trip keeps them. AppErrors.fromJSON restores an error of a catalog, with its type and its redaction floor.

Answer clients

  • The public-error pipeline turns an error into a safe view (project), adds a localized message (localize) and builds an RFC 9457 body (toProblem).
  • One registration per public code controls the status, the type, the message and the details that a client can see.

Safe in a catch block

  • No function on the logging, rendering or traversal path throws. A getter that throws, a revoked Proxy or a cyclic cause chain costs only the affected value. The log shows a marker there, or leaves the field out.
  • The work stays bounded for any value that code throws, also for very deep or very wide error graphs.

When to use it

The library fits a service or an API that has more than a few error cases, writes structured logs, or answers HTTP clients. For a small script, a plain Error is enough.

Documentation

The full guide is in docs/guide/. To read it locally, run pnpm docs:dev.

Introduction

Core

Boundaries

Reference

Agent Skill

The package ships an agent skill for AI coding assistants in skills/base-error/, and the npm and JSR packages include it. Copy the folder into your project's skill directory, such as .claude/skills/ for Claude Code or .agents/skills/ for other agents. It teaches the assistant the catalogs, the guards and matchers, the log path with redaction, the setups for pino and Sentry, and the public-error pipeline. It also maps the APIs of older majors onto the APIs of this major version.

Requirements

  • Node.js 24 or a later version. Bun, Deno, modern browsers and edge runtimes work too.
  • TypeScript 5.0 or a later version, with strict mode, for full type safety.
  • lib ES2020 or a later version.
  • For an ES module: module resolution node16, nodenext or bundler.
  • For a CommonJS module: module: nodenext with TypeScript 5.8 or later, or module: node20 with TypeScript 5.9 or later. module: node16 refuses the package (TS1479). module: commonjs (resolution node10) works with TypeScript 5 and 6. TypeScript 7 removed node10.
  • The package ships one ES module build with type declarations. require() loads it on Node.js 24, so import and require load the same classes.

Upgrade

Version 10 ships one ES module build. A TypeScript project that compiles to CommonJS with module: node16 must switch to module: nodenext or module: node20. Read the migration guide before you upgrade from 9.x.

Version 9 changes the log output and some APIs. For example, it removes buildLogObject() in favor of buildOwnLogFields(). Read the migration guide before you upgrade from 8.x.

License

MIT