Skip to content

Either

Either monad for functional, type-safe error handling. It represents a value that can be either a success (Right) or an error (Left), avoiding the use of exceptions.

How to import?

The library exposes the DEither and E namespaces from the main entry or via direct import (tree-shaking friendly), which lets you only load what you need.

typescript
import { DEither, E } from "@duplojs/utils";
import * as DEither from "@duplojs/utils/either";
import * as E from "@duplojs/utils/either";

What is an Either monad?

What is an Either monad?

INFO

An Either monad is a container that represents a value that can be in one of two states: success or error. It enables elegant, type-safe error handling without throwing exceptions.

The Either monad has two constructors:

  • E.left: represents an error (the "bad" side)
  • E.right: represents a valid value (the "good" side)
typescript
if (result > 0) {
  return E.right("success", result);
} else {
  return E.left("error", result);
}

WARNING

Order matters: Left is conventionally used for errors and Right for successes. Do not invert them in your business logic.

Advantages:

  • Type-safe: TypeScript forces you to handle both cases
  • No exceptions: Avoids try-catch and unpredictable behavior
  • Composable: Either operations can be chained with map, flatMap, etc.
  • Readable: Code becomes more explicit about error paths

TIP

You can chain Either operations for elegant error handling without nested conditionals.

The power of contextualization with Info

The power of contextualization with Info

INFO

The real strength of this library lies in the mandatory addition of an info to every state (success or error). This info stays both on the monad AND in the TypeScript typing, enabling precise, type-safe pattern matching.

The problem without info:

A monad can contain multiple different errors and multiple different successes. Without contextualization, you are forced to do generic pattern matching or re-validate to know what the monad really contains.

typescript
// Without info: ambiguous, what do we really have?
const result = someFunction();
if (E.isRight(result)) {
  // But which success? We don't really know
  const value = unwrap(result);
}

The solution with info:

The info is a literal string that contextualizes the output. It stays in the type, so TypeScript can help you during pattern matching.

typescript
const result = someFunction();

if (E.isRight(result) && E.hasInformation(result, "user.created")) {
	// TypeScript knows exactly which success!
	const newUser = unwrap(result);
} else if (E.isRight(result) && E.hasInformation(result, "user.updated")) {
	// Another success, completely different
	const updatedUser = unwrap(result);
} else if (E.isLeft(result) && E.hasInformation(result, "emailAlreadyExists")) {
	// A specific error, easy to handle
	const conflict = unwrap(result);
	...
}

Advantages of this approach:

  • No extra validation: The info is enough to precisely identify the state
  • Guaranteed exhaustiveness: TypeScript forces you to handle all possible cases
  • Clear semantics: The code documents itself about what happens
  • Avoids generic errors: No need for generic monads anymore, each case is explicit

WARNING

The info must be explicit and representative. Use clear names like "emailAlreadyExists", "validationFailed", "user.created" rather than generic codes.

Right constructors

Builds a typed EitherRight with mandatory business information (optional payload).

success

Shortcut to return a success right("success", value) in an expressive way.

ok

Returns a Right without a value (void) tagged with the literal information "ok".

Left constructors

left

Builds an EitherLeft by providing business information and optionally a value.

error

Shortcut to signal a typed error left("error", value).

fail

Returns a Left without payload tagged "fail" for generic failure cases.

Right checks

isRight

Type guard that checks whether a value is an EitherRight.

whenIsRight

Runs a function only when the input is Right, otherwise forwards the original value.

Left checks

isLeft

Type guard that detects an EitherLeft.

whenIsLeft

Allows applying a function when receiving a Left and then continuing the flow.

Right-oriented pipelines

rightPipe

Chains synchronous transformations as long as results are Right, and stops at the first Left.

rightAsyncPipe

Async version that accepts promises, Future, or Either and automatically stops on a Left.

group

Aggregates multiple synchronous Either and returns the first Left or an object of Right values.

asyncGroup

Async version of group that accepts promises and Future.

Other

hasInformation

Type guard based on the literal information to precisely target a business case.

whenHasInformation

Pattern matching that triggers a function when the information (or a list of infos) matches.

safeCallback

Runs a callback and captures exceptions into a Left<"callback">.

Boolean helpers

bool

Converts any value into a boolean Either (Right if truthy, Left if falsy) while preserving typing.

boolTruthy

Forces the creation of a Right<"bool"> by explicitly marking a truthy value.

boolFalsy

Builds a Left<"bool"> from a falsy value (undefined, null, "", 0, false).

isBoolTruthy

Specialized type guard for boolTruthy.

whenIsBoolTruthy

Triggers a function only when a value (or the result of bool) is truthy.

isBoolFalsy

Specialized type guard for boolFalsy.

whenIsBoolFalsy

Triggers a function only when a value (or the result of bool) is falsy.

Handling nullish values

nullish

Transforms a potentially null/undefined value into an Either, filled on the right if the value exists.

nullishEmpty

Explicitly builds a Left<"nullish"> with a null or undefined value.

nullishFilled

Builds a Right<"nullish"> from a defined value.

isNullishEmpty

Type guard to detect a nullishEmpty.

whenIsNullishEmpty

Applies a function only for the nullishEmpty case.

isNullishFilled

Type guard to detect a nullishFilled.

whenIsNullishFilled

Applies a function when the nullish value is actually defined (Right).

Handling nullable values

nullable

Wraps a possible null in an Either, which forces handling the absence of a value.

nullableEmpty

Builds a Left<"nullable"> containing null.

nullableFilled

Builds a Right<"nullable"> with a non-null value.

isNullableEmpty

Type guard for nullableEmpty.

whenIsNullableEmpty

Callback triggered only when the value is null.

isNullableFilled

Type guard for nullableFilled.

whenIsNullableFilled

Callback triggered when the nullable value is present (Right).

Handling optional values

optional

Wraps a possibly undefined value in an Either, useful for optional fields.

optionalEmpty

Builds a Left<"optional"> containing undefined.

optionalFilled

Builds a Right<"optional"> with a defined value.

isOptionalEmpty

Type guard for optionalEmpty.

whenIsOptionalEmpty

Callback triggered only when an optional is empty.

isOptionalFilled

Type guard for optionalFilled.

whenIsOptionalFilled

Callback triggered when an optional contains a value.

Futures and asynchronism

future

Converts a value (or an Either) into a Future, a class derived from Promise with Future.all support.

futureSuccess

Builds an EitherRight<"future"> to explicitly signal a successful resolution.

futureError

Builds an EitherLeft<"future"> to represent a standardized async error.

Released under the MIT license.