JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 4
  • Score
    100M100P100Q78279F
  • License MIT

Rust-inspired error handling and functional utilities for TypeScript

Package Exports

  • @locci/rusty-utils
  • @locci/rusty-utils/option
  • @locci/rusty-utils/result

Readme

Rusty Utils

npm version Build Status TypeScript License: MIT

Rust-inspired error handling and functional programming utilities for TypeScript. Bring the power of Rust's Result<T, E> and Option<T> types to your TypeScript projects with full type safety and ergonomic APIs.

🚀 Features

  • Result Type: Rust-style error handling without exceptions
  • Option Type: Safe nullable value handling
  • Functional Utilities: Pipe, compose, curry, and more
  • Full Type Safety: Comprehensive TypeScript support
  • Zero Dependencies: Lightweight and focused
  • Tree Shakeable: Import only what you need
  • Extensive Testing: 100% test coverage

📦 Installation

npm install @locci/rusty-utils
yarn add @locci/rusty-utils
pnpm add @locci/rusty-utils

🔧 Usage

Result Type - Error Handling

Replace try-catch blocks and error-prone nullable returns with expressive, type-safe error handling:

import {
  ok,
  err,
  andThenResult,
  matchResult,
  Result,
} from "@locci/rusty-utils";

interface User {
  email: string;
  age: number;
}

// Define your domain errors
type ValidationError =
  | { type: "InvalidEmail"; email: string }
  | { type: "TooYoung"; age: number };

// Functions that can fail return Result<T, E>
const validateEmail = (email: string): Result<string, ValidationError> => {
  return email.includes("@") ? ok(email) : err({ type: "InvalidEmail", email });
};

const validateAge = (age: number): Result<number, ValidationError> => {
  return age >= 18 ? ok(age) : err({ type: "TooYoung", age });
};

// Chain operations safely
const createUser = (email: string, age: number) =>
  andThenResult(validateEmail(email), (validEmail) =>
    andThenResult(validateAge(age), (validAge) =>
      ok({ email: validEmail, age: validAge })
    )
  );

// Handle results with pattern matching
const handleResult = (result: Result<User, ValidationError>) =>
  matchResult(result, {
    ok: (user) => `Created user: ${user.email}`,
    err: (error) => {
      switch (error.type) {
        case "InvalidEmail":
          return `Invalid email: ${error.email}`;
        case "TooYoung":
          return `Too young: ${error.age}`;
      }
    },
  });

// Usage
const result = createUser("john@example.com", 20);
console.log(handleResult(result)); // "Created user: john@example.com"

Option Type - Nullable Safety

Handle nullable values without null/undefined errors:

import { some, none, Option, mapOption, andThenOption, unwrapOrOption } from "@locci/rusty-utils";

interface User {
  id: string;
  name: string;
  email?: string;
}

const users: User[] = [
  { id: "1", name: "Alice", email: "alice@example.com" },
  { id: "2", name: "Bob" },
];

// Safe array access
const findUser = (id: string): Option<User> => {
  const user = users.find((u) => u.id === id);
  return user ? some(user) : none;
};

// Chain operations safely
const getUserEmail = (id: string): Option<string> =>
  andThenOption(findUser(id), (user) => (user.email ? some(user.email) : none));

// Transform values safely
const getEmailDomain = (id: string): Option<string> =>
  mapOption(getUserEmail(id), (email) => email.split("@")[1]);

// Provide defaults
const displayEmail = (id: string): string =>
  unwrapOrOption(getUserEmail(id), "No email provided");

console.log(displayEmail("1")); // "alice@example.com"
console.log(displayEmail("2")); // "No email provided"

Functional Programming Utilities

import { pipe, compose, memoize, debounce, groupBy } from "@locci/rusty-utils";

interface User {
  id: string;
  name: string;
  email?: string;
}

const users: User[] = [
  { id: "1", name: "Alice", email: "alice@example.com" },
  { id: "2", name: "Bob" },
];

// Data transformation pipelines
const processUsers = (users: User[]) =>
  pipe(
    users,
    (users) => users.filter((u) => u.age >= 18),
    (users) => users.map((u) => ({ ...u, isAdult: true })),
    (users) => groupBy(users, (u) => u.department)
  );

