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

nevernullable

v2.1.0

Published

Type-safe Option<T>, Some<T>, None for TypeScript and JavaScript. Replace null and undefined with a Rust-inspired Option type with map, andThen, filter, match, unwrap and Promise interop. Zero dependencies, dual ESM + CJS, ~2 KB.

Readme

nevernullable

npm version npm downloads CI bundle size types license

"I call it my billion-dollar mistake. It was the invention of the null reference in 1965." — Tony Hoare, the null reference "inventor"

A small, zero-dependency, fully typed Option<T> for TypeScript and JavaScript. Replace null / undefined with a Rust-inspired Option type and get a real null-safe API: unwrap, map, andThen, filter, match, Promise interop, dual ESM + CJS, zero runtime deps.

Inspired by Rust, neverthrow, and oxide.ts.

Table of contents

Why

null and undefined produce a category of runtime errors that the TypeScript compiler can only partially catch. Even with strictNullChecks, you end up writing a lot of defensive code:

const user = users.find((u) => u.id === id);
if (user != null && user.email != null) {
  send(user.email.toUpperCase());
}

With Option<T>, the same becomes a single pipeline that never throws and never returns a nullable:

Option(users.find((u) => u.id === id))
  .andThen((u) => Option(u.email))
  .map((e) => e.toUpperCase())
  .match({
    Some: send,
    None: () => console.warn('no email for user', id),
  });

The library is intentionally tiny (about 3 KB min+gzip), has zero dependencies, ships dual ESM + CJS with full type maps, and is built for modern Node (18+) and any bundler.

Install

npm install nevernullable
# or
pnpm add nevernullable
# or
yarn add nevernullable

Quick start

import { Option, Some, None } from 'nevernullable';

// Construct
Some(42); // Some(42)
None; // None singleton
Option('hi'); // Some('hi')
Option(null); // None
Option(undefined); // None
Option(0); // Some(0)     — falsy values are valid
Option(''); // Some('')
Option(false); // Some(false)

// Inspect
Some(1).isSome(); // true
None.isNone(); // true

// Extract
Some(1).unwrap(); // 1
None.unwrapOr(0); // 0
None.unwrapOrElse(() => compute()); // computed default
Some(1).unwrapOrNull(); // 1
None.unwrapOrUndefined(); // undefined

// Transform
Some(2).map((n) => n * 5); // Some(10)
None.map((n) => n * 5); // None

// Chain
Some('42').andThen((s) => {
  const n = Number(s);
  return Number.isFinite(n) ? Some(n) : None;
}); // Some(42)

// Combine
Some(1).zip(Some('a')); // Some([1, 'a'])
Option.all([Some(1), Some(2)]); // Some([1, 2])
Option.any([None, Some(3)]); // Some(3)

// Filter
Some(4).filter((n) => n > 0); // Some(4)
Some(-1).filter((n) => n > 0); // None

// Match
Some(1).match({
  Some: (n) => `got ${n}`,
  None: () => 'nothing',
});

// Iterate
[...Some(1)]; // [1]
[...None]; // []
for (const v of Some('x')) {
  // v === 'x', body runs once
}

API reference

Factories

Option<T>(value)

Wraps a value in an Option:

  • null / undefinedNone.
  • Anything else (including 0, '', false, NaN) → Some(value).
  • A Promise<T>Promise<Option<NonNullable<T>>>.
Option(0); // Some(0)
Option(null); // None
await Option(fetchSomething()); // Option<NonNullable<...>>

Some<T>(value)

Wraps a present value. Throws TypeError at runtime if value is null or undefined. Use Option(value) instead if the input may be nullable.

Some('x'); // Some('x')
Some(null); // throws TypeError

For Promise input, the runtime check happens after the promise resolves — the returned promise rejects with TypeError on a resolved nullable.

None

The shared None singleton. Every None-producing path in the library returns this same instance, so result === None is a reliable check (in addition to result.isNone()).

fromNullable(fn)

Lift a function whose return value may be nullable into one that returns an Option. Both sync and async functions are supported.

const safeFind = fromNullable((arr: number[], x: number) => arr.find((n) => n === x));

safeFind([1, 2, 3], 2); // Some(2)
safeFind([1, 2, 3], 99); // None

Also available as Option.fromNullable.

Instance methods

All methods are pure — they return a new Option (or a plain value) without mutating the receiver.

| Method | Returns | Notes | | ----------------------------- | --------------------- | ----------------------------------------------------------------------- | | unwrap() | T | Throws if None. Prefer the safer methods below. | | expect(message) | T | Like unwrap, but uses your message. | | unwrapOr(fallback) | T \| U | Returns fallback if None. Eager. | | unwrapOrElse(fn) | T \| U | Like unwrapOr, lazy fallback. | | unwrapOrNull() | T \| null | Interop with nullable APIs. | | unwrapOrUndefined() | T \| undefined | | | isSome() | boolean | | | isNone() | boolean | | | match({ Some, None }) | K \| P | Exhaustive pattern match. | | map(fn) | Option<U> | Transforms value. null / undefined from fn collapse to None. | | mapOr(fallback, fn) | U \| V | Eager fallback. | | mapOrElse(onNone, fn) | U \| V | Lazy fallback. | | andThen(fn) / flatMap(fn) | Option<U> | Monadic bind: chain operations returning Options. | | or(other) | Option<T \| U> | This if Some, else other. Eager. | | orElse(fn) | Option<T \| U> | Lazy or. | | filter(predicate) | Option<T> | Keeps only if predicate is truthy. | | zip(other) | Option<[T, U]> | Some only when both are Some. | | zipWith(other, fn) | Option<R> | zip + transform. Null collapses to None. | | flatten() | Option<U> | Requires this: Option<Option<U>>. | | toString() | string | Some(...) / None. | | toJSON() | object | { _tag: 'Some', value } or { _tag: 'None' }. | | [Symbol.iterator]() | IterableIterator<T> | Some yields once, None yields nothing. Works in for...of, spread. |

