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

@raghava.indra/result-ts

v1.1.1

Published

A lightweight, type-safe Result monad for TypeScript — handle errors without exceptions

Downloads

61

Readme

@raghava.indra/result-ts

A lightweight, type-safe Result type for TypeScript. No dependencies.


The Problem

When functions can fail, we usually throw exceptions:

function divide(a: number, b: number): number {
  if (b === 0) throw new Error("division by zero");
  return a / b;
}

const result = divide(10, 0); // throws at runtime

This has problems:

  • You don't know it can fail — the return type number gives no hint that an error is possible
  • You must remember try/catch — but nothing enforces it
  • The error type is unknowncatch (e) gives you unknown, not a typed error

The Solution

Return a Result instead. A Result is either:

  • Ok — it worked, here's the data
  • Err — it failed, here's the error
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); // Ok(5)
const failed = divide(10, 0); // Err("division by zero")

Now the return type tells you everything:

  • It might succeed with number
  • It might fail with string
  • You must handle both cases

Install

npm install @raghava.indra/result-ts

Quick Start

import { ok, err, Result } from '@raghava.indra/result-ts';

function parseAge(input: string): Result<number, string> {
  const num = Number(input);
  if (isNaN(num)) return err("not a number");
  if (num < 0) return err("age cannot be negative");
  return ok(num);
}

Every Result has an ok field. Use it to check success or failure:

const result = parseAge("25");

if (result.ok) {
  console.log(result.data);   // 25 (TypeScript knows it's Ok here)
} else {
  console.log(result.error);  // the error message (TypeScript knows it's Err here)
}

API Reference

Creating Results

ok(data)

Create a success result.

const result = ok(42);

result.ok      // true
result.data    // 42
result.isOk()  // true
result.isErr() // false

err(error)

Create a failure result.

const result = err("file not found");

result.ok      // false
result.error   // "file not found"
result.isOk()  // false
result.isErr() // true

Result<T, E>

Use this as a return type. T is the success type, E is the error type.

function findUser(id: number): Result<User, string> {
  // must return ok(User) or err(string)
}

Transforming Values

.map(fn) — Transform the success value

When to use: You have an Ok and want to change the data inside it.

// Ok gets transformed
ok(10).map(x => x * 2)       // Ok(20)

// Err is passed through unchanged — the function is never called
err("fail").map(x => x * 2)  // Err("fail")

Real example: Converting a raw database row into a formatted string:

function getDisplayName(userId: number): Result<string, string> {
  return findUser(userId)     // Result<User, string>
    .map(user => user.name)   // Result<string, string> — only runs if user was found
    .map(name => name.toUpperCase()); // still works, errors pass through
}

.mapErr(fn) — Transform the error value

When to use: You want to change or enrich the error, not the data.

// Err gets transformed
err("not found").mapErr(e => `Error: ${e}`)  // Err("Error: not found")

// Ok is passed through unchanged
ok(10).mapErr(e => `Error: ${e}`)             // Ok(10)

Real example: Enriching errors with context before returning to the caller:

function readConfig(path: string): Result<Config, string> {
  return parseFile(path)                           // Result<Config, string>
    .mapErr(e => `Failed to read config at ${path}: ${e}`);
}

.flatMap(fn) — Chain operations that return Results

When to use: Your next step also returns a Result. Like map, but the callback returns a Result instead of a plain value.

// Ok: callback runs, its Result becomes the new result
ok(10).flatMap(x => ok(x * 2))       // Ok(20)
ok(10).flatMap(x => err("overflow")) // Err("overflow")

// Err: callback is skipped, error passes through
err("fail").flatMap(x => ok(x * 2)) // Err("fail")

Real example: A multi-step process where any step can fail:

function createAccount(email: string): Result<Account, string> {
  return validateEmail(email)          // Result<string, string>
    .flatMap(validEmail => checkAvailable(validEmail))  // Result<string, string>
    .flatMap(availableEmail => createProfile(availableEmail)); // Result<Account, string>
}
// If validateEmail fails, the rest is skipped automatically
// If checkAvailable fails, createProfile is never called

Extracting Values

.fold(onErr, onOk) — Handle both cases, get a single value out

When to use: You're done processing and need a concrete value (e.g., to send an HTTP response or log a message).

const message = parseAge("abc").fold(
  (error) => `Invalid input: ${error}`,  // runs if Err
  (age)   => `Age is ${age}`             // runs if Ok
);
// message = "Invalid input: not a number"

Real example: Building an HTTP response:

function handleRequest(req: Request): Response {
  return processOrder(req).fold(
    (error) => new Response(error, { status: 400 }),
    (order) => new Response(JSON.stringify(order), { status: 200 })
  );
}

.getOrElse(defaultValue) — Get the data or a fallback

When to use: You want the data if it worked, otherwise a sensible default.

ok(42).getOrElse(0)      // 42
err("fail").getOrElse(0)  // 0

Real example: Loading a cached value with a fallback:

const timeout = getCachedTimeout().getOrElse(30_000); // use 30s if cache missing

Async Operations

When working with Promises, errors get lost. try/catch doesn't compose well and catch gives you unknown. This library provides tools to convert promises into Results and chain async operations.

