tuple-result is a minimal, functional, and tree-shakable Result library for TypeScript that prioritizes simplicity and serialization.
- 🔮 Simple, declarative API: Intuitive array destructuring with full type safety
- 🍃 Lightweight & Tree Shakable: Function-based design with ~150B core
- ⚡ High Performance: Minimal overhead with just a 3-element array
- 🔍 Easy Serialization: Simple array format perfect for wire transmission
- 📦 Zero Dependencies: Standalone library ensuring ease of use in various environments
- 🔧 Functional Helpers: Powerful map, unwrap, and utility functions
- 🧵 Type Safe: Full TypeScript support with literal types and type guards
📚 Examples
🌟 Motivation
Build a minimal, functional Result library that prioritizes simplicity and serialization. While libraries like ts-results and neverthrow offer robust features, their class-based implementations can create challenges with serialization and bundle size. tuple-result provides a functional alternative using simple arrays - combining minimal overhead (~150B core), easy serialization for APIs and frameworks like React Router, and helper functions while adhering to the KISS principle.
⚖️ Alternatives
📖 Usage
tuple-result provides a simple approach to error handling. Here's how to use it:
1. Creating Results
import { Err, Ok } from 'tuple-result';
const success = Ok(42);
const failure = Err('Something went wrong');
2. Working with Results
if (success.isOk()) {
console.log(success.value);
}
const [ok, error, value] = success;
if (ok) {
console.log(value);
}
const value = success.unwrap();
3. Wrapping Functions
import { t, tAsync } from 'tuple-result';
const result = t(() => JSON.parse('invalid'));
const asyncResult = await tAsync(fetch('/api/data'));
import { unwrapErr, unwrapOr } from 'tuple-result';
const value = unwrapOr(failure, 0);
5. Transforming Results
import { mapErr, mapOk } from 'tuple-result';
const doubled = mapOk(success, (x) => x * 2);
const wrapped = mapErr(failure, (e) => `Error: ${e}`);
6. Pattern Matching
import { match } from 'tuple-result';
const message = match(result, {
ok: (value) => `Success: ${value}`,
err: (error) => `Error: ${error}`
});
const processed = match(result, {
ok: (user) => ({ ...user, displayName: user.name.toUpperCase() }),
err: (error) => ({ id: 0, name: 'Unknown', error: error.message })
});
7. Serialization
const serialized = success.toArray();
const reconstructed = fromArray(serialized);
📚 API Reference
Core Functions
Ok<T, E>(value: T): OkResult<T, E>
Creates a successful result containing the given value.
const result = Ok(42);
console.log(result.unwrap());
console.log(result.isOk());
Err<T, E>(error: E): ErrResult<T, E>
Creates an error result containing the given error.
const result = Err('Something went wrong');
console.log(result.isErr());
console.log(result.error);
Type Guards
isOk<T, E>(result: TResult<T, E>): result is OkResult<T, E>
Type guard to check if a result is successful.
if (isOk(result)) {
console.log(result.unwrap());
}
isErr<T, E>(result: TResult<T, E>): result is ErrResult<T, E>
Type guard to check if a result is an error.
if (isErr(result)) {
console.log(result.error);
}
Unwrapping Functions
unwrap<T, E>(result: TResult<T, E>): T
Extracts the value from a result, throwing if it's an error.
try {
const value = unwrap(success);
} catch (error) {
}
unwrapOk<T, E>(result: TResult<T, E>): T
Extracts the value from an Ok result, throwing if it's an error.
const value = unwrapOk(success);
unwrapErr<T, E>(result: TResult<T, E>): E
Extracts the error from an Err result, throwing if it's successful.
const error = unwrapErr(failure);
unwrapOr<T, E>(result: TResult<T, E>, defaultValue: T): T
Extracts the value from a result, returning a default if it's an error.
const value = unwrapOr(failure, 0);
unwrapOrNull<T, E>(result: TResult<T, E>): T | null
Extracts the value from a result, returning null if it's an error.
const value = unwrapOrNull(failure);
Transformation Functions
mapOk<T, E, U>(result: TResult<T, E>, mapFn: (value: T) => U): TResult<U, E>
Maps the value inside an Ok result using the provided function.
const doubled = mapOk(Ok(21), (x) => x * 2);
mapErr<T, E, F>(result: TResult<T, E>, mapFn: (error: E) => F): TResult<T, F>
Maps the error inside an Err result using the provided function.
const wrapped = mapErr(Err(404), (code) => `HTTP ${code}`);
Pattern Matching Functions
match<T, E, R>(result: TResult<T, E> | TResultArray<T, E>, handlers: { ok: (value: T) => R; err: (error: E) => R }): R
Pattern matches on a result, calling the appropriate handler. Similar to Rust's match! macro.
const message = match(result, {
ok: (value) => `Success: ${value}`,
err: (error) => `Error: ${error}`
});
Function Wrappers
t<T, Args extends any[]>(fn: (...args: Args) => T, ...args: Args): TResult<T, unknown>
Wraps a synchronous function call in a Result.
const result = t(() => JSON.parse('invalid'));
const safeDivide = (a: number, b: number) => t(() => a / b, a, b);
tAsync<T>(promise: Promise<T>): Promise<TResult<T, unknown>>
Wraps a Promise in a Result.
const result = await tAsync(fetch('/api/data'));
Serialization Functions
toArray() (Instance Method)
Converts a result to a plain array for serialization.
const result = Ok(42);
const serialized = result.toArray();
fromArray<T, E>(array: TResultArray<T, E>): TResult<T, E>
Creates a result instance from a plain array.
const result = fromArray([true, undefined, 42]);
❓ FAQ
Why both TResult and TResultArray?
TResultArray is a subset of TResult - same array structure, but TResult adds convenience methods.
TResult: Full-featured classes with .isOk(), .unwrap(), .value methods
TResultArray: Plain arrays perfect for serialization (React Router, APIs, JSON)
Key benefit: All helper functions work with both types seamlessly.
const classResult = Ok('hello');
const arrayResult = [true, undefined, 'hello'] as const;
isOk(classResult);
isOk(arrayResult);
When do I use each?
Use TResult by default. You get TResultArray from:
- React Router loaders:
useLoaderData()
- JSON parsing:
JSON.parse(response)
- API responses
For serialization: result.toArray() → send over network → use helpers directly on received arrays or deserialize using fromArray(result).
No conversion needed - helpers work with both!
💡 Resources / References