Skip to main content

Module Alchemy

Each Pithos module solves one problem and works on its own. But some combine naturally to simplify your code. This page covers a few combinations and the synergies they create.


Kanon + Zygos = ensure()​

The only dedicated bridge function in Pithos. ensure() takes a Kanon schema and returns a Result instead of a plain { success, data } object, so you can chain validation with everything else.

Benefit of ensure()​

Kanon's parse() works fine on its own. But the moment you chain validation with other operations, you end up writing imperative if/else blocks:

const parsed = parse(schema, data);
if (!parsed.success) {
return handleError(parsed.error);
}
const transformed = transform(parsed.data);
// ... more if/else for each step

ensure() simply returns a Result:

import { ensure } from "@pithos/core/bridges/ensure";
import { object, string, number } from "@pithos/core/kanon";

const UserSchema = object({
name: string().minLength(1),
age: number().min(0).int(),
});

ensure(UserSchema, data)
.map(user => ({ ...user, name: user.name.trim() }))
.mapErr(error => `Validation failed: ${error}`);

Variants​

FunctionReturnsUse when
ensure()Result<T, string>Sync validation : forms, configs, function args
ensureAsync()ResultAsync<T, string>Async chains : when data is already resolved in a ResultAsync pipeline
ensurePromise()ResultAsync<T, string>Promise + validation in one step -> fetch, DB queries

ensurePromise() eliminates the ResultAsync.fromPromise(...).andThen(...) boilerplate:

import { ensurePromise } from "@pithos/core/bridges/ensurePromise";
import { object, string, number } from "@pithos/core/kanon";

const UserSchema = object({
name: string().minLength(1),
age: number().min(0).int(),
});

// One line: fetch + validate + typed ResultAsync
ensurePromise(UserSchema, fetch("/api/user").then(r => r.json()))
.map(user => user.name.toUpperCase());

Sphalma + Zygos : Typed errors in Result chains​

Instead of throwing errors, return them as typed Err values. CodedError carries a hex code, a type label, and optional details. Every failure path is visible in the function signature:

import { createErrorFactory, CodedError } from "@pithos/core/sphalma/error-factory";
import { ok, err, Result } from "@pithos/core/zygos/result/result";

const createUserError = createErrorFactory<0x3001 | 0x3002>("USER_ERROR");

function getUser(id: string): Result<User, CodedError> {
if (!id) return err(createUserError(0x3001, { reason: "Empty ID" }));
const user = db.find(id);
if (!user) return err(createUserError(0x3002, { id }));
return ok(user);
}

// The caller handles both cases explicitly
getUser("user-123").match(
user => console.log(user.name),
error => console.log(error.key) // "USER_ERROR:0x3002"
);

No try/catch, no untyped string errors. The compiler knows exactly what can go wrong.


Kanon + Sphalma : Validation to domain errors​

Kanon catches malformed data. Sphalma handles domain-level errors that occur after validation passes. Together, they cover the full error spectrum:

import { parse, object, string, number } from "@pithos/core/kanon";
import { createErrorFactory, CodedError } from "@pithos/core/sphalma/error-factory";
import { ok, err, Result } from "@pithos/core/zygos/result/result";

const createOrderError = createErrorFactory<0x4001 | 0x4002>("ORDER_ERROR");

const OrderSchema = object({
product: string().minLength(1),
quantity: number().min(1).int(),
});

function createOrder(input: unknown): Result<Order, CodedError | string> {
// Step 1: Kanon validates the shape
const parsed = parse(OrderSchema, input);
if (!parsed.success) return err(parsed.error);

// Step 2: Sphalma handles business rules
if (!isInStock(parsed.data.product)) {
return err(createOrderError(0x4001, { product: parsed.data.product }));
}

return ok(saveOrder(parsed.data));
}

Kanon says "is this data well-formed?", Sphalma says "is this operation valid?".


Arkhe → Taphos : a continuity, not a synergy​

This isn't a combination of two modules, it's the natural lifecycle of a function. When a native equivalent ships, an Arkhe utility moves to Taphos where it becomes a deprecated polyfill. Your IDE guides you toward the native replacement at your own pace.

See the Taphos page for the full migration diagram and code examples.


The full pipeline​

Combine all four modules in a single typed chain : validate, transform, handle errors, and propagate structured failures:

import { ensure } from "@pithos/core/bridges/ensure";
import { object, string, number } from "@pithos/core/kanon";
import { createErrorFactory, CodedError } from "@pithos/core/sphalma/error-factory";
import { ok, err, Result } from "@pithos/core/zygos/result/result";
import { capitalize, trim } from "@pithos/core/arkhe";

const createApiError = createErrorFactory<0x5001 | 0x5002>("API_ERROR");

const ContactSchema = object({
name: string().minLength(1),
email: string().email(),
age: number().min(18).int(),
});

function processContact(input: unknown): Result<Contact, CodedError | string> {
return ensure(ContactSchema, input)
// Arkhe transforms
.map(data => ({
...data,
name: capitalize(trim(data.name)),
}))
// Domain validation with Sphalma
.andThen(data => {
if (isBlacklisted(data.email)) {
return err(createApiError(0x5001, { email: data.email }));
}
return ok(data);
});
}
  • Kanon validates the shape.
  • Arkhe transforms the data.
  • Zygos chains everything.
  • Sphalma structures the errors.

One pipeline, four modules, zero try/catch.


Related Resources