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

nothrow-ts

v1.0.0

Published

Type-safe error handling with Result and Option types, inspired by Rust. Features generator-based error propagation using yield*.

Readme

nothrow-ts - Result and Option Types for TypeScript

npm version

A TypeScript library for type-safe error handling, inspired by Rust's Result and Option types. Features generator-based error propagation using yield* for automatic unwrapping, similar to Rust's ? operator.

Installation

npm install nothrow-ts

Why nothrow-ts?

Traditional JavaScript error handling with try/catch has several problems:

  • Invisible errors: Functions don't declare what errors they might throw
  • Runtime surprises: No compile-time guarantees about error handling
  • Nested try/catch blocks: Complex error handling leads to deeply nested code
  • Mixed concerns: Business logic mixed with error handling

Nothrow-ts solves these by making errors explicit in the type system:

// Before: Hidden errors, runtime surprises
function parseUser(json: string): User {
  return JSON.parse(json); // Can throw, but type doesn't show it
}

// After: Explicit errors, compile-time safety
function parseUser(json: string): Result<User, string> {
  return fromThrowable(() => JSON.parse(json));
}

Features

  • Type-safe error handling without exceptions
  • Generator-based error propagation with yield*
  • Composable operations with map, flatMap, and more
  • Async operations with automatic retry and exponential backoff
  • Full TypeScript support with excellent type inference
  • Zero runtime dependencies
  • Comprehensive test coverage
  • Production-ready

Quick Start

Generator Approach (Recommended)

Use Result.gen() with yield* for automatic error propagation:

import { ok, err, gen, Result } from "nothrow-ts";

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

const calculate = gen(function* (a: number, b: number, c: number) {
  // yield* automatically unwraps Ok Values
  // and immediately returns Err values
  const sum = yield* divide(a, b);
  const result = yield* divide(sum, c);
  return ok(result * 2);
});

calculate(10, 2, 5).unwrap(); // 2
calculate(10, 0, 5).isErr(); // true

Traditional Chaining

import { ok, err } from "nothrow-ts";

const result = divide(10, 2)
  .flatMap((x) => divide(x, 5))
  .map((x) => x * 2);

result.unwrap(); // 2

API Reference

Result Type

Result represents either success (Ok) or failure (Err).

Construction

import { ok, err, fromThrowable, fromPromise } from "nothrow-ts";

// Create Ok or Err
const success = ok(42);
const failure = err("Something went wrong");

// From throwing functions
const result = fromThrowable(() => JSON.parse(jsonString));

// From promises
const asyncResult = await fromPromise(fetch("/api/data"));

Type Guards

result.isOk(): boolean   // Check if Ok
result.isErr(): boolean  // Check if Err
result.ok(): boolean     // Alias for isOk()

Extracting Values

result.unwrap(): T                      // Get value or throw
result.unwrapOr(defaultValue: T): T    // Get value or default
result.unwrapOrElse((err) => T): T     // Get value or compute default
result.expect(message: string): T       // Unwrap with custom error message
result.expectErr(message: string): E    // Unwrap error or throw

Transformations

// Transform the Ok value
result.map((value) => newValue): Result<U, E>

// Transform the Err value
result.mapErr((error) => newError): Result<T, F>

// Chain Result-returning operations
result.flatMap((value) => anotherResult): Result<U, E>

Combinators

result.and(otherResult): Result<U, E>  // Returns other if Ok, else Err
result.or(otherResult): Result<T, E>   // Returns Ok if Ok, else other

Pattern Matching

const output = result.match({
  ok: (value) => `Success: ${value}`,
  err: (error) => `Error: ${error}`,
});

Generator-Based Error Handling

Result.gen()

Wrap a generator function to enable automatic error propagation:

import { gen, ok, err } from "nothrow-ts";

const myFunction = gen(function* (x: number) {
  const a = yield* someOperation(x);
  const b = yield* anotherOperation(a);
  const c = yield* finalOperation(b);
  return ok(c);
});

Benefits:

  • No manual error checking
  • Early returns on error automatically
  • Reads like synchronous code
  • Type-safe throughout

