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

nalloc

v0.2.2

Published

Rust-like Option and Result for TypeScript with near-zero allocations for extreme performance

Readme

nalloc

Rust-like safety at JavaScript speed.

Type-safe Option and Result for TypeScript with near-zero allocations for extreme performance.

npm version npm downloads

Why nalloc?

Most Option/Result libraries wrap every value in an object. nalloc doesn't.

Other libraries:    Some(42) --> { _tag: 'Some', value: 42 }   // allocates
nalloc:            Some(42) --> 42                            // zero allocation

Other libraries:    Ok(data) --> { _tag: 'Ok', value: data }   // allocates
nalloc:            Ok(data) --> data                          // zero allocation
  • Rust-like types - Option<T> and Result<T, E> with full pattern matching and combinators
  • Near-zero allocations - Some and Ok are the values themselves. Only Err allocates.
  • Extreme performance - Up to 2x faster than alternatives on the happy path

Benchmarks

| Operation | nalloc | neverthrow | ts-results | oxide.ts | |-----------|---------|------------|------------|----------| | ok() | 91M ops/s | 75M ops/s | 56M ops/s | 40M ops/s | | err() | 55M ops/s | 52M ops/s | 0.1M ops/s | 47M ops/s | | some() | 85M ops/s | - | 40M ops/s | 50M ops/s | | Result.map | 74M ops/s | 53M ops/s | 47M ops/s | 64M ops/s | | Option.map | 71M ops/s | - | 52M ops/s | 41M ops/s |

Run pnpm bench to reproduce.

Install

npm install nalloc

Quick start

import { Option, Result, ok, err, none, pipe, gen } from 'nalloc';

// Option: the value IS the Option
const port = Option.fromNullable(process.env.PORT);
const portNum = Option.unwrapOr(port, '3000');

// Result: Ok is the value itself, Err wraps the error
const config = Result.tryCatch(() => JSON.parse(raw), () => 'invalid json');
const data = Result.match(
  config,
  cfg => cfg,
  error => ({ fallback: true })
);

// gen: Rust-like ? operator with type-safe errors
const result = gen(function*($) {
  const a = yield* $(parseNumber('10'));
  const b = yield* $(parseNumber('5'));
  return a + b;
}); // Result<number, ParseError>

// pipe: left-to-right composition
const userId = pipe(
  Result.tryCatch(() => JSON.parse(raw)),
  r => Result.map(r, data => data.userId),
  r => Result.unwrapOr(r, 0),
);

How it works

| Type | Representation | Allocation | |------|----------------|------------| | Option<T> | T \| null \| undefined | Zero | | Some<T> | The value itself | Zero | | None | null or undefined | Zero | | Ok<T> | The value itself (branded) | Zero | | Err<E> | Minimal wrapper { error: E } | One object |

This means zero GC pressure on the happy path - your success values stay as plain values.

Coming from other libraries?

From neverthrow

// neverthrow
import { ok, err, Result } from 'neverthrow';
const result: Result<number, string> = ok(42);
result.map(x => x * 2);

// nalloc - same concepts, faster execution
import { Result, ok, err } from 'nalloc';
const result = ok(42);                    // zero allocation
Result.map(result, x => x * 2);           // function-based API

From fp-ts

// fp-ts
import { pipe } from 'fp-ts/function';
import * as O from 'fp-ts/Option';
pipe(O.some(42), O.map(x => x * 2));

// nalloc - simpler, faster
import { Option, pipe } from 'nalloc';
pipe(42, v => Option.map(v, x => x * 2)); // built-in pipe, value IS the Option

From oxide.ts

// oxide.ts
import { Some, None, Ok, Err } from 'oxide.ts';
const opt = Some(42);
opt.map(x => x * 2);
const result = Ok(42);
result.mapErr(e => new Error(e));

// nalloc - no wrapper objects on the happy path
import { Option, Result, ok } from 'nalloc';
Option.map(42, x => x * 2);              // 42 is the Option itself
const result = ok(42);                    // zero allocation
Result.mapErr(result, e => new Error(e));

