A tiny, zero-dependency library for handling synchronous and asynchronous results with robust error handling.
- 🛡️ Type-Safe: Makes errors first‑class values in the type system -
Result<T, E>infers and bubbles up bothTandE, letting you chain and handle failures without throwing. - 🔄 Seamless Sync/Async Context Boundaries: Provides
Resultfor synchronous operations andAsyncResultfor promise-based workflows, with seamless interoperability between them via chaining API methods. - 🛠️ Familiar, Fluent & Immutable API: Inspired by ECMAScript built‑ins - use chainable methods like
map,flatMap,catchandfinallyto compose workflows cleanly. - 📦 Zero-Dependency: Lightweight with no external dependencies.
npm install @jaydenfyi/tinyresulttinyresult provides a straightforward way to manage operations that can either succeed or fail. Here's a more comprehensive example that showcases chaining and asynchronous operations.
import { Result, AsyncResult } from "@jaydenfyi/tinyresult";
import { Octokit } from "@octokit/rest";
class ValidationError {
readonly _tag = "ValidationError";
constructor(readonly message: string) {}
}
class NetworkError {
readonly _tag = "NetworkError";
constructor(readonly status: number, readonly message: string) {}
}
class UserNotFoundError {
readonly _tag = "UserNotFoundError";
constructor(readonly username: string) {}
}
const octokit = new Octokit();
function getGithubUserBio(username: string) {
return (
// Step 1: Start with a synchronous value
Result.ok(username)
// Step 2: Validate the input
.flatMap((u) =>
!u || u.trim().length === 0
? Result.error(new ValidationError("Username cannot be empty"))
: Result.ok(u),
)
// Step 3: Log the valid input (side-effect)
.tap((u) => console.log(`Fetching bio for ${u}...`))
// Step 4: Switch to an async context to fetch data
.flatMap((u) =>
AsyncResult.fromPromise(
octokit.users.getByUsername({ username: u }),
(err) => new NetworkError(500, err.message),
),
)
// Step 5: Check the response and extract the data
.flatMap((response) =>
response.status !== 200
? Result.error(new NetworkError(response.status, "Failed to fetch user"))
: Result.ok(response.data),
)
// Step 6: Process the data
.flatMap((user) =>
!user.bio
? Result.error(new UserNotFoundError(user.login))
: Result.ok({ bio: user.bio, login: user.login }),
)
// Step 7: Transform the success value
.map(({ bio, login }) => ({
bio,
bioLength: bio.length,
user: login,
}))
// Step 8: Log any errors that occurred in the chain
.tapError((err) => console.error(`[Error Log]: ${err._tag}`))
);
}
async function run() {
const result = await getGithubUserBio("jaydenfyi");
result.match(
(data) => console.log("User Bio details:", data),
(error) => {
switch (error._tag) {
case "ValidationError":
console.error("Validation failed:", error.message);
break;
case "NetworkError":
console.error(`Network error (${error.status}):`, error.message);
break;
case "UserNotFoundError":
console.error(`Could not find a bio for user: ${error.username}`);
break;
}
},
);
}
run();Handles synchronous operations.
Result.ok<T>(value: T): Creates a success result.Result.error<E>(error: E): Creates a failure result.Result.try<T, E>(fn: () => T): Executes a function and catches any thrown error, returning aResult.Result.all<T, E>(iterable): Transforms an iterable ofResults into a singleResultof an array of values. If any result is an error, the first error is returned.Result.from<T, E>(resultLike): Creates aResultfrom aResultLikeobject ({ ok: boolean, ... }).Result.isResult(value): Checks if a value is aResultinstance.
.map<U>(fn): Maps aResult<T, E>toResult<U, E>by applying a function to a success value..flatMap<U, F>(fn): Maps aResult<T, E>toResult<U, F>by applying a function that returns aResult..mapError<F>(fn): Maps aResult<T, E>toResult<T, F>by applying a function to a failure value..flatMapError<U, F>(fn): Maps aResult<T, E>toResult<U, F>by applying a function that returns aResult..catch<U>(fn): Recovers from an error by applying a function that returns a new success value..tap(fn): Performs a side-effect with the success value without changing theResult..tapError(fn): Performs a side-effect with the error value without changing theResult..finally(fn): Performs a side-effect regardless of success or failure..match(onOk, onError): Unwraps theResult, executingonOkfor success oronErrorfor failure.
A promise-like structure for asynchronous operations that resolves to a Result.
AsyncResult.ok<T>(value: T): Creates anAsyncResultthat resolves to a success.AsyncResult.error<E>(error: E): Creates anAsyncResultthat resolves to a failure.AsyncResult.try<T, E>(fn: () => T | Promise<T>): Executes an async function and catches any thrown error.AsyncResult.all<T, E>(iterable): Similar toResult.allbut forAsyncResults.AsyncResult.from<T, E>(resultLike): Creates anAsyncResultfrom aResultLikeor aPromise<ResultLike>.AsyncResult.fromPromise<T, E>(promise): Creates anAsyncResultfrom aPromise.AsyncResult.isAsyncResult(value): Checks if a value is anAsyncResultinstance.
AsyncResult has the same chainable methods as Result (map, flatMap, etc.), but they operate on the promised Result and can accept async callbacks.
.then():AsyncResultis "thenable" and can be awaited directly in async functions..match(onOk, onError): Returns aPromisethat resolves with the result ofonOkoronError.