@davemanufor/resultify
v2.1.0
Published
A lightweight, type-safe Result type for functional error handling in TypeScript. Provides Ok and Err variants for clear, expressive success and failure management in your code.
Maintainers
Readme
Table of Contents
- Features
- Installation
- Quick Start
- API Reference
- Async Pipelines
- Jest Matchers
- Type Safety
- Examples
- Contributing
- License
Features
- Type-safe — Explicit
Ok/Errhandling with full TypeScript inference. - Any error type —
Ecan be a string code, discriminated union, plain object, orError. - Composable —
flatMap,mapErr,match,tap, and more for expressive pipelines. - Async-first —
Result.fromPromise,mapAsync, and chainableResultAsync. - Zero runtime dependencies — Minimal footprint, tree-shakeable (
sideEffects: false). - Test helpers — Optional Jest matchers via
@davemanufor/resultify/jest.
Installation
npm install @davemanufor/resultifyRequires Node.js 18+.
Quick Start
import { Result } from "@davemanufor/resultify";
type AppError =
| { type: "NOT_FOUND"; id: string }
| { type: "VALIDATION"; fields: string[] };
function findUser(id: string): Result<User, AppError> {
const user = db.get(id);
return Result.fromNullable(user, { type: "NOT_FOUND", id });
}
const message = findUser("42").match({
ok: (result) => `Hello ${result.value.name}`,
err: (result) =>
result.error.type === "NOT_FOUND"
? `User ${result.error.id} not found`
: "Validation failed",
});API Reference
Entry points
| Import | Use when |
|--------|----------|
| @davemanufor/resultify | Sync Result, Ok, Err, guards |
| @davemanufor/resultify/async | ResultAsync pipelines |
| @davemanufor/resultify/jest | Jest custom matchers (dev/test only) |
Types
Result<T, E>— Union ofOk<T, E>|Err<T, E>Ok<T, E>— Success variant withreadonly value: TErr<T, E>— Failure variant withreadonly error: E
Factory (Result)
| Method | Description |
|--------|-------------|
| Result.ok(value) | Create an Ok |
| Result.err(error) | Create an Err |
| Result.try(fn) | Wrap a sync function; Err on throw |
| Result.fromThrowable(fn) | Alias for Result.try |
| Result.fromNullable(value, error) | null / undefined → Err |
| Result.all(results) | Short-circuit combine (like Promise.all) |
| Result.combine(results) | Alias for Result.all |
| Result.allSettled(results) | Collect all outcomes like Promise.allSettled |
| Result.fromJSON(json) | Reconstruct a Result from toJSON() output |
| Result.partition(results) | Split into [oks, errs] |
| Result.any(results) | First Ok, or Err of all errors |
| Result.fromPromise(promise, mapError?) | Wrap a Promise |
| Result.fromAsync(fn, mapError?) | Wrap an async function |
Instance methods
| Method | Description |
|--------|-------------|
| isOk() / isErr() / isError() | Type-narrowing guards (isError aliases isErr) |
| unwrap() | Get value; throws on Err (with cause) |
| unwrapError() | Get error; throws on Ok |
| throwError() | Throw the error on Err |
| safeUnwrap() | T \| undefined without throwing |
| map(fn) | Transform Ok value |
| flatMap(fn) / andThen(fn) | Chain Result-returning functions |
| mapErr(fn) | Transform error type |
| orElse(fn) / or(other) | Fallback on Err |
| unwrapOr(default) / unwrapOrElse(fn) | Safe unwrap with default |
| match({ ok, err }) | Exhaustive pattern match |
| and(other) | Return other on Ok |
| flatten() | Unwrap nested Result |
| mapAsync(fn) / flatMapAsync(fn) | Async map / flatMap |
| toJSON() | { ok, value } or { ok, error } |
| toString() | Ok(42) / Err(message) |
| toPromise() | Resolve on Ok, reject on Err |
| toNullable() / toOption() | T \| null |
| tap(fn) / tapErr(fn) | Side effects without breaking chain |
| contains(value) / containsErr(error) | Strict equality check |
| equals(other) | Strict equality (===) |
| deepEquals(other) | Deep structural equality |
| [Symbol.iterator]() | Destructure as [value, error] |
Type guards
import { isOk, isErr } from "@davemanufor/resultify";
const successes = results.filter(isOk);Async Pipelines
import { ResultAsync } from "@davemanufor/resultify/async";
const html = await ResultAsync.fromPromise(fetch("/api/user"))
.flatMap((res) => ResultAsync.fromPromise(res.json()))
.match({
ok: (result) => render(result.value),
err: (result) => showError(result.error),
});
const batch = await ResultAsync.all([
ResultAsync.fromPromise(fetchUser("1")),
ResultAsync.fromPromise(fetchUser("2")),
]).toResult();Jest Matchers
// jest.setup.ts
import "@davemanufor/resultify/jest";expect(result).toBeOk();
expect(result).toBeErr();
expect(result).toBeOkWith(42);
expect(result).toBeErrWith("not-found");Type Safety
E is not constrained to Error — use structured error types:
type ParseError = { code: "INVALID_JSON" } | { code: "EMPTY_INPUT" };
const parsed = Result.try(() => JSON.parse(raw)).mapErr(
(): ParseError => ({ code: "INVALID_JSON" }),
);Examples
Chaining
const result = Result.ok("42")
.flatMap((s) => Result.try(() => parseInt(s, 10)))
.flatMap((n) => (n > 0 ? Result.ok(n) : Result.err("non-positive")));Form validation
const result = Result.all([
validateName(name),
validateEmail(email),
validateAge(age),
]);
// Result<[string, string, number], ValidationError>Publishing
npm run publish:provenancePublishes with npm provenance attestation (requires CI OIDC).
Contributing
Contributions, issues, and feature requests are welcome! See issues.
License
MIT © David Manufor
