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

@flagg2/result

v1.8.2

Published

A type-safe rust-like result type for TypeScript

Downloads

469

Readme

@flagg2/result

npm npm npm package minimized gzipped size (select exports) NPM

This library provides a Result type for Typescript, allowing for better and safer error handling.

Table of Contents

Features

  • Rust-like Result type
  • Better error handling
  • Automatic type inference
  • More robust code
  • Zero dependencies
  • Minimalistic
  • Small package size

Usage

Imagine having a function which you use to split time into seconds and minutes. We will look at one implementation which uses result and a second one which does not.

// returns {hours: number, mins: number}
function parseTime(time: string) {
   const splitTime = time.split(":")

   return {
      hours: parseInt(splitTime[0], 10),
      mins: parseInt(splitTime[1], 10),
   }
}

Now you call parseTime in a different place of our codebase. This function uses a .split and relies on the result being at least 2 items long.

Because of that, you have to keep in mind that this function could throw, even though there is no indication by the type system that it could be the case.

This leads to uncaught errors.

Somehow, the function get called with an incorrect argument, for example "2051" instead of "20:51".

This arugment is however still a string which makes typescript unable to help us catch this error.

function faultyArgument() {
   const time = "2051"

   const result = splitTime(time)

   // You do not have any indication by the type system that this could throw.
   // You forget to use a try catch segment and end up with a runtime error

   return result
}

This is when the Result class comes in. Result indicates a computation which could fail. At runtime, could be either an Ok or an Err depending on cirumstances.

The massive benefit we get with Result is that we do not catch errors like the previously mentioned one at runtime , but rather at compilation time .

Let's look at the previous example with Result

function parseTime(time: string) {
   const splitTime = time.split(":")
   if (splitTime.length !== 2) {
      return Result.err("SPLIT_ERROR")
   }

   if (isNaN(parseInt(splitTime[0], 10)) || isNaN(parseInt(splitTime[1], 10))) {
      return Result.err("PARSE_ERROR")
   }

   if (parseInt(splitTime[0], 10) > 23 || parseInt(splitTime[1], 10) > 59) {
      return Result.err("VALUE_ERROR")
   }

   return Result.ok({
      hours: parseInt(splitTime[0], 10),
      mins: parseInt(splitTime[1], 10),
   })
}

Now, using the Result pattern, we are forced to deal with the fact that it could fail at compilation time .

Better yet, we know exactly which errors can occur and we can handle them accordingly.

For example:

function faultyArgument() {
   const time = "2051"

   const result = parseTime(time)
   // type is Result<{hours: number, mins: number}, "SPLIT_ERROR" | "PARSE_ERROR" | "VALUE_ERROR">

   // Here you gracefully handle the error case

   if (result.isErr()) {
      // errValue is only available after the type system is sure that the result is an Err
      switch (result.errValue) {
         case "SPLIT_ERROR":
            console.log("The time was not in the correct format")
            break
         case "PARSE_ERROR":
            console.log("The time contained non-numeric characters")
            break
         case "VALUE_ERROR":
            console.log("The time contained invalid values")
            break
      }

      return
   }

   // Here the type system is sure that the result is an Ok, and we get access to the "value" property

   const { hours, mins } = result.value

   console.log(`The time is ${hours}:${mins}`)
}

As you can see, it is much harder to shoot yourself in the foot while handling errors, making our code much more robust.

Whenever possible, the result return type gets inferred automatically for the best dev experience possible.

Base Classes

Result<T, E>

A class representing a computation which may succeed or fail.

Ok<T>

A class representing a successful computation.

Err<E>

A class representing a failed computation.

API

Result

Result.ok()

Creates a new Ok variant; If no value is provided, it defaults to null.

 static ok<T>(value?: T): Ok<T>

Result.err()

Creates a new Err variant. If no value is provided, it defaults to null. Optionally takes an origin argument which is the original error that was thrown.

 static err<E>(errValue?: E, origin?: Error): Err<E>

Result.from()

Creates a Result from a function, a promise, or a promise-returning function.

If an error is thrown at any point, it is caught and wrapped in an Err. Takes an optional catchFn argument which should be a function returning the value contained in the Err variant.

If the function or promise resolves successfully, the value will be wrapped in an Ok.

 static from<T, E>(fnOrPromise: (() => T | Promise<T>) | Promise<T>, catchFn = (err: Error) => null as E): Promise<Result<T>>

Result.tryCatch()

Wraps a function that returns a Result but may still throw an error, in which case it is caught and wrapped in and Err from the catchFn.

static tryCatch<T, const E = null>(fn: () => Result<T, E> | Promise<Result<T, E>>,catchFn: (err: Error) => E = () => null as E)

Result.infer()

Sometimes type inference does not work well with Result unions. You might notice that your arguments are being inferred as any or that the return types are not correct.

This can be the case when using andThen , map , mapErr , or match .

When this happens, call this function to get a type that is easier to work with.

 static infer<T extends Result>(result: T): T

Result.all()

Takes an array of Result instances and returns a single Result instance containing an array of all the Ok values.

If any of the results are an Err, the first Err value is returned.

 static all<T>(results: Result<T>[]): Result<[...]>

Result.isOk()

Returns true if the result is an Ok variant. If true, casts the result as Ok

   isOk(): this is Ok<T>

Result.isErr()

Returns true if the result is an Err variant. If true, casts the result as Err

   isErr(): this is Err<E>

Result.unwrap()

Returns the contained Ok value. Throws an error if the value is an Err.

   unwrap(): T

Result.unwrapErr()

Returns the contained Err value. Throws an error if the value is an Ok.

   unwrapErr(): E

Result.unwrapOr()

Returns the contained Ok value. If the value is an Err, returns the provided default value.

   unwrapOr(defaultValue: T): T

Result.expect()

Returns the contained Ok value. If the value is an Err, throws an error with the provided message.

   expect(message: string): T

Result.expectErr()

Returns the contained Err value. If the value is an Ok, throws an error with the provided message.

   expectErr(message: string): E

Result.match()

Calls the appropriate function based on the result based on if it is an Ok or an Err.

   match<U>(fn: { ok: (value: T) => U; err: (errValue: E) => U }): U

Result.andThen()

Calls the provided function if the result is an Ok. If the result is an Err, returns the Err value.

   andThen<U>(fn: (value: T) => Result<U, E>): Result<U, E>

Result.map()

Maps a Result<T, E> to Result<U, E> by applying a function to a contained Ok value, leaving an Err value untouched.

   map<U>(fn: (value: T) => U): Result<U, E>

Result.mapErr()

Maps a Result<T, E> to Result<T, F> by applying a function to a contained Err value, leaving an Ok value untouched.

   mapErr<F>(fn: (errValue: E) => F): Result<T, F>

Result.logIfErr()

Logs the error to the console if the result is an Err.

   logIfErr(): this

Ok

Ok.value

The value contained in the Ok variant.

value: T

Err

Err.errValue

The value contained in the Err variant.

errValue: E

Err.cause

A chain of Result instances that led to the error. Might have a Error instance at the end of the chain.

origin: Cause

Err.log()

Logs the error to the console.

   log(): this

Err.getTrace(): string

Returns a string representation of the error chain.

   getTrace(): string

Err.getCause(): Cause

Returns the cause of the error.

   getCause(): Cause

Err.getRootCause(): Cause

Returns the root cause of the error.

   getRootCause(): Cause