Socket
Book a DemoSign in
Socket

okay-error

Package Overview
Dependencies
Maintainers
1
Versions
11
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

okay-error - npm Package Compare versions

Comparing version
0.2.1
to
1.0.1
+52
-277
dist/index.d.ts

@@ -1,186 +0,11 @@

/**
* Represents a successful computation result.
* Contains a value and methods for working with it in a chain.
*
* @typeparam ValueType - The type of the contained value
*/
export declare class Ok<ValueType> implements Iterable<ValueType> {
export type Ok<ValueType> = {
readonly ok: true;
readonly value: ValueType;
/** Discriminant property, always true for Ok results. */
readonly ok: true;
/**
* Creates a new Ok result containing the provided value.
*
* @param value - The successful result value
*/
constructor(value: ValueType);
get raw(): {
ok: boolean;
value: ValueType;
};
get error(): undefined;
/**
* Transform the contained value using the provided function.
* Similar to Array.map(), this lets you transform the value while staying in the Result context.
*
* @example
* ok(5).map(n => n * 2) // Ok with value 10
*
* @param fn - Function to transform the value
* @returns A new Ok containing the transformed value
*/
map<NewValue>(fn: (v: ValueType) => NewValue): Ok<NewValue>;
/**
* Transform the error in an error result. For Ok instances this is a no-op.
* Provided for API compatibility with Err.mapErr().
*
* @param _fn - Function to transform the error (ignored for Ok)
* @returns This same Ok instance
*/
mapErr<NewError>(_fn: (e: never) => NewError): Ok<ValueType>;
/**
* Chain another Result-returning operation after this one.
* This is useful for sequences of operations that might fail.
*
* @example
* ok(10).flatMap(n => n > 0 ? ok(n) : err('Negative')()) // Ok with value 10
*
* @param fn - Function that takes the value and returns a new Result
* @returns The Result returned by the function
*/
flatMap<NewValue, ErrorType>(fn: (v: ValueType) => Result<NewValue, ErrorType>): Result<NewValue, ErrorType>;
/**
* Pattern‑match on this Result.
* The `err` branch will never run for Ok, but must be provided for exhaustiveness.
*/
match<OnOk, OnErr>(arms: {
ok: (v: ValueType) => OnOk;
err: (e: never) => OnErr;
}): OnOk;
/**
* Extract the value from this Ok result.
* Safe to use when you know the Result is Ok.
*
* @example
* ok(42).unwrap() // 42
*
* @returns The contained value
*/
unwrap(): ValueType;
/**
* Extract the value or use a fallback if this is an Err.
* For Ok instances, this simply returns the contained value.
*
* @example
* ok(42).or(0) // 42
*
* @param _fallback - Value to use if this is an Err (unused for Ok)
* @returns The contained value
*/
or(_fallback: ValueType): ValueType;
/**
* Iteration: `for…of` yields the contained value once.
*/
[Symbol.iterator](): Iterator<ValueType>;
}
/**
* Represents a failed computation result.
* Contains an error value and methods for working with it in a chain.
*
* @typeparam ErrorType - The type of the error value
*/
export declare class Err<ErrorType = unknown> implements Iterable<never> {
readonly error?: undefined;
};
export type Err<ErrorType = unknown> = {
readonly ok: false;
readonly error: ErrorType;
/** Discriminant property, always false for Err results. */
readonly ok: false;
/**
* Creates a new Err result containing the provided error.
*
* @param error - The error value
*/
constructor(error: ErrorType);
get raw(): {
ok: boolean;
error: ErrorType;
};
get value(): undefined;
/**
* Add context to this error, creating a new error with a cause chain.
* This is particularly useful for wrapping lower-level errors with higher-level context.
*
* @example
* // Create an error chain
* const ioError = err('IO')({ errno: 'ENOENT' });
* const configError = ioError.annotate('ConfigFileMissing', { path: '/etc/app.json' });
*
* @param type - The type for the new error
* @param payload - Additional context properties for the new error
* @returns A new Err with this error as its cause
*/
annotate<K extends string, P extends Record<string, unknown> = Record<string, unknown>>(type: K, payload?: P): Err<{
type: K;
} & P & {
cause: ErrorType;
}>;
/**
* Transform the value in a successful result. For Err instances this is a no-op.
* This allows for chainable operations that safely skip over errors.
*
* @param _fn - Function to transform the value (ignored for Err)
* @returns This same Err instance
*/
map<NewValue>(_fn: (v: never) => NewValue): Err<ErrorType>;
/**
* Transform the contained error using the provided function.
* This is useful for adapting or enriching error information.
*
* @example
* err('NotFound')({ id: 123 }).mapErr(e => ({ ...e, message: `Item ${e.id} not found` }))
*
* @param fn - Function to transform the error
* @returns A new Err containing the transformed error
*/
mapErr<NewError>(fn: (e: ErrorType) => NewError): Err<NewError>;
/**
* Chain another Result-returning operation after this one.
* For Err instances, this is a no-op that propagates the error.
*
* @param _fn - Function that would map the value (ignored for Err)
* @returns This same Err instance
*/
flatMap<NewValue>(_fn: (v: never) => Result<NewValue, ErrorType>): Err<ErrorType>;
/**
* Pattern‑match on this Result.
* The `ok` branch will never run for Err, but must be provided.
*/
match<OnOk, OnErr>(arms: {
ok: (v: never) => OnOk;
err: (e: ErrorType) => OnErr;
}): OnErr;
/**
* Try to extract a value from this Err result.
* Since this is an Err, this always throws the contained error.
*
* @example
* err('NotFound')().unwrap() // Throws the error
*
* @throws The contained error
*/
unwrap(): never;
/**
* Extract the value or use a fallback if this is an Err.
* For Err instances, this returns the provided fallback.
*
* @example
* err('NotFound')().or(0) // 0
*
* @param fallback - Value to use instead of the error
* @returns The provided fallback value
*/
or<Fallback>(fallback: Fallback): Fallback;
/**
* Iteration: an error yields nothing, behaving like an empty collection.
*/
[Symbol.iterator](): Iterator<never>;
}
readonly value?: undefined;
};
/**

@@ -197,8 +22,4 @@ * Union type representing either a successful (Ok) or failed (Err) computation result.

* @example
* const success = ok(42);
* console.log(success.value); // 42
*
* @overload
* @param value - The value to wrap in an Ok result
* @returns An Ok result containing the value
* const success = ok(42)
* console.log(success.value) // 42
*/

