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

better-option

v1.1.2

Published

Lightweight Option type with generator-based composition

Downloads

65

Readme

better-option

Lightweight Option type for TypeScript with generator-based composition.

Install

New to better-option?

npx better-option init

Or install manually:

npm install better-option

Quick Start

import { Option } from "better-option";

// Convert nullable to Option
const option = Option.from(maybeNull);

// Check and use
if (Option.isSome(option)) {
  console.log(option.value);
} else {
  console.log("No value");
}

// Or use pattern matching
const message = option.match({
  some: (data) => `Got: ${data.name}`,
  none: () => "Empty",
});

Contents

Creating Options

// Present value
const some = Option.some(42);

// Absent value
const none = Option.none();

// From nullable (null/undefined become None)
const option = Option.from(maybeNull);

// From predicate
const option = Option.fromPredicate(value, (x) => x > 0);

Transforming Options

const option = Option.some(2)
  .map((x) => x * 2) // Some(4)
  .filter((x) => x > 0) // Some(4) or None
  .andThen((x) => (x > 0 ? Option.some(x) : Option.none()));

// Standalone functions (data-first or data-last)
Option.map(option, (x) => x + 1);
Option.map((x) => x + 1)(option); // Pipeable

Extracting Values

// Unwrap (throws on None)
const value = option.unwrap();
const value = option.unwrap("custom error message");

// With fallback
const value = option.unwrapOr(defaultValue);

// Lazy fallback
const value = Option.unwrapOrElse(option, () => expensiveDefault());

// Pattern match
const value = option.match({
  some: (v) => v,
  none: () => fallback,
});

Generator Composition

Chain multiple Options without nested callbacks or early returns:

const option = Option.gen(function* () {
  const user = yield* findUser(id); // Unwraps or short-circuits
  const profile = yield* findProfile(user.id);
  return Option.some({ user, profile });
});
// Option<{user, profile}>

Async version with Option.await:

const option = await Option.gen(async function* () {
  const user = yield* Option.await(fetchUser(id));
  const posts = yield* Option.await(fetchPosts(user.id));
  return Option.some({ user, posts });
});

When any yielded Option is None, the generator short-circuits and returns None immediately.

Combining Options

// Zip two Options into tuple
const zipped = Option.zip(optionA, optionB);
// Some([a, b]) if both Some, None otherwise

// Flatten nested Option
const flat = Option.flatten(Option.some(Option.some(42))); // Some(42)

Panic

Thrown (not returned) when user callbacks throw inside Option operations. Represents a defect in your code, not a domain error.

import { Panic } from "better-option";

// Callback throws → Panic
Option.some(1).map(() => {
  throw new Error("bug");
}); // throws Panic

// Generator cleanup throws → Panic
Option.gen(function* () {
  try {
    yield* Option.none();
  } finally {
    throw new Error("cleanup bug");
  }
}); // throws Panic

Why Panic? None is for absent values. Panic is for bugs — like Rust's panic!(). If your .map() callback throws, that's not an expected absence, it's a defect to fix.

Panic properties:

| Property | Type | Description | | --------- | --------- | ----------------------------- | | message | string | Describes where/what panicked | | cause | unknown | The exception that was thrown |

Panic also provides toJSON() for error reporting services (Sentry, etc.).

Serialization

Convert Options to plain objects for RPC, storage, or server actions:

import { Option, SerializedOption } from "better-option";

// Serialize to plain object
const option = Option.some(42);
const serialized = Option.serialize(option);
// { status: "some", value: 42 }

// Deserialize back to Option instance
const deserialized = Option.deserialize<number>(serialized);
// Some(42) - can use .map(), .andThen(), etc.

// Typed boundary for Next.js server actions
async function findUser(id: string): Promise<SerializedOption<User>> {
  const option = await lookupUser(id);
  return Option.serialize(option);
}

// Client-side
const serialized = await findUser(userId);
const option = Option.deserialize<User>(serialized);

API Reference

Option

| Method | Description | | ------------------------------ | ---------------------------------------- | | Option.some(value) | Create present value | | Option.none() | Create absent value | | Option.from(value) | Convert nullable to Option | | Option.fromPredicate(v, fn) | Some if predicate passes, None otherwise | | Option.isSome(option) | Type guard for Some | | Option.isNone(option) | Type guard for None | | Option.filter(option, fn) | Some to None if predicate fails | | Option.zip(a, b) | Combine two Options into tuple | | Option.unwrapOrElse(option, fn) | Extract value or compute fallback | | Option.gen(fn) | Generator composition | | Option.await(promise) | Wrap Promise for generators | | Option.serialize(option) | Convert Option to plain object | | Option.deserialize(value) | Rehydrate serialized Option | | Option.partition(options) | Split array into [values, noneCount] | | Option.flatten(option) | Flatten nested Option |

Instance Methods

| Method | Description | | ------------------------ | ------------------------------------- | | .isSome() | Type guard, narrows to Some | | .isNone() | Type guard, narrows to None | | .map(fn) | Transform value | | .filter(fn) | Some to None if predicate fails | | .andThen(fn) | Chain Option-returning function | | .andThenAsync(fn) | Chain async Option-returning function | | .match({ some, none }) | Pattern match | | .unwrap(message?) | Extract value or throw | | .unwrapOr(fallback) | Extract value or return fallback | | .unwrapOrElse(fn) | Extract value or compute fallback | | .tap(fn) | Side effect on Some | | .tapAsync(fn) | Async side effect on Some |

Type Helpers

| Type | Description | | --------------------- | ------------------------------ | | InferValue<O> | Extract value type from Option | | SerializedOption<A> | Plain object form of Option | | SerializedSome<A> | Plain object form of Some | | SerializedNone | Plain object form of None |

Error Utilities

| Method | Description | | -------------------- | -------------------------- | | panic(message, c?) | Throw unrecoverable Panic | | isPanic(value) | Type guard for Panic |

Agents and AI

better-option ships with skills for AI coding agents (OpenCode, Claude Code, Codex).

Quick Start

npx better-option init

Interactive setup that:

  1. Installs the better-option package
  2. Optionally fetches source code via opensrc for better AI context
  3. Installs the adoption skill + /adopt-better-option command for your agent
  4. Optionally launches your agent

What the skill does

The /adopt-better-option command guides your AI agent through:

  • Converting T | null | undefined to Option<T>
  • Migrating null checks to Option combinators
  • Refactoring nullable chains to generator composition
  • Replacing array.find() patterns with Option returns

Supported agents

| Agent | Config detected | Skill location | | -------- | ----------------------- | -------------------------------------- | | OpenCode | .opencode/ | .opencode/skill/better-option-adopt/ | | Claude | .claude/, CLAUDE.md | .claude/skills/better-option-adopt/ | | Codex | .codex/, AGENTS.md | .codex/skills/better-option-adopt/ |

Manual usage

If you prefer not to use the interactive CLI:

# Install package
npm install better-option

# Add source for AI context (optional)
npx opensrc better-option

# Then copy skills/ directory to your agent's skill folder

Acknowledgments

better-option is inspired by and designed as a companion to better-result by Dillon Mulroy. The project structure, API design patterns, and generator-based composition approach are all influenced by better-result's excellent work on typed error handling in TypeScript.

If you're looking for typed error handling (Result types), check out better-result!

License

MIT