fromPromise(promise, mapErr) — Convert a Promise to a Result

When to use: You have a Promise (like a fetch call) and want to handle both success and rejection as a Result.

const result = await fromPromise(
  fetch("/api/users"),
  (e) => `Network error: ${e}`
);
// result is Result<Response, string>

Real example: Wrapping an API call:

async function getUser(id: number): Promise<Result<User, string>> {
  return fromPromise(
    fetch(`/api/users/${id}`).then(r => r.json()),
    (e) => `Request failed: ${e}`
  );
}

tryAsync(fn, mapErr) — Run an async function, catch errors

When to use: You have an async operation that might throw, and you want to catch it as a Result.

const result = await tryAsync(
  () => fs.readFile("/config.json", "utf-8"),
  (e) => `Read failed: ${e}`
);
// result is Result<string, string>

.mapAsync(fn) — Async version of .map()

When to use: Same as .map(), but the transformation is async (returns a Promise).

// Ok: async transform runs
const result = await ok(42).mapAsync(async (n) => {
  const res = await fetch(`/api/items/${n}`);
  return res.json();
});
// result is Ok(parsedData)

// Err: transform is skipped
const failed = await err("not found").mapAsync(async (n) => { /* never runs */ });
// failed is Err("not found")

.flatMapAsync(fn) — Async version of .flatMap()

When to use: Chain async operations where each step returns a Result.

const result = await ok(userId)
  .flatMapAsync(async (id) => {
    const user = await fetchUser(id);   // returns Result<User, string>
    return user;
  });

Real example: A multi-step async workflow:

async function placeOrder(itemId: number): Promise<Result<Order, string>> {
  return ok(itemId)
    .flatMapAsync(async (id) => checkStock(id))      // Result<Item, string>
    .flatMapAsync(async (item) => reserveItem(item))  // Result<Reservation, string>
    .flatMapAsync(async (res) => createOrder(res));    // Result<Order, string>
}
// If checkStock fails, the rest is skipped automatically

.mapErrAsync(fn) — Async version of .mapErr()

When to use: You want to transform the error asynchronously.

const result = await err("db-001")
  .mapErrAsync(async (code) => await lookupErrorMessage(code));

AsyncResult<T, E> — Type alias

AsyncResult<T, E> is simply Promise<Result<T, E>>. Use it as a return type for async functions that return Results.

async function fetchConfig(): AsyncResult<Config, string> {
  return fromPromise(
    fetch("/config").then(r => r.json()),
    (e) => `Failed: ${e}`
  );
}

Quick Reference

| Method | On Ok | On Err | |--------|-------|--------| | .map(fn) | Runs fn on data, wraps result in Ok | Skipped, passes Err through | | .mapErr(fn) | Skipped, passes Ok through | Runs fn on error, wraps result in Err | | .flatMap(fn) | Runs fn, returns its Result | Skipped, passes Err through | | .fold(onErr, onOk) | Runs onOk | Runs onErr | | .getOrElse(val) | Returns data | Returns val | | .mapAsync(fn) | Runs async fn, wraps result in Ok | Skipped, resolves to Err | | .flatMapAsync(fn) | Runs async fn, returns its Result | Skipped, resolves to Err | | .mapErrAsync(fn) | Skipped, resolves to Ok | Runs async fn, wraps result in Err |

| Standalone | Description | |-----------|-------------| | fromPromise(p, mapErr) | Convert Promise<T> to AsyncResult<T, E> | | tryAsync(fn, mapErr) | Run async fn, catch throws as AsyncResult<T, E> |


Full Example

A user registration flow with validation, duplicate checks, and database insertion — each step can fail:

import { ok, err, Result, fromPromise, AsyncResult } from '@raghava.indra/result-ts';

type User = { id: number; email: string; name: string };
type AppError = { field: string; message: string };

function validateEmail(email: string): Result<string, AppError> {
  if (!email.includes("@")) {
    return err({ field: "email", message: "invalid email format" });
  }
  return ok(email);
}

async function checkDuplicate(email: string): AsyncResult<string, AppError> {
  return fromPromise(
    fetch(`/api/check?email=${email}`),
    () => err({ field: "email", message: "network error" })
  ).flatMapAsync(async (res) => {
    const data = await res.json();
    return data.exists
      ? err({ field: "email", message: "email already registered" })
      : ok(email);
  });
}

async function insertUser(email: string, name: string): AsyncResult<User, AppError> {
  return fromPromise(
    fetch("/api/users", {
      method: "POST",
      body: JSON.stringify({ email, name }),
    }),
    () => err({ field: "email", message: "failed to create user" })
  ).mapAsync(async (res) => res.json() as User);
}

async function register(
  email: string,
  name: string
): Promise<Result<User, AppError>> {
  return validateEmail(email)
    .flatMap((validEmail) => checkDuplicate(validEmail))
    .flatMapAsync((uniqueEmail) => insertUser(uniqueEmail, name));
}

// Usage
const result = await register("[email protected]", "Alice");

result.fold(
  (error) => console.log(`${error.field}: ${error.message}`),
  (user)   => console.log(`Welcome, ${user.name}!`)
);

License

MIT