okengine 0.3.6 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/AGENTS.md +2 -0
  2. package/package.json +12 -12
  3. package/site/content/docs/ai/skills.mdx +5 -3
  4. package/site/content/docs/elements/ai.mdx +2 -0
  5. package/site/content/docs/elements/channel.mdx +2 -0
  6. package/site/content/docs/elements/clock.mdx +1 -4
  7. package/site/content/docs/elements/flow.mdx +4 -10
  8. package/site/content/docs/elements/gate.mdx +2 -0
  9. package/site/content/docs/elements/signal.mdx +1 -5
  10. package/site/content/docs/elements/store.mdx +27 -6
  11. package/site/content/docs/elements/vault.mdx +10 -11
  12. package/site/content/docs/get-started/basic-usage.mdx +76 -41
  13. package/site/content/docs/get-started/installation.mdx +95 -43
  14. package/site/content/docs/get-started/introduction.mdx +128 -75
  15. package/site/content/docs/get-started/meta.json +1 -1
  16. package/site/content/docs/get-started/why.mdx +141 -0
  17. package/site/content/docs/plugins/ip-allowlist.mdx +1 -2
  18. package/site/content/docs/plugins/security-headers.mdx +1 -1
  19. package/site/content/docs/reference/configuration.mdx +1 -1
  20. package/site/content/docs/reference/environment-variables.mdx +5 -3
  21. package/site/content/docs/reference/fx.mdx +16 -3
  22. package/src/cli/competitor-mention-removal.test.ts +117 -0
  23. package/src/cli/dev.ts +20 -0
  24. package/src/cli/meilisearch-local.test.ts +69 -0
  25. package/src/cli/meilisearch-local.ts +188 -0
  26. package/src/compiler/fixtures/skyport/src/flows/payments/index.ts +5 -1
  27. package/src/console/server/store.test.ts +1 -1
  28. package/src/console/server/store.ts +11 -1
  29. package/src/console/ui/dist/assets/index-CjxwRGVv.js +10 -0
  30. package/src/console/ui/dist/assets/panel-access-BGv45snf.js +64 -0
  31. package/src/console/ui/dist/assets/{panel-ai-D_m6WQI8.js → panel-ai-B2S7LEii.js} +1 -1
  32. package/src/console/ui/dist/assets/{panel-architecture-CKnXFyUx.js → panel-architecture-D7UJh91v.js} +1 -1
  33. package/src/console/ui/dist/assets/{panel-channels-DCDd4WAC.js → panel-channels-9T3ybqRu.js} +1 -1
  34. package/src/console/ui/dist/assets/panel-clock-Cb1UXGRQ.js +1 -0
  35. package/src/console/ui/dist/assets/{panel-diff-cdonmH8c.js → panel-diff-DmYbKWmN.js} +1 -1
  36. package/src/console/ui/dist/assets/panel-flows-PiHwT55z.js +48 -0
  37. package/src/console/ui/dist/assets/{panel-gates-B5eTE8XH.js → panel-gates-BQGYXvjT.js} +1 -1
  38. package/src/console/ui/dist/assets/panel-overview-BBnRO18l.js +1 -0
  39. package/src/console/ui/dist/assets/{panel-plugins-Cj7DK1er.js → panel-plugins-D0PsmVw2.js} +1 -1
  40. package/src/console/ui/dist/assets/panel-runs-CWuRDe0r.js +1 -0
  41. package/src/console/ui/dist/assets/{panel-signals-whmDXIg3.js → panel-signals-Bbg4ewpP.js} +1 -1
  42. package/src/console/ui/dist/assets/{panel-store-CEMHLvaw.js → panel-store-CPCbsDRa.js} +1 -1
  43. package/src/console/ui/dist/assets/panel-traces-DVAzuA_S.js +1 -0
  44. package/src/console/ui/dist/assets/{panel-vault-C9wjbki8.js → panel-vault-D1_MvOmo.js} +1 -1
  45. package/src/console/ui/dist/assets/{rolldown-runtime-CNC7AqOf.js → rolldown-runtime-B0Z9INg1.js} +1 -1
  46. package/src/console/ui/dist/index.html +2 -2
  47. package/src/docker/compose.ts +5 -0
  48. package/src/docker/docker.test.ts +41 -0
  49. package/src/docker/recipes/index.ts +10 -2
  50. package/src/docker/recipes/meilisearch.ts +31 -0
  51. package/src/drivers/conformance.test.ts +16 -1
  52. package/src/drivers/conformance.ts +40 -3
  53. package/src/drivers/index.ts +14 -2
  54. package/src/drivers/libsql.ts +4 -4
  55. package/src/drivers/meilisearch.integration.test.ts +77 -0
  56. package/src/drivers/meilisearch.test.ts +181 -0
  57. package/src/drivers/meilisearch.ts +208 -0
  58. package/src/drivers/memory.ts +4 -4
  59. package/src/drivers/pgvector.ts +6 -6
  60. package/src/drivers/types.ts +93 -12
  61. package/src/drivers/vault-driver-removal.test.ts +6 -0
  62. package/src/drivers/vault-types.ts +4 -4
  63. package/src/elements/ai/runtime.ts +6 -0
  64. package/src/elements/ai.test.ts +22 -0
  65. package/src/elements/store/index-boot.test.ts +49 -7
  66. package/src/elements/store/runtime.ts +50 -15
  67. package/src/elements/store.ts +2 -0
  68. package/src/elements/vault.test.ts +27 -4
  69. package/src/elements/vault.ts +1 -1
  70. package/src/index.ts +4 -0
  71. package/src/kernel/boot-bind/store.test.ts +9 -0
  72. package/src/kernel/boot-bind/store.ts +30 -2
  73. package/src/kernel/concurrency.test.ts +58 -0
  74. package/src/kernel/concurrency.ts +48 -0
  75. package/src/kernel/fx.test.ts +11 -2
  76. package/src/kernel/fx.ts +37 -5
  77. package/src/kernel/index.ts +10 -1
  78. package/src/kernel/redacted.ts +74 -0
  79. package/src/kernel/router.ts +3 -3
  80. package/src/test/provisions.integration.test.ts +1 -1
  81. package/site/content/docs/get-started/comparison.mdx +0 -65
  82. package/src/console/ui/dist/assets/index-CrKMmO__.js +0 -10
  83. package/src/console/ui/dist/assets/panel-access-C0J2D-a2.js +0 -64
  84. package/src/console/ui/dist/assets/panel-clock-DjGGFPzr.js +0 -1
  85. package/src/console/ui/dist/assets/panel-flows-DlCU5zjA.js +0 -45
  86. package/src/console/ui/dist/assets/panel-overview-BsFvDdts.js +0 -1
  87. package/src/console/ui/dist/assets/panel-runs-C0gmnoYL.js +0 -1
  88. package/src/console/ui/dist/assets/panel-traces-BDiAuVSK.js +0 -1
  89. package/src/drivers/vault-infisical.ts +0 -57
