Skip to main content

ensureAsync()

ensureAsync<T>(schema, input): ResultAsync<T, string>

ensureAsync<S>(schema, input): ResultAsync<Infer<S>, string>

Parses a value against a Kanon schema and returns a Zygos ResultAsync. Async variant of ensure — designed for ResultAsync chains where validation is one step in an async pipeline.

💎 Why is this a Hidden Gem?

Like ensure, but returns a ResultAsync. Slots into any async chain as a validation step.


Type Parameters​

T: T​

The expected output type of the schema.

S: S extends GenericSchema​


Parameters​

Overload 1:

schema: Schema<T>​

Kanon schema to validate against.

input: unknown​

Value to validate.

Overload 2:

schema: S​

input: unknown​


Returns: ResultAsync<T, string>​

OkAsync<T> if validation succeeds, ErrAsync<string> otherwise.


Since​

2.1.0


Example​

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

safeFetch("/api/user")
.andThen(res => safeJson(res))
.andThen(data => ensureAsync(UserSchema, data))
.map(user => user.name);

Use Cases​

Validate an API response inside an async pipeline 📌​

When you already have a ResultAsync from a previous operation, ensureAsync slots in as a validation step. The pipeline stays flat. Essential for any fetch-then-validate pattern.

import { ensureAsync } from "@pithos/core/bridges/ensureAsync";
import { string, object, number, array } from "@pithos/core/kanon";
import { ResultAsync } from "@pithos/core/zygos/result/result-async";

const userSchema = object({
id: string(),
name: string(),
score: number(),
});

const leaderboardSchema = array(userSchema);

function fetchLeaderboard(): ResultAsync<{ id: string; name: string; score: number }[], string> {
return ResultAsync.fromPromise(
fetch("/api/leaderboard").then(r => r.json()),
(e) => `Network error: ${e}`,
)
.andThen(data => ensureAsync(leaderboardSchema, data))
.map(users => users.filter(u => u.score > 0));
}

// The entire chain is a single ResultAsync<User[], string>

Chain multiple async steps with validation between each 📌​

Authenticate, then fetch a profile, validating each response before moving on. Each step is typed and validated. Perfect for login flows, onboarding wizards, or any multi-step async workflow.

import { ensureAsync } from "@pithos/core/bridges/ensureAsync";
import { string, object, number } from "@pithos/core/kanon";
import { ResultAsync } from "@pithos/core/zygos/result/result-async";

const tokenSchema = object({ accessToken: string(), expiresIn: number() });
const profileSchema = object({ id: string(), name: string(), email: string() });

function authenticateAndLoadProfile(credentials: unknown) {
return ResultAsync.fromPromise(
fetch("/auth/login", {
method: "POST",
body: JSON.stringify(credentials),
}).then(r => r.json()),
(e) => `Auth request failed: ${e}`,
)
.andThen(data => ensureAsync(tokenSchema, data))
.andThen(token =>
ResultAsync.fromPromise(
fetch("/api/profile", {
headers: { Authorization: `Bearer ${token.accessToken}` },
}).then(r => r.json()),
(e) => `Profile request failed: ${e}`,
),
)
.andThen(data => ensureAsync(profileSchema, data));
}

// ResultAsync<{ id, name, email }, string> - fully typed, fully validated

Guard incoming WebSocket messages​

Validate each incoming message from a WebSocket or event stream independently. Essential for real-time apps where the server might send unexpected shapes.

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

const messageSchema = object({
type: string(),
payload: object({ value: number(), timestamp: string() }),
});

function handleMessage(raw: unknown) {
return ensureAsync(messageSchema, raw)
.map(msg => ({
kind: msg.type,
value: msg.payload.value,
receivedAt: new Date(msg.payload.timestamp),
}))
.mapErr(error => `Malformed message: ${error}`);
}

// ws.onmessage = (event) => {
// handleMessage(JSON.parse(event.data))
// .match(
// msg => processEvent(msg),
// err => console.warn(err),
// );
// };

Secure database query results​

Validate data returned by a database query or ORM before using it. Catches shape mismatches even when the DB layer returns unknown. Perfect for any data access layer that needs runtime guarantees.

import { ensureAsync } from "@pithos/core/bridges/ensureAsync";
import { string, object, number, array, optional } from "@pithos/core/kanon";
import { ResultAsync } from "@pithos/core/zygos/result/result-async";

const productSchema = object({
id: string(),
name: string(),
price: number(),
category: optional(string()),
});

const productsSchema = array(productSchema);

function getProducts(categoryFilter?: string) {
return ResultAsync.fromPromise(
db.query("SELECT * FROM products WHERE category = $1", [categoryFilter]),
(e) => `Query failed: ${e}`,
)
.andThen(rows => ensureAsync(productsSchema, rows))
.map(products => products.sort((a, b) => a.price - b.price));
}

// ResultAsync<Product[], string> - validated and sorted

Retry a validated operation on transient failure​

Use orElse to retry when the first attempt fails. Both attempts are validated. Critical for health checks, external API calls, or any operation prone to transient errors.

import { ensureAsync } from "@pithos/core/bridges/ensureAsync";
import { string, object } from "@pithos/core/kanon";
import { ResultAsync } from "@pithos/core/zygos/result/result-async";

const healthSchema = object({ status: string(), version: string() });

function checkHealth(url: string) {
const attempt = () =>
ResultAsync.fromPromise(
fetch(url).then(r => r.json()),
(e) => `Unreachable: ${e}`,
).andThen(data => ensureAsync(healthSchema, data));

return attempt()
.orElse(() => {
console.log("First attempt failed, retrying...");
return attempt();
});
}

// Tries once, retries on failure, validates both times
checkHealth("https://api.example.com/health");

Validate drag-and-drop data transfer payload​

Validate the data transferred during a drag-and-drop operation before processing. Essential for CDK-style drag-and-drop that accepts external data.

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

const dragDataSchema = object({
type: string(),
items: array(object({ id: string(), index: number() })),
sourceContainerId: string(),
});

function handleDrop(rawData: unknown, targetContainerId: string) {
return ensureAsync(dragDataSchema, rawData)
.map(data => ({
...data,
targetContainerId,
movedItems: data.items.map(item => item.id),
}))
.match(
data => commitReorder(data),
error => console.warn(`Invalid drop data: ${error}`),
);
}

Validate payment webhook payloads​

Validate incoming payment webhook data before processing transactions. Critical for payment integrations where malformed data could cause financial errors.

import { ensureAsync } from "@pithos/core/bridges/ensureAsync";
import { string, object, number, literal, union } from "@pithos/core/kanon";
import { ResultAsync } from "@pithos/core/zygos/result/result-async";

const paymentEventSchema = object({
id: string(),
type: union(literal("payment_intent.succeeded"), literal("payment_intent.failed")),
data: object({
object: object({
amount: number(),
currency: string(),
customer: string(),
}),
}),
});

function handlePaymentWebhook(rawBody: unknown) {
return ensureAsync(paymentEventSchema, rawBody)
.map(event => ({
eventId: event.id,
amount: event.data.object.amount / 100,
currency: event.data.object.currency.toUpperCase(),
customerId: event.data.object.customer,
succeeded: event.type === "payment_intent.succeeded",
}))
.mapErr(error => `Invalid webhook payload: ${error}`);
}