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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@cardellini/ts-result

v0.3.2

Published

Useful type to model success and failure

Downloads

34

Readme

Result Coverage Status npm version npm downloads GitHub license

Useful type to model success and failure, implemented with focus on type safety, developer experience and preserving flat learning curve.

Installation

npm install @cardellini/ts-result

Usage

Creating a Result

import { Result, ok, err } from '@cardellini/ts-result';

type JsonObject = Record<string, unknown>;

const okIfObject = (value: unknown): Result<JsonObject, 'ERR_NOT_AN_OBJECT'> =>
  typeof value === 'object' && value !== null && !Array.isArray(value)
    ? ok(value as JsonObject)
    : err('ERR_NOT_AN_OBJECT');

const okIfInt = (value: unknown): Result<number, 'ERR_NOT_AN_INT'> =>
  Number.isInteger(value)
    ? ok(value as number)
    : err('ERR_NOT_AN_INT');

const okIfString = (value: unknown): Result<string, 'ERR_NOT_A_STRING'> =>
  typeof value === 'string'
    ? ok(value)
    : err('ERR_NOT_A_STRING');

Composing with Do-notation

type Person = {
  name: string;
  age: number;
}

const okIfPerson = (value: unknown): Result<Person, 'ERR_NOT_A_PERSON'> =>
  Do(function*() {
    const obj = yield* okIfObject(value);
    const name = yield* okIfString(obj.name);
    const age = yield* okIfInt(obj.age);

    return { name, age };
  }).mapErr(() => 'ERR_NOT_A_PERSON');

const person: Person = okIfPerson({ name: 'John', age: 42 }).unwrap();

Composing with chain

const okIfPerson =
  (value: unknown) => okIfObject(value).chain(
  (obj)            => okIfString(obj.name).chain(
  (name)           => okIfInt(obj.age).chain(
  (age)            => ok({ name, age })
)));

or the same with map on the last step:

const okIfPerson =
  (value: unknown) => okIfObject(value).chain(
  (obj)            => okIfString(obj.name).chain(
  (name)           => okIfInt(obj.age).map(
  (age)            => ({ name, age })
)));

Note: from the performance perspective, using chain is preferable to Do-notation, because chain doesn't create and run generators. However, Do-notation is more readable and easier to use. Additionally, the formatting of the code in this section requires specific linters and formatters configuration.

Collecting Ok-s from a Result Array

const lordOfTheRingsAuthors = collect([
  ok({ id, name: 'J. R. R. Tolkien' }),
  ok({ id, name: 'Christopher Tolkien' }),
]);

const silmarillionAuthors = collect([
  ok({ id, name: 'J. R. R. Tolkien' }),
  err('ERR_PERSON_NOT_FOUND' as const),
]);

console.log(lordOfTheRingsAuthors.unwrap());
// Prints to console:
// [
//   { id, name: 'J. R. R. Tolkien' },
//   { id, name: 'Christopher Tolkien' }
// ]

console.log(silmarillionAuthors.unwrapErr());
// Prints to console: ERR_PERSON_NOT_FOUND

Working with Async Results

import { asyncDo, collect, err, ok } from '@cardellini/ts-result';

const getBookWithAuthors = (bookId: string) =>
  asyncDo(async function* () {
    const book = yield* await fetchBook(bookId);
    const authors = yield* await fetchPersons(book.authorIds);

    return { ...book, authors };
  });

const fetchBook = async (id: string) => (
  id === '1' ? ok({ id, title: 'The Lord of the Rings', authorIds: ['1', '2'] }) :
  id === '2' ? ok({ id, title: 'The Silmarillion', authorIds: ['1', '3'] }) :
  err('ERR_BOOK_NOT_FOUND' as const)
);

const fetchPersons = async (ids: string[]) => collect(
  ids.map(id => (
    id === '1' ? ok({ id, name: 'J. R. R. Tolkien' }) :
    id === '2' ? ok({ id, name: 'Christopher Tolkien' }) :
    err("ERR_PERSON_NOT_FOUND" as const)
  ))
);