Result.tryPromise()

Handle async operations with automatic retry:

import { tryPromise } from "nothrow-ts";

const result = await tryPromise({
  try: async () => {
    const response = await fetch("/api/data");
    return response.json();
  },
  catch: (error) => ({
    type: "NETWORK_ERROR",
    message: error.message,
  }),
  retry: {
    times: 3,
    delayMs: 200,
    backoff: "exponential", // 200ms, 400ms, 800ms
  },
});

Result.all()

Convert an array of Results into a Result of an array:

import { ok, err, all } from "nothrow-ts";

const results = [ok(1), ok(2), ok(3)];
const combined = all(results);
combined.unwrap(); // [1, 2, 3]

const withError = [ok(1), err("failed"), ok(3)];
all(withError).isErr(); // true

Result.partition()

Split an array of Results into successes and failures:

import { ok, err, partition } from "nothrow-ts";

const results = [ok(1), err("e1"), ok(2), err("e2")];
const { ok: successes, err: failures } = partition(results);

console.log(successes); // [1, 2]
console.log(failures); // ['e1', 'e2']

Option Type

Option represents an optional value: Some or None.

Construction

import { some, none, fromNullable } from "nothrow-ts";

const value = some(42);
const empty = none<number>();
const maybeValue = fromNullable(possiblyNull);

Type Guards

option.isSome(): boolean  // Check if Some
option.isNone(): boolean  // Check if None

Extracting Values

option.unwrap(): T                      // Get value or throw
option.unwrapOr(defaultValue: T): T    // Get value or default
option.unwrapOrElse(() => T): T        // Get value or compute default
option.expect(message: string): T       // Unwrap with custom error message

Transformations

option.map((value) => newValue): Option<U>
option.flatMap((value) => anotherOption): Option<U>
option.filter((value) => boolean): Option<T>

Conversion to Result

option.okOr(error): Result<T, E>
option.okOrElse(() => error): Result<T, E>

Pattern Matching

const output = option.match({
  some: (value) => `Value: ${value}`,
  none: () => "No value",
});

Examples

User Registration Flow

import { ok, err, gen, Result } from "nothrow-ts";

interface User {
  email: string;
  age: number;
  username: string;
}

type ValidationError =
  | { type: "INVALID_EMAIL"; message: string }
  | { type: "INVALID_AGE"; message: string }
  | { type: "USERNAME_TAKEN"; message: string };

function validateEmail(email: string): Result<string, ValidationError> {
  if (!email.includes("@")) {
    return err({ type: "INVALID_EMAIL", message: "Must contain @" });
  }
  return ok(email);
}

function validateAge(age: number): Result<number, ValidationError> {
  if (age < 13) {
    return err({ type: "INVALID_AGE", message: "Must be 13 or older" });
  }
  return ok(age);
}

function checkUsernameAvailable(
  username: string
): Result<string, ValidationError> {
  if (username === "admin") {
    return err({ type: "USERNAME_TAKEN", message: "Username taken" });
  }
  return ok(username);
}

const registerUser = gen(function* (
  email: string,
  age: number,
  username: string
) {
  const validEmail = yield* validateEmail(email);
  const validAge = yield* validateAge(age);
  const validUsername = yield* checkUsernameAvailable(username);

  return ok({
    email: validEmail,
    age: validAge,
    username: validUsername,
  });
});

// Usage
const user = registerUser("[email protected]", 25, "alice");
user.match({
  ok: (u) => console.log("Registered:", u),
  err: (e) => console.error("Error:", e.message),
});

API Request with Error Handling

import { tryPromise, Result } from "nothrow-ts";

interface ApiError {
  type: "NETWORK" | "HTTP" | "PARSE";
  status?: number;
  message: string;
}

async function fetchUser(id: string): Promise<Result<User, ApiError>> {
  return tryPromise<User, ApiError>({
    try: async () => {
      const response = await fetch(`/api/users/${id}`);

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }

      return response.json();
    },
    catch: (error) => {
      if ((error as Error).message.includes("HTTP")) {
        return {
          type: "HTTP",
          status: parseInt((error as Error).message.split(" ")[1]),
          message: "Request failed",
        };
      }
      return {
        type: "NETWORK",
        message: (error as Error).message,
      };
    },
    retry: {
      times: 3,
      delayMs: 1000,
      backoff: "exponential",
    },
  });
}

