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

xult

v1.4.1

Published

<div align='center'> <img src='./.github/XULT.png' width=500 alt='XULT logo' /> <br> <a href='https://www.npmjs.com/package/xult'><img src='https://img.shields.io/npm/v/xult?logo=npm&logoColor=white&label=%20&labelColor=red' /></a>    <a h

Readme

You do that with a robust, type-safe, and expressive Result-type for TypeScript, inspired by Rust's Result enum. xult provides a simple and powerful way to handle operations that can either succeed (Ok) or fail (Err), without resorting to throwing exceptions.

Why xult?

In many programming languages, errors are handled using exceptions. While exceptions can be useful, they can also make code harder to reason about, especially in asynchronous contexts. xult offers a different approach: representing the result of an operation as a value, which can be either a success (Ok) or a failure (Err).

This makes error handling explicit, predictable, and type-safe.

Features

  • ✅  Type-Safe  —  Leverage TypeScript's type system to ensure you handle both success and error cases.
  • ⏳  Async Ready  —  First-class support for promises and async functions.
  • 🌯  Function Wrapping  —  Easily wrap existing functions to return Result types.
  • 👍  Validation  —  Built-in support for input validation using any library that implements the @standard-schema/spec.
  • ❤️  Expressive API  —  A clean and intuitive API that is a joy to use.
  • 🌤️  Lightweight  —  xult is a tiny library with zero runtime dependencies.
  • ✨  Pretty Print  —  Beautifully outputs console.logs

Core Concepts

The core of xult is the Result<TValue, TError> type, which can be one of two things:

  • Ok<TValue, never>
    Represents a successful result, containing a value of type TValue.
  • Err<never, TError>
    Represents an error, containing an error of type TError which extends the error-shape of
    Result.ErrorShape = { code: string, message: string, details?: unknown }

xult differs itself from other similar libraries via an intuitive API, types that can be narrowed, simplified and expressed elegantly.
Please see how in Quickstart!

Quickstart

  

When we handle errors, narrowing becomes crucial to providing the best UX/DX feedback:

const result: Result<never, { code: 'INVALID'; details: { issue: unknown } } | { code: 'UNKNOWN' }>

if (result.isErr('INVALID')) {
    // ^? Err<never, { code: 'INVALID'; details: { issue: unknown } }>
    console.log(result.details.issue)
}

  

Say goodbye to nested if statements and embrace clean, linear logic. xult brings the power of do-notation to TypeScript through generator functions.

import { func, err, ok } from 'xult'

const isEven = func((num: number) => {
    if (num === 0) {
        return err('ZERO', 'Zero is neither even nor odd.')
    }
    return ok(num % 2 === 0)
})
//    ^? Result<boolean, { code: 'ZERO' }>

const processNumber = func(function*(num: number) {
    // Yield a result. If it's an Err, the function returns it immediately.
    // If it's an Ok, the value is unwrapped and assigned.
    const isNumEven = yield* isEven(num)
    //    ^? boolean

    if (isNumEven) {
        return `The number ${num} is even.`
    }

    return `The number ${num} is odd.`
})
//    ^? Result<string, { code: 'ZERO' }>

When you yield* a Result, xult handles the boilerplate: it unwraps the success value or short-circuits the execution with the error. This makes complex, multi-step operations a joy to write.

Basic Usage

Depending on your , you can import xult by:

import Result from 'xult'

Result.ok(...)
Result.err(...)
Result.validate(...)
Result.func(...)
Result.async(...)

or (my preference)

import { ok, err, validate, func, async } from 'xult'
import type { Result } from 'xult'

Creating Results

You can create Ok and Err results using the static methods on Result:

import { err, ok } from 'xult'

function divide(a: number, b: number) {
    if (b === 0) {
        return err('DIVISION_BY_ZERO', 'Cannot divide by zero')
    }
    return ok(a / b)
}

Checking Results

Use the isOk() and isErr() methods to check the type of a result. These methods act as type guards, allowing TypeScript to narrow the type of the result.

const result = divide(10, 2)

if (result.isOk()) {
    // result is of type Ok<number, never>
    console.log(`Result: ${result.value}`) // Output: Result: 5
}

const errorResult = divide(10, 0)

if (errorResult.isErr()) {
    // errorResult is of type Err<never, { code: 'DIVISION_BY_ZERO' }>
    console.error(`Error: ${errorResult.message}`) // Output: Error: Cannot divide by zero
}

You can also check for specific error codes with isErr():

