🚀 The enum utility that TypeScript forgot
— JavaScript too!
Ever wanted Validation, Iteration, Key-Lookups, Localization for your enums in Typescript or JavaScript?
Ever wanted it to be so simple you'll use it everywhere?
superenum is the enum utility that TS forgot:
- 🚀 Super-powers for your enums
- ✅ Input validation
- 📚 Iterable, order guaranteed
↔️ key ⬌ value conversion- 🌐 Localization (i18n)
- ✨ Simple to use
- 🔒 Type-safe
Additionally:
- 👍 Works with Node 24 without
--experimental-transform-types
- 🌐 Works with TypeScript or JavaScript enums
- 🖥️ Works in NodeJS, Deno, Bun, or the browser
- 📦 Very small code footprint (~1.5kB minified + gzipped)
- 🛠️ No dependencies
- 📜 Permissive license (BSD-2-Clause)
npm install @ncoderz/superenum
pnpm install @ncoderz/superenum
yarn add @ncoderz/superenum
deno add @ncoderz/superenum
bun add @ncoderz/superenum
Using jsDelivr CDN (ES6 IFFE module):
<script src="https://cdn.jsdelivr.net/npm/@ncoderz/superenum@latest/dist/browser/superenum.global.js"></script>
Using unpkg CDN (ES6 IFFE module):
<script src="https://unpkg.com/@ncoderz/superenum@latest/dist/browser/superenum.global.js"></script>
Replace latest
with a specific version in either of the URLs above to use a specific version.
@ncoderz/superenum
can be used via ES Module import, CommonJS, or as a script import directly in the browser.
import { Enum } from '@ncoderz/superenum';
const { Enum } = require('@ncoderz/superenum');
<script src="https://cdn.jsdelivr.net/npm/@ncoderz/superenum@latest/dist/browser/superenum.global.js"></script>
<script>
const { Enum } = window.superenum;
</script>
import { Enum, EnumType } from '@ncoderz/superenum';
// Standard TypeScript enum
enum MyEnum {
node = 'node',
chrome = 'chrome',
safari = 'safari',
};
// ALTERNATIVE: Standard object style enum
const MyEnum {
node: 'node',
chrome: 'chrome',
safari: 'safari',
};
// ALTERNATIVE: String enums can also be declared
// short-hand using arrays (keys and values are the same, so lower case in this case)
const MyEnum = Enum.fromArray([
'node',
'chrome',
'safari'
]);
// Type for object and array enum declarations
// (not necessary for TypeScript enums as enum is already also type)
type MyEnum = EnumType<typeof MyEnum>;
enum MyEnum {
NODE = 'node',
CHROME = 'chrome',
SAFARI = 'safari',
}
// Validation
const value = Enum(MyEnum).fromValue('node'); // MyEnum.NODE
// Validation with default
const value = Enum(MyEnum).fromValue('not supported') ?? MyEnum.SAFARI; // MyEnum.SAFARI
enum MyEnum {
NODE = 'node',
CHROME = 'chrome',
SAFARI = 'safari',
}
// Iteration (values)
for (const value of Enum(MyEnum) /* or Enum(MyEnum).values() */) {
console.log(value); // 'node', 'chrome', 'safari'
}
// Iteration (keys)
for (const key of Enum(MyEnum).keys()) {
console.log(key); // 'NODE', 'CHROME', 'SAFARI'
}
// Iteration (entries)
for (const entry of Enum(MyEnum).entries()) {
console.log(entry); // ['NODE', 'node'], ['CHROME', 'chrome'], ['SAFARI', 'safari']
}
enum MyEnum {
NODE = 'node',
CHROME = 'chrome',
SAFARI = 'safari',
}
// Key to Value (fromKey)
const value = Enum(MyEnum).fromKey('NODE'); // MyEnum.node
enum MyEnum {
NODE = 'node',
CHROME = 'chrome',
SAFARI = 'safari',
}
// Value to Key (keyFromValue)
const key = Enum(MyEnum).keyFromValue(MyEnum.NODE); // 'NODE'
enum MyEnum {
HELLO = 'hello',
GOODBYE = 'goodbye',
}
// Localization (i18n)
// Set labels all at one
Enum(MyEnum).setAllLabels({
[MyEnum.HELLO]: {
en: 'Hello',
de: 'Guten Tag',
},
[MyEnum.GOODBYE]: {
en: 'Goodbye',
de: 'Auf Wiedersehen',
},
});
// Or set labels one by one
Enum(MyEnum).setLabels(MyEnum.HELLO, { en: 'Hello', de: 'Guten Tag' });
Enum(MyEnum).setLabels(MyEnum.GOODBYE, { en: 'Goodbye', de: 'Auf Wiedersehen' });
// Get a label in the first configured (default) language
Enum(MyEnum).getLabel(MyEnum.HELLO); // 'Hello'
// Get a label in a specific language
Enum(MyEnum).getLabel(MyEnum.HELLO, 'de'); // 'Guten Tag'
// If a locale does not exist, the enum value is returned
Enum(MyEnum).getLabel(MyEnum.HELLO, 'es'); // 'hello'
Validation is a common use case when reading data from an API, file, or database.
@ncoderz/superenum
makes this easy with Enum(<enum>).fromValue()
which returns a typed enum or undefined if the data does not match an enum value.
const MyEnum = Enum.fromArray(['node', 'chrome', 'safari']);
// End enum declaration
// Input values could come from external data / API
const valueNode = Enum(MyEnum).fromValue('node'); // MyEnum.node
const valueChrome = Enum(MyEnum).fromValue('chrome'); // MyEnum.chrome
const invalid = Enum(MyEnum).fromValue('surfari'); // undefined
const invalid2 = Enum(MyEnum).fromValue(undefined); // undefined
// Validate with default of MyEnum.node
const validOrDefault = Enum(MyEnum).fromValue('invalid') ?? MyEnum.node; // MyEnum.node
It is easy to iterate the enum values, keys, or entries.
The Enum(<enum>)
itself is iterable and will iterate the values in the defined key order.
The Enum(<enum>).values()
, Enum(<enum>).keys()
, Enum(<enum>).entries()
functions return arrays in the defined key order which can be iterated, mapped, reduced, etc.
enum MyNumericEnum = {
node = 0,
chrome = 1,
safari = 2,
};
// End enum declaration
// Iterate enum values
for (const value of Enum(MyNumericEnum)) {
console.log(value);
}
// 0
// 1
// 2
for (const value of Enum(MyNumericEnum).values()) {
console.log(value);
}
// 0
// 1
// 2
Enum(MyNumericEnum).values().forEach((value) => {
console.log(value);
});
// 0
// 1
// 2
Enum(MyNumericEnum).values().map((value) => {
console.log(value + 1);
});
// 1
// 2
// 3
// Iterate enum keys (forEach, map, etc also work on keys())
for (const value of Enum(MyNumericEnum).keys()) {
console.log(value);
}
// node
// chrome
// safari
// Iterate enum entries (forEach, map, etc also work on entries())
for (const value of Enum(MyNumericEnum).entries()) {
console.log(value);
}
// [ 'node', 0 ]
// [ 'chrome': 1 ]
// [ 'safari': 2 ]
Iteration order will be the order of declaration of items in the enum.
However there is one exception which can affect object or array enums (NOT TypeScript enums).
If a key is a positive integer like string, for example '99', then it will be iterated before all other keys.
This is a feature of how JavaScript engines store keys. It cannot be avoided without making the library more complex, and it is only a very minor edge case. It is documented here for reference.
It does not affect TypeScript enums because they specifically forbid numeric like keys as otherwise they would clash with the reverse lookups.
There are use-cases where you might want to get an enum value from the enum key. An example might be where the key is stored in a configuration file, and matched to a value in code.
This is possible with Enum(<enum>).fromKey()
const MyEnum = {
node: 0,
chrome: 1,
safari: 2,
};
type MyEnum = EnumType<typeof MyEnum>;
// End enum declaration
const nodeValue = Enum(MyEnum).fromKey('node'); // MyEnum.node
const invalidValue = Enum(MyEnum).fromKey('NoDe'); // undefined
const nodeValue2 = Enum(MyEnum).fromKey('NoDe', {
ignoreCase: true,
}); // MyEnum.node
There are use-cases where you might want to get an enum key from the enum value. An example might be for logging purposes.
This is possible with Enum(<enum>).keyFromValue()
const MyEnum = superenum({
node: 'NodeJS',
chrome: 'GoogleChrome',
safari: 'MacOSSafari',
});
// End enum declaration
const nodeKey = Enum(MyEnum).keyFromValue('NodeJS'); // 'node'
const nodeKey2 = Enum(MyEnum).keyFromValue(MyEnum.node); // 'node'
const invalid = Enum(MyEnum).keyFromValue('node'); // undefined
Version 1 changed the philosophy to leave enum declarations as they are, and apply Superenum features when required using a lightweight, weak map cache.
const MyEnum = superenum({
key: 'value',
});
// ==>
const MyEnum = {
key: 'value',
};
const v = MyEnum.fromValue('value');
// ==>
import { Enum } from '@ncoderz/superenum';
const v = Enum(MyEnum).fromValue();
This open source software is licensed under the BSD-2-Clause license.