Database Transaction

import { gen, ok, err, Result } from "nothrow-ts";

interface DBError {
  type: "NOT_FOUND" | "CONSTRAINT_VIOLATION" | "CONNECTION_ERROR";
  message: string;
}

const transferMoney = gen(function* (
  fromAccount: string,
  toAccount: string,
  amount: number
) {
  const fromUser = yield* findAccount(fromAccount);

  if (fromUser.balance < amount) {
    return err({
      type: "CONSTRAINT_VIOLATION",
      message: "Insufficient funds",
    });
  }

  const toUser = yield* findAccount(toAccount);

  yield* updateBalance(fromAccount, fromUser.balance - amount);
  yield* updateBalance(toAccount, toUser.balance + amount);
  yield* logTransaction({ from: fromAccount, to: toAccount, amount });

  return ok(undefined);
});

Form Validation Pipeline

import { ok, err, gen, all } from "nothrow-ts";

interface FormData {
  username: string;
  email: string;
  password: string;
  confirmPassword: string;
}

const validateForm = gen(function* (data: FormData) {
  const validations = [
    validateUsername(data.username),
    validateEmail(data.email),
    validatePassword(data.password),
  ];

  yield* all(validations);

  if (data.password !== data.confirmPassword) {
    return err("Passwords do not match");
  }

  return ok(data);
});

Migration from try-catch

Before:

async function fetchAndProcess(id: string) {
  try {
    const response = await fetch(`/api/data/${id}`);
    const data = await response.json();
    return processData(data);
  } catch (error) {
    console.error("Error:", error);
    return null;
  }
}

After:

const fetchAndProcess = gen(function* (id: string) {
  const response = yield* tryPromise({
    try: () => fetch(`/api/data/${id}`).then((r) => r.json()),
  });

  const processed = yield* processData(response);
  return ok(processed);
});

const result = await fetchAndProcess("123");
result.match({
  ok: (data) => handleSuccess(data),
  err: (error) => handleError(error),
});

Best Practices

  1. Use generators for sequential operations with multiple steps
  2. Use traditional chaining for simple transformations
  3. Define error types explicitly using discriminated unions
  4. Handle all error cases using match()
  5. Choose one error handling style per function for consistency

TypeScript Tips

Discriminated Union Errors

type AppError =
  | { type: "VALIDATION"; field: string; message: string }
  | { type: "NETWORK"; status: number }
  | { type: "AUTH"; reason: "EXPIRED" | "INVALID" };

function handleError(error: AppError) {
  switch (error.type) {
    case "VALIDATION":
      return `${error.field}: ${error.message}`;
    case "NETWORK":
      return `HTTP ${error.status}`;
    case "AUTH":
      return `Auth failed: ${error.reason}`;
  }
}

Generic Error Helpers

function wrapError<T, E>(
  fn: () => T,
  errorMapper: (e: unknown) => E
): Result<T, E> {
  try {
    return ok(fn());
  } catch (e) {
    return err(errorMapper(e));
  }
}

Roadmap

NoThrow is actively developed with plans to become a comprehensive functional programming toolkit:

v0.2.0 - Enhanced Error Handling

  • Custom error types with stack traces
  • Error context chaining and wrapping
  • Structured logging integration
  • Performance optimizations for generator chains

v0.3.0 - Advanced Combinators

  • traverse and sequence for collections
  • race and timeout for async operations
  • retry with configurable strategies
  • parallel execution with error aggregation

v1.0.0 - Production Ready

  • Comprehensive benchmarks and performance analysis
  • Advanced TypeScript utilities and type helpers
  • Plugin system for custom error handling strategies
  • Complete documentation with interactive examples

License

MIT

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for development setup, commit conventions, and our automated CI/CD process.

npm install
npm test
npm run lint
npm run build