npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

fates

v0.0.3

Published

Making typescript more reliable

Downloads

18

Readme

Fates 🔮 - Tame Your TypeScript Destiny!

Hey there, fellow coder! 👋 Tired of wrestling with unpredictable outcomes in your TypeScript projects? Say hello to Fates, your new best friend in the battle against uncertainty!

npm version License: ISC

Table of Contents

Introduction

Fates is a powerful TypeScript library that revolutionizes error handling and data flow management. By introducing a Result type inspired by functional programming paradigms, Fates enables developers to write more predictable, maintainable, and expressive code.

Key Features

  • 🛡️ Type-Safe Error Handling: Leverage TypeScript's type system for robust error management.
  • 🔗 Elegant Function Composition: Chain operations seamlessly, even with potential failures.
  • 🚀 First-Class Async Support: Handle asynchronous operations with grace and predictability.
  • 🧰 Comprehensive Utility Set: A rich collection of tools for manipulating and transforming Results.
  • 🏗️ Flexible Validation Framework: Construct complex, reusable validation pipelines with ease.
  • 🔄 Built-in Retry Mechanism: Handle transient failures in distributed systems effortlessly.
  • 🧩 Powerful Combinators: Combine and manipulate multiple Results with intuitive operators.

Installation

Choose your preferred package manager:

npm install fates
# or
yarn add fates
# or
pnpm add fates

Quick Start

Let's dive into a simple example to see Fates in action:

import { ok, err, Result } from 'fates';

function divideAndFormatCurrency(a: number, b: number): Result<string, string> {
  if (b === 0) return err("Division by zero");
  const result = a / b;
  return ok(`$${result.toFixed(2)}`);
}

const result = divideAndFormatCurrency(100, 3);

result.match({
  ok: (value) => console.log(`Formatted result: ${value}`),
  err: (error) => console.log(`Error occurred: ${error}`),
});
// Output: Formatted result: $33.33

Core Concepts

The Result Type

The foundation of Fates is the Result type:

type Result<T, E = Error> = Ok<T> | Err<E>;

It encapsulates either a success value (Ok<T>) or a failure value (Err<E>).

The Option Type

Fates also provides an Option type for representing values that may or may not exist:

type Option<T> = Ok<T> | Err<null>;

The Option type is useful for handling nullable values without resorting to null checks:

import { fromNullable, Option } from 'fates';

function findUser(id: number): Option<User> {
  const user = database.getUser(id);
  return fromNullable(user);
}

const userOption = findUser(1);
userOption.match({
  ok: (user) => console.log(`Found user: ${user.name}`),
  err: () => console.log("User not found"),
});

Ok and Err

Create success and failure results:

import { ok, err } from 'fates';

const success = ok(42);
const failure = err("Something went wrong");

console.log(success.isOk());  // true
console.log(failure.isErr()); // true

Essential Methods

Both Ok and Err provide powerful methods for working with results:

match

Pattern match on the result:

result.match({
  ok: (value) => console.log(`Success: ${value}`),
  err: (error) => console.log(`Error: ${error}`),
});

map and flatMap

Transform success values:

const doubled = ok(21).map(x => x * 2);  // Ok(42)
const maybeSquared = doubled.flatMap(x => x > 50 ? err("Too large") : ok(x * x));  // Err("Too large")

mapErr

Transform error values:

const newError = err("error").mapErr(e => new Error(e));  // Err(Error("error"))

unwrap and unwrapOr

Extract values (with caution):

const value = ok(42).unwrap();  // 42
const fallback = err("error").unwrapOr(0);  // 0

safeUnwrap

Extract raw values regardless of error

const value ok(41).safeUnwrap(); // 41
const fallback = err("error").safeUnwrap(); // "error"
const badfallback = err("some error").unwrap(); // Throws "some error"

Advanced Usage

Async Operations

Fates seamlessly integrates with asynchronous code:

import { fromPromise, AsyncResult } from 'fates';

async function fetchUserData(id: number): AsyncResult<User, Error> {
  return fromPromise(fetch(`https://api.example.com/users/${id}`).then(res => res.json()));
}

const result = await fetchUserData(1);
result.match({
  ok: (user) => console.log(`Welcome, ${user.name}!`),
  err: (error) => console.log(`Fetch failed: ${error.message}`),
});

Chaining and Composition

Elegantly chain operations that might fail:

import { chain } from 'fates';

const getUser = (id: number): Result<User, Error> => { /* ... */ };
const getUserPosts = (user: User): Result<Post[], Error> => { /* ... */ };
const formatPosts = (posts: Post[]): Result<string, Error> => { /* ... */ };

const formattedUserPosts = getUser(1)
  .flatMap(chain(getUserPosts))
  .flatMap(chain(formatPosts));

