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

@shirudo/result

v0.0.4

Published

Robust, type-safe error handling for TypeScript using the Result pattern (Monad)

Readme

@shirudo/result

Robust, type-safe error handling for TypeScript.

⚠️ Beta Notice: This library is currently in beta. The API may change before the stable release. Use with caution in production environments.

@shirudo/result brings the power of the Result pattern (Monad) to TypeScript. It helps you write safer, more predictable code by treating errors as values rather than exceptions. Stop guessing if a function will throw—let the type system guide you.

TypeScript Runtime License Beta

🌟 Key Features

  • Type-Safe: generic Result<T, E> type discriminates between Success (Ok) and Failure (Err).
  • Pipeable Architecture: Functional, tree-shakeable operators via .pipe() and .pipeAsync().
  • Async Support: First-class support for Promises and async transformations.
  • Do-Notation: A task generator utility to write sequential code without callback hell (similar to Rust's ? operator).
  • Rich Pattern Matching: Fluent builders for exhaustive matching and error handling.
  • Comprehensive Utilities: Helpers for collections, conversion from/to Promises, Nullables, and try/catch blocks.

📦 Installation

npm install @shirudo/result
# or
pnpm add @shirudo/result
# or
yarn add @shirudo/result

🚀 Quick Start

Basic Usage

Instead of throwing errors, return a Result.

import { ok, err, Result } from "@shirudo/result";

function divide(a: number, b: number): Result<number, string> {
  if (b === 0) {
    return err("Division by zero");
  }
  return ok(a / b);
}

const result = divide(10, 2);

if (result.isOk()) {
  // TypeScript narrows 'result' to Ok<number>
  console.log("Success:", result.value); // 5
} else {
  // TypeScript narrows 'result' to Err<string>
  console.error("Error:", result.error);
}

Functional Pipelines

Use .pipe() to chain operations. If an error occurs at any step, the chain short-circuits and returns the Err.

import { ok, map, filter, mapErr } from "@shirudo/result";

const processed = ok(10).pipe(
  map((n) => n * 2), // 20
  filter(
    (n) => n > 50,
    () => "Too small"
  ), // Returns Err('Too small')
  mapErr((e) => `Error: ${e}`) // Transforms the error message
);

console.log(processed.isErr()); // true
console.log(processed.unwrapOr(0)); // 0 (fallback)

💡 Core Concepts

Creating Results

There are several static factories to help you wrap existing code or values.

import { Result } from "@shirudo/result";

// Standard
const a = Result.ok(42);
const b = Result.err("Something went wrong");

// From a function that might throw
const json = Result.try(() => JSON.parse('{"valid": true}'));

// From a potentially null/undefined value
const user = Result.fromNullable(maybeUser, "User not found");

// From a Promise (catches rejections)
const asyncRes = await Result.fromPromise(fetch("/api/data"));

Async Pipelines

Transforming async results is seamless with .pipeAsync().

import { ok, mapAsync, tryCatchAsync } from "@shirudo/result";

const result = await ok(1).pipeAsync(
  mapAsync(async (id) => {
    const user = await db.getUser(id);
    return user.name;
  }),
  tryCatchAsync(async (name) => {
    // If this throws, it becomes an Err
    return await externalService.validate(name);
  })
);

Generator "Do-Notation" (task)

The task (or gen) utility allows you to write code that looks imperative but handles Result flow control automatically. Use yield* to unwrap Ok values; if an Err is yielded, the function returns early with that error.

import { task, ok, err } from "@shirudo/result";

const calculate = task(function* () {
  // yield* automatically unwraps the value if Ok
  const x = yield* ok(10);
  const y = yield* ok(20);

  // If this were err(), execution would stop here and return that err
  const z = yield* validate(x + y);

  return z; // Returns Ok(z)
});

// calculate is a Promise<Result<number, Error>>

Error Handling with onThrow

The task() function accepts an optional onThrow callback for custom error mapping:

const result = await task(
  function* () {
    const data = yield* fetchData();
    return process(data);
  },
  (error) => new CustomError(`Failed: ${error}`)  // custom error mapping
);
// Returns Result<ProcessedData, CustomError>

Folding Results

The simplest way to handle both Ok and Err cases and return a single value:

import { ok, err } from "@shirudo/result";

const result = ok(42);

const message = result.fold(
  (val) => `Success: ${val}`,
  (err) => `Error: ${err}`
);
// message = "Success: 42"

// Useful for side effects
result.fold(
  (val) => console.log("Yay:", val),
  (err) => console.error("Nay:", err)
);

// Convert to HTTP response
const response = result.fold(
  (data) => ({ status: 200, body: data }),
  (error) => ({ status: 500, body: { error } })
);

Pattern Matching

Handle errors exhaustively using the fluent matching API for complex error types. You can match by Error class (.err) or by primitive value (.errVal).

import { Result } from "@shirudo/result";

class NetworkError extends Error {}
class ValidationError extends Error {}

const result = Result.err(new NetworkError("Timeout"));

const message = result
  .match()
  .err(NetworkError, (e) => `Retry later: ${e.message}`)
  .err(ValidationError, (e) => `Invalid input: ${e.message}`)
  .errVal("TIMEOUT_CODE", () => "Operation timed out") // Match primitive values
  .ok((val) => `Success: ${val}`)
  .run();

Async Pattern Matching

For async handlers, use matchAsync:

import { matchAsync } from "@shirudo/result";

const result = await matchAsync({
  ok: async (val) => `Success: ${val}`,
  err: async (e) => `Error: ${e}`
})(someResult);

When to use what:

  • Use .fold() for simple cases where you handle both Ok and Err
  • Use .match() for complex pattern matching on multiple error types
  • Use fold() pipe operator for functional composition in pipelines

📚 API Reference

Creation & Conversions

  • ok(value) / err(error): Create basic instances.
  • okIf(condition, okValue, errValue): Conditionally create Ok or Err.
  • okIfLazy(condition, okFn, errFn): Lazy conditional creation.
  • Result.try(fn): Execute a sync function; catches exceptions as Err.
  • Result.fromNullable(val, fallback): Convert null | undefined to Err.
  • Result.fromPromise(promise): Convert a Promise to Promise<Result>.
  • .toPromise(): Convert Ok to resolved Promise, Err to rejected.
  • .toNullable(): Convert Ok to value, Err to null.

Instance Methods

  • .isOk(): Type guard for success.
  • .isErr(): Type guard for failure.
  • .unwrap(): Get value or throw (use carefully).
  • .unwrapErr(): Get error or throw (use carefully).
  • .unwrapOr(default): Get value or return default.
  • .unwrapOrElse(fn): Get value or generate default from error.
  • .unwrapOrThrow(): Get value or throw original error (preserves stack trace).
  • .expect(msg): Get value or throw with specific message.
  • .expectErr(msg): Get error or throw with specific message.
  • .fold(onOk, onErr): Handle both cases and return a single value.
  • .pipe(...): Chain operators synchronously.
  • .pipeAsync(...): Chain operators asynchronously.
  • .match(): Start a fluent pattern matching builder (Err only).
  • .matchErr(): Pattern matching builder for Err cases.
  • .serialize(): Convert to { isSuccess, data?, error? }.
  • .toUserFriendly(): User-friendly serialization with error messages.

Error types: Methods that throw (e.g., .unwrap(), .unwrapErr(), .expect(), .expectErr(), and invalid-state checks) now use custom error classes with stable code values like ERR_UNWRAP_ON_ERR and ERR_INVALID_STATE. These classes and constants are exported from the package for programmatic handling.

Utilities

Type guards and helper functions:

  • isResult(value): Type guard to check if a value is a Result.
  • contains(result, value): Check if Ok contains a specific value.
  • containsErr(result, error): Check if Err contains a specific error.
  • fromResult(fn): Execute a function, catching exceptions (Rust Result::from).
import { isResult, contains, containsErr, fromResult, ok, err } from "@shirudo/result";

isResult(ok(5)); // true
isResult("not a result"); // false

const result = ok(42);
contains(result, 42); // true
contains(result, 100); // false

const errResult = err("not found");
containsErr(errResult, "not found"); // true

const wrapped = fromResult(() => JSON.parse('{"valid": true}'));

Pipeable Operators

Import these from the root package to use inside .pipe().

| Operator | Description | | :--------------------- | :------------------------------------------------------- | | map(fn) | Transform the Ok value. | | mapErr(fn) | Transform the Err value. | | mapBoth(fnOk, fnErr) | Transform both sides. | | flatMap(fn) | Chain a function that returns a Result (monadic bind). | | filter(pred, errFn) | Turn Ok into Err if predicate fails. | | tap(observer) | Run side effects (logging) without changing the result. | | recover(val) | Convert Err to Ok with a default value. | | tryCatch(fn) | Run a function, catching exceptions into Err. | | tryMap(fn) | Like map, but catches exceptions. | | fold({ ok, err }) | Terminate the pipe and return a value based on state. |

Async Variants: mapAsync, mapErrAsync, flatMapAsync, filterAsync, tapAsync, tryCatchAsync, tryMapAsync, foldAsync.

Combinators

Combinators (inspired by Rust) for composing and transforming Results:

| Combinator | Description | | :--------- | :---------- | | and(r1, r2) | Short-circuit AND: returns r2 only if r1 is Ok | | or(r1, r2) | Returns r1 if Ok, otherwise r2 | | orElse(r, fn) | Returns r if Ok, otherwise calls fn(error) | | mapOr(r, default, fn) | Maps Ok value or returns default | | mapOrElse(r, defaultFn, fn) | Maps Ok value or computes default from error | | swap(r) | Swaps Ok and Err: Result<T, E>Result<E, T> |

import { and, or, orElse, mapOr, mapOrElse, swap, ok, err } from "@shirudo/result";

const a = ok(5);
const b = ok(10);

// and: returns b only if a is Ok
and(a, b); // Ok(10)

// or: returns first Ok, otherwise fallback
or(err("fallback"), ok("success")); // Ok("success")

// orElse: lazy fallback with error context
orElse(err("error"), (e) => ok(`recovered: ${e}`)); // Ok("recovered: error")

// mapOr: map or use default
mapOr(ok(5), 0, (n) => n * 2); // 10
mapOr(err("x"), 0, (n) => n * 2); // 0

// swap: interchange Ok and Err
swap(ok("value")); // Err("value")
swap(err("error")); // Ok("error")

Collections

  • sequence(results): Turn Result[] into Result<T[]>. First error stops the process.
  • sequenceRecord(record): Like sequence, but for objects ({ a: Result, b: Result }Result<{ a, b }>).
  • collectFirstOk(results): Find the first success, or return all errors.
  • collectFirstOkAsync(results): Async version - find the first success.
  • collectFirstOkParallelAsync(results): Parallel variant - first success wins, all rejections continue.
  • collectAllErrors(results): Returns Ok(values) only if all are Ok, otherwise collects all errors.
  • partition(results): Separate a list into arrays of [oks, errs].
  • flatten(result): Flattens a nested Result<Result<T, E>, E> into Result<T, E>.
  • zip(r1, r2): Combine two results into a tuple.

🤝 Contributing

We welcome contributions! Please follow the standard pull request process. Ensure usage of TypeScript and Vitest for testing.

📄 License

This project is licensed under the MIT License.