package/AGENTS.md CHANGED
@@ -96,3 +96,5 @@ Engine: Bun `>=1.3`.
96
96
  ## After every implementation
97
97
 
98
98
  Before claiming work done: run [`.agents/skills/oke-ship`](.agents/skills/oke-ship/SKILL.md) — append notes to `changelog.md` under `## Unreleased` (never under a shipped `## v…` section), and update site docs via [`.agents/skills/oke-docs`](.agents/skills/oke-docs/SKILL.md) for any user-facing surface. Version bump is separate: `bun run bump` promotes Unreleased into the next `## vX.Y.Z`.
99
+
100
+ Dependency bumps (one package, one `package.json`, or all): [`.agents/skills/oke-deps`](.agents/skills/oke-deps/SKILL.md).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okengine",
3
- "version": "0.3.6",
3
+ "version": "0.4.3",
4
4
  "description": "One law. Eight elements. Ten exports. One package. One manifest. Every backend need is derived, never added.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -56,7 +56,7 @@
56
56
  "prepack": "bun run build",
57
57
  "bench": "bun test ./src/runtime/cold-start.bench.ts src/client/budget.test.ts src/console/budget.test.ts src/kernel/budget.test.ts src/kernel/routing-budget.test.ts src/runs/bench.test.ts",
