Skip to content
brix.v2 stable

Search documentation

70 guides

Documentation menu
Docs / APIs and data / Typed resultsRead Markdown ↗

Handle synchronous typed results

Model expected synchronous failures with tagged errors, narrow or match each branch, and transform or chain results without throwing.

Decide between a result and an exception

Return Result<T, E> for expected domain, validation, network, or provider outcomes. Throw for programmer errors, impossible states, and broken invariants. This keeps recoverable branches visible in types without converting every defect into normal control flow.

Import through Brix rather than installing or importing better-result directly:

import { Result, TaggedError } from "brix/result";
import type { Err, Ok, ResultPromise } from "brix/result";

The public entry point exports Result, TaggedError, resultPromise, and the ResultPromise, Ok, and Err types.

Model a tagged expected error

Tagged errors provide a stable discriminant and typed payload:

import { Result, TaggedError } from "brix/result";

class BookingNotFoundError extends TaggedError("BookingNotFoundError")<{
  bookingId: string;
}>() {}

type Booking = { id: string; reference: string };
const bookings = new Map<string, Booking>();

function findBooking(id: string): Result<Booking, BookingNotFoundError> {
  const booking = bookings.get(id);
  return booking ? Result.ok(booking) : Result.err(new BookingNotFoundError({ bookingId: id }));
}

Use distinct tags for failures callers treat differently. Include useful context, but do not put secrets into error payloads that may be logged or serialized.

Narrow or match a result

isErr() and isOk() narrow the corresponding branch:

const result = findBooking(bookingId);

if (result.isErr()) {
  return { status: 404, bookingId: result.error.bookingId };
}

return result.value;

Use match when both branches produce one output:

const label = result.match({
  ok: (booking) => booking.reference,
  err: () => "Missing booking",
});

Reserve unwrap() for tests or boundaries where failure should terminate the operation. unwrapOr(fallback) is appropriate for a genuine default, not for hiding a failure that needs user feedback or observability.

Transform and chain synchronously

map changes the success value and mapError changes the failure:

const summary = findBooking(bookingId)
  .map((booking) => ({ id: booking.id, reference: booking.reference }))
  .mapError((error) => ({ source: "booking", error }));

Use andThen when the next synchronous operation also returns a Result:

const confirmed = findBooking(bookingId).andThen((booking) =>
  booking.reference
    ? Result.ok({ booking, confirmed: true })
    : Result.err(new InvalidBookingError({ bookingId: booking.id })),
);

Use tap, tapError, or tapBoth for observation that must not alter either branch.

Continue with compose asynchronous typed results.