Enum Utils
Make enums more intuitive to use with familiar Set
and Map
interfaces! Works
with numeric, string, and heterogeneous enums, and allows for easy enum type
guarding, creation of enum subsets, and safe mapping of enums to anything
else—even other enums!
GitHub, npm | Releases: Changelog | Author: Shaun Grady
Package:Install
npm install @sg.js/enum-utils
Requires TypeScript >=4.7
What's Inside
Classes
EnumSet
: Use an enum as an immutable Set.
- Construction via
fromEnum(Enum)
method takes a single enum argument. -
has(value)
is a type guard for narrowing serialized values types to the enum. -
subset(Enum[])
safely creates anEnumSet
from a subset of enum members. -
toEnumMap(mapping)
creates anEnumMap
, safely mapping an enum (or enum subset) to anything. - Shares
Set
iterable methods.
EnumMap
: Use an enum as an immutable Map.
- Construction via
fromEnum(Enum, { [Enum]: any })
provides exhaustive type safety for map keys while preventing numeric enum keys from being coerced to strings. -
has(value)
is a type guard for narrowing serialized values types to the enum. -
get()
has dynamically-typed return value;has()
guarded values always return a value and illegal enum values always returnundefined
. - Shares
Map
iterable methods.
Functions
-
enumToSet
: Convert an enum object to Set of enum members. -
isEnumMember
: Type guard for a given enum. -
isValidEnumMember
: Type guard for values that are strings and finite numbers.
Table of Contents
Usage Example
enum Priority {
Low = 'L',
Medium = 'M',
High = 'H',
ThisIsFine = 'OhNo',
}
// Define our base EnumSet with all Priority members
const priorities = EnumSet.fromEnum(Priority);
// EnumSets are immutable, so aliasing to another variable is safe.
const adminPriorities = priorities;
// Non-admins will only be allowed to use a subset of priorities.
const userPriorities = adminPriorities.subset([
Priority.Low,
Priority.Medium,
Priority.High,
]);
// Create a map with values constrained to a union of i18n keys.
priorityI18nMap = priorities.toEnumMap<I18nKey>({
[Priority.Low]: 'common.low',
[Priority.Medium]: 'common.medium',
[Priority.High]: 'common.high',
[Priority.ThisIsFine]: 'common.makeItStop',
});
// EnumMaps can also be constructed like this:
// EnumMap.fromEnum(Priority, { … } as const)
// However, value types aren't as easily constrained with a single type argument,
// so using the `as const` assertion for the mapping is recommended
// for maximum type safety.
const PrioritySelect = () => {
const { t } = useTranslation();
// Determine which Priority to set based on user's role
const { isAdmin } = useSession();
const allowedPriorities = isAdmin ? adminPriorities : userPriorities;
// This component allows a `priorityLock` search param to be set that disables
// the priority select with the given priority. Very secure. 👌
const { query } = useRouter();
const priorityLock: unknown = query.priorityLock;
const hasPriorityLock = priorityLock != null;
// Guard `priorityLock` to our Priority enum type
if (hasPriorityLock && !allowedPriorities.has(priorityLock)) {
const priorityList = [...allowedPriorities].join(', ');
throw new Error(
`searchParam 'lockPriority' must be one of: ${priorityList}.`
);
}
const [priority, setPriority] = useState<Priority>(
priorityLock ?? allowedPriorities.values().next().value
);
return (
<Select
onchange={setPriority}
disabled={hasPriorityLock}
optionValues={Array.from(allowedPriorities)}
renderOption={(priority: Priority) => t(priorityI18nMap.get(priority))}
/>
);
};
EnumSet
Construction
EnumSet()
Although new EnumSet()
can be called with the same value signature of Set
,
the type arguments aren't very developer-friendly; instead, it's recommended to
make use of the EnumSet.fromEnum()
static method.
Static methods
EnumSet.fromEnum()
Creates a new EnumSet
instance from the given enum object.
fromEnum(Enum);
enum Color {
Red,
Green,
Blue,
}
const colors = EnumSet.fromEnum(Color);
Instance methods
EnumSet.prototype.has()
The has() method returns a boolean indicating whether an enum member with
the specified value exists in the EnumSet
object or not, acting as a
type guard.
has(value);
const colors = EnumSet.fromEnum(Color);
const value: unknown = router.query.color;
let color: Color;
if (colorToHexMap.has(value)) {
color = value;
}
EnumSet.prototype.subset()
The subset() method returns a new EnumSet
instance containing only the
enum members specified, which must be members of the EnumSet
.
subset([Enum]);
enum Locale {
enUS = 'en-US',
enGB = 'en-GB',
frCA = 'fr-CA',
esMX = 'es-MX',
jaJP = 'ja-JP',
}
const siteLocales = EnumSet.from(Locale);
const videoLocales = siteLocales.subset([
Locale.enUS,
Locale.enGB,
Locale.esMX,
]);
if (videoLocales.has(value)) {
// typeof value ⮕ `Locale.enUS | Locale.enGB | Locale.esMX`
}
EnumSet.prototype.toEnumMap()
Returns an EnumMap
instance that maps each enum member in the EnumSet
to a
corresponding value in the given mappings object. The mapping object value types
may either be inferred or defined by the optional type argument. If inferred,
it's recommended to use the as const
assertion on the mapping object to narrow
the value types.
EnumMap(mapping);
enum Locale {
enUS = 'en-US',
enGB = 'en-GB',
frCA = 'fr-CA',
esMX = 'es-MX',
}
const locales = EnumSet.from(Locale);
const localeFileSuffixes = locales.toEnumMap({
[Locale.enUS]: 'en',
[Locale.enGB]: 'en',
[Locale.frCA]: 'fr-ca',
[Locale.esMX]: 'es',
} as const);
// We can optionally constrain the values to a type
const localeI18nKeys = locales.toEnumMap<I18nKeys>({
[Locale.enUS]: 'common.americanEnglish',
[Locale.enGB]: 'common.britishEnglish',
[Locale.frCA]: 'common.canadianFrench',
[Locale.esMX]: 'common.mexicanSpanish',
});
EnumSet.prototype.size
EnumSet.prototype.keys()
EnumSet.prototype.values()
EnumSet.prototype.entries()
EnumSet.prototype.forEach()
These methods behave identically to the Set
class.
See MDN documentation
for more details.
EnumMap
Construction
EnumMap()
Although new EnumMap()
can be called with the same value signature of Map
,
the type arguments aren't very developer-friendly; instead, it's recommended to
make use of the EnumMap.fromEnum()
static method, or the
EnumSet.prototype.toEnumMap()
instance method, which allows for typing the
mapping keys more easily.
Static methods
EnumMap.fromEnum()
Returns an EnumMap
instance that maps each enum member in the given enum to a
corresponding value in the given mappings object. The mapping object value types
may either be inferred or defined by the optional type argument. If inferred,
it's recommended to use the as const
assertion on the mapping object to narrow
the value types.
fromEnum(Enum, mapping as const);
enum Color {
Red,
Green,
Blue,
}
const colorHexMap = EnumMap.fromEnum(Color, {
[Color.Red]: '#f00',
[Color.Green]: '#0f0',
[Color.Blue]: '#00f',
} as const);
Instance methods
EnumMap.prototype.has()
The has() method returns a boolean indicating whether an enum member with
the specified value exists in the EnumMap
object or not, acting as a
type guard.
has(value);
const colorToHexMap = EnumMap.fromEnum(Color, {
[Color.Red]: '#f00',
[Color.Green]: '#0f0',
[Color.Blue]: '#00f',
} as const);
const value: unknown = router.query.color;
let color: Color;
if (colorToHexMap.has(value)) {
color = value;
}
EnumMap.prototype.get()
The get() method returns a specified element from an EnumMap
object. If
the key's value has been guarded by the has() method, then the return type
will be non-nullish. If the key is an illegal enum member type, then return type
will be undefined
.
get(value);
const colorToHexMap = EnumMap.fromEnum(Color, {
[Color.Red]: '#f00',
[Color.Green]: '#0f0',
[Color.Blue]: '#00f',
} as const);
const value: unknown = router.query.color;
let colorHex: '#f00' | '#0f0' | '#00f';
if (colorToHexMap.has(value)) {
colorHex = colorToHexMap.get(value);
}
EnumMap.prototype.size
EnumMap.prototype.keys()
EnumMap.prototype.values()
EnumMap.prototype.entries()
EnumMap.prototype.forEach()
These methods behave identically to the Map
class.
See MDN documentation
for more details.
enumToSet()
Converts an enum runtime object to an array of its members. This is safe to use with numeric, string, and heterogeneous enums.
enum Fruit {
Apple = 'apple',
Banana = 'banana',
Orange = 'orange',
}
const fruitSet: Set<Fruit> = enumToSet<Fruit>(Fruit);
console.log(fruitSet); // ⮕ Set { 'apple', 'banana', 'orange' }
isEnumMember()
Checks if the given value is a valid member of the specified enum object, acting as a type guard.
enum CatBreed {
Siamese,
NorwegianForestCat,
DomesticShorthair,
}
if (isEnumMember(CatBreed, 'Greyhound')) {
// …
}
isValidEnumMember()
Type guards values as an eligible enum member: a finite number or string.
isValidEnumMember('foo'); // ⮕ true
isValidEnumMember(42); // ⮕ true
isValidEnumMember(NaN); // ⮕ false
isValidEnumMember({}); // ⮕ false