58
58
  "budgets": "bun src/release/publish.ts",
59
- "gate": "bun test src/cli/doc-staleness.test.ts src/drivers/vault-driver-removal.test.ts src/kernel/errors.registry.test.ts src/upgrade/codemods.test.ts",
59
+ "gate": "bun test src/cli/doc-staleness.test.ts src/cli/competitor-mention-removal.test.ts src/drivers/vault-driver-removal.test.ts src/kernel/errors.registry.test.ts src/upgrade/codemods.test.ts",
60
60
  "dev": "bun run --cwd site dev",
61
61
  "site:build": "bun run --cwd site build",
62
62
  "ci": "bun scripts/ci.ts",
@@ -66,10 +66,10 @@
66
66
  },
67
67
  "dependencies": {
68
68
  "@clack/prompts": "^1.7.0",
69
- "@duckdb/node-api": "^1.5.5-r.1",
69
+ "@duckdb/node-api": "^1.5.5-r.2",
70
70
  "ajv": "^8.20.0",
71
71
  "ajv-formats": "^3.0.1",
72
- "oxc-parser": "^0.141.0",
72
+ "oxc-parser": "^0.142.0",
73
73
  "sently": "^0.8.0"
74
74
  },
75
75
  "devDependencies": {
@@ -78,20 +78,20 @@
78
78
  "@codemirror/lang-json": "^6.0.2",
79
79
  "@codemirror/state": "^6.7.1",
80
80
  "@codemirror/theme-one-dark": "^6.1.3",
81
- "@codemirror/view": "^6.43.6",
81
+ "@codemirror/view": "^6.43.7",
82
82
  "@electric-sql/pglite": "^0.5.4",
83
83
  "@electric-sql/pglite-pgvector": "^0.0.5",
84
84
  "@libsql/client": "^0.17.4",
85
- "@oxc-project/types": "^0.141.0",
86
- "@playwright/test": "^1.62.0",
85
+ "@oxc-project/types": "^0.142.0",
86
+ "@playwright/test": "^1.62.1",
87
87
  "@sinclair/typebox": "^0.34.52",
88
88
  "@tailwindcss/vite": "^4.3.3",
89
89
  "@tanstack/react-query": "^5.101.4",
90
90
  "@tanstack/react-router": "^1.170.18",
91
91
  "@types/bun": "latest",
92
- "@types/react": "^19.2.17",
93
- "@types/react-dom": "^19.2.3",
94
- "@vitejs/plugin-react": "^6.0.4",
92
+ "@types/react": "^19.2.18",
93
+ "@types/react-dom": "^19.2.4",
94
+ "@vitejs/plugin-react": "^6.0.5",
95
95
  "arktype": "^2.2.3",
96
96
  "axe-core": "^4.12.1",
97
97
  "clsx": "^2.1.1",
@@ -105,13 +105,13 @@
105
105
  "oxfmt": "^0.61.0",
106
106
  "oxlint": "^1.76.0",
107
107
  "oxlint-tsgolint": "^7.0.2001",
108
- "playwright": "^1.62.0",
108
+ "playwright": "^1.62.1",
109
109
  "react": "^19.2.8",
110
110
  "react-dom": "^19.2.8",
111
111
  "tailwindcss": "^4.3.3",
112
112
  "typescript": "^7.0.2",
113
113
  "valibot": "^1.4.2",
114
- "vite": "^8.1.5",
114
+ "vite": "^8.2.0",
115
115
  "zod": "^4.4.3"
116
116
  },