@@ -210,111 +31,65 @@ export declare function ok<T>(value: T): Ok<T>;

* @example
* const success = ok();
* console.log(success.value); // undefined
*
* @overload
* @returns An Ok result containing undefined
* const success = ok()
* console.log(success.value) // undefined
*/
export declare function ok(): Ok<undefined>;
/**
* Construct a typed error Result with a discriminated error type and optional payload.
* Automatically captures a stack trace frame to help with debugging.
* Construct an error Result with a type and payload.
*
* @example
* // Create a timeout error with additional context
* const timeout = err('Timeout')({ ms: 2000 });
*
* // Create an error with just a type
* const notFound = err('NotFound')();
*
* @param type - The string discriminant type for the error
* @returns A function that takes an optional payload and returns an Err
* err('Timeout', { ms: 1000 }) // Err<{ type: 'Timeout', ms: 1000 }>
*/
export declare const err: <K extends string>(type: K) => <Payload extends Record<string, unknown> = Record<string, unknown>>(payload?: Payload) => Err<{
export declare function err<K extends string, P extends Record<string, unknown>>(type: K, payload: P): Err<{
type: K;
} & Payload>;
} & P>;
/**
* Wrap any error value in an Err result without adding trace information.
* Useful for working with existing error types or values that aren't in the Result format.
* Construct an error Result with only a type.
*
* @example
* try {
* // Some code that might throw
* } catch (error) {
* return errAny(error);
* }
*
* @param e - Any value to wrap as an error
* @returns An Err result containing the error value
* err('Timeout') // Err<{ type: 'Timeout' }>
*/
export declare const errAny: <E = unknown>(e: E) => Err<E>;
export declare function err<K extends string>(type: K): Err<{
type: K;
}>;
/**
* Wraps a synchronous function that might throw, converting exceptions into Result types.
* Construct an error Result from an arbitrary error value (object, string, etc).
* Type safety and pattern matching only work if the value is an object with a `type` property.
*
* @example
* const parsed = result(() => JSON.parse(json));
* if (parsed.ok) {
* console.log(parsed.value);
* } else {
* console.error('Parse error:', parsed.error);
* }
*
* @param work - A function that might throw an exception
* @returns A Result containing either the function's return value or the caught error
* err({ message: 'Something failed' })
* err(new Error('fail'))
*/
export declare function result<ValueType>(work: () => ValueType): Result<ValueType>;
export declare function err<K>(payload: K): Err<K>;
/**
* Wraps a Promise, converting fulfilled/rejected states into Result types.
* Helper to embed a cause error payload when constructing a new Err.
*
* @example
* const response = await result(fetch(url));
* if (!response.ok) {
* console.error('Network error:', response.error);
* return;
* }
*
* const data = await result(response.value.json());
*
* @param work - A Promise or Promise-like object
* @returns A Promise that resolves to a Result
* const low = err('Low')
* const high = err('High', cause(low))
*/
export declare function result<ValueType>(work: PromiseLike<ValueType>): Promise<Result<ValueType>>;
export declare function cause<T extends {
error: any;
}>(e: T): {
cause: T["error"];
};
export declare const map: <ValueType, NewValue, ErrorType = unknown>(r: Result<ValueType, ErrorType>, fn: (v: ValueType) => NewValue) => Result<NewValue, ErrorType>;
export declare const mapErr: <ValueType, ErrorType, NewError>(r: Result<ValueType, ErrorType>, fn: (e: ErrorType) => NewError) => Result<ValueType, NewError>;
export declare const flatMap: <ValueType, NewValue, ErrorType = unknown>(r: Result<ValueType, ErrorType>, fn: (v: ValueType) => Result<NewValue, ErrorType>) => Result<NewValue, ErrorType>;
export declare const unwrap: <ValueType, ErrorType = unknown>(r: Result<ValueType, ErrorType>) => ValueType;
export declare const orElse: <ValueType, ErrorType = unknown>(r: Result<ValueType, ErrorType>, fallback: ValueType) => ValueType;
export declare function result<ValueType>(work: () => ValueType): Result<ValueType>;
export declare function result<ValueType, ErrorType = unknown>(work: PromiseLike<ValueType>): Promise<Result<ValueType, ErrorType>>;
/**
* Re-hydrates a plain Result-like object with the proper prototype methods.
* Pattern match on a Result object (Ok/Err) or a discriminant string.
*
* @example
* // After JSON.parse, the Result has lost its methods
* const raw = JSON.parse(serializedResult);
*
* // Re-hydrate to get the methods back
* const live = result(raw);
* const value = live.unwrap();
*
* @param work - A plain object with Result shape ({ok: true, value} or {ok: false, error})
* @returns A proper Result instance with all methods
* Overloads:
* - match(result, { ok, err })
* - match(type, { A: fn, B: fn })
*/
export declare function result<ValueType, ErrorType>(work: Result<ValueType, ErrorType>): Result<ValueType, ErrorType>;
/**
* Exhaustively pattern‑match on the `type` discriminant of any object
* (error or otherwise). Works on unions like
*
* ```ts
* type Foo =
* | { type: "A"; a: number }
* | { type: "B"; b: string };
*
* declare const foo: Foo;
* const out = matchType(foo, {
* A: v => v.a,
* B: v => v.b.length,
* });
* ```
*
* If you omit a case, TypeScript raises an error.
*/
export declare function matchType<T extends {
type: string;
}, Cases extends {
[K in T["type"]]: (val: Extract<T, {
type: K;
}>) => unknown;
}>(value: T, cases: Cases): ReturnType<Cases[keyof Cases]>;
export declare function match<ValueType, ErrorType, OnOk, OnErr>(r: Result<ValueType, ErrorType>, arms: {
ok: (v: ValueType) => OnOk;
err: (e: ErrorType) => OnErr;
}): OnOk | OnErr;
export declare function match<T extends string & (string extends T ? never : unknown), Cases extends {
[K in T]: () => unknown;
}>(value: T, cases: Cases & Record<Exclude<keyof Cases, T>, never>): ReturnType<Cases[keyof Cases]>;

