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
Iron Enum is a lightweight library that brings Rust-like tagged enums (also called algebraic data types or discriminated unions) to TypeScript. It provides a fluent, functional style for creating, inspecting, and pattern-matching on variant data structures — all with TypeScript’s strong type-checking at compile time.
Try Iron Enum Now - Free StackBlitz Sandbox
Table of Contents
- Iron Enum
Features
- Lightweight & Zero Dependencies: A minimal, dependency-free implementation (>1k bytes) leveraging TypeScript’s advanced type system and Proxies.
- Type-Safe Tagged Variants: Each variant is created with a unique tag and associated data, enforced at compile time by TypeScript.
- Powerful Pattern Matching: Intuitive
match
/matchAsync
methods eliminate the need for brittle switch statements or nested conditionals. - Conditional Checks: Minimal overhead for checking variants (via
if
/ifNot
) in a readable style. - Supports "Empty" Variants: Easily represent states like
None
orEmpty
by defining a variant withundefined
. - Reduced Boilerplate: The library handles the “discriminant” under the hood, cutting down on repetitive code.
Why Use This Library?
- Improved Code Clarity: Handle your logic in one place using pattern matching, reducing scattered if-else blocks and clarifying your app’s possible states.
- Type Safety: No more unverified type casts. TypeScript ensures that once you branch on a variant, you get the correct data type without extra checks.
- Maintainability & Scalability: Adding or removing variants is simple—update your variant definitions, and TypeScript will highlight where changes are needed.
- Functional Style: Iron Enum fits perfectly in FP-oriented codebases, or wherever you want to avoid
class
-based hierarchies and large frameworks. - Great for Error Handling & State Management: Pattern matching simplifies code that handles many possible outcomes.
Installation
Install via npm or yarn:
npm install iron-enum
# or
yarn add iron-enum
Then import and start defining your enums:
import { IronEnum } from "iron-enum";
Basic Usage
Defining Variants
Suppose you want an enum-like type with three variants:
- Foo contains an object
{ x: number }
- Bar contains a string
- Empty contains no data (i.e.,
undefined
)
import { IronEnum } from "iron-enum";
// 1. Define a record of variant keys to their associated data types
type MyVariants = {
Foo: { x: number };
Bar: string;
Empty: undefined;
};
// 2. Construct an enum builder using IronEnum
const MyEnum = IronEnum<MyVariants>();
Creating Values
You can create new enum values by calling the builder’s variant methods:
const fooValue = MyEnum.Foo({ x: 42 });
const barValue = MyEnum.Bar("Hello");
const emptyValue = MyEnum.Empty();
Each call returns a tagged object with helpful methods (e.g., match
, if
, ifNot
, etc.).
Pattern Matching
Handle each variant cleanly with match
. You can also provide a fallback (_
) if you wish:
fooValue.match({
Foo: (val) => {
// val is { x: number }
console.log("Foo with:", val.x);
},
Bar: (val) => {
console.log("Bar with:", val);
},
Empty: () => {
console.log("It's empty!");
},
_: () => {
// Optional fallback if you didn't specify all variants
console.log("No match found!");
}
});
Conditional Checks (if
/ ifNot
)
if
and ifNot
let you quickly test if a variant matches (or doesn’t match) a specific key, and optionally run callbacks.
// 1. Returns true/false by default
const isFoo = fooValue.if.Foo();
// isFoo === true if fooValue.tag === 'Foo', otherwise false
// 2. Optional callbacks
fooValue.if.Foo(
(val) => { // called if enum is "Foo" variant.
console.log("Yes, it's Foo:", val.x);
return "someReturnValue";
}, // else:
(json) => {
// json is { Bar?: string, Empty?: undefined, Foo?: { x: number } }
return "elseReturnValue";
}
);
// The return value is inferred. If you provide returns, you'll get that union type back.
fooValue.ifNot.Bar(() => {
console.log("Definitely not a Bar variant!");
});
Async Pattern Matching
When working with async logic, use matchAsync
. Each branch callback should return a Promise
(or use async
):
const result = await barValue.matchAsync({
Foo: async (val) => {
// Handle Foo asynchronously
return await fetchSomeData(val);
},
Bar: async (val) => {
// Handle Bar asynchronously
return "barValue resolved";
},
Empty: async () => {
await doSomethingAsync();
return "Handled Empty";
},
_: async () => "Default fallback"
});
// return type is inferred from match arm functions
console.log("Async match result:", result);
Serialization & Deserialization
Iron Enum values can be easily serialized to JSON (or sent across the network) by using toJSON()
. To get them back into an Iron Enum value, you can call parse
on the builder’s _
property.
const simpleEnum = IronEnum<{
foo: { text: string };
bar: { title: string };
}>();
const originalValue = simpleEnum.foo({ text: "hello" });
const jsonValue = originalValue.toJSON();
// jsonValue is now { foo: { text: "hello" } }
const parsedValue = simpleEnum._.parse(jsonValue);
// parsedValue is again a fully featured Iron Enum variant
parsedValue.match({
foo: (val) => console.log("Parsed back successfully:", val),
_: () => {}
});
Advanced Examples
Using Classes and Complex Objects
Iron Enum variants can contain anything: classes, nested objects, arrays, or even other Iron Enums.
class SimpleClass {
constructor(public name: string) {}
}
const nestedEnum = IronEnum<{ alpha: number; beta: string }>();
const complexEnum = IronEnum<{
test: typeof nestedEnum._.typeOf;
aClass: SimpleClass;
nestedData: {
foo: string;
bar: string;
array: { someProperty: string; anotherProperty: number }[];
};
}>();
const myInstance = complexEnum.aClass(new SimpleClass("TestName"));
const myNested = complexEnum.test(nestedEnum.alpha(42));
Passing Enums as Arguments
One of the biggest perks is that you can pass these enums around, and the type system will protect you from invalid usage:
const testEnum = IronEnum<{ foo: string; bar: string }>();
function handleTestEnum(value: typeof testEnum._.typeOf) {
// Now we can pattern-match safely
return value.match({
foo: (val) => `Got foo: ${val}`,
bar: (val) => `Got bar: ${val}`,
});
}
const result = handleTestEnum(testEnum.foo("Hello!"));
console.log(result); // "Got foo: Hello!"
Inferring Data Types
TypeScript automatically infers the data type for each branch when you match on an Iron Enum. You can also create custom types to extract payload information:
const myEnum = IronEnum<{ Foo: { x: number }; Bar: string }>();
const fooValue = myEnum.Foo({ x: 42 });
type InferFooDataType<X extends typeof myEnum._.typeOf> =
X extends { tag: "Foo"; data: infer Payload }
? Payload
: never;
type Inferred = InferFooDataType<typeof fooValue>;
// Inferred = { x: number }
Option & Result
Iron Enum includes convenient implementations for two common patterns: Option
and Result
.
Absolutely! Here’s a set of clear and practical usage examples for the Option
and Result
types that you can include in your README to help users understand how to use your library effectively.
Option
The Option
type is useful for representing values that may or may not exist.
Creating Options
const NumberOption = Option<number>();
const some = NumberOption.Some(42);
const none = NumberOption.None();
Unwrap
some.unwrap(); // 42
none.unwrap(); // ❌ throws: Called .unwrap() on an Option.None enum!
Unwrap with fallback
some.unwrap_or(100); // 42
none.unwrap_or(100); // 100
some.unwrap_or_else(() => 999); // 42
none.unwrap_or_else(() => 999); // 999
Convert to Result
const OkOrErr = some.ok_or("Not found"); // Ok(42)
const ErrRes = none.ok_or("Not found"); // Err("Not found")
const OkOrErrLazy = some.ok_or_else(() => "fail"); // Ok(42)
const ErrResLazy = none.ok_or_else(() => "fail"); // Err("fail")
Matching
some.match({
Some: (val) => `Value is ${val}`,
None: () => "No value"
}); // "Value is 42"
none.match({
Some: (val) => `Value is ${val}`,
None: () => "No value"
}); // "No value"
Result
The Result
type is useful for returning either a success value (Ok
) or an error (Err
).
Creating Results
const NumResult = Result<number, string>();
const ok = NumResult.Ok(123);
const err = NumResult.Err("Something went wrong");
Unwrap
ok.unwrap(); // 123
err.unwrap(); // ❌ throws: Called .unwrap() on a Result.Err enum!
Unwrap with fallback
ok.unwrap_or(0); // 123
err.unwrap_or(0); // 0
ok.unwrap_or_else(() => 999); // 123
err.unwrap_or_else(() => 999); // 999
Convert to Option
const SomeOpt = ok.ok(); // Some(123)
const NoneOpt = err.ok(); // None()
Matching
ok.match({
Ok: (val) => `Success: ${val}`,
Err: (e) => `Failure: ${e}`
}); // "Success: 123"
err.match({
Ok: (val) => `Success: ${val}`,
Err: (e) => `Failure: ${e}`
}); // "Failure: Something went wrong"
Contributing
Contributions, suggestions, and feedback are welcome! Please open an issue or submit a pull request on the GitHub repository.
License
This library is available under the MIT license. See the LICENSE file for details.