@kanzen/result v1.0.12
@kanzen/result
A minimal TypeScript implementation of Rust's Result type for elegant error handling without exceptions.
📋 Table of Contents
🌟 Introduction
@kanzen/result provides a robust alternative to traditional try/catch error handling in JavaScript and TypeScript. Inspired by Rust's Result type, it enables functional, type-safe error handling that makes your code more predictable and easier to reason about.
With this library, errors become first-class citizens in your code rather than exceptional flow-breaking events, allowing for more elegant composition of functions that might fail.
📦 Installation
# npm
npm install @kanzen/result
# yarn
yarn add @kanzen/result
# pnpm
pnpm add @kanzen/result✨ Key Features
- Type-safe error handling - Leverage TypeScript to ensure errors are handled properly
- Functional approach - Chain operations with clear success and error paths
- Synchronous and asynchronous support - Handle both sync and async operations with a consistent API
- Zero dependencies - Lightweight and focused implementation
- Comprehensive TypeScript types - Full type inference for both success and error values
🚀 Basic Usage
Synchronous Example
import { okSync, errSync, safeTrySync } from '@kanzen/result';
// Create a successful result
const success = okSync('Hello, world!');
console.log(success.unwrap()); // 'Hello, world!'
// Create a failed result
const failure = errSync(new Error('Something went wrong'));
console.log(failure.isErr()); // true
// Handle both cases with match
const message = failure.match({
ok: (value) => `Success: ${value}`,
err: (error) => `Error: ${error.message}`,
});
console.log(message); // 'Error: Something went wrong'
// An operation that might throw
function divideBy (a: number, b: number): number {
if (b === 0) {
throw new Error('Division by zero')
};
return a / b;
};
const safeDivideBy = safeTrySync(divideBy);
const result = safeDivideBy(10, 2)
.map(result => result * 2)
.andTee(result => console.log(`Result: ${result}`))
.unwrapOr(0);
console.log(result); // 10Asynchronous Example
import { ok, err, safeTry } from '@kanzen/result';
async function fetchUser(id: string): Promise<unknown> {
const response = await fetch(`https://api.example.com/users/${id}`);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
return await response.json();
}
// Fetch API with Result
const safeFetchUser = safeTry(fetchUser)
// Use the result
const value = await safeFetchUser('123')
.andThen((user) => {
// Only runs if fetchUser succeeded
return ok({ ...user, lastLogin: new Date() });
})
.orElse((error) => {
// Only runs if fetchUser failed
console.error(`Error fetching user: ${error.message}`);
return ok({ id: '0', name: 'Default User', lastLogin: null });
})
.unwrap();🔄 Migration from try/catch
Before
function divideNumbers(a: number, b: number) {
try {
if (b === 0) {
throw new Error('Division by zero');
}
return a / b;
} catch (error) {
console.error('Error dividing numbers:', error);
return null;
}
}
// Usage:
const result = divideNumbers(10, 0);
if (result === null) {
console.log('An error occurred');
} else {
console.log(`Result: ${result}`);
}After
import { safeTrySync } from '@kanzen/result';
// Declare a unsafe function
function divideNumbers(a: number, b: number): number {
if (b === 0) {
throw new Error("Division by zero");
}
return a / b;
}
// Make it safe function
const safeDivideNumbers = safeTrySync(
divideNumbers,
(error) => new Error(`Division error: ${String(error)}`), // optional
);
// Usage:
safeDivideNumbers(10, 2).match({
ok: (result) => console.log(`Result: ${result}`),
err: (error) => console.log(`An error occurred: ${error.message}`),
});📘 API Reference
Synchronous API
Creating Results
okSync<T>(value: T): Result<T, never>- Creates a successful Resultimport { okSync } from '@kanzen/result'; // Create a successful result with a string value const success = okSync('Operation completed'); console.log(success.isOk()); // true console.log(success.unwrap()); // 'Operation completed'errSync<E>(error: E): Result<never, E>- Creates a failed Resultimport { errSync } from '@kanzen/result'; // Create a failed result with an Error object const failure = errSync(new Error('Operation failed')); console.log(failure.isErr()); // true console.log(failure.unwrapOr('Default value')); // 'Default value'safeTrySync<T, E>(fn: () => T, fnErr?: (error: unknown) => E): Result<T, E>- Safely executes a function that might throw, wrapping the result in a Result typeimport { safeTrySync } from '@kanzen/result'; // Original function that may throw function divide(a: number, b: number): number { if (b === 0) throw new Error('Cannot divide by zero'); return a / b; } // Create a safe version of the function const safeDivide = safeTrySync(divide); // Use the safe version const result = safeDivide(10, 2); // Ok(5) const errorResult = safeDivide(10, 0); // Err(Error: Cannot divide by zero) // With custom error mapping const customSafeDivide = safeTrySync( divide, (error) => `Division error: ${String(error)}` );
Result Methods
isOk(): boolean- Checks if the Result is successfulimport { okSync, errSync } from '@kanzen/result'; const success = okSync(42); console.log(success.isOk()); // true const failure = errSync('Failed'); console.log(failure.isOk()); // falseisErr(): boolean- Checks if the Result is an errorimport { okSync, errSync } from '@kanzen/result'; const success = okSync(42); console.log(success.isErr()); // false const failure = errSync('Failed'); console.log(failure.isErr()); // trueunwrap(): T- Returns the value if Ok, throws the error if Errimport { okSync, errSync } from '@kanzen/result'; const success = okSync('Success value'); console.log(success.unwrap()); // 'Success value' const failure = errSync(new Error('Failure reason')); // failure.unwrap(); // This would throw the Error: 'Failure reason'unwrapOr<U>(fallback: U): T | U- Returns the value if Ok, or the fallback if Errimport { okSync, errSync } from '@kanzen/result'; const success = okSync(42); console.log(success.unwrapOr(0)); // 42 const failure = errSync('Error occurred'); console.log(failure.unwrapOr(0)); // 0match<R>({ ok, err }: { ok: (value: T) => R, err: (error: E) => R }): R- Pattern matches on the Result, applying the appropriate function based on the Result stateimport { okSync, errSync } from '@kanzen/result'; const success = okSync('Success message'); const resultMessage = success.match({ ok: (value) => `Operation succeeded: ${value}`, err: (error) => `Operation failed: ${error}`, }); console.log(resultMessage); // 'Operation succeeded: Success message' const failure = errSync('Error message'); const errorMessage = failure.match({ ok: (value) => `Operation succeeded: ${value}`, err: (error) => `Operation failed: ${error}`, }); console.log(errorMessage); // 'Operation failed: Error message'map<U>(fn: (value: T) => U): Result<U, E>- Maps the success value to a new value using the provided functionimport { okSync, errSync } from '@kanzen/result'; const success = okSync(5); const doubled = success.map(x => x * 2); console.log(doubled.unwrap()); // 10 const failure = errSync('Error'); const mappedFailure = failure.map(x => x * 2); // it will not execute because it's a failure console.log(mappedFailure.isErr()); // truemapErr<F>(fn: (error: E) => F): Result<T, F>- Maps the error value to a new error using the provided functionimport { okSync, errSync } from '@kanzen/result'; const success = okSync(5); const mappedSuccess = success.mapErr(e => new Error(String(e))); // it will not execute because it's a success const failure = errSync('Something went wrong'); const enhancedError = failure.mapErr(msg => new Error(`Error: ${msg}`)); console.log(enhancedError.unwrapOr('Default')); // 'Default' // If we were to unwrap, we'd get Error: Error: Something went wrongandThen<U, F>(fn: (value: T) => Result<U, F>): Result<U, E | F>- Chains a function that returns a Result, flattening the nested Resultimport { okSync, errSync } from '@kanzen/result'; // A function that returns a Result const parseNumber = (input: string) => { const num = parseInt(input, 10); return isNaN(num) ? errSync(new Error('Invalid number')) : okSync(num); }; // Chain operations with andThen const result = okSync('42') .andThen(parseNumber) .andThen(num => okSync(num * 2)); console.log(result.unwrap()); // 84 // Error handling is automatic const badResult = okSync('not a number') .andThen(parseNumber) .andThen(num => okSync(num * 2)); console.log(badResult.isErr()); // trueorElse<U, F>(fn: (error: E) => Result<U, F>): Result<T | U, F>- Handles errors by returning a new Resultimport { okSync, errSync } from '@kanzen/result'; const getUserById = (id: string) => { if (id === '1') { return okSync({ id: '1', name: 'Alice' }); } return errSync(new Error('User not found')); }; // Handle specific errors const result = getUserById('999') .orElse(error => { if (error.message === 'User not found') { return okSync({ id: '0', name: 'Guest User' }); } return errSync(error); }); console.log(result.unwrap()); // { id: '0', name: 'Guest User' }andTee(fn: (value: T) => unknown): Result<T, E>- Performs a side effect on success without changing the Result (tee/tap pattern)import { okSync, errSync } from '@kanzen/result'; const success = okSync(42); const sameResult = success.andTee(value => { console.log(`Processing value: ${value}`); // Side effect // This return value is ignored }); // sameResult is still Ok(42) console.log(sameResult.unwrap()); // 42orTee(fn: (error: E) => unknown): Result<T, E>- Performs a side effect on error without changing the Resultimport { okSync, errSync } from '@kanzen/result'; const failure = errSync(new Error('Operation failed')); const sameFailure = failure.orTee(error => { console.error(`Logging error: ${error.message}`); // Side effect // This return value is ignored }); // sameFailure is still Err(Error: Operation failed) console.log(sameFailure.isErr()); // true
Asynchronous API
Creating Async Results
ok<T>(value: T): ResultAsync<T, never>- Creates a successful ResultAsyncimport { ok } from '@kanzen/result'; const successAsync = ok('Async success'); console.log(await successAsync.isOk()); // trueerr<E>(error: E): ResultAsync<never, E>- Creates a failed ResultAsyncimport { err } from '@kanzen/result'; const failureAsync = err(new Error('Async error')); console.log(await failureAsync.isErr()); // truesafeTry<T, E>(fn: () => Promise<T>, fnErr?: (error: unknown) => E): ResultAsync<T, E>- Safely executes an async functionimport { safeTry } from '@kanzen/result'; // Original async function that may throw async function fetchUserData(id: string): Promise<{id: string, name: string}> { const response = await fetch(`https://api.example.com/users/${id}`); if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`); } return await response.json(); } // Create a safe version of the function const safeFetchUserData = safeTry(fetchUserData); // Use the safe version with async/await const userResult = await safeFetchUserData('123'); // Process the result let finalResult; if (await userResult.isOk()) { const userData = await userResult.unwrap(); console.log(`User: ${userData.name}`); finalResult = userData; } else { const error = await userResult.match({ ok: () => null, err: (e) => e }); console.error(`Failed to fetch user: ${error.message}`); finalResult = { id: '0', name: 'Unknown User' }; } // With custom error mapping const customSafeFetch = safeTry( fetchUserData, (error) => `API Error: ${String(error)}` );toAsync<T, E>(result: Result<T, E>): ResultAsync<T, E>- Converts a Result to a ResultAsyncimport { okSync, toAsync } from '@kanzen/result'; const syncResult = okSync(42); const asyncResult = toAsync(syncResult); const value = await asyncResult.unwrap(); // 42
ResultAsync Methods
ResultAsync implements all the methods from Result but returns promises or new ResultAsync instances:
isOk(): Promise<boolean>- Asynchronously checks if the ResultAsync is successfulimport { ok, err } from '@kanzen/result'; const success = ok(42); console.log(await success.isOk()); // true const failure = err('Failed'); console.log(await failure.isOk()); // falseisErr(): Promise<boolean>- Asynchronously checks if the ResultAsync is an errorimport { ok, err } from '@kanzen/result'; const success = ok(42); console.log(await success.isErr()); // false const failure = err('Failed'); console.log(await failure.isErr()); // trueunwrap(): Promise<T>- Asynchronously returns the value if Ok, rejects with the error if Errimport { ok, err } from '@kanzen/result'; const success = ok('Success value'); console.log(await success.unwrap()); // 'Success value' const failure = err(new Error('Failure reason')); // try { await failure.unwrap(); } catch (error) { console.error(error); } // This would log Error: Failure reasonunwrapOr<U>(fallback: U): Promise<T | U>- Asynchronously returns the value if Ok, or the fallback if Errimport { ok, err } from '@kanzen/result'; const success = ok(42); console.log(await success.unwrapOr(0)); // 42 const failure = err('Error occurred'); console.log(await failure.unwrapOr(0)); // 0match<R>({ ok, err }: { ok: (value: T) => R, err: (error: E) => R }): Promise<R>- Asynchronously pattern matches on the ResultAsyncimport { ok, err } from '@kanzen/result'; const success = ok('Success message'); const successMessage = await success.match({ ok: value => `Operation succeeded: ${value}`, err: error => `Operation failed: ${error}`, }); console.log(successMessage); // 'Operation succeeded: Success message' const failure = err('Error message'); const failureMessage = await failure.match({ ok: value => `Operation succeeded: ${value}`, err: error => `Operation failed: ${error}`, }); console.log(failureMessage); // 'Operation failed: Error message'map<U>(fn: (value: T) => U): ResultAsync<U, E>- Asynchronously maps the success valueimport { ok, err } from '@kanzen/result'; const success = ok(5); const mappedValue = await success.map(x => x * 2).unwrap(); console.log(mappedValue); // 10 const failure = err('Error'); const stillError = await failure.map(x => x * 2).isErr(); // Still Err('Error') console.log(stillError); // truemapErr<F>(fn: (error: E) => F): ResultAsync<T, F>- Asynchronously maps the error valueimport { ok, err } from '@kanzen/result'; const success = ok(5); const mappedValue = await success.mapErr(e => new Error(String(e))).unwrap(); // Still Ok(5) console.log(mappedValue); // 5 const failure = err('Something went wrong'); const defaultValue = await failure.mapErr(msg => new Error(`Error: ${msg}`)).unwrapOr('Default'); console.log(defaultValue); // 'Default'andThen<U, F>(fn: (value: T) => ResultAsync<U, F>): ResultAsync<U, E | F>- Asynchronously chains a function that returns a ResultAsyncimport { ok, err, safeTry } from '@kanzen/result'; // An async function that returns a Result const fetchUserProfile = async (userId: string) => { const response = await fetch(`https://api.example.com/users/${userId}`); if (!response.ok) { throw new Error(`HTTP error: ${response.status}`); } return await response.json(); }; const safeFetchUserProfile = safeTry(fetchUserProfile); // Fetch user and then fetch their posts const fetchUserPosts = async (user: any) => { const response = await fetch(`https://api.example.com/users/${user.id}/posts`); if (!response.ok) { throw new Error(`HTTP error: ${response.status}`); } return { user, posts: await response.json() }; }; const safeFetchUserPosts = safeTry(fetchUserPosts); // Chain operations with async/await try { const userResult = await safeFetchUserProfile('123'); const user = await userResult.unwrap(); const postsResult = await safeFetchUserPosts(user); const data = await postsResult.unwrap(); console.log(`Found ${data.posts.length} posts by ${data.user.name}`); } catch (error) { console.error(`Error: ${error.message}`); }orElse<U, F>(fn: (error: E) => ResultAsync<U, F>): ResultAsync<T | U, F>- Asynchronously handles errors by returning a new ResultAsyncimport { ok, err, safeTry } from '@kanzen/result'; // An async function that may fail const fetchFromPrimarySource = async () => { throw new Error('Primary source unavailable'); }; const safeFetchPrimary = safeTry(fetchFromPrimarySource); // A backup async function const fetchFromBackupSource = async () => { return { data: 'Backup data', source: 'backup' }; }; const safeFetchBackup = safeTry(fetchFromBackupSource); // Try primary source, fall back to backup on failure using async/await let data; try { const primaryResult = await safeFetchPrimary(); data = await primaryResult.unwrap(); } catch (error) { console.log(`Primary source failed: ${error.message}`); try { const backupResult = await safeFetchBackup(); data = await backupResult.unwrap(); } catch (backupError) { console.error(`All sources failed: ${backupError.message}`); return; } } console.log(`Retrieved data from ${data.source}`);andTee(fn: (value: T) => unknown): ResultAsync<T, E>- Asynchronously performs a side effect on successimport { ok } from '@kanzen/result'; const success = ok(42); const result = await success.andTee(value => { console.log(`Processing value: ${value}`); // Side effect }) .unwrap(); console.log(`Result: ${result}`); // Result: 42orTee(fn: (error: E) => unknown): ResultAsync<T, E>- Asynchronously performs a side effect on errorimport { err } from '@kanzen/result'; const failure = err(new Error('Operation failed')); const isError = await failure.orTee(error => { console.error(`Logging error: ${error.message}`); // Side effect }) .isErr(); console.log(`Is error: ${isError}`); // Is error: true
📜 License
This project is licensed under the MIT License - see the LICENSE file for details.
Made with ❤️ by Oscar Luis Jimenez Gonzalez