@@ -1,268 +0,39 @@

/* ── Core classes ─────────────────────────────────────────── */
/**
* Represents a successful computation result.
* Contains a value and methods for working with it in a chain.
*
* @typeparam ValueType - The type of the contained value
*/
export class Ok {
/**
* Creates a new Ok result containing the provided value.
*
* @param value - The successful result value
*/
constructor(value) {
this.value = value;
/** Discriminant property, always true for Ok results. */
this.ok = true;
}
get raw() {
return { ok: true, value: this.value };
}
get error() {
return undefined;
}
/**
* Transform the contained value using the provided function.
* Similar to Array.map(), this lets you transform the value while staying in the Result context.
*
* @example
* ok(5).map(n => n * 2) // Ok with value 10
*
* @param fn - Function to transform the value
* @returns A new Ok containing the transformed value
*/
map(fn) {
return new Ok(fn(this.value));
}
/**
* Transform the error in an error result. For Ok instances this is a no-op.
* Provided for API compatibility with Err.mapErr().
*
* @param _fn - Function to transform the error (ignored for Ok)
* @returns This same Ok instance
*/
mapErr(_fn) {
return this;
}
/**
* Chain another Result-returning operation after this one.
* This is useful for sequences of operations that might fail.
*
* @example
* ok(10).flatMap(n => n > 0 ? ok(n) : err('Negative')()) // Ok with value 10
*
* @param fn - Function that takes the value and returns a new Result
* @returns The Result returned by the function
*/
flatMap(fn) {
return fn(this.value);
}
/**
* Pattern‑match on this Result.
* The `err` branch will never run for Ok, but must be provided for exhaustiveness.
*/
match(arms) {
return arms.ok(this.value);
}
/**
* Extract the value from this Ok result.
* Safe to use when you know the Result is Ok.
*
* @example
* ok(42).unwrap() // 42
*
* @returns The contained value
*/
unwrap() {
return this.value;
}
/**
* Extract the value or use a fallback if this is an Err.
* For Ok instances, this simply returns the contained value.
*
* @example
* ok(42).or(0) // 42
*
* @param _fallback - Value to use if this is an Err (unused for Ok)
* @returns The contained value
*/
or(_fallback) {
return this.value;
}
/**
* Iteration: `for…of` yields the contained value once.
*/
*[Symbol.iterator]() {
yield this.value;
}
// ── Core Types ───────────────────────────────────────────
export function ok(value) {
return { ok: true, value: value };
}
/**
* Represents a failed computation result.
* Contains an error value and methods for working with it in a chain.
*
* @typeparam ErrorType - The type of the error value
*/
export class Err {
/**
* Creates a new Err result containing the provided error.
*
* @param error - The error value
*/
constructor(error) {
this.error = error;
/** Discriminant property, always false for Err results. */
this.ok = false;
export function err(typeOrPayload, payload) {
if (payload !== undefined) {
return { ok: false, error: { type: typeOrPayload, ...payload } };
}
get raw() {
return { ok: false, error: this.error };
}
get value() {
return undefined;
}
/**
* Add context to this error, creating a new error with a cause chain.
* This is particularly useful for wrapping lower-level errors with higher-level context.
*
* @example
* // Create an error chain
* const ioError = err('IO')({ errno: 'ENOENT' });
* const configError = ioError.annotate('ConfigFileMissing', { path: '/etc/app.json' });
*
* @param type - The type for the new error
* @param payload - Additional context properties for the new error
* @returns A new Err with this error as its cause
*/
annotate(type, payload = {}) {
return new Err({
type,
...payload,
cause: this.error,
});
}
/**
* Transform the value in a successful result. For Err instances this is a no-op.
* This allows for chainable operations that safely skip over errors.
*
* @param _fn - Function to transform the value (ignored for Err)
* @returns This same Err instance
*/
map(_fn) {
return this;
}
/**
* Transform the contained error using the provided function.
* This is useful for adapting or enriching error information.
*
* @example
* err('NotFound')({ id: 123 }).mapErr(e => ({ ...e, message: `Item ${e.id} not found` }))
*
* @param fn - Function to transform the error
* @returns A new Err containing the transformed error
*/
mapErr(fn) {
return new Err(fn(this.error));
}
/**
* Chain another Result-returning operation after this one.
* For Err instances, this is a no-op that propagates the error.
*
* @param _fn - Function that would map the value (ignored for Err)
* @returns This same Err instance
*/
flatMap(_fn) {
return this;
}
/**
* Pattern‑match on this Result.
* The `ok` branch will never run for Err, but must be provided.
*/
match(arms) {
return arms.err(this.error);
}
/**
* Try to extract a value from this Err result.
* Since this is an Err, this always throws the contained error.
*
* @example
* err('NotFound')().unwrap() // Throws the error
*
* @throws The contained error
*/
unwrap() {
throw this.error;
}
/**
* Extract the value or use a fallback if this is an Err.
* For Err instances, this returns the provided fallback.
*
* @example
* err('NotFound')().or(0) // 0
*
* @param fallback - Value to use instead of the error
* @returns The provided fallback value
*/
or(fallback) {
return fallback;
}
/**
* Iteration: an error yields nothing, behaving like an empty collection.
*/
*[Symbol.iterator]() {
// empty
}
return { ok: false, error: typeOrPayload };
}
/**
* Implementation of ok function
*/
export function ok(value) {
return new Ok(value);
}
/**
* Construct a typed error Result with a discriminated error type and optional payload.
* Automatically captures a stack trace frame to help with debugging.
* Helper to embed a cause error payload when constructing a new Err.
*
* @example
* // Create a timeout error with additional context
* const timeout = err('Timeout')({ ms: 2000 });
*
* // Create an error with just a type
* const notFound = err('NotFound')();
*
* @param type - The string discriminant type for the error
* @returns A function that takes an optional payload and returns an Err
* const low = err('Low')
* const high = err('High', cause(low))
*/
export const err = (type) => (payload = {}) => new Err({
type,
...payload,
});
/**
* Wrap any error value in an Err result without adding trace information.
* Useful for working with existing error types or values that aren't in the Result format.
*
* @example
* try {
* // Some code that might throw
* } catch (error) {
* return errAny(error);
* }
*
* @param e - Any value to wrap as an error
* @returns An Err result containing the error value
*/
export const errAny = (e) => new Err(e);
/* ── Universal factory / wrapper / rehydrator ─────────────── */
export function cause(e) {
return { cause: e.error };
}
// ── Utility Functions (functional style) ────────────────
export const map = (r, fn) => (r.ok ? ok(fn(r.value)) : r);
export const mapErr = (r, fn) => (r.ok ? r : err(fn(r.error)));
export const flatMap = (r, fn) => (r.ok ? fn(r.value) : r);
export const unwrap = (r) => {
if (r.ok)
return r.value;
throw r.error;
};
export const orElse = (r, fallback) => (r.ok ? r.value : fallback);
// ── Universal factory / wrapper / rehydrator ────────────
const isPromiseLike = (v) => typeof v === "object" && v !== null && "then" in v;
export function result(work) {
// Rehydrate
if (typeof work === "object" && work !== null && "ok" in work) {
return work.ok ? new Ok(work.value) : new Err(work.error);
}
// Promise‑like
if (isPromiseLike(work)) {
return Promise.resolve(work)
.then((ok))
.catch((e) => errAny(e));
.catch((e) => err(e));
}
// Sync fn
try {

@@ -272,29 +43,10 @@ return ok(work());

catch (e) {
return errAny(e);
return err(e);
}
}
/* -------------------------------------------------- */
/* Universal matchType */
/* -------------------------------------------------- */
/**
* Exhaustively pattern‑match on the `type` discriminant of any object
* (error or otherwise). Works on unions like
*
* ```ts
* type Foo =
* | { type: "A"; a: number }
* | { type: "B"; b: string };
*
* declare const foo: Foo;
* const out = matchType(foo, {
* A: v => v.a,
* B: v => v.b.length,
* });
* ```
*
* If you omit a case, TypeScript raises an error.
*/
export function matchType(value, cases) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return cases[value.type](value);
export function match(a, b) {
if (typeof a === "object" && a && "ok" in a) {
return a.ok ? b.ok(a.value) : b.err(a.error);
}
return b[a]();
}
+1
-1
{
"name": "okay-error",
"version": "0.2.1",
"version": "1.0.1",
"author": "Henry Mao",

@@ -5,0 +5,0 @@ "private": false,

+111
-85

@@ -13,8 +13,34 @@ # `okay-error`

* **Type‑level errors** - every possible failure is visible in the function signature (`Result<T, E>`), not thrown from the shadows. Rely on the type checker to ensure you handle every possible failure.
* **Cause‑chain built‑in** - wrap lower‑level errors with the `.annotate()` method; walk the `cause` links later to see the full logical call stack.
* **Iterable & ergonomic** - `for (const v of ok(3)) …` works, and helpers `map`, `flatMap`, `or` feel familiar to JS arrays.
* **Re‑hydration** - after `JSON.parse`, call `result(raw)` to get the fluent API back.
* **Cause‑chain built‑in** - link any parent error using the `cause()` helper; walk the `cause` links later to see the full logical call stack.
* **Ergonomic** - helpers `map`, `flatMap`, `or` feel familiar to JS arrays.
* **Re‑hydration** - after `JSON.parse`, call `result` to get a plain `Result` object.
---
## Table of Contents
- [`okay-error`](#okay-error)
- [Why *okay-error*?](#why-okay-error)
- [Table of Contents](#table-of-contents)
- [Install](#install)
- [Quick tour](#quick-tour)
- [From try-catch to Result](#from-try-catch-to-result)
- [Propagating context](#propagating-context)
- [How cause works](#how-cause-works)
- [Working with async operations](#working-with-async-operations)
- [Feature checklist](#feature-checklist)
- [API reference](#api-reference)
- [Constructors](#constructors)
- [Functions](#functions)
- [Types](#types)
- [JSON round‑trip example](#json-roundtrip-example)
- [Error with cause example](#error-with-cause-example)
- [The `cause()` helper](#the-cause-helper)
- [Pattern matching example](#pattern-matching-example)
- [Pattern matching with `match`](#pattern-matching-with-match)
- [Type Safety and Exhaustiveness](#type-safety-and-exhaustiveness)
- [License](#license)
---
## Install

@@ -46,3 +72,3 @@

// Alternative approach with Result
import { ok, err, result } from 'okay-error';
import { ok, err, result, annotate } from 'okay-error';

@@ -53,3 +79,3 @@ // Define functions that return Result types

if (id <= 0) {
return err('InvalidId')({ id });
return err('InvalidId', { id });
}

@@ -61,3 +87,3 @@ // Simulating database lookup

// Convert any unexpected errors
return err('DbError')({ cause: error });
return err('DbError', { cause: error });
}

@@ -80,3 +106,3 @@ }

? ok(`Hello ${name}!`) // Return Ok for success
: err('NameTooShort')({ min: 1 }) // Return Err for failure
: err('NameTooShort', { min: 1 }) // Return Err for failure
)

@@ -93,3 +119,3 @@ .or('Hi stranger!'); // Use fallback if any step failed

```ts
function readConfig(): Result<string, ConfigErr> { … }
function readConfig(): Result<string, ConfigErr> { /* ... */ }

@@ -100,3 +126,3 @@ function boot(): Result<void, BootErr> {

// Add higher-level context while preserving the original error
return cfg.annotate('BootConfig', { phase: 'init' });
return err('BootConfig', { phase: 'init', ...cause(cfg) });
}

@@ -107,11 +133,6 @@ return ok();

#### How annotation works
#### How cause works
`.annotate()` creates a new error that wraps the original error:
`cause` creates a new object `{ cause: error }` that can be spread into your error payload. This creates a discoverable, traceable error chain that's useful for debugging:
1. The original error becomes the `cause` property of the new error
2. Any additional payload properties are merged into the new error
This creates a discoverable, traceable error chain that's invaluable for debugging:
```plain

@@ -141,3 +162,3 @@ Err {

if (!response.ok) {
return response.annotate('NetworkError', { userId });
return annotate(response, 'NetworkError', { userId });
}

@@ -148,3 +169,3 @@

if (!data.ok) {
return data.annotate('ParseError', { userId });
return annotate(data, 'ParseError', { userId });
}

@@ -154,3 +175,3 @@

if (!data.value.name) {
return err('ValidationError')({
return err('ValidationError', {
userId,

@@ -193,10 +214,10 @@ message: 'User name is required'

| ✔ | Feature | Example |
|---|---------|---------|
| Typed constructors | `err('Timeout')({ ms: 2000 })` |
| `map`, `flatMap`, `or` | `ok(1).map(x=>x+1).flatMap(fn).or(0)` |
| Works with **Promise** | `await result(fetch(url))` |
| Cause‑chain + optional stack frame | `err(...).annotate('DB', {...})` |
| JSON serialisable & iterable | `JSON.stringify(err('X')())`, `[...ok(7)]` |
| Re‑hydrate after JSON | `const live = result(JSON.parse(raw))` |
| ✔ | Feature | Example |
| ---------------------------------- | --------------------------------------- | ------- |
| Typed constructors | `err({ type: 'Timeout', ms: 2000 })` or `err('Timeout', { ms: 2000 })` |
| `map`, `flatMap`, `or` | `ok(1).map(x=>x+1).flatMap(fn).or(0)` |
| Works with **Promise** | `await result(fetch(url))` |
| Cause‑chain + optional stack frame | `annotate(err(...), 'DB', {...})` |
| JSON serialisable | `JSON.stringify(err('X', {}))` |
| Re‑hydrate after JSON | `const plain = result(JSON.parse(raw))` |

@@ -209,26 +230,17 @@ ---

| function | purpose |
|----------|---------|
| `ok(value)` | success result |
| `err(kind)(payload?)` | typed error **+ trace** |
| `errAny(value)` | error without a discriminant / trace |
| `result(x)` | wrap a sync fn, a Promise, **or** re‑hydrate a raw object |
| function | purpose |
| --------------------- | --------------------------------------------------------- |
| `ok(value)` | success result |
| `err(type, payload?)` | typed error, payload is merged with `{ type }` |
| `err({ ... })` | error from arbitrary value (object, string, etc) |
| `result(x)` | wrap a sync fn, a Promise, **or** re‑hydrate a raw object |
### Instance methods (on `Ok` & `Err`)
### Functions
| method | on `Ok` | on `Err` |
|--------|---------|----------|
| `map(fn)` | transform value | no‑op |
| `mapErr(fn)` | no‑op | transform error |
| `flatMap(fn)` | chain another `Result` | propagate error |
| `match(arms)` | run `ok` arm | run `err` arm |
| `matchType(cases)` | N/A | match on error type |
| `unwrap()` | get value | throw error |
| `or(fallback)` | value | fallback |
| `[Symbol.iterator]()` | yields value | yields nothing |
| function | purpose |
| ---------------------------------- | --------------------------------------------------- |
| `cause(error)` | wrap an error as a cause for another error |
| `match(result, { ok, err })` | pattern match on Result (success/failure) |
| `match(type, cases)` | pattern match on a discriminant string (exhaustive) |
### Instance Methods
* `.annotate(kind, payload?)` – add context + cause (on `Err` instances only)
### Types

@@ -245,7 +257,6 @@

```ts
const errOut = err('DbConn')({ host: 'db.local' });
const errOut = err('DbConn', { host: 'db.local' }); // preferred
const raw = JSON.stringify(errOut);
const back = result(JSON.parse(raw)); // re‑hydrated
for (const v of back) console.log(v); // nothing, because Err
```

@@ -256,7 +267,16 @@

```ts
// Create an error chain
const ioError = err('IO')({ errno: 'ENOENT' });
const configError = ioError.annotate('ConfigFileMissing', { path: '/etc/app.json' });
const bootError = configError.annotate('BootConfig', { phase: 'init' });
import { err, cause } from 'okay-error';
// Preferred: use err(type, payload) and cause()
const ioError = err('IO', { errno: 'ENOENT' });
const configError = err('ConfigFileMissing', { path: '/etc/app.json', ...cause(ioError) });
const bootError = err('BootConfig', { phase: 'init', ...cause(configError) });
// You can also chain inline:
const chained = err('BootConfig', cause(
err('ConfigFileMissing', cause(
err('IO', { errno: 'ENOENT' })
))
));
// Now you can navigate the error chain

@@ -267,17 +287,30 @@ console.log(bootError.error.type); // 'BootConfig'

---
## The `cause()` helper
The `cause(error)` function is the idiomatic way to link any parent error as the cause of the current error—this parent could be a lower-level error, a related error, or any error that led to the current one:
```ts
const base = err('Base', { info: 123 })
const wrapped = err('Higher', { ...cause(base), context: 'extra' })
// wrapped.error.cause === base
```
This is preferred over annotate, and is composable for deep error chains.
## Pattern matching example
### Basic pattern matching with `match`
### Pattern matching with `match`
The `match` function is overloaded:
- Use `match(result, { ok, err })` to branch on Result objects.
- Use `match(type, { ...cases })` to branch on discriminant string unions (exhaustive, type-safe).
- `matchType` is now an alias for the discriminant string overload for backwards compatibility.
```ts
// Define a function that returns a Result
function divide(a: number, b: number): Result<number, { type: string; message: string }> {
if (b === 0) {
return err('DivideByZero')({ message: 'Cannot divide by zero' });
}
return ok(a / b);
}
// Use match to handle both success and error cases in one expression
const result = divide(10, 2).match({
// Result matching
const result = divide(10, 2);
const message = match(result, {
ok: (value) => `Result: ${value}`,

@@ -298,4 +331,7 @@ err: (error) => `Error: ${error.message}`

### Type-based pattern matching with `matchType`
### Type Safety and Exhaustiveness
When using `match` with a discriminant string union, TypeScript will enforce exhaustiveness, ensuring you handle all possible cases. This provides an additional layer of type safety for error handling.
```ts

@@ -310,22 +346,13 @@ // Define a discriminated union of error types

function fetchData(id: string): Result<{ name: string }, ApiError> {
// Simulate different errors based on input
if (id === '404') {
return err('NotFound')({ id });
} else if (id === 'slow') {
return err('Timeout')({ ms: 5000 });
} else if (id === 'auth') {
return err('Unauthorized')({ reason: 'Token expired' });
}
return ok({ name: 'Sample Data' });
// ...
}
// Use matchType to handle each error type differently
// Use match to handle each error type differently
const response = fetchData('slow');
if (!response.ok) {
const errorMessage = response.matchType({
NotFound: (e) => `Item ${e.id} could not be found`,
Timeout: (e) => `Request timed out after ${e.ms}ms`,
Unauthorized: (e) => `Access denied: ${e.reason}`
const errorMessage = match(response.error.type, {
NotFound: () => `Item ${response.error.id} could not be found`,
Timeout: () => `Request timed out after ${response.error.ms}ms`,
Unauthorized: () => `Access denied: ${response.error.reason}`
});

@@ -335,10 +362,9 @@

}
// Warning: match requires a discriminated union
// If you're not using a discriminated union, use match instead
```
The `matchType` method provides exhaustive pattern matching for discriminated union error types, ensuring you handle all possible error cases at compile time.
---
## License
MIT
+101
-376

@@ -1,245 +0,13 @@

/* ── Core classes ─────────────────────────────────────────── */
// ── Core Types ───────────────────────────────────────────
/**
* Represents a successful computation result.
* Contains a value and methods for working with it in a chain.
*
* @typeparam ValueType - The type of the contained value
*/
export class Ok<ValueType> implements Iterable<ValueType> {
/** Discriminant property, always true for Ok results. */
readonly ok = true as const
/**
* Creates a new Ok result containing the provided value.
*
* @param value - The successful result value
*/
constructor(public readonly value: ValueType) {}
get raw() {
return { ok: true, value: this.value }
}
get error(): undefined {
return undefined
}
/**
* Transform the contained value using the provided function.
* Similar to Array.map(), this lets you transform the value while staying in the Result context.
*
* @example
* ok(5).map(n => n * 2) // Ok with value 10
*
* @param fn - Function to transform the value
* @returns A new Ok containing the transformed value
*/
map<NewValue>(fn: (v: ValueType) => NewValue): Ok<NewValue> {
return new Ok(fn(this.value))
}
/**
* Transform the error in an error result. For Ok instances this is a no-op.
* Provided for API compatibility with Err.mapErr().
*
* @param _fn - Function to transform the error (ignored for Ok)
* @returns This same Ok instance
*/
mapErr<NewError>(_fn: (e: never) => NewError): Ok<ValueType> {
return this
}
/**
* Chain another Result-returning operation after this one.
* This is useful for sequences of operations that might fail.
*
* @example
* ok(10).flatMap(n => n > 0 ? ok(n) : err('Negative')()) // Ok with value 10
*
* @param fn - Function that takes the value and returns a new Result
* @returns The Result returned by the function
*/
flatMap<NewValue, ErrorType>(
fn: (v: ValueType) => Result<NewValue, ErrorType>,
): Result<NewValue, ErrorType> {
return fn(this.value)
}
/**
* Pattern‑match on this Result.
* The `err` branch will never run for Ok, but must be provided for exhaustiveness.
*/
match<OnOk, OnErr>(arms: {
ok: (v: ValueType) => OnOk
err: (e: never) => OnErr
}): OnOk {
return arms.ok(this.value)
}
/**
* Extract the value from this Ok result.
* Safe to use when you know the Result is Ok.
*
* @example
* ok(42).unwrap() // 42
*
* @returns The contained value
*/
unwrap(): ValueType {
return this.value
}
/**
* Extract the value or use a fallback if this is an Err.
* For Ok instances, this simply returns the contained value.
*
* @example
* ok(42).or(0) // 42
*
* @param _fallback - Value to use if this is an Err (unused for Ok)
* @returns The contained value
*/
or(_fallback: ValueType): ValueType {
return this.value
}
/**
* Iteration: `for…of` yields the contained value once.
*/
*[Symbol.iterator](): Iterator<ValueType> {
yield this.value
}
export type Ok<ValueType> = {
readonly ok: true
readonly value: ValueType
readonly error?: undefined
}
/**
* Represents a failed computation result.
* Contains an error value and methods for working with it in a chain.
*
* @typeparam ErrorType - The type of the error value
*/
export class Err<ErrorType = unknown> implements Iterable<never> {
/** Discriminant property, always false for Err results. */
readonly ok = false as const
/**
* Creates a new Err result containing the provided error.
*
* @param error - The error value
*/
constructor(public readonly error: ErrorType) {}
get raw() {
return { ok: false, error: this.error }
}
get value(): undefined {
return undefined
}
/**
* Add context to this error, creating a new error with a cause chain.
* This is particularly useful for wrapping lower-level errors with higher-level context.
*
* @example
* // Create an error chain
* const ioError = err('IO')({ errno: 'ENOENT' });
* const configError = ioError.annotate('ConfigFileMissing', { path: '/etc/app.json' });
*
* @param type - The type for the new error
* @param payload - Additional context properties for the new error
* @returns A new Err with this error as its cause
*/
annotate<
K extends string,
P extends Record<string, unknown> = Record<string, unknown>,
>(type: K, payload: P = {} as P) {
return new Err({
type,
...payload,
cause: this.error,
})
}
/**
* Transform the value in a successful result. For Err instances this is a no-op.
* This allows for chainable operations that safely skip over errors.
*
* @param _fn - Function to transform the value (ignored for Err)
* @returns This same Err instance
*/
map<NewValue>(_fn: (v: never) => NewValue): Err<ErrorType> {
return this
}
/**
* Transform the contained error using the provided function.
* This is useful for adapting or enriching error information.
*
* @example
* err('NotFound')({ id: 123 }).mapErr(e => ({ ...e, message: `Item ${e.id} not found` }))
*
* @param fn - Function to transform the error
* @returns A new Err containing the transformed error
*/
mapErr<NewError>(fn: (e: ErrorType) => NewError): Err<NewError> {
return new Err(fn(this.error))
}
/**
* Chain another Result-returning operation after this one.
* For Err instances, this is a no-op that propagates the error.
*
* @param _fn - Function that would map the value (ignored for Err)
* @returns This same Err instance
*/
flatMap<NewValue>(
_fn: (v: never) => Result<NewValue, ErrorType>,
): Err<ErrorType> {
return this
}
/**
* Pattern‑match on this Result.
* The `ok` branch will never run for Err, but must be provided.
*/
match<OnOk, OnErr>(arms: {
ok: (v: never) => OnOk
err: (e: ErrorType) => OnErr
}): OnErr {
return arms.err(this.error)
}
/**
* Try to extract a value from this Err result.
* Since this is an Err, this always throws the contained error.
*
* @example
* err('NotFound')().unwrap() // Throws the error
*
* @throws The contained error
*/
unwrap(): never {
throw this.error
}
/**
* Extract the value or use a fallback if this is an Err.
* For Err instances, this returns the provided fallback.
*
* @example
* err('NotFound')().or(0) // 0
*
* @param fallback - Value to use instead of the error
* @returns The provided fallback value
*/
or<Fallback>(fallback: Fallback): Fallback {
return fallback
}
/**
* Iteration: an error yields nothing, behaving like an empty collection.
*/
*[Symbol.iterator](): Iterator<never> {
// empty
}
export type Err<ErrorType = unknown> = {
readonly ok: false
readonly error: ErrorType
readonly value?: undefined
}

@@ -257,3 +25,3 @@

/* ── Constructors ─────────────────────────────────────────── */
// ── Constructors ─────────────────────────────────────────

@@ -264,8 +32,4 @@ /**

* @example
* const success = ok(42);
* console.log(success.value); // 42
*
* @overload
* @param value - The value to wrap in an Ok result
* @returns An Ok result containing the value
* const success = ok(42)
* console.log(success.value) // 42
*/

@@ -277,171 +41,132 @@ export function ok<T>(value: T): Ok<T>

* @example
* const success = ok();
* console.log(success.value); // undefined
*
* @overload
* @returns An Ok result containing undefined
* const success = ok()
* console.log(success.value) // undefined
*/
export function ok(): Ok<undefined>
/**
* Implementation of ok function
*/
export function ok<T>(value?: T): Ok<T> {
return new Ok(value as T)
return { ok: true, value: value as T }
}
/**
* Construct a typed error Result with a discriminated error type and optional payload.
* Automatically captures a stack trace frame to help with debugging.
* Construct an error Result with a type and payload.
*
* @example
* // Create a timeout error with additional context
* const timeout = err('Timeout')({ ms: 2000 });
*
* // Create an error with just a type
* const notFound = err('NotFound')();
*
* @param type - The string discriminant type for the error
* @returns A function that takes an optional payload and returns an Err
* err('Timeout', { ms: 1000 }) // Err<{ type: 'Timeout', ms: 1000 }>
*/
export const err =
<K extends string>(type: K) =>
<Payload extends Record<string, unknown> = Record<string, unknown>>(
payload: Payload = {} as Payload,
) =>
new Err({
type,
...payload,
})
export function err<K extends string, P extends Record<string, unknown>>(
type: K,
payload: P,
): Err<{ type: K } & P>
/**
* Wrap any error value in an Err result without adding trace information.
* Useful for working with existing error types or values that aren't in the Result format.
* Construct an error Result with only a type.
*
* @example
* try {
* // Some code that might throw
* } catch (error) {
* return errAny(error);
* }
*
* @param e - Any value to wrap as an error
* @returns An Err result containing the error value
* err('Timeout') // Err<{ type: 'Timeout' }>
*/
export const errAny = <E = unknown>(e: E): Err<E> => new Err(e)
export function err<K extends string>(type: K): Err<{ type: K }>
/* ── Universal factory / wrapper / rehydrator ─────────────── */
const isPromiseLike = <T>(v: unknown): v is PromiseLike<T> =>
typeof v === "object" && v !== null && "then" in v
/**
* Wraps a synchronous function that might throw, converting exceptions into Result types.
* Construct an error Result from an arbitrary error value (object, string, etc).
* Type safety and pattern matching only work if the value is an object with a `type` property.
*
* @example
* const parsed = result(() => JSON.parse(json));
* if (parsed.ok) {
* console.log(parsed.value);
* } else {
* console.error('Parse error:', parsed.error);
* }
*
* @param work - A function that might throw an exception
* @returns A Result containing either the function's return value or the caught error
* err({ message: 'Something failed' })
* err(new Error('fail'))
*/
export function result<ValueType>(work: () => ValueType): Result<ValueType>
export function err<K>(payload: K): Err<K>
export function err(typeOrPayload: any, payload?: any): Err<any> {
if (payload !== undefined) {
return { ok: false, error: { type: typeOrPayload, ...payload } }
}
return { ok: false, error: typeOrPayload }
}
/**
* Wraps a Promise, converting fulfilled/rejected states into Result types.
* Helper to embed a cause error payload when constructing a new Err.
*
* @example
* const response = await result(fetch(url));
* if (!response.ok) {
* console.error('Network error:', response.error);
* return;
* }
*
* const data = await result(response.value.json());
*
* @param work - A Promise or Promise-like object
* @returns A Promise that resolves to a Result
* const low = err('Low')
* const high = err('High', cause(low))
*/
export function result<ValueType>(
export function cause<T extends { error: any }>(e: T): { cause: T["error"] } {
return { cause: e.error }
}
// ── Utility Functions (functional style) ────────────────
export const map = <ValueType, NewValue, ErrorType = unknown>(
r: Result<ValueType, ErrorType>,
fn: (v: ValueType) => NewValue,
): Result<NewValue, ErrorType> => (r.ok ? ok(fn(r.value)) : r)
export const mapErr = <ValueType, ErrorType, NewError>(
r: Result<ValueType, ErrorType>,
fn: (e: ErrorType) => NewError,
): Result<ValueType, NewError> => (r.ok ? r : err(fn(r.error)))
export const flatMap = <ValueType, NewValue, ErrorType = unknown>(
r: Result<ValueType, ErrorType>,
fn: (v: ValueType) => Result<NewValue, ErrorType>,
): Result<NewValue, ErrorType> => (r.ok ? fn(r.value) : r)
export const unwrap = <ValueType, ErrorType = unknown>(
r: Result<ValueType, ErrorType>,
): ValueType => {
if (r.ok) return r.value
throw r.error
}
export const orElse = <ValueType, ErrorType = unknown>(
r: Result<ValueType, ErrorType>,
fallback: ValueType,
): ValueType => (r.ok ? r.value : fallback)
// ── Universal factory / wrapper / rehydrator ────────────
const isPromiseLike = <T>(v: unknown): v is PromiseLike<T> =>
typeof v === "object" && v !== null && "then" in v
export function result<ValueType>(work: () => ValueType): Result<ValueType>
export function result<ValueType, ErrorType = unknown>(
work: PromiseLike<ValueType>,
): Promise<Result<ValueType>>
/**
* Re-hydrates a plain Result-like object with the proper prototype methods.
*
* @example
* // After JSON.parse, the Result has lost its methods
* const raw = JSON.parse(serializedResult);
*
* // Re-hydrate to get the methods back
* const live = result(raw);
* const value = live.unwrap();
*
* @param work - A plain object with Result shape ({ok: true, value} or {ok: false, error})
* @returns A proper Result instance with all methods
*/
): Promise<Result<ValueType, ErrorType>>
export function result<ValueType, ErrorType>(
work: Result<ValueType, ErrorType>,
): Result<ValueType, ErrorType>
export function result<ValueType, ErrorType>(
work:
| (() => ValueType)
| PromiseLike<ValueType>
| Result<ValueType, ErrorType>,
work: (() => ValueType) | PromiseLike<ValueType>,
): Result<ValueType, ErrorType> | Promise<Result<ValueType, ErrorType>> {
// Rehydrate
if (typeof work === "object" && work !== null && "ok" in work) {
return work.ok ? new Ok(work.value) : new Err(work.error)
}
// Promise‑like
if (isPromiseLike<ValueType>(work)) {
return Promise.resolve(work)
.then(ok<ValueType>)
.catch((e: unknown) => errAny<ErrorType>(e as ErrorType))
.catch((e: unknown) => err(e as ErrorType))
}
// Sync fn
try {
return ok((work as () => ValueType)())
} catch (e) {
return errAny<ErrorType>(e as ErrorType)
return err(e as ErrorType)
}
}
/* -------------------------------------------------- */
/* Universal matchType */
/* -------------------------------------------------- */
/**
* Exhaustively pattern‑match on the `type` discriminant of any object
* (error or otherwise). Works on unions like
* Pattern match on a Result object (Ok/Err) or a discriminant string.
*
* ```ts
* type Foo =
* | { type: "A"; a: number }
* | { type: "B"; b: string };
*
* declare const foo: Foo;
* const out = matchType(foo, {
* A: v => v.a,
* B: v => v.b.length,
* });
* ```
*
* If you omit a case, TypeScript raises an error.
* Overloads:
* - match(result, { ok, err })
* - match(type, { A: fn, B: fn })
*/
export function matchType<
// Discriminated‑union we’re matching on
T extends { type: string },
// Exhaustive handler object – must have a key for every `type`
Cases extends {
[K in T["type"]]: (val: Extract<T, { type: K }>) => unknown
},
>(value: T, cases: Cases): ReturnType<Cases[keyof Cases]> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (cases as any)[value.type](value as any)
export function match<ValueType, ErrorType, OnOk, OnErr>(
r: Result<ValueType, ErrorType>,
arms: { ok: (v: ValueType) => OnOk; err: (e: ErrorType) => OnErr },
): OnOk | OnErr
export function match<
T extends string & (string extends T ? never : unknown),
Cases extends { [K in T]: () => unknown },
>(
value: T,
cases: Cases & Record<Exclude<keyof Cases, T>, never>,
): ReturnType<Cases[keyof Cases]>
export function match(a: any, b: any): any {
if (typeof a === "object" && a && "ok" in a) {
return a.ok ? b.ok(a.value) : b.err(a.error)
}
return b[a]()
}