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

@valencets/resultkit

v0.4.0

Published

Opinionated, minimal Result monad for TypeScript. Railway-oriented programming with Ok, Err, map, andThen, match.

Readme

resultkit

Type-safe errors with pattern matching and zero escape hatches.

What is this?

A Result monad for TypeScript that enforces strict typed errors, ships pattern matching as a first-class paradigm, and includes an eslint preset that makes railway-oriented programming a project-wide discipline — not just a suggestion.

Why not neverthrow?

neverthrow is a good library. resultkit exists because neverthrow is permissive where we think strictness is needed:

| | neverthrow | resultkit | |--|-----------|-----------| | Error types | unknown by default | errorFn required — you type every error | | Pattern matching | .match() method only | Standalone match(), matchOn() with exhaustive discriminated unions | | Unhandled Results | Silent | Enforced by team convention | | throw / try/catch | Your problem | eslint preset bans them, points to fromThrowable | | switch statements | Your problem | eslint preset bans them, points to match() | | .catch() / .finally() | Your problem | eslint preset bans them, points to ResultAsync.fromPromise and .tap() |

resultkit is for teams that want guardrails, not just types.

Install

npm install @valencets/resultkit

Quick Start

import { ok, err, fromThrowable, match } from '@valencets/resultkit'
import type { Result } from '@valencets/resultkit'

// Every error is typed — no unknown allowed
interface AppError {
  readonly code: 'DIVISION_BY_ZERO' | 'NEGATIVE'
  readonly message: string
}

function divide (a: number, b: number): Result<number, AppError> {
  if (b === 0) return err({ code: 'DIVISION_BY_ZERO', message: 'Cannot divide by zero' })
  if (b < 0) return err({ code: 'NEGATIVE', message: 'Negative divisor' })
  return ok(a / b)
}

// Pattern match on the error code — exhaustive, type-narrowing
const result = divide(10, 0)

result.match({
  ok: (value) => console.log(`Result: ${value}`),
  err: (error) => match(error.code, {
    DIVISION_BY_ZERO: () => console.error('Cannot divide by zero'),
    NEGATIVE: () => console.error('Negative divisor')
  })
})

Core API

Constructors

| Function | Description | |----------|-------------| | ok(value) | Create a success Result | | err(error) | Create a failure Result | | okAsync(value) | Create an async success | | errAsync(error) | Create an async failure | | fromThrowable(fn, errorFn) | Wrap a throwing function — errorFn required | | fromThrowableAsync(fn, errorFn) | Wrap an async throwing function — errorFn required | | fromNullable(value, error) | Convert T \| null \| undefined to Result<T, E> | | ResultAsync.fromPromise(promise, errorFn) | Wrap a rejectable promise | | ResultAsync.fromSafePromise(promise) | Wrap a promise guaranteed not to reject |

Methods

All methods are available on Ok, Err, and ResultAsync.

| Method | On Ok | On Err | |--------|-------|--------| | .isOk() | true (narrows type) | false | | .isErr() | false | true (narrows type) | | .map(fn) | Applies fn to value | Passes through | | .mapErr(fn) | Passes through | Applies fn to error | | .andThen(fn) | Applies fn (returns Result) | Passes through | | .orElse(fn) | Passes through | Applies fn (returns Result) | | .match(okFn, errFn) | Calls okFn(value) | Calls errFn(error) | | .match({ ok, err }) | Calls ok(value) | Calls err(error) | | .tap(fn) | Calls fn(value), returns self | Passes through | | .tapErr(fn) | Passes through | Calls fn(error), returns self | | .unwrapOr(default) | Returns value | Returns default | | .unwrap() | Returns value | Throws (lint-flagged) | | .unwrapErr() | Throws (lint-flagged) | Returns error | | .toAsync() | Converts to ResultAsync | Converts to ResultAsync |

Combinators

| Function | Description | |----------|-------------| | combine(results) | Result[]Result<[T1, T2, ...], E1 \| E2 \| ...> — tuple-aware, first Err wins | | combineAsync(results) | Async variant of combine | | partition(results) | Result<T, E>[][T[], E[]] — split into ok/err arrays | | flatten(result) | Result<Result<T, E2>, E1>Result<T, E1 \| E2> |

