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

@vennbee/result-type

v1.1.0

Published

Lightweight TypeScript Result type with Ok/Err constructors, type guards, transformers, and async utilities

Downloads

233

Readme

@vennbee/result-type · npm license

Lightweight, zero-dependency Result<T, E> type for TypeScript. Make errors explicit, composable, and impossible to ignore — designed for agent tool pipelines.


Why

TypeScript functions either throw or return T | null | undefined. Both force you to read the implementation (or get surprised at runtime) to know what can go wrong. Result<T, E> makes the failure case part of the type signature — the compiler tells you when you haven't handled it.

// Before — can this throw? what does null mean?
function parseConfig(raw: string): Config | null { ... }

// After — contract is explicit
function parseConfig(raw: string): Result<Config, ParseError> { ... }

This pattern is especially valuable in AI agent tool implementations, where:

  • Errors need to be returned as structured data, not thrown exceptions
  • Tool results cross JSON serialization boundaries
  • Pipelines chain multiple async tool calls sequentially or in parallel
  • Partial failures in fan-out calls should be handled gracefully

Install

npm install @vennbee/result-type
# or
pnpm add @vennbee/result-type

Quick Start

import { ok, err, isOk, map, flatMap, unwrapOr } from "@vennbee/result-type";

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

const result = divide(10, 2);

if (result.ok) {
  console.log(result.value); // 5
} else {
  console.error(result.error);
}

API

Types

type Ok<T>        = { readonly ok: true;  readonly value: T }
type Err<E>       = { readonly ok: false; readonly error: E }
type Result<T, E> = Ok<T> | Err<E>

The shape is a plain discriminated union — no class instances, no methods on the object. This means Results are:

  • JSON-serializable out of the box (safe across tool call boundaries)
  • Tree-shakeable — import only what you use
  • Structurally typed — compatible with any { ok: true, value } / { ok: false, error } shape

Constructors

ok<T>(value: T): Ok<T>

Wraps a success value.

const r = ok(42); // Ok<number>

err<E>(error: E): Err<E>

Wraps a failure value. The error can be anything — a string, an Error instance, or a structured object.

const r = err(new Error("oops"));               // Err<Error>
const r = err({ code: 404, message: "Not found" }); // Err<{ code: number, message: string }>

Type Guards

isOk(result): result is Ok<T>

isErr(result): result is Err<E>

Narrow a Result to its variant. Works as a predicate in .filter() chains.

const results = [ok(1), err("bad"), ok(3)];
const successes = results.filter(isOk); // Ok<number>[]
const failures  = results.filter(isErr); // Err<string>[]

Sync Transformers

map(result, fn): Result<U, E>

Transforms the Ok value; passes Err through unchanged.

const length = map(ok("hello"), s => s.length); // Ok<number>
const same   = map(err("oops"), s => s.length); // Err<string> — fn never called

mapErr(result, fn): Result<T, F>

Transforms the Err value; passes Ok through unchanged. Useful for normalizing error types at boundaries.

const r = mapErr(err(404), code => new Error(`HTTP ${code}`)); // Err<Error>

flatMap(result, fn): Result<U, E>

Chains a Result-returning function, flattening the nesting. Short-circuits on the first Err.

const parsed  = flatMap(readFile(path), raw  => parseJson(raw));
const checked = flatMap(parsed,         json => validate(json));

Async Transformers

mapAsync(result, fn): Promise<Result<U, E>>

The async counterpart to map. Transforms the Ok value with an async function; passes Err through.

const result = await mapAsync(ok(userId), id => fetchUser(id));

flatMapAsync(result, fn): Promise<Result<U, E>>

The async counterpart to flatMap, and the primary building block for agent tool chains. Chains an async Result-returning function, short-circuiting on the first Err.

const result = await flatMapAsync(
  parseConfig(raw),
  async config => tryCatchAsync(() => fetchUser(config.userId))
);

Branching

match(result, { ok, err }): U

Exhaustive pattern match — both branches must be handled and return the same type. Preferred over if/else in agent decision loops because it forces you to handle both cases.

const msg = match(result, {
  ok:  value => `Got ${value}`,
  err: error => `Failed: ${error.message}`,
});

Side Effects

tap(result, fn): Result<T, E>

tapErr(result, fn): Result<T, E>

Run a side effect (logging, tracing, metrics) without breaking the pipeline. Returns the original Result unchanged.

