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

okay-error

v1.0.4

Published

A small opinionated library to bring Rust-like results idiomatically to TypeScript.

Readme

okay-error

NPM Version

Typed, chain‑friendly, JSON‑safe Results for TypeScript

A small opinionated TypeScript library providing strongly-typed Result objects with chaining capabilities, inspired by Rust std::result.

Why okay-error?

  • Plain object compatibility - an Ok is { ok: true, value }, an Err is { ok: false, error }. Log it, persist it, send it over the wire.
  • Type‑level errors - every possible failure is visible in the function signature (Result<T, E>), not thrown from the shadows. Rely on the type checker to ensure you handle every possible failure.
  • Cause‑chain built‑in - link any parent error using the cause() helper; walk the cause links later to see the full logical call stack.
  • Ergonomic - helpers map, flatMap, or feel familiar to JS arrays.
  • Re‑hydration - after JSON.parse, call result to get a plain Result object.

Table of Contents


Install

npm i okay-error

Quick tour

From try-catch to Result

Here's how okay-error changes error handling from exceptions to data:

// Traditional approach with try-catch
try {
  const user = getUserById(123);
  const greeting = formatGreeting(user.name);
  console.log(greeting);
} catch (error) {
  // Error source and type information can be ambiguous
  console.error('Something went wrong', error);
}

// Alternative approach with Result
import { ok, err, result, annotate } from 'okay-error';

// Define functions that return Result types
function getUserById(id: number) {
  try {
    if (id <= 0) {
      return err('InvalidId', { id });
    }
    // Simulating database lookup
    const user = { id, name: 'Ada' };
    return ok(user);
  } catch (error) {
    // Convert any unexpected errors
    return err('DbError', { cause: error });
  }
}

// Using the Result-returning function
const userResult = getUserById(123);
if (!userResult.ok) {
  // Typed error handling with precise context
  console.error(`Database error: ${userResult.error.type}`);
  return;
}

// Chain operations on successful results
const greeted = userResult
  .map(u => u.name.toUpperCase())         // Ok<string>
  .flatMap(name =>
    name.startsWith('A')
      ? ok(`Hello ${name}!`)              // Return Ok for success
      : err('NameTooShort', { min: 1 })   // Return Err for failure
  )
  .or('Hi stranger!');                    // Use fallback if any step failed

console.log(greeted);                     // "Hello ADA!"

Propagating context

Context propagation allows you to wrap lower-level errors with higher-level context as they move up through your application's layers so you know where the error occurred.

function readConfig(): Result<string, ConfigErr> { /* ... */ }

function boot(): Result<void, BootErr> {
  const cfg = readConfig();
  if (!cfg.ok) {
    // Add higher-level context while preserving the original error
    return err('BootConfig', { phase: 'init', ...cause(cfg) });
  }
  return ok();
}

How cause works

cause creates a new object { cause: error } that can be spread into your error payload. This creates a discoverable, traceable error chain that's useful for debugging:

Err {
  type: "BootConfig",
  phase: "init",
  cause: Err {
    type: "ConfigFileMissing",
    path: "/etc/app.json",
    cause: Err { type: "IO", errno: "ENOENT" }
  }
}

Working with async operations

okay-error can be used with async code to handle errors as data:

import { result } from 'okay-error';

// Wrap fetch with Result to handle both network and parsing errors
async function fetchUserData(userId: string) {
  // First, handle the network request
  const response = await result(fetch(`/api/users/${userId}`));
  if (!response.ok) {
    return annotate(response, 'NetworkError', { userId });
  }
  
  // Then handle the JSON parsing
  const data = await result(response.value.json());
  if (!data.ok) {
    return annotate(data, 'ParseError', { userId });
  }
  
  // Validate the data
  if (!data.value.name) {
    return err('ValidationError', { 
      userId,
      message: 'User name is required'
    });
  }
  
  return ok(data.value);
}

// Usage with proper error handling
async function displayUserProfile(userId: string) {
  const userData = await fetchUserData(userId);
  
  if (!userData.ok) {
    // Each error has context about where it happened
    switch (userData.error.type) {
      case 'NetworkError':
        console.error('Connection failed');
        break;
      case 'ParseError':
        console.error('Invalid response format');
        break;
      case 'ValidationError':
        console.error(userData.error.message);
        break;
    }
    return;
  }
  
  // Work with the data safely
  console.log(`Welcome, ${userData.value.name}!`);
}

