Errors you can see.
Types you can trust.
Stop guessing what went wrong in catch blocks. Result makes errors visible, typed, and impossible to forget.
-- THE PROBLEM --
Try-catch gives you unknown. Result gives you types.
try { const user = findUser(id); const order = createOrder(user); return order.confirmation; } catch (e) { // e is `unknown` — what went wrong? // User not found? Order failed? DB down? console.error("something broke", e); }
import { ok, err } from '@hex-di/result'; const result = findUser(id); // ^? Result<User, NotFoundError> if (result.isOk()) { console.log(result.value.name); // ^? User — fully typed } else { console.log(result.error._tag); // ^? NotFoundError — never unknown }
:: quick start
Three steps. That's it.
npm install @hex-di/result
return b === 0 ? err("division by zero") : ok(a / b);
result.match(v => use(v), e => handle(e));
:: familiar
You already know this
If you've used .map() on an array or .then() on a Promise, you already know how Result works.
JS you know
if (response.ok)
Result equivalent
if (result.isOk())
Same check — but the types narrow automatically.
JS you know
array.map(x => ...)
Result equivalent
result.map(x => ...)
Transform the value inside, skip if empty/error.
JS you know
promise.then(x => ...)
Result equivalent
result.andThen(x => ...)
Chain async-like steps — errors short-circuit.
:: features
Why Result?
No More Try-Catch
Errors are values, not exceptions. Pattern match on success and failure paths with full type safety.
Errors Skip Automatically
When something fails, the rest of the chain is skipped. No nested if-checks needed.
Never Forget an Error
TypeScript tells you at build time if you missed handling an error case.
Zero Runtime Cost
Lightweight wrapper with no dependencies. Result<T, E> compiles away to simple objects.
Handle Errors by Name
Each error has a name. Handle them one by one — TypeScript tracks which ones are left.
Reusable Error Handlers
Write an error handler once, apply it anywhere. Compose multiple handlers into one — TypeScript tracks what's left.
:: pipeline
Chain operations, not try-catch blocks
Build error-handling pipelines with a fluent API. Each step is type-checked. Errors short-circuit automatically.
Start building type-safe error handling today
Replace try-catch with composable, typed pipelines. Zero dependencies. Full TypeScript inference.
:: going deeper
Master these patterns when you're ready.
:: api
50+ methods. One import.
Create results, chain operations, combine results, handle tagged errors — everything you need to stop throwing.
// constructors
ok / err
const a = ok(42); // Ok<number> const b = err('fail'); // Err<string>
Wrap values into the Result type
fromThrowable
const r = fromThrowable( () => JSON.parse(s), (e) => new ParseError(e), );
Catch exceptions as typed errors
fromNullable
const r = fromNullable( map.get(key), () => new NotFound(key), );
Convert null | undefined to Err
// chaining
map / mapErr
ok(2) .map(n => n * 10) // Ok(20) .mapErr(e => wrap(e)) // skipped
Transform the Ok or Err value
andThen / orElse
ok(id) .andThen(id => findUser(id)) .orElse(e => fallback(e))
Chain Result-returning functions
andTee / orTee
ok(user) .andTee(u => log('found', u)) .orTee(e => report(e))
Side-effects without changing the value
// combinators
all()
const r = all( fetchUser(id), fetchOrder(id), ); // Ok<[User, Order]>
All must succeed or first Err wins
zipOrAccumulate()
const r = zipOrAccumulate( validateName(name), validateAge(age), ); // Err<[E, ...E[]]> collects ALL
Accumulate all errors, no short-circuit
partition()
const [oks, errs] = partition( items.map(i => validate(i)) );
Split into successes and failures
// tagged error handling
catchTag()
result .catchTag("NotFound", (e) => ok(`Fallback: ${e.resource}`) ) // narrows error union
Handle one tagged error, narrow the type
catchTags()
result.catchTags({ NotFound: (e) => ok("default"), Timeout: (e) => ok("retry"), }) // handles multiple at once
Handle multiple tagged errors in one call
createErrorGroup()
const Http = createErrorGroup("Http"); const NotFound = Http.create("NotFound"); const e = NotFound({ url: "/api", status: 404 }); Http.is(e) // true
Two-level discriminated error families
:: tagged errors
Eliminate errors one tag at a time
Use catchTag and catchTags to progressively handle errors by their discriminant. TypeScript narrows the union after each handler — until nothing remains.
:: generators
Write straight-line code that bails on errors
Use safeTry with generator functions to write linear code that stops at the first error. Each yield* unwraps an Ok value or immediately returns the Err. No nesting, no callbacks, no .then() chains.
:: accumulate
Build typed objects field by field
Use bind and let_ to accumulate fields into a typed object. Each step can fail — and the full object type is inferred automatically.
bind adds a Result-producing field. let_ adds a pure value. Both accumulate into the same typed record.
:: effect system
Composable error handlers
Write a handler for each error type, then snap them together. Apply the combined handler to any Result — TypeScript proves which errors are left. Zero runtime cost.
:: ecosystem
Part of the HexDI stack
Result integrates seamlessly with HexDI adapters. Every factory returns Result<T, E> — errors are typed and composable across your entire dependency graph.
Start building type-safe error handling today
Replace try-catch with composable, typed pipelines. Zero dependencies. Full TypeScript inference.