Обзор

Map Error

Map domain errors without catching runtime errors.

errorscomposition

Install

$
Terminal window
pnpm dlx shadcn@latest add https://archyn.net/r/map-error.json

Copy the files below into the target paths shown in each tab.

ts@lib/flow/map-error.ts
import { type Awaitable, err } from "./core/result";
import { type Behavior, type RuntimeContext, isRuntimeError, runBehavior } from "./core/runtime";
/**
* Maps expected domain errors without catching runtime errors.
*
* Successful results pass through unchanged. Runtime errors such as cancellation and thrown
* exceptions bypass the mapper so only expected failures are transformed.
*
* @typeParam I - Source input type.
* @typeParam O - Source output type.
* @typeParam E - Source domain error type.
* @typeParam E2 - Mapped domain error type.
* @typeParam C - Custom context type.
* @param source - Behavior whose domain errors should be mapped.
* @param mapper - Callback invoked for source domain errors.
* @returns A behavior with the same output type and mapped domain error type.
*
* @example
* ```ts
* const publicLoad = mapError(loadUser, (error) => ({ kind: "load-user", cause: error }));
* ```
*/
export function mapError<I, O, E, E2, C extends object>(
source: Behavior<I, O, E, C>,
mapper: (error: E, context: RuntimeContext<C>) => Awaitable<E2>,
): Behavior<I, O, E2, C> {
return async (input, context) => {
const result = await runBehavior(source, input, context);
if (result.ok) {
return result;
}
if (isRuntimeError(result.error)) {
return err(result.error);
}
return err(await mapper(result.error, context));
};
}

Update the import paths to match your project setup.

Transform typed domain errors without catching runtime errors.

Usage

mapError changes the domain error shape of a Behavior. It is useful at module boundaries where lower-level errors need to become higher-level failures.

Use it when:

  • Convert infrastructure failures into feature-level errors.
  • Keep successful output unchanged.
  • Avoid accidentally catching cancellation or thrown runtime errors.
import { mapError } from "@/lib/flow/map-error";
import { task } from "@/lib/flow/task";
type DbError = { kind: "not-found" };
type ProfileError = { kind: "profile-missing" };
const loadProfile = task<string, { id: string }, DbError>(async (id, context) =>
id ? { id } : context.fail({ kind: "not-found" }),
);
const publicLoadProfile = mapError<string, { id: string }, DbError, ProfileError, {}>(
loadProfile,
() => ({ kind: "profile-missing" }),
);

Behavior

On success, mapError returns the source result. On domain failure, it calls mapper and returns the mapped error.

Runtime errors are returned unchanged and never passed to mapper, which prevents cancellation and thrown exceptions from being converted into domain errors.

Types

The success output is preserved and the domain error type is replaced.

Import from @/lib/flow/map-error.

Composition Notes

Use mapError at module boundaries to translate low-level errors into public domain errors.

Do not use mapError to recover from a failure; use recover or catchKind.

Source

ts@lib/flow/map-error.ts
import { type Awaitable, err } from "./core/result";
import { type Behavior, type RuntimeContext, isRuntimeError, runBehavior } from "./core/runtime";
/**
* Maps expected domain errors without catching runtime errors.
*
* Successful results pass through unchanged. Runtime errors such as cancellation and thrown
* exceptions bypass the mapper so only expected failures are transformed.
*
* @typeParam I - Source input type.
* @typeParam O - Source output type.
* @typeParam E - Source domain error type.
* @typeParam E2 - Mapped domain error type.
* @typeParam C - Custom context type.
* @param source - Behavior whose domain errors should be mapped.
* @param mapper - Callback invoked for source domain errors.
* @returns A behavior with the same output type and mapped domain error type.
*
* @example
* ```ts
* const publicLoad = mapError(loadUser, (error) => ({ kind: "load-user", cause: error }));
* ```
*/
export function mapError<I, O, E, E2, C extends object>(
source: Behavior<I, O, E, C>,
mapper: (error: E, context: RuntimeContext<C>) => Awaitable<E2>,
): Behavior<I, O, E2, C> {
return async (input, context) => {
const result = await runBehavior(source, input, context);
if (result.ok) {
return result;
}
if (isRuntimeError(result.error)) {
return err(result.error);
}
return err(await mapper(result.error, context));
};
}