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

rusty-enum

v0.2.4

Published

A lightweight Rust/Haskell-like enum for Typescript, with some runtime overhead.

Downloads

14

Readme

Rusty Enum

A lightweight Rust/Haskell-like enum for Typescript, with some runtime overhead.

Overview

rusty-enum aims to replicate the rust enum syntax, and chooses to provide an easy-to-use API. As a result, some runtime overhead is required. For a 0kb Rust-like enum for Typescript, see unenum.

Defining an Enum

import {EnumType, Enum} from "rusty-enum"; 

/* Define an enum schema as an interface.  */
interface Message {
  Quit: null,
  Move: { x: number, y: number },
  Write: string,
  ChangeColor: [number, number, number]
};
/* Acquire a factory for your enum. This returns a global factory instance. */
const Message = Enum<Message>();

Instantiation

const quitMsg = Message.Quit();
const moveMsg = Message.Move({ x: 42, y: 64 });
const writeMsg = Message.Write("some text");
const changeColorMsg = Message.ChangeColor(102, 204, 255);

Variant Matching

rusty-enum enforces compile-time exhaustive matching. You can either provide matching handlers for every possible variant

function handleMsg(msg: EnumType<Message>) {
  return msg.match({
    Move: ({ x, y }) => `moved to (${x}, ${y})`,
    Write: (s) => `wrote '${s}'`,
    ChangeColor: (r, g, b) => `color changed to rgb(${r}, ${g}, ${b})`,
    Quit: () => "someone quit",
  });
  // Or equivalently
  return match(msg, {
    Move: ({ x, y }) => `moved to (${x}, ${y})`,
    Write: (s) => `wrote '${s}'`,
    ChangeColor: (r, g, b) => `color changed to rgb(${r}, ${g}, ${b})`,
    Quit: () => "someone quit",
  });
}

...or provide a wildcard handler.

function handleMsgWithDefault(msg: EnumType<Message>) {
  return msg.match({
    Write: () => "a message is written",
    _: () => "nothing is written",
  });
}

By default, rusty-enum expects all arms to return the same type, unless a union type is provided explicitly.

msg.match<number | string>({
  Write: () => 42, 
  _: () => "Fourty-two",
});

/* This does not compile */
msg.match({
  Write: () => 42, 
  _: () => "Fourty-two",
});

If you are only interested in a certain variant, you can use the ifLet util function.

function ifLet(rustyEnum, variant, cb);

const moveX = ifLet(moveMsg, "Move", ({ x }) => x);
expect(moveX).toEqual(42);

If rustyEnum is of variant variant, cb is executed with rustyEnum's data, and the return value is returned. Otherwise null is returned immediately.

To determine the variant of an enum without unwrapping its content, use enum.is${Variant}().

msg.isQuit();
msg.isMove();
msg.isWrite();
msg.isChangeColor();

Or directly access the ._variant readonly attribute.

msg._variant;

Result and Option

rusty-enum comes with Result<T, E> and Option<T>.

import {EnumType, Result} from "rusty-enum";

type QueryResult = Result<number, Error>;

function handleQueryResult(res: EnumType<QueryResult>) {
  res.match({
    Ok: (num) => console.log(num),
    Err: (err) => console.error(err),
  });
  return res.isOk();
}

Enum in Promise and Async Matching

async function msgPromise(): EnumPromise<Message> {
  return Message.Move({x: 42, y: 64});
}

An async function that returns an enum can have its return type as EnumPromise<S>, which is short for Promise<EnumType<S>>. An EnumPromise<S> can be processed by asyncMatch.

const moveMsgPromise = msgPromise();
const x = await asyncMatch(moveMsgPromise, {
  Move({ x }) {
    return x;
  },
  _: () => 0
});

expect(x).toEqual(42);

Converting JS Promises into Enums

A JS Promise<T> can be converted into OptionPromise<T> or ResultPromise<T, E>.

intoOptionPromise(p: Promise<T>) return OptionPromise.Some(T) if p is resolved, or OptionPromise.None() if it is rejected.

Similarly, intoResultPromise(p: Promise<T>) return ResultPromise.Ok(T) if p is resolved, or ResultPromise.Err(err) if it is rejected, where err is the reason of rejection with type any.

Additionally, intoResultPromise accepts a callback function, where the any typed err can be mapped into a given error type E.

const rejectPromise: Promise<number> = new Promise((_, rej) => rej(42));
const rejectResult = await intoResultPromise<number, string>(rejectPromise, (err) => err.toString());

Read more in the library test script.

Converting Error-Throwing Functions into Result-Returning Functions

function foo(arg: number) {
  if (isNaN(arg)) {
    throw "Sample error string";
  } else {
    return 42;
  }
}
const enumifiedFoo = enumifyFn<string, typeof foo>(foo);
// OR
const enumifiedFoo = enumifyFn(foo); // returns Result<number, unknown>

enumifiedFoo(43).match({
  Ok(res) {
    ...
  },
  Err(err) {
    ...
  }
})

Runtime Costs

For creating enum instances, one and only one factory object is created by rusty-enum.

const factoryInstance: Factory<any> = {};

const factoryProxy = new Proxy(factoryInstance, {
  get(target, prop, receiver) {
    return (...args: any[]) => create(prop, ...args);
  }
});

Every enum instance is proxied to support .is${Variant}() methods.

function create(...args: any[]) {
  return new Proxy({
    _variant: args[0],
    _data: args.length > 2 ? args.slice(1) : args[1],
    match,
  }, {
    get(target, prop, receiver) {
      const propStr = prop.toString();
      if (propStr.startsWith("is")) {
        return () => target._variant === propStr.substring(2)
      }
      return Reflect.get(target, prop, receiver);
    }
  })
}