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>