117
117
  "peerDependencies": {
@@ -7,14 +7,15 @@ source: "docs/spec/unified-theory.md"
7
7
 
8
8
  Tools alone don't make a good operator — an agent also needs to know the _vocabulary_: what a Flow is, why `fx` is the only door, which driver ids are legal. OKE ships that knowledge as contracts and skills that agents load automatically, so sessions start aligned instead of drifting and getting corrected.
9
9
 
10
- ## The four layers
10
+ ## The layers
11
11
 
12
12
  | Layer | Path | Loaded when | Teaches |
13
13
  | ------------------ | -------------------------- | ------------------------------------------------- | --------------------------------------------------------------------- |
14
14
  | **Agent contract** | `AGENTS.md` (repo root) | Every agent session, automatically | The one law, eight elements, ten exports, the fx rule, ports, budgets |
15
15
  | **Element skill** | `.agents/skills/oke/` | Building or changing an okengine app | The element contract in depth — declaration patterns per element |
16
16
  | **Docs skill** | `.agents/skills/oke-docs/` | Writing or editing docs under `site/content/docs` | The documentation information-architecture standard and its gates |
17
- | **Ship skill** | `.agents/skills/oke-ship/` | After any implementation, before claiming done | Changelog under the current version + docs sync via `oke-docs` |
17
+ | **Ship skill** | `.agents/skills/oke-ship/` | After any implementation, before claiming done | Changelog under `## Unreleased` + docs sync via `oke-docs` |
18
+ | **Deps skill** | `.agents/skills/oke-deps/` | Updating `package.json` dependencies | Scoped bumps, Bun install, pins (Drizzle RC, fumadocs alias, …) |
18
19
 
19
20
  ## AGENTS.md — the root contract
20
21
 
@@ -22,13 +23,14 @@ Every OKE app's repo carries an `AGENTS.md` that agents (Cursor, Claude Code, an
22
23
 
23
24
  ## Skills — installable know-how
24
25
 
25
- Skills are `SKILL.md` packages an agent loads when the work matches their description. The three OKE ships:
26
+ Skills are `SKILL.md` packages an agent loads when the work matches their description. OKE ships:
26
27
 
27
28
  | Skill | Use it for | Inside |
28
29
  | ---------- | -------------------------------------------- | ------------------------------------------------------------------------- |
29
30
  | `oke` | App work — flows, elements, drivers | The agent contract, element patterns, the fx invariants |
30
31
  | `oke-docs` | Docs work — new pages, rewrites | The page skeleton, verification sources, the density gate |
31
32
  | `oke-ship` | Closing an implementation — changelog + docs | Append under `## Unreleased`; `bun run bump` promotes it into `## vX.Y.Z` |
33
+ | `oke-deps` | Dependency updates — one package or all | Scope map, `ncu` + Bun, reject downgrades / protect RC and aliases |
32
34
 
33
35
  All live in the repo under `.agents/skills/`, so they travel with the code and stay versioned with what they describe.
34
36
 
@@ -86,6 +86,8 @@ export const triage = smart.prompt("ticket-triage", {
86
86
  | `evals` | string | Path to a `.jsonl` eval set, regression-gated via `oke eval` |
87
87
  | `budget` | object | `maxCostPerCall` — cost is a first-class dimension |
88
88
 
89
+ <AiGuardrails />
90
+
89
91
  ## Agents with real guardrails
90
92
 
91
93
  An agent's tools are your flows — each carrying its own gates, effects, and typed errors, so the agent can never do anything a flow couldn't:
@@ -79,6 +79,8 @@ Locally the `console` driver captures mail into an inbox instead of sending; in
79
79
 
80
80
  ## The human physics
81
81
 
82
+ <ChannelPhysics />
83
+
82
84
  ### Consent is checked before sending
83
85
 
84
86
  Opt-out is first-class: a subject who opted out of a medium is **suppressed** — the send resolves without contacting the provider, and the receipt says so. You never hand-roll "did they unsubscribe?" checks.
@@ -83,10 +83,7 @@ do: async (input, fx) => {
83
83
 
84
84
  ## Two kinds of schedules
85
85
 
86
- | Declaration | Shows in Console | Cron + timezone | Runtime-editable | Use for |
87
- | ------------------- | ---------------- | --------------- | ------------------ | --------------------------------- |
88
- | `every("1h")` | no | no | no | Simple fixed intervals |
89
- | `clock(name, opts)` | yes | yes | when `overridable` | Business schedules operators tune |
86
+ <ClockSchedules />
90
87
 
91
88
  Both are triggers consumed with the same `on(trigger, flow)` — the flow underneath does not know the difference.
92
89
 
@@ -96,13 +96,7 @@ Every response follows one envelope — success `{ data, error: null }`, failure
96
96
 
97
97
  ## The five triggers
98
98
 
99
- | Trigger | Starts when | Replaces |
100
- | ---------------------------- | ---------------------------- | ------------------ |
101
- | `http.post("/orders")` | a request arrives | endpoint · handler |
102
- | `orderPlaced` (a signal) | another flow emits | queue consumer |
103
- | `every("1h")` | time passes | cron job |
104
- | `db.table(orders).changed()` | a row changes | CDC pipeline |
105
- | — none | another flow calls `fx.call` | "private" helper |
99
+ <FlowTriggers />
106
100
 
107
101
  ### http — a request arrives
108
102
 
@@ -192,7 +186,7 @@ Everything a flow may touch, on one object:
192
186
  | `fx.ask(prompt, input)` | ask | Call a versioned AI prompt |
193
187
  | `fx.run(agent, input)` | ask | Run a bounded agent |
194
188
  | `fx.call(flow, input)` | call | Invoke another flow |
195
- | `fx.vault(contract)` | read | Read a secret (redacted from logs) |
189
+ | `fx.vault(contract)` | read | Read a secret (`Redacted`; logs masked) |
196
190
  | `fx.clock.now()` / `.sleep(…)` | — | Injected time / durable sleep |
197
191
  | `fx.cache.get/set` | — | Shared cache with effect-aware invalidation |
198
192
  | `fx.step(name, fn)` | — | Named durable step — never re-runs on replay |
@@ -218,12 +212,12 @@ export const chargeOrder = flow({
218
212
  out: z.boolean(),
219
213
  do: async ({ orderId }, fx) => {
220
214
  const intent = await fx.step("create-intent", () =>
221
- stripe(fx.vault(stripeKey)).create(orderId),
215
+ stripe(fx.vault(stripeKey).reveal()).create(orderId),
222
216
  );
223
217
 
224
218
  await fx.clock.sleep("verify-window", "2m"); // survives restart and deploy
225
219
 
226
- return fx.step("confirm", () => stripe(fx.vault(stripeKey)).confirm(intent));
220
+ return fx.step("confirm", () => stripe(fx.vault(stripeKey).reveal()).confirm(intent));
227
221
  },
228
222
  });
229
223
  ```
@@ -72,6 +72,8 @@ A denied request never reaches `do`. It returns one of three typed failures, lik
72
72
 
73
73
  </Steps>
74
74
 
75
+ <GatePipeline />
76
+
75
77
  ## Two kinds of gates
76
78
 
77
79
  | Declaration | Question it answers | Evaluated against |
@@ -76,11 +76,7 @@ That's the loop: declare → `fx.emit` → `on(signal, flow)`. The runtime handl
76
76
 
77
77
  ## The three delivery physics
78
78
 
79
- | `delivery` | Semantics | Use for |
80
- | ------------- | ------------------------------------------------------ | ---------------------------------------------------------- |
81
- | `"once"` | Queue: competing consumers, retries, dead-letter queue | Jobs that must happen exactly once — emails, payments sync |
82
- | `"broadcast"` | Pub/sub: **every** subscriber receives a copy | Cache invalidation, cross-service events |
83
- | `"live"` | Stream: client-subscribable and replayable | Live feeds, dashboards, progress updates |
79
+ <SignalDelivery />
84
80
 
85
81
  The declaration is identical in shape for all three — switching physics later is a one-word change, not a migration to another library.
86
82
 
@@ -90,12 +90,7 @@ export const createNote = on(
90
90
 
91
91
  Pick the facet that matches the physics of your data. Each declaration is one line; the runtime handle shows what flows can do with it.
92
92
 
93
- | Facet | Declares | Best for | `fx.store(…)` handle |
94
- | -------------------------- | ----------------- | ------------------------------------- | ------------------------------------------------------ |
95
- | `store.sql(name, opts)` | a SQL database | domain tables, relations, constraints | `select` · `insert` · `update` · `delete` · `findById` |
96
- | `store.kv(name, opts?)` | a key-value space | cache, sessions, rate limits | `get` · `set(key, value, ttl?)` · `delete` · `list` |
97
- | `store.files(name, opts?)` | a blob bucket | uploads, exports, attachments | `put` · `get` · `delete` · `list(prefix?)` |
98
- | `store.index(name, opts)` | a vector index | semantic search / RAG (`dims`) | `upsert` · `search(vector, topK?)` · `delete` |
93
+ <StoreFacets />
99
94
 
100
95
  Every facet accepts optional `description` — a human title in the Console
101
96
  (falls back to the store name). `store.index` also takes `dims`.
@@ -282,6 +277,32 @@ drivers: {
282
277
 
283
278
  **Consequence:** a configured SQL-backed index that cannot reach its engine — missing peer, missing `vector` extension, wrong sql driver — fails loudly at first use. It never silently falls back to `memory`.
284
279
 
280
+ ### Full-text search — `meilisearch` (opt-in)
281
+
282
+ `meilisearch` is a fourth `store.index` backend, opt-in only — `memory` stays the default. It is a genuinely different capability from the vector ANN drivers: typo-tolerant, faceted full-text relevance, not cosine similarity. The two models are kept apart by a **discriminated union** on `driverId`, so TypeScript rejects a vector query against a text index (and vice versa) at compile time — never at runtime:
283
+
284
+ | `driverId` | `upsert` | `search` | `score` means |
285
+ | -------------------------------- | --------------------- | ---------------------------------------- | --------------------------- |
286
+ | `memory` / `pgvector` / `libsql` | `(id, vector, meta?)` | `search(vector, topK?)` → `IndexHit[]` | cosine similarity |
287
+ | `meilisearch` | `(id, document)` | `search(q, opts?)` → `{ hits, facets? }` | relevance (`_rankingScore`) |
288
+
289
+ ```typescript title="oke.config.ts"
290
+ drivers: {
291
+ store: {
292
+ index: { local: "meilisearch", docker: "meilisearch", prod: "meilisearch" },
293
+ },
294
+ },
295
+ images: {
296
+ "store.index": "getmeili/meilisearch:v1.37",
297
+ },
298
+ ```
299
+
300
+ - **Local mode** needs the `meilisearch` binary on `PATH` (a documented prerequisite, like Docker for `--docker` — OKE never auto-downloads binaries). `oke dev` spawns it with a generated master key persisted under `.oke/meilisearch/` (`0700`/`0600`).
301
+ - **Docker / prod** uses the image recipe; the app gets `OKE_STORE_INDEX_URL` + `OKE_STORE_INDEX_KEY` from Compose — a standalone HTTP service, never the shared `sqlUrl`.
302
+ - **Fail-loud:** a configured meilisearch that is unreachable or unhealthy throws `MeilisearchUnavailableError` — never a silent memory fallback.
303
+ - **Not for vectors:** `ai.embed` / `fx.search` stay vector-only; pointing an embed `into` a meilisearch index fails loud (embeddings don't apply).
304
+ - **Alpine caveat:** the raw glibc binary fails on Alpine/musl _hosts_; the official image is musl-clean. Install the binary for your platform (e.g. Homebrew / install script), or use `--docker`.
305
+
285
306
  ## Privacy built in
286
307
 
287
308
  Columns tagged `.pii()` or `.sensitive()` are masked at the store boundary — flows, logs, and the Console see a mask, not the value. Revealing cleartext PII requires an explicit `pii:reveal` gate on the flow, so access is a permission, not a convention.
@@ -55,7 +55,9 @@ oke vault set STRIPE_KEY sk_test_your_key_here
55
55
  <Step>
56
56
  ### Read it in a Flow
57
57
 
58
- Flows read secrets through `fx.vault` — never through `process.env`:
58
+ Flows read secrets through `fx.vault` — never through `process.env`. `fx.vault` returns a
59
+ `Redacted<string>`: printing, logging, or serializing it yields a placeholder, never the value.
60
+ Call `.reveal()` once at the boundary that needs the real credential:
59
61
 
60
62
  ```typescript title="src/flows/billing/charge.ts"
61
63
  export const charge = on(
@@ -64,14 +66,17 @@ export const charge = on(
64
66
  in: ChargeInput,
65
67
  out: z.object({ id: z.string() }),
66
68
  do: async (input, fx) => {
67
- const key = fx.vault(stripeKey); // cleartext, inside this flow only
68
- const intent = await stripe(key).create(input);
69
+ const key = fx.vault(stripeKey); // Redacted safe to pass to fx.log
70
+ const intent = await stripe(key.reveal()).create(input);
69
71
  return { id: intent.id };
70
72
  },
71
73
  }),
72
74
  );
73
75
  ```
74
76
 
77
+ `fx.log` masks any `Redacted` found in the logged data (nested included). A revealed string that
78
+ still reaches `fx.log` is scrubbed by the boot redactor as before.
79
+
75
80
  </Step>
76
81
 
77
82
  </Steps>
@@ -111,13 +116,7 @@ export const dbUrl = vault.secret("DATABASE_URL", {
111
116
 
112
117
  At boot, each contract is resolved through this chain — **first hit wins**:
113
118
 
114
- | # | Source | Typical content |
115
- | --- | -------------------- | ------------------------------------------------- |
116
- | 1 | `process.env` | Real environment (CI, hosting platform) |
117
- | 2 | `.env.local` | Your machine's local overrides (gitignored) |
118
- | 3 | `docker/.env.docker` | Generated compose stack credentials |
119
- | 4 | vault driver | OpenBao in docker/prod mode |
120
- | 5 | `dev` fallback | The `dev:` option on the contract (never in prod) |
119
+ <VaultResolution />
121
120
 
122
121
  If every layer misses a contract, boot **fails** before any request is served:
123
122
 
@@ -167,7 +166,7 @@ vault: {
167
166
  | `openbao` | OpenBao (KV v2) | Docker + prod — durable, access-controlled |
168
167
  | `memory` | in-process map | Tests |
169
168
 
170
- Other drivers (`infisical`, `managed`) implement the same `VaultDriver` interface — adding one is a driver exercise, not an architecture change.
169
+ Other drivers (`managed`) implement the same `VaultDriver` interface — adding one is a driver exercise, not an architecture change.
171
170
 
172
171
  ## OpenBao in docker and prod
173
172
 
@@ -1,11 +1,24 @@
1
1
  ---
2
2
  title: Basic Usage
3
- description: Scaffold the standard starter, write a Flow, and inspect it in the Console.
3
+ description: Scaffold the standard starter, call the health Flow, and inspect it in the Console.
4
4
  source: packages/create-oke/template
5
5
  icon: SquareTerminal
6
6
  ---
7
7
 
8
- ## Scaffold an app
8
+ Write your first Flow, call it from a typed client, and prove it with
9
+ `bun:test` — using the same standard starter `create-oke` scaffolds.
10
+
11
+ <Callout title="The one rule">
12
+ All reads, writes, emits, sends, and external calls belong behind `fx`. That is what makes effects
13
+ inspectable and tests deterministic.
14
+ </Callout>
15
+
16
+ ## Quick start
17
+
18
+ <Steps>
19
+
20
+ <Step>
21
+ ### Scaffold and run
9
22
 
10
23
  ```bash title="Terminal"
11
24
  bunx create-oke@latest my-app
@@ -13,26 +26,16 @@ cd my-app
13
26
  oke dev
14
27
  ```
15
28
 
16
- The app runs on `:6530`, the Console on `:6533`, and MCP on `:6535`.
29
+ <Surfaces />
17
30
 
18
- ## Mental model
31
+ </Step>
19
32
 
20
- Every backend behavior has one shape:
33
+ <Step>
34
+ ### Read the health Flow
21
35
 
22
- ```text
23
- on(Trigger) → Effects
24
- ```
36
+ The standard starter already exports a health Flow:
25
37
 
26
- 1. **Trigger** — how work starts (`http.post`, `every("10m")`, a signal, …)
27
- 2. **Contracts** — `in`, `out`, and typed `errors`
28
- 3. **`do`** — the body; world access goes only through `fx`
29
- 4. **Effects** — inferred from the `fx` calls
30
-
31
- ## Your first Flow
32
-
33
- The standard starter includes a health Flow:
34
-
35
- ```typescript
38
+ ```typescript title="flows/main"
36
39
  import { on, flow, http } from "okengine";
37
40
  import { z } from "zod";
38
41
 
@@ -45,57 +48,65 @@ export const health = on(
45
48
  );
46
49
  ```
47
50
 
48
- Open `src/flows/main/index.ts`, change the route or output, and save. The app and
49
- Console update together from the same Manifest.
51
+ Change the route or output and save. App and Console update together from the
52
+ same Manifest.
50
53
 
51
- <Callout title="The invariant">
52
- All reads, writes, emits, sends, and external calls belong behind `fx`. This is what makes effects
53
- inspectable and tests deterministic.
54
- </Callout>
54
+ </Step>
55
55
 
56
- ## Wire the app
56
+ <Step>
57
+ ### Call it from a typed client
57
58
 
58
- The starter adopts the module in `src/app.ts`:
59
+ Adopted modules become namespaces on the client:
59
60
 
60
- ```typescript
61
+ ```typescript title="app"
61
62
  import { oke } from "okengine";
62
63
  import * as main from "./flows/main";
63
64
 
64
- export const app = oke({ name: "my-app" }).adopt({ main });
65
+ export const app = oke({ name: "standard" }).adopt({ main });
65
66
  export type App = typeof app;
66
67
  ```
67
68
 
68
- The namespace becomes the typed-client namespace and each exported Flow becomes
69
- a method.
70
-
71
- ## Typed client
72
-
73
- ```typescript
69
+ ```typescript title="client"
74
70
  import { createClient } from "okengine/client";
75
- import type { App } from "../src/app";
71
+ import type { App } from "./app";
76
72
 
77
73
  const api = createClient<App>("http://localhost:6530");
78
74
  const { data, error } = await api.main.health({});
79
75
  ```
80
76
 
81
- `data` and `error` are inferred from the Flow contracts; no separate client
82
- schema or code-generation project is required.
77
+ `data` and `error` are inferred from the Flow contracts no separate client
78
+ schema or codegen project.
79
+
80
+ </Step>
81
+
82
+ </Steps>
83
+
84
+ ## Mental model
85
+
86
+ Every backend behavior has one shape:
87
+
88
+ <FlowShape />
89
+
90
+ ## From Flow to client
91
+
92
+ Export → adopt → call or test. Same App type — no separate codegen project.
93
+
94
+ <ClientLoop />
83
95
 
84
96
  ## Test
85
97
 
86
- Use `createTestApp` to boot the same app with test drivers:
98
+ Boot the same app with test drivers:
87
99
 
88
- ```typescript
100
+ ```typescript title="tests/standard.test.ts"
89
101
  import { expect, test } from "bun:test";
90
102
  import { createTestApp } from "okengine/test";
91
103
  import { app } from "../src/app";
92
104
 
93
- test("health", async () => {
105
+ test("boots — health flow", async () => {
94
106
  const t = await createTestApp(app);
95
107
  const { data, error } = await t.api.main.health({});
96
108
  expect(error).toBeNull();
97
109
  expect(data).toEqual({ ok: true });
98
- await app.stop();
99
110
  });
100
111
  ```
101
112
 
@@ -103,6 +114,30 @@ test("health", async () => {
103
114
  bun test
104
115
  ```
105
116
 
117
+ ## Troubleshooting
118
+
119
+ <Accordions>
120
+
121
+ <Accordion title="api.main.health is not a function / type error">
122
+ Confirm the Flow is `export`ed from the module you `.adopt({main})`, and that `createClient` is
123
+ typed with your `App` type. Restart `oke dev` after renaming exports so the Manifest refreshes.
124
+ </Accordion>
125
+
126
+ <Accordion title="createTestApp fails to boot">
127
+ The starter patches test env and stores on the app options. Keep the template's side-imports
128
+ (vault, gates, channels, core db) — removing them can leave Vault or Store unresolved under test
129
+ drivers.
130
+ </Accordion>
131
+
132
+ </Accordions>
133
+
134
+ ## Learn more
135
+
136
+ - [Flow](/docs/elements/flow) — triggers, contracts, effects, and composition
137
+ - [Store](/docs/elements/store) — SQL, KV, files, index
138
+ - [fx](/docs/reference/fx) — the full effect surface
139
+ - [Console · Overview](/docs/console/overview) — Manifest-derived panels
140
+
106
141
  ## Next
107
142
 
108
143
  <Cards>