Skip to main content
Result<T, E> represents a value that is either a success (Ok<T>) or a failure (Err<E>). Every Result carries its error type in the signature — no implicit throw, no silent null. Both branches share the same set of chainable methods.
import { chas } from 'ts-chas';

// Ok branch — has .ok === true and .value
const success: chas.Result<number, never> = chas.ok(42);

// Err branch — has .ok === false and .error
const failure: chas.Result<never, string> = chas.err('something went wrong');

Creating Results

chas.ok(value)

Wraps a value in an Ok result.
value
T
required
The success value to wrap.
returns
Result<T, never>
A result in the Ok state containing value.
const result = chas.ok(42);
// result.ok === true
// result.value === 42

chas.err(error)

Wraps an error in an Err result.
error
E
required
The error value to wrap.
returns
Result<never, E>
A result in the Err state containing error.
const result = chas.err('not found');
// result.ok === false
// result.error === 'not found'

chas.okAsync(value)

Wraps a value (or Promise of a value) in a ResultAsync that resolves to Ok.
value
T | Promise<T>
required
The success value or promise to wrap.
returns
ResultAsync<T, never>
A ResultAsync that resolves to Ok(value).
const res = chas.okAsync(42);
const result = await res; // Ok(42)

chas.errAsync(error)

Wraps an error (or Promise of an error) in a ResultAsync that resolves to Err.
error
E | Promise<E>
required
The error value or promise to wrap.
returns
ResultAsync<never, E>
A ResultAsync that resolves to Err(error).
const res = chas.errAsync('request failed');
const result = await res; // Err('request failed')

chas.tryCatch(fn, mapErr?)

Calls a synchronous function that may throw and wraps the outcome in a Result. Returns Ok on success or Err if the function throws.
fn
() => T
required
The synchronous function to call.
mapErr
(error: unknown) => E
Maps the thrown value to your error type. When omitted, the error is typed as unknown.
returns
Result<T, E>
Ok(returnValue) or Err(mappedError).
const result = chas.tryCatch(
  () => JSON.parse('{"name":"Alice"}'),
  (e) => new Error(`Invalid JSON: ${e}`)
);

if (result.isOk()) {
  console.log(result.value.name); // 'Alice'
}

chas.fromPromise(promise, mapErr?)

Wraps a Promise in a ResultAsync. Resolves to Ok on fulfillment or Err on rejection.
promise
Promise<T>
required
The promise to wrap.
mapErr
(error: unknown) => E
Maps the rejection reason to your error type. When omitted, the error is typed as unknown.
returns
ResultAsync<T, E>
A ResultAsync wrapping the promise outcome.
const result = await chas.fromPromise(
  fetch('/api/user').then(r => r.json()),
  (e) => `Fetch failed: ${e}`
);

if (result.isOk()) {
  console.log(result.value); // parsed JSON body
}

Instance methods

Every Result<T, E> has the following methods regardless of whether it is Ok or Err.

.isOk()

Type guard that narrows to Ok<T>.
returns
boolean
true if the result is Ok<T>, false otherwise. When true, TypeScript narrows the type so result.value is accessible.
const result = chas.ok(5);

if (result.isOk()) {
  console.log(result.value); // 5 — TypeScript knows .value exists here
}

.isErr()

Type guard that narrows to Err<E>.
returns
boolean
true if the result is Err<E>, false otherwise. When true, TypeScript narrows the type so result.error is accessible.
const result = chas.err('not found');

if (result.isErr()) {
  console.log(result.error); // 'not found'
}

.map(fn)

Transforms the Ok value. Err passes through unchanged.
fn
(value: T) => U
required
Function applied to the Ok value.
returns
Result<U, E>
Ok(fn(value)) or the original Err.
const doubled = chas.ok(5).map(v => v * 2);
// Ok(10)

const passthrough = chas.err('oops').map(v => v * 2);
// Err('oops') — fn is never called

.mapErr(fn)

Transforms the Err error. Ok passes through unchanged.
fn
(error: E) => F
required
Function applied to the Err error.
returns
Result<T, F>
The original Ok or Err(fn(error)).
const result = chas.err('not_found').mapErr(e => e.toUpperCase());
// Err('NOT_FOUND')

.andThen(fn)

Chains another Result-returning function (flatMap / monadic bind). If Ok, calls fn with the value and returns its result. Err short-circuits.
fn
(value: T) => Result<U, F>
required
Function that receives the Ok value and returns a new Result.
returns
Result<U, E | F>
The result of fn, or the original Err.
function parseAge(raw: string): chas.Result<number, string> {
  const n = parseInt(raw, 10);
  return isNaN(n) ? chas.err('not a number') : chas.ok(n);
}

function validateAge(age: number): chas.Result<number, string> {
  return age >= 18 ? chas.ok(age) : chas.err('too young');
}

const result = parseAge('21').andThen(validateAge);
// Ok(21)

const failed = parseAge('abc').andThen(validateAge);
// Err('not a number') — validateAge is never called

