@moon7/result v0.1.1
@moon7/result
A lightweight, zero-dependency TypeScript library for handling operations that might fail in a functional way.
Features
- 🛡️ Type-safe error handling - Handle success and failure states without exceptions
- 🧩 Composable operations - Chain operations that might fail with clean, readable code
- 🔄 Async support - Full support for asynchronous operations with promises
- 🧪 Pattern matching - Elegant pattern matching for handling different result states
- 📦 Zero dependencies - Lightweight and focused utility
Installation
# npm
npm install @moon7/result
# yarn
yarn add @moon7/result
# pnpm
pnpm add @moon7/resultCore Concepts
Result Type
The core of the library is the Result<V, E> type, which can be either a Success<V> or a Failure<E>:
type Result<V, E = unknown> = Success<V> | Failure<E>;
interface Success<V> {
readonly value: V;
}
interface Failure<E> {
readonly error: E;
}Maybe Type
The library includes a Maybe<T> type for handling optional values in a functional way. It's unified with the Result type, meaning all Result operations work seamlessly with Maybe:
import { some, none, isSome, isNone, Maybe } from '@moon7/result';
import { map, chain, unwrapOr } from '@moon7/result';
// Creating Maybe values
const someValue = some(42); // Contains a value
const noneValue = none; // Represents absence of a value
// Type guards
if (isSome(someValue)) {
console.log(someValue.value); // 42
}
// Safely extracting values
const value = unwrapOr(someValue, 0); // 42
const fallback = unwrapOr(noneValue, 0); // 0
// Transformations
const doubled = map(someValue, x => x * 2); // some(84)
const chained = chain(someValue, x => x > 20 ? some(x) : none); // some(42)The Maybe type is implemented as a specialized Result where Some<T> is a Success<T> and None is a Failure<null>. This allows you to reuse all the Result utility functions with Maybe values.
Outcome Type
The Outcome<V, E> type represents the common Node.js callback argument pattern of (error, value) tuples:
import { Outcome, fromOutcome } from '@moon7/result';
// Outcome is a [error, value] tuple, common in Node.js callbacks
type SuccessOutcome<V> = [undefined | null, V];
type FailureOutcome<E> = [E, undefined];
type Outcome<V, E = unknown> = SuccessOutcome<V> | FailureOutcome<E>;
// Converting from Outcome to Result
const nodeOutcome: Outcome<string, Error> = [null, "operation succeeded"];
// Success with "operation succeeded"
const result = fromOutcome(nodeOutcome);
const errorOutcome: Outcome<string, Error> = [new Error("operation failed"), undefined];
// Failure with Error("operation failed")
const errorResult = fromOutcome(errorOutcome);The Outcome type is primarily used:
- As a representation of Node.js callback argument tuples
- For conversion to Result via
fromOutcome()to leverage Result's rich API
Basic Usage
import { success, failure, isSuccess, unwrapOr } from '@moon7/result';
// Creating Results
const successResult = success(42);
const failureResult = failure(new Error("Something went wrong"));
// Checking result type
if (isSuccess(successResult)) {
console.log(successResult.value); // 42
}
// Safely extracting values with fallbacks
const value = unwrapOr(failureResult, 0); // 0Safe Operations
import { fromTry, fromPromise } from '@moon7/result';
// Safe synchronous operations
const result = fromTry(() => JSON.parse(someInput));
// Safe asynchronous operations
const asyncResult = await fromPromise(fetch('https://api.example.com/data'));Working with Results
import {
match, map, chain, recover, all, any,
unwrapOr, unwrapOrElse
} from '@moon7/result';
// Pattern matching
const message = match(result, {
success: value => `Got value: ${value}`,
failure: error => `Error: ${error.message}`
});
// Transforming successful results
const doubled = map(result, x => x * 2);
// Chaining operations
const final = chain(result, value => {
return someOtherOperationThatMightFail(value);
});
// Recovering from errors
const recovered = recover(result, error => {
console.log(`Recovering from: ${error.message}`);
return defaultValue;
});
// Working with multiple results
const combined = all([result1, result2, result3]); // Success only if ALL succeed
const any = any([result1, result2, result3]); // Success if ANY succeedsAsync Support
The library provides full support for asynchronous operations:
import { fromTryAsync, fromPromise } from '@moon7/result';
// Creating async results
const result = await fromTryAsync(async () => {
const response = await fetch('https://api.example.com/data');
return response.json();
});
// TypeScript narrows the type for safe value access
const value = isSuccess(result) ? result.value : defaultValue;
// Or use match pattern
const data = match(result, {
success: value => value,
failure: error => defaultValue
});AsyncResult for Loading States
The library also provides an AsyncResult type that adds a third "pending" state to represent loading operations:
import {
AsyncResult, pending, success, failure, matchAsync
} from '@moon7/result';
// Component that displays user data with loading states
function UserProfile({ userId }: { userId: string }) {
// State to hold the AsyncResult
const [state, setState] = useState<AsyncResult<User, Error>>(pending);
// Fetch user data when component mounts or userId changes
useEffect(() => {
async function fetchUser() {
// Start with pending state
setState(pending);
try {
// Simulate API call
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
setState(failure(new Error("Error occurred")));
return;
}
const userData = await response.json();
setState(success(userData));
} catch (error) {
setState(failure(error));
}
}
fetchUser();
}, [userId]);
// Render different UI based on the AsyncResult state
return (
<div className="user-profile">
{matchAsync(state, {
pending: () => (
<div className="loading">Loading user...</div>
),
success: (user) => (
<div className="user-data">
<h2>{user.name}</h2>
<p>Email: {user.email}</p>
<p>Role: {user.role}</p>
</div>
),
failure: (error) => (
<div className="error">
<h2>Could not load user</h2>
<p>{error.message}</p>
<button onClick={() => fetchUser()}>Retry</button>
</div>
)
})}
</div>
);
}Outcome Utilities
The library also provides a fromNodeCallback and liftOutcome utilities that directly works with Node.js-style callback functions:
import { fromNodeCallback, liftOutcome } from '@moon7/result';
import { readFile } from 'fs';
// Convert a Node.js callback function directly to a Promise<Result>
const fileResult = await fromNodeCallback(cb =>
readFile("package.json", "utf8", cb)
);
// Now you can use all Result operations
if (isSuccess(fileResult)) {
const content = fileResult.value;
// Process content...
}
// If you have a function with multiple Outcome callbacks
multipleCallbacks(
(err, val) => ...,
(err, val) => ...
);
// Use liftOutcome to work with result values instead
multipleCallbacks(
liftOutcome(result => ...),
liftOutcome(result => ...)
);This allows you to easily bridge Node.js callback-based APIs with the functional Result pattern.
Additional Utilities
The library also provides utilities for assertions and nullable handling:
import { must, strictMust, assert, assertNever } from '@moon7/result';
// Check for null or undefined
const value = must(maybeNull, "Value cannot be null");
// Type assertions
assert(condition, "Condition must be true");
// Exhaustive type checking with assertNever
type Shape = Circle | Square | Triangle;
function processShape(shape: Shape) {
switch (shape.type) {
case 'circle':
return calculateCircleArea(shape);
case 'square':
return calculateSquareArea(shape);
case 'triangle':
return calculateTriangleArea(shape);
default:
// This ensures compiler error if you add a new shape type
// but forget to handle it in this switch statement
return assertNever(shape);
}
}The assertNever function is particularly valuable for exhaustiveness checking in TypeScript. If you add a new variant to the Shape type but forget to handle it in the switch statement, TypeScript will give you a compile-time error, preventing potential bugs.
Error Raising
The library provides convenient ways to throw errors as expressions:
import { raise } from '@moon7/result';
// Throw an error as an expression in a ternary
const value = condition ? computeValue() : raise(new Error("Condition failed"));
// Use in place of default values
const item = items.find(i => i.id === id) ?? raise(new Error(`Item ${id} not found`));API Reference
Core Types
Result<V, E>: Union type ofSuccess<V>andFailure<E>Success<V>: Represents a successful operation with a valueFailure<E>: Represents a failed operation with an errorMaybe<T>: Union type ofSome<T>andNonefor handling optional valuesSome<T>: Represents a present value in a Maybe contextNone: Represents absence of a value in a Maybe contextAsyncResult<V, E>: Represents a value that can be pending, success, or failurePending: Represents a pending/loading stateOutcome<V, E>: Tuple-based representation of error, value pairs
Type Guards
isSuccess<V, E>(result): Checks if a result is aSuccess<V>isFailure<V, E>(result): Checks if a result is aFailure<E>isSome<T>(maybe): Checks if a maybe is aSome<T>isNone<T>(maybe): Checks if a maybe isNoneisPending<V, E>(result): Checks if an async result isPendingisResult<V, E>(result): Checks if something is aResult<V, E>isAsyncResult<V, E>(result): Checks if something is anAsyncResult<V, E>
Constructors
success<V>(value): Creates aSuccess<V>resultfailure<E>(error): Creates aFailure<E>resultsome<T>(value): Creates aSome<T>maybe valuenone: Constant representingNonepending: Constant representing the pending state
Unwrapping Functions
unwrap<V, E>(result): Extracts the value or throws the errorunwrapOr<V, E>(result, defaultValue): Extracts the value or returns a defaultunwrapOr<V, E>(result): Extracts the value or returns undefinedunwrapOrElse<V, E>(result, fn): Extracts the value or computes a fallback
Error Recovery
recover<V, E>(result, fn): Transforms a failure into a success by recovering from the error
Result Creation
fromTry<V, E>(fn): Creates a result from a function that might throwfromTryAsync<V, E>(fn): Creates a result from an async function that might throwfromPromise<V, E>(promise): Creates a result from a promisefromNullable<V, E>(value, error): Creates a result from a nullable valuefromNodeCallback<V, E>(fn): Creates a result from a Node.js style callbackfromOutcome<V, E>(outcome): Converts anOutcome<V, E>to aResult<V, E>fromMaybe<T>(maybe): Converts aMaybe<T>to aResult<T, null>liftOutcome<V, E>(cb): Converts a Result callback to a Node-style callback
Collection Operations
all<V, E>(results): Succeeds if all results succeed, fails on first failureany<V, E>(results): Succeeds on first success, fails if all fail
Pattern Matching
match<V, E, T>(result, patterns): Applies success or failure function based on resultmatchAsync<V, E, T>(result, patterns): Async version ofmatchfor AsyncResults
Transformations
map<V, U, E>(result, fn): Maps a success value, preserves failurechain<V, U, E>(result, fn): Maps a success to another result, preserves failure
Utility Functions
must<T>(value, errorMessage?): Ensures a value is not null or undefinedstrictMust<T>(value, errorMessage?): Ensures a value is not undefinedassert<T>(condition, message?): Throws if condition is falseassertNever(value): Used for exhaustive checks in switch statementssafely<T>(x, defaultValue): Safely executes a function returning a default on errorattempt<T>(x): Similar tosafelybut returns aResultinsteadraise<E>(error?): Throws an error as an expression
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Related Libraries
The moon7 ecosystem includes several companion libraries that work well together:
- @moon7/async - Utilities for asynchronous programming
- @moon7/inspect - Advanced object inspection and formatting
- @moon7/validate - Validation library with composable rules
- @moon7/signals - Simple, lightweight signal/event system
License
MIT © Munir Hussin