You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

okay-error

Package Overview
Dependencies
Maintainers
1
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

okay-error

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

latest
npmnpm
Version
1.0.3
Version published
Weekly downloads
7.3K
8.44%
Maintainers
1
Weekly downloads
 
Created
Source

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

FeatureExample
Typed constructorserr({ type: 'Timeout', ms: 2000 }) or err('Timeout', { ms: 2000 })
map, flatMap, orok(1).map(x=>x+1).flatMap(fn).or(0)
Works with Promiseawait result(fetch(url))
Cause‑chain + optional stack frameannotate(err(...), 'DB', {...})
JSON serialisableJSON.stringify(err('X', {}))
Re‑hydrate after JSONconst plain = result(JSON.parse(raw))

API reference

Constructors

functionpurpose
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

functionpurpose
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

FAQs

Package last updated on 10 Jul 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts