Socket
Book a DemoSign in
Socket

okay-error

Package Overview
Dependencies
Maintainers
1
Versions
11
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.

Source
npmnpm
Version
0.2.1
Version published
Weekly downloads
8K
10.81%
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 - wrap lower‑level errors with the .annotate() method; walk the cause links later to see the full logical call stack.
  • Iterable & ergonomic - for (const v of ok(3)) … works, and helpers map, flatMap, or feel familiar to JS arrays.
  • Re‑hydration - after JSON.parse, call result(raw) to get the fluent API back.

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 } 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 cfg.annotate('BootConfig', { phase: 'init' });
  }
  return ok();
}

How annotation works

.annotate() creates a new error that wraps the original error:

  • The original error becomes the cause property of the new error
  • Any additional payload properties are merged into the new error

This creates a discoverable, traceable error chain that's invaluable 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 response.annotate('NetworkError', { userId });
  }
  
  // Then handle the JSON parsing
  const data = await result(response.value.json());
  if (!data.ok) {
    return data.annotate('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('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 frameerr(...).annotate('DB', {...})
JSON serialisable & iterableJSON.stringify(err('X')()), [...ok(7)]
Re‑hydrate after JSONconst live = result(JSON.parse(raw))

API reference

Constructors

functionpurpose
ok(value)success result
err(kind)(payload?)typed error + trace
errAny(value)error without a discriminant / trace
result(x)wrap a sync fn, a Promise, or re‑hydrate a raw object

Instance methods (on Ok & Err)

methodon Okon Err
map(fn)transform valueno‑op
mapErr(fn)no‑optransform error
flatMap(fn)chain another Resultpropagate error
match(arms)run ok armrun err arm
matchType(cases)N/Amatch on error type
unwrap()get valuethrow error
or(fallback)valuefallback
[Symbol.iterator]()yields valueyields nothing

Instance Methods

  • .annotate(kind, payload?) – add context + cause (on Err instances only)

Types

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

JSON round‑trip example

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

const back = result(JSON.parse(raw)); // re‑hydrated
for (const v of back) console.log(v); // nothing, because Err

Error with cause example

// Create an error chain
const ioError = err('IO')({ errno: 'ENOENT' });
const configError = ioError.annotate('ConfigFileMissing', { path: '/etc/app.json' });
const bootError = configError.annotate('BootConfig', { phase: 'init' });

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

Pattern matching example

Basic pattern matching with match

// Define a function that returns a Result
function divide(a: number, b: number): Result<number, { type: string; message: string }> {
  if (b === 0) {
    return err('DivideByZero')({ message: 'Cannot divide by zero' });
  }
  return ok(a / b);
}

// Use match to handle both success and error cases in one expression
const result = divide(10, 2).match({
  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-based pattern matching with matchType

// 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> {
  // Simulate different errors based on input
  if (id === '404') {
    return err('NotFound')({ id });
  } else if (id === 'slow') {
    return err('Timeout')({ ms: 5000 });
  } else if (id === 'auth') {
    return err('Unauthorized')({ reason: 'Token expired' });
  }
  
  return ok({ name: 'Sample Data' });
}

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

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

The matchType method provides exhaustive pattern matching for discriminated union error types, ensuring you handle all possible error cases at compile time.

License

MIT

FAQs

Package last updated on 14 May 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