// Function composition
const addTax = (rate: number) => (price: number) => price * (1 + rate);
const formatCurrency = (amount: number) => amount.toFixed(2) as unknown as number;
const calculateTotal = compose(formatCurrency, addTax(0.1));

console.log(calculateTotal(100)); // "$110.00"

// Performance optimizations
const expensiveCalculation = memoize((x: number) => {
  // Some expensive operation
  return x * x * x;
});

const debouncedSave = debounce((data: any) => {
  // Save to database
}, 300);

📚 API Reference

Result<T, E>

Function Description
ok(value) Create a successful Result
err(error) Create a failed Result
isOk(result) Type guard for Ok results
isErr(result) Type guard for Err results
map(result, fn) Transform Ok value
mapErr(result, fn) Transform Err value
andThen(result, fn) Chain Result operations
orElse(result, fn) Handle Err cases
match(result, patterns) Pattern matching
unwrapOr(result, default) Get value or default
tryCatch(fn) Wrap throwing function

Option

Function Description
some(value) Create an Option with value
none Represents no value
isSome(option) Type guard for Some
isNone(option) Type guard for None
fromNullable(value) Create from nullable
map(option, fn) Transform Some value
andThen(option, fn) Chain Option operations
filter(option, predicate) Filter based on predicate
unwrapOr(option, default) Get value or default

Functional Utilities

Function Description
pipe(value, ...fns) Left-to-right function composition
compose(...fns) Right-to-left function composition
memoize(fn) Cache function results
debounce(fn, delay) Delay function execution
throttle(fn, interval) Limit function call rate
groupBy(array, keyFn) Group array elements
chunk(array, size) Split array into chunks
unique(array) Remove duplicates

🏗️ Integration Examples

With Express.js

import express from "express";
import { tryCatchAsync, matchResult } from "@locci/rusty-utils";

app.get("/users/:id", async (req, res) => {
  const result = await tryCatchAsync(() => userService.findById(req.params.id));

  const response = matchResult(result, {
    ok: (user) => res.json(user),
    err: (error) => res.status(404).json({ error: error.message }),
  });
});

With NestJS

import { Injectable } from "@nestjs/common";
import { Result, ok, err, andThenResult } from "@locci/rusty-utils";

@Injectable()
export class UserService {
  async createUser(
    data: CreateUserDto
  ): Promise<Result<User, ValidationError>> {
    return andThenResult(this.validateInput(data), (validData) =>
      andThenResult(this.checkEmailExists(validData.email), () =>
        this.saveUser(validData)
      )
    );
  }
}

With React

import React from "react";
import { Option, matchOption } from "@locci/rusty-utils";

interface Props {
  user: Option<User>;
}

const UserProfile: React.FC<Props> = ({ user }) => {
  return matchOption(user, {
    some: (u) => (
      <div>
        <h1>{u.name}</h1>
        <p>{u.email}</p>
      </div>
    ),
    none: () => <div>No user found</div>,
  });
};

🎯 Why Use This Library?

Before (Traditional Error Handling)

// Prone to runtime errors, unclear error types
async function getUser(id: string) {
  try {
    const user = await userRepository.findById(id);
    if (!user) {
      throw new Error("User not found");
    }

    if (!user.email) {
      throw new Error("User has no email");
    }

    return user.email.split("@")[1];
  } catch (error) {
    // What type of error is this?
    // Did findById throw? Was user null? Was email null?
    throw error;
  }
}

After (With @locci/rusty-utils)

// Type-safe, explicit error handling, composable
async function getUser(id: string): Promise<Result<string, UserError>> {
  return andThenAsync(
    tryCatchAsync(() => userRepository.findById(id)),
    (user) =>
      user
        ? andThen(fromNullable(user.email), (email) =>
            ok(email.split("@")[1])
          ) ?? err({ type: "NoEmail", userId: id })
        : err({ type: "NotFound", userId: id })
  );
}

🤝 Contributing

Contributions are welcome! Please read our Contributing Guide for details.

📄 License

MIT License. See LICENSE file for details.

🙏 Acknowledgments

Inspired by:


Built with ❤️ for the Locci Cloud and the TypeScript community