From ts-results

// ts-results
import { Ok, Err, Some, None } from 'ts-results';
const result = new Ok(42);
result.map(x => x * 2);
const opt = Some(42);
opt.unwrapOr(0);

// nalloc - same safety, zero allocations
import { Option, Result, ok } from 'nalloc';
const result = ok(42);                    // no wrapper
Result.map(result, x => x * 2);
Option.unwrapOr(42, 0);                  // value IS the Option

From Rust

The API mirrors Rust's Option and Result:

import { Option, Result, ok, err, none, gen } from 'nalloc';

// Rust: Some(42).map(|x| x * 2)
Option.map(42, x => x * 2);

// Rust: Ok(42).and_then(|x| if x > 0 { Ok(x) } else { Err("negative") })
Result.andThen(ok(42), x => x > 0 ? ok(x) : err('negative'));

// Rust: result.unwrap_or(0)
Result.unwrapOr(result, 0);

// Rust: let value = get_value()?
const value = gen(function*($) {
  const a = yield* $(getValue());
  return a + 1;
});

Option

An Option<T> is T | null | undefined. The value itself is the Option.

import { Option, none } from 'nalloc';

// Create from nullable
const maybePort = Option.fromNullable(process.env.PORT);

// Transform
const doubled = Option.map(maybePort, p => parseInt(p) * 2);
const validated = Option.filter(doubled, n => n > 0);

// Extract
const port = Option.unwrapOr(maybePort, '3000');

// Pattern match
const label = Option.match(
  maybePort,
  value => `Port: ${value}`,
  () => 'Port: default'
);

// Assert and narrow
Option.assertSome(maybePort, 'PORT is required');

// Filter collections
const activeIds = Option.filterMap(users, user =>
  user.isActive ? user.id : none
);

// Async
const maybeUser = await Option.fromPromise(fetchUserById(id));

Result

A Result<T, E> is either Ok<T> (the value itself) or Err<E> (a wrapper).

import { Result, ok, err, gen, genAsync, pipe } from 'nalloc';

// Wrap throwing functions
const parsed = Result.tryCatch(
  () => JSON.parse(raw),
  e => 'invalid json'
);

// gen: Rust-like ? operator with preserved error types
const result = gen(function*($) {
  const config = yield* $(parseConfig(raw));
  const db = yield* $(connectDb(config.url));
  return db.query('SELECT 1');
}); // Result<QueryResult, ConfigError | DbError>

// Async gen
const data = await genAsync(async function*($) {
  const res = yield* $(await Result.fromPromise(fetchUser(id)));
  const posts = yield* $(await Result.fromPromise(fetchPosts(res.id)));
  return { user: res, posts };
}); // Promise<Result<{user, posts}, unknown>>

// wrap / toThrowable: ecosystem boundaries
const safeParse = Result.wrap(JSON.parse);            // throwing -> Result
safeParse('{"a":1}');                                 // Ok({a: 1})
const throwingFind = Result.toThrowable(findUser);    // Result -> throwing
throwingFind('123');                                  // returns User or throws

// Standard Schema validation (Zod, Valibot, ArkType, etc.)
const validated = Result.fromSchema(userSchema, input);
// Result<User, readonly SchemaIssue[]>

// Transform with pipe
const userId = pipe(
  parsed,
  r => Result.map(r, data => data.userId),
  r => Result.flatMap(r, id => id > 0 ? ok(id) : err('invalid id')),
);

// Pattern match
const user = Result.match(
  parsed,
  data => data.user,
  error => null
);

// Assert and narrow
Result.assertOk(loaded, 'Config required');

// Collections
const combined = Result.all([ok(1), ok(2), ok(3)]);      // Ok([1, 2, 3])
const first = Result.any([err('a'), ok(42), err('b')]);  // Ok(42)
const [oks, errs] = Result.partition(results);

Iter

Iterator utilities for working with fallible iteration and Result streams.

import { Iter } from 'nalloc';

