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

better-enums

v0.2.2

Published

Better enums for TypeScript

Downloads

234

Readme

better-enums

npm version License: MIT typescript CI codecov bundle size

Better enums for TypeScript.

The better-enums library provides a simple utility for creating an improved version of TypeScript enums.

Full documentation is hosted here.

Motivation

Enums vs unions

Many in the TypeScript community consider using TypeScript's built-in enum keyword a bad practice (Anders Hejlsberg himself has stated he wouldn't have put them into the language in retrospect). The recommendation is to use union types instead, which may be inferred from arrays or objects when some runtime representation is also needed (e.g. for iteration). For more information on this topic, see:

Best of both worlds

This library provides a custom enum implementation which attempts to avoid the pitfalls of TS enums, while also enhancing unions with runtime features.

These "better enums" are created either from an array of values (a "simple enum") or an object mapping keys to values (a "labeled enum"), with a union type then being automatically inferred from these values. This pattern is the commonly recommended alternative to the enum keyword. Using this library has the advantage of encapsulating some of the necessary TypeScript magic that can be daunting for less experienced TypeScript programmers.

The labeled enums offer good compatibilty with built-in enums. Since they support the same dot syntax and can even be created from an existing built-in enum, migrating away from the enum keyword should be fairly straightforward for existing projects.

The main advantages of using better-enums over built-in enums and/or unions are:

  • 😌 simplifies the recommended "inferred union" pattern,
  • no built-in enum weirdness (e.g. no nominal typing, no mixed-in reverse mapping),
  • 🛡️ provides convenient type guards for CFA-compatible runtime checks (i.e. automatic type narrowing),
  • 📜 provides array of all values (labeled enums also provide arrays of keys or key-value pairs),
  • 🏭 includes helper functions for composing new enums from existing ones (by extending values or excluding from them),
  • 📈 can easily convert built-in enums,
  • 🌑 support for dot syntax (optional),
  • 💙 excellent type-safety in general.

Setup

Install the better-enums package using your favourite package manager:

npm install better-enums
yarn add better-enums
pnpm add better-enums

Usage

Creating enums

Simple enums

Import the Enum callable object to create a simple enum and infer its union type with the InferValue utility type:

import { Enum, type InferValue } from 'better-enums';

const ROLES = Enum(['viewer', 'editor', 'admin']);
type Role = InferValue<typeof ROLES>;

Then you can use the inferred type in your type definitions:

type User = {
  email: string;
  role: Role; // role is 'viewer' | 'editor' | 'admin'
};

If you prefer an enum-style syntax for accessing values, you can use the .accessor property (keys match values exactly):

const ROLES = Enum(['Viewer', 'Editor', 'Admin']);
type Role = InferValue<typeof ROLES>;
const Role = ROLES.accessor;

// ...

let role: Role;
// these are equivalent
role = Role.Admin;
role = 'Admin';

The enum object enables you to use runtime features:

  • list all values with .values():

    ROLES.values().forEach(role => {
      console.log(role);
    });
  • check value with .hasValue(x) (returns true/false):

    function f(value: string | undefined) {
      // value is string | undefined
      if (ROLES.hasValue(value)) {
        // value is Role
      }
    }
  • check value with .assertValue(x) (throws RangeError if invalid):

    function f(value: string | undefined) {
      try {
        // value is string | undefined
        ROLES.assertValue(value);
        // value is Role
      } catch (err: unknown) {
        if (err instanceof RangeError) {
          // 'Enum value out of range (received undefined, expected one of: "user", "admin", "superadmin")'
          console.warn(err.message);
        }
      }
    }

Labeled enums

If you prefer to use something more similar to classic enums, you can provide an object instead of an array when calling Enum:

const ROLES = Enum({
  Viewer: 'viewer',
  Editor: 'editor',
  Admin: 'admin',
});
type Role = InferValue<typeof ROLES>;
const Role = ROLES.accessor;

Then you can access enum values either directly or via their key:

function createUser(email: string, role: Role) {}

