https://github.com/simonepriuli/fx
https://github.com/simonepriuli/fx
Last synced: 12 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/simonepriuli/fx
- Owner: simonepriuli
- Created: 2025-06-30T11:02:44.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2025-06-30T11:06:03.000Z (about 1 year ago)
- Last Synced: 2025-06-30T12:23:25.687Z (about 1 year ago)
- Language: TypeScript
- Size: 21.5 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
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
```bash
npm install @fx-ts/fx
```
## Quick Start
```typescript
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
```typescript
type Result = [null, R] | [E, null];
type ResultAsync = Promise>;
```
### Creating Results
```typescript
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
```typescript
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 throws
```
## API 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.
```typescript
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).
```typescript
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.
```typescript
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.
```typescript
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.
```typescript
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: Oops
```
#### `fx.unwrapOr(result, defaultValue)`
Extract the value or return a default.
```typescript
const result = fx.err(new Error('Failed'));
const value = fx.unwrapOr(result, 0); // 0
```
#### `fx.unwrapOrElse(result, fn)`
Extract the value or compute a default.
```typescript
const result = fx.err(new Error('Failed'));
const value = fx.unwrapOrElse(result, err => err.message.length); // 6
```
### Type 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.
```typescript
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.
```typescript
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.
```typescript
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.
```typescript
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.
```typescript
const taggedError = fx.tagError("VALIDATION_ERROR", "Invalid input");
const hasTag = fx.hasTag(taggedError, "VALIDATION_ERROR"); // true
```
### Combinators
#### `fx.all(results)`
Combine multiple Results. Returns success only if all are successful.
```typescript
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.
```typescript
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.
```typescript
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:
```typescript
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
1. **Use Results for expected errors** - Don't use Results for unexpected errors (bugs)
2. **Chain operations** - Use `fx.andThen` to build complex workflows
3. **Handle errors explicitly** - Use `fx.mapError` and `fx.orElse` to handle errors gracefully
4. **Use combinators** - Use `fx.all`, `fx.zip` for working with multiple Results
5. **Prefer unwrapOr over unwrap** - Avoid throwing in production code
## Examples
### API Call with Error Handling
```typescript
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
```typescript
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('user@example.com', 25);
// [null, ["user@example.com", 25]]
```
### Error Tagging Pipeline
```typescript
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