.orElse(fn)

Recovers from an Err by calling fn with the error and returning its result. Ok passes through unchanged.
fn
(error: E) => Result<T2, F>
required
Function that receives the Err error and returns a recovery Result.
returns
Result<T | T2, F>
The original Ok, or the result of fn.
const result = chas.err('cache miss')
  .orElse(() => chas.ok('default value'));
// Ok('default value')

.tap(fn)

Runs a side effect when Ok. Returns the original result unchanged.
fn
(value: T) => void
required
Side-effect function called with the Ok value.
returns
Result<T, E>
The original result, unmodified.
const result = chas.ok(42)
  .tap(v => console.log('Got value:', v)) // logs 42
  .map(v => v * 2);
// Ok(84) — tap did not alter the value

.tapErr(fn)

Runs a side effect when Err. Returns the original result unchanged.
fn
(error: E) => void
required
Side-effect function called with the Err error.
returns
Result<T, E>
The original result, unmodified.
chas.err('upload failed')
  .tapErr(e => console.error('Error:', e))
  .mapErr(e => ({ message: e }));

.match({ ok, err })

Exhaustively handles both branches and returns a value. Unlike .map, this always produces a non-Result value.
ok
(value: T) => U
required
Handler called when the result is Ok.
err
(error: E) => F
required
Handler called when the result is Err.
returns
U | F
The return value of whichever handler ran.
const message = chas.ok(42).match({
  ok: v => `The answer is ${v}`,
  err: e => `Failed: ${e}`,
});
// 'The answer is 42'

.unwrap()

Returns the Ok value, or throws the Err error.
returns
T
The Ok value.
Throws the contained error if the result is Err. Prefer .unwrapOr(), .match(), or type-guarded access in production code.
const value = chas.ok(5).unwrap(); // 5
chas.err('oops').unwrap();          // throws 'oops'

.unwrapOr(defaultValue)

Returns the Ok value, or defaultValue if Err.
defaultValue
T2
required
Fallback value returned when the result is Err.
returns
T | T2
The Ok value or the default.
const value = chas.err('missing').unwrapOr(0);
// 0

.unwrapErr()

Returns the Err error, or throws if Ok.
returns
E
The Err error.
const error = chas.err('not found').unwrapErr();
// 'not found'

.toOption()

Discards the error and converts to an Option<T>. Ok becomes Some, Err becomes None.
returns
Option<T>
Some(value) when Ok, or None when Err.
const some = chas.ok(42).toOption();  // Some(42)
const none = chas.err('x').toOption(); // None

.pipe(fn1, fn2, ...)

Passes the result through a sequence of functions. Each function receives the output of the previous one. Up to 9 functions are supported with full type inference.
fn1, fn2, ...
(a: A) => B
required
Functions to apply in order.
returns
B
The output of the last function.
const add5 = (r: chas.Result<number, never>) => r.map(v => v + 5);
const double = (r: chas.Result<number, never>) => r.map(v => v * 2);

const result = chas.ok(1).pipe(add5, double);
// Ok(12)

.context(ctx)

Attaches a debug label or metadata object to the error when Err. Context entries are stored in error._context as an array, most recent first. No-op when Ok.
ctx
string | Record<string, unknown>
required
A description string or metadata object for the current step.
returns
Result<T, E>
The same result with context prepended to error._context (if Err).
const result = await fetchUser(id)
  .context('fetching user')
  .andThen(u => validatePermissions(u))
  .context('checking permissions');

if (result.isErr()) {
  console.log(result.error._context);
  // ['checking permissions', 'fetching user']
}

.catchTag(target, handler)

Catches a specific tagged error variant by its _tag string or error factory, calls handler with it, and removes that tag from the error union type. Unmatched tags pass through unchanged.
target
string | ErrorFactory
required
The _tag string to match, or an error factory created with chas.defineErrs.
handler
(error: MatchedError) => Result<T2, E2>
required
Recovery function. Return a new Result to replace the caught error.
returns
Result<T | T2, Exclude<E, { _tag: ... }> | E2>
A result with the matched tag removed from the error union.
const AppError = chas.defineErrs({
  NotFound: (resource: string) => ({ resource }),
  Unauthorized: () => ({}),
});

// fetchUser returns Result<User, NotFound | Unauthorized>
const result = getUser(id)
  .catchTag('NotFound', () => chas.ok(GUEST_USER));
// Result<User | GuestUser, Unauthorized> — NotFound is removed from the union

.tapTag(target, handler)

Runs a side effect for a specific tagged error, leaving the result unchanged. Use this for logging or telemetry on individual error variants.
target
string | ErrorFactory
required
The _tag string to match, or an error factory.
handler
(error: MatchedError) => void
required
Side-effect function called when the tag matches.
returns
Result<T, E>
The original result, unmodified.
getUser(id)
  .tapTag('NotFound', (e) => analytics.track('user_not_found', { id }))
  .tapTag('Unauthorized', () => redirectToLogin());