Pattern Matching

import { match, matchOn } from '@valencets/resultkit'

type Status = 'active' | 'inactive' | 'banned'

// Exhaustive — compiler errors if you miss a variant
match(status, {
  active: () => 'Welcome',
  inactive: () => 'Please verify',
  banned: () => 'Access denied'
})

// Wildcard — handle some, catch-all the rest
match(status, {
  banned: () => 'Access denied',
  _: () => 'OK'
})

// Discriminated unions — type narrows in each handler
type AppError =
  | { code: 'NOT_FOUND'; path: string }
  | { code: 'TIMEOUT'; ms: number }

matchOn(error, 'code', {
  NOT_FOUND: (e) => `Missing: ${e.path}`,   // e is { code: 'NOT_FOUND', path: string }
  TIMEOUT: (e) => `Waited ${e.ms}ms`        // e is { code: 'TIMEOUT', ms: number }
})

Serialization

import { resultToJSON, resultFromJSON } from '@valencets/resultkit'

const json = resultToJSON(ok(42))     // { _tag: 'Ok', value: 42 }
const wire = JSON.stringify(json)      // send across process boundary
const result = resultFromJSON(JSON.parse(wire))  // back to Result

Branded Errors

import { ResultError, isResultError, matchOn, err } from '@valencets/resultkit'

type AppError =
  | ResultError<'NOT_FOUND'>
  | ResultError<'TIMEOUT'>
  | ResultError<'VALIDATION'>

const error = new ResultError('NOT_FOUND', 'User not found', { userId: 123 })

// Works with matchOn — code is the discriminant
matchOn(error, 'code', {
  NOT_FOUND: (e) => `Missing: ${e.message}`,
  TIMEOUT: (e) => `Slow: ${e.message}`,
  VALIDATION: (e) => `Bad input: ${e.message}`
})

// Type guard for unknown → ResultError narrowing
if (isResultError(caught)) {
  console.log(caught.code, caught.context)
}

Type Helpers

import type { InferOkType, InferErrType, Result } from '@valencets/resultkit'

type T = InferOkType<Result<number, string>>   // number
type E = InferErrType<Result<number, string>>   // string

The eslint Preset

resultkit ships an eslint preset that enforces railway-oriented programming across your project.

// eslint.config.js
import resultkit from '@valencets/resultkit/eslint'

export default [
  ...resultkit.strict,
  // your overrides
]

What it enforces

Three flat-config presets are exported. strict (errors) and recommended (warnings) share the same core error-handling rules; opinionated extends strict with stylistic rules that are unrelated to the error-handling thesis.

Core (strict / recommended):

| Rule | Rationale | |------|-----------| | Ban throw | Use err() or fromThrowable() | | Ban try/catch | Use fromThrowable() at the boundary | | Ban switch | Use match() or matchOn() | | Ban .catch() | Use ResultAsync.fromPromise() | | Ban .finally() | Use .tap() for cleanup | | Ban enum | Use const unions with match() | | Warn .unwrap() / .unwrapErr() | Prefer .match() or .unwrapOr() — override in test files |

Opinionated (adds):

| Rule | Rationale | |------|-----------| | Ban export default | Named exports only |

Design Principles

  1. Every error is typed. No unknown leaking through your codebase. errorFn is required on every boundary function.
  2. Pattern matching is the primary control flow. match() and matchOn() replace switch, if/else chains, and ad-hoc property checks.
  3. The library is a workflow. The eslint preset makes the discipline enforceable, not optional.
  4. Strict by default. Permissive escape hatches exist (unwrap, as unknown) but the tooling flags them.

Requirements

  • Node.js >= 22
  • TypeScript >= 5.9
  • ESM only ("type": "module")

The Node 22 floor tracks the Node.js release schedule: Node 20 reached end-of-life in April 2026, so 22 is the oldest line still receiving security patches. The source itself has no Node 22-specific requirement — it compiles to ES2022 with no node: imports — so projects still on 20 can override the engines field at their own risk.

License

MIT