async function run() {
  const LordOfTheRings = await getBookWithAuthors('1');
  console.log(LordOfTheRings.unwrap());
  // Prints to console book with authors populated

  const Silmarillion = await getBookWithAuthors('2');
  console.log(Silmarillion.unwrapErr());
  // Prints to console: ERR_PERSON_NOT_FOUND

  const TheHobbit = await getBookWithAuthors('3');
  console.log(TheHobbit.unwrapErr());
  // Prints to console: ERR_BOOK_NOT_FOUND
}

run().catch(console.error);

Documentation

[TODO: insert link to documentation]

Result Type

Result<T, E> is a generic type that represents either success or failure, where:

  • T is the type of value that represents success and wrapped with Ok<T>.
  • E is the type of error that represents failure and wrapped with Err<E>.

The Result<T, E> is not a union type, it is an interface with the methods described below, which is implemented by Ok<T> and Err<E> classes.

Methods of Result<T, E> Instances

isOk()

Returns true if Result is Ok<T>, false otherwise. Narrows the Result<T, E> to Ok<T>.

Method Signature:

interface Result<T, E> {
  isOk(): this is Ok<T>
}

Function Signature:

const isOk: <T, E>(result: Result<T, E>) => result is Ok<T>

isErr()

Returns true if Result is Err<E>, false otherwise. Narrows the Result<T, E> to Err<E>.

Method Signature:

interface Result<T, E> {
  isErr(): this is Err<E>
}

Function Signature:

const  isErr: <T, E>(result: Result<T, E>): result is Err<E>

map(fn)

Applies fn to the value of Ok<T> and returns the value wrapped in Ok<S>. If Result<T, E> is Err<E> returns itself without applying fn.

Method Signature:

interface Result<T, E> {
  map<S>(fn: (data: T) => S): Result<S, E>
}

Curried Function Signature:

const map:
  <T, S>(fn: (data: T) => S) =>
  <E>(result: Result<T, E>) => Result<S, E>

mapErr(fn)

Applies fn to the value of Err<E> and returns the value wrapped in Err<F>. If Result<T, E> is Ok<T> returns itself without applying fn.

Method Signature:

interface Result<T, E> {
  mapErr<F>(fn: (error: E) => F): Result<T, F>
}

Curried Function Signature:

const mapErr:
  <E, F>(fn: (error: E) => F) =>
  <T>(result: Result<T, E>) => Result<T, F>

chain(next)

Applies next to the value of Ok<T> and returns the result of next. If the Result<T, E> is Err<E>, returns itself without applying next.

Method Signature:

interface Result<T, E> {
  chain<S, F>(next: (data: T) => Result<S, F>): Result<T | S, E | F>
}

Curried Function Signature:

const chain:
  <T, S, E, F>(next: (data: T) => Result<S, F>) =>
  (result: Result<T, E>) => Result<T | S, E | F>

chainErr(next)

Applies next to the value of Err<E> and returns the result of next. If the Result<T, E> is Ok<T>, returns itself without applying next.

Method Signature:

interface Result<T, E> {
  chainErr<S, F>(next: (error: E) => Result<S, F>): Result<T | S, E | F>
}

Curried Function Signature:

const chainErr:
  <S, E, F>(next: (error: E) => Result<S, F>) =>
  <T>(result: Result<T, E>) => Result<T | S, E | F>

unwrap()

Returns the value of Ok<T>. If the Result<T, E> is Err<E> throws a TypeError where cause is the Err<E>.

Method Signature:

interface Result<T, E> {
  unwrap(): T
}

Function Signature:

const unwrap: <T>(result: Result<T, unknown>) => T

unwrapGen()

Returns a generator function that yields the value of Err<E> or returns the value of Ok<T>.

Method Signature:

interface Result<T, E> {
  unwrapGen(): Generator<E, T, unknown>
}

Function Signature:

const unwrapGen: <T, E>(result: Result<T, E>) => Generator<E, T, unknown>

unwrapOr(fallback)

Returns the value of Ok<T>. If the Result<T, E> is Err<E> returns fallback.

Method Signature:

interface Result<T, E> {
  unwrapOr<S>(fallback: S): T | S
}

Curried Function Signature:

const unwrapOr:
  <T, S>(fallback: S) =>
  (result: Result<T, unknown>) => T | S

unwrapOrThrow()

Returns the value of Ok<T>. If the Result<T, E> is Err<E> throws a value of type E.

