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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@exodus/errors

v3.5.0

Published

Utilities for error handling in client code, such as sanitization

Readme

@exodus/errors

Usage

import { SafeError } from '@exodus/errors'

try {
  throw new Error('Something went wrong')
} catch (e) {
  const safeError = SafeError.from(e)

  // It's now safe to report or log the error, even to a remote server.
  console.error({
    name: safeError.name, // Sanitized error name.
    code: safeError.code, // Optional error code (if present).
    hint: safeError.hint, // Optional error hint (if present).
    stack: safeError.stack, // Sanitized stack trace.
    timestamp: safeError.timestamp, // When the error occurred.
  })
}

FAQ

Why using SafeError instead of built-in Errors?

In large codebases, errors can be thrown from anywhere, making it impossible to audit every error message for sensitive information. A single error containing sensitive data could potentially expose user information. Centralizing error handling with SafeError makes it possible to enforce security and consistency across the board, by ensuring:

  1. Controlled Error Flow: All errors go through a single, well-tested sanitization layer before they hit error tracking systems.
  2. Enforceable Security: Error handling can be owned through codeowners and covered by tests, so nothing slips through unnoticed.

In addition to enforcing these practices, SafeError includes a few key design decisions that make it safer and more reliable than native Error objects:

  1. Message Sanitization: The message property from built-in Errors is intentionally omitted as it often contains sensitive information. Instead, a hint property is used that contains only sanitized, non-sensitive information.
  2. Native Stack Parsing: The library uses the Error.prepareStackTrace API to parse stack traces, providing consistent and reliable stack trace information across different JavaScript environments.
  3. Immutability: Once created, a SafeError instance cannot be modified, preventing tampering with error data.
  4. Safe Serialization: The toJSON method ensures safe serialization for logging or sending to error tracking services.

Why not redacting Error messages?

Parsing/sanitization of error messages is unreliable and the cost of failure is potential loss of user funds and permanent reputation damage.

Why not use the built-in .stack Error property?

Unfortunately, the built-in .stack property is mutable and outside of our control. Instead, we use the Error.prepareStackTrace API, which enables us to make sure we access the actual call stack and not a cached err.stack value that may have already been consumed and modified. We then parse it into a structured format that we can safely sanitize and control. This approach provides consistent, reliable stack traces across different environments (currently supporting both V8 and Hermes).

Recipes

[!TIP] Before diving into the recipes, you might want to get familiar with what a 'Safe String' is: https://github.com/ExodusMovement/exodus-hydra/tree/master/libraries/safe-string

I want to preserve server errors in Safe Errors

Do you control the server? If so, better send short error codes from your server instead. err.code will be passed through SafeError coercion.

If you do NOT control the server, and you know the specific error messages returned by the server, you can predefine them wrapped in safeString:

import { safeString } from '@exodus/safe-string'

// From: https://github.com/ethereum/go-ethereum/blob/master/core/txpool/errors.go.
export const KnownErrors = new Set([
  safeString`already known`,
  safeString`invalid sender`,
  safeString`transaction underpriced`,
])

You can now handle failed requests like this:

import { safeString } from '@exodus/safe-string`
import { KnownErrors } from './errors.js'

const response = await this.request(request)
const error = response?.error

if (error) {
  const message = KnownErrors.get(msg) ?? safeString`unknown error`
  throw new Error(safeString`Bad rpc response: ${message}`)
}

Troubleshooting

A SafeError instance returns undefined stack trace?

That likely means that something accesses error.stack before the Safe Error constructor had a chance to apply the custom Error.prepareStackTrace handler. This could be React, a high-level error handler, or any other framework. error.stack is computed only on the first access, so the custom handler won’t be called on subsequent attempts (see the stack.test.js for a quick demo).

If you can identify the exact place where .stack is accessed, consider capturing the stack trace explicitly like this:

import { captureStackTrace, SafeError } from '@exodus/errors'

try {
  // the devil's work
} catch (e) {
  captureStackTrace(e)
  void e.stack // Intentionally access the property to "break" it here.
  SafeError.from(e).stack // A non-empty string!
  // 🎉 Congrats — you just (hopefully) saved hours of debugging! The custom stack trace parsing logic now works because the stack was captured explicitly above.
}