okengine 0.18.4 → 0.19.0

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 (512) hide show
  1. package/AGENTS.md +18 -10
  2. package/README.md +2 -2
  3. package/package.json +51 -50
  4. package/site/content/docs/ai/index.mdx +25 -6
  5. package/site/content/docs/ai/llms-txt.mdx +1 -1
  6. package/site/content/docs/ai/meta.json +1 -1
  7. package/site/content/docs/ai/skills.mdx +4 -1
  8. package/site/content/docs/ai/try-it.mdx +58 -0
  9. package/site/content/docs/client/auth.mdx +208 -0
  10. package/site/content/docs/client/calling.mdx +451 -0
  11. package/site/content/docs/client/index.mdx +209 -0
  12. package/site/content/docs/client/live.mdx +234 -0
  13. package/site/content/docs/client/meta.json +5 -0
  14. package/site/content/docs/client/react.mdx +249 -0
  15. package/site/content/docs/elements/ai/agents.mdx +232 -0
  16. package/site/content/docs/elements/ai/index.mdx +341 -0
  17. package/site/content/docs/elements/ai/mcp.mdx +275 -0
  18. package/site/content/docs/elements/ai/meta.json +5 -0
  19. package/site/content/docs/elements/ai/models.mdx +283 -0
  20. package/site/content/docs/elements/ai/prompts.mdx +256 -0
  21. package/site/content/docs/elements/channel/email.mdx +266 -0
  22. package/site/content/docs/elements/channel/index.mdx +344 -0
  23. package/site/content/docs/elements/channel/meta.json +5 -0
  24. package/site/content/docs/elements/channel/push.mdx +218 -0
  25. package/site/content/docs/elements/channel/receipts.mdx +206 -0
  26. package/site/content/docs/elements/channel/sms.mdx +264 -0
  27. package/site/content/docs/elements/channel/whatsapp.mdx +220 -0
  28. package/site/content/docs/elements/clock/index.mdx +300 -0
  29. package/site/content/docs/elements/clock/meta.json +5 -0
  30. package/site/content/docs/elements/clock/schedules.mdx +508 -0
  31. package/site/content/docs/elements/clock/sleep.mdx +445 -0
  32. package/site/content/docs/elements/flow/consumers.mdx +707 -0
  33. package/site/content/docs/elements/flow/http.mdx +1080 -0
  34. package/site/content/docs/elements/flow/index.mdx +618 -0
  35. package/site/content/docs/elements/flow/meta.json +5 -0
  36. package/site/content/docs/elements/flow/routing.mdx +559 -0
  37. package/site/content/docs/elements/flow/workflows.mdx +640 -0
  38. package/site/content/docs/elements/gate/auth.mdx +371 -0
  39. package/site/content/docs/elements/gate/authorization.mdx +298 -0
  40. package/site/content/docs/elements/gate/index.mdx +418 -0
  41. package/site/content/docs/elements/gate/meta.json +5 -0
  42. package/site/content/docs/elements/gate/rate-limits.mdx +365 -0
  43. package/site/content/docs/elements/gate/rls.mdx +303 -0
  44. package/site/content/docs/elements/gate/tenancy.mdx +372 -0
  45. package/site/content/docs/elements/index.mdx +52 -11
  46. package/site/content/docs/elements/signal/broadcast.mdx +517 -0
  47. package/site/content/docs/elements/signal/index.mdx +352 -0
  48. package/site/content/docs/elements/signal/live.mdx +590 -0
  49. package/site/content/docs/elements/signal/meta.json +5 -0
  50. package/site/content/docs/elements/signal/once.mdx +596 -0
  51. package/site/content/docs/elements/store/files.mdx +659 -0
  52. package/site/content/docs/elements/store/index.mdx +310 -0
  53. package/site/content/docs/elements/store/kv.mdx +629 -0
  54. package/site/content/docs/elements/store/meta.json +5 -0
  55. package/site/content/docs/elements/store/search.mdx +946 -0
  56. package/site/content/docs/elements/store/sql.mdx +937 -0
  57. package/site/content/docs/elements/vault/config.mdx +256 -0
  58. package/site/content/docs/elements/vault/index.mdx +356 -0
  59. package/site/content/docs/elements/vault/meta.json +5 -0
  60. package/site/content/docs/elements/vault/rotation.mdx +273 -0
  61. package/site/content/docs/elements/vault/secrets.mdx +285 -0
  62. package/site/content/docs/index.mdx +78 -30
  63. package/site/content/docs/meta.json +11 -8
  64. package/site/content/docs/plugins/cors.mdx +2 -1
  65. package/site/content/docs/plugins/csrf.mdx +4 -1
  66. package/site/content/docs/plugins/headers.mdx +2 -1
  67. package/site/content/docs/plugins/ip-allowlist.mdx +2 -1
  68. package/site/content/docs/plugins/magic-link.mdx +6 -0
  69. package/site/content/docs/plugins/maintenance-mode.mdx +2 -1
  70. package/site/content/docs/plugins/oauth.mdx +1 -1
  71. package/site/content/docs/plugins/otp.mdx +12 -1
  72. package/site/content/docs/plugins/passkey.mdx +39 -15
  73. package/site/content/docs/plugins/two-factor.mdx +91 -34
  74. package/site/content/docs/plugins/username.mdx +1 -1
  75. package/site/content/docs/recipes/caddy.mdx +3 -3
  76. package/site/content/docs/recipes/index.mdx +18 -28
  77. package/site/content/docs/recipes/mailpit.mdx +1 -1
  78. package/site/content/docs/recipes/meilisearch.mdx +3 -3
  79. package/site/content/docs/recipes/meta.json +1 -4
  80. package/site/content/docs/recipes/nginx.mdx +2 -2
  81. package/site/content/docs/recipes/openrouter.mdx +283 -0
  82. package/site/content/docs/recipes/pgdog.mdx +3 -3
  83. package/site/content/docs/recipes/postgres.mdx +1 -2
  84. package/site/content/docs/recipes/rustfs.mdx +2 -2
  85. package/site/content/docs/recipes/traefik.mdx +3 -3
  86. package/site/content/docs/reference/cli.mdx +231 -0
  87. package/site/content/docs/reference/configuration.mdx +30 -10
  88. package/site/content/docs/reference/environment-variables.mdx +93 -20
  89. package/site/content/docs/reference/errors.mdx +132 -29
  90. package/site/content/docs/reference/fx.mdx +137 -73
  91. package/site/content/docs/reference/index.mdx +13 -21
  92. package/site/content/docs/reference/meta.json +4 -5
  93. package/site/content/docs/reference/okid.mdx +42 -17
  94. package/site/content/docs/reference/plugins.mdx +4 -3
  95. package/site/content/docs/reference/security.mdx +197 -0
  96. package/site/content/docs/understand/meta.json +5 -0
  97. package/site/content/docs/understand/the-anatomy.mdx +132 -0
  98. package/site/content/docs/understand/the-model.mdx +32 -0
  99. package/site/content/docs/understand/the-problem.mdx +74 -0
  100. package/site/content/docs/understand/the-vocabulary.mdx +26 -0
  101. package/src/auth/api-keys.ts +2 -1
  102. package/src/auth/bindings.ts +64 -16
  103. package/src/auth/gate-auth.test.ts +6 -1
  104. package/src/auth/identity.ts +152 -5
  105. package/src/auth/index.ts +29 -0
  106. package/src/auth/invites.ts +2 -1
  107. package/src/auth/method-context.ts +41 -0
  108. package/src/auth/oauth-as/crypto.ts +2 -1
  109. package/src/auth/oauth-as/stores.ts +2 -1
  110. package/src/auth/operator.ts +2 -1
  111. package/src/auth/sessions-jwt.test.ts +77 -0
  112. package/src/auth/sessions.ts +20 -4
  113. package/src/auth/tenants.ts +3 -2
  114. package/src/auth/two-factor-challenge.test.ts +61 -0
  115. package/src/auth/two-factor-challenge.ts +284 -0
  116. package/src/auth/verification.ts +5 -0
  117. package/src/bench/REPORT.md +49 -0
  118. package/src/bench/g03-signal-once.bench.ts +1 -1
  119. package/src/bench/g10-observability-contention.bench.ts +1 -1
  120. package/src/bench/g17-hybrid-search.bench.ts +572 -0
  121. package/src/bench/load-app.ts +2 -10
  122. package/src/cli/ai-setup/ai-setup.test.ts +331 -42
  123. package/src/cli/ai-setup/apply.ts +294 -52
  124. package/src/cli/ai-setup/catalog.ts +238 -1342
  125. package/src/cli/ai-setup/index.ts +14 -99
  126. package/src/cli/ai-setup/prompts.ts +60 -443
  127. package/src/cli/ask-seed.test.ts +96 -0
  128. package/src/cli/ask-seed.ts +82 -0
  129. package/src/cli/ask-vault-gaps.test.ts +84 -0
  130. package/src/cli/ask-vault-gaps.ts +146 -0
  131. package/src/cli/build.ts +2 -2
  132. package/src/cli/client-add.test.ts +26 -1
  133. package/src/cli/client-add.ts +106 -27
  134. package/src/cli/competitor-mention-removal.test.ts +1 -1
  135. package/src/cli/db-seed.ts +2 -0
  136. package/src/cli/db.ts +150 -11
  137. package/src/cli/dev.ts +119 -211
  138. package/src/cli/docker-clean.ts +2 -2
  139. package/src/cli/docker-cli.test.ts +1 -1
  140. package/src/cli/doctor-diff.ts +4 -2
  141. package/src/cli/doctor-pii.test.ts +1 -1
  142. package/src/cli/doctor.ts +14 -1
  143. package/src/cli/hero-meta.test.ts +4 -2
  144. package/src/cli/load-config.images.test.ts +8 -10
  145. package/src/cli/load-config.ts +1 -1
  146. package/src/cli/registry.ts +22 -10
  147. package/src/cli/replay.ts +3 -1
  148. package/src/cli/start.ts +1 -1
  149. package/src/cli/tui/keys.ts +2 -3
  150. package/src/client/auth/cookies.ts +74 -0
  151. package/src/client/auth/create-auth-client.ts +636 -0
  152. package/src/client/auth/denials.ts +99 -0
  153. package/src/client/auth/session.ts +283 -0
  154. package/src/client/auth.test.ts +251 -0
  155. package/src/client/auth.ts +39 -114
  156. package/src/client/create-with-session.ts +245 -0
  157. package/src/client/create.ts +51 -5
  158. package/src/client/index.ts +11 -1
  159. package/src/client/live.ts +11 -79
  160. package/src/client/notes-contract.test.ts +5 -10
  161. package/src/client/sse.ts +137 -0
  162. package/src/client/stream.ts +147 -0
  163. package/src/client/transport.test.ts +32 -0
  164. package/src/client/transport.ts +123 -26
  165. package/src/client/types.ts +118 -14
  166. package/src/client-react/index.ts +185 -24
  167. package/src/client-react/use-live-query.ts +13 -3
  168. package/src/compiler/aot.test.ts +7 -4
  169. package/src/compiler/effects-embed.test.ts +56 -0
  170. package/src/compiler/effects-fetch.test.ts +43 -0
  171. package/src/compiler/effects-infer.ts +58 -2
  172. package/src/compiler/extract.test.ts +258 -44
  173. package/src/compiler/extract.ts +566 -48
  174. package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +7 -4
  175. package/src/compiler/fixtures/skyport/src/flows/bookings/signals.ts +2 -8
  176. package/src/compiler/fixtures/skyport.expected.json +4 -4
  177. package/src/compiler/fixtures/triggers/five-triggers.ts +14 -10
  178. package/src/compiler/response.ts +2 -2
  179. package/src/compiler/search-writer-isolation.test.ts +40 -0
  180. package/src/config/index.ts +11 -0
  181. package/src/console/server/app.ts +17 -23
  182. package/src/console/server/bind.ts +4 -0
  183. package/src/console/server/console.test.ts +2 -2
  184. package/src/console/server/flows-invoke.test.ts +50 -34
  185. package/src/console/server/flows.ts +436 -255
  186. package/src/console/server/runs-ingest.test.ts +6 -3
  187. package/src/console/server/serve.ts +1 -1
  188. package/src/console/server/signals.test.ts +1 -5
  189. package/src/console/server/signals.ts +8 -6
  190. package/src/console/server/state.ts +6 -1
  191. package/src/console/server/store.ts +1 -1
  192. package/src/console/ui-next/dist/assets/FileExportIcon-Ck-5od4R.js +1 -0
  193. package/src/console/ui-next/dist/assets/MoreHorizontalCircle01Icon-gMNGsE37.js +1 -0
  194. package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +1 -0
  195. package/src/console/ui-next/dist/assets/UnavailableIcon-D9cvHVPr.js +1 -0
  196. package/src/console/ui-next/dist/assets/UserIcon-DaE2PB5_.js +1 -0
  197. package/src/console/ui-next/dist/assets/access-page-DFeymU07.js +4 -0
  198. package/src/console/ui-next/dist/assets/agent-disclosure-U1rdfblp.js +1 -0
  199. package/src/console/ui-next/dist/assets/cache-glyph-BeFJeqBG.js +1 -0
  200. package/src/console/ui-next/dist/assets/call-pii-button-CVAONPii.js +1 -0
  201. package/src/console/ui-next/dist/assets/collapsible-D2A6NJ-3.js +1 -0
  202. package/src/console/ui-next/dist/assets/copy-inline-button-CAYD18cr.js +1 -0
  203. package/src/console/ui-next/dist/assets/dagre.esm-B1_XeuLP.js +1 -0
  204. package/src/console/ui-next/dist/assets/detail-header-DVWNjWjg.js +1 -0
  205. package/src/console/ui-next/dist/assets/dropdown-menu-4h2LOVXM.js +1 -0
  206. package/src/console/ui-next/dist/assets/duration-tone-Cgk_h5ja.js +9 -0
  207. package/src/console/ui-next/dist/assets/element-icons-BI8cJgdh.js +1 -0
  208. package/src/console/ui-next/dist/assets/explorer-empty-CJs5A-wm.js +1 -0
  209. package/src/console/ui-next/dist/assets/flows-page-Dss7941e.js +1 -0
  210. package/src/console/ui-next/dist/assets/highlighted-json-MYZQtRnw.js +154 -0
  211. package/src/console/ui-next/dist/assets/http-method-Jrh39p7A.js +1 -0
  212. package/src/console/ui-next/dist/assets/index-D0zS5rKO.css +2 -0
  213. package/src/console/ui-next/dist/assets/index-DXP2dBIF.js +63 -0
  214. package/src/console/ui-next/dist/assets/observability-page-HvolXxTI.js +4 -0
  215. package/src/console/ui-next/dist/assets/react-dom-Ddte4I-Q.js +9 -0
  216. package/src/console/ui-next/dist/assets/replica-lag-CSh2dzrb.js +18 -0
  217. package/src/console/ui-next/dist/assets/request-meta-BatF8KrK.js +1 -0
  218. package/src/console/ui-next/dist/assets/shortcut-keys-3ILd8oGn.js +1 -0
  219. package/src/console/ui-next/dist/assets/{sql-BskegiFM.js → sql-BsFa4tDR.js} +1 -1
  220. package/src/console/ui-next/dist/assets/store-page-eiKiHnNe.js +41 -0
  221. package/src/console/ui-next/dist/assets/trace-detail-sheet-CZkMeKS-.js +2 -0
  222. package/src/console/ui-next/dist/assets/tree-expand-toggle-CW8y5A2h.js +55 -0
  223. package/src/console/ui-next/dist/assets/units-page-B_RWJrEO.js +1 -0
  224. package/src/console/ui-next/dist/assets/{use-vault-list-uk4WVboC.js → use-vault-list-CT4-gajj.js} +1 -1
  225. package/src/console/ui-next/dist/assets/vault-page-CWrg-A68.js +2 -0
  226. package/src/console/ui-next/dist/assets/xyflow-CSyC6ryz.css +1 -0
  227. package/src/console/ui-next/dist/assets/xyflow-yApv7D4e.js +7 -0
  228. package/src/console/ui-next/dist/index.html +6 -13
  229. package/src/console/ui-next/seed-invoke-host.ts +21 -18
  230. package/src/console/ui-next/src/client.ts +7 -2
  231. package/src/console/ui-next/src/features/flows/graph/element-map.ts +2 -0
  232. package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +12 -2
  233. package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +4 -0
  234. package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +8 -0
  235. package/src/console/ui-next/src/features/flows/traces/waterfall-bars.ts +4 -0
  236. package/src/console/ui-next/src/features/flows/traces/waterfall-tooltip.ts +9 -3
  237. package/src/console/ui-next/src/features/store/detail/reveal-cell.tsx +7 -11
  238. package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +2 -2
  239. package/src/console/ui-next/src/features/store/lib/fields-from-table.ts +2 -0
  240. package/src/console/ui-next/src/features/store/lib/grid-model.test.ts +25 -1
  241. package/src/console/ui-next/src/features/store/lib/grid-model.ts +36 -1
  242. package/src/console/ui-next/src/features/store/query/query-results.tsx +3 -3
  243. package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +2 -0
  244. package/src/console/ui-next/src/features/units/lib/call-read-safe.ts +3 -1
  245. package/src/console/ui-next/src/features/units/lib/fields-from-schema.ts +6 -0
  246. package/src/docker/ai-model-status.test.ts +1 -12
  247. package/src/docker/ai-model-status.ts +10 -38
  248. package/src/docker/compose-up.test.ts +100 -0
  249. package/src/docker/compose-up.ts +292 -0
  250. package/src/docker/compose.ts +4 -14
  251. package/src/docker/derive.ts +9 -34
  252. package/src/docker/docker.test.ts +16 -261
  253. package/src/docker/images-config.test.ts +13 -25
  254. package/src/docker/index.ts +1 -24
  255. package/src/docker/recipes/index.ts +0 -23
  256. package/src/docker/recipes/pgdog.ts +6 -4
  257. package/src/docker/stack-id.test.ts +2 -2
  258. package/src/docker/stack-id.ts +0 -1
  259. package/src/docker/types.ts +2 -2
  260. package/src/drivers/ai-anthropic.ts +10 -0
  261. package/src/drivers/ai-openai-compatible.ts +34 -7
  262. package/src/drivers/ai-providers.test.ts +0 -107
  263. package/src/drivers/ai-stream.test.ts +0 -34
  264. package/src/drivers/ai-types.ts +17 -8
  265. package/src/drivers/channel-fcm.ts +6 -1
  266. package/src/drivers/channel-msegat.ts +6 -1
  267. package/src/drivers/channel-resend.ts +5 -1
  268. package/src/drivers/channel-smtp.ts +5 -1
  269. package/src/drivers/channel-sndr.ts +10 -1
  270. package/src/drivers/channel-taqnyat-mail.ts +5 -1
  271. package/src/drivers/channel-taqnyat-whatsapp.ts +6 -1
  272. package/src/drivers/channel-taqnyat.ts +6 -1
  273. package/src/drivers/channel-types.ts +10 -0
  274. package/src/drivers/channel-unifonic.ts +6 -1
  275. package/src/drivers/channel-wa-cloud.ts +6 -1
  276. package/src/drivers/channel-webpush.ts +6 -1
  277. package/src/drivers/external.ts +33 -0
  278. package/src/drivers/index.ts +0 -10
  279. package/src/drivers/meilisearch.ts +6 -0
  280. package/src/drivers/oauth-types.ts +4 -0
  281. package/src/drivers/postgres.test.ts +74 -3
  282. package/src/drivers/postgres.ts +175 -6
  283. package/src/drivers/signal-types.ts +5 -5
  284. package/src/drivers/types.ts +7 -0
  285. package/src/drivers/vault-types.ts +5 -0
  286. package/src/elements/ai/declare.ts +17 -4
  287. package/src/elements/ai/eval.ts +1 -1
  288. package/src/elements/ai/mcp-http.ts +1 -1
  289. package/src/elements/ai/pii.ts +1 -1
  290. package/src/elements/ai/providers.test.ts +289 -0
  291. package/src/elements/ai/providers.ts +169 -0
  292. package/src/elements/ai/runtime.ts +68 -5
  293. package/src/elements/ai/schema.ts +2 -2
  294. package/src/elements/ai.test.ts +7 -7
  295. package/src/elements/ai.ts +17 -1
  296. package/src/elements/channel/runtime.ts +105 -12
  297. package/src/elements/clock/cron-fields.test.ts +144 -0
  298. package/src/elements/clock/cron-fields.ts +185 -0
  299. package/src/elements/clock/declare.ts +247 -3
  300. package/src/elements/clock.ts +21 -1
  301. package/src/elements/index.ts +15 -0
  302. package/src/elements/signal/chaos-child.ts +1 -2
  303. package/src/elements/signal/declare.ts +65 -37
  304. package/src/elements/signal/delivery-modes.test.ts +11 -31
  305. package/src/elements/signal/dry-run-replay.test.ts +1 -7
  306. package/src/elements/signal/dry-run-write-isolation.test.ts +1 -7
  307. package/src/elements/signal/key-ordering.test.ts +6 -26
  308. package/src/elements/signal/lease-reclaim.test.ts +2 -6
  309. package/src/elements/signal/optional-emit.test.ts +6 -9
  310. package/src/elements/signal/order-lifecycle.test.ts +4 -17
  311. package/src/elements/signal/orphan-messages.test.ts +4 -15
  312. package/src/elements/signal/reconcile.test.ts +2 -2
  313. package/src/elements/signal/runtime.ts +1 -1
  314. package/src/elements/signal/schema-emit.test.ts +4 -8
  315. package/src/elements/signal.test.ts +24 -49
  316. package/src/elements/signal.ts +9 -2
  317. package/src/elements/store/domain-ddl.test.ts +1 -1
  318. package/src/elements/store/live-http.test.ts +9 -4
  319. package/src/elements/store/prepare-row.test.ts +123 -0
  320. package/src/elements/store/resource.ts +43 -12
  321. package/src/elements/store/schema-decl.ts +117 -0
  322. package/src/elements/store/search-backfill.ts +205 -0
  323. package/src/elements/store/search-bind.ts +66 -0
  324. package/src/elements/store/search-bm25.ts +78 -0
  325. package/src/elements/store/search-ddl.ts +156 -0
  326. package/src/elements/store/search-embed-flow.ts +141 -0
  327. package/src/elements/store/search-errors.ts +34 -0
  328. package/src/elements/store/search-fusion.ts +112 -0
  329. package/src/elements/store/search-lsh.ts +179 -0
  330. package/src/elements/store/search-runtime.ts +300 -0
  331. package/src/elements/store/search.test.ts +144 -0
  332. package/src/elements/store/sql-session.test.ts +1 -0
  333. package/src/elements/store/sql-session.ts +83 -3
  334. package/src/elements/store/table.ts +23 -2
  335. package/src/elements/store/upsert-app.test.ts +1 -3
  336. package/src/elements/store.ts +50 -0
  337. package/src/full.ts +4 -2
  338. package/src/http.ts +4 -1
  339. package/src/i18n/catalogs/ar.ts +14 -6
  340. package/src/i18n/catalogs/en.ts +14 -6
  341. package/src/index.ts +6 -1
  342. package/src/kernel/adopt-barrel-fresh.test.ts +7 -7
  343. package/src/kernel/adopt-routes.ts +9 -1
  344. package/src/kernel/app-auth.ts +5 -0
  345. package/src/kernel/app.ts +102 -9
  346. package/src/kernel/auto-cache.test.ts +6 -12
  347. package/src/kernel/auto-registry.test.ts +3 -3
  348. package/src/kernel/boot-bind/ai.test.ts +22 -19
  349. package/src/kernel/boot-bind/ai.ts +5 -43
  350. package/src/kernel/boot-bind/clock.ts +23 -5
  351. package/src/kernel/boot-bind/honor-config.test.ts +4 -4
  352. package/src/kernel/boot.test.ts +11 -6
  353. package/src/kernel/boot.ts +66 -16
  354. package/src/kernel/boundary-contract.ts +93 -0
  355. package/src/kernel/budget.test.ts +1 -1
  356. package/src/kernel/call.ts +89 -0
  357. package/src/kernel/capability.ts +6 -0
  358. package/src/kernel/client-descriptor.ts +123 -0
  359. package/src/kernel/clock-timezone.test.ts +80 -0
  360. package/src/kernel/correlation.test.ts +1 -1
  361. package/src/kernel/dry-run.ts +11 -8
  362. package/src/kernel/effects-stamping.test.ts +42 -9
  363. package/src/kernel/effects.test.ts +4 -2
  364. package/src/kernel/effects.ts +51 -9
  365. package/src/kernel/errors-channel.ts +17 -0
  366. package/src/kernel/errors-live-resume.ts +3 -2
  367. package/src/kernel/errors-tenant.ts +7 -4
  368. package/src/kernel/errors.registry-helpers.ts +96 -0
  369. package/src/kernel/errors.registry.test.ts +139 -25
  370. package/src/kernel/errors.ts +102 -23
  371. package/src/kernel/external-effects.test.ts +196 -0
  372. package/src/kernel/flow.test.ts +13 -4
  373. package/src/kernel/flow.ts +69 -71
  374. package/src/kernel/fx-ask-telemetry.test.ts +2 -2
  375. package/src/kernel/fx-dead-letters.test.ts +3 -3
  376. package/src/kernel/fx-fetch.ts +55 -0
  377. package/src/kernel/fx-live-stream.ts +2 -2
  378. package/src/kernel/fx-live.test.ts +8 -8
  379. package/src/kernel/fx.test.ts +17 -11
  380. package/src/kernel/fx.ts +180 -57
  381. package/src/kernel/horizontal-child.ts +1 -1
  382. package/src/kernel/http-query.test.ts +3 -6
  383. package/src/kernel/index.ts +13 -3
  384. package/src/kernel/instance-id.ts +1 -1
  385. package/src/kernel/instances.test.ts +40 -0
  386. package/src/kernel/instances.ts +47 -9
  387. package/src/kernel/live-http.test.ts +6 -7
  388. package/src/kernel/live-http.ts +6 -2
  389. package/src/kernel/live-resume.test.ts +1 -1
  390. package/src/kernel/mcp-tool.test.ts +3 -3
  391. package/src/kernel/on.ts +98 -4
  392. package/src/kernel/pipeline.test.ts +12 -15
  393. package/src/kernel/plugin-elements.test.ts +1 -1
  394. package/src/kernel/stamp-http.test.ts +3 -3
  395. package/src/kernel/triggers.ts +210 -95
  396. package/src/kernel-entry.ts +0 -1
  397. package/src/manifest/diff.ts +11 -1
  398. package/src/manifest/fixtures/skyport.excerpt.json +1 -1
  399. package/src/manifest/types.ts +46 -2
  400. package/src/mcp/docs-index.ts +3 -3
  401. package/src/mcp/docs-mcp.test.ts +2 -2
  402. package/src/mcp/docs-tools.ts +1 -1
  403. package/src/okid.test.ts +56 -0
  404. package/src/okid.ts +55 -11
  405. package/src/plugins/anonymous.ts +8 -4
  406. package/src/plugins/auth/shared.ts +27 -3
  407. package/src/plugins/auth-delivery.mailpit.integration.test.ts +1 -1
  408. package/src/plugins/auth-methods.security.test.ts +561 -23
  409. package/src/plugins/config-source.ts +1 -1
  410. package/src/plugins/csrf.test.ts +65 -0
  411. package/src/plugins/index.ts +2 -0
  412. package/src/plugins/magic-link.ts +36 -16
  413. package/src/plugins/oauth/shared.ts +3 -1
  414. package/src/plugins/oauth.ts +22 -14
  415. package/src/plugins/otp.ts +37 -21
  416. package/src/plugins/passkey-webauthn.ts +9 -6
  417. package/src/plugins/passkey.ts +98 -26
  418. package/src/plugins/pre-account-hijack.test.ts +223 -0
  419. package/src/plugins/two-factor.ts +466 -37
  420. package/src/plugins/username.ts +19 -7
  421. package/src/release/build-lib.ts +1 -0
  422. package/src/release/limits.ts +2 -2
  423. package/src/release/measure.ts +1 -1
  424. package/src/runs/collect.test.ts +0 -2
  425. package/src/runtime/json-code-block.test.ts +250 -20
  426. package/src/runtime/json-code-block.ts +1261 -41
  427. package/src/term.test.ts +1 -1
  428. package/src/test/create-test-app.test.ts +12 -12
  429. package/src/test/live-signals.test.ts +6 -6
  430. package/src/test/provisions.integration.test.ts +10 -14
  431. package/src/test/reset-element-registries.ts +1 -1
  432. package/src/test/tenant-isolation.test.ts +12 -6
  433. package/site/content/docs/deployment/docker-swarm.mdx +0 -164
  434. package/site/content/docs/deployment/docker.mdx +0 -227
  435. package/site/content/docs/deployment/index.mdx +0 -83
  436. package/site/content/docs/deployment/kubernetes.mdx +0 -176
  437. package/site/content/docs/deployment/meta.json +0 -5
  438. package/site/content/docs/deployment/reverse-proxy.mdx +0 -234
  439. package/site/content/docs/elements/ai.mdx +0 -385
  440. package/site/content/docs/elements/channel.mdx +0 -346
  441. package/site/content/docs/elements/clock.mdx +0 -244
  442. package/site/content/docs/elements/flow.mdx +0 -420
  443. package/site/content/docs/elements/gate.mdx +0 -436
  444. package/site/content/docs/elements/signal.mdx +0 -380
  445. package/site/content/docs/elements/store.mdx +0 -1099
  446. package/site/content/docs/elements/vault.mdx +0 -405
  447. package/site/content/docs/get-started/basic-usage.mdx +0 -173
  448. package/site/content/docs/get-started/index.mdx +0 -43
  449. package/site/content/docs/get-started/installation.mdx +0 -220
  450. package/site/content/docs/get-started/introduction.mdx +0 -132
  451. package/site/content/docs/get-started/meta.json +0 -13
  452. package/site/content/docs/get-started/project-structure.mdx +0 -925
  453. package/site/content/docs/get-started/testing.mdx +0 -328
  454. package/site/content/docs/get-started/why.mdx +0 -162
  455. package/site/content/docs/recipes/llama-cpp.mdx +0 -151
  456. package/site/content/docs/recipes/ollama.mdx +0 -142
  457. package/site/content/docs/recipes/sglang.mdx +0 -105
  458. package/site/content/docs/recipes/vllm.mdx +0 -106
  459. package/site/content/docs/reference/cli.md +0 -232
  460. package/site/content/docs/reference/client.mdx +0 -469
  461. package/site/content/docs/reference/security.md +0 -74
  462. package/src/cli/ai-setup/detect-ollama.ts +0 -213
  463. package/src/cli/ai-setup/recommend.test.ts +0 -225
  464. package/src/cli/ai-setup/recommend.ts +0 -225
  465. package/src/cli/dev-controls.test.ts +0 -122
  466. package/src/cli/dev-controls.ts +0 -164
  467. package/src/cli/tui/DevLive.tsx +0 -126
  468. package/src/console/ui-next/dist/assets/access-page-De7Lc2JC.js +0 -4
  469. package/src/console/ui-next/dist/assets/agent-disclosure-BP0Y0Sux.js +0 -1
  470. package/src/console/ui-next/dist/assets/cache-glyph-B5X-NM0-.js +0 -1
  471. package/src/console/ui-next/dist/assets/call-pii-button-ChAUiLo9.js +0 -1
  472. package/src/console/ui-next/dist/assets/collapsible-DGnOM2ph.js +0 -1
  473. package/src/console/ui-next/dist/assets/copy-inline-button-DBHhgHKP.js +0 -1
  474. package/src/console/ui-next/dist/assets/dagre.esm-ZwcdTuZZ.js +0 -1
  475. package/src/console/ui-next/dist/assets/detail-header-DhHM1iaZ.js +0 -1
  476. package/src/console/ui-next/dist/assets/dropdown-menu-_NjTEo5_.js +0 -1
  477. package/src/console/ui-next/dist/assets/duration-tone-sC3lGABz.js +0 -9
  478. package/src/console/ui-next/dist/assets/element-icons-BVXtRyd3.js +0 -1
  479. package/src/console/ui-next/dist/assets/explorer-empty-2uIhBu0_.js +0 -1
  480. package/src/console/ui-next/dist/assets/flows-page-RGy7VEA_.js +0 -1
  481. package/src/console/ui-next/dist/assets/highlighted-json-Awq7gYdu.js +0 -154
  482. package/src/console/ui-next/dist/assets/http-method-_UHM2ODJ.js +0 -1
  483. package/src/console/ui-next/dist/assets/index-Ck88Jmv8.css +0 -2
  484. package/src/console/ui-next/dist/assets/index-_rgpdVzo.js +0 -66
  485. package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +0 -1
  486. package/src/console/ui-next/dist/assets/observability-page-Ds6pcnh-.js +0 -4
  487. package/src/console/ui-next/dist/assets/preload-helper-oH4irX4C.js +0 -1
  488. package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +0 -1
  489. package/src/console/ui-next/dist/assets/react-dom-Bph1y7z7.js +0 -9
  490. package/src/console/ui-next/dist/assets/replica-lag-DKRrbvdo.js +0 -18
  491. package/src/console/ui-next/dist/assets/request-meta-DV0ywz7t.js +0 -1
  492. package/src/console/ui-next/dist/assets/shortcut-JQIZlWfm.js +0 -1
  493. package/src/console/ui-next/dist/assets/shortcut-keys-DKxNTe_m.js +0 -1
  494. package/src/console/ui-next/dist/assets/skeleton-D-czQJT6.js +0 -1
  495. package/src/console/ui-next/dist/assets/store-page-02xOiqIK.js +0 -41
  496. package/src/console/ui-next/dist/assets/trace-detail-sheet-B09O8rA6.js +0 -2
  497. package/src/console/ui-next/dist/assets/tree-expand-toggle-BtyhmWb4.js +0 -55
  498. package/src/console/ui-next/dist/assets/units-page-4rHOePuE.js +0 -1
  499. package/src/console/ui-next/dist/assets/useMutation-B8EO02Ej.js +0 -1
  500. package/src/console/ui-next/dist/assets/useRender-BE2A9BWC.js +0 -1
  501. package/src/console/ui-next/dist/assets/vault-page-DISPgxLM.js +0 -2
  502. package/src/console/ui-next/dist/assets/xyflow-D7n4g6go.js +0 -7
  503. package/src/console/ui-next/dist/assets/xyflow-DZ0Ws1xk.css +0 -1
  504. package/src/docker/ollama-pull.ts +0 -232
  505. package/src/docker/recipes/llama-cpp.ts +0 -298
  506. package/src/docker/recipes/ollama.ts +0 -44
  507. package/src/docker/recipes/sglang.ts +0 -55
  508. package/src/docker/recipes/vllm.ts +0 -44
  509. package/src/drivers/ai-ollama-tools.integration.test.ts +0 -109
  510. package/src/drivers/ai-ollama.integration.test.ts +0 -184
  511. package/src/drivers/ai-ollama.ts +0 -389
  512. package/src/drivers/ollama.ts +0 -14
