Skip to content

Latest commit

History

9 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

unique-enum

A runtime-safe alternative to TypeScript enums that maintains type safety and ergonomics.

import{Enum}from'unique-enum';// ESMconst{ Enum }=require('unique-enum');// CommonJSconstDirection=Enum('North','East','South','West');

Installation

You can install unique-enum from npm:

npm install unique-enum

Usage

An enum can be declared as in the first example. The Enum function returns a class with its members as its only possible instances. Its members will autocomplete like normal enum variants (eg. typing Direction. will show North, East etc. after declaration.)

Each variant's string representation can be accessed using the variant property or by calling toString:

console.log(Direction.North.variant)// prints: 'North'

Enums also have convenient string representations and will work normally with Object.keys, iterators etc:

console.log(Direction.toString());// prints: Enum { North, East, South, West }console.log(Object.keys(Direction));// prints: [ 'North', 'East', 'South', 'West' ]for(letdirectionofDirection){/* iterates over the variants */}

Type Invariants

A unique enum guarantees the following invariants throughout the program:

  • Each variant will only be equal to itself and no other object or primitive.
  • Variants are the sole objects for which instanceof enum will be true.
  • After declaration, no new variants can be constructed or assigned, and variants cannot be changed or destroyed.
newDirection('northwest');// ErrorDirection.NorthWest={};// ErrorDirection.North=0;// ErrorDirection.North.variant='South'// ErrordeleteDirection.North;// Errorfunctionis_north(d){return(dinstanceofDirection// Only true for the 4 Direction instances&&d==Direction.North// Only true for Direction.North);}

These invariants make unique enums more resilient at runtime and when used from vanilla JavaScript.

Debugging enum values

Since unique enums maintain type information at runtime, inspecting them makes debugging easier compared to TypeScript's implementation.

console.log(Direction.North)// prints: Value { variant: 'North' }

Unique Enums + TypeScript

If you want to use variants of a specific enum as an object property or function argument, you can use the provided Variant type.

import{Variant}from'unique-enum';functionis_north(d: Variant<typeofDirection>): d is typeofDirection.North{returnd==Direction.North;}is_north(Direction.North);// trueis_north(Direction.South);// falseisNorth({variant: 'North'});// Error

Unfortunately, unlike TypeScript enums, unique enums cannot autofill switch statements at runtime, and hacks like a default arm that returns never will not work to check for exhaustiveness. This is because the type checker is unable to check for exhaustiveness of types that are not unions of literal types.

Limitation of TypeScript's Type System

TypeScript's type system has a limitation when dealing with unique enum variants: For two enums E1 and E2, a variant of E1 is considered compatible with a variant of E2 if their names are the same, and the variant names of E1 form a subset of those in E2.

constSoftwareVersion=Enum("V1","V4","V6");constIP=Enum("V4","V6");letincompatible: typeofSoftwareVersion.V4=IP.V4;// Assignment is allowed despite the types being incompatible

Note that the assignment will instead fail if IP contains any variant name that is not also a variant name of SoftwareVersion, so this should not be a problem in practice.

This flaw is due to the type system's inability to distinguish between different instances of classes returned from a function even if they have non-overlapping private members, an issue which is considered a design limitation.

However, note that TypeScript enums are themselves not fully type safe. While they do not suffer from this specific flaw, enum variant types are considered equivalent to their literal types, so the following is valid code:

enumIP{V4,V6}letliteral: IP.V4=0;// No error even though 0 is not an IP

About

Runtime-safe alternative to typescript enums that maintains type safety and ergonomics

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages