
Security News
Crates.io Users Targeted by Phishing Emails
The Rust Security Response WG is warning of phishing emails from rustfoundation.dev targeting crates.io users.
better-enums
Advanced tools
Better enums for TypeScript.
The better-enums
library provides a simple utility for creating an improved version of TypeScript enums.
Full documentation is hosted here.
Many in the TypeScript community consider using TypeScript's built-in enum
keyword a bad practice (Anders Hejlsberg himself has stated he wouldn't have put them into the language in retrospect). The recommendation is to use union types instead, which may be inferred from arrays or objects when some runtime representation is also needed (e.g. for iteration). For more information on this topic, see:
This library provides a custom enum implementation which attempts to avoid the pitfalls of TS enums, while also enhancing unions with runtime features.
These "better enums" are created either from an array of values (a "simple enum") or an object mapping keys to values (a "labeled enum"), with a union type then being automatically inferred from these values. This pattern is the commonly recommended alternative to the enum
keyword. Using this library has the advantage of encapsulating some of the necessary TypeScript magic that can be daunting for less experienced TypeScript programmers.
The labeled enums offer good compatibilty with built-in enums. Since they support the same dot syntax and can even be created from an existing built-in enum, migrating away from the enum
keyword should be fairly straightforward for existing projects.
The main advantages of using better-enums
over built-in enums and/or unions are:
Install the better-enums
package using your favourite package manager:
npm install better-enums
yarn add better-enums
pnpm add better-enums
Import the Enum
callable object to create a simple enum and infer its union type with the InferValue
utility type:
import { Enum, type InferValue } from 'better-enums';
const ROLES = Enum(['viewer', 'editor', 'admin']);
type Role = InferValue<typeof ROLES>;
Then you can use the inferred type in your type definitions:
type User = {
email: string;
role: Role; // role is 'viewer' | 'editor' | 'admin'
};
If you prefer an enum-style syntax for accessing values, you can use the .accessor
property (keys match values exactly):
const ROLES = Enum(['Viewer', 'Editor', 'Admin']);
type Role = InferValue<typeof ROLES>;
const Role = ROLES.accessor;
// ...
let role: Role;
// these are equivalent
role = Role.Admin;
role = 'Admin';
The enum object enables you to use runtime features:
list all values with .values()
:
ROLES.values().forEach(role => {
console.log(role);
});
check value with .hasValue(x)
(returns true
/false
):
function f(value: string | undefined) {
// value is string | undefined
if (ROLES.hasValue(value)) {
// value is Role
}
}
check value with .assertValue(x)
(throws RangeError
if invalid):
function f(value: string | undefined) {
try {
// value is string | undefined
ROLES.assertValue(value);
// value is Role
} catch (err: unknown) {
if (err instanceof RangeError) {
// 'Enum value out of range (received undefined, expected one of: "user", "admin", "superadmin")'
console.warn(err.message);
}
}
}
If you prefer to use something more similar to classic enums, you can provide an object instead of an array when calling Enum
:
const ROLES = Enum({
Viewer: 'viewer',
Editor: 'editor',
Admin: 'admin',
});
type Role = InferValue<typeof ROLES>;
const Role = ROLES.accessor;
Then you can access enum values either directly or via their key:
function createUser(email: string, role: Role) {}
// these are equivalent
createUser('john.doe@example.com', Role.Admin);
createUser('john.doe@example.com', 'admin');
Labeled enums support all the methods of simple enums (e.g. .values()
or .hasValue(x)
), as well as additional methods:
.keys()
,.hasKey(x)
or .assertKey(x)
,.entries()
,.hasEntry([x, y])
or .assertEntry([x, y])
,.keyOf(x)
.In addition to creating brand new enums, you can easily derive new enums from existing ones.
Enum.extend
)To add values to a simple enum, pass in an array of values:
const ROLES = Enum(['viewer', 'editor', 'admin']);
const ENHANCED_ROLES = Enum.extend(ROLES, ['superadmin']);
// equivalent to: Enum(['viewer', 'editor', 'admin', 'superadmin'])
To add values to a labeled enum, pass in an object:
const ROLES = Enum({
Viewer: 'viewer',
Editor: 'editor',
Admin: 'admin',
});
const ENHANCED_ROLES = Enum.extend(ROLES, {
SuperAdmin: 'superadmin',
});
/* equivalent to:
const ENHANCED_ROLES = Enum({
Viewer: 'viewer',
Editor: 'editor',
Admin: 'admin',
SuperAdmin: 'superadmin',
});
*/
If you pass in an array of values for a labeled enum, the result will be a simple enum:
const ROLES = Enum({
Viewer: 'viewer',
Editor: 'editor',
Admin: 'admin',
});
const ENHANCED_ROLES = Enum.extend(ROLES, ['superadmin']);
// equivalent to: Enum(['viewer', 'editor', 'admin', 'superadmin'])
Enum.exclude
)To remove values from a simple enum, pass in an array of values:
const ROLES = Enum(['viewer', 'editor', 'admin']);
const RESTRICTED_ROLES = Enum.exclude(ROLES, ['admin']);
// equivalent to: Enum(['viewer', 'editor'])
To remove values from a labeled enum, you have two alternatives:
pass in an array of keys:
const ROLES = Enum({
Viewer: 'viewer',
Editor: 'editor',
Admin: 'admin',
});
const RESTRICTED_ROLES = Enum.exclude(ROLES, ['Admin']);
/* equivalent to:
const RESTRICTED_ROLES = Enum({
Viewer: 'viewer',
Editor: 'editor',
});
*/
pass in an array of values:
const ROLES = Enum({
Viewer: 'viewer',
Editor: 'editor',
Admin: 'admin',
});
const RESTRICTED_ROLES = Enum.exclude(ROLES, ['admin']);
/* equivalent to:
const RESTRICTED_ROLES = Enum({
Viewer: 'viewer',
Editor: 'editor',
});
*/
If you're stuck with some built-in TypeScript enum
s in your project (e.g. from some code generator), you can easily upgrade them to better enums. 🙂
enum
enum Role = {
Viewer = 'viewer',
Editor = 'editor',
Admin = 'admin',
}
const ROLES = Enum(Role);
/* equivalent to:
const ROLES = Enum({
Viewer: 'viewer',
Editor: 'editor',
Admin: 'admin',
});
*/
enum
enum Role = {
Viewer,
Editor,
Admin,
}
const ROLES = Enum(Role);
/* equivalent to:
const ROLES = Enum({
Viewer: 0,
Editor: 1,
Admin: 2,
});
*/
For numeric enums, the Enum
function takes care of excluding the reverse mapping in the underlying runtime representation TypeScript creates. E.g. .keys()
will only include keys and .values()
will include values, unlike if you called Object.keys
or Object.values
on the original enum
.
npm install
,npm test
,npm run build
,npm run docs
.FAQs
Better enums for TypeScript
The npm package better-enums receives a total of 47 weekly downloads. As such, better-enums popularity was classified as not popular.
We found that better-enums demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
The Rust Security Response WG is warning of phishing emails from rustfoundation.dev targeting crates.io users.
Product
Socket now lets you customize pull request alert headers, helping security teams share clear guidance right in PRs to speed reviews and reduce back-and-forth.
Product
Socket's Rust support is moving to Beta: all users can scan Cargo projects and generate SBOMs, including Cargo.toml-only crates, with Rust-aware supply chain checks.