if (errorResult.isErr('DIVISION_BY_ZERO')) {
    // This block will run
}

Working with Promises

Result.async makes it easy to work with promises. It wraps a promise and returns a Promise<Result<...>>. If the promise resolves, it returns an Ok with the resolved value. If the promise rejects, it returns an Err.

import { async, err } from 'xult'
import type { Result } from 'xult'

interface UserDTO {
    id: number
    name: string
}

async function fetchUser(id: number): Promise<Result<UserDTO, { code: 'FETCH_ERROR' }>> {
    const request = fetch(`https://api.example.com/users/${id}`).then(res => res.json() as Promise<UserDTO>)

    return async(
        request,
        thrown => err('FETCH_ERROR', 'Could not retrieve user data.', thrown.details)
    )
}

const userResult = await fetchUser(1)

if (userResult.isOk()) {
    console.log(userResult.value.name)
} else {
    console.error(userResult.message)
}

Wrapping Functions with Result.func

Result.func is a powerful utility for wrapping existing functions to return Result types. It can automatically handle errors, promises, and even input validation.

Simple Function

import { func, err } from 'xult'

const safeParse = func(
    JSON.parse,
    // optional: handle Result.ThrownError
    error => err('INVALID_JSON_STRING', 'Malformatted JSON string', error.details)
)

const result = safeParse('{"name": "John"}') // Result<any, { code: 'INVALID_JSON_STRING', details: unknown }>

if (result.isOk()) {
    console.log(result.value.name) // Output: John
}

const errorResult = safeParse('not json')

if (errorResult.isErr()) {
    console.error(errorResult.message) // Malformatted JSON string
}

Function with Validation

Result.func can also validate the arguments of a function using @standard-schema/spec.

import { func } from 'xult'
import v from 'validation-library'

const createUser = func(
    [v.string().min(3), v.number().min(18)],
    (name: string, age: number) => {
        // This code only runs if validation passes
        return { name, age }
    }
)

const userResult = await createUser('John', 30)

if (userResult.isOk()) {
    console.log(userResult.value) // Output: { name: 'John', age: 30 }
}

const validationErrorResult = await createUser('Jo', 17)

if (validationErrorResult.isErr('FUNC_VALIDATION_ERROR')) {
    console.error(validationErrorResult.details.issues)
}

  

@standard-schema/spec is a shared interface designed by the authors of Zod, Valibot, and ArkType. Any schema library that implements it can plug into xult.func with zero adapters.

Class Methods

Result.func preserves the this context, allowing you to wrap methods that access instance properties.

import { func, ok } from 'xult'

class User {
    constructor(private name: string) {}

    // Use a regular function to access `this`
    greet = func(function(this: User) {
        return ok(`Hello, I am ${this.name}`)
    })
}

const user = new User('Alice')
const result = user.greet() // Ok('Hello, I am Alice')

Elegant Workflows with Generators

This is where xult shines. Generator functions let you write sequential, business-friendly logic without the if (result.isErr()) pyramid.

// Without generators – lots of branching
const processOrder = (input: OrderInput) => {
    const validated = validateOrder(input)
    if (validated.isErr()) return validated

    const payment = chargeCustomer(validated.value)
    if (payment.isErr()) return payment

    return createOrderRecord(validated.value, payment.value)
}
// With generators – linear, expressive, type-safe
import { func } from 'xult'

const processOrder = func(function*(input: OrderInput) {
    const payload = yield* validateOrder(input)
    const receipt = yield* chargeCustomer(payload)
    const order = yield* createOrderRecord(payload, receipt)

    return order
})
//    ^? Result<Order, ErrorOf<validateOrder> | ErrorOf<chargeCustomer> | ErrorOf<createOrderRecord>>

Each yield* unwraps an Ok value or exits early with the originating Err, preserving the original error shape and stack trace.

JSON Handlers with Result.funcJSON

Result.funcJSON works exactly like Result.func, but automatically converts the result to a JSON-serializable object. This is especially useful for API endpoints where you need to send results over the network. See Serialization.md for more info.

import { funcJSON } from 'xult'

const handler = funcJSON(async function*(req) {
    const user = yield* getUser(req.params.id)
    const posts = yield* getUserPosts(user.id)
    return { user, posts }
})

// Returns: { ok: true, value: { user: {...}, posts: [...] } }
// Or: { ok: false, code: 'NOT_FOUND', message: 'User not found', stack: '...' }

Unwrapping Results

