Skip to main content

Zygos Interoperability

Zygos replaces two libraries with a single, lighter package. Both Neverthrow and fp-ts users can switch by changing imports — no code changes required.

TL;DR

LibraryZygos replacementCompatibilityMigration
NeverthrowResult, ResultAsync100% API compatibleImport change only
fp-tsEither, Task, TaskEither100% API compatibleImport change only
Bottom line

Swap your imports. Your code works as-is. Zygos is 2.6x smaller (~774 B vs ~1.96 kB) than Neverthrow, with zero any types.


Neverthrow — Result & ResultAsync

Zygos Result is a micro implementation of Neverthrow's Result type: 2.6x smaller (~774 B vs ~1.96 kB), 100% API compatible, zero any types.

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

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

Full API Compatibility

Click to expand each category:

Result Constructors (100%)2/2

ok(value), err(error)

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

const success: Result<number, string> = ok(42);
const failure: Result<number, string> = err("Something went wrong");
Result Methods (100%)7/7

.isOk(), .isErr(), .map(), .mapErr(), .andThen(), .unwrapOr(), .match()

const result = ok(5)
.map(x => x * 2)
.mapErr(e => `Error: ${e}`)
.andThen(x => x > 0 ? ok(x) : err("negative"));

const value = result.unwrapOr(0);

const message = result.match(
value => `Success: ${value}`,
error => `Error: ${error}`
);
Result Static Methods (100%)2/2

Create Results from throwable functions and combine multiple Results into one. Result.fromThrowable(), Result.combine()

const safeParse = Result.fromThrowable(
JSON.parse,
(error) => `Parse error: ${error}`
);

const result = safeParse('{"valid": "json"}'); // Ok({valid: "json"})
const error = safeParse('invalid'); // Err("Parse error: ...")

const combined = Result.combine([ok(1), ok(2), ok(3)]); // Ok([1, 2, 3])
ResultAsync Constructors (100%)2/2

okAsync(), errAsync()

import { okAsync, errAsync, ResultAsync } from "@pithos/core/zygos/result/result-async";

const asyncSuccess = okAsync(Promise.resolve(42));
const asyncError = errAsync("network error");
ResultAsync Methods (100%)7/7

.map(), .mapErr(), .andThen(), .unwrapOr(), .match(), .orElse(), .then()

const result = await okAsync(Promise.resolve(5))
.map(x => x * 2)
.andThen(x => okAsync(Promise.resolve(x.toString())));

const message = await result.match(
value => `Success: ${value}`,
error => `Error: ${error}`
);

const withFallback = await errAsync("error")
.orElse(() => okAsync("fallback"));
ResultAsync Static Methods (100%)4/4

Build async Results from promises, safe promises, throwable functions, and combine them. ResultAsync.fromPromise(), ResultAsync.fromSafePromise(), ResultAsync.fromThrowable(), ResultAsync.combine()

const result = ResultAsync.fromPromise(
fetch('/api/data'),
(error) => `Fetch failed: ${error}`
);

const safe = ResultAsync.fromSafePromise(Promise.resolve(42));

const safeFetch = ResultAsync.fromThrowable(
async (url: string) => {
const res = await fetch(url);
return res.json();
},
(error) => `Request failed: ${error}`
);

const combined = ResultAsync.combine([
okAsync(1),
okAsync(2),
okAsync(3)
]); // Ok([1, 2, 3])
safeTry (100%)1/1

Use generator syntax to write Result chains that look like imperative code, with early returns on errors. safeTry()

import { safeTry, ok, err } from "@pithos/core/zygos/result/result";

const result = safeTry(function* () {
yield err("validation failed");
return ok(42);
});

const direct = safeTry(() => ok(42));

fp-ts — Either, Task, TaskEither

Lightweight reimplementations of fp-ts monads, 100% API compatible. Same functions, same signatures, smaller bundle.

// 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/core/zygos/either";
import * as T from "@pithos/core/zygos/task";
import * as TE from "@pithos/core/zygos/task-either";

Supported 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

Usage example

import * as E from "@pithos/core/zygos/either";
import * as TE from "@pithos/core/zygos/task-either";
import { pipe } from "@pithos/core/arkhe/function/pipe";

// Either — same as fp-ts
const result = pipe(
E.right(5),
E.map(x => x * 2),
E.flatMap(x => E.right(x + 1))
);

// TaskEither — async operations that can fail
const fetchUser = pipe(
TE.tryCatch(
() => fetch("/api/user").then(r => r.json()),
() => "Network error"
),
TE.map(user => user.name)
);

Zygos-only features

Beyond compatibility, Zygos adds features that neither Neverthrow nor fp-ts provide:

FeatureDescription
2.6x smaller~774 B vs ~1.96 kB
Zero any typesBetter type safety with unknown types
Result ↔ fp-ts bridgesConvert between Result and Either/Option
safeAsyncTrySimplified async error handling
combineWithAllErrorsCollect all errors instead of stopping at first

Result ↔ fp-ts bridges

Zygos provides built-in converters between Result and fp-ts types, so you can mix both ecosystems in the same codebase without manual wrapping:

import { fromOption, fromEither, toEither, ok, err } from "@pithos/core/zygos/result/result";

// Option → Result
const fromSome = fromOption(() => "No value")({ _tag: "Some", value: 42 }); // Ok(42)
const fromNone = fromOption(() => "No value")({ _tag: "None" }); // Err("No value")

// Either → Result
const fromRight = fromEither({ _tag: "Right", right: 42 }); // Ok(42)
const fromLeft = fromEither({ _tag: "Left", left: "error" }); // Err("error")

// Result → Either
const toRight = toEither(ok(42)); // { _tag: "Right", right: 42 }
const toLeft = toEither(err("error")); // { _tag: "Left", left: "error" }

safeAsyncTry

Handle async operations that might throw without needing generators or verbose fromPromise wrappers. One function call, one Result:

import { safeAsyncTry } from "@pithos/core/zygos/result/result";

const result = await safeAsyncTry(() => fetch("/api/user").then(r => r.json()));

if (result.isOk()) {
console.log(result.value);
} else {
console.log(result.error);
}

combineWithAllErrors

When validating multiple operations, Result.combine stops at the first error. combineWithAllErrors collects every failure so you can report them all at once:

import { ResultAsync, okAsync, errAsync } from "@pithos/core/zygos/result/result-async";

const combined = ResultAsync.combineWithAllErrors([
okAsync(1),
errAsync("error1"),
okAsync(3),
errAsync("error2")
]);

const resolved = await combined;
// Err(["error1", "error2"]) — collects ALL errors

Why choose Zygos?

AspectZygosNeverthrowfp-ts
Bundle Size~774 B~1.96 kB~50 kB+
Type SafetyZero any typesUses any in places
Result + Either BothResult onlyEither only
Bridges Result ↔ Either/Option
MigrationImport change onlyImport change only
Recommendation

Whether you're coming from Neverthrow or fp-ts, switching to Zygos is the same: change your imports, keep your code. You get a smaller bundle, better types, and both ecosystems in one package.

Ready to migrate? The complete step-by-step migration guide is in the Zygos module documentation: install, swap imports, and start using additional features. Go to migration guide →

Not sure which library fits your use case? See the comparison overview.


Related