// Safe iteration: wraps throws as Err, values as Ok
for (const result of Iter.safeIter(riskyIterator)) {
  if (isOk(result)) handleValue(result);
  else handleError(result.error);
}

// mapWhile: yields mapped values while fn returns Some
const taken = [...Iter.mapWhile([1, 2, 3, 4, 5], n =>
  n < 4 ? n * 10 : null
)]; // [10, 20, 30]

// tryCollect: collect Results into Result<T[], E>
const collected = Iter.tryCollect(results); // Ok([1, 2, 3]) or first Err

// tryFold: fold with early exit on Err
const sum = Iter.tryFold([1, 2, 3], 0, (acc, n) => ok(acc + n));

// tryForEach: iterate with early exit on Err
Iter.tryForEach(items, item => processItem(item));

API Reference

Option

| Function | Description | |----------|-------------| | fromNullable(value) | Convert nullable to Option | | fromPromise(promise) | Promise to Option (rejection = None) | | map(opt, fn) | Transform Some value | | flatMap(opt, fn) | Chain Option-returning functions | | andThen(opt, fn) | Alias for flatMap | | tap(opt, fn) | Side effect on Some, return original | | tapNone(opt, fn) | Side effect on None, return original | | filter(opt, predicate) | Keep Some if predicate passes | | match(opt, onSome, onNone) | Pattern match | | unwrap(opt) | Extract or throw | | unwrapOr(opt, default) | Extract or default | | unwrapOrElse(opt, fn) | Extract or compute default | | unwrapOrReturn(opt, fn) | Extract or return computed value | | expect(opt, msg) | Extract or throw with message | | assertSome(opt, msg?) | Assert and narrow to Some | | isSome(opt) / isNone(opt) | Type guards | | isSomeAnd(opt, pred) | Some and predicate passes | | isNoneOr(opt, pred) | None or predicate passes | | or(opt, other) | Return first Some | | orElse(opt, fn) | Return Some or compute fallback | | and(opt, other) | Return second if first is Some | | xor(opt, other) | Some if exactly one is Some | | zip(opt, other) | Combine two Options into tuple | | unzip(opt) | Split tuple Option into two | | flatten(opt) | Flatten nested Option | | contains(opt, value) | Check if Some contains value | | mapOr(opt, default, fn) | Map or return default | | mapOrElse(opt, defaultFn, fn) | Map or compute default | | toArray(opt) | Some to [value], None to [] | | toNullable(opt) | Some to value, None to null | | toUndefined(opt) | Some to value, None to undefined | | okOr(opt, error) | Option to Result | | okOrElse(opt, fn) | Option to Result with computed error | | ofOk(result) | Ok to Some, Err to None | | ofErr(result) | Err to Some, Ok to None | | filterMap(items, fn) | Map and filter in one pass | | findMap(items, fn) | Find first Some from mapping |

Result