@@ -1,420 +0,0 @@
1
- ---
2
- title: "Flow"
3
- description: "Behavior — endpoints, jobs, consumers, and workflows as one species: a typed trigger, declared contracts, and a do that touches the world only through fx."
4
- icon: "Workflow"
5
- source: "docs/spec/unified-theory.md"
6
- ---
7
-
8
- Flow is how your backend **does anything**. An HTTP endpoint, a queue consumer, a cron job, a multi-step payment workflow — in other stacks these are four frameworks; here they are one species with one shape: `on(trigger, flow)`. Learn the shape once, and only the trigger ever changes.
9
-
10
- <Callout title="The one rule">
11
- All world access goes through `fx`. A direct `fetch`, `Date.now()`, or `node:` import inside a
12
- flow is a defect — effects are **inferred from what a flow touches through `fx`**, and that
13
- inference is what powers the Manifest, the Console, caching, and durability.
14
- </Callout>
15
-
16
- ## Quick start
17
-
18
- <Steps>
19
-
20
- <Step>
21
- ### Write the flow
22
-
23
- Four declarations plus a `do`:
24
-
25
- ```typescript title="src/flows/orders/create.ts"
26
- import { on, flow, http } from "okengine";
27
- import { z } from "zod";
28
-
29
- export const createOrder = on(
30
- http.post("/orders"),
31
- flow("orders.create", {
32
- in: z.object({ sku: z.string(), qty: z.number().int().min(1) }),
33
- out: z.object({ id: z.string() }),
34
- errors: { OutOfStock: z.object({ left: z.number() }) },
35
- do: async (input, fx) => {
36
- const id = fx.id();
37
- await fx
38
- .store(db)
39
- .insert(orders)
40
- .values({ id, ...input, status: "pending" });
41
- return { id };
42
- },
43
- }),
44
- );
45
- ```
46
-
47
- </Step>
48
-
49
- <Step>
50
- ### Run it
51
-
52
- `oke dev` picks up every exported flow — no route registration, no controller wiring:
53
-
54
- ```bash
55
- oke dev
56
- ```
57
-
58
- </Step>
59
-
60
- <Step>
61
- ### Call it
62
-
63
- ```bash
64
- curl -X POST localhost:6530/orders -d '{"sku":"SKU-1","qty":2}' -H 'content-type: application/json'
65
- # { "data": { "id": "…" }, "error": null }
66
- ```
67
-
68
- </Step>
69
-
70
- </Steps>
71
-
72
- ## Anatomy of a Flow
73
-
74
- | Part | Role |
75
- | -------------- | ------------------------------------------------------------------------------------ |
76
- | trigger | What starts the flow — `http`, a signal, `every`, a row change |
77
- | `in` | Input contract — validated before `do` runs; bad input is a 422 |
78
- | `out` | Output contract — the return value is checked against it |
79
- | `errors` | Typed failures — **returned** with `fx.fail`, never thrown |
80
- | `retry` | Optional whole-`do` backoff on thrown errors (same journal) |
81
- | `cache` | Optional — `false` opts out; `"30s"` adds a TTL. Read-only flows cache by default |
82
- | `tenantScoped` | Default `true` when `gate.auth.tenant` is on — set `false` to skip tenant-role union |
83
- | `do` | The work — every read, write, emit, and call goes through `fx` |
84
- | `compensate` | Durable only — optional hook after auto per-step `{ undo }` (same journal) |
85
-
86
- Failures are values, not exceptions:
87
-
88
- ```typescript
89
- do: async (input, fx) => {
90
- const [product] = await fx.store(db).select().from(products).where(eq(products.sku, input.sku));
91
- if (!product || product.stock < input.qty) {
92
- return fx.fail("OutOfStock", { left: product?.stock ?? 0 }); // declared in errors
93
- }
94
- // …
95
- };
96
- ```
97
-
98
- Every response follows one envelope — success `{ data, error: null }`, failure
99
- `{ data: null, error: { code, data, message? } }` — clients switch on `error.code`.
100
- Built-in codes attach a localized `message` ([Errors](/docs/reference/errors)); custom codes stay message-less unless registered or `{ message }` is passed.
101
-
102
- <Callout title="List pages">
103
- Prefer `out` as the item array and `return fx.json.withQuery(rows, input)` so the pager is
104
- top-level `meta`. Do not put `{ data, meta }` on `out`. See
105
- [Store](/docs/elements/store#handwritten-http-lists).
106
- </Callout>
107
-
108
- <Callout title="Helpers take the real Fx">
109
- Type a helper's `fx` as the exported `Fx` (`import type { Fx } from
110
- "okengine"`). A narrower `{ store: … }` will not match `store()` overloads
111
- — that is the `as never` trap. See [fx](/docs/reference/fx).
112
- </Callout>
113
-
114
- ## The triggers
115
-
116
- <FlowTriggers />
117
-
118
- ### http — a request arrives
119
-
120
- `http.get` · `http.post` · `http.put` · `http.patch` · `http.delete` · `http.head` · `http.options` · `http.query`. QUERY (RFC 10008) is a safe, idempotent read with a JSON body.
121
-
122
- `http.get()` with no path is filled from the file tree (`flows/notes/[id]/get.ts` → `/notes/:id`). `flow({ do })` is named `unit.export`. Explicit `http.get("/me/tasks")` and `flow("notes.get")` still win. Folder conventions live on [Project structure](/docs/get-started/project-structure).
123
-
124
- `Content-Type: application/json` is required — missing → **400** `InvalidQuery`, anything else → **415** `UnsupportedMediaType` with `Accept-Query: "application/json"`. Invalid JSON is **400** (no sniffing). Responses advertise `Accept-Query`. okengine does not assign `Location` / `Content-Location` result URIs.
125
-
126
- A browser `GET` (`Accept` prefers `text/html`) paints the `{ data, error }` envelope in traces chrome — status, latency, and cache, Routes rail (static GET links), copy, line numbers, Pretty / Raw, Console on **6533**. `curl`, `Accept: application/json`, and `?format=json` stay JSON.
127
-
128
- ```typescript
129
- export const findOrder = on(
130
- http.get("/orders/:id").gate(member), // gates evaluate before do runs
131
- flow("orders.find", {
132
- in: z.object({ id: z.string() }),
133
- out: Order,
134
- errors: { NotFound: z.object({}) },
135
- do: async ({ id }, fx) => (await fx.store(db).findById(orders, id)) ?? fx.fail("NotFound", {}),
136
- }),
137
- );
138
- ```
139
-
140
- ### signal — another flow emits
141
-
142
- The producer emits through `fx`. For `once` and `broadcast` the consumer is the same species —
143
- `on(signal, flow)`, no `subscribe()` registration. `live` replay is HTTP SSE: `.live(signal)` on a
144
- GET trigger, `fx.live` as the stream carrier, `api.live` on the client.
145
-
146
- ```typescript
147
- await fx.emit(orderPlaced, { orderId: id }); // inside the producing flow
148
-
149
- on(
150
- orderPlaced,
151
- flow("orders.sendReceipt", {
152
- do: async ({ orderId }, fx) => {
153
- /* … */
154
- },
155
- }),
156
- );
157
- ```
158
-
159
- ### clock — time passes
160
-
161
- `every("1h")` is a trigger value, not a registration with a scheduler library:
162
-
163
- ```typescript
164
- on(
165
- every("1h"),
166
- flow("sessions.sweepExpired", {
167
- do: async (_, fx) => {
168
- const cutoff = fx.clock.ago("30d");
169
- await fx.store(db).delete(sessions).where(lt(sessions.createdAt, cutoff));
170
- },
171
- }),
172
- );
173
- ```
174
-
175
- ### store change — a row changes
176
-
177
- CDC is built in; the flow receives `{ before, after }`:
178
-
179
- ```typescript
180
- on(
181
- db.table(orders).changed("status"),
182
- flow("orders.reverify", {
183
- do: ({ before, after }, fx) => fx.log.info("status", { from: before.status, to: after.status }),
184
- }),
185
- );
186
- ```
187
-
188
- ### no trigger — a callable flow
189
-
190
- Drop `on()` and it is still a real Flow — contracts, Manifest entry, everything. Other flows call it through `fx.call`:
191
-
192
- ```typescript
193
- export const getOrder = flow("orders.get", {
194
- in: OrderRef,
195
- out: Order,
196
- do: async ({ id }, fx) => {
197
- /* … */
198
- },
199
- });
200
-
201
- const order = await fx.call(getOrder, { id: orderId }); // from any other flow
202
- ```
203
-
204
- ### mcp — an agent calls a tool
205
-
206
- `mcp.tool(name)` marks a flow for **OAuth-protected user-plane** exposure over MCP. Deny-by-default: a flow without this trigger is never listed over `tools/list`. A gate is required, exactly like a sensitive HTTP route:
207
-
208
- ```typescript
209
- export const createBooking = on(
210
- mcp.tool("bookings.create").gate(member), // gates required — deny-by-default
211
- flow("bookings.create", {
212
- plane: "user",
213
- in: BookingRef,
214
- out: Booking,
215
- do: async ({ id }, fx) => {
216
- /* … */
217
- },
218
- }),
219
- );
220
- ```
221
-
222
- - The tool name is namespaced (`bookings.create`) and appears in the Manifest under `trigger.mcp.name` with its gates.
223
- - Binding the **same tool name** twice fails boot with **OKE1018** (`MCP tool "{tool}" is bound twice`).
224
- - Ungated `mcp.tool(...)` fails gate posture at boot (`GateBootError`) — the trigger never exposes an unauthenticated tool.
225
-
226
- This is one direction of [MCP](/docs/ai/mcp): exposing your flows as tools. The other is consuming external servers as `ai.mcpServer` tools via `fx.ask` / `ai.agent`.
227
-
228
- ## fx — the only door
229
-
230
- Everything a flow may touch, on one object:
231
-
232
- | Surface | Effect recorded | What it does |
233
- | ------------------------------------------- | --------------- | -------------------------------------------------- |
234
- | `fx.store(db).select/insert/…` | read / write | SQL, KV, files, index sessions |
235
- | `fx.emit(signal, payload)` | emit | Publish a signal (transactional with writes) |
236
- | `fx.deadLetters(signal)` | read | Dead-lettered messages for that signal |
237
- | `fx.live(signal, { match? })` | read | Live signal SSE carrier (`JsonStreamResult`) |
238
- | `fx.send(template, opts)` | send | Reach a human (email · SMS · …) |
239
- | `fx.ask(prompt, input)` | ask | Call a versioned AI prompt |
240
- | `fx.run(agent, input)` | ask | Run a bounded agent |
241
- | `fx.call(flow, input)` | call | Invoke another flow |
242
- | `fx.vault.get(contract)` | secret | Read a secret (`Redacted`; logs masked) |
243
- | `fx.clock.now/ago/fromNow/duration/sleep` | — | Injected instants, spans, durable sleep |
244
- | `fx.cache.get/set` | — | Shared cache with effect-aware invalidation |
245
- | `fx.step(name, fn, { undo? })` | — | Named durable step — optional LIFO undo on failure |
246
- | `fx.all` / `fx.race` / `fx.retry` | — | Structured concurrency + backoff retry |
247
- | `fx.signal` | — | Ambient `AbortSignal` for the current branch |
248
- | `fx.id()` · `fx.log` · `fx.t` · `fx.locale` | — | UUIDs, redacting logger, ICU i18n |
249
- | `fx.auth` · `fx.operator` · `fx.tenant` | — | Who is calling (user / operator / tenant) |
250
-
251
- <Callout title="Why this strictness pays off">
252
- Effects are inferred from `fx` usage, so the Manifest knows exactly which flows read `orders` or
253
- send PII to a model — without you declaring any of it. That one graph drives the Console panels,
254
- cache invalidation, least-privilege tokens, and durable replay.
255
- </Callout>
256
-
257
- ## Cache — automatic for reads
258
-
259
- Read-only flows cache automatically from those inferred `reads`. A write to the same
260
- resource invalidates the entry. You do not declare `effects` or set `cache: "30s"`
261
- unless you want a TTL or to opt out.
262
-
263
- | On the flow | What happens |
264
- | ---------------- | --------------------------------------- |
265
- | omitted / `true` | Tier-1 cache when the flow only `reads` |
266
- | `cache: "30s"` | Same, plus a TTL |
267
- | `cache: false` | Skip cache (live SQL every time) |
268
-
269
- Mutations, `fx.ask`, and `durable: true` stay uncached. Console Traces shows **hit** /
270
- **miss** on cached reads, and **not applicable** only when the flow never used the cache.
271
-
272
- A hit records the cache lookup on the waterfall (`computed:…` reads), not a replay of
273
- the original store reads.
274
-
275
- ## Durability — flows that survive the process
276
-
277
- <FlowDurable />
278
-
279
- Set `durable: true` and every `fx` call is journaled. Wrap side effects in `fx.step` and they never re-run on replay:
280
-
281
- ```typescript
282
- export const chargeOrder = flow("payments.chargeOrder", {
283
- durable: true, // every fx call below is journaled
284
- in: OrderRef,
285
- out: z.boolean(),
286
- do: async ({ orderId }, fx) => {
287
- const key = await fx.vault.get(stripeKey);
288
- const intent = await fx.step("create-intent", () => stripe(key.reveal()).create(orderId));
289
-
290
- await fx.clock.sleep("verify-window", "2m"); // journals wakeAt in a durable flow
291
-
292
- return fx.step("confirm", () => stripe(key.reveal()).confirm(intent));
293
- },
294
- });
295
- ```
296
-
297
- **Consequence:** completed steps replay from the journal — on resume, `create-intent` does not re-run, so the card is not charged twice.
298
-
299
- **Crash recovery needs a shared journal.** Set `drivers.journal: "postgres"` (the `dev`/`prod` template default) and every run persists in `oke_journal_runs` under a per-run lease: boot scans for orphaned `running` / `compensating` / due `sleeping` runs and resumes them, and the lease lets exactly one instance execute a run at a time. The default `memory` journal is per process — replay holds for that process only.
300
-
301
- Replay is **at-least-once** for a step whose body never persisted (crash mid-step, lease overrun): that step re-runs on reclaim, so keep step bodies short or internally idempotent. A client transport retry starts a _new_ run with a fresh `runId` — request-level idempotency is an application concern, not the journal's.
302
-
303
- For flaky sub-steps, wrap the work in `fx.retry` **inside** `fx.step` so a completed charge is never retried on resume. Coarse whole-body retry is also available as `flow(name, { retry: { retries, delay, backoff, jitter } })` and reuses the same journal session.
304
-
305
- After a **terminal** failure (`fx.fail` or exhausted retries / uncaught throw), declare
306
- undo next to each step. Only **persisted** steps undo (LIFO) — if `charge` throws before
307
- it journals, `undo:charge` does not run:
308
-
309
- ```typescript
310
- export const chargeOrder = flow("payments.chargeOrder", {
311
- durable: true,
312
- in: OrderRef,
313
- out: z.boolean(),
314
- do: async ({ orderId }, fx) => {
315
- await fx.step("reserve", () => reserve(orderId), {
316
- undo: () => release(orderId),
317
- });
318
- await fx.step("charge", () => charge(orderId), {
319
- undo: (intent) => refund(intent), // journaled value — resume-safe
320
- });
321
- return true;
322
- },
323
- });
324
- ```
325
-
326
- The runtime journals undos as `undo:<name>` under `compensating`, then commits `failed`.
327
- Orphan reclaim resumes that phase; `flow.compensate` is optional after auto undos.
328
- `JournalSuspend` is park (no undo). Prefer `fx.using` or `{ undo }` — not both for one release.
329
-
330
- ## Composition is just calls
331
-
332
- Wiring is values flowing between flows — declared in code, never configured in a dashboard:
333
-
334
- ```typescript
335
- on(http.post("/orders"), createOrder); // ① a request arrives
336
- on(orderPlaced, sendReceipt); // ② its emit starts the consumer
337
- on(every("1h"), sweepExpired); // ③ time passes
338
- on(db.table(orders).changed("status"), reverify); // ④ a row changes
339
- // ⑤ getOrder — nothing starts it; every flow above can fx.call it
340
- ```
341
-
342
- ## Troubleshooting
343
-
344
- <Accordions>
345
- <Accordion title="My flow used fetch / Date.now directly and weird things happened">
346
-
347
- That bypasses the effect graph: the Manifest can't see the call, cache keys miss it, durable replay re-executes it, and tests can't freeze it. Move it behind `fx` — `fx.ask` / `fx.send` / `fx.clock.now()`, or a step inside a durable flow for true third-party calls.
348
-
349
- </Accordion>
350
- <Accordion title="Should I throw or fx.fail?">
351
-
352
- `fx.fail(code, data)` for expected outcomes — anything in `errors`. A throw is a crash: the run fails with a 500-shaped error and no typed code for the client. Expected failures are values so clients can switch on `error.code`.
353
-
354
- </Accordion>
355
- <Accordion title="Promise.all left orphan work running after one branch failed">
356
-
357
- Use `fx.all` (or `fx.race`) with thunks. The first rejection aborts siblings through `fx.signal`. Bare `Promise.all` / `Promise.race` do not cancel losers.
358
-
359
- </Accordion>
360
- <Accordion title="Durable flow retried a completed charge">
361
-
362
- Put `fx.retry` **inside** `fx.step`, not around it. Completed steps replay from the journal and never re-run. `flow(name, { retry })` is for coarse whole-body retries on the same session.
363
-
364
- </Accordion>
365
- <Accordion title="When does undo / compensate run?">
366
-
367
- Only after terminal failure for that journal attempt — not on sleep, not mid-retry, not on success. Per-step `{ undo }` runs first (LIFO, only for persisted steps), then optional `flow.compensate`. The run stays `compensating` until undos finish so a crash mid-undo resumes the undo phase, not forward `do`. Undo bodies must be idempotent. Irreversible effects (`send` / `ask`) need developer-declared undos or an explicit no-op.
368
-
369
- </Accordion>
370
- <Accordion title="How do I share logic between flows — a private function?">
371
-
372
- Make it a plain flow with no trigger and `fx.call` it. You keep contracts, the Manifest entry, and tracing; a bare function would hide the work from the effect graph.
373
-
374
- </Accordion>
375
- <Accordion title="The browser shows a code block instead of JSON">
376
-
377
- That is the HTML view for a navigated `GET` when `Accept` prefers `text/html`. Send `Accept: application/json`, add `?format=json`, or use `curl` — the envelope is unchanged. `?raw=1` is the compact view of the same page.
378
-
379
- </Accordion>
380
- <Accordion title="GET on a POST-only path">
381
-
382
- The router returns **405 Method Not Allowed** with `Allow` listing every method bound to that path. A path that no flow owns is still 404. When QUERY is allowed, the 405 also carries `Accept-Query: "application/json"`.
383
-
384
- </Accordion>
385
- <Accordion title="QUERY without Content-Type">
386
-
387
- RFC 10008 forbids sniffing. Send `Content-Type: application/json` and a JSON body. Missing header → `InvalidQuery` **400**; `text/plain` (or any non-JSON type) → `UnsupportedMediaType` **415**.
388
-
389
- </Accordion>
390
- <Accordion title="What happens if a cron flow throws?">
391
-
392
- Only that run fails — the schedule keeps firing and the process does not exit. The failed run lands in Console → Flows (Traces) with its trace.
393
-
394
- </Accordion>
395
- <Accordion title="Console shows Cache not applicable on a GET">
396
-
397
- The flow is not a read-only cache candidate — it writes, asks a model, is durable, or set
398
- `cache: false`. Leave `cache` omitted. The first identical GET is a **miss**; the second is
399
- a **hit**. A matching write turns the next read into a miss.
400
-
401
- </Accordion>
402
- </Accordions>
403
-
404
- ## Learn more
405
-
406
- - [Project structure](/docs/get-started/project-structure) — folders are the URL; `unit.export` is the name
407
- - [fx](/docs/reference/fx) — full `fx` surface, including ICU `fx.t` / `fx.locale`
408
- - [Signal](/docs/elements/signal) — delivery physics (`once` · `broadcast` · `live`)
409
- - [Clock](/docs/elements/clock) — schedules and durable sleep
410
-
411
- ## Next
412
-
413
- <Cards>
414
- <Card title="Signal" description="Continue to Signal." href="/docs/elements/signal" />
415
- <Card
416
- title="Introduction"
417
- description="Eight elements overview."
418
- href="/docs/get-started/introduction"
419
- />
420
- </Cards>