@crvouga/result
v1.5.0
Published
A comprehensive Result type library for TypeScript with Ok, Err, Loading, and NotAsked states
Maintainers
Readme
result
A comprehensive JavaScript library for handling success and error states with support for remote data patterns. Provides type-safe error handling, pattern matching, and functional programming utilities.
Features
- Type-safe error handling with
OkandErrtypes - Remote data patterns with
LoadingandNotAskedstates - Pattern matching for all result types
- Functional utilities like
map,flatMap,bimap,swap - Promise integration with
toPromise - Nullable value handling with
fromNullableandfromUndefined - Comprehensive type guards with optional validation
- Equality comparison and utility functions
Installation
npm install @crvouga/resultQuick Start
import { Ok, Err, Loading, NotAsked, match, mapOk, flatMapOk } from '@crvouga/result';
// Basic usage
const result = Ok(42);
const doubled = mapOk(result, x => x * 2); // Ok(84)
// Error handling
const errorResult = Err('Something went wrong');
const message = match(errorResult, {
ok: value => `Success: ${value}`,
err: error => `Error: ${error}`,
loading: () => 'Loading...',
notAsked: () => 'Not started'
});
// Remote data patterns
const userData = Loading();
if (isLoading(userData)) {
console.log('Loading user data...');
}Core Types
Result Types
Ok<T>- Represents a successful result with a value of typeTErr<E>- Represents an error result with an error of typeELoading- Represents a loading state (for remote data)NotAsked- Represents an initial state (for remote data)
Type Aliases
type Result<T, E> = Ok<T> | Err<E>;
type RemoteResult<T, E> = Ok<T> | Err<E> | Loading | NotAsked;API Reference
Constructors
Ok(value)
Creates a successful result.
const result = Ok(42);
console.log(result); // { type: "ok", value: 42 }Err(error)
Creates an error result.
const result = Err('Something went wrong');
console.log(result); // { type: "err", error: "Something went wrong" }Loading()
Creates a loading state.
const result = Loading();
console.log(result); // { type: "loading" }NotAsked()
Creates a not-asked state.
const result = NotAsked();
console.log(result); // { type: "not-asked" }Type Guards
isOk(result, validator?)
Checks if a result is successful, with optional value validation.
const result = Ok(42);
console.log(isOk(result)); // true
// With validation
const isNumber = (value) => typeof value === 'number';
console.log(isOk(result, isNumber)); // true
console.log(isOk(Ok('hello'), isNumber)); // falseisErr(result, validator?)
Checks if a result is an error, with optional error validation.
const result = Err('Network error');
console.log(isErr(result)); // true
// With validation
const isString = (error) => typeof error === 'string';
console.log(isErr(result, isString)); // true
console.log(isErr(Err(404), isString)); // falseisLoading(result)
Checks if a result is in loading state.
const result = Loading();
console.log(isLoading(result)); // trueisNotAsked(result)
Checks if a result is in not-asked state.
const result = NotAsked();
console.log(isNotAsked(result)); // trueisResult(value, valueValidator?, errorValidator?)
Comprehensive type guard for Result types with optional validation.
const result = Ok(42);
console.log(isResult(result)); // true
// With validation
const isNumber = (value) => typeof value === 'number';
const isString = (error) => typeof error === 'string';
console.log(isResult(result, isNumber, isString)); // trueisRemoteResult(value, valueValidator?, errorValidator?)
Type guard for RemoteResult types with optional validation.
const result = Loading();
console.log(isRemoteResult(result)); // truePattern Matching
match(result, matchers)
Pattern matching for all RemoteResult states.
const result = Ok(42);
const message = match(result, {
ok: value => `Success: ${value}`,
err: error => `Error: ${error}`,
loading: () => 'Loading...',
notAsked: () => 'Not started'
});
console.log(message); // "Success: 42"fold(result, onOk, onErr)
Pattern matching for basic Result types (Ok/Err only).
const result = Ok(42);
const message = fold(result,
value => `Success: ${value}`,
error => `Error: ${error}`
);
console.log(message); // "Success: 42"foldRemote(result, matchers)
Pattern matching for RemoteResult types with semantic naming.
const result = Ok(42);
const message = foldRemote(result, {
success: value => `Success: ${value}`,
failure: error => `Error: ${error}`,
loading: () => 'Loading...',
notAsked: () => 'Not started'
});
console.log(message); // "Success: 42"Transformation Functions
mapOk(result, mapper)
Maps a function over the value of a successful result.
const result = Ok(42);
const doubled = mapOk(result, x => x * 2);
console.log(doubled); // { type: "ok", value: 84 }mapErr(result, mapper)
Maps a function over the error of an error result.
const result = Err('network error');
const formatted = mapErr(result, error => `Error: ${error.toUpperCase()}`);
console.log(formatted); // { type: "err", error: "Error: NETWORK ERROR" }flatMapOk(result, mapper)
Maps a function that returns a result over a successful result.
const result = Ok(5);
const processed = flatMapOk(result, x => x > 10 ? Ok(x) : Err('Too small'));
console.log(processed); // { type: "err", error: "Too small" }flatMapErr(result, mapper)
Maps a function that returns a result over an error result.
const result = Err('network error');
const recovered = flatMapErr(result, error =>
error.includes('network') ? Ok('Using cached data') : Err(error)
);
console.log(recovered); // { type: "ok", value: "Using cached data" }bimap(result, okMapper, errMapper)
Maps both success and error cases in one operation.
const result = Ok(42);
const transformed = bimap(result,
value => value * 2,
error => `Error: ${error}`
);
console.log(transformed); // { type: "ok", value: 84 }mapRemote(result, mapper)
Maps over successful RemoteResults, passing through other states unchanged.
const result = Ok({ id: 1, name: 'John' });
const mapped = mapRemote(result, user => user.name);
console.log(mapped); // { type: "ok", value: "John" }Utility Functions
unwrap(result)
Extracts the value from a successful result, throws on error.
const result = Ok(42);
const value = unwrap(result); // 42
const errorResult = Err('error');
unwrap(errorResult); // throws "error"unwrapOr(result, defaultValue)
Extracts the value from a successful result, or returns a default.
const result = Ok(42);
const value = unwrapOr(result, 0); // 42
const errorResult = Err('error');
const value = unwrapOr(errorResult, 0); // 0getOrElse(result, defaultValue)
Alias for unwrapOr for consistency with functional libraries.
const result = Ok(42);
const value = getOrElse(result, 0); // 42fromNullable(value, error)
Creates a Result from a nullable value.
const user = getUserFromCache();
const result = fromNullable(user, 'User not found in cache');
// Returns Ok(user) if user is not null/undefined, otherwise Err('User not found in cache')fromUndefined(value, error)
Creates a Result from a value that might be undefined.
const config = process.env.API_KEY;
const result = fromUndefined(config, 'API key not configured');
// Returns Ok(config) if config is not undefined, otherwise Err('API key not configured')toPromise(result)
Converts a Result to a Promise.
const result = Ok(42);
const promise = toPromise(result);
const value = await promise; // 42
const errorResult = Err('Something went wrong');
const promise = toPromise(errorResult);
try {
await promise; // throws 'Something went wrong'
} catch (error) {
console.log(error); // 'Something went wrong'
}swap(result)
Swaps Ok and Err values of a Result.
const result = Ok(42);
const swapped = swap(result);
console.log(swapped); // { type: "err", error: 42 }
const errorResult = Err('Something went wrong');
const swapped = swap(errorResult);
console.log(swapped); // { type: "ok", value: "Something went wrong" }equals(a, b, valueEquals?)
Checks if two Results are equal. Safely handles unknown parameters and supports custom equality functions.
const a = Ok(42);
const b = Ok(42);
console.log(equals(a, b)); // true
const c = Err('error');
const d = Err('error');
console.log(equals(c, d)); // true
// Safe with unknown values
console.log(equals(null, Ok(42))); // false
console.log(equals({}, Ok(42))); // false
// With custom equality function for objects
const objA = Ok({ id: 1, name: 'John' });
const objB = Ok({ id: 1, name: 'John' });
console.log(equals(objA, objB)); // false (reference equality)
const deepEquals = (a, b) => JSON.stringify(a) === JSON.stringify(b);
console.log(equals(objA, objB, deepEquals)); // true
// With custom equality for arrays
const arrA = Ok([1, 2, 3]);
const arrB = Ok([1, 2, 3]);
const arrayEquals = (a, b) => a.length === b.length && a.every((x, i) => x === b[i]);
console.log(equals(arrA, arrB, arrayEquals)); // truefromNullish(value, error)
Creates a Result from a value that might be null or undefined. Returns Ok(value) if the value is not null/undefined, otherwise Err(error).
const result = fromNullish('hello', 'Value is nullish');
// { type: 'ok', value: 'hello' }
const result2 = fromNullish(null, 'Value is nullish');
// { type: 'err', error: 'Value is nullish' }
const result3 = fromNullish(undefined, 'Value is nullish');
// { type: 'err', error: 'Value is nullish' }fromFalsy(value, error)
Creates a Result from a value that might be falsy. Returns Ok(value) if the value is truthy, otherwise Err(error).
const result = fromFalsy('hello', 'Value is falsy');
// { type: 'ok', value: 'hello' }
const result2 = fromFalsy('', 'Value is falsy');
// { type: 'err', error: 'Value is falsy' }
const result3 = fromFalsy(0, 'Value is falsy');
// { type: 'err', error: 'Value is falsy' }
const result4 = fromFalsy(false, 'Value is falsy');
// { type: 'err', error: 'Value is falsy' }
const result5 = fromFalsy(null, 'Value is falsy');
// { type: 'err', error: 'Value is falsy' }
const result6 = fromFalsy(undefined, 'Value is falsy');
// { type: 'err', error: 'Value is falsy' }Error Handling
tryCatchSync(fn)
Wraps a synchronous function that might throw.
const result = tryCatchSync(() => {
const value = JSON.parse('invalid json');
return value;
});
console.log(result); // { type: "err", error: SyntaxError }tryCatch(fn)
Wraps an asynchronous function that might throw.
const result = await tryCatch(async () => {
const response = await fetch('/api/data');
return response.json();
});RemoteResult Type Guards
isRemoteSuccess(result, validator?)
Checks if a value is a successful RemoteResult (Ok only).
const result = Ok(42);
console.log(isRemoteSuccess(result)); // true
const loadingResult = Loading();
console.log(isRemoteSuccess(loadingResult)); // falseisRemoteFailure(result, validator?)
Checks if a value is a failed RemoteResult (Err only).
const result = Err('error');
console.log(isRemoteFailure(result)); // true
const successResult = Ok(42);
console.log(isRemoteFailure(successResult)); // falseExamples
Basic Error Handling
import { Ok, Err, isOk, isErr, unwrapOr } from '@crvouga/result';
function divide(a, b) {
if (b === 0) {
return Err('Division by zero');
}
return Ok(a / b);
}
const result = divide(10, 2);
if (isOk(result)) {
console.log(`Result: ${result.value}`); // Result: 5
} else {
console.log(`Error: ${result.error}`);
}
// Or use unwrapOr for default values
const value = unwrapOr(result, 0);API Response Handling
import { Ok, Err, Loading, match, fromNullable } from '@crvouga/result';
async function fetchUser(id) {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
return Err(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
return fromNullable(data.user, 'User not found');
} catch (error) {
return Err(`Network error: ${error.message}`);
}
}
// In your component
const userData = Loading(); // Initial state
const renderUser = (data) => {
return match(data, {
ok: user => <UserCard user={user} />,
err: error => <ErrorMessage error={error} />,
loading: () => <Spinner />,
notAsked: () => <button onClick={fetchUser}>Load User</button>
});
};Form Validation
import { Ok, Err, fromNullable, flatMapOk } from '@crvouga/result';
function validateEmail(email) {
return fromNullable(email, 'Email is required')
|> (r) => flatMapOk(r, email =>
email.includes('@') ? Ok(email) : Err('Invalid email format')
);
}
function validateAge(age) {
return fromNullable(age, 'Age is required')
|> (r) => flatMapOk(r, age =>
age >= 18 ? Ok(age) : Err('Must be 18 or older')
);
}
const emailResult = validateEmail('[email protected]');
const ageResult = validateAge(25);
if (isOk(emailResult) && isOk(ageResult)) {
console.log('Form is valid');
} else {
console.log('Validation errors:', {
email: emailResult.type === 'err' ? emailResult.error : null,
age: ageResult.type === 'err' ? ageResult.error : null
});
}Promise Integration
import { Ok, Err, toPromise, tryCatch } from '@crvouga/result';
// Convert Results to Promises
const result = Ok(42);
const promise = toPromise(result);
const value = await promise; // 42
// Wrap async operations
const fetchData = async () => {
const result = await tryCatch(async () => {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
});
return result;
};
const data = await fetchData();
if (isOk(data)) {
console.log('Data:', data.value);
} else {
console.error('Error:', data.error);
}Complex Business Logic
import { Ok, Err, match, bimap, swap } from '@crvouga/result';
function processOrder(order) {
// Validate order
if (!order.items || order.items.length === 0) {
return Err('Order must contain at least one item');
}
if (order.total <= 0) {
return Err('Order total must be positive');
}
// Apply business rules
const processedOrder = {
...order,
status: order.total > 100 ? 'premium' : 'standard',
discount: order.total > 200 ? 0.1 : 0
};
return Ok(processedOrder);
}
const order = { items: ['laptop'], total: 150 };
const result = processOrder(order);
// Transform the result for display
const displayResult = bimap(result,
order => `Order processed: ${order.status} (${order.discount * 100}% discount)`,
error => `Order failed: ${error}`
);
// Or use pattern matching for complex logic
const message = match(result, {
ok: order => {
if (order.status === 'premium') {
return `🎉 Premium order confirmed! Total: $${order.total}`;
}
return `✅ Order confirmed! Total: $${order.total}`;
},
err: error => {
if (error.includes('items')) {
return `🛒 ${error}. Please add items to your cart.`;
}
return `❌ ${error}`;
},
loading: () => '⏳ Processing your order...',
notAsked: () => '🛒 Add items to cart to continue'
});Contributing
See CONTRIBUTING.md for details on how to contribute to this project.
License
MIT License - see LICENSE for details.
Changelog
See CHANGELOG.md for a list of changes and version history.
