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

verdict-ts

v1.0.0

Published

Rust-inspired Result<T, E> for TypeScript — make fallible operations type-safe

Readme

verdict-ts

Make fallible operations type-safe — Rust-inspired Result<T, E> for TypeScript

npm version bundle size coverage license

zero dependencies · 491 bytes gzipped · JSON-serializable · works everywhere


Why verdict-ts?

try/catch makes errors invisible in type signatures. You don't know a function can fail until it throws at runtime. verdict-ts makes success and failure both explicit in the type system — the compiler enforces that you handle errors.

// Before: invisible failure
function parseJSON(input: string): unknown { // can throw? who knows!
  return JSON.parse(input);
}

// After: explicit failure
function parseJSON(input: string): Result<unknown> {
  return trySync(() => JSON.parse(input));
}

No classes, no prototypes, no dependencies. Just plain objects: { ok: true, value } or { ok: false, error }. They serialize to JSON, survive structuredClone, and work in every runtime.

verdict-ts vs result.ts

| | verdict-ts | result.ts | |---|---|---| | Bundle size | 491B gzipped | ~3KB+ gzipped | | Dependencies | Zero | Zero | | Implementation | Plain objects, no classes | Class-based | | JSON serialization | ✅ JSON.stringify(result) works | ❌ Class instances don't serialize | | structuredClone | ✅ Works | ❌ Class instances fail | | Method chaining | ✅ .map().flatMap().unwrapOr() | ✅ | | Standalone functions | ✅ map(r, fn) for tree-shaking | ❌ Methods only | | Error type default | Result<T>Result<T, Error> | Result<T, unknown> | | Tuple combine | ✅ combine([ok(1), ok("x")]) preserves types | ❌ Arrays only | | Async support | tryAsync() built-in | Separate ResultAsync class | | Runtime overhead | Near zero (plain objects + closures) | Class instantiation per Result |

When to pick verdict-ts: You want the smallest possible Result type that works everywhere — browsers, Edge, Deno, Bun — and serializes to JSON.

When to pick result.ts: You want a full-featured Result toolkit with operators like andThen, orElse, asyncMap, and a richer API surface.

✨ Features

  • Discriminated unionresult.ok narrows the type, no instanceof needed
  • Method chainingresult.map(fn).flatMap(fn2).unwrapOr(default)
  • Standalone functionsmap(result, fn) for tree-shaking
  • Async supporttryAsync() wraps Promises safely
  • Combine resultscombine([r1, r2, r3]) validates in bulk with tuple preservation
  • Zero dependencies — nothing in node_modules except dev tools
  • 491 bytes gzipped — smaller than this README
  • Universal — Node.js, Bun, Deno, Cloudflare Workers, browsers

🚀 Quick Start

import { ok, err, trySync, match } from "verdict-ts";

// Create results directly
const user = ok({ name: "Alice", age: 30 });
const failure = err(new Error("not found"));

// Wrap throwable functions
const parsed = trySync(() => JSON.parse('{"key": "value"}'));

// Pattern match to handle both cases
const message = match(parsed, {
  ok: (data) => `Got: ${JSON.stringify(data)}`,
  err: (error) => `Failed: ${error.message}`,
});

📦 Installation

# npm
npm install verdict-ts

# pnpm
pnpm add verdict-ts

# yarn
yarn add verdict-ts

# bun
bun add verdict-ts

Deno via npm specifier:

import { ok, err } from "npm:verdict-ts";

📖 Usage

Creating Results

import { ok, err, trySync, tryAsync } from "verdict-ts";

// Direct construction
const success = ok(42);           // Ok<number>
const failure = err(new Error()); // Err<Error>

// With custom error types
type ValidationError = { field: string; message: string };
const invalid = err<ValidationError>({ field: "email", message: "required" });

// Wrap synchronous throwable code
const config = trySync(() => JSON.parse(readFileSync("config.json", "utf-8")));

// Wrap async throwable code
const response = await tryAsync(() => fetch("https://api.example.com/data"));

Transforming Results

import { ok } from "verdict-ts";

const result = ok("hello");

// map: transform the success value
const upper = result.map(s => s.toUpperCase());
// Ok<"HELLO">

// flatMap: chain operations that return Results
const length = result.flatMap(s =>
  s.length > 0 ? ok(s.length) : err(new Error("empty string"))
);
// Ok<5>