Feature checklist

| ✔ | Feature | Example | | ---------------------------------- | --------------------------------------- | ------- | | Typed constructors | err({ type: 'Timeout', ms: 2000 }) or err('Timeout', { ms: 2000 }) | | map, flatMap, or | ok(1).map(x=>x+1).flatMap(fn).or(0) | | Works with Promise | await result(fetch(url)) | | Cause‑chain + optional stack frame | annotate(err(...), 'DB', {...}) | | JSON serialisable | JSON.stringify(err('X', {})) | | Re‑hydrate after JSON | const plain = result(JSON.parse(raw)) |


API reference

Constructors

| function | purpose | | --------------------- | --------------------------------------------------------- | | ok(value) | success result | | err(type, payload?) | typed error, payload is merged with { type } | | err({ ... }) | error from arbitrary value (object, string, etc) | | result(x) | wrap a sync fn, a Promise, or re‑hydrate a raw object |

Functions

| function | purpose | | ---------------------------------- | --------------------------------------------------- | | cause(error) | wrap an error as a cause for another error | | match(result, { ok, err }) | pattern match on Result (success/failure) | | match(type, cases) | pattern match on a discriminant string (exhaustive) |

Types

type Result<T, E = unknown> = Ok<T> | Err<E>;

JSON round‑trip example

const errOut = err('DbConn', { host: 'db.local' }); // preferred
const raw = JSON.stringify(errOut);

const back = result(JSON.parse(raw)); // re‑hydrated

Error with cause example

import { err, cause } from 'okay-error';

// Preferred: use err(type, payload) and cause()
const ioError = err('IO', { errno: 'ENOENT' });
const configError = err('ConfigFileMissing', { path: '/etc/app.json', ...cause(ioError) });
const bootError = err('BootConfig', { phase: 'init', ...cause(configError) });

// You can also chain inline:
const chained = err('BootConfig', cause(
  err('ConfigFileMissing', cause(
    err('IO', { errno: 'ENOENT' })
  ))
));

// Now you can navigate the error chain
console.log(bootError.error.type);    // 'BootConfig'
console.log(bootError.error.cause.type); // 'ConfigFileMissing'

The cause() helper

The cause(error) function is the idiomatic way to link any parent error as the cause of the current error—this parent could be a lower-level error, a related error, or any error that led to the current one:

const base = err('Base', { info: 123 })
const wrapped = err('Higher', { ...cause(base), context: 'extra' })

// wrapped.error.cause === base

This is preferred over annotate, and is composable for deep error chains.

Pattern matching example

Pattern matching with match

The match function is overloaded:

  • Use match(result, { ok, err }) to branch on Result objects.
  • Use match(type, { ...cases }) to branch on discriminant string unions (exhaustive, type-safe).
  • matchType is now an alias for the discriminant string overload for backwards compatibility.
// Result matching
const result = divide(10, 2);
const message = match(result, {
  ok: (value) => `Result: ${value}`,
  err: (error) => `Error: ${error.message}`
});

console.log(result); // "Result: 5"

// With an error case
const errorResult = divide(10, 0).match({
  ok: (value) => `Result: ${value}`,
  err: (error) => `Error: ${error.message}`
});

console.log(errorResult); // "Error: Cannot divide by zero"

Type Safety and Exhaustiveness

When using match with a discriminant string union, TypeScript will enforce exhaustiveness, ensuring you handle all possible cases. This provides an additional layer of type safety for error handling.

// Define a discriminated union of error types
type ApiError =
  | { type: 'NotFound'; id: string }
  | { type: 'Timeout'; ms: number }
  | { type: 'Unauthorized'; reason: string };

// Function that returns different error types
function fetchData(id: string): Result<{ name: string }, ApiError> {
  // ...
}

// Use match to handle each error type differently
const response = fetchData('slow');

if (!response.ok) {
  const errorMessage = match(response.error.type, {
    NotFound: () => `Item ${response.error.id} could not be found`,
    Timeout: () => `Request timed out after ${response.error.ms}ms`,
    Unauthorized: () => `Access denied: ${response.error.reason}`
  });
  
  console.log(errorMessage); // "Request timed out after 5000ms"
}

// Warning: match requires a discriminated union
// If you're not using a discriminated union, use match instead

License

MIT