@raghava.indra/result-ts
v1.1.1
Published
A lightweight, type-safe Result monad for TypeScript — handle errors without exceptions
Maintainers
Readme
@raghava.indra/result-ts
A lightweight, type-safe Result type for TypeScript. No dependencies.
The Problem
When functions can fail, we usually throw exceptions:
function divide(a: number, b: number): number {
if (b === 0) throw new Error("division by zero");
return a / b;
}
const result = divide(10, 0); // throws at runtimeThis has problems:
- You don't know it can fail — the return type
numbergives no hint that an error is possible - You must remember try/catch — but nothing enforces it
- The error type is unknown —
catch (e)gives youunknown, not a typed error
The Solution
Return a Result instead. A Result is either:
Ok— it worked, here's the dataErr— it failed, here's the error
function divide(a: number, b: number): Result<number, string> {
if (b === 0) return err("division by zero");
return ok(a / b);
}
const result = divide(10, 2); // Ok(5)
const failed = divide(10, 0); // Err("division by zero")Now the return type tells you everything:
- It might succeed with
number - It might fail with
string - You must handle both cases
Install
npm install @raghava.indra/result-tsQuick Start
import { ok, err, Result } from '@raghava.indra/result-ts';
function parseAge(input: string): Result<number, string> {
const num = Number(input);
if (isNaN(num)) return err("not a number");
if (num < 0) return err("age cannot be negative");
return ok(num);
}Every Result has an ok field. Use it to check success or failure:
const result = parseAge("25");
if (result.ok) {
console.log(result.data); // 25 (TypeScript knows it's Ok here)
} else {
console.log(result.error); // the error message (TypeScript knows it's Err here)
}API Reference
Creating Results
ok(data)
Create a success result.
const result = ok(42);
result.ok // true
result.data // 42
result.isOk() // true
result.isErr() // falseerr(error)
Create a failure result.
const result = err("file not found");
result.ok // false
result.error // "file not found"
result.isOk() // false
result.isErr() // trueResult<T, E>
Use this as a return type. T is the success type, E is the error type.
function findUser(id: number): Result<User, string> {
// must return ok(User) or err(string)
}Transforming Values
.map(fn) — Transform the success value
When to use: You have an Ok and want to change the data inside it.
// Ok gets transformed
ok(10).map(x => x * 2) // Ok(20)
// Err is passed through unchanged — the function is never called
err("fail").map(x => x * 2) // Err("fail")Real example: Converting a raw database row into a formatted string:
function getDisplayName(userId: number): Result<string, string> {
return findUser(userId) // Result<User, string>
.map(user => user.name) // Result<string, string> — only runs if user was found
.map(name => name.toUpperCase()); // still works, errors pass through
}.mapErr(fn) — Transform the error value
When to use: You want to change or enrich the error, not the data.
// Err gets transformed
err("not found").mapErr(e => `Error: ${e}`) // Err("Error: not found")
// Ok is passed through unchanged
ok(10).mapErr(e => `Error: ${e}`) // Ok(10)Real example: Enriching errors with context before returning to the caller:
function readConfig(path: string): Result<Config, string> {
return parseFile(path) // Result<Config, string>
.mapErr(e => `Failed to read config at ${path}: ${e}`);
}.flatMap(fn) — Chain operations that return Results
When to use: Your next step also returns a Result. Like map, but the callback returns a Result instead of a plain value.
// Ok: callback runs, its Result becomes the new result
ok(10).flatMap(x => ok(x * 2)) // Ok(20)
ok(10).flatMap(x => err("overflow")) // Err("overflow")
// Err: callback is skipped, error passes through
err("fail").flatMap(x => ok(x * 2)) // Err("fail")Real example: A multi-step process where any step can fail:
function createAccount(email: string): Result<Account, string> {
return validateEmail(email) // Result<string, string>
.flatMap(validEmail => checkAvailable(validEmail)) // Result<string, string>
.flatMap(availableEmail => createProfile(availableEmail)); // Result<Account, string>
}
// If validateEmail fails, the rest is skipped automatically
// If checkAvailable fails, createProfile is never calledExtracting Values
.fold(onErr, onOk) — Handle both cases, get a single value out
When to use: You're done processing and need a concrete value (e.g., to send an HTTP response or log a message).
const message = parseAge("abc").fold(
(error) => `Invalid input: ${error}`, // runs if Err
(age) => `Age is ${age}` // runs if Ok
);
// message = "Invalid input: not a number"Real example: Building an HTTP response:
function handleRequest(req: Request): Response {
return processOrder(req).fold(
(error) => new Response(error, { status: 400 }),
(order) => new Response(JSON.stringify(order), { status: 200 })
);
}.getOrElse(defaultValue) — Get the data or a fallback
When to use: You want the data if it worked, otherwise a sensible default.
ok(42).getOrElse(0) // 42
err("fail").getOrElse(0) // 0Real example: Loading a cached value with a fallback:
const timeout = getCachedTimeout().getOrElse(30_000); // use 30s if cache missingAsync Operations
When working with Promises, errors get lost. try/catch doesn't compose well and catch gives you unknown. This library provides tools to convert promises into Results and chain async operations.
fromPromise(promise, mapErr) — Convert a Promise to a Result
When to use: You have a Promise (like a fetch call) and want to handle both success and rejection as a Result.
const result = await fromPromise(
fetch("/api/users"),
(e) => `Network error: ${e}`
);
// result is Result<Response, string>Real example: Wrapping an API call:
async function getUser(id: number): Promise<Result<User, string>> {
return fromPromise(
fetch(`/api/users/${id}`).then(r => r.json()),
(e) => `Request failed: ${e}`
);
}tryAsync(fn, mapErr) — Run an async function, catch errors
When to use: You have an async operation that might throw, and you want to catch it as a Result.
const result = await tryAsync(
() => fs.readFile("/config.json", "utf-8"),
(e) => `Read failed: ${e}`
);
// result is Result<string, string>.mapAsync(fn) — Async version of .map()
When to use: Same as .map(), but the transformation is async (returns a Promise).
// Ok: async transform runs
const result = await ok(42).mapAsync(async (n) => {
const res = await fetch(`/api/items/${n}`);
return res.json();
});
// result is Ok(parsedData)
// Err: transform is skipped
const failed = await err("not found").mapAsync(async (n) => { /* never runs */ });
// failed is Err("not found").flatMapAsync(fn) — Async version of .flatMap()
When to use: Chain async operations where each step returns a Result.
const result = await ok(userId)
.flatMapAsync(async (id) => {
const user = await fetchUser(id); // returns Result<User, string>
return user;
});Real example: A multi-step async workflow:
async function placeOrder(itemId: number): Promise<Result<Order, string>> {
return ok(itemId)
.flatMapAsync(async (id) => checkStock(id)) // Result<Item, string>
.flatMapAsync(async (item) => reserveItem(item)) // Result<Reservation, string>
.flatMapAsync(async (res) => createOrder(res)); // Result<Order, string>
}
// If checkStock fails, the rest is skipped automatically.mapErrAsync(fn) — Async version of .mapErr()
When to use: You want to transform the error asynchronously.
const result = await err("db-001")
.mapErrAsync(async (code) => await lookupErrorMessage(code));AsyncResult<T, E> — Type alias
AsyncResult<T, E> is simply Promise<Result<T, E>>. Use it as a return type for async functions that return Results.
async function fetchConfig(): AsyncResult<Config, string> {
return fromPromise(
fetch("/config").then(r => r.json()),
(e) => `Failed: ${e}`
);
}Quick Reference
| Method | On Ok | On Err |
|--------|-------|--------|
| .map(fn) | Runs fn on data, wraps result in Ok | Skipped, passes Err through |
| .mapErr(fn) | Skipped, passes Ok through | Runs fn on error, wraps result in Err |
| .flatMap(fn) | Runs fn, returns its Result | Skipped, passes Err through |
| .fold(onErr, onOk) | Runs onOk | Runs onErr |
| .getOrElse(val) | Returns data | Returns val |
| .mapAsync(fn) | Runs async fn, wraps result in Ok | Skipped, resolves to Err |
| .flatMapAsync(fn) | Runs async fn, returns its Result | Skipped, resolves to Err |
| .mapErrAsync(fn) | Skipped, resolves to Ok | Runs async fn, wraps result in Err |
| Standalone | Description |
|-----------|-------------|
| fromPromise(p, mapErr) | Convert Promise<T> to AsyncResult<T, E> |
| tryAsync(fn, mapErr) | Run async fn, catch throws as AsyncResult<T, E> |
Full Example
A user registration flow with validation, duplicate checks, and database insertion — each step can fail:
import { ok, err, Result, fromPromise, AsyncResult } from '@raghava.indra/result-ts';
type User = { id: number; email: string; name: string };
type AppError = { field: string; message: string };
function validateEmail(email: string): Result<string, AppError> {
if (!email.includes("@")) {
return err({ field: "email", message: "invalid email format" });
}
return ok(email);
}
async function checkDuplicate(email: string): AsyncResult<string, AppError> {
return fromPromise(
fetch(`/api/check?email=${email}`),
() => err({ field: "email", message: "network error" })
).flatMapAsync(async (res) => {
const data = await res.json();
return data.exists
? err({ field: "email", message: "email already registered" })
: ok(email);
});
}
async function insertUser(email: string, name: string): AsyncResult<User, AppError> {
return fromPromise(
fetch("/api/users", {
method: "POST",
body: JSON.stringify({ email, name }),
}),
() => err({ field: "email", message: "failed to create user" })
).mapAsync(async (res) => res.json() as User);
}
async function register(
email: string,
name: string
): Promise<Result<User, AppError>> {
return validateEmail(email)
.flatMap((validEmail) => checkDuplicate(validEmail))
.flatMapAsync((uniqueEmail) => insertUser(uniqueEmail, name));
}
// Usage
const result = await register("[email protected]", "Alice");
result.fold(
(error) => console.log(`${error.field}: ${error.message}`),
(user) => console.log(`Welcome, ${user.name}!`)
);License
MIT
