@castral/ts-enum
v1.1.0
Published
A utility library for working with TypeScript enum-like objects.
Maintainers
Readme
ts-enum
Type-safe TypeScript enum utilities with perfect type inference and zero dependencies
Why Another Enum Library?
Because existing solutions are over-engineered. Popular enum libraries use wrapper objects, WeakMaps, and type assertions to work around TypeScript's limitations. This library takes a different approach: work with TypeScript's type system, not against it.
import { Enum } from '@castral/ts-enum';
enum Status { Idle = 0, Running = 1, Complete = 2 }
// ✅ Perfect type inference - no casts needed
const keys = Enum.keys(Status); // ("Idle" | "Running" | "Complete")[]
const values = Enum.values(Status); // Status[] (= (0 | 1 | 2)[])
// ✅ Literal types preserved through type guards
const value = 1;
if (Enum.isValue(Status, value)) {
// value is narrowed to Status.Running
}
// ✅ TypeScript proves unreachable code
const invalid = 99;
if (Enum.isValue(Status, invalid)) {
// Type: never - TypeScript knows this will never execute!
}Features
- 🎯 Perfect Type Inference - Literal union types, not generic
string[]ornumber[] - 🔍 Exact Member Narrowing - Type guards narrow to specific enum members, not just unions
- ⚡ Zero Dependencies - No external packages, just pure TypeScript
- 📦 Tiny Bundle - ~1KB minified, tree-shakeable
- 🛡️ Type-Safe - Works with single Enums as well as unions of Enums, ie:
typeof FirstEnum | typeof SecondEnum - ✅ Compile-Time Proof - TypeScript mathematically proves correctness
- 🔧 Works Everywhere - Numeric enums, string enums, and mixed enums
Installation
npm install @castral/ts-enumyarn add @castral/ts-enumpnpm add @castral/ts-enumQuick Start
import { Enum } from '@castral/ts-enum';
enum Priority {
Low = 1,
Medium = 2,
High = 3
}
// Get enum keys with exact literal types
const keys = Enum.keys(Priority);
// Type: ("Low" | "Medium" | "High")[]
// Value: ["Low", "Medium", "High"]
// Get enum values with exact literal types
const values = Enum.values(Priority);
// Type: Priority[] (= (1 | 2 | 3)[])
// Value: [1, 2, 3]
// Type-safe validation
function handlePriority(value: unknown) {
if (Enum.isValue(Priority, value)) {
// value is narrowed to Priority
console.log('Valid priority:', value);
}
}API Reference
Enum.keys<E>(enum)
Returns an array of enum keys with exact literal types.
enum Status { Idle = 0, Running = 1 }
const keys = Enum.keys(Status);
// Type: ("Idle" | "Running")[]
// Not: string[] ❌Enum.values<E>(enum)
Returns an array of enum values with exact literal types.
enum Status { Idle = 0, Running = 1 }
const values = Enum.values(Status);
// Type: Status[] (= (0 | 1)[])
// Not: (string | number)[] ❌Enum.entries<E>(enum)
Returns an array of [key, value] tuples with exact types.
enum Status { Idle = 0, Running = 1 }
const entries = Enum.entries(Status);
// Type: [("Idle" | "Running"), Status][]
// Value: [["Idle", 0], ["Running", 1]]Enum.isKey<E>(enum, key)
Type guard that narrows to exact enum keys.
enum Status { Idle = 0, Running = 1 }
function parse(key: unknown) {
if (Enum.isKey(Status, key)) {
const val = Status[key];
console.log(`Status is ${key}: ${val}`);
}
}
// Must exactly match enum key
const invalid = 'idle';
if (Enum.isKey(Status, invalid)) {
// Type: never - this will never execute!
}Enum.isValue<E>(enum, value)
Type guard that narrows to exact enum values.
enum Status { Idle = 0, Running = 1 }
function process(value: unknown) {
if (Enum.isValue(Status, value)) {
// value is narrowed to Status
switch (value) {
case Status.Idle: // ✅ Type-safe
case Status.Running: // ✅ Type-safe
}
}
}
const invalid = 99;
if (Enum.isValue(Status, invalid)) {
// Type: never - this will never execute!
}Enum.createIsKeyGuard<E>(enum)
Creates a reusable key type guard with cached keys (O(1) lookup).
enum Status { Idle = 0, Running = 1 }
const isStatus = Enum.createIsKeyGuard(Status);
// Use in hot paths for better performance
if (isStatus(key)) {
// key is narrowed to Status
}Enum.createIsValueGuard<E>(enum)
Creates a reusable value type guard with cached values (O(1) lookup).
enum Status { Idle = 0, Running = 1 }
const isStatus = Enum.createIsValueGuard(Status);
// Use in hot paths for better performance
if (isStatus(value)) {
// value is narrowed to Status
}Enum.map<E, T>(enum, fn)
Transform enum entries with type-safe callback.
enum Priority { Low = 1, Medium = 2, High = 3 }
const options = Enum.map(Priority, (key, value) => ({
label: key, // Type: "Low" | "Medium" | "High"
value: value, // Type: 1 | 2 | 3
priority: value
}));Enum.keyOf<E>(enum, value)
Reverse lookup: get key from value.
enum Status { Idle = 0, Running = 1 }
const key = Enum.keyOf(Status, 1);
// Type: ("Idle" | "Running") | undefined
// Value: "Running"Enum.keyOfStrict<E>(enum, value)
Strict reverse lookup: throws if value not found.
enum Status { Idle = 0, Running = 1 }
const key = Enum.keyOfStrict(Status, 1);
// Type: "Idle" | "Running"
// Throws if value is not 0 or 1Enum.keyOfValueToString<E>(enum, value)
Convert enum value to string key.
enum Status { Idle = 0, Running = 1 }
const str = Enum.keyOfValueToString(Status, Status.Idle);
// Type: string | undefined
// Value: "Idle"Enum.valueOfKey<E>(enum, Key<E>)
enum Status { Idle = 0, Running = 1 }
const value = Enum.valueOfKey(Status, Enum.keys(Status)[1] ?? 'Idle');
// Type: Status
// Value: 1Enum.get<E>(enum, key: unknown)
enum Status { Idle = 0, Running = 1 }
const currentStatus: string | undefined = 'Running';
const value = Enum.get(Status, currentStatus);
// Type: Status | undefined
// Value: 1Enum.valueOfKeyFromString<E>(enum, string)
Parse string to enum value (case-insensitive).
enum Status { Idle = 0, Running = 1 }
const value = Enum.valueFromKeyString(Status, 'idle');
// Type: Status | undefined
// Value: 0Advanced Type Inference
Exact Member Narrowing
Unlike other libraries, ts-enum narrows to exact enum members, not just the union type.
enum Color { Red = 'red', Blue = 'blue' }
const literal = 'red';
if (Enum.isValue(Color, literal)) {
// Other libraries: literal has type Color (= Color.Red | Color.Blue)
// This library: literal has type Color.Red
const exact: Color.Red = literal; // ✅ Works!
}Literal Type Preservation
TypeScript can prove compile-time value mappings.
enum Status { Idle = 0, Running = 1 }
const value = 0; // Literal type: 0
if (Enum.isValue(Status, value)) {
// value is proven to be Status.Idle at compile time
const idle: Status.Idle = value; // ✅ No cast needed
}
const invalid = 99; // Literal type: 99
if (Enum.isValue(Status, invalid)) {
const x = invalid; // Type: never
// This code is unreachable!
}Type Helpers
Extract types from enums at compile time.
enum Status { Idle = 0, Running = 1, Complete = 2 }
type StatusKey = Enum.Key<typeof Status>;
// = "Idle" | "Running" | "Complete"
type StatusValue = Enum.Value<typeof Status>;
// = Status.Idle | Status.Running | Status.Complete
// = 0 | 1 | 2vs. Native Object Methods
enum Status { Idle = 0, Running = 1 }
// ❌ Object.keys() - loses type information
Object.keys(Status) // string[] (includes "0", "1")
Object.values(Status) // (string | number)[]
// ✅ Enum utilities - preserves exact types
Enum.keys(Status) // ("Idle" | "Running")[]
Enum.values(Status) // Status[] (= (0 | 1)[])Use Cases
Compile-Time Configuration
enum AgentPaths {
dashboard = 'Agent Dashboard',
}
enum StreamPaths {
overview = 'Stream Overview',
}
const Outlets = {
agent: AgentPaths,
stream: StreamPaths,
} as const;
interface Route<T extends keyof typeof Outlets> {
component: any;
outlet: T;
path: Enum.Key<Enum.EnumLike<(typeof Outlets)[T]>>;
}
const ROUTES: (Route<'agent'> | Route<'stream'>)[] = [
{
component: AgentDashboard,
outlet: 'agent',
// Type is typeof AgentPaths
path: 'dashboard', // Can't be 'overview'
},
{
component: StreamOverview,
outlet: 'stream',
// Type is typeof StreamPaths
path: 'overview', // Can't be 'dashboard'
},
] as const;
const currentPath = 'dashboard';
Object.keys(Outlets).forEach((outlet) => {
// Type is: typeof AgentPaths | typeof StreamPaths
const titleEnum = Outlets[outlet as keyof typeof Outlets];
const pageTitle = Enum.get(titleEnum, currentPath) ?? 'Unknown Page';
})Form Select Options
enum Priority { Low = 1, Medium = 2, High = 3 }
const options = Enum.map(Priority, (key, value) => ({
label: key,
value: value,
color: value === Priority.High ? 'red' : 'gray'
}));
// Type: { label: "Low" | "Medium" | "High", value: 1 | 2 | 3, color: string }[]API Validation
enum HttpStatus {
OK = 200,
BadRequest = 400,
Unauthorized = 401,
NotFound = 404
}
const isHttpStatus = Enum.createIsValueGuard(HttpStatus);
function handleResponse(status: number) {
if (isHttpStatus(status)) {
// status is narrowed to HttpStatus
console.log(`Status: ${Enum.keyOf(HttpStatus, status)}`);
} else {
console.error('Unknown status code');
}
}Exhaustive Switching
enum Action { Create = 'create', Update = 'update', Delete = 'delete' }
function handle(action: Action) {
switch (action) {
case Action.Create:
return 'creating';
case Action.Update:
return 'updating';
case Action.Delete:
return 'deleting';
// TypeScript enforces exhaustiveness - no default needed!
}
}Runtime Validation
enum Role { Admin = 'admin', User = 'user', Guest = 'guest' }
function parseRole(input: unknown): Role {
if (Enum.isValue(Role, input)) {
return input; // Type: Role
}
throw new Error(`Invalid role: ${input}`);
}Works With All Enum Types
Numeric Enums
enum Status { Idle = 0, Running = 1, Complete = 2 }
Enum.keys(Status) // ("Idle" | "Running" | "Complete")[]
Enum.values(Status) // (0 | 1 | 2)[]String Enums
enum Color { Red = 'red', Blue = 'blue', Green = 'green' }
Enum.keys(Color) // ("Red" | "Blue" | "Green")[]
Enum.values(Color) // ("red" | "blue" | "green")[]Mixed Enums
enum Mixed { A = 1, B = 2, C = 'c', D = 'd' }
Enum.keys(Mixed) // ("A" | "B" | "C" | "D")[]
Enum.values(Mixed) // (1 | 2 | "c" | "d")[]TypeScript Configuration
For best results, use strict mode:
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true
}
}Why This Works
This library leverages TypeScript's type system correctly:
- No wrapper objects - Direct enum access preserves type information
- Proper type predicates -
v is E[keyof E & string]enables narrowing - Literal type preservation - TypeScript can prove exact value mappings
- Control flow analysis - Type guards work with TypeScript's narrowing
Other libraries fight the type system with runtime structures (WeakMaps, wrappers) and type assertions. This library works with TypeScript to let it prove correctness.
Contributing
Contributions welcome! Please open an issue or PR.
License
MIT © Cas Argilla
Note: This library does not work with const enum because const enums are compile-time only and have no runtime representation. Use regular enums instead.
