@rustable/enum
Version:
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
275 lines (211 loc) • 7.68 kB
Markdown
# @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
```bash
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.
```typescript
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.
```typescript
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
```typescript
// 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
1. **Name Your Enums**: Always provide a name parameter to `Enums.create` for better debugging and error messages
2. **Type Parameters**: Use explicit type parameters when complex type inference is needed
3. **Variant Arguments**: Use underscore prefix for unused parameters to show intent
4. **Pattern Matching**: Always handle all variants or provide a default case
5. **Type-safe Checks**: Use generated `isVariant()` methods instead of string-based `is()`
### Option Type (`option.ts`)
Represents an optional value that may or may not be present. A type-safe alternative to null/undefined.
```typescript
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 None
```
### Result Type (`result.ts`)
Represents either success (Ok) or failure (Err). A type-safe way to handle operations that may fail.
```typescript
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
```typescript
// 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
```typescript
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
```typescript
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
1. Use `Option` instead of null/undefined for optional values
2. Use `Result` for operations that may fail
3. Leverage pattern matching for exhaustive case handling
4. Chain methods to transform values safely
5. 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