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
| Library | Zygos replacement | Compatibility | Migration |
|---|---|---|---|
| Neverthrow | Result, ResultAsync | 100% API compatible | Import change only |
| fp-ts | Either, Task, TaskEither | 100% API compatible | Import change only |
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
| Module | Functions |
|---|---|
| Either | left, right, isLeft, isRight, map, mapLeft, flatMap, fold, match, getOrElse, orElse, fromOption, fromNullable, tryCatch, Do, bind, bindTo, apS |
| Task | of, map, flatMap, ap |
| TaskEither | left, 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:
| Feature | Description |
|---|---|
2.6x smaller | ~774 B vs ~1.96 kB |
Zero any types | Better type safety with unknown types |
Result ↔ fp-ts bridges | Convert between Result and Either/Option |
safeAsyncTry | Simplified async error handling |
combineWithAllErrors | Collect 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?
| Aspect | Zygos | Neverthrow | fp-ts |
|---|---|---|---|
| Bundle Size | ~774 B | ~1.96 kB | ~50 kB+ |
| Type Safety | Zero any types | Uses any in places | ![]() |
| Result + Either | Both | Result only | Either only |
| Bridges | Result ↔ Either/Option | ![]() | ![]() |
| Migration | — | Import change only | Import change only |
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
- Zygos vs Neverthrow — Full comparison: philosophy, API, migration
- Kanon ↔ Zod Interoperability — Another migration compatibility story
- Equivalence Table — Full library equivalence across all modules
- Zygos Module Guide — Full module documentation
Zero
Result ↔ fp-ts bridges
safeAsyncTry
combineWithAllErrors
