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

@ev3rlit/errorable

v0.1.3

Published

TypeScript error handling inspired by Go errors and the Go samber/oops library.

Downloads

14

Readme

errorable

errorable is a TypeScript error-handling library built in the style of Go.

It explicitly follows two Go ideas:

  • the standard errors package traversal model: join, unwrap, is, as
  • a structured error builder inspired by the Go oops library

This project is based on:

  • Go's standard errors package design
  • the Go samber/oops library

This is not an official port of samber/oops. It is a TypeScript library that adopts the same overall philosophy and adapts it to the JavaScript/TypeScript runtime model.

Install

npm install @ev3rlit/errorable

Quick start

import { errorable, is, as, join } from "@ev3rlit/errorable";

const err = errorable()
  .in("user-service")
  .tags("database", "postgres")
  .code("network_failure")
  .public("Unable to load user information.")
  .with("requestId", "req-123")
  .errorf("failed to fetch user: %s", "connection timeout");

const wrapped = errorable()
  .trace("trace-123")
  .with("productId", "456")
  .wrapf(err, "user operation failed");

console.log(wrapped?.toJSON());
console.log(is(wrapped, err));
console.log(as(wrapped, Error)?.message);
console.log(join(err, new Error("secondary failure"))?.message);

Design origin

errorable should be read as a Go-style library first, not as a Result-type library.

  • Error traversal is intentionally modeled after Go errors
  • Structured wrapping and metadata are intentionally modeled after Go oops
  • The API is adapted to TypeScript and native JavaScript Error, cause, and stack behavior

API

errors helpers

  • newError(message)
  • join(...errors)
  • unwrap(error)
  • is(error, target)
  • as(error, matcher)

errorable helpers

  • errorable() to create an immutable builder
  • builder methods: .code() (string or integer), .in(), .tags(), .trace(), .span(), .with(), .withContext(), .hint(), .public(), .owner(), .user(), .tenant()
  • terminal methods: .new(), .errorf(), .wrap(), .wrapf(), .join(), .recover(), .recoverf()
  • assertions: assert(), assertf()
  • structured extraction: error.context(), error.tags(), error.code(), error.toJSON(), getPublic(error, fallback)

API design guidance

In normal TypeScript application code, prefer Error as your external contract.

  • return Error from public boundaries and shared interfaces
  • create errors with errorable() internally when you want richer metadata
  • keep the public shape simple and let logging or boundary code consume the extra metadata

Example:

import { errorable } from "@ev3rlit/errorable";

function loadUser(): Error {
  return errorable()
    .in("user-service")
    .code("user_load_failed")
    .errorf("failed to load user");
}

throw loadUser();

Using with neverthrow

errorable can be used together with neverthrow.

  • neverthrow models control flow with Result<T, E>
  • errorable makes the error value itself richer and easier to debug

Example:

import { err, ok, type Result } from "neverthrow";
import { errorable } from "@ev3rlit/errorable";

type User = {
  id: string;
  name: string;
};

function loadUser(id: string): Result<User, Error> {
  try {
    const user = fetchUserFromSomewhere(id);
    return ok(user);
  } catch (cause) {
    const baseError = cause instanceof Error ? cause : new Error(String(cause));

    return err(
      errorable()
        .in("user-service")
        .code("user_load_failed")
        .with("userId", id)
        .wrapf(baseError, "failed to load user")!,
    );
  }
}

This works well when:

  • you want neverthrow to enforce explicit success/failure handling
  • you want errorable to preserve stacktrace, code, trace/span, and structured metadata

Benchmarking

Latest benchmark snapshot:

  • measured on March 10, 2026
  • machine: Apple M1 Max
  • runtime: Node v22.16.0
  • benchmark runner: vitest bench with time-based sampling

Hot-path results from the latest run:

| Scenario | Mean/op | | --- | ---: | | errorf() flat creation | 41.6 us/op | | wrap() plain Error | 42.0 us/op | | wrapf() create depth 10 chain | 550.2 us/op | | code() first-hit depth 25 | 1.7 us/op | | code() fallback-to-cause depth 25 | 1.9 us/op | | code({ codeSource: "deepest" }) depth 25 | 1.6 us/op | | toJSON() filtered depth 10 | 152.0 us/op | | toJSON({ frameFilter: "all" }) depth 10 | 91.8 us/op | | stacktrace() filtered depth 10 | 60.0 us/op | | stacktrace({ frameFilter: "all" }) depth 10 | 5.5 us/op |

