8000 GitHub - only-cliches/iron-enum: Ergonomic Rust like enums in Typescript
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

only-cliches/iron-enum

Repository files navigation

Iron Enum

Super‑lightweight Rust‑style tagged unions for TypeScript — fully type‑safe, zero‑dependency, < 1 kB min+gz.

GitHub Repo stars NPM Version JSR Version npm package minimized gzipped size License: MIT

TL;DR   Stop writing brittle switch statements or sprawling if / 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.

▶ Open playground


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 TransporttoJSON() / _.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({}).
  • Serializationenum.toJSON(){ Variant: payload }, and Enum._.parse(obj) brings it back.
  • Type Extractiontypeof 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

About

Ergonomic Rust like enums in Typescript

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published
0