Skip to main content

The Result Type

The Result<T, E> type is the foundation of error handling in @hex-di/result. It represents an operation that can either succeed with a value of type T or fail with an error of type E.

Core Structure

Result<T, E>

A discriminated union of Ok<T, E> | Err<T, E>:

type Result<T, E> = Ok<T, E> | Err<T, E>;

Both variants share a rich API of methods for transforming, combining, and extracting values.

Ok<T, E> Variant

The success variant represents a successful operation.

Properties:

  • _tag: "Ok" — Discriminator for pattern matching
  • value: T — The success value
const success: Ok<number, never> = {
_tag: "Ok",
value: 42,
};

Err<T, E> Variant

The failure variant represents a failed operation.

Properties:

  • _tag: "Err" — Discriminator for pattern matching
  • error: E — The error value
const failure: Err<never, string> = {
_tag: "Err",
error: "operation failed",
};

Factory Functions

ok(value)

Creates a success result.

function ok<T>(value: T): Ok<T, never>;

The ok factory produces Ok<T, never> — the E phantom parameter widens to match any E in context.

import { ok } from "@hex-di/result";

const result = ok(42);
result.isOk(); // true
result.value; // 42

err(error)

Creates a failure result.

function err<E>(error: E): Err<never, E>;
import { err } from "@hex-di/result";

const result = err("not found");
result.isErr(); // true
result.error; // 'not found'

Type Guards

isResult(value)

Runtime type guard for synchronous results.

import { isResult } from "@hex-di/result";

if (isResult(maybeResult)) {
// maybeResult is Result<unknown, unknown>
if (maybeResult.isOk()) {
console.log(maybeResult.value);
}
}

isResultAsync(value)

Runtime type guard for ResultAsync.

import { isResultAsync } from "@hex-di/result";

if (isResultAsync(maybeResult)) {
// maybeResult is ResultAsync<unknown, unknown>
const result = await maybeResult;
console.log(result);
}

Result Methods

Both Ok and Err implement the same method set — the behavior differs by variant.

Type Guards

MethodDescription
isOk()true if Ok
isErr()true if Err
isOkAnd(predicate)true if Ok and predicate passes
isErrAnd(predicate)true if Err and predicate passes
const result = ok(5);
result.isOk(); // true
result.isOkAnd(n => n > 0); // true
result.isErr(); // false

Transformations

MethodOk behaviorErr behavior
map(f)Applies f to valueReturns self
mapErr(f)Returns selfApplies f to error
mapBoth(onOk, onErr)Applies onOkApplies onErr
flatten()Unwraps Ok<Result<U, E2>, E>Returns self
flip()Returns Err(value)Returns Ok(error)
const result = ok(5)
.map(n => n * 2) // Ok(10)
.mapErr(e => `Error: ${e}`) // Ok(10) - no-op on Ok
.mapBoth(
n => n.toString(), // Applied: "10"
e => e.toUpperCase() // Not applied
); // Ok("10")

Chaining

MethodOk behaviorErr behavior
andThen(f)Calls f(value)Returns self
orElse(f)Returns selfCalls f(error)
andTee(f)Calls f(value) (side-effect), returns selfReturns self
orTee(f)Returns selfCalls f(error) (side-effect), returns self
andThrough(f)Calls f(value), propagates Err if it failsReturns self
inspect(f)Calls f(value), returns selfReturns self
inspectErr(f)Returns selfCalls f(error), returns self
const result = ok(5)
.andThen(n => ok(n * 2)) // Ok(10)
.andTee(n => console.log(`Value: ${n}`)) // Logs: "Value: 10"
.orElse(e => ok(0)); // Ok(10) - no-op on Ok

Logical Combinators

MethodOk behaviorErr behavior
and(other)Returns otherReturns self
or(other)Returns selfReturns other
ok(5).and(ok(10)); // Ok(10)
ok(5).and(err("fail")); // Err("fail")
err("first").or(ok(10)); // Ok(10)
err("first").or(err("second")); // Err("second")

Extraction

MethodDescription
match(onOk, onErr)Pattern match — returns onOk(value) or onErr(error)
unwrapOr(default)Returns value or default
unwrapOrElse(f)Returns value or f(error)
mapOr(default, f)Returns f(value) or default
mapOrElse(defaultF, f)Returns f(value) or defaultF(error)
contains(value)true if Ok and value matches
containsErr(error)true if Err and error matches
expect(message)Returns value or throws with message
expectErr(message)Returns error or throws with message
const result: Result<number, string> = ok(5);

result.match(
value => `Success: ${value}`,
error => `Failed: ${error}`
); // "Success: 5"

result.unwrapOr(0); // 5
result.unwrapOrElse(e => -1); // 5
result.contains(5); // true

Conversion

MethodOkErr
toNullable()valuenull
toUndefined()valueundefined
intoTuple()[null, value][error, null]
merge()valueerror
toOption()Some(value)None
toOptionErr()NoneSome(error)
toJSON(){ _tag: "Ok", _schemaVersion: 1, value }{ _tag: "Err", _schemaVersion: 1, error }
ok(5).toNullable(); // 5
err("fail").toNullable(); // null

ok(5).intoTuple(); // [null, 5]
err("fail").intoTuple(); // ["fail", null]

ok(5).toOption(); // Some(5)
err("fail").toOption(); // None

Async Bridges

MethodDescription
toAsync()Wraps in ResultAsync
asyncMap(f)Maps with an async function
asyncAndThen(f)Chains with a function returning ResultAsync
const result = ok(5)
.asyncMap(async n => n * 2) // ResultAsync<number, never>
.then(r => console.log(r)); // Ok(10)

Generator Protocol

Result implements [Symbol.iterator], enabling use inside safeTry:

import { safeTry, ok, err } from "@hex-di/result";

const result = safeTry(function* () {
const a = yield* ok(10); // 10
const b = yield* err("!"); // short-circuits here
return ok(a + b); // never reached
});
// result = err('!')

Each yield* unwraps the Ok value or short-circuits the generator on Err, providing a linear flow for sequential operations that may fail.