const result = tap(
  tapErr(riskyOperation(), e => logger.error("failed", e)),
  v => logger.info("succeeded", v)
);

Unwrappers

unwrap(result): T

Returns the Ok value or throws the error. Use only at trust boundaries where failure is truly unexpected — avoid in agent tools where the caller expects a Result.

const value = unwrap(ok(42)); // 42
unwrap(err("oops"));          // throws "oops"

unwrapOr(result, fallback): T

Returns the Ok value or a static fallback.

unwrapOr(err("oops"), 0); // 0

unwrapOrElse(result, fn): T

Returns the Ok value or computes a fallback from the error.

unwrapOrElse(err(404), code => `default-${code}`);

Lifting

tryCatch(fn, mapError?): Result<T, E>

Wraps a sync function, catching any thrown value into an Err. The optional mapError maps the caught value to a typed error.

const result = tryCatch(() => JSON.parse(raw));
const typed  = tryCatch(() => JSON.parse(raw), e => new ParseError(String(e)));

tryCatchAsync(fn, mapError?): Promise<Result<T, E>>

Wraps an async function, catching any rejection into an Err. Use when wrapping a function call you control.

const result = await tryCatchAsync(() => fetch(url).then(r => r.json()));

fromPromise(promise, mapError?): Promise<Result<T, E>>

Converts an existing Promise into a Result, never rejecting. Prefer this over tryCatchAsync when you already have a Promise in hand.

const result = await fromPromise(fetch(url).then(r => r.json()));

Collection Utilities

collect(results): Result<T[], E>

Fail-fast over an array of Results. Returns the first Err encountered, or Ok with all values if every result succeeded.

const all = collect([ok(1), ok(2), ok(3)]); // Ok<[1, 2, 3]>
const bad = collect([ok(1), err("oops")]);  // Err<"oops">

collectAsync(promises): Promise<Result<T[], E>>

The async counterpart to collect. Runs all promises in parallel via Promise.all, then fails fast on the first Err.

const result = await collectAsync([
  tryCatchAsync(() => fetchUser(id)),
  tryCatchAsync(() => fetchOrders(id)),
]);

partition(results): [T[], E[]]

Splits an array of Results into [successes, failures]. Unlike collect, never short-circuits — processes every result. Use in fan-out patterns where partial success is acceptable.

const [values, errors] = partition([ok(1), err("bad"), ok(3)]);
// values → [1, 3]
// errors → ["bad"]

Patterns

Sequential async pipeline

Chain tool calls where each step depends on the previous result. Short-circuits on the first failure.

const result = await flatMapAsync(
  await flatMapAsync(
    await fromPromise(db.findUser(id)),
    async user => fromPromise(db.findPosts(user.id))
  ),
  async posts => tryCatchAsync(() => rankPosts(posts))
);

Agent tool implementation

Return Result from tool functions instead of throwing, so the agent can decide how to handle failures.

import { tryCatchAsync, flatMapAsync, tap, match } from "@vennbee/result-type";

async function fetchUserOrders(userId: string) {
  return flatMapAsync(
    tap(
      await tryCatchAsync(() => db.findUser(userId)),
      user => console.log("fetched user", user.id)
    ),
    user => tryCatchAsync(() => db.findOrders(user.id))
  );
}

const result = await fetchUserOrders("u_123");

// Convert to a structured agent response
const response = match(result, {
  ok:  orders => ({ success: true,  data: orders }),
  err: error  => ({ success: false, reason: error.message }),
});

Parallel fan-out with partial failure handling

Fire multiple independent requests and collect what succeeded.

const results = await Promise.all(
  userIds.map(id => tryCatchAsync(() => fetchUser(id)))
);

const [users, errors] = partition(results);

if (errors.length > 0) {
  logger.warn(`${errors.length} users failed to load`);
}

// Continue with the users we did get
await processUsers(users);

Fail-fast validation

Collect validation results and short-circuit if any field fails.

const validation = collect([
  validateName(form.name),
  validateEmail(form.email),
  validateAge(form.age),
]);

if (!validation.ok) {
  return { error: validation.error.message };
}

const [name, email, age] = validation.value;

Logging without breaking the chain

Observe results mid-pipeline without altering them.

const result = pipe(
  tap(fetchResult, v => logger.info("fetched", v)),
  r => tapErr(r, e => logger.error("failed", e)),
  r => flatMap(r, transform)
);

License

MIT