Skip to main content

zygos Zygos

ζυγός - "balance"

Functional error handling. Lightweight alternatives to Neverthrow and fp-ts (Either, Task, TaskEither).

Zygos brings functional error handling patterns to TypeScript without the weight of a full FP library. Instead of try/catch blocks that lose type information, Zygos uses Result, Option, and Either types to make errors explicit in your function signatures. Every failure path is visible to the compiler, so you handle errors before they reach production.


Quick Example

The Result type represents an operation that can succeed (Ok) or fail (Err). Pattern matching on the result forces you to handle both cases, eliminating unhandled exceptions:

import { ok, err, Result } from "pithos/zygos/result/result";

function divide(a: number, b: number): Result<number, string> {
if (b === 0) return err("Division by zero");
return ok(a / b);
}

const result = divide(10, 2);

if (result.isOk()) {
console.log(result.value); // 5
} else {
console.log(result.error); // Never reached
}

Available Monads

MonadDescriptionUse Case
ResultOk<T> or Err<E>Operations that can fail
OptionSome<A> or NoneOptional values (no null)
EitherLeft<E> or Right<A>Generic two-case branching
TaskLazy async computationDeferred async operations
TaskEitherAsync EitherAsync operations that can fail

Result

Zygos Result is 2.6x smaller (~774 B vs ~1.96 kB) than Neverthrow, while maintaining 100% API compatibility. You get the same developer experience with a fraction of the bundle cost.

Drop-in Replacement for Neverthrow

Migrate from Neverthrow with a single line change:

// Before (Neverthrow)
import { ok, err, Result, ResultAsync } from "neverthrow";

// After (Zygos): only change the import, code stays IDENTICAL
import { ok, err, Result } from "pithos/zygos/result/result";
import { ResultAsync } from "pithos/zygos/result/result-async";

// Your existing code works unchanged
Migration

Search & replace from "neverthrow" → split into the two imports above. All methods work the same.

Usage

Results support chaining with map, mapErr, and andThen. Each operation transforms the success value while propagating errors automatically, similar to how Promises chain with .then():

import { ok, err, Result } from "pithos/zygos/result/result";
import { ResultAsync } from "pithos/zygos/result/result-async";

// Sync
const success = ok(42);
const failure = err("Something went wrong");

// Chaining transforms the Ok value through each step.
// If any step returns an Err, the chain short-circuits.
ok(5)
.map(x => x * 2) // Ok(10)
.mapErr(e => `Error: ${e}`) // Still Ok(10)
.andThen(x => ok(x + 1)); // Ok(11)

// ResultAsync wraps Promises in the same Result pattern,
// so async operations get typed error handling too.
const asyncResult = ResultAsync.fromPromise(
fetch("/api/data"),
() => "Network error"
);

Option

Handle optional values without null/undefined. The Option type makes the absence of a value explicit in the type system, replacing nullable types with Some (value present) or None (value absent):

import { some, none, fromNullable, Option, map, flatMap } from "pithos/zygos/option";
import { pipe } from "pithos/arkhe/function/pipe";

const value = some(42);
const empty = none;

// From nullable
const maybeNull: string | null = "hello";
const opt = fromNullable(maybeNull); // Some("hello")

// Pattern matching
const result = isSome(opt)
? opt.value
: "default";

// Chaining with pipe
pipe(
some(5),
map(x => x * 2), // Some(10)
flatMap(x => some(x + 1)) // Some(11)
);

Either, Task, TaskEither

Lightweight implementations based on fp-ts, 100% API compatible. These monads cover more advanced functional programming patterns: Either for generic two-case branching, Task for lazy async computations, and TaskEither for async operations that can fail.

Drop-in Replacement for fp-ts

Migrate from fp-ts with a single line change:

// Before (fp-ts)
import * as E from "fp-ts/Either";
import * as T from "fp-ts/Task";
import * as TE from "fp-ts/TaskEither";

// After (Zygos): only change the import, code stays IDENTICAL
import * as E from "pithos/zygos/either";
import * as T from "pithos/zygos/task";
import * as TE from "pithos/zygos/task-either";

// Your existing code works unchanged
const result = pipe(
E.right(5),
E.map(x => x * 2),
E.flatMap(x => E.right(x + 1))
);
Migration

Search & replace from "fp-ts/Either"from "pithos/zygos/either", etc. All functions work the same.

Available functions

ModuleFunctions
Eitherleft, right, isLeft, isRight, map, mapLeft, flatMap, fold, match, getOrElse, orElse, fromOption, fromNullable, tryCatch, Do, bind, bindTo, apS...
Taskof, map, flatMap, ap
TaskEitherleft, right, tryCatch, fromEither, fromTask, fromOption, map, mapLeft, flatMap, chain, fold, match, getOrElse, orElse, swap...

safe() - Convert throwing functions

Wrap any function that might throw into a Result-returning function. This is especially useful for third-party APIs or built-in functions like JSON.parse that communicate errors through exceptions:

import { safe } from "pithos/zygos/safe";

const safeJsonParse = safe(JSON.parse);

const result = safeJsonParse('{"valid": true}');
// Ok({ valid: true })

const invalid = safeJsonParse('not json');
// Err(SyntaxError: ...)

checkmark When to Use

Zygos shines in codebases where error handling needs to be explicit and composable:

  • API callsResultAsync for typed error handling
  • Validation chainsResult with andThen
  • Optional dataOption instead of null | undefined
  • Wrapping unsafe codesafe() for try/catch elimination

cross When NOT to Use

For tasks outside error handling and control flow, other Pithos modules are more appropriate:

NeedUse Instead
Schema validationKanon
Data transformationArkhe
Typed error factoriesSphalma

Migrating from Neverthrow or fp-ts

From Neverthrow

Step 1: Install

Add Pithos to your project. It includes Zygos and all other modules:

npm install @pithos/core

Step 2: Update imports

// Before
import { ok, err, Result, ResultAsync, safeTry } from "neverthrow";

// After
import { ok, err, Result, safeTry } from "pithos/zygos/result/result";
import { ResultAsync } from "pithos/zygos/result/result-async";

Step 3: Run your tests

All your existing code works as-is. The API is 100% compatible.

Step 4 (optional): Use additional features

Once migrated, you can take advantage of Zygos-specific features:

// fp-ts bridges
import { fromOption, fromEither, toEither } from "pithos/zygos/result/result";

// Simplified async try
import { safeAsyncTry } from "pithos/zygos/result/result";

// Collect all errors
const allErrors = ResultAsync.combineWithAllErrors(results);

From fp-ts

Step 1: Install

Add Pithos to your project. It includes Zygos and all other modules:

npm install @pithos/core

Step 2: Update imports

// Before
import * as E from "fp-ts/Either";
import * as T from "fp-ts/Task";
import * as TE from "fp-ts/TaskEither";

// After
import * as E from "pithos/zygos/either";
import * as T from "pithos/zygos/task";
import * as TE from "pithos/zygos/task-either";

Step 3: Run your tests

All functions work the same. pipe, map, flatMap, fold, etc. are all compatible.

For the complete list of supported Neverthrow and fp-ts methods, see the Zygos interoperability matrix which documents every function across Result, ResultAsync, Either, Task, and TaskEither.

Zygos works naturally with Sphalma typed error factories: define structured error codes with Sphalma, then return them as typed Err values in your Result chains.


Related Resources