Basic Usage
Scaffold the standard starter, write a Flow, and inspect it in the Console.
Scaffold an app
bunx create-oke@latest my-app
cd my-app
oke devThe app runs on :6530, the Console on :6533, and MCP on :6535.
Mental model
Every backend behavior has one shape:
on(Trigger) → Effects- Trigger — how work starts (
http.post,every("10m"), a signal, …) - Contracts —
in,out, and typederrors do— the body; world access goes only throughfx- Effects — inferred from the
fxcalls
Your first Flow
The standard starter includes a health Flow:
import { on, flow, http } from "okengine";
import { z } from "zod";
export const health = on(
http.get("/health"),
flow({
out: z.object({ ok: z.literal(true) }),
do: () => ({ ok: true as const }),
}),
);Open src/flows/main/index.ts, change the route or output, and save. The app and
Console update together from the same Manifest.
The invariant
All reads, writes, emits, sends, and external calls belong behind fx. This is what makes effects
inspectable and tests deterministic.
Wire the app
The starter adopts the module in src/app.ts:
import { oke } from "okengine";
import * as main from "./flows/main";
export const app = oke({ name: "my-app" }).adopt({ main });
export type App = typeof app;The namespace becomes the typed-client namespace and each exported Flow becomes a method.
Typed client
import { createClient } from "okengine/client";
import type { App } from "../src/app";
const api = createClient<App>("http://localhost:6530");
const { data, error } = await api.main.health({});data and error are inferred from the Flow contracts; no separate client
schema or code-generation project is required.
Test
Use createTestApp to boot the same app with test drivers:
import { expect, test } from "bun:test";
import { createTestApp } from "okengine/test";
import { app } from "../src/app";
test("health", async () => {
const t = await createTestApp(app);
const { data, error } = await t.api.main.health({});
expect(error).toBeNull();
expect(data).toEqual({ ok: true });
await app.stop();
});bun test