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.
TL;DR Stop writing brittle
switch
statements or sprawlingif / else
chains. Model your program’s states with expressive, type‑sound enums that compile down to plain JavaScript objects with helper methods — no classes, no runtime bloat.
Table of Contents
Why Iron Enum?
- Clarity — Express all possible states in one place; TypeScript warns you when you forget a branch.
- Maintainability — Adding a new variant instantly surfaces every site that needs to handle it.
- Functional Flair — Great for FP‑oriented codebases or anywhere you want to banish
null
& friends. - Safe Data Transport —
toJSON()
/_.parse()
make it effortless to serialize across the wire.
Native discriminated unions are great, but they leave you to hand‑roll guards and pattern matching every time. Iron Enum wraps the same type‑level guarantees in an ergonomic, reusable runtime API.
Installation
npm i iron-enum
# or
pnpm add iron-enum
# or
yarn add iron-enum
Quick Start
import { IronEnum } from "iron-enum";
// 1. Declare your variants
const Status = IronEnum<{
Idle: undefined;
Loading: undefined;
Done: { items: number };
}>();
// 2. Produce values
const state = Status.Done({ items: 3 });
// 3. Handle them exhaustively
state.match({
Idle: () => console.log("No work yet."),
Loading: () => console.log("Crunching…"),
Done: ({ items }) => console.log(`Completed with ${items} items.`),
});
// 4. Handle as args
const handleLoadingState = (stateInstance: typeof Status._.typeOf) => { /* .. */ }
handleLoadingState(state);
Pattern Matching & Guards
Exhaustive match
// branching
value.match({
Foo: (x) => doSomething(x),
Bar: (s) => console.log(s),
_: () => fallback(), // optional catch‑all
});
// return with type inference
const returnValue = value.match({
Foo: (x) => x,
Bar: (s) => s,
_: () => null
});
// typeof returnValue == x | s | null
Fluent if.*
/ ifNot.*
// branching
value.if.Foo(
({ count }) => console.log(`It *is* Foo with ${count}`),
() => console.log("It is NOT Foo"),
);
// return through callbacks with type inference
const isNumber = value.if.Foo(
// if true
({ count }) => count,
// if false
() => 0,
);
// in statement, callbacks optional
if (value.if.Foo()) {
// value is Foo!
} else {
// value is NOT Foo!
}
Both helpers return the callback’s result or a boolean when you omit callbacks, so they slot neatly into expressions.
Async Workflows
Need to await network calls inside branches? Use matchAsync
:
await status.matchAsync({
Idle: async () => cache.get(),
Loading: async () => await poll(),
Done: async ({ items }) => items,
});
Option & Result Helpers
import { Option, Result } from "iron-enum";
// Option<T>
const MaybeNum = Option<number>();
const some = MaybeNum.Some(42);
const none = MaybeNum.None();
console.log(some.unwrap()); // 42
console.log(none.unwrap_or(0)); // 0
// Result<T, E>
const NumOrErr = Result<number, Error>();
const ok = NumOrErr.Ok(123);
const err = NumOrErr.Err(new Error("Boom"));
ok.match({ Ok: (v) => v, Err: console.error });
The helper instances expose Rust‑style sugar (isOk()
, isErr()
, ok()
, etc.) while still being regular Iron Enum variants under the hood.
Try / TryInto Utilities
Run any operation that may throw and return it as a Result
type:
import { Try } from "iron-enum";
const result = Try.sync(() => {
// risk stuffy that might throw new Error()
});
if (result.if.Ok()) { /* … */ }
Or create a new function that may throw that always returns a Result
.
import { TryInto } from "iron-enum";
const safeParseInt = TryInto.sync((s: string) => {
const n = parseInt(s, 10);
if (Number.isNaN(n)) throw new Error("NaN");
return n;
});
const result = safeParseInt("55");
result.if.Ok((value) => {
console.log(value) // 55;
})
Try
and TryInto
also have async variants that work with Promises
and async/await
.
Advanced Recipes
- Nested Enums — compose enums inside payloads for complex state machines.
- Optional‑object payloads — if all payload keys are optional, the constructor arg becomes optional:
E.Query()
==E.Query({})
. - Serialization —
enum.toJSON()
➜{ Variant: payload }
, andEnum._.parse(obj)
brings it back. - Type Extraction —
typeof MyEnum._.typeOf
gives you the union type of all variants.
FAQ & Trade‑offs
Does Iron Enum add runtime overhead?
No. Each constructed value is a plain object { tag, data, …helpers }
. The helper methods are closures created once per value; for most apps this is negligible compared with the clarity you gain.
Why not stick with vanilla TypeScript unions?
Vanilla unions keep types safe but leave guards up to you. Iron Enum bakes common guard logic into reusable helpers and ensures your match statements stay exhaustive.
Can I tree‑shake out helpers I don’t use?
Yes. Because everything is property‑based access on the enum instance, dead‑code elimination removes unused helpers in modern bundlers.
Contributing
PRs and issues are welcome!
License
MIT © 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