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.
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.checkout.process({
cart_id: cart.value.id,
customer: { name: "Akua", email: "akua@example.com", phone: "+233244000000" },
order_type: "delivery",
payment_method: "mobile_money",
});
}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
Every write method on the SDK accepts an optional `{ idempotencyKey }` as its second argument. Replays of the same key against the same body return the original response and never a duplicate side effect.