// mapErr: transform the error (on Err, passes through on Ok)

Extracting Values

import { ok, err, match } from "verdict-ts";

const result = ok(42);

// unwrap: returns value or throws
const value = result.unwrap(); // 42

// unwrapOr: returns value or fallback
const safe = err(new Error("fail")).unwrapOr(0); // 0

// match: pattern matching with full type narrowing
const label = match(result, {
  ok: (value) => `Success: ${value}`,
  err: (error) => `Error: ${error.message}`,
});

Combining Results

Validate multiple fields at once:

import { ok, err, combine } from "verdict-ts";

type FieldError = { field: string; reason: string };

function validateName(name: string) {
  return name.trim().length > 0
    ? ok(name.trim())
    : err({ field: "name", reason: "required" });
}

function validateAge(age: number) {
  return age >= 0 && age <= 150
    ? ok(age)
    : err({ field: "age", reason: "out of range" });
}

const fields = combine([
  validateName("Alice"),
  validateAge(30),
]);
// Ok<["Alice", 30]> — tuple types preserved

const badFields = combine([
  validateName(""),
  validateAge(999),
]);
// Err<{ field: "name"; reason: "required" }>  (first error)

Type Guards

import { isOk, isErr } from "verdict-ts";

const result = someOperation();

if (isOk(result)) {
  console.log(result.value); // TypeScript knows: value exists
} else {
  console.log(result.error); // TypeScript knows: error exists
}

Standalone Functions (Tree-Shaking)

Every method is also exported as a standalone function for optimal bundle sizes:

// Method chaining (convenient)
result.map(fn).flatMap(fn2).unwrapOr(defaultValue);

// Standalone functions (tree-shakeable)
import { map, flatMap, unwrapOr } from "verdict-ts";
unwrapOr(flatMap(map(result, fn), fn2), defaultValue);

Both styles are type-safe and produce identical results. Bundlers can eliminate unused standalone functions.

⚙️ API Reference

Types

| Type | Description | |------|-------------| | Ok<T> | Success variant with value: T and ok: true | | Err<E> | Failure variant with error: E and ok: false | | Result<T, E = Error> | Union Ok<T> \| Err<E> | | AsyncResult<T, E = Error> | Alias for Promise<Result<T, E>> |

Constructors

| Function | Signature | Description | |----------|-----------|-------------| | ok | (value: T) => Ok<T> | Create a success result | | err | (error: E) => Err<E> | Create a failure result | | trySync | (fn: () => T) => Result<T> | Wrap a sync function, catch throws | | tryAsync | (fn: () => Promise<T>) => AsyncResult<T> | Wrap an async function, catch rejections |

Methods (on Ok / Err objects and as standalone)

| Method | Standalone | Description | |--------|-----------|-------------| | .map(fn) | map(result, fn) | Transform Ok value, pass Err through | | .mapErr(fn) | mapErr(result, fn) | Transform Err error, pass Ok through | | .flatMap(fn) | flatMap(result, fn) | Chain a function returning Result, flatten | | .unwrap() | unwrap(result) | Return value or throw on Err | | .unwrapOr(def) | unwrapOr(result, def) | Return value or fallback | | .match(cases) | match(result, cases) | Pattern match with { ok, err } callbacks |

Utilities

| Function | Signature | Description | |----------|-----------|-------------| | combine | (results: Result<T, E>[]) => Result<T[], E> | All Ok → Ok of array, any Err → first Err | | isOk | (result) => result is Ok<T> | Type guard for Ok | | isErr | (result) => result is Err<E> | Type guard for Err |

🏗️ Architecture

Pure objects, no classes. Every Result is a frozen-shape plain object with ok: true | false as the discriminant. Methods are bound via closures at construction time and delegate to standalone functions — so bundlers can tree-shake anything you don't use.

Circular import resolved. methods.ts imports constructors (ok, err) and constructors.ts imports methods (map, flatMap, ...). ES module hoisting handles this cleanly at runtime.

Error default. Result<T> defaults E to Error, so simple cases don't need explicit error typing. Specialize when you need domain-specific errors.

🧪 Running Tests

npm test          # run all tests
npm run typecheck # type-check without emit
npm run build     # build ESM + CJS + declarations

📄 License

Apache-2.0