| Function | Description | |----------|-------------| | tryCatch(fn, onError?) | Wrap throwing function | | tryCatchMaybePromise(fn, onError?) | Wrap sync-or-async, preserving sync | | of(fn) | Alias for tryCatch (no error mapper) | | wrap(fn, onError?) | Wrap throwing function once, reuse | | toThrowable(fn) | Inverse of wrap: Result-returning to throwing | | fromSchema(schema, value) | Validate via Standard Schema v1 | | gen(fn) | Generator do-notation with typed errors | | genAsync(fn) | Async generator do-notation | | safeTry(fn) | Imperative error handling with unwrap | | safeTryAsync(fn) | Async version of safeTry | | unwrap(result) | Extract Ok or throw error value | | unwrapErr(result) | Extract Err or throw | | unwrapOr(result, default) | Extract Ok or default | | unwrapOrElse(result, fn) | Extract Ok or compute from error | | unwrapOrReturn(result, fn) | Extract Ok or return computed value | | expect(result, msg) | Extract Ok or throw with message | | expectErr(result, msg) | Extract Err or throw with message | | map(result, fn) | Transform Ok value | | mapErr(result, fn) | Transform Err value | | bimap(result, okFn, errFn) | Transform both Ok and Err | | flatMap(result, fn) | Chain Result-returning functions | | andThen(result, fn) | Alias for flatMap | | tap(result, fn) | Side effect on Ok, return original | | tapErr(result, fn) | Side effect on Err, return original | | match(result, onOk, onErr) | Pattern match | | assertOk(result, msg?) | Assert and narrow to Ok | | assertErr(result, msg?) | Assert and narrow to Err | | isOk(result) / isErr(result) | Type guards | | isOkAnd(result, pred) | Ok and predicate passes | | isErrAnd(result, pred) | Err and predicate passes | | isSomeErr(result) | Err with non-null error | | and(result, other) | Return second if first is Ok | | or(result, other) | Return first Ok | | orElse(result, fn) | Ok or compute fallback from error | | zip(a, b) | Combine two Ok into tuple | | zipWith(a, b, fn) | Combine two Ok with function | | flatten(result) | Flatten nested Result | | transpose(result) | Result to Option | | toOption(result) | Ok to Some, Err to None | | toErrorOption(result) | Err to Some, Ok to None | | mapOr(result, default, fn) | Map Ok or return default | | mapOrElse(result, defaultFn, fn) | Map Ok or compute default | | all(results) | Collect all Ok or first Err | | any(results) | First Ok or all Errs | | collect(results) | Collect Ok values or first Err | | collectAll(results) | All Ok or all Errs | | partition(results) | Split into [oks, errs] | | partitionAsync(promises) | Async partition | | filterOk(results) | Extract all Ok values | | filterErr(results) | Extract all Err values | | settleMaybePromise(values) | Settle sync/async values to Results | | partitionMaybePromise(values) | Partition sync/async Results |

Iter

| Function | Description | |----------|-------------| | safeIter(source) | Wrap iterable: values as Ok, throws as Err | | mapWhile(source, fn) | Yield mapped values while fn returns Some | | tryCollect(source) | Collect Results into Result<T[], E> | | tryFold(source, init, fn) | Fold with early exit on Err | | tryForEach(source, fn) | Iterate with early exit on Err |

Utilities

| Function | Description | |----------|-------------| | pipe(value, ...fns) | Thread value through functions left-to-right |

Comparison

| Feature | nalloc | neverthrow | fp-ts | oxide.ts | ts-results | |---------|---------|------------|-------|----------|------------| | Zero-alloc Option | Yes | No | No | No | No | | Zero-alloc Ok | Yes | No | No | No | No | | Bundle size | Tiny | Small | Large | Small | Small | | Learning curve | Low | Low | High | Low | Low | | Async support | Yes | Yes | Yes | Limited | No | | Tree-shakeable | Yes | Yes | Yes | Yes | Yes | | Iterator utilities | Yes | No | Yes | No | No | | gen (? operator) | Yes | Yes | No | No | No | | pipe | Yes | No | Yes | No | No | | Schema validation | Yes | No | No | No | No | | Ecosystem interop | Yes | No | No | No | No |

Alternatives

Looking for a TypeScript Result/Option library? Here's how nalloc compares:

  • neverthrow - Popular, method-chaining API. Allocates a wrapper for every Ok and Err. nalloc avoids allocation on the happy path entirely.
  • fp-ts / effect - Full functional programming ecosystem with Either and Option. Powerful but heavy and steep learning curve. nalloc focuses on Result/Option with a simpler API.
  • oxide.ts - Rust-inspired with class-based wrappers. Every Some/Ok allocates an object. nalloc represents Some/Ok as the raw value.
  • ts-results / ts-results-es - Direct Rust port with class instances. Similar API surface to nalloc, but allocates on every construction.
  • true-myth - Result and Maybe with a functional API. Wraps all values. nalloc is a similar philosophy with zero-allocation design.

nalloc is designed for codebases where allocation pressure matters - high-throughput servers, hot loops, and performance-sensitive paths - while keeping the same safety guarantees.

License

MIT

Contributing

Contributions welcome! Open an issue or PR for ideas, bug fixes, or docs.