@fx-ts/fx
v0.0.5
Published
A functional result type for TypeScript
Maintainers
Readme
fx - Functional Result Type Library
A lightweight, type-safe Result type library for TypeScript that provides a functional approach to error handling.
Features
- 🎯 Type-safe error handling with Result types
- 🏷️ Error tagging for selective error handling
- ⚡ Async support with ResultAsync
- 🔧 Functional transformations (map, flatMap, andThen)
- 🎲 Combinators for working with multiple Results
- 🛡️ Safe execution with tryCatch utilities
- 📦 Zero dependencies - pure TypeScript
Installation
npm install @fx-ts/fxQuick Start
import { fx } from 'fx';
// Create a function that might fail
const divide = fx.fn((a: number, b: number) => {
if (b === 0) {
return fx.err(new Error('Division by zero'));
}
return fx.ok(a / b);
});
// Use the function
const result = divide(10, 2);
// result: [null, 5] (success)
const errorResult = divide(10, 0);
// errorResult: [Error: Division by zero, null] (error)
// Transform results
const doubled = fx.map(result, x => x * 2);
// doubled: [null, 10]
// Chain operations
const chained = fx.andThen(result, x => {
if (x > 3) {
return fx.ok(`Big number: ${x}`);
}
return fx.err(new Error('Number too small'));
});
// chained: [null, "Big number: 5"]Core Concepts
Result Type
A Result is a tuple [error, value] where:
[null, value]represents success[error, null]represents failure
type Result<R, E> = [null, R] | [E, null];
type ResultAsync<R, E> = Promise<Result<R, E>>;Creating Results
import { fx } from 'fx';
// Success
const success = fx.ok(42);
// [null, 42]
// Error
const error = fx.err(new Error('Something went wrong'));
// [Error: Something went wrong, null]Safe Function Execution
import { fx } from 'fx';
// Wrap any function (sync or async)
const safeFunction = fx.fn(() => {
// This function might throw
const random = Math.random();
if (random < 0.5) {
throw new Error('Random failure');
}
return random;
});
const result = safeFunction();
// Always returns a Result, never throwsAPI Reference
Core Functions
fx.ok(value)
Creates a success Result.
fx.err(error)
Creates an error Result.
fx.fn(function)
Wraps a function to return Results instead of throwing.
Transformations
fx.map(result, fn)
Transform the success value of a Result.
const result = fx.ok(5);
const doubled = fx.map(result, x => x * 2);
// [null, 10]fx.andThen(result, fn)
Chain operations on a Result (flatMap).
const result = fx.ok(5);
const chained = fx.andThen(result, x => {
if (x > 3) return fx.ok(x * 2);
return fx.err(new Error('Too small'));
});fx.mapError(result, fn)
Transform the error of a Result.
const result = fx.err(new Error('Original error'));
const transformed = fx.mapError(result, err => `Custom: ${err.message}`);
// ["Custom: Original error", null]fx.orElse(result, fn)
Provide a fallback when Result is an error.
const result = fx.err(new Error('Failed'));
const fallback = fx.orElse(result, err => fx.ok('Default value'));
// [null, "Default value"]Unwrapping
fx.unwrap(result)
Extract the value or throw the error.
const result = fx.ok(42);
const value = fx.unwrap(result); // 42
const errorResult = fx.err(new Error('Oops'));
const value = fx.unwrap(errorResult); // throws Error: Oopsfx.unwrapOr(result, defaultValue)
Extract the value or return a default.
const result = fx.err(new Error('Failed'));
const value = fx.unwrapOr(result, 0); // 0fx.unwrapOrElse(result, fn)
Extract the value or compute a default.
const result = fx.err(new Error('Failed'));
const value = fx.unwrapOrElse(result, err => err.message.length); // 6Type Guards
fx.isOk(result)
Check if Result is success.
fx.isErr(result)
Check if Result is error.
Error Tagging
Error tagging allows you to categorize errors and handle them selectively in your pipelines.
fx.tagError(tag, error)
Create a tagged error.
const taggedError = fx.tagError("VALIDATION_ERROR", "Invalid input");
// { tag: "VALIDATION_ERROR", error: "Invalid input" }fx.errTagged(tag, error)
Create a Result with a tagged error.
const result = fx.errTagged("NETWORK_ERROR", "Connection failed");
// [{ tag: "NETWORK_ERROR", error: "Connection failed" }, null]fx.catchByTag(result, tag, handler)
Catch errors with a specific tag and handle them.
const result = fx.errTagged("VALIDATION_ERROR", "Invalid email");
const handled = fx.catchByTag(
result,
"VALIDATION_ERROR",
(error) => fx.ok(`Handled: ${error}`)
);
// [null, "Handled: Invalid email"]fx.catchByTags(result, tags, handler)
Catch errors with any of the specified tags.
const result = fx.errTagged("NETWORK_ERROR", "Connection failed");
const handled = fx.catchByTags(
result,
["VALIDATION_ERROR", "NETWORK_ERROR"],
(error) => fx.ok(`Handled: ${error}`)
);
// [null, "Handled: Connection failed"]fx.hasTag(error, tag)
Check if an error has a specific tag.
const taggedError = fx.tagError("VALIDATION_ERROR", "Invalid input");
const hasTag = fx.hasTag(taggedError, "VALIDATION_ERROR"); // trueCombinators
fx.all(results)
Combine multiple Results. Returns success only if all are successful.
const results = [fx.ok(1), fx.ok(2), fx.ok(3)];
const combined = fx.all(results);
// [null, [1, 2, 3]]fx.any(results)
Return the first successful Result, or the last error if all fail.
fx.zip(result1, result2)
Combine two Results into a tuple.
const result1 = fx.ok(1);
const result2 = fx.ok('hello');
const zipped = fx.zip(result1, result2);
// [null, [1, "hello"]]fx.tryCatch(fn)
Execute a function and return a Result.
const result = fx.tryCatch(() => {
const value = JSON.parse('invalid json');
return value;
});
// [SyntaxError: Unexpected token i in JSON at position 0, null]Async Support
All functions work with async Results:
import { fx } from 'fx';
const asyncResult = fx.fn(async () => {
const response = await fetch('/api/data');
if (!response.ok) {
return fx.err(new Error('API failed'));
}
return fx.ok(await response.json());
});
const transformed = await fx.mapAsync(asyncResult(), data => data.name);Best Practices
- Use Results for expected errors - Don't use Results for unexpected errors (bugs)
- Chain operations - Use
fx.andThento build complex workflows - Handle errors explicitly - Use
fx.mapErrorandfx.orElseto handle errors gracefully - Use combinators - Use
fx.all,fx.zipfor working with multiple Results - Prefer unwrapOr over unwrap - Avoid throwing in production code
Examples
API Call with Error Handling
import { fx } from 'fx';
const fetchUser = fx.fn(async (id: number) => {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
return fx.err(new Error(`HTTP ${response.status}`));
}
return fx.ok(await response.json());
});
const getUserName = fx.andThen(fetchUser(123), user => {
if (!user.name) {
return fx.err(new Error('User has no name'));
}
return fx.ok(user.name);
});
const result = await getUserName();Validation Pipeline
import { fx } from 'fx';
const validateEmail = (email: string) => {
if (!email.includes('@')) {
return fx.err(new Error('Invalid email'));
}
return fx.ok(email);
};
const validateAge = (age: number) => {
if (age < 18) {
return fx.err(new Error('Too young'));
}
return fx.ok(age);
};
const validateUser = (email: string, age: number) => {
const emailResult = validateEmail(email);
const ageResult = validateAge(age);
return fx.all([emailResult, ageResult]);
};
const result = validateUser('[email protected]', 25);
// [null, ["[email protected]", 25]]Error Tagging Pipeline
import { fx } from 'fx';
const validateInput = (input: string) => {
if (!input) return fx.errTagged("VALIDATION_ERROR", "Input is required");
if (input.length < 3) return fx.errTagged("VALIDATION_ERROR", "Input too short");
return fx.ok(input);
};
const processData = (input: string) => {
if (input === "error") return fx.errTagged("PROCESSING_ERROR", "Failed to process");
return fx.ok(input.toUpperCase());
};
const saveData = (data: string) => {
if (data === "ERROR") return fx.errTagged("DATABASE_ERROR", "Failed to save");
return fx.ok(`Saved: ${data}`);
};
// Pipeline with selective error handling
const pipeline = (input: string) => {
const step1 = validateInput(input);
const step2 = fx.catchByTag(
step1,
"VALIDATION_ERROR",
(error) => fx.ok(`Validation failed: ${error}`)
);
const step3 = processData(fx.unwrap(step2));
const step4 = fx.catchByTag(
step3,
"PROCESSING_ERROR",
(error) => fx.ok(`Processing failed: ${error}`)
);
return saveData(fx.unwrap(step4));
};
const result = pipeline("hello");
// [null, "Saved: HELLO"]
const errorResult = pipeline("error");
// [null, "Saved: Processing failed: Failed to process"]License
MIT
