npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

iron-enum

v1.7.2

Published

Rust like enums for Typescript

Readme

🦾 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 TypeScript License: MIT

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.

▶ Open playground

Table of Contents

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 match and matchAsync methods
  • 🛡️ Error handling - Built-in Try and TryInto utilities

Installation

npm install iron-enum
# or
yarn add iron-enum
# or
pnpm add iron-enum

Quick 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}`
});

// Use in UI components...
// Works with React and SolidJS out of the box!
const component = (props) => {

    const [value, setValue] = useState(Status.Loading());

    return (
        <div>
            {value.match({
                Loading: () => <div>Loading</div>,
                Ready: ({ finishedAt }) => <div>{`Done at ${finishedAt.toLocaleTimeString()}`}</div>,
                Error: ({ message }) => <div>{`Failed: ${message}`}</div>
            })}
        </div>
    )
}

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';

const DivideResult = Result<number, string>();

// 2. Use the factory's type for the return annotation
function divide(a: number, b: number): typeof DivideResult._.typeOf {
  if (b === 0) {
    return DivideResult.Err("Division by zero");
  }
  return DivideResult.Ok(a / b);
}

const result = divide(10, 2);

// Pattern matching
const message = result.match({
  Ok: (value) => `Result: ${value}`,
  Err: (error) => `Error: ${error}`
});

console.log(message); // "Result: 5"

// Convenience methods
console.log(result.isOk());        // true
console.log(result.unwrap());      // 5
console.log(result.unwrap_or(0));  // 5

Option<T>

Nullable value handling:

import { Option } from 'iron-enum';

// Assumes 'User' type is defined elsewhere

type User = {id: number, name: string};

const optUser = Option<User>();

const userOption = optUser.Some({ id: 123, name: "Alice" }); // Example

// 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 | null

Try 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); // Example for Node.js

// Use the wrapped functions
const parseResult = safeParse('{"key": "value"}');
parseResult.match({
  Ok: (data) => console.log("Parsed:", data),
  Err: (error) => console.log("Parse failed:", error)
});

EcoSystem Helpers

Zod

For runtime validation (e.g., parsing API responses), you can use the iron-enum-zod helper to create an IronEnum and a zod schema from a single definition.

npm install iron-enum-zod zod

This gives you a single, powerful factory with constructors and parsing methods.

import { z } from 'zod';
import { createZodEnum } from 'iron-enum-zod';

// 1. Define your payload schemas using Zod
const StatusPayloads = {
  Loading: z.undefined(),
  Ready: z.object({ finishedAt: z.date() }),
  Error: z.object({ message: z.string(), code: z.number() }),
};

// 2. Create the enhanced enum factory
const Status = createZodEnum(StatusPayloads);

// 3. You get all the standard constructors
const ready = Status.self.Ready({ finishedAt: new Date() });

// 4. And you get new, type-safe parsing methods
const apiInput = { tag: "Ready", data: { finishedAt: "2025-10-25T10:00:00.000Z" } } as const;

// .parse() returns an enum that's been recursively parsed by zod then converted into an `IronEnum` type.
const apiParsed = Status.parse(apiInput);
// now use apiPased as a normal enum
apiParsed.match(...)
apiParsed.if(...)

// You can also access the raw schema
const UserSchema = z.object({
  id: z.string(),
  status: Status.schema,
});

Vue

iron-enum-vue provides a createEnumMatch function that generates a component that uses slots for an idiomatic, type-safe matching experience.

<script setup lang="ts">
import { IronEnum } from "iron-enum";
import { createEnumMatch } from "iron-enum-vue";
import { ref } from "vue";

const Status = IronEnum<{
  Loading: undefined;
  Ready: { finishedAt: Date };
  Error: { message: string; code: number };
}>();

const EnumMatch = createEnumMatch(Status);

const statusValue = ref(Status.Loading());

</script>

<template>
  <EnumMatch :of="statusValue">
    <template #Loading>
      <div>Loading</div>
    </template>

    <template #Ready="{ finishedAt }">
      <div>Done at {{ finishedAt.toLocaleTimeString() }}</div>
    </template>

    <template #Error="{ message, code }">
      <div>Failed: {{ message }} ({{ code }})</div>
    </template>

    <!-- Optional fallback for any unhandled tag -->
    <template #_="{ tag }">
      <div>Unknown: {{ tag }}</div>
    </template>
  </EnumMatch>
</template>

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;
    return data; // Example
  },
  Failure: async ({ error }) => {
    // await logError(error);
    return null;
  }
});

Serialization & Parsing

Enums have a built-in toJSON() method for easy serialization. Use _.parse() for deserialization from plain objects.

const Status = IronEnum<{
  Active: { since: string };
  Inactive: { reason: string };
}>();

const status = Status.Active({ since: new Date().toISOString() });

// Convert to JSON
const json = status.toJSON(); 
// { tag: "Active", data: { since: "2025-10-24T..." } }

// ... send over network ...

// Parse from JSON
const parsed = Status._.parse(json); 

console.log(parsed.tag); // "Active"

Type Guards and Narrowing

const Message = IronEnum<{
  Text: { content: string };
  Image: { url: string; alt?: string };
  Video: { url: string; duration: number };
}>();

// Use `typeof Message._.typeOf` for the union type
function processMessage(msg: typeof Message._.typeOf) {
  // The tag property enables type narrowing
  switch (msg.tag) {
    case "Text":
      console.log(msg.data.content); // TypeScript knows this is string
      break;
    case "Image":
      console.log(msg.data.url);     // TypeScript knows this is a string
      break;
    case "Video":
      console.log(msg.data.duration); // TypeScript knows this is number
      break;
  }
}

Performance Optimization

Normally each time you call IronEnum() a proxy is created, however this can be bypassed for performance-critical applications by providing the variant keys as parameters.

Passing in keys also adds key validation to the myEnum._.parse(...) method.

// 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
});

// This avoids the Proxy overhead for better performance

API Reference

IronEnum Methods

Every enum instance has these methods:

  • tag: The variant name (discriminant).
  • data: The variant's associated data.
  • toJSON(): Convert to plain object.
  • is(key): Conditional check for if(..) statements.
  • if(key, onMatch?, onMismatch?): Conditional execution.
  • ifNot(key, onMismatch?, onMismatch?): Inverse conditional.
  • match(handlers): Optional exhaustive pattern matching, fallback allowed
  • matchAsync(handlers): Async pattern matching.
  • matchExhaustive(handlers) Exhaustive pattern matching, no fallback method allowed.

Result Methods

In addition to enum methods:

  • isOk(): Check if Result is Ok.
  • isErr(): Check if Result is Err.
  • unwrap(): Get value or throw error.
  • unwrap_or(default): Get value or return default.
  • unwrap_or_else(fn): Get value or compute default.
  • ok(): Convert to Option, discarding error.

Option Methods

In addition to enum methods:

  • isSome(): Check if Option has a value.
  • isNone(): Check if Option is None.
  • unwrap(): Get value or throw error.
  • unwrap_or(default): Get value or return default.
  • unwrap_or_else(fn): Get value or compute default.
  • ok_or(error): Convert to Result with provided error.
  • ok_or_else(fn): Convert to Result with computed error.

Best Practices

  1. Use exhaustive matching - Always handle all variants or use _ fallback.
  2. Leverage type inference - Let TypeScript infer types from your variants.
  3. Prefer Option/Result - Use built-in types for common patterns.
  4. Keep payloads immutable - Treat enum data as read-only.
  5. 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("[email protected]");
result.match({
  Valid: ({ value }) => console.log("Email accepted:", value),
  Invalid: ({ errors }) => console.error("Validation failed:", errors)
});

License

MIT © 2025 Scott Lott

Made with ❤️ by a developer who misses Rust's enums in TypeScript