Overview
Typed caller for your Flows — createClient from okengine/client, zero codegen, errors as values.
okengine/client is how a browser, CLI, or another service calls your app's Flows. Import the
generated barrel, take typeof app, and api.bookings.get({ id }) is fully typed — same contracts
the server already has, no separate schema project.
For developers consuming an okengine app from the outside — create the client, call a Flow, narrow the envelope.
The one rule
Treat every call as a result envelope: { data, error }. Flow failures are values you switch on
(error.code); they are never thrown. Only transport / protocol problems use
code: "TransportError".
Smallest Example
Export App from the server
import "@/core";
import "@/flows/generated";
import { oke } from "okengine/http";
export const app = oke({ name: "commerce" });
export type App = typeof app;.adopt({ bookings }) is optional and additive when a unit is not already in the generated barrel.
Load a booking from the client
import { createClient } from "okengine/client";
import { vault } from "okengine/vault";
import { app } from "../../src/app";
// Base URL via vault.env (empty = same-origin proxy in create-oke web)
const api = createClient(app, vault.env("PUBLIC_API_URL") ?? "");
const { data, error } = await api.bookings.get({ id: "bkg_7f3a" });
if (error) {
// TransportError or a declared flow code (NotFound, …)
return;
}
console.log(data.confirmationCode, data.seats);With $routes wired that is GET /bookings/bkg_7f3a on the backend (port 6530):
{
"data": {
"id": "bkg_7f3a",
"confirmationCode": "SK-4812",
"seats": 2,
"status": "confirmed"
},
"error": null
}Progressive Patterns
Same typed proxy from same-repo REST to a separate frontend repo:
Pass the app value so HTTP triggers hit REST (method + path from $routes):
import { createClient } from "okengine/client";
import { vault } from "okengine/vault";
import { app } from "./app";
const api = createClient(app, vault.env("PUBLIC_API_URL") ?? "");
await api.bookings.get({ id: "bkg_7f3a" }); // GET /bookings/bkg_7f3aFrom App to client
one App type- 01
App + routes
app.$routes · oke-client.routesTyped adopt or generated routes module — contracts for every Flow.
- same Manifest02
createClient
createClient(url, { $routes, auth? })Optional auth attaches api.auth. Same App type in browser or SSR.
- same Manifest03
Envelope + ambient
{ data, error } · oke-client.d.tsEvery call returns data and error. Ambient types after oke client add.
Adopt remains available for same-repo typing — generated routes are the handbook path.
Flows only
Flows only
The client calls Flows. Every other element runs on the server through fx. You reach its
outcome by calling a Flow that uses it — or by handling a gate denial on that call.
| Element | On the client | How |
|---|---|---|
| Flow | Direct | api.unit.flow(input) — the only public surface |
| Gate | Indirect | Bearer via auth; denials as Unauthorized / Forbidden / RateLimited |
| Store | Via Flows | fx.store inside Flows; store.resource + on(http.resource…) → five Flows on $routes |
| Signal | Live SSE | api.live(signal, input?, { onEvent }) — HTTP GET, callback + unsubscribe |
| Clock | Via Flows | Schedules fire on the server — the client never ticks a clock |
| Vault | Via Flows | Secrets stay server-side; never ship them to the browser package |
| Channel | Via Flows | fx.send in a Flow — the client does not send email/SMS/push |
| AI | Via Flows | fx.ask / fx.run inside a Flow; the client gets that Flow’s out |
Pages
Calling
createClient forms, REST vs RPC, options, envelopes, resources, remote types.
Auth
memorySession, Bearer refresh, and gate denials as values.
Live
api.live SSE, resume gaps, and live resource queries.
React
useSession, useLive, useLiveQuery from okengine/client-react.
Learn more
- Routing — folders are the URL;
$routeswithout.adopt() - HTTP — verbs,
http.resource, live SSE - The Architecture — generated barrel → client → test loop; same contract for Client, Console, MCP
- Errors — framework codes vs failure values