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

@wunderwerk/ts-results

v0.1.1

Published

A typescript implementation of Rust's Result and Option objects.

Downloads

4

Readme

ts-results

A typescript implementation of Rust's Result and Option objects.

Brings compile-time error checking and optional values to typescript.

Contents

Installation

$ npm install ts-results

or

$ yarn add ts-results

Example

Result Example

Convert this:

import { existsSync, readFileSync } from "fs";

function readFile(path: string): string {
  if (existsSync(path)) {
    return readFileSync(path);
  } else {
    // Callers of readFile have no way of knowing the function can fail
    throw new Error("invalid path");
  }
}

// This line may fail unexpectedly without warnings from typescript
const text = readFile("test.txt");

To this:

import { existsSync, readFileSync } from "fs";
import { Ok, Err, Result } from "ts-results";

function readFile(path: string): Result<string, "invalid path"> {
  if (existsSync(path)) {
    return new Ok(readFileSync(path)); // new is optional here
  } else {
    return new Err("invalid path"); // new is optional here
  }
}

// Typescript now forces you to check whether you have a valid result at compile time.
const result = readFile("test.txt");
if (result.ok) {
  // text contains the file's content
  const text = result.val;
} else {
  // err equals 'invalid path'
  const err = result.val;
}

Option Example

Convert this:

declare function getLoggedInUsername(): string | undefined;

declare function getImageURLForUsername(username: string): string | undefined;

function getLoggedInImageURL(): string | undefined {
  const username = getLoggedInUsername();
  if (!username) {
    return undefined;
  }

  return getImageURLForUsername(username);
}

const stringUrl = getLoggedInImageURL();
const optionalUrl = stringUrl ? new URL(stringUrl) : undefined;
console.log(optionalUrl);

To this:

import { Option, Some, None } from "ts-results";

declare function getLoggedInUsername(): Option<string>;

declare function getImageForUsername(username: string): Option<string>;

function getLoggedInImage(): Option<string> {
  return getLoggedInUsername().andThen(getImageForUsername);
}

const optionalUrl = getLoggedInImage().map((url) => new URL(stringUrl));
console.log(optionalUrl); // Some(URL('...'))

// To extract the value, do this:
if (optionalUrl.some) {
  const url: URL = optionalUrl.val;
}

Usage

import { Result, Err, Ok } from "ts-results";

Creation

let okResult: Result<number, Error> = Ok(10);
let errorResult: Result<number, Error> = Err(new Error("bad number!"));

Type Safety

Note: Typescript currently has a bug, making this type narrowing only work when strictNullChecks is turned on.

let result: Result<number, Error> = Ok(1);
if (result.ok) {
  // Typescript knows that result.val is a number because result.ok was true
  let number = result.val + 1;
} else {
  // Typescript knows that result.val is an `Error` because result.ok was false
  console.error(result.val.message);
}

if (result.err) {
  // Typescript knows that result.val is an `Error` because result.err was true
  console.error(result.val.message);
} else {
  // Typescript knows that result.val is a number because result.err was false
  let number = result.val + 1;
}

Stack Trace

A stack trace is generated when an Err is created.

let error = Err("Uh Oh");
let stack = error.stack;

Unwrap

let goodResult = new Ok(1);
let badResult = new Err(new Error("something went wrong"));

goodResult.unwrap(); // 1
badResult.unwrap(); // throws Error("something went wrong")

Expect

let goodResult = Ok(1);
let badResult = Err(new Error("something went wrong"));

goodResult.expect("goodResult should be a number"); // 1
badResult.expect("badResult should be a number"); // throws Error("badResult should be a number - Error: something went wrong")

ExpectErr

let goodResult = Ok(1);
let badResult = Err(new Error("something went wrong"));

goodResult.expect("goodResult should not be a number"); // throws Error("goodResult should not be a number")
badResult.expect("badResult should not be a number"); // new Error('something went wrong')

Map and MapErr

let goodResult = Ok(1);
let badResult = Err(new Error("something went wrong"));

goodResult.map((num) => num + 1).unwrap(); // 2
badResult.map((num) => num + 1).unwrap(); // throws Error("something went wrong")

goodResult
  .map((num) => num + 1)
  .mapErr((err) => new Error("mapped"))
  .unwrap(); // 2
badResult
  .map((num) => num + 1)
  .mapErr((err) => new Error("mapped"))
  .unwrap(); // throws Error("mapped")

andThen

let goodResult = Ok(1);
let badResult = Err(new Error("something went wrong"));

goodResult.andThen((num) => new Ok(num + 1)).unwrap(); // 2
badResult.andThen((num) => new Err(new Error("2nd error"))).unwrap(); // throws Error('something went wrong')
goodResult.andThen((num) => new Err(new Error("2nd error"))).unwrap(); // throws Error('2nd error')

goodResult
  .andThen((num) => new Ok(num + 1))
  .mapErr((err) => new Error("mapped"))
  .unwrap(); // 2
badResult
  .andThen((num) => new Err(new Error("2nd error")))
  .mapErr((err) => new Error("mapped"))
  .unwrap(); // throws Error('mapped')
goodResult
  .andThen((num) => new Err(new Error("2nd error")))
  .mapErr((err) => new Error("mapped"))
  .unwrap(); // thros Error('mapped')

Else

Deprecated in favor of unwrapOr

UnwrapOr

let goodResult = Ok(1);
let badResult = Err(new Error("something went wrong"));

goodResult.unwrapOr(5); // 1
badResult.unwrapOr(5); // 5

Empty

function checkIsValid(isValid: boolean): Result<void, Error> {
  if (isValid) {
    return Ok.EMPTY;
  } else {
    return new Err(new Error("Not valid"));
  }
}