Static helpers on Option

Option.isOption(x)

Runtime type guard.

Option.isOption(Some(1)); // true
Option.isOption({}); // false

Option.all([opt1, opt2, ...])

Some([v1, v2, ...]) if every entry is Some, otherwise the first None. Short-circuits.

Option.all([Some(1), Some('x')]); // Some([1, 'x'])
Option.all([Some(1), None]); // None
Option.all([]); // Some([])

Option.any([opt1, opt2, ...])

The first Some, or None if all are None. Short-circuits.

Option.any([None, None, Some(3)]); // Some(3)
Option.any([None, None]); // None

Cookbook

1. Safe lookup + chain

const findUser = (id: number) => Option(users.find((u) => u.id === id));
const userEmail = (id: number) => findUser(id).andThen((u) => Option(u.email));
const upper = (id: number) => userEmail(id).map((e) => e.toUpperCase());

upper(1).unwrapOr('no email');

2. Replace a try/catch for parsing

const parseJsonOption = fromNullable(<T>(s: string): T | null => {
  try {
    return JSON.parse(s) as T;
  } catch {
    return null;
  }
});

parseJsonOption('{"ok":1}').map((v) => v.ok); // Some(1)
parseJsonOption('not json').isNone(); // true

3. Combine values

const fullName = Some(firstName).zipWith(Some(lastName), (a, b) => `${a} ${b}`);

4. Express handler with match

app.get('/users/:id', (req, res) => {
  findUser(Number(req.params.id)).match({
    Some: (user) => res.json(user),
    None: () => res.status(404).json({ error: 'not found' }),
  });
});

5. React hook

function useOptional<T>(value: T | null | undefined): Option<T> {
  return useMemo(() => Option(value), [value]);
}

const userOpt = useOptional(data?.user);
return userOpt.match({
  Some: (u) => <Profile user={u} />,
  None: () => <Skeleton />,
});

Promise interop

Option and fromNullable both accept Promise<T> and return Promise<Option<NonNullable<T>>>. You handle the promise once, on the outside, and the inside is always synchronous:

const opt = await Option(fetch('/me').then((r) => r.json()));
opt.match({
  Some: (me) => console.log('hi', me.name),
  None: () => console.warn('no /me'),
});

Why not Option<Promise<T>>?

It would compose two layers of uncertainty (pending vs. resolved, present vs. absent) into the same value, which is awkward to consume. Returning Promise<Option<T>> makes the order clear: first the promise resolves, then you get an Option.

Comparison with similar libraries

| Capability | nevernullable | oxide.ts | fp-ts Option | neverthrow | | ----------------------------------- | :-----------: | :--------------------------------------------------: | :-------------------------------------------------------------------: | :----------------------------------------------------: | | Option<T> | ✅ | ✅ | ✅ | ❌¹ | | Result<T, E> | —² | ✅ | ✅ | ✅ | | map / andThen / filter / or | ✅ | ✅ | ✅ | —¹ | | match / pattern matching | ✅ | ✅ | ✅ | ✅ | | Promise input on factories | ✅ | — | — | ✅ | | Symbol.iterator | ✅ | ✅ | — | — | | Dual ESM + CJS, types in both lanes | ✅ | ✅ | ✅ | ✅ | | Zero runtime dependencies | ✅ | ✅ | ❌ | ✅ | | Bundle size (min+gzip, approx.) | ~3 KB | ~6 KB | ~50 KB | ~5 KB |

¹ neverthrow focuses on Result<T, E>; you can emulate Option as Result<T, void>. ² Result<T, E> is planned for a future release.

Performance

Microbenchmarks (map, unwrap, match) place nevernullable within the same order of magnitude as oxide.ts and fp-ts — roughly 10M ops/sec on Node 22. Full numbers and methodology live in bench/RESULTS.md; reproduce with npm run bench.

Migration from 1.x

v2.0.0 is mostly additive. The only breaking changes:

  1. Some(null) and Some(undefined) now throw TypeError at runtime. Previously, they constructed a "fake Some" that contradicted its own type.
    • If you don't know whether a value is nullable, use Option(value) instead — it converts to None automatically.
    • This makes the type contract Some<NonNullable<T>> real at runtime.
  2. The internal NonNullable<T> re-export from nevernullable/dist/shared is gone (it shadowed TypeScript's built-in global, which was a bug). Use TypeScript's built-in NonNullable<T> directly.
  3. The package now uses an exports map. Deep imports such as nevernullable/dist/option.js no longer resolve. Use the public entry point nevernullable for everything.

Everything else is additive: unwrap, unwrapOr, unwrapOrElse, match, expect, fromNullable, Option(...), Some(...), None all keep their existing semantics.

Quick migration recipe

// before (1.x)
const x = Some(maybeNull); // ⚠️ silently wrong if maybeNull is null
// after (2.x)
const x = Option(maybeNull); // None on null, Some(value) otherwise

Contributing

PRs and issues welcome. See CONTRIBUTING.md for the local setup, scripts, coding style and commit-message conventions.

License

MIT