formattedUserPosts.match({
  ok: (formatted) => console.log(formatted),
  err: (error) => console.error(`Error: ${error.message}`),
});

Error Recovery and Transformation

Gracefully handle and transform errors:

import { recover, mapError } from 'fates';

const result = getUserData(1)
  .mapErr(error => `Failed to fetch user: ${error.message}`)
  .recover(error => ({ id: 0, name: 'Guest' }));

console.log(result.unwrap());  // Either user data or the guest user

Validation Pipelines

Build complex, reusable validation logic:

import { validate, validateAll } from 'fates';

const validateAge = (age: number) => validate(age, a => a >= 18, "Must be an adult");
const validateEmail = (email: string) => validate(email, e => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e), "Invalid email");

const validateUser = (user: { age: number, email: string }) =>
  validateAll(user, [
    u => validateAge(u.age),
    u => validateEmail(u.email)
  ]);

const result = validateUser({ age: 25, email: "[email protected]" });
result.match({
  ok: (user) => console.log(`Valid user: ${JSON.stringify(user)}`),
  err: (error) => console.error(`Validation failed: ${error}`),
});

Parsing and Type Conversion

Safely parse and convert data:

import { parseNumber, parseDate, parseJSON } from 'fates';

const num = parseNumber("42").unwrapOr(0);
const date = parseDate("2023-04-01").unwrapOr(new Date());
const config = parseJSON<Config>(jsonString).unwrapOr(defaultConfig);

Combining Results

Work with multiple results simultaneously:

import { all, any, sequenceObject } from 'fates';

const results = all([fetchUser(1), fetchPosts(1), fetchComments(1)]);
results.match({
  ok: ([user, posts, comments]) => console.log(`Fetched data for ${user.name}`),
  err: (error) => console.error(`One or more fetches failed: ${error}`),
});

const firstSuccess = any([unreliableServiceA(), unreliableServiceB(), unreliableServiceC()]);
firstSuccess.match({
  ok: (result) => console.log(`Got a successful result: ${result}`),
  err: (errors) => console.error(`All services failed: ${errors.join(', ')}`),
});

const userResult = sequenceObject({
  name: validateName(formData.name),
  email: validateEmail(formData.email),
  age: validateAge(formData.age)
});

Processing Pipelines

Create robust data processing flows:

import { pipeline } from 'fates';

const processOrder = pipeline(
  validateOrder,
  calculateTotalWithTax,
  applyDiscount,
  saveToDatabase,
  sendConfirmationEmail
);

const result = await processOrder(orderData);
result.match({
  ok: (confirmationId) => console.log(`Order processed, confirmation: ${confirmationId}`),
  err: (error) => console.error(`Order processing failed: ${error}`),
});

Retry Mechanisms

Handle transient failures in distributed systems:

import { retry } from 'fates';

const fetchWithRetry = retry(
  () => fetchFromUnreliableService(),
  3,  // max attempts
  1000  // delay between attempts (ms)
);

const result = await fetchWithRetry();
result.match({
  ok: (data) => console.log(`Fetched successfully: ${data}`),
  err: (error) => console.error(`All attempts failed: ${error}`),
});

API Reference

For a complete API reference, please visit our API Documentation.

Best Practices and Patterns

  • Prefer flatMap over map when chaining operations that return Result.
  • Use tap for side effects without changing the Result value.
  • Leverage validateAll for complex object validations.
  • Use AsyncResult consistently for asynchronous operations.
  • Prefer recover over unwrapOr for more flexible error handling.

Performance Considerations

Fates is designed with performance in mind, but here are some tips to optimize your use:

  • Avoid unnecessary map and flatMap chains when a single operation would suffice.
  • Use AsyncResult directly instead of wrapping Promise<Result<T, E>>.
  • Leverage all and any for parallel operations instead of sequential flatMap chains.

Migrating from Try-Catch

Fates offers a more expressive and type-safe alternative to traditional try-catch blocks. Here's a quick comparison:

// Traditional try-catch
try {
  const result = riskyOperation();
  console.log(result);
} catch (error) {
  console.error(error);
}

// With Fates
riskyOperation()
  .match({
    ok: (result) => console.log(result),
    err: (error) => console.error(error),
  });

Comparison with Other Libraries

Fates draws inspiration from functional programming concepts and libraries like Rust's Result type. However, it's tailored specifically for TypeScript, offering seamless integration with async/await and providing a rich set of utilities designed for real-world TypeScript applications.

Contributing

We welcome contributions to Fates! Whether you're fixing bugs, adding features, or improving documentation, your help is appreciated. Please check our Contribution Guidelines for more information.

License

Fates is licensed under the ISC License. See the LICENSE file for details.


Embrace the power of Fates and elevate your TypeScript projects to new heights of reliability and expressiveness!