unwrapOrThrow doesn't check if E is an instance of Error or not, so it is possible to throw a non-error literal.

Method Signature:

interface Result<T, E> {
  unwrapOrThrow(): T
}

Function Signature:

const unwrapOrThrow: <T>(result: Result<T, unknown>) => T

unwrapOrElse

Returns the value of Ok<T>. If the Result<T, E> is Err<E> returns the result of fallbackFn.

Method Signature:

interface Result<T, E> {
  unwrapOrElse<S>(fallbackFn: (error: E) => S): T | S
}

Curried Function Signature:

const unwrapOrElse:
  <T, S>(fallbackFn: (error: unknown) => S) =>
  (result: Result<T, unknown>) => T | S

unwrapErr

Returns the value of Err<E>. If the Result<T, E> is Ok<T> throws a TypeError where cause is the Ok<T>.

Method Signature:

interface Result<T, E> {
  unwrapErr(): E
}

Function Signature:

const unwrapErr: <E>(result: Result<unknown, E>) => E

unwrapErrOr(fallback)

Returns the value of Err<E>. If the Result<T, E> is Ok<T> returns fallback.

Method Signature:

interface Result<T, E> {
  unwrapErrOr<F>(fallback: F): E | F
}

Curried Function Signature:

const unwrapErrOr:
  <F>(fallback: F) =>
  <T, E>(result: Result<T, E>) => E | F

unwrapErrOrElse(fallbackFn)

Returns the value of Err<E>. If the Result<T, E> is Ok<T> returns the result of fallback.

Method Signature:

interface Result<T, E> {
  unwrapErrOrElse<F>(fallbackFn: (data: T) => F): E | F
}

Curried Function Signature:

const unwrapErrOrElse:
  <F, T>(fallbackFn: (data: T) => F) =>
  <E>(result: Result<T, E>) => E | F

unpack()

Returns the value of Ok<T> or Err<E>.

Method Signature:

interface Result<T, E> {
  unpack(): T | E
}

Function Signature:

const unpack: <T, E>(result: Result<T, E>) => T | E

match(okMatcher, errMatcher)

Applies okMatcher to the value of Ok<T> and returns the result. Applies errMatcher to the value of Err<E> and returns the result.

Method Signature:

interface Result<T, E> {
  match<S, F>(okMatcher: (data: T) => S, errMatcher: (error: E) => F): S | F
}

Curried Function Signature:

const match:
  <T, S, E, F>(okMatcher: (data: T) => S, errMatcher: (error: E) => F) =>
  (result: Result<T, E>) => S | F

tap(fn)

Applies fn to the value of Ok<T> and returns the original result. If the Result<T, E> is Err<E> doesn't apply fn.

Method Signature:

interface Result<T, E> {
  tap(fn: (data: T) => void): Result<T, E>
}

Curried Function Signature:

const tap:
  <T>(fn: (data: T) => void) =>
  <E>(result: Result<T, E>) => Result<T, E>

tapErr(fn)

Applies fn to the value of Err<E> and returns the original result. If the Result<T, E> is Ok<T> doesn't apply fn.

Method Signature:

interface Result<T, E> {
  tapErr(fn: (error: E) => void): Result<T, E>
}

Curried Function Signature:

const tapErr:
  <E>(fn: (error: E) => void) =>
  <T>(result: Result<T, E>) => Result<T, E>

apply(fnResult)

Applies the function wrapped in Ok<(data: T) => S> of argument fnResult to the value of Ok<T> and returns the result of the application wrapped into Ok<S>. If the Result<T, E> is Err<E> or fnResult is Err<F>, returns itself without applying the function.

Method Signature:

interface Result<T, E> {
  apply<S, F>(fnResult: Result<(data: T) => S, F>): Result<S, E | F>
}

Curried Function Signature:

const apply: 
  <T, S, F>(fnResult: Result<(data: T) => S, F>) =>
  <E>(result: Result<T, E>) => Result<S, E | F>

Operating on Multiple Results

collect(results)

Collects Ok<T> values from an array of Result<T, E> and returns a Result<T[], E>.

Function Signature:

const collect:
  <R extends readonly Result<any, any>[]>(results: R) => Result<Collected<R>, ErrTypeOf<R[number]>>