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

@pickle-packs/nads

v1.0.8

Published

Monads, aka 'Mah Nads'

Readme

Maybe and Outcome 'Nads

Monadic utilities for explicit control flow in TypeScript.

  • Maybe<T> is the Option monad. It encodes presence or absence of a value.
  • Outcome<T> is the Result monad. It encodes success or failure as data.

Use them to make every branch explicit, keep the happy path linear, and avoid throwing exceptions for routine control flow.

Install

npm i @pickle-packer/nads

Imports

Aliasing may be required if importing both

// Maybe (Option)
import { some, none, map, flatMap, match, coalesce, type Maybe } from "@pickle-packer/nads/maybe";

// Outcome (Result)
import {
  success,
  failure,
  map,
  flatMap,
  match,
  pipe,
  type Outcome,
  type IFailure
} from "@pickle-packer/nads/outcome";

Why monads here

In this style you get total control flow because match forces you to handle every case, which makes it easy for reviews and tooling to spot gaps. The happy path stays linear since map and flatMap let you write top to bottom logic instead of nesting if or try.

You stop throwing for expected situations and keep throw for truly exceptional faults, returning failure for everything else. Tests become straightforward because you assert on plain data rather than reconstructing stack traces or wiring global handlers. Local reasoning improves too: a function that returns Outcome<T> cannot hide a thrown error on the success path.

Maybe usage

Basic flow

const m = some(2);

const m2 = map(m, x => x * 10);         // some(20)
const m3 = flatMap(m, x => some(x + 1)); // some(3)
const m4 = flatMap(none as Maybe<number>, x => some(x + 1)); // none

const text = match(
  m,
  x => `value:${x}`,
  "no value"
);

Coalescing defaults

Use coalesce on multiple Maybe to filter and remove instances of none and extract values wrapped by some

const a = some(0);
const b = none;
const c = some(3);

const values = coalesce(a, b, c); // [0, 3]

or

const a = some(0);
const b = none;
const c = some(3);
const maybes = [a, b, c];

const values = coalesce(...maybes); // [0, 3]

When to use Maybe

  • Inputs that may be missing.
  • Parsing or lookups that can fail silently.
  • Optional relationships where null would leak into many call sites.

Outcome usage

Outcome<T> carries success(T) or failure(IFailure). Failures are plain data.

type User = { id: string };
type Profile = { label: string };

function getUser(id: string): Outcome<User> {
  return id ? success({ id }) : failure("missing id", "MISSING_ID");
}

function loadProfile(u: User): Outcome<Profile> {
  return success({ label: `user ${u.id}` });
}

const prof = flatMapOut(getUser("u1"), loadProfile);

const label = matchOut(
  prof,
  p => p.label,
  f => `error:${f.code}`
);

Pipe for multi step flows

Short circuits on the first failure.

const result = pipe(
  success(1),
  n => success(n + 1),
  n => success(n * 2)
);
// success(4)

Avoiding exceptions as control flow

Wrap exception sources at the boundary and keep the inside exception free.

function fromJson(s: string): Outcome<unknown> {
  try {
    return success(JSON.parse(s));
  } catch (e) {
    const err = e instanceof Error ? e : new Error(String(e));
    return failure("invalid json", "PARSE", err);
  }
}

Extending IFailure

IFailure has code, detail, and maybeError. Extend it when a domain needs more fields. Use code as a discriminant for duck typing across layers.

interface ValidationFailure extends IFailure {
  readonly code: "VALIDATION";
  readonly errors: Readonly<Record<string, ReadonlyArray<string>>>;
}

function validateName(name: string): Outcome<string> {
  if (name.trim().length === 0) {
    return failure<never>({
      code: "VALIDATION",
      detail: "name required",
      maybeError: none,
      errors: { name: ["required"] }
    } as ValidationFailure);
  }
  return success(name);
}

const msg = matchOut(
  validateName(""),
  v => v,
  f => {
    if (f.code === "VALIDATION") {
      const vf = f as ValidationFailure;
      return `invalid fields: ${Object.keys(vf.errors).join(", ")}`;
    }
    return `error:${f.code}`;
  }
);

You can also add a second discriminant for teams that prefer explicit types.

interface NotFoundFailure extends IFailure {
  readonly code: "NOT_FOUND";
  readonly entity: string;
  readonly kind: "NotFoundFailure";
}

Interop with Maybe

IFailure.maybeError uses Maybe<Error>. This lets you attach a captured exception without forcing one to exist.

const logIfError = (o: Outcome<unknown>): void => {
  matchOut(
    o,
    () => undefined,
    f => match(f.maybeError, e => console.error(e), undefined)
  );
};

Patterns and guidance

  • Never throw for predictable outcomes. Return failure with a clear code.
  • Keep detail readable for humans. Use code for programs and tests.
  • Prefer one match at the boundary. Use map or flatMap internally to avoid nested matching.
  • Keep failures stable. Treat code as part of your public surface so clients can branch without string parsing.
  • Convert unexpected exceptions to failure at IO boundaries.

API reference

@pickle-packer/nads/maybe

  • some<T>(value: T): Maybe<T>
  • none: Maybe<never>
  • map<T, U>(m: Maybe<T>, f: (t: T) => U): Maybe<U>
  • flatMap<T, U>(m: Maybe<T>, f: (t: T) => Maybe<U>): Maybe<U>
  • match<T, U>(m: Maybe<T>, onSome: (t: T) => U, onNone: (() => U) | U): U
  • coalesce<T>(...m: ReadonlyArray<Maybe<T>>): Array<T>

@pickle-packer/nads/outcome

  • success<T>(t: T): Outcome<T>
  • failure<T>(detail: string, code: string, error?: Error): Outcome<T>
  • failure<T>(f: IFailure): Outcome<T>
  • map<T, U>(o: Outcome<T>, f: (t: T) => U): Outcome<U>
  • flatMap<T, U>(o: Outcome<T>, f: (t: T) => Outcome<U>): Outcome<U>
  • match<T, U>(o: Outcome<T>, onSuccess: (t: T) => U, onFailure: (f: IFailure) => U): U
  • pipe(o: Outcome<unknown>, ...steps: ReadonlyArray<(x: unknown) => Outcome<unknown>>): Outcome<unknown>

Notes

  • Uses module local Symbols to avoid accidental key collisions.
  • All public objects are Readonly to discourage mutation.
  • No runtime dependencies.