Result
Every SDK method returns `Result<T, CimplifyError>` and never throws. You narrow on `.ok`, and TypeScript gives you either `value` or `error` on the other branch.
The shape
type Result<T, E = CimplifyError> =
| { ok: true; value: T }
| { ok: false; error: E };Narrowing
Check r.ok first. Inside the truthy branch r.value is typed; inside the falsy branch r.error is typed. Trying to read the wrong one is a compile error.
const r = await client.catalogue.getProducts({ limit: 24 });
if (!r.ok) {
// r.error is CimplifyError here
console.error(r.error.code, r.error.message);
return;
}
// r.value is { items, pagination } here
for (const product of r.value.items) {
console.log(product.name);
}Why no exceptions
Network failures, validation errors, rate limits, and business-rule violations are all expected outcomes of a request, not exceptional control flow. Forcing callers to wrap every call in try/catch hides the failure modes from the type system. A Result makes them visible: you cannot read value without also handling error.
| try / catch | Result |
|---|---|
| Errors are invisible until runtime | Failure is part of the return type |
Forgetting catch crashes the page | Forgetting !r.ok is a compile error |
| Mixed sync/async error handling | One branch, same shape, every call |
| Stack traces with no domain context | code, retryable, context |
In React components
Hooks like useProducts unwrap the Result for you and surface { data, error, isLoading }. When you call the client directly inside an effect or a server action, narrow the same way you would on the server.
"use server";
import { getServerClient } from "@cimplify/sdk/server";
export async function applyCoupon(code: string) {
const r = await getServerClient().cart.applyCoupon(code);
if (!r.ok) {
return { ok: false, message: r.error.message };
}
return { ok: true, cart: r.value };
}Composing Results
For a sequence of dependent calls, fail early on the first error. Don't try to recreate Promise chaining; straight-line code with early returns is the readable form.
import { PURCHASE_INTENT_SOURCE_KIND } from "@cimplify/sdk";
async function placeFirstOrder() {
const products = await client.catalogue.getProducts({ limit: 1 });
if (!products.ok) return products;
const first = products.value.items[0];
if (!first) return { ok: false as const, error: new Error("empty catalogue") };
const added = await client.cart.addItem({ item_id: first.id, quantity: 1 });
if (!added.ok) return added;
const cart = await client.cart.get();
if (!cart.ok) return cart;
return client.purchaseIntents.mint({
client_intent_key: stableKey,
source: { kind: PURCHASE_INTENT_SOURCE_KIND.CART, cart_id: cart.value.id },
checkout: checkoutInput,
});
}Next
-
Errors CimplifyError shape and codes
-
Idempotency Retrying writes safely
Money
Every monetary value in the SDK is a `Money`: a branded string at runtime, a distinct type at compile time. Strings keep decimal precision intact across JSON, databases, and currency boundaries.
Idempotency
Idempotency keys identify one logical command. Exact replays return the original authority or result without duplicating a side effect.