Package Exports
- iron-enum
- iron-enum/dist/mod.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (iron-enum) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
Iron Enum
Super‑lightweight Rust‑style tagged unions for TypeScript — fully type‑safe, zero‑dependency, < 1 kB min+gz.
IronEnum lets you model expressive enums (a.k.a. tagged unions) in plain TypeScript and gives you ergonomic helpers inspired by Rust’s Option, Result, and try patterns.
Features
- 🦀 Rust-inspired - Familiar
Result,Option, and pattern matching - 🎯 Type-safe - Full TypeScript support with excellent type inference
- 🚀 Zero dependencies - Lightweight and fast (~1kb gzipped)
- 🔧 Ergonomic API - Intuitive constructors and method chaining
- 🎮 Pattern matching - Exhaustive
matchandmatchAsyncmethods - 🛡️ Error handling - Built-in
TryandTryIntoutilities
Installation
npm install iron-enum
# or
yarn add iron-enum
# or
pnpm add iron-enumQuick Start
import { IronEnum } from 'iron-enum';
// Define your enum variants
const Status = IronEnum<{
Loading: undefined;
Ready: { finishedAt: Date };
Error: { message: string; code: number };
}>();
// Create instances
const loading = Status.Loading();
const ready = Status.Ready({ finishedAt: new Date() });
const error = Status.Error({ message: "Network error", code: 500 });
// Pattern match
const message = ready.match({
Loading: () => "Still working...",
Ready: ({ finishedAt }) => `Done at ${finishedAt.toLocaleTimeString()}`,
Error: ({ message }) => `Failed: ${message}`
});Core Concepts
Creating Enums
IronEnum uses TypeScript's type system to create discriminated unions with zero runtime overhead:
// Simple enum without payloads
const Direction = IronEnum<{
North: undefined;
South: undefined;
East: undefined;
West: undefined;
}>();
// Enum with different payload types
const UserEvent = IronEnum<{
Login: { userId: string; timestamp: Date };
Logout: { userId: string };
Update: { userId: string; changes: Record<string, any> };
}>();
// Using the enum
const event = UserEvent.Login({
userId: "user123",
timestamp: new Date()
});Pattern Matching
The match method ensures exhaustive handling of all variants:
const Shape = IronEnum<{
Circle: { radius: number };
Rectangle: { width: number; height: number };
Triangle: { base: number; height: number };
}>();
const shape = Shape.Circle({ radius: 5 });
const area = shape.match({
Circle: ({ radius }) => Math.PI * radius ** 2,
Rectangle: ({ width, height }) => width * height,
Triangle: ({ base, height }) => (base * height) / 2
});
// With fallback using '_'
const description = shape.match({
Circle: () => "Round shape",
_: () => "Polygonal shape" // Catches Rectangle and Triangle
});Guards and Conditionals
Use if and ifNot for conditional logic:
const Auth = IronEnum<{
Authenticated: { user: { id: string; name: string } };
Anonymous: undefined;
}>();
const auth = Auth.Authenticated({ user: { id: "123", name: "Alice" } });
// Simple boolean check
if (auth.if("Authenticated")) {
console.log("User is logged in");
}
// With callbacks
const userName = auth.if(
"Authenticated",
({ user }) => user.name,
() => "Guest"
);
// Inverse check
auth.ifNot(
"Anonymous",
() => console.log("User is authenticated")
);Built-in Types
Result<T, E>
Rust-style error handling:
import { Result, Ok, Err } from 'iron-enum';
function divide(a: number, b: number): Result<number, string> {
if (b === 0) {
return Err("Division by zero");
}
return Ok(a / b);
}
const result = divide(10, 2);
// Pattern matching
const message = result.match({
Ok: (value) => `Result: ${value}`,
Err: (error) => `Error: ${error}`
});
// Convenience methods
console.log(result.isOk()); // true
console.log(result.unwrap()); // 5
console.log(result.unwrap_or(0)); // 5Option
Nullable value handling:
import { Option, Some, None } from 'iron-enum';
function findUser(id: string): Option<User> {
const user = database.find(u => u.id === id);
return user ? Some(user) : None();
}
const userOption = findUser("123");
// Convert to Result
const userResult = userOption.ok_or("User not found");
// Pattern matching
userOption.match({
Some: (user) => console.log(`Found: ${user.name}`),
None: () => console.log("User not found")
});
// Convenience methods
console.log(userOption.isSome()); // boolean
console.log(userOption.unwrap_or(null)); // User | nullTry and TryInto
Automatic exception handling:
import { Try, TryInto } from 'iron-enum';
// Wrap a potentially throwing function
const result = Try.sync(() => {
return JSON.parse('{"valid": "json"}');
});
// Async version
const asyncResult = await Try.async(async () => {
const response = await fetch('/api/data');
return response.json();
});
// Transform existing functions
const safeParse = TryInto.sync(JSON.parse);
const safeReadFile = TryInto.async(fs.promises.readFile);
// Use the wrapped functions
const parseResult = safeParse('{"key": "value"}');
parseResult.match({
Ok: (data) => console.log("Parsed:", data),
Err: (error) => console.log("Parse failed:", error)
});Advanced Usage
Async Pattern Matching
const RemoteData = IronEnum<{
NotAsked: undefined;
Loading: undefined;
Success: { data: any };
Failure: { error: Error };
}>();
const state = RemoteData.Success({ data: { id: 1, name: "Item" } });
const processed = await state.matchAsync({
NotAsked: async () => null,
Loading: async () => "Loading...",
Success: async ({ data }) => {
// Async processing
const enhanced = await enhanceData(data);
return enhanced;
},
Failure: async ({ error }) => {
await logError(error);
return null;
}
});Serialization
Enums can be easily serialized to JSON:
const Status = IronEnum<{
Active: { since: Date };
Inactive: { reason: string };
}>();
const status = Status.Active({ since: new Date() });
// Convert to JSON
console.log(status.toJSON());
// { Active: { since: "2024-01-01T00:00:00.000Z" } }
// Parse from JSON
const parsed = Status._.parse({ Active: { since: new Date() } });Type Guards and Narrowing
const Message = IronEnum<{
Text: { content: string };
Image: { url: string; alt?: string };
Video: { url: string; duration: number };
}>();
function processMessage(msg: typeof Message._.typeOf) {
// The tag property enables type narrowing
switch (msg.tag) {
case "Text":
console.log(msg.payload.content); // TypeScript knows this is string
break;
case "Image":
console.log(msg.payload.url); // TypeScript knows this is a string
break;
case "Video":
console.log(msg.payload.duration); // TypeScript knows this is number
break;
}
}Performance Optimization
For performance-critical applications, you can pre-define variant keys:
// Pre-allocated version (no Proxy)
const Status = IronEnum<{
Idle: undefined;
Running: { pid: number };
Stopped: { exitCode: number };
}>({
keys: ["Idle", "Running", "Stopped"] // <- provide all keys in an array available at runtime.
});
// This avoids the Proxy overhead for better performanceAPI Reference
IronEnum Methods
Every enum instance has these methods:
tag: The variant name (discriminant)payload: The variant's associated datatoJSON(): Convert to plain objectkey(): Get the variant keyif(key, onMatch?, onMismatch?): Conditional executionifNot(key, onMismatch?, onMatch?): Inverse conditionalmatch(handlers): Exhaustive pattern matchingmatchAsync(handlers): Async pattern matching
Result Methods
In addition to enum methods:
isOk(): Check if Result is OkisErr(): Check if Result is Errunwrap(): Get value or throwunwrap_or(default): Get value or defaultunwrap_or_else(fn): Get value or compute defaultok(): Convert to Option
Option Methods
In addition to enum methods:
isSome(): Check if Option has valueisNone(): Check if Option is Noneunwrap(): Get value or throwunwrap_or(default): Get value or defaultunwrap_or_else(fn): Get value or compute defaultok_or(error): Convert to Resultok_or_else(fn): Convert to Result with computed error
Best Practices
- Use exhaustive matching - Always handle all variants or use
_fallback - Leverage type inference - Let TypeScript infer types from your variants
- Prefer Option/Result - Use built-in types for common patterns
- Keep payloads immutable - Treat enum data as read-only
- Use meaningful variant names - Make your code self-documenting
Examples
State Machine
const State = IronEnum<{
Idle: undefined;
Processing: { taskId: string; startedAt: Date };
Completed: { taskId: string; result: string };
Failed: { taskId: string; error: Error };
}>();
class TaskProcessor {
private state = State.Idle();
start(taskId: string) {
this.state = State.Processing({ taskId, startedAt: new Date() });
}
complete(result: string) {
this.state.if("Processing", ({ taskId }) => {
this.state = State.Completed({ taskId, result });
});
}
getStatus(): string {
return this.state.match({
Idle: () => "Ready",
Processing: ({ taskId }) => `Processing ${taskId}...`,
Completed: ({ taskId }) => `Task ${taskId} completed`,
Failed: ({ error }) => `Failed: ${error.message}`
});
}
}Form Validation
const ValidationResult = IronEnum<{
Valid: { value: string };
Invalid: { errors: string[] };
}>();
function validateEmail(email: string): ValidationResult {
const errors: string[] = [];
if (!email) errors.push("Email is required");
if (!email.includes("@")) errors.push("Invalid email format");
if (email.length > 100) errors.push("Email too long");
return errors.length > 0
? ValidationResult.Invalid({ errors })
: ValidationResult.Valid({ value: email.toLowerCase() });
}
// Usage
const result = validateEmail("user@example.com");
result.match({
Valid: ({ value }) => console.log("Email accepted:", value),
Invalid: ({ errors }) => console.error("Validation failed:", errors)
});License
MIT © 2025 Scott Lott
Keywords
typescript, enum, tagged union, tagged unions, discriminated union, algebraic data type, adt, sum type, union types, rust enums, rust, pattern matching, option type, result type, functional programming
Made with ❤️ by developers who miss Rust's enums in TypeScript