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

typesafely

v0.0.5

Published

TypeScript types designed to emulate the Rust Option and Result types.

Downloads

3

Readme

TypeSafely

TypeScript types (Option, Result and AsyncResult) designed to emulate Rust types and patterns.

There are a lot of npm libraries which do this. Here's another.

type CalculationResult = Result<CalculationOk, CalculationError>;

// Return a Result from a calculation which may fail
const performCalculationSafely = (): CalculationResult => {
  try {
    const data = handleCalculation();
    return Ok(data);
  } catch (e) {
    return Err(e.message);
  }
};

// The data variable is now a Result type which must be either Ok or Err
const data: CalculationResult = performCalculationSafely();

// Now pass data to the matchResult function and explicitly handle each state:
matchResult(data, {
  ok: (x) => x,
  err: (e) => e,
});

Usage

This library provides three high level types:

Option
Result
AsyncResult

The Option and Result types are modeled after the same types in Rust. The AsyncResult type is like a Result but includes an additional state to represent "loading" and is intended to be used for data which is produced asynchronously.

In addition to these type primitives there are a few additional helper methods and functions:

  • 'matching' functions, matchOption, matchResult, matchAsyncResult which operate like Rust match expressions.
  • unwrap and unwrapOr methods. Like in Rust, unwrap will "panic" if a type was not in an "Ok" state.
  • if[Ok|Err|Loading] and if[Some|None] methods which allow you to conditionally run some logic if a type is of a particular variants. Non-matching variants will be ignored.

Option Type

// Create a Some Option variant and pass it to a match statement
const opt: Option<number> = Some(900);

// The some branch will run and x will be 900
matchOption(opt, {
  some: (x) => console.log("[Some variant]:", x),
  none: () => console.log("[None variant]"),
});

// Create a None Option variant and pass it to a match statement
const opt: Option<number> = None();

// The none branch will run:
matchOption(opt, {
  some: (x) => console.log("[Some variant]:", x),
  none: () => console.log("[None variant]"),
});

Result Type

// Create an Ok Result variant and pass it to a match statement
const result: Result<number, string> = Ok(100);

// The ok branch will run and x will be 100:
matchResult(result, {
  ok: (x) => console.log("[Ok variant]:", x),
  err: (e) => console.log("[Err variant]:", e),
});

// Create an Ok Result variant and pass it to a match statement
const err: Result<number, string> = Err("Error");

// The err branch will run and e will be "Error"
matchResult(result, {
  ok: (x) => console.log("[Ok variant]:", x),
  err: (e) => console.log("[Err variant]:", e),
});

AsyncResult Type

The AsyncResult is similar to the Result type but includes another variant to represent loading state. This is especially useful for modelling asynchronously fetched data and provides strong guarantees you are handling the appropriate state of the response. No need to independently set and update loading/error/response states, which is error prone. No need to write out fragile logic like !loading && !response to check for error states.

const FetchDataComponent: React.FC = () => {
  // Use an AsyncResult to model some asynchronously fetched data
  const [data, setData] = React.useState<AsyncResult<number, string>>(
    AsyncResultLoading(),
  );

  const fetchData = async () => {
    try {
      // Handle fetching data here...
      setData(AsyncOk({ data: "ok!" }));
    } catch (err) {
      setData(AsyncErr("Failed to fetch data..."));
    }
  }

  React.useEffect(() => {
    fetchData();
  });

  return (
    <>
      {matchAsyncResult(data, {
        ok: x => <p>Data: {JSON.stringify(x)}<p>,
        err: e => <p>Error fetching data: {JSON.stringify(e)}</p>,
        loading: () => <p>Loading...</p>,
      })}
    </>
  );
};

Motivation

The main idea behind this approach is twofold and similar to the rationale for the similar design in Rust:

  • Result types can be used to model values which may represent an error state and avoid throwing and catching errors (which is difficult to type-check correctly in TypeScript). A Result makes it explicitly that a function may result in an error state, which calling code must handle.
  • Option types can be used to model values which may be in a present or absent state, which otherwise in JS/TS are usually modeled with null or undefined. An Option makes this presence or absence more explicit and avoids issues like 0 == false "" == false" etc.

For instance, imagine you have some value which is declared but not initialized yet.

const value: number = null;
// Somewhere else:
value = 50;

Later you want to check if the value is initialized and then run some other code:

if (!!value) {
  // Run some other code which expects value to be defined
}

But what if actually, some other code had already set this value to be 0? Then your !!value check would result in false and your code wouldn't run.

This is a simple example but a common pitfall and one which TypeScript can't easily protect against. Consider instead this:

const value: Option<number> = None();

matchOption(value, {
  some: (x) => x, // Handle non-empty case
  none: () => null, // Handle empty case
});

This code avoids the above issues completely.