// these are equivalent
createUser('[email protected]', Role.Admin);
createUser('[email protected]', 'admin');

Labeled enums support all the methods of simple enums (e.g. .values() or .hasValue(x)), as well as additional methods:

  • list all keys with .keys(),
  • check key with .hasKey(x) or .assertKey(x),
  • list all key-value pairs with .entries(),
  • check key-value pair with .hasEntry([x, y]) or .assertEntry([x, y]),
  • get key for given value with .keyOf(x).

Composing enums

In addition to creating brand new enums, you can easily derive new enums from existing ones.

Adding values (Enum.extend)

To add values to a simple enum, pass in an array of values:

const ROLES = Enum(['viewer', 'editor', 'admin']);

const ENHANCED_ROLES = Enum.extend(ROLES, ['superadmin']);
// equivalent to: Enum(['viewer', 'editor', 'admin', 'superadmin'])

To add values to a labeled enum, pass in an object:

const ROLES = Enum({
  Viewer: 'viewer',
  Editor: 'editor',
  Admin: 'admin',
});

const ENHANCED_ROLES = Enum.extend(ROLES, {
  SuperAdmin: 'superadmin',
});
/* equivalent to:
const ENHANCED_ROLES = Enum({
  Viewer: 'viewer',
  Editor: 'editor',
  Admin: 'admin',
  SuperAdmin: 'superadmin',
});
*/

If you pass in an array of values for a labeled enum, the result will be a simple enum:

const ROLES = Enum({
  Viewer: 'viewer',
  Editor: 'editor',
  Admin: 'admin',
});

const ENHANCED_ROLES = Enum.extend(ROLES, ['superadmin']);
// equivalent to: Enum(['viewer', 'editor', 'admin', 'superadmin'])

Removing values (Enum.exclude)

To remove values from a simple enum, pass in an array of values:

const ROLES = Enum(['viewer', 'editor', 'admin']);

const RESTRICTED_ROLES = Enum.exclude(ROLES, ['admin']);
// equivalent to: Enum(['viewer', 'editor'])

To remove values from a labeled enum, you have two alternatives:

  • pass in an array of keys:

    const ROLES = Enum({
      Viewer: 'viewer',
      Editor: 'editor',
      Admin: 'admin',
    });
    
    const RESTRICTED_ROLES = Enum.exclude(ROLES, ['Admin']);
    /* equivalent to:
    const RESTRICTED_ROLES = Enum({
      Viewer: 'viewer',
      Editor: 'editor',
    });
    */
  • pass in an array of values:

    const ROLES = Enum({
      Viewer: 'viewer',
      Editor: 'editor',
      Admin: 'admin',
    });
    
    const RESTRICTED_ROLES = Enum.exclude(ROLES, ['admin']);
    /* equivalent to:
    const RESTRICTED_ROLES = Enum({
      Viewer: 'viewer',
      Editor: 'editor',
    });
    */

Converting enums

If you're stuck with some built-in TypeScript enums in your project (e.g. from some code generator), you can easily upgrade them to better enums. 🙂

Convert a built-in string enum

enum Role = {
  Viewer = 'viewer',
  Editor = 'editor',
  Admin = 'admin',
}

const ROLES = Enum(Role);
/* equivalent to:
const ROLES = Enum({
  Viewer: 'viewer',
  Editor: 'editor',
  Admin: 'admin',
});
*/

Convert a built-in number enum

enum Role = {
  Viewer,
  Editor,
  Admin,
}

const ROLES = Enum(Role);
/* equivalent to:
const ROLES = Enum({
  Viewer: 0,
  Editor: 1,
  Admin: 2,
});
*/

For numeric enums, the Enum function takes care of excluding the reverse mapping in the underlying runtime representation TypeScript creates. E.g. .keys() will only include keys and .values() will include values, unlike if you called Object.keys or Object.values on the original enum.

Contributing

  • install dependencies using npm install,
  • run tests (written with Jest and TSD) using npm test,
  • build library using npm run build,
  • generate documentation (with TypeDoc) using npm run docs.