Aller au contenu principal

ensurePromise()

ensurePromise<T>(schema, promise): ResultAsync<T, string>

ensurePromise<S>(schema, promise): ResultAsync<Infer<S>, string>

Resolves a Promise, validates the result against a Kanon schema, and returns a ResultAsync. Eliminates the ResultAsync.fromPromise(...).andThen(...) boilerplate when you need to fetch data and validate it in one step.

💎 Why is this a Hidden Gem?

Pass a schema and a promise, get a validated ResultAsync. The shortest path from fetch to typed data.


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.

promise: Promise<unknown>

Promise that resolves to the value to validate.

Overload 2:

schema: S

promise: Promise<unknown>


Returns: ResultAsync<T, string>

ResultAsync<T, string>Ok<T> if the promise resolves and validation succeeds, Err<string> otherwise.


Since

2.1.0


Example

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

const UserSchema = object({ name: string(), age: number() });

ensurePromise(UserSchema, fetch("/api/user").then(r => r.json()))
.map(user => user.name.toUpperCase());

Use Cases

Fetch and validate in a single call 📌

Replace the ResultAsync.fromPromise(...).andThen(validate) boilerplate with one call. Essential for any endpoint that returns JSON you need to trust.

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

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

const result = ensurePromise(
productSchema,
fetch("/api/products/42").then(r => r.json()),
);

result
.map(product => ({
...product,
priceFormatted: `${product.price.toFixed(2)}`,
}))
.match(
product => console.log(`${product.name}: ${product.priceFormatted}`),
error => console.error(`Failed: ${error}`),
);

Load remote configuration at startup 📌

Load and validate a remote JSON config file. Fall back to defaults if the shape is wrong. Perfect for feature flags, A/B testing configs, or any remote settings.

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

const remoteConfigSchema = object({
featureFlags: object({
darkMode: boolean(),
betaAccess: boolean(),
}),
apiVersion: string(),
maxRetries: number(),
maintenanceMessage: optional(string()),
});

async function loadRemoteConfig() {
const config = await ensurePromise(
remoteConfigSchema,
fetch("/config.json").then(r => r.json()),
);

return config.match(
cfg => cfg,
error => {
console.warn(`Remote config invalid (${error}), using defaults`);
return {
featureFlags: { darkMode: false, betaAccess: false },
apiVersion: "v1",
maxRetries: 3,
maintenanceMessage: undefined,
};
},
);
}

Aggregate errors from parallel API calls

Fetch and validate multiple endpoints in parallel. Collect all errors instead of failing on the first one. Essential for dashboard pages that load data from several sources.

import { ensurePromise } from "@pithos/core/bridges/ensurePromise";
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() });
const statsSchema = object({ views: number(), likes: number() });
const postsSchema = array(object({ id: string(), title: string() }));

function loadDashboard(userId: string) {
return ResultAsync.combineWithAllErrors([
ensurePromise(userSchema, fetch(`/api/users/${userId}`).then(r => r.json())),
ensurePromise(statsSchema, fetch(`/api/stats/${userId}`).then(r => r.json())),
ensurePromise(postsSchema, fetch(`/api/posts?author=${userId}`).then(r => r.json())),
]).map(([user, stats, posts]) => ({
user,
stats,
posts,
}));
}

// ResultAsync<Dashboard, string[]> - all errors collected if multiple fail

Upload a file and validate the server response

Upload a file and validate what the server sends back in one pipeline. Perfect for file upload flows where the response shape matters.

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

const uploadResponseSchema = object({
fileId: string(),
url: string(),
size: number(),
});

function uploadFile(file: File) {
const body = new FormData();
body.append("file", file);

return ensurePromise(
uploadResponseSchema,
fetch("/api/upload", { method: "POST", body }).then(r => r.json()),
).map(response => ({
...response,
sizeFormatted: `${(response.size / 1024).toFixed(1)} KB`,
}));
}

// ResultAsync<{ fileId, url, size, sizeFormatted }, string>

Load design system tokens from a remote source

Fetch and validate design tokens from a remote theme server. Essential for design systems that load theme configurations dynamically.

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

const themeTokensSchema = object({
colors: object({
primary: string(),
secondary: string(),
background: string(),
text: string(),
}),
spacing: object({
unit: number(),
scale: array(number()),
}),
breakpoints: object({
sm: number(),
md: number(),
lg: number(),
xl: number(),
}),
});

function loadTheme(themeId: string) {
return ensurePromise(
themeTokensSchema,
fetch(`/api/themes/${themeId}`).then(r => r.json()),
).match(
tokens => applyThemeTokens(tokens),
error => {
console.warn(`Theme ${themeId} invalid: ${error}, using defaults`);
applyThemeTokens(defaultTokens);
},
);
}

Trust a GraphQL response

GraphQL servers can return unexpected shapes. Validate the response before using it. Critical for typed frontends that rely on specific response structures.

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

const gqlResponseSchema = object({
data: object({
repository: object({
name: string(),
stargazerCount: number(),
issues: object({
totalCount: number(),
}),
}),
}),
});

function fetchRepoInfo(owner: string, name: string) {
const query = `query ($owner: String!, $name: String!) {
repository(owner: $owner, name: $name) { name stargazerCount issues { totalCount } }
}`;

return ensurePromise(
gqlResponseSchema,
fetch("https://api.github.com/graphql", {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
body: JSON.stringify({ query, variables: { owner, name } }),
}).then(r => r.json()),
).map(res => res.data.repository);
}

// ResultAsync<{ name, stargazerCount, issues: { totalCount } }, string>