The safest strategy is to keep values wrapped until you're ready to handle the error. Either branch explicitly or lean on yield* inside a generator.

import { err, ok } from 'xult'

const divide = (a: number, b: number) => (b === 0 ? err('DIVISION_BY_ZERO', 'Cannot divide by zero') : ok(a / b))

const result = divide(10, 2)
if (result.isErr()) {
    return result // short-circuit with the original error
}

// result is Ok here
const value = result.value

When you truly know a result is Ok (for example inside tests), _unsafeUnwrap() is available. It rethrows the error with the original metadata if the result is an Err.

import { ok, err } from 'xult'

const okResult = ok(42)
const value = okResult._unsafeUnwrap() // value is 42

const fatal = err('ERROR', 'Something went wrong')
try {
    fatal._unsafeUnwrap()
} catch (error) {
    if (error instanceof Error) {
        console.error(error.message)
    }
}

Prefer the safe pattern whenever possible—Result makes it ergonomic to avoid exceptions altogether.

Transformation & Recovery

xult provides methods to transform values and recover from errors without breaking the chain.

  • result.map(fn)
    Transform the value if the result is Ok. If it's Err, the error is passed through.

    const result = ok(10).map(n => n * 2) // Ok(20)
  • result.catch(fn)
    Recover from an error by returning a new value.

    const result = err('FAIL', 'oops').catch(e => 'recovered') // Ok('recovered')
  • result.ifOk(fn) / result.ifErr(fn)
    Execute side-effects (like logging) without changing the result.

    result.ifErr(e => console.error(e))

JSON Serialization

xult results are fully serializable, making them easy to send over the network or store.

For more on serialization, see SERIALIZATION.md.

  • result.toJSON() / result.toString()
    Convert a result to a plain JSON object or string.

  • Result.fromJSON(json)
    Restore a result from a JSON string, object, or promise.

    const result = await Result.fromJSON(fetch('/api/data').then(r => r.json()))
  • Result.tryJSON(json)
    Safely attempt to parse a value into a Result. Returns undefined if the shape doesn't match.

  • Result.isJSON(value)
    Check if a value matches the Result JSON shape.
    A valid JSON shape contains either ok: true or ok: false, code: string, message: string, as these are the required properties for Result.ok and Result.err

  • Result.from(value)
    Converts any value into a Result. If the value is already a Result, returns it as-is. If it's a JSON shape ({ ok: true, value } or { ok: false, code, message }), it's converted to a Result. Otherwise, wraps the value in Result.ok(). Also handles promises.

    Result.from(123)                    // Result<number, never>
    Result.from(Result.ok(42))          // Result<number, never>
    Result.from({ ok: true, value: 5 }) // Result<number, never>
    Result.from(Promise.resolve(10))    // Promise<Result<number, never>>
    Result.from({ ok: false, code: 'ERR', message: 'fail' }) // Result<never, { code: 'ERR' }>

API Overview

Static helpers

  • Result.ok(value) – wrap any value in an Ok
  • Result.err(code, message, details?) – build structured errors consistently
  • Result.async(promise, handleError?) – convert promises into Result
  • Result.func([schemas?], fn, handleError?) – wrap sync, async, or generator functions (with optional validation)
  • Result.funcJSON([schemas?], fn, handleError?) – same as func, but returns JSON-serializable { ok, value } or { ok, code, message } shape
  • Result.validate(schema | schemas, input) – validate inputs using any @standard-schema/spec implementation
  • Result.from(value) – convert any value (plain, Result, JSON shape, or Promise) into a Result
  • Result.fromJSON(json) – restore a result from its serialised shape
  • Result.tryJSON(json) – safely parse JSON into Result or undefined
  • Result.isJSON(value) – check if value is a Result JSON shape

Instance helpers

  • result.isOk() / result.isErr(code?) – type-guarding checks with optional error-code narrowing
  • result.map(fn) – transform Ok value
  • result.catch(fn) – recover from Err
  • result.ifOk(fn) / result.ifErr(fn) – side-effects
  • result._unsafeUnwrap() – throw if Err, otherwise return the inner value
  • result.toJSON() – serialise a result for transport or storage
  • result.toString() – JSON string representation

Utilities & types

  • Result.ValidationError(issues) – standardised validation failure shape
  • Result.ThrownError(details) – wraps unknown thrown errors
  • Type exports: Result.Ok, Result.Err, Result.Any, Result.ValueOf<T>, Result.ErrorOf<T>, Result.ErrorOnly<T> for advanced typing needs