
Security News
Axios Supply Chain Attack Reaches OpenAI macOS Signing Pipeline, Forces Certificate Rotation
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.
type-safe-enum
Advanced tools
A type-safe, flexible enum factory for TypeScript with runtime validation and type inference
A type-safe, flexible enum factory for TypeScript with runtime validation and type inference. Create robust enums with minimal boilerplate while maintaining full type safety. This package provides a more type-safe alternative to TypeScript's native enums with additional features like runtime validation, bi-directional mapping, and better type inference.
Blazing Fast Lookups - O(1) constant time for all lookup operations:
fromValue(), fromKey(), fromIndex()hasValue(), hasKey(), hasIndex()getKey(), getValue(), getIndex()Efficient Iteration - O(n) linear time for collection operations:
getEntries(), getKeys(), getValues(), getIndexes()Minimal Memory Footprint
Map for O(1) lookups by value, key, and indexObject.freeze() to prevent modificationstoJSON() and toString() methods| Feature | Native Enum | String Unions | Const Objects | type-safe-enum |
|---|---|---|---|---|
| Type Safety | ✅ | ✅ | ⚠️ (requires care) | ✅ (nominal typing) |
| Runtime Safety | ❌ | ❌ | ✅ | ✅ |
| IntelliSense | ✅ | ✅ | ✅ | ✅ |
| Reverse Lookup | ✅ (but unsafe) | ❌ | ❌ | ✅ |
| JSON Serialization | ❌ (numeric issues) | ✅ | ✅ | ✅ |
| Maintenance | ❌ (verbose) | ✅ | ✅ | ✅ |
| String Comparison | ❌ (can be confusing) | ❌ | ❌ | ✅ |
| Iteration | ❌ | ❌ | ✅ | ✅ |
| Bundle Size | ✅ (0kB) | ✅ (0kB) | ✅ (0kB) | ✅ (~2kB) |
| Tree Shaking | ❌ | ✅ | ✅ | ✅ |
enum Role {
ADMIN = 'admin',
EDITOR = 'editor',
VIEWER = 'viewer'
}
/*
* Can't easily validate a string
* Enums are compiled weirdly (numeric fallbacks, bi-directional maps)
* Serialization/deserialization is clumsy
*/
function isValidRole(role: string): boolean {
return Object.values(Role).includes(role as Role); // not type-safe
}
// String Unions: No runtime validation or utilities
type Role = 'admin' | 'editor' | 'viewer';
/*
* Repeating the list of values manually everywhere
* Can't iterate roles cleanly
* No structure beyond flat string
*/
function isValidRole(role: string): role is Role {
return ['admin', 'editor', 'viewer'].includes(role); // hardcoded list
}
// Const Objects: No runtime validation or utilities
const Role = {
ADMIN: 'admin',
EDITOR: 'editor',
VIEWER: 'viewer'
} as const;
type Role = typeof Role[keyof typeof Role];
/*
* Still no .fromValue() or .fromKey()
* No index, no rich object model
* No safe .map()/.entries(), just raw Object.entries()
* No default constObject.stringify() support
* Some IntelliSense support
* No reverse lookups
* No automatic indexing
*/
function isValidRole(role: string): role is Role {
return Object.values(Role).includes(role as Role); // still loose
}
import { CreateSafeEnumFromArray, type SafeEnum } from 'type-safe-enum';
/*
* Full runtime validation
* Safe .fromValue() / .fromKey()
* Safe .map() / .entries()
* safeEnum.toJSON() support
* Tree-shaking
* Full IntelliSense support
* Reverse lookups
* Automatic indexing
*/
const Status =
CreateSafeEnumFromArray(["Pending", "Approved", "Rejected"] as const, "Status");
// Type for enum values with nominal typing
type StatusType = SafeEnum<"Status">;
// Usage
const currentStatus: StatusType = Status.PENDING;
console.log(currentStatus.value); // 'Pending'
console.log(currentStatus.index); // 0 (auto-assigned)
// Type-safe lookups
const approved = Status.fromValue("Approved"); // Status.APPROVED | undefined
const pending = Status.fromKey("PENDING"); // Status.PENDING | undefined
// Type guards
if (Status.hasValue("Pending")) {
// TypeScript knows this is valid
const status: Status = Status.fromValue("Pending")!;
}
// Type-safe usage in functions
function processStatus(status: StatusType): string {
if (status.isEqual(Status.APPROVED)) {
return 'Approved!';
}
return 'Not approved!';
}
// Type usage in your code
function checkAccess(role: StatusType): boolean {
return role.isEqual(Status.APPROVED);
}
npm install type-safe-enum
# or
yarn add type-safe-enum
# or
pnpm add type-safe-enum
This library provides two main concepts:
Status, UserRole)Status.PENDING, UserRole.ADMIN)SafeEnum<TypeName>: Interface for a single enum value with nominal typing (contains key, value, index, and __typeName)SafeEnumObject<TypeName>: Interface for the enum object (contains all values and utility methods)The library is designed to work seamlessly across module boundaries in monorepos and complex TypeScript projects. Both CreateSafeEnum and CreateSafeEnumFromArray return SafeEnumObject<TypeName>, ensuring portable type definitions that avoid TS2742 errors when exporting enums between packages.
// ✅ Works in cross-module scenarios
import { CreateSafeEnumFromArray, type SafeEnum } from "type-safe-enum"
export const envList = CreateSafeEnumFromArray(
["development", "test", "testing", "production"],
"EnvType"
)
export type EnvType = SafeEnum<"EnvType">
import { CreateSafeEnum, type SafeEnumObject } from 'type-safe-enum';
import type { SafeEnum } from 'type-safe-enum';
// Create an enum with custom values and indices
const UserRole: SafeEnumObject<"UserRole"> = CreateSafeEnum({
ADMIN: { value: 'admin', index: 10 }, // Explicit index
EDITOR: { value: 'editor', index: 13 }, // Explicit index
VIEWER: { value: 'viewer' }, // Auto-assigns next index (14)
} as const, "UserRole");
type UserRoleType = SafeEnum<"UserRole">;
// Mixed explicit and auto indexes
const Priority = CreateSafeEnum({
LOW: { value: 'low'}, // auto-indexed: 0
MEDIUM: { value: 'medium', index: 10 },
HIGH: { value: 'high' } // auto-indexed: 11
}, "Priority");
type PriorityType = SafeEnum<"Priority">;
// Usage examples
const admin: UserRoleType = UserRole.ADMIN;
console.log(admin.key); // 'ADMIN'
console.log(admin.value); // 'admin'
console.log(admin.index); // 10
// Type-safe usage in functions
function greet(userRole: UserRoleType): string {
if (userRole.isEqual(UserRole.ADMIN)) {
return 'Hello Admin!';
}
return 'Welcome!';
}
function greetFromApi(role: unknown): string {
if (!UserRole.isEnumValue(role)) {
return 'Welcome!';
}
return greet(role);
}
// Type-safe lookups
const isValid = UserRole.hasValue('admin'); // true
// Get all values, keys, and indexes
const allValues = UserRole.getValues(); // ['admin', 'editor', 'viewer']
const allKeys = UserRole.getKeys(); // ['ADMIN', 'EDITOR', 'VIEWER']
const allIndexes = UserRole.getIndexes(); // [10, 13, 14]
// Iterate over entries
for (const [key, role] of UserRole.getEntries()) {
console.log(`${key}: ${role.value} (${role.index})`);
}
import { CreateSafeEnum, type SafeEnum } from 'type-safe-enum';
const StatusCode = CreateSafeEnum({
OK: { value: 'ok', index: 200 },
CREATED: { value: 'created', index: 201 },
BAD_REQUEST: { value: 'bad_request', index: 400 },
UNAUTHORIZED: { value: 'unauthorized', index: 401 },
NOT_FOUND: { value: 'not_found', index: 404 },
SERVER_ERROR: { value: 'server_error', index: 500 },
} as const, "StatusCode");
type StatusCodeType = SafeEnum<"StatusCode">;
// Type-safe status code handling
function handleResponse(statusCode: number): string {
const status = StatusCode.fromIndex(statusCode);
if (!status) return 'Unknown status';
if (status.index === StatusCode.OK.index) {
return 'Success!';
} else if (status.isEqual(StatusCode.UNAUTHORIZED)) {
return 'Please log in';
}
return `Error: ${status.value}`;
}
// Example usage
const responseCode: StatusCodeType = StatusCode.OK;
console.log(handleResponse(200)); // 'Success!'
console.log(handleResponse(401)); // 'Please log in'
import { CreateSafeEnum, type SafeEnum } from 'type-safe-enum';
import { useState } from 'react';
const FormState = CreateSafeEnum({
IDLE: { value: 'idle', index: 10 },
SUBMITTING: { value: 'submitting' }, // auto-indexed: 11
SUCCESS: { value: 'success', index: 20 },
ERROR: { value: 'error' }, // auto-indexed: 21
} as const, "FormState");
type FormStateType = SafeEnum<"FormState">;
function FormComponent() {
const [state, setState] = useState<FormStateType>(FormState.IDLE);
// Example usage in event handlers
const handleSubmit = async () => {
try {
setState(FormState.SUBMITTING);
await submitForm();
setState(FormState.SUCCESS);
} catch (error) {
setState(FormState.ERROR);
}
};
return (
<div>
{state.isEqual(FormState.SUBMITTING) && <Spinner />}
{state.isEqual(FormState.ERROR) && <ErrorBanner />}
{state.isEqual(FormState.SUCCESS) && <SuccessMessage />}
<button
onClick={handleSubmit}
disabled={state.isEqual(FormState.SUBMITTING)}
>
Submit
</button>
</div>
);
}
CreateSafeEnum(enumMap): SafeEnumObject<TypeName>Creates a type-safe enum from an object mapping.
CreateSafeEnumFromArray(values): SafeEnumObject<TypeName>Creates a type-safe enum from an array of string literals.
| Method | Description | Example |
|---|---|---|
fromValue(value: string): SafeEnum<TypeName> | Returns the enum value matching the given value, or undefined if not found | Get enum value by string value |
fromKey(key: string): SafeEnum<TypeName> | Returns the enum value matching the given key, or undefined if not found | Get enum value by key |
fromIndex(index: number): SafeEnum<TypeName> | Returns the enum value matching the given index, or undefined if not found | Get enum value by index |
hasValue(value: string): boolean | Returns true if the enum has a value matching the given value, false otherwise | Check if value exists |
hasKey(key: string): boolean | Returns true if the enum has a key matching the given key, false otherwise | Check if key exists |
hasIndex(index: number): boolean | Returns true if the enum has an index matching the given index, false otherwise | Check if index exists |
isEnumValue(value: unknown): value is SafeEnum<TypeName> | Returns true if the value is from this enum, false otherwise | Type guard: Check if value is from this enum |
isEqual(other: SafeEnum<TypeName> | SafeEnum<TypeName>[]): boolean | Single value: true if it matches a member of the enum. Array: true if all items are equal to the first. | if (UserRole.isEqual([role1, role2])) { ... } |
toString(): string | Get string representation of the enum object | UserRole.toString() |
toJSON(): { typeName: string, values: Array<{ key: string, value: string, index: number }> } | Get JSON-serializable representation of the enum | UserRole.toJSON() |
getValues(): string[] | Get all enum values as strings | UserRole.getValues() |
getIndexes(): number[] | Get all enum indices as numbers | UserRole.getIndexes() |
getEntries(): [string, SafeEnum<TypeName>][] | Get all [key, value] pairs | UserRole.getEntries() |
getKeys(): string[] | Get all enum keys as strings | UserRole.getKeys() |
getKey(): string | Get the first enum member's key | UserRole.getKey() |
getValue(): string | Get the first enum member's value | UserRole.getValue() |
getIndex(): number | Get the first enum member's index | UserRole.getIndex() |
[Symbol.iterator](): IterableIterator<SafeEnum<TypeName>> | Iterate enum values (supports for...of / Array.from) | Array.from(UserRole) |
| Method | Description | Example |
|---|---|---|
hasValue(value: string): boolean | Check if this enum value has the given value | UserRole.ADMIN.hasValue('admin') |
hasKey(key: string): boolean | Check if this enum value has the given key | UserRole.ADMIN.hasKey('ADMIN') |
hasIndex(index: number): boolean | Check if this enum value has the given index | UserRole.ADMIN.hasIndex(10) |
isEnumValue(value: unknown): value is SafeEnum<TypeName> | Type guard: Check if value is from the same enum | UserRole.ADMIN.isEnumValue(role) |
isEqual(other: SafeEnum<TypeName> | SafeEnum<TypeName>[]): boolean | Compare with another enum value or array of values | UserRole.ADMIN.isEqual(otherRole) |
toString(): string | Get string representation in format "KEY: value, index: N" | UserRole.ADMIN.toString() |
toJSON(): { key: string, value: string, index: number } | Get JSON-serializable object | UserRole.ADMIN.toJSON() |
getKeyOrThrow(): string | Get the key of the enum value (throws if missing) | UserRole.ADMIN.getKeyOrThrow() |
getValueOrThrow(): string | Get the value of the enum value (throws if missing) | UserRole.ADMIN.getValueOrThrow() |
getIndexOrThrow(): number | Get the index of the enum value (throws if missing) | UserRole.ADMIN.getIndexOrThrow() |
MIT © Elfre Valdes
"Unlike traditional TypeScript enums, which can be opaque and error-prone (especially with numeric values and reverse mappings), type-safe-enum uses object literals or classes to infer literal union types that are transparent, predictable, and safe. It embraces the full power of TypeScript's type system to ensure better IntelliSense, stricter compile-time checks, and improved maintainability — particularly in large codebases and shared libraries.
I highly recommend type-safe-enum over native enums for most modern TypeScript projects. It's a cleaner, more reliable way to define constants and enum-like structures, without the pitfalls of traditional enums."
FAQs
A type-safe, flexible enum factory for TypeScript with runtime validation and type inference
We found that type-safe-enum demonstrated a healthy version release cadence and project activity because the last version was released less than 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
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.

Security News
Open source is under attack because of how much value it creates. It has been the foundation of every major software innovation for the last three decades. This is not the time to walk away from it.

Security News
Socket CEO Feross Aboukhadijeh breaks down how North Korea hijacked Axios and what it means for the future of software supply chain security.