Depth sweep from the latest run:

| Depth | Create | code() | toJSON() | stacktrace() | | --- | ---: | ---: | ---: | ---: | | 1 | 0.0986 ms | 0.0002 ms | 0.0166 ms | 0.0115 ms | | 10 | 0.5546 ms | 0.0007 ms | 0.2043 ms | 0.0600 ms | | 100 | 5.0212 ms | 0.0081 ms | 7.2669 ms | 0.5659 ms | | 500 | 28.7357 ms | 0.0411 ms | 182.8077 ms | 2.6056 ms |

Deep probe from the latest run:

| Depth | Create | code() | code({ codeSource: "deepest" }) | | --- | ---: | ---: | ---: | | 1000 | 64.2984 ms | 0.0861 ms | 0.0976 ms | | 2000 | 139.8489 ms | 0.1867 ms | 0.1976 ms | | 4000 | 411.9070 ms | 0.4683 ms | 0.4114 ms |

What these numbers suggest:

  • errorf() and wrap() are cheap enough to use freely on normal error paths.
  • code() lookup remains very cheap even on deep chains.
  • toJSON() is the most expensive read path and scales the fastest with chain depth.
  • filtered stack output costs more than raw stack output because frame filtering runs on every read.

These numbers are machine-dependent. Use them as a relative cost snapshot and for regression comparison, not as fixed universal guarantees.

The benchmark suite lives in bench/errorable.bench.ts. For fresh local numbers run npm run bench, and for CI comparison output run npm run bench:json, which writes bench/results/latest.json.

Vitest's raw console output follows tinybench conventions and shows hz, min, max, mean, p75, p99, rme, and samples. In this README the results are rewritten into us/op and ms/op, which is usually easier to read if you are used to Go-style benchmark tables such as ns/op, B/op, and allocs/op.

The JSON report is useful when you want to compare benchmark runs in CI or post-process them into your own table format. The top-level shape looks like this:

{
  "files": [
    {
      "filepath": "/path/to/bench/errorable.bench.ts",
      "groups": [
        {
          "fullName": "bench/errorable.bench.ts > microbenchmarks",
          "benchmarks": [
            {
              "name": "errorf() flat creation",
              "hz": 24010.8631757659,
              "mean": 0.04164781551915624,
              "min": 0.032999999999901775,
              "max": 1.0425000000000182,
              "p75": 0.03712499999983265,
              "p99": 0.10887499999989814,
              "rme": 3.0800002877322834,
              "sampleCount": 7204
            }
          ]
        }
      ]
    }
  ]
}

Useful fields in bench/results/latest.json:

  • files[].groups[].fullName: benchmark suite name such as microbenchmarks or depth probe
  • files[].groups[].benchmarks[].name: individual benchmark case name
  • hz: operations per second
  • mean: average time per operation in milliseconds
  • min / max: fastest and slowest recorded sample in milliseconds
  • p75 / p99: latency percentiles in milliseconds
  • rme: relative margin of error
  • sampleCount: number of collected samples

Publishing

Use the release helper scripts to let the repository handle version bump, release commit, tag creation, and tag push:

npm run release:patch
npm run release:minor
npm run release:major

These commands:

  • require a clean git worktree
  • require the current branch to be main
  • bump package.json and package-lock.json
  • run typecheck, test, and build
  • create a release commit
  • create an annotated vX.Y.Z tag
  • push main and the tag to origin

After the tag is pushed, GitHub Actions publishes the package to npm.

If you already changed the version manually and only want to publish that exact version, use:

npm run release:publish

Notes

  • The project targets Node.js and uses native Error.cause.
  • Formatting follows Node's util.format, so %s, %d, %j style placeholders work.
  • Errors created by errorable() capture stack frames at creation time and expose structured metadata helpers such as stackFrames(), stacktrace(), and toJSON().
  • The stacktrace model filters internal/runtime frames by default, but stackFrames(), stacktrace(), and toJSON() can expose all frames with { frameFilter: "all" }.