
Research
Malicious npm Packages Impersonate Flashbots SDKs, Targeting Ethereum Wallet Credentials
Four npm packages disguised as cryptographic tools steal developer credentials and send them to attacker-controlled Telegram infrastructure.
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.
Result
, Option
, and pattern matchingmatch
and matchAsync
methodsTry
and TryInto
utilitiesnpm install iron-enum
# or
yarn add iron-enum
# or
pnpm add iron-enum
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}`
});
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()
});
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
});
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")
);
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)); // 5
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 | null
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)
});
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;
}
});
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() } });
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;
}
}
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 performance
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 matchingIn 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 OptionIn 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_
fallbackconst 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}`
});
}
}
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)
});
MIT © 2025 Scott Lott
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
1.6.3 July 11, 2025
FAQs
Rust like enums for Typescript
The npm package iron-enum receives a total of 4 weekly downloads. As such, iron-enum popularity was classified as not popular.
We found that iron-enum demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Four npm packages disguised as cryptographic tools steal developer credentials and send them to attacker-controlled Telegram infrastructure.
Security News
Ruby maintainers from Bundler and rbenv teams are building rv to bring Python uv's speed and unified tooling approach to Ruby development.
Security News
Following last week’s supply chain attack, Nx published findings on the GitHub Actions exploit and moved npm publishing to Trusted Publishers.