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.
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)
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.
// 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.
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
right
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.
