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}`);
}