Combining Results

ts-results has two helper functions for operating over n Result objects.

Result.all

Either returns all of the Ok values, or the first Err value

let pizzaResult: Result<Pizza, GetPizzaError> = getPizzaSomehow();
let toppingsResult: Result<Toppings, GetToppingsError> = getToppingsSomehow();

let result = Result.all(pizzaResult, toppingsResult); // Result<[Pizza, Toppings], GetPizzaError | GetToppingsError>

let [pizza, toppings] = result.unwrap(); // pizza is a Pizza, toppings is a Toppings.  Could throw GetPizzaError or GetToppingsError.
Result.any

Either returns the first Ok value, or all Err values

let url1: Result<string, Error1> = attempt1();
let url2: Result<string, Error2> = attempt2();
let url3: Result<string, Error3> = attempt3();

let result = Result.any(url1, url2, url3); // Result<string, Error1 | Error2 | Error3>

let url = result.unwrap(); // At least one attempt gave us a successful url

Usage with rxjs

resultMap

Allows you to do the same actions as the normal rxjs map operator on a stream of Result objects.

import { of, Observable } from "rxjs";
import { Ok, Err, Result } from "ts-results";
import { resultMap } from "ts-results/rxjs-operators";

const obs$: Observable<Result<number, Error>> = of(Ok(5), Err("uh oh"));

const greaterThanZero = obs$.pipe(
  resultMap((number) => number > 0), // Doubles the value
); // Has type Observable<Result<boolean, 'uh oh'>>

greaterThanZero.subscribe((result) => {
  if (result.ok) {
    console.log("Was greater than zero: " + result.val);
  } else {
    console.log("Got Error Message: " + result.val);
  }
});

// Logs the following:
// Got number: 10
// Got Error Message: uh oh

resultMapErr

import { resultMapErr } from "ts-results/rxjs-operators";

Behaves exactly the same as resultMap, but maps the error value.

resultMapTo

import { resultMapTo } from "ts-results/rxjs-operators";

Behaves the same as resultMap, but takes a value instead of a function.

resultMapErrTo

import { resultMapErrTo } from "ts-results/rxjs-operators";

Behaves the same as resultMapErr, but takes a value instead of a function.

elseMap

Allows you to turn a stream of Result objects into a stream of values, transforming any errors into a value.

Similar to calling the else function, but works on a stream of Result objects.

import { of, Observable } from "rxjs";
import { Ok, Err, Result } from "ts-results";
import { elseMap } from "ts-results/rxjs-operators";

const obs$: Observable<Result<number, Error>> = of(
  Ok(5),
  Err(new Error("uh oh")),
);

const doubled = obs$.pipe(
  elseMap((err) => {
    console.log("Got error: " + err.message);

    return -1;
  }),
); // Has type Observable<number>

doubled.subscribe((number) => {
  console.log("Got number: " + number);
});

// Logs the following:
// Got number: 5
// Got error: uh oh
// Got number: -1

elseMapTo

import { elseMapTo } from "ts-results/rxjs-operators";

Behaves the same as elseMap, but takes a value instead of a function.

resultSwitchMap and resultMergeMap

Allows you to do the same actions as the normal rxjs switchMap and rxjs switchMap operator on a stream of Result objects.

Merging or switching from a stream of Result<T, E> objects onto a stream of <T2> objects turns the stream into a stream of Result<T2, E> objects.

Merging or switching from a stream of Result<T, E> objects onto a stream of Result<T2, E2> objects turn the stream into a stream of Result<T2, E | T2> objects.

import { of, Observable } from "rxjs";
import { Ok, Err, Result } from "ts-results";
import { resultMergeMap } from "ts-results/rxjs-operators";

const obs$: Observable<Result<number, Error>> = of(
  new Ok(5),
  new Err(new Error("uh oh")),
);

const obs2$: Observable<Result<string, CustomError>> = of(
  new Ok("hi"),
  new Err(new CustomError("custom error")),
);

const test$ = obs$.pipe(
  resultMergeMap((number) => {
    console.log("Got number: " + number);

    return obs2$;
  }),
); // Has type Observable<Result<string, CustomError | Error>>

test$.subscribe((result) => {
  if (result.ok) {
    console.log("Got string: " + result.val);
  } else {
    console.log("Got error: " + result.val.message);
  }
});

// Logs the following:
// Got number: 5
// Got string: hi
// Got error: custom error
// Got error: uh oh

filterResultOk

Converts an Observable<Result<T, E>> to an Observble<T> by filtering out the Errs and mapping to the Ok values.

import { of, Observable } from "rxjs";
import { Ok, Err, Result } from "ts-results";
import { filterResultOk } from "ts-results/rxjs-operators";

const obs$: Observable<Result<number, Error>> = of(
  new Ok(5),
  new Err(new Error("uh oh")),
);

const test$ = obs$.pipe(filterResultOk()); // Has type Observable<number>

test$.subscribe((result) => {
  console.log("Got number: " + result);
});

// Logs the following:
// Got number: 5

filterResultErr

Converts an Observable<Result<T, E>> to an Observble<T> by filtering out the Oks and mapping to the error values.

import { of, Observable } from "rxjs";
import { Ok, Err, Result } from "ts-results";
import { filterResultOk } from "ts-results/rxjs-operators";

const obs$: Observable<Result<number, Error>> = of(
  new Ok(5),
  new Err(new Error("uh oh")),
);

const test$ = obs$.pipe(filterResultOk()); // Has type Observable<number>

test$.subscribe((result) => {
  console.log("Got number: " + result);
});

// Logs the following:
// Got number: 5