question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

array enumerations as runtime array + union type

See original GitHub issue

Suggestion

Array enums

const X = enum ['a', 'b', 'c'];
// or
const X = ['a', 'b', 'c'] as enum;

🔍 Search Terms

Related tickets

✅ Viability Checklist

  • This wouldn’t be a breaking change in existing TypeScript/JavaScript code
  • This wouldn’t change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn’t a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript’s Design Goals.

⭐ Suggestion

The idea is that if enum keyword is used in a constant array declaration, it leaves the array as runtime value but also returns the type as a union type of the values.

📃 Motivation

Currently enums are causing lots of issues by their nature that violates a few of the TypeScript design goals:

  • Impose no runtime overhead on emitted programs
  • Use a consistent, fully erasable, structural type system.

They require devs to create key-value pairs for each entry in the enum and that transpiles to a considerable amount of runtime code.

const enums came to help there but they lack the ability to get the list of valid values as a runtime array for runtime validation.

This can solve both issues with a terse, clean and removable syntax addition, and facilitates the usage of union types as enumerations.

💻 Use Cases

const Status = enum ['active', 'inactive', 'cancelled'];
// or
const Status = ['active', 'inactive', 'cancelled'] as enum;

Would transpile to…

// just `enum` striped
const Status = ['active', 'inactive', 'cancelled'];

… and be identical to…

const Status = ['active', 'inactive', 'cancelled'] as const;
type Status = typeof Status[number];

That means that we can use Status both as runtime value:

function validate(status) {
  if (!Status.includes(status)) {
    throw new Error(`${status} is not a valid status: ${Status.join()}`);
  }
}

validate('cancelled') // ok
validate('hello') // Runtime error

And as a type

function set(status: Status) {
   // do something
}

set('active') // ok
set('hello') // type check error

Even combined

const isStatus = (x: unknown) : x is Status => Status.includes(x);

if (isStatus(queryParams.status)) {
  set(queryParams.status) // type narrowed to Status
}

Abstraction in code

This abstraction reaches the same goal but requires users to declare both the runtime array and the type in separated sentences and declare them with the same name.

type ValidKeys = string | number;

type Enum<T extends ValidKeys[]> = T[number];

function Enum<T extends ValidKeys[]>(...keys: [...T]) {
  return keys;
}

const Status = Enum('active', 'inactive', 'cancelled');
type Status = Enum<typeof Status>;

Comparison with enum & const enum

Declaration

enum Enum { A = 'a' }
const enum ConstEnum { A = 'a' }
const ArrayEnum = ['a'] as enum;

Usage as type

function enumFn(x: Enum) {}
function constEnumFn(x: ConstEnum) {}
function arrayEnumFn(x: ArrayEnum) {}

function EnumJsx(props: {x: Enum}) { return null }
function ConstEnumJsx(props: {x: ConstEnum}) { return null }
function ArrayEnumJsx(props: {x: ArrayEnum}) { return null }

Value usage

// Enum
enumFn(Enum.A);
enumFn('a' as Enum);
<EnumJsx x={Enum.A} />
<EnumJsx x={'a' as Enum} />

// ConstEnum
constEnumFn(ConstEnum.A);
constEnumFn('a' as ConstEnum); 
<ConstEnumJsx x={ConstEnum.A} />;
<ConstEnumJsx x={'a' as ConstEnum} />;

// ArrayEnum
arrayEnumFn('a');
<ArrayEnumJsx x="a" />;

Enumerate possible values at runtime

console.log(Object.values(Enum));
// not possible with ConstEnum
console.log(ArrayEnum);

Generated output

// Enum
var Enum;
(function (Enum) {
    Enum["A"] = "a";
})(Enum || (Enum = {}));

// ConstEnum
// no output

// ArrayEnum
const ArrayEnum = ['a'];

Alternative

Alternatively we can extend closer to the as const feature:

Discarded since as const is expression syntax (see comments)

const Status = ['active', 'inactive', 'cancelled'] as enum;

Issue Analytics

  • State:open
  • Created 2 years ago
  • Reactions:3
  • Comments:10 (1 by maintainers)

github_iconTop GitHub Comments

1reaction
fatcerberuscommented, Mar 10, 2022

Regardless of the choice of keyword, this is expression-level syntax. What do you propose should happen here:

foo(['x', 'y'] as enum);

If you intend that the creation of a new named type is tied specifically to the const x = ... then this would be better suited to dedicated syntax that makes that obvious, like enum type X = 'foo' | 'bar', and then it becomes a lot clearer why this feature has runtime impact.

0reactions
zhujinxuancommented, Oct 3, 2022

Hi, I think the repetitive requests on enum is due to the need for Record<keyof typeof enum, Enum>. But currently, we have Record<keyof typeof enum, Enum> & Record<Enum, keyof typeof enum>.

I am not sure whether `Object.defineProperty´ would violate non-goals, like this

oneWay enum Weekdays {
  Mon, 
}

would compile to

(function (Weekdays) {
// if Weekdays.Mon !== "Mon"
Object.defineProperty(Weekdays, 0, {value: "Mon", enumerable: false} )
})(Weekdays || Weekdays = {})

But it might not be very compatible with early ES. But we can enable this keyword when and only when the target supports Objcet.defineProperty

Read more comments on GitHub >

github_iconTop Results From Across the Web

Derive union type from tuple/array values - Stack Overflow
I want this because I want to define type which allows only values from static array, and also need to enumerate these values...
Read more >
Is it possible to create an array union types as values? - Reddit
Types are removed during runtime. The best you can do is probably use an enum instead of a type literal. You can iterate...
Read more >
Documentation - Everyday Types - TypeScript
Everyday Types · The primitives: string , number , and boolean · Arrays · any · Type Annotations on Variables · Functions ·...
Read more >
Overview | Odin Programming Language
Slices, dynamic arrays, and maps behave like pointers in this case (Internally they ... An array's index can be any integer, character, or...
Read more >
Schema Language - courier
... be any pegasus type, including primitives, enums, unions, maps and arrays. ... are pre-generated by Courier and provided in the courier-runtime artifact ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found