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

@arnonym/result

v1.0.5

Published

Result type for TypeScript inspired by Rust

Readme

@arnonym/result

A Result type for TypeScript inspired by Rust.

Why?

Result is a type that represents either success or failure. It is a more explicit way to handle errors compared with exceptions.

What are the problems with exceptions?

1. Hidden Control Flow

  • Exceptions disrupt the normal flow of execution, making it harder to understand how a program will behave.

  • Code can jump to a completely different part of the program, leading to unexpected behavior.

2. Lack of Type Safety

  • Unlike function return types, exceptions are untyped in TypeScript.

Getting started

Installation

Install using a package manager of your choice (e.g. npm):

npm install @arnonym/result

Requirements

You need to enable strict mode in your tsconfig.json:

{
    "compilerOptions": {
        "strict": true
    }
}

Using Result

A typical function using Result might look like this:

import { Result } from '@arnonym/result';

type Version = 'one' | 'two';

class VersionError extends Error {
    readonly type = 'version';
}

class HeaderSizeError extends Error {
    readonly type = 'header-size';
}

function parseHeaderVersion(header: number[]): Result<Version, VersionError | HeaderSizeError> {
    switch (header[0]) {
        case undefined:
            return Result.err(new HeaderSizeError('Header size mismatch'));
        case 1:
            return Result.ok('one');
        case 2:
            return Result.ok('two');
        default:
            return Result.err(new VersionError('Invalid version'));
    }
}

function parseHeaderLength(header: number[]): Result<number, HeaderSizeError> {
    switch (header[1]) {
        case undefined:
            return Result.err(new HeaderSizeError('Header size mismatch'));
        default:
            return Result.ok(header[1]);
    }
}

const header = [1, 2, 3];
const version = parseHeaderVersion(header);
version.match({
    ok: v => console.log(`Using version: ${v}`),
    err: e => console.error(`Error: ${e.message}`),
});
const headerLength = parseHeaderLength(header);
if (headerLength.isOk()) {
    console.log(`Header length: ${headerLength.value}`);
}

Result.handle

Let's first take a look at this function:

function parseHeader(header: number[]): Result<[Version, number], VersionError | HeaderSizeError> {
    const version = parseHeaderVersion(header);
    if (version.isErr()) {
        return version;
    }
    const length = parseHeaderLength(header);
    if (length.isErr()) {
        return length;
    }
    if (length.value !== header.length) {
        return Result.err(new HeaderSizeError('Header size does not match'));
    }
    return Result.ok([version.value, length.value]);
}

The if statements are a bit noisy and the function is not very readable.

We can use Result.handle to make it more concise:

function parseHeader(header: number[]): Result<[Version, number], VersionError | HeaderSizeError> {
    return Result.handle(function* () {
        // if parseHeaderVersion returns an error, the computation will stop here
        // and propagate the error to the outer function, otherwise the unwrapped value
        // will be used in the next computation
        const version: Version = yield* parseHeaderVersion(header);
        const length: number = yield* parseHeaderLength(header);
        if (length !== header.length) {
            return yield* Result.err(new HeaderSizeError('Header size does not match'));
        }
        return [version, length];
    });
}

This is using a generator function to short-circuit the computation if an error occurs and uses the Result-less value inside the function for further computation. The returned value will be wrapped in a successful Result. Also, the result type reflects all the errors that might occur. Kinda like the question mark operator in Rust.

And these tests will pass for both functions:

const parsedHeader = parseHeader([1, 3, 3]);
expect(parsedHeader.isOk() && parsedHeader.value).toStrictEqual(['one', 3]);

const anotherHeader = parseHeader([3, 2, 1]);
expect(anotherHeader.isErr() && anotherHeader.err.type).toStrictEqual('version');

const yetAnotherHeader = parseHeader([1]);
expect(yetAnotherHeader.isErr() && yetAnotherHeader.err.type).toStrictEqual('header-size');

const moreHeader = parseHeader([1, 5, 3]);
expect(moreHeader.isErr() && moreHeader.err.message).toStrictEqual('Header size does not match');

Same goes for Result.handleAsync to handle async functions:

async function asyncParseHeader(header: number[]): Promise<Result<[Version, number], Error>> {
    return Result.handleAsync(async function* () {
        // These are not async functions, but it's just an example
        const version: Version = yield* await parseHeaderVersion(header);
        const length: number = yield* await parseHeaderLength(header);
        return [version, length];
    });
}

Pipe

You can also use pipe to build pipelines:

const header = [1, 2];
const result = pipe(
    header,
    parseHeaderLength,
    Result.andThen(length => {
        if (header.length !== length) {
            return Result.err(new HeaderSizeError());
        } else {
            return Result.ok(length);
        }
    }),
);
expect(result).toStrictEqual(Result.ok(2));

Convenience functions

There are also some convenience functions to make working with Result easier:

// create a Result with a value or an error
const goodResult: Result<number, Error> = Result.ok(10);
const badResult: Result<number, Error> = Result.err(new Error('Something went wrong'));

// isOK and isErr functions do what you expect
expect(goodResult.isOk()).toBe(true);
expect(goodResult.isErr()).toBe(false);
expect(badResult.isErr()).toBe(true);
expect(badResult.isOk()).toBe(false);
// use them to narrow down the type and access value or error
expect(goodResult.isOk() && goodResult.value).toBe(10);
expect(badResult.isErr() && badResult.err.message).toBe('Something went wrong');

// `map` and `mapErr` maps `Result` to another one of same type.
const mappedGoodResult: Result<number, Error> = goodResult.map(i => i + 1);
const mappedBadResult: Result<number, Error> = badResult.mapErr(e => new Error(e.message + " and I can't fix it"));
expect(mappedGoodResult.isOk() && mappedGoodResult.value).toEqual(11);
expect(mappedBadResult.isErr() && mappedBadResult.err.message).toBe("Something went wrong and I can't fix it");

// Use `andThen` to continue the computation.
const andThenGoodResult: Result<boolean, Error> = goodResult.andThen(i => Result.ok(i === 10));
expect(andThenGoodResult.isOk() && andThenGoodResult.value).toEqual(true);

// Use `or` or `orElse` to handle the error.
const orBadResult: Result<number, Error> = badResult.or(10);
expect(orBadResult.isOk() && orBadResult.value).toEqual(10);

const orElseBadResult: Result<number, Error> = badResult.orElse(e => e.message.length);
expect(orElseBadResult.isOk() && orElseBadResult.value).toEqual(20);

// Unwrap gets you out of Result land:

// If you're sure that the result is `Ok`, you can use `unwrap` to get the value.
// This will throw an exception if the result is an error, so use with caution.
const finalAwesomeResult = goodResult.unwrap();
expect(finalAwesomeResult).toBe(10);

// If you're not sure, you can provide a default value with `unwrapOr`.
const anotherAwesomeResult = badResult.unwrapOr(42);
expect(anotherAwesomeResult).toBe(42);

// Or you can provide a function that will be called with the error.
const yetAnotherAwesomeResult = badResult.unwrapOrElse(e => e.message.length);
expect(yetAnotherAwesomeResult).toBe(20);

Interop with exceptions and promises

To use Result with code that throws exceptions, you can use the try function:

class LegacyError extends Error {
    readonly type = 'legacy';
}

function mightThrowAnError(): number {
    throw new Error('This is an error');
}

const result = Result.try(() => mightThrowAnError()).mapErr(e => new LegacyError(String(e)));
expect(result.isErr() && result.err.type).toBe('legacy');

Or a Promise that might reject:

function mightReject(): Promise<number> {
    return Promise.reject(new Error('This is an error'));
}

const result = await Result.tryPromise(() => mightReject());
expect(result.isErr() && (result.err as Error).message).toBe('This is an error');

API docs

tbd