0.4.10 • Published 6 months ago

@rustable/enum v0.4.10

Weekly downloads
-
License
MIT
Repository
github
Last release
6 months ago

@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

  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.

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.

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

  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

0.4.10

6 months ago

0.4.9

6 months ago

0.4.8

6 months ago

0.4.7

9 months ago

0.4.4

9 months ago

0.4.2

9 months ago

0.4.1

9 months ago

0.4.0

9 months ago

0.3.17

9 months ago

0.3.16

9 months ago

0.3.15

9 months ago

0.3.14

9 months ago

0.3.13

9 months ago

0.3.12

9 months ago

0.3.11

9 months ago

0.3.10

9 months ago

0.3.9

9 months ago

0.3.8

10 months ago

0.3.7

10 months ago

0.3.6

10 months ago

0.3.5

10 months ago

0.3.4

10 months ago

0.3.3

10 months ago

0.3.2

10 months ago

0.3.1

10 months ago

0.3.0

10 months ago

0.2.11

10 months ago

0.2.10

10 months ago

0.2.9

10 months ago

0.2.8

10 months ago

0.2.7

10 months ago

0.2.6

10 months ago

0.2.5

10 months ago

0.2.4

10 months ago

0.2.3

10 months ago

0.2.2

10 months ago

0.2.1

10 months ago

0.2.0

10 months ago

0.1.7

10 months ago

0.1.6

10 months ago

0.1.5

10 months ago

0.1.4

11 months ago

0.1.3

11 months ago

0.1.2

11 months ago

0.1.1

11 months ago