@ncoderz/superenum

Simple, typesafe enums in TypeScript, fully compatible with standard JavaScript.
Why @ncoderz/superenum?
The standard TypeScript enum implementation is over-complicated, missing basic features, and is not easily compatible with standard JavaScript enums.
@ncoderz/superenum provides an alternative that is:
- an extension of standard JavaScript object enums
- simple to use
- type-safe
- supports IDE autocompletion
- iteration order can be guaranteed
- is interoperable with standard JavaScript enums
- works in node or the browser
- has a very small code footprint (< 1kB minified + gzipped)
Additionally, the library is committed to:
- an API that will always remain backwards compatible
- no dependencies
- permissive license (BSD-2-Clause)
- fixing bugs
Installation
yarn add @ncoderz/superenum
npm install --save @ncoderz/superenum
Importing
@ncoderz/superenum can be used via ES Module import, CommonJS, or as a script import directly in the browser.
import { superenum, EnumType } from '@ncoderz/superenum';
const { superenum, EnumType } = require('@ncoderz/superenum');
<script src="https://cdn.jsdelivr.net/npm/@ncoderz/superenum/dist/browser/superenum.min.js"></script>
<script
const { superenum } = window.superenum;
</script>
Enum Declaration
Enums are declared as JavaScript objects wrapped with the superenum.fromObject(enumObject) function.
As long as the keys and values of the object are constants, or TypeScript can infer a constant value, then
the enum will be typesafe and IDE auto-completion will work.
Enums can also be declared as JavaScript arrays using superenum.fromArray(enumArray). These will be expanded to objects with the same keys as values, and they will behave as if they had be declared as such.
const MyEnum = superenum({
node: 'node',
chrome: 'chrome',
safari: 'safari',
});
type MyEnumType = EnumType<typeof MyEnum>;
const MyEnumFromArray = superenum.fromArray([
'node',
'chrome',
'safari',
]);
type MyEnumFromArrayType = EnumType<typeof MyEnumFromArray>
const MyNumericEnum = superenum({
node: 0,
chrome: 1,
safari: 2,
});
type MyNumericEnumType = EnumType<typeof MyNumericEnum>
const MyMixedEnum = superenum({
node: 'NodeJS',
chrome: 1,
safari: 'MacOSSafari',
});
type MyMixedEnumType = EnumType<typeof MyMixedEnum>
Basic Usage
const MyEnum = superenum({
node: 'node',
chrome: 'chrome',
safari: 'MacOSSafari',
});
type MyEnumType = EnumType<typeof MyEnum>;
const value: MyEnumType = MyEnum.node;
console.assert(value === MyEnum.node)
console.assert(value === 'node')
console.assert(value === MyEnum.chrome)
console.assert(value === 'firefox')
switch (value) {
case MyEnum.node:
break;
case MyEnum.chrome:
break;
case MyEnum.safari:
break;
default:
}
switch (value) {
case 'node':
break;
case 'chrome':
break;
case 'MacOSSafari':
break;
case 'safari':
break;
case 'something else':
break;
default:
}
Validation
Validation is a common use case when reading data from an API, file or database.
@ncoderz/superenum makes this easy with <enum>.fromValue() which returns a typed enum or undefined if the
data does not match an enum value.
const MyEnum = superenum.fromArray([
'node',
'chrome',
'safari',
]);
const valueNode = MyEnum.fromValue('node');
const valueChrome = MyEnum.fromValue('chrome');
const invalid = MyEnum.fromValue('surfari');
const invalid2 = MyEnum.fromValue(undefined);
const validOrDefault = MyEnum.fromValue('invalid') ?? MyEnum.node;
Iteration / forEach / map / reduce
It is easy to iterate the enum values, keys or entries.
The <enum> itself is iterable and will iterate the values in the defined key order.
The <enum>.values(), <enum>.keys(), <enum>.entries() functions return arrays in the defined key order which
can be iterated, mapped, reduced, etc.
const MyNumericEnum = superenum({
node: 0,
chrome: 1,
safari: 2,
});
for (const value of MyNumericEnum) {
console.log(value)
}
for (const value of MyNumericEnum.values()) {
console.log(value)
}
MyNumericEnum.values().forEach((value) => {
console.log(value)
});
MyNumericEnum.values().map((value) => {
console.log(value + 1)
});
for (const value of MyNumericEnum.keys()) {
console.log(value)
}
for (const value of MyNumericEnum.entries()) {
console.log(value)
}
The order of iteration is guaranteed unless an enum key can be converted to an integer. In this case it can be guaranteed by using superenum.fromArray() to initialise the enum, or by setting EnumOptions.iterationKeys to represent the desired iteration order.
const MyMixedEnum = superenum({
node: 0,
1: 1,
safari: 2,
'2': 'two'
}, {
iterationKeys: ['node', 1, 'safari', '2']
});
for (const value of MyMixedEnum.keys()) {
console.log(value)
}
Enum value from enum key
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>.fromKey()
const MyEnum = superenum({
node: 0,
chrome: 1,
safari: 2,
});
const nodeValue = MyEnum.fromKey('node');
const invalidValue = MyEnum.fromKey('NoDe');
const nodeValue2 = MyEnum.fromKey('NoDe', {
ignoreCase: true,
});
Enum key from enum value
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>.keyFromValue()
const MyEnum = superenum({
node: 'NodeJS',
chrome: 'GoogleChrome',
safari: 'MacOSSafari',
});
const nodeKey = MyEnum.keyFromValue('NodeJS');
const nodeKey2 = MyEnum.keyFromValue(MyEnum.node);
const invalid = MyEnum.keyFromValue('node');
Metadata
Want to associate some data with a particular enum value? An example might be to store a set of description strings
against a set of keys... the possibilities are endless.
This is possible with <enum>.setMetadata() and <enum>.getMetadata()
const MyEnum = superenum.fromArray([
'node',
'chrome',
'safari',
]);
MyEnum.setMetadata(MyEnum.node, 'Node.js is an open-source, cross-platform...');
MyEnum.setMetadata(MyEnum.chrome, 'Chrome is faster than fast – it’s engine...');
MyEnum.setMetadata(MyEnum.safari, 'Safari is the best way to experience the...');
const valueNode = MyEnum.fromValue('node');
const desc = MyEnum.getMetadata(valueNode);
API
API Documentation
Feature Comparison
| Compatible with simple JS enums | Yes | X | X |
| Mixed numeric and string keys | Yes | X | X |
| Compare value | value === Enum.key | Enum[value] === Enum[Enum.key] | value === Enum.key |
| Validate external data to valid typed enum | const val: EnumType = Enum.fromValue(value) | const val: Enum = ((Enum[value] === Enum[Enum.key]) ? value : undefined) as Enum | const val: Enum = ((value === Enum.key) ? value : undefined) as Enum |
| Value from key | const val: EnumType = Enum.fromKey(key) | Enum[key] - only if key is const; not very useful | Object.entries(Enum).reduce((acc, [k,v]) => { if (acc) return acc; if (k === key) return v; }, '') - not typed (is string) |
| Value from key safe? | Safe | No, will return key for invalid input which happens to be a valid value | Safe, but see above! |
| Key from value | Enum.keyFromValue(value) | Enum[value] | Object.entries(Enum).reduce((acc, [k,v]) => { if (acc) return acc; if (v === value) return k; }, '') |
| Key from value safe? | Safe | No, will return value for invalid input which happens to be a valid key | Safe, but see above! |
| Iterate values? | Enum.values() | Object.values(Enum).filter((v) => isNaN(Number(v)) | Object.values(Enum) |
| Iterate keys? | Enum.keys() | Object.keys(Enum).filter((k) => !isNaN(Number(k)) | Object.keys(Enum) |
| Iterate entries? | Enum.entries() | Object.entries(Enum).filter(([k,v]) => isNaN(Number(k))) | Object.entries(Enum) |
| Associate metadata with an enum value | Yes | X | X |
| Declare just first number value and increment | X | Yes | N/A |
| Bloat code? | < 1kB | Built-in | Built-in |
| Fast? | Yes | Workarounds involve looping | Workarounds involve looping |
Limitations
Enums may not use the keys:
fromKey, fromValue, keyFromValue, setMetadata, getMetadata, keys, values, entries
as these clash with the extension functions.
If such a key is used, it will be overwitten with the extension function.
License
This open source software is licenced under the BSD-2-Clause licence.
Alternatives