@rustable/enum
v0.4.10
Published
Rust-inspired pattern matching and type-safe error handling for TypeScript. Includes Option<T> for null-safety and Result<T, E> for error handling, with comprehensive pattern matching support
Downloads
152
Maintainers
Readme
@rustable/enum
A TypeScript implementation of Rust-style pattern matching, Option, and Result types. This package provides type-safe alternatives to null/undefined and error handling patterns in TypeScript.
✨ Features
- 🔒 Type-safe Enums: Full TypeScript support with proper type inference
- 🎭 Pattern Matching: Exhaustive pattern matching with compile-time checks
- 🔄 Option Type: Safe handling of optional values without null/undefined
- ✅ Result Type: Elegant error handling with Ok/Err variants
- 🎯 Let Pattern: Type-safe conditional execution with proper inference
- 🔗 Method Chaining: Rich set of combinators for value transformation
- 🛡️ Null Safety: Complete elimination of null/undefined related bugs
- 🏭 Factory API: Easy creation of custom enums with type checking
- 🔄 Async Support: First-class support for async operations with Result type
- 📦 Zero Dependencies: No external runtime dependencies
📦 Installation
npm install @rustable/enum
# or
yarn add @rustable/enum
# or
pnpm add @rustable/enum📚 Core Components
Pattern Matching (enum.ts)
Implements Rust-style pattern matching for TypeScript through the Enum base class and variant system.
import { Enum } from '@rustable/enum';
// Define an enum with proper type parameter
class MyEnum extends Enum<typeof MyEnum> {
static Variant1(value: string) {
return new MyEnum('Variant1', value);
}
static Variant2(value: number) {
return new MyEnum('Variant2', value);
}
}
const value = MyEnum.Variant1('test');
// Type-safe pattern matching requires all variants to be handled
value.match({
Variant1: (str) => console.log(str),
Variant2: (num) => console.log(num),
});
// Conditional execution with let pattern matching
value.let('Variant1', {
if: (str) => console.log(`Found Variant1: ${str}`),
else: () => console.log('Not Variant1'),
});Custom Enums (Enums.create)
Create type-safe enums with custom variants and parameters using Enums.create. This provides a more concise and type-safe way to define enums compared to class extension.
import { Enums } from '@rustable/enum';
// Define enum with different variant signatures
const UserState = Enums.create('UserState', {
LoggedOut: () => {},
LoggedIn: (userId: string, role: string) => {},
Suspended: (reason: string) => {},
});
// Or
const UserState = Enums.create({
LoggedOut: () => {},
LoggedIn: (userId: string, role: string) => {},
Suspended: (reason: string) => {},
});
// Create instances with type checking
const state1 = UserState.LoggedOut();
const state2 = UserState.LoggedIn('user123', 'admin');
const state3 = UserState.Suspended('violation');
// Type-safe pattern matching (must handle all variants)
state2.match({
LoggedIn: (userId, role) => console.log(`User ${userId} is ${role}`),
Suspended: (reason) => console.log(`Account suspended: ${reason}`),
LoggedOut: () => console.log('Please log in'),
});
// or
state2.match({
LoggedIn: (userId, role) => console.log(`User ${userId} is ${role}`),
_: () => console.log('Not logged in'),
});
// Conditional execution with let pattern matching
state2.letLoggedIn({
if: (userId, role) => console.log(`User ${userId} is ${role}`),
else: () => console.log('Not logged in'),
});
// Type-safe variant checking
if (state2.isLoggedIn()) {
// TypeScript knows this is a LoggedIn variant
console.log(state2.unwrapTuple()); // ['user123', 'admin']
}
// Clone support
const clonedState = state2.clone();Type Definitions
// Define variant signatures
type UserStateVariants = {
LoggedOut: () => void;
LoggedIn: (userId: string, role: string) => void;
Suspended: (reason: string) => void;
};
const userStateVariants: UserStateVariants = {
LoggedOut: () => {},
LoggedIn: (userId: string, role: string) => {},
Suspended: (reason: string) => {},
};
// Create enum with type information
const UserState = Enums.create<UserStateVariants>('UserState', userStateVariants);
// Type information is preserved
type UserStateEnum = EnumInstance<UserStateVariants>;Best Practices for Custom Enums
- Name Your Enums: Always provide a name parameter to
Enums.createfor better debugging and error messages - Type Parameters: Use explicit type parameters when complex type inference is needed
- Variant Arguments: Use underscore prefix for unused parameters to show intent
- Pattern Matching: Always handle all variants or provide a default case
- Type-safe Checks: Use generated
isVariant()methods instead of string-basedis()
Option Type (option.ts)
Represents an optional value that may or may not be present. A type-safe alternative to null/undefined.
import { Some, None } from '@rustable/enum';
function divide(a: number, b: number): Option<number> {
return b === 0 ? None : Some(a / b);
}
const result = divide(10, 2)
.map((n) => n * 2) // Transform if Some
.unwrapOr(0); // Default if NoneResult Type (result.ts)
Represents either success (Ok) or failure (Err). A type-safe way to handle operations that may fail.
import { Result } from '@rustable/enum';
// Basic usage
function validateAge(age: number): Result<number, Error> {
return age >= 0 && age <= 120 ? Result.Ok(age) : Result.Err(new Error('Invalid age'));
}
const result = validateAge(25)
.map((age) => age + 1) // Transform if Ok
.unwrapOr(0); // Default if Err
// Async operation handling
const asyncResult = await Result.fromAsync<string, Error>(
fetch('https://api.example.com/data').then((res) => res.text()),
);
if (asyncResult.isOk()) {
console.log('Got data:', asyncResult.unwrap());
} else {
console.error('Failed:', asyncResult.unwrapErr().message);
}
// Function error wrapping
const parseJSON = Result.fromFn<any, Error, [string]>(JSON.parse);
// Success case
const parseResult = parseJSON('{"key": "value"}');
if (parseResult.isOk()) {
console.log('Parsed:', parseResult.unwrap());
}
// Error case
const errorResult = parseJSON('invalid json');
if (errorResult.isErr()) {
console.error('Parse failed:', errorResult.unwrapErr().message);
}📖 Usage
Pattern Matching
// All variants must be handled
enum.match({
Variant1: (value) => handleVariant1(value),
Variant2: (value) => handleVariant2(value),
});
// Conditional execution with let pattern
enum.letVariant1({
if: (value) => handleVariant1(value),
else: () => handleOtherCases(),
});Option Type
const opt = Some(5);
// Pattern matching
opt.match({
Some: (value) => console.log(value),
None: () => console.log('No value'),
});
// Method chaining
opt
.map((n) => n * 2)
.filter((n) => n > 5)
.unwrapOr(0);Result Type
const result = Result.Ok(42);
// Pattern matching
result.match({
Ok: (value) => console.log(`Success: ${value}`),
Err: (error) => console.error(`Error: ${error.message}`),
});
// Error handling
result.mapErr((err) => new Error(`Wrapped: ${err.message}`)).unwrapOr(0);📝 Best Practices
- Use
Optioninstead of null/undefined for optional values - Use
Resultfor operations that may fail - Leverage pattern matching for exhaustive case handling
- Chain methods to transform values safely
- Always handle both success and failure cases explicitly
📝 Notes
- All types are immutable and side-effect free
- Pattern matching is exhaustive by default
- Methods follow Rust's naming conventions
- Full TypeScript type inference support
📄 License
MIT © illuxiza
