A type-safe, flexible enum factory for TypeScript with runtime validation and type inference
npm install type-safe-enum





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
- Uses Map for O(1) lookups by value, key, and index
- No prototype pollution - clean object structure
- Values are cached and reused
- Type Safety First - Full TypeScript support with strict type checking
- Runtime Validation - Verify enum values at runtime
- Zero Dependencies - No external packages required
- Tiny Bundle - ~2KB minified + gzipped
- Tree-shakeable - Only includes what you use
- Immutable by Design - Uses Object.freeze() to prevent modifications
- Auto-completion - Full IntelliSense support in modern IDEs
- Serialization Ready - Built-in toJSON() and toString() methods
- Comprehensive API - All the utility methods you need
- Battle-tested - Tested with Vitest to ensure 100% test coverage
| Feature | Native Enum | String Unions | Const Objects | type-safe-enum |
| ------------------ | -------------------------------------------------- | ------------------------------------ | ---------------------------------------------- | ------------------------------------- |
| Type Safety |
typescript
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
}
`$3
`typescript
// 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
}
`$3
`typescript
// 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
}
`$3
`typescript
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);
}
`Requirements
- Node.js >= 16.0.0
- TypeScript >= 4.9.0
- npm >= 7.0.0 or yarn >= 1.22.0 or pnpm >= 6.0.0
Installation
`bash
npm install type-safe-enum
or
yarn add type-safe-enum
or
pnpm add type-safe-enum
`Type System Overview
This library provides two main concepts:
1. Enum Object: The container that holds all enum values and utility methods (e.g.,
Status, UserRole)
2. Enum Value: A single value from the enum (e.g., Status.PENDING, UserRole.ADMIN)$3
-
SafeEnum: Interface for a single enum value with nominal typing (contains key, value, index, and __typeName)
- SafeEnumObject: Interface for the enum object (contains all values and utility methods)$3
The library is designed to work seamlessly across module boundaries in monorepos and complex TypeScript projects. Both
CreateSafeEnum and CreateSafeEnumFromArray return SafeEnumObject, ensuring portable type definitions that avoid TS2742 errors when exporting enums between packages.`typescript
// ā
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">
`Quick Start Guide
$3
`typescript
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}));
}
`Real-World Examples
$3
`typescript
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'
`$3
`typescript
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(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 (
{state.isEqual(FormState.SUBMITTING) && }
{state.isEqual(FormState.ERROR) && }
{state.isEqual(FormState.SUCCESS) && }
onClick={handleSubmit}
disabled={state.isEqual(FormState.SUBMITTING)}
>
Submit
);
}
`API Reference
$3
Creates a type-safe enum from an object mapping.
$3
Creates a type-safe enum from an array of string literals.
$3
| Method | Description | Example |
|------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------|--------------------------------------------------------------|
|
fromValue(value: string): SafeEnum | Returns the enum value matching the given value, or undefined if not found | Get enum value by string value | UserRole.fromValue('admin') |
| fromKey(key: string): SafeEnum | Returns the enum value matching the given key, or undefined if not found | Get enum value by key | UserRole.fromKey('ADMIN') |
| fromIndex(index: number): SafeEnum | Returns the enum value matching the given index, or undefined if not found | Get enum value by index | UserRole.fromIndex(0) |
| hasValue(value: string): boolean | Returns true if the enum has a value matching the given value, false otherwise | Check if value exists | UserRole.hasValue('admin') |
| hasKey(key: string): boolean | Returns true if the enum has a key matching the given key, false otherwise | Check if key exists | UserRole.hasKey('ADMIN') |
| hasIndex(index: number): boolean | Returns true if the enum has an index matching the given index, false otherwise | Check if index exists | UserRole.hasIndex(0) |
| isEnumValue(value: unknown): value is SafeEnum | Returns true if the value is from this enum, false otherwise | Type guard: Check if value is from this enum | UserRole.isEnumValue(maybeRole) |
| isEqual(other: SafeEnum | 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 | 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 | Iterate enum values (supports for...of / Array.from) | Array.from(UserRole) |$3
| 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 | Type guard: Check if value is from the same enum | UserRole.ADMIN.isEnumValue(role) |
| isEqual(other: SafeEnum | 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."