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
@@ -0,0 +1,1080 @@
1
+ ---
2
+ title: "HTTP"
3
+ description: "Synchronous REST endpoints, RFC 10008 QUERY, CRUD mounts, live SSE streams, and gate chains on Flow."
4
+ icon: "Globe"
5
+ source: "docs/spec/unified-theory.md"
6
+ ---
7
+
8
+ HTTP triggers bind web requests directly to Flows. Every standard REST verb is available alongside RFC 10008 QUERY for safe body reads, multi-verb CRUD mounts, and live Server-Sent Events (SSE).
9
+
10
+ For developers building APIs on okengine — declare the route, attach gates, return typed data.
11
+
12
+ <Callout title="The one rule">
13
+ An HTTP trigger parses the request, checks attached gates, and invokes `flow({ do })`.
14
+ All business logic runs inside the Flow via `fx`. Declare `in`, `out`, and `errors` on
15
+ the HTTP bag when the route has a body, path params, or domain failures.
16
+ </Callout>
17
+
18
+ ## Smallest Example
19
+
20
+ <Steps>
21
+
22
+ <Step>
23
+ ### Define the route
24
+
25
+ ```typescript title="src/flows/main/ping.ts"
26
+ import { on, flow, http } from "okengine";
27
+
28
+ export const ping = on(
29
+ http.get().public(),
30
+ flow({
31
+ do: () => ({ status: "ok" }),
32
+ }),
33
+ );
34
+ ```
35
+
36
+ </Step>
37
+
38
+ <Step>
39
+ ### Call the endpoint
40
+
41
+ ```bash
42
+ curl -X GET http://localhost:6530/ping -H "accept: application/json"
43
+ ```
44
+
45
+ Response:
46
+
47
+ ```json
48
+ {
49
+ "data": { "status": "ok" },
50
+ "error": null
51
+ }
52
+ ```
53
+
54
+ </Step>
55
+
56
+ </Steps>
57
+
58
+ <Callout title="Omit path and name">
59
+ Tree default: `http.get()` and `flow({ do })` — no path or name strings. The
60
+ file stamps both. Pass either only for
61
+ [control](/docs/elements/flow/routing#when-to-omit--when-to-pass).
62
+ </Callout>
63
+
64
+ ## Progressive Patterns
65
+
66
+ Explore HTTP flow patterns from minimal handlers to schema-validated, error-handling, and gate-protected endpoints:
67
+
68
+ <Tabs items={["Minimal", "Validated", "Failures", "Gates"]}>
69
+
70
+ <Tab value="Minimal">
71
+
72
+ Return data directly with automatic JSON response enveloping and zero boilerplate:
73
+
74
+ ```typescript title="src/flows/main/health.ts"
75
+ import { on, flow, http } from "okengine";
76
+
77
+ export const health = on(
78
+ http.get().public(),
79
+ flow({
80
+ do: () => ({ ok: true }),
81
+ }),
82
+ );
83
+ ```
84
+
85
+ </Tab>
86
+
87
+ <Tab value="Validated">
88
+
89
+ Extract URL parameters and JSON body with runtime schema validation on the HTTP bag:
90
+
91
+ ```typescript title="src/flows/notes/create.ts"
92
+ import { on, flow, http } from "okengine";
93
+ import { z } from "zod";
94
+
95
+ export const create = on(
96
+ http.post({
97
+ in: z.object({ title: z.string().min(1) }),
98
+ out: z.object({ id: z.string(), title: z.string() }),
99
+ }),
100
+ flow({
101
+ do: async ({ title }, fx) => {
102
+ const id = fx.id();
103
+ return { id, title };
104
+ },
105
+ }),
106
+ );
107
+ ```
108
+
109
+ </Tab>
110
+
111
+ <Tab value="Failures">
112
+
113
+ Declare typed domain errors and return clean failure responses using `fx.fail`:
114
+
115
+ ```typescript title="src/flows/orders/[id]/get.ts"
116
+ import { on, flow, http } from "okengine";
117
+ import { z } from "zod";
118
+
119
+ export const get = on(
120
+ http.get({
121
+ in: z.object({ id: z.string() }),
122
+ out: z.object({ id: z.string(), sku: z.string(), qty: z.number() }),
123
+ errors: { NotFound: z.object({ id: z.string() }) },
124
+ }),
125
+ flow({
126
+ do: async ({ id }, fx) => {
127
+ const [order] = await fx.store(db).select().from(orders).where(eq(orders.id, id));
128
+ if (!order) return fx.fail("NotFound", { id });
129
+ return order;
130
+ },
131
+ }),
132
+ );
133
+ ```
134
+
135
+ </Tab>
136
+
137
+ <Tab value="Gates">
138
+
139
+ Chain policies and rate limits on the trigger with `.gate(...)`.
140
+ `fx.json.empty()` answers `204 No Content` with no body:
141
+
142
+ ```typescript title="src/flows/account/delete.ts"
143
+ import { on, flow, http, gate } from "okengine";
144
+ import { eq } from "drizzle-orm";
145
+ import { db, users } from "@/schema";
146
+
147
+ const member = gate.policy("member", ({ auth }) => !!auth.verified);
148
+ const admin = gate.scope("admin");
149
+ const deleteRate = gate.rate({ max: 5, per: "1m", keyBy: "user" });
150
+
151
+ export const deleteAccount = on(
152
+ http.delete().gate(member, admin, deleteRate),
153
+ flow({
154
+ do: async (_, fx) => {
155
+ await fx.store(db).delete(users).where(eq(users.id, fx.auth.userId!));
156
+ return fx.json.empty();
157
+ },
158
+ }),
159
+ );
160
+ ```
161
+
162
+ </Tab>
163
+
164
+ </Tabs>
165
+
166
+ ## Method Reference
167
+
168
+ | Method | Signature | Purpose | Body Allowed | Idempotent |
169
+ | --------------- | -------------------------- | --------------------------------------- | --------------- | -------------- |
170
+ | `http.get` | `http.get(path?)` | Fetch a resource or list | No | Yes |
171
+ | `http.post` | `http.post(path?)` | Create resource / command | Yes | No |
172
+ | `http.put` | `http.put(path?)` | Replace entire resource | Yes | Yes |
173
+ | `http.patch` | `http.patch(path?)` | Partial resource update | Yes | No |
174
+ | `http.delete` | `http.delete(path?)` | Remove a resource | Optional | Yes |
175
+ | `http.query` | `http.query(path?)` | Safe read with JSON body | Yes (RFC 10008) | Yes |
176
+ | `http.head` | `http.head(path?)` | Retrieve response headers | No | Yes |
177
+ | `http.options` | `http.options(path?)` | Discover allowed methods | No | Yes |
178
+ | `http.resource` | `http.resource(path, ops)` | Five CRUD verbs; live when on | Verb-dependent | Verb-dependent |
179
+ | `http.live` | `http.live(signal)` | Firehose SSE on `GET /_oke/live/{name}` | No | Yes |
180
+
181
+ ## Path Conventions
182
+
183
+ **Default — omit the path.** Tree files stamp the URL from disk (`http.get()`).
184
+ Pass a path only for [control](/docs/elements/flow/routing#when-to-omit--when-to-pass).
185
+
186
+ **Control — explicit path** — pass the URL template when the folder should not own the route:
187
+
188
+ ```typescript
189
+ http.get("/organizations/:orgId/members/:memberId");
190
+ ```
191
+
192
+ **Pathless** — omit so the compiler stamps from disk location:
193
+
194
+ ```typescript title="src/flows/users/[id]/get.ts"
195
+ import { on, flow, http } from "okengine";
196
+
197
+ // Stamped automatically to GET /users/:id · flow users.get
198
+ export const get = on(http.get(), flow({ do: async ({ id }) => ({ id }) }));
199
+ ```
200
+
201
+ Full file-tree rules: [Routing](/docs/elements/flow/routing).
202
+
203
+ ## Request Parsing
204
+
205
+ Before `flow({ do })`, the HTTP engine merges request parts into one object checked against
206
+ the trigger's `in`:
207
+
208
+ 1. **Path parameters** — `:param` segments (e.g. `{ id: "123" }`).
209
+ 2. **Query string** — `?sort=desc` keys at the root.
210
+ 3. **JSON body** — object fields merged into the same root.
211
+ 4. **Headers & cookies** — bags under `headers` / `cookie` when `do` reads them (declare the same keys in `in`).
212
+
213
+ ```typescript title="src/flows/items/[id]/update.ts"
214
+ import { on, flow, http } from "okengine";
215
+ import { z } from "zod";
216
+ import { eq } from "drizzle-orm";
217
+ import { db, items } from "@/schema";
218
+
219
+ export const update = on(
220
+ http.patch({
221
+ // :id from the path merges with JSON body { title }
222
+ in: z.object({
223
+ id: z.string(),
224
+ title: z.string().min(1),
225
+ }),
226
+ }),
227
+ flow({
228
+ do: async ({ id, title }, fx) => {
229
+ await fx.store(db).update(items).set({ title }).where(eq(items.id, id));
230
+ return { id, title };
231
+ },
232
+ }),
233
+ );
234
+ ```
235
+
236
+ Read request metadata by naming `headers` / `cookie` in `in` and destructuring them in `do`
237
+ (header names are lower-cased):
238
+
239
+ ```typescript title="src/flows/uploads/create.ts"
240
+ import { on, flow, http } from "okengine";
241
+ import { z } from "zod";
242
+
243
+ export const create = on(
244
+ http.post({
245
+ in: z.object({
246
+ name: z.string().min(1),
247
+ headers: z.object({
248
+ "content-type": z.string().optional(),
249
+ "x-request-id": z.string().optional(),
250
+ }),
251
+ cookie: z.object({
252
+ sid: z.string().optional(),
253
+ }),
254
+ }),
255
+ }),
256
+ flow({
257
+ do: async ({ name, headers, cookie }, fx) => {
258
+ return {
259
+ id: fx.id(),
260
+ name,
261
+ contentType: headers["content-type"] ?? "application/octet-stream",
262
+ session: cookie.sid ?? null,
263
+ };
264
+ },
265
+ }),
266
+ );
267
+ ```
268
+
269
+ ## HTTP Methods
270
+
271
+ Each verb binds with `on(http.<method>(), flow({…}))`. Omit the path on tree
272
+ files; pass one only for [control](/docs/elements/flow/routing#when-to-omit--when-to-pass).
273
+
274
+ <Tabs items={["GET", "POST", "PUT", "PATCH", "DELETE", "QUERY", "HEAD", "OPTIONS"]}>
275
+
276
+ <Tab value="GET">
277
+
278
+ Fetch a resource or collection:
279
+
280
+ ```typescript title="src/flows/notes/[id]/get.ts"
281
+ import { on, flow, http } from "okengine";
282
+ import { z } from "zod";
283
+ import { eq } from "drizzle-orm";
284
+ import { db, notes } from "@/schema";
285
+
286
+ export const get = on(
287
+ http.get({
288
+ in: z.object({ id: z.string() }),
289
+ out: z.object({ id: z.string(), title: z.string() }),
290
+ errors: { NotFound: z.object({ id: z.string() }) },
291
+ }),
292
+ flow({
293
+ do: async ({ id }, fx) => {
294
+ const [note] = await fx.store(db).select().from(notes).where(eq(notes.id, id));
295
+ if (!note) return fx.fail("NotFound", { id });
296
+ return note;
297
+ },
298
+ }),
299
+ );
300
+ ```
301
+
302
+ </Tab>
303
+
304
+ <Tab value="POST">
305
+
306
+ Create a resource or run a command. `fx.json.create(value)` returns `201 Created` with
307
+ `{ data: value, error: null }`:
308
+
309
+ ```typescript title="src/flows/notes/create.ts"
310
+ import { on, flow, http } from "okengine";
311
+ import { z } from "zod";
312
+ import { db, notes } from "@/schema";
313
+
314
+ export const create = on(
315
+ http.post({
316
+ in: z.object({ title: z.string().min(1) }),
317
+ out: z.object({ id: z.string(), title: z.string() }),
318
+ }),
319
+ flow({
320
+ do: async ({ title }, fx) => {
321
+ const id = fx.id();
322
+ await fx.store(db).insert(notes).values({ id, title });
323
+ return fx.json.create({ id, title });
324
+ },
325
+ }),
326
+ );
327
+ ```
328
+
329
+ </Tab>
330
+
331
+ <Tab value="PUT">
332
+
333
+ Replace an entire resource (idempotent full write):
334
+
335
+ ```typescript title="src/flows/notes/[id]/replace.ts"
336
+ import { on, flow, http } from "okengine";
337
+ import { z } from "zod";
338
+ import { eq } from "drizzle-orm";
339
+ import { db, notes } from "@/schema";
340
+
341
+ export const replace = on(
342
+ http.put({
343
+ in: z.object({
344
+ id: z.string(),
345
+ title: z.string().min(1),
346
+ body: z.string(),
347
+ }),
348
+ out: z.object({ id: z.string(), title: z.string(), body: z.string() }),
349
+ }),
350
+ flow({
351
+ do: async ({ id, title, body }, fx) => {
352
+ await fx.store(db).update(notes).set({ title, body }).where(eq(notes.id, id));
353
+ return { id, title, body };
354
+ },
355
+ }),
356
+ );
357
+ ```
358
+
359
+ </Tab>
360
+
361
+ <Tab value="PATCH">
362
+
363
+ Apply a partial update (only declared fields change):
364
+
365
+ ```typescript title="src/flows/notes/[id]/update.ts"
366
+ import { on, flow, http } from "okengine";
367
+ import { z } from "zod";
368
+ import { eq } from "drizzle-orm";
369
+ import { db, notes } from "@/schema";
370
+
371
+ export const update = on(
372
+ http.patch({
373
+ in: z.object({
374
+ id: z.string(),
375
+ title: z.string().min(1).optional(),
376
+ }),
377
+ out: z.object({ id: z.string(), title: z.string() }),
378
+ errors: { NotFound: z.object({ id: z.string() }) },
379
+ }),
380
+ flow({
381
+ do: async ({ id, title }, fx) => {
382
+ if (title !== undefined) {
383
+ await fx.store(db).update(notes).set({ title }).where(eq(notes.id, id));
384
+ }
385
+ const [note] = await fx.store(db).select().from(notes).where(eq(notes.id, id));
386
+ if (!note) return fx.fail("NotFound", { id });
387
+ return note;
388
+ },
389
+ }),
390
+ );
391
+ ```
392
+
393
+ </Tab>
394
+
395
+ <Tab value="DELETE">
396
+
397
+ Remove a resource. `fx.json.empty()` returns `204 No Content` with no body:
398
+
399
+ ```typescript title="src/flows/notes/[id]/remove.ts"
400
+ import { on, flow, http } from "okengine";
401
+ import { z } from "zod";
402
+ import { eq } from "drizzle-orm";
403
+ import { db, notes } from "@/schema";
404
+
405
+ export const remove = on(
406
+ http.delete({ in: z.object({ id: z.string() }) }),
407
+ flow({
408
+ do: async ({ id }, fx) => {
409
+ await fx.store(db).delete(notes).where(eq(notes.id, id));
410
+ return fx.json.empty();
411
+ },
412
+ }),
413
+ );
414
+ ```
415
+
416
+ </Tab>
417
+
418
+ <Tab value="QUERY">
419
+
420
+ Safe, idempotent read with a JSON body (RFC 10008) — filters that would overflow a URL:
421
+
422
+ ```typescript title="src/flows/orders/search.ts"
423
+ import { on, flow, http } from "okengine";
424
+ import { z } from "zod";
425
+ import { db } from "@/schema";
426
+
427
+ export const search = on(
428
+ http.query({
429
+ in: z.object({
430
+ filters: z.array(z.string()),
431
+ dateRange: z.object({ from: z.string(), to: z.string() }),
432
+ }),
433
+ out: z.array(z.object({ id: z.string(), total: z.number() })),
434
+ }),
435
+ flow({
436
+ do: async ({ filters, dateRange }, fx) => {
437
+ return await fx.store(db).queryOrders(filters, dateRange);
438
+ },
439
+ }),
440
+ );
441
+ ```
442
+
443
+ Clients must send `Content-Type: application/json`.
444
+
445
+ Some browsers, HTTP libraries, and reverse proxies still reject or strip bodies on methods other
446
+ than `POST`/`PUT`/`PATCH`. Prefer modern clients, or fall back to `POST` for the same search
447
+ contract when you must support older stacks.
448
+
449
+ </Tab>
450
+
451
+ <Tab value="HEAD">
452
+
453
+ Probe existence / headers without returning a body. `head` is not a reserved
454
+ leaf, so pass the path when the URL must match GET (`/notes/:id`):
455
+
456
+ ```typescript title="src/flows/notes/[id]/head.ts"
457
+ import { on, flow, http } from "okengine";
458
+ import { z } from "zod";
459
+ import { eq } from "drizzle-orm";
460
+ import { db, notes } from "@/schema";
461
+
462
+ export const head = on(
463
+ http.head("/notes/:id", {
464
+ in: z.object({ id: z.string() }),
465
+ errors: { NotFound: z.object({ id: z.string() }) },
466
+ }),
467
+ flow({
468
+ do: async ({ id }, fx) => {
469
+ const [note] = await fx
470
+ .store(db)
471
+ .select({ id: notes.id })
472
+ .from(notes)
473
+ .where(eq(notes.id, id));
474
+ if (!note) return fx.fail("NotFound", { id });
475
+ return;
476
+ },
477
+ }),
478
+ );
479
+ ```
480
+
481
+ </Tab>
482
+
483
+ <Tab value="OPTIONS">
484
+
485
+ Advertise allowed verbs. Same idea — pass the collection path explicitly when
486
+ the leaf name would otherwise add a segment:
487
+
488
+ ```typescript title="src/flows/notes/options.ts"
489
+ import { on, flow, http } from "okengine";
490
+
491
+ export const options = on(
492
+ http.options("/notes"),
493
+ flow({
494
+ do: () => ({
495
+ allow: ["GET", "POST", "PUT", "PATCH", "DELETE", "QUERY", "HEAD", "OPTIONS"],
496
+ }),
497
+ }),
498
+ );
499
+ ```
500
+
501
+ </Tab>
502
+
503
+ </Tabs>
504
+
505
+ ## Resources
506
+
507
+ <Callout title="Detailed section">
508
+ If you only need the basic mount, jump to the example below. `on(http.resource(path, ops))` takes
509
+ **no Flow as a second argument** — `.all()` is the bag; options live on `store.resource`. A second
510
+ argument throws `on(http.resource(...)) takes no second argument`.
511
+ </Callout>
512
+
513
+ `http.resource(path, ops)` mounts five CRUD Flows in one `on()` call. Pass
514
+ `store.resource(…).all()` or any bag with `list` · `create` · `get` · `update` · `remove`.
515
+
516
+ Chain `.gate(...)` / `.public()` once — every verb (and live, when present) gets the same gates.
517
+
518
+ <Tabs items={["Define", "Mount"]}>
519
+
520
+ <Tab value="Define">
521
+
522
+ `store.resource` builds the five Flows. The factory registers no routes.
523
+
524
+ ```typescript title="src/flows/notes/resource.ts"
525
+ import { store } from "okengine";
526
+ import { z } from "zod";
527
+ import { db, notesTable } from "@/schema";
528
+
529
+ export const notesResource = store.resource(db, notesTable, {
530
+ in: z.object({ title: z.string().min(1) }),
531
+ out: z.object({ id: z.string(), title: z.string() }),
532
+ });
533
+ ```
534
+
535
+ </Tab>
536
+
537
+ <Tab value="Mount">
538
+
539
+ `.gate(member)` stamps every verb. The client sees `api.notes.list` / `.create` / `.get` /
540
+ `.update` / `.remove` after `oke({ name: "app" }).adopt({ notes })`.
541
+
542
+ ```typescript title="src/flows/notes/index.ts"
543
+ import { on, http } from "okengine";
544
+ import { member } from "@/core/gate";
545
+ import { notesResource } from "./resource";
546
+
547
+ export const notes = on(http.resource("/notes", notesResource.all()).gate(member));
548
+ ```
549
+
550
+ </Tab>
551
+
552
+ </Tabs>
553
+
554
+ The URL id segment is always `:id`. Update is **PATCH**, not PUT. There is no
555
+ pathless `http.resource()` — pass an explicit base path.
556
+
557
+ | Op | Method | Path | Typical status |
558
+ | -------- | -------- | ------------- | ---------------------------------- |
559
+ | `list` | `GET` | `/notes` | `200` + `{ data, error, meta }` |
560
+ | `create` | `POST` | `/notes` | `201 Created` (`fx.json.create`) |
561
+ | `get` | `GET` | `/notes/:id` | `200`, or `NotFound` |
562
+ | `update` | `PATCH` | `/notes/:id` | `200`, or `NotFound` |
563
+ | `remove` | `DELETE` | `/notes/:id` | `204 No Content` (`fx.json.empty`) |
564
+ | `live` | `GET` | `/notes/live` | SSE — only when live is on |
565
+
566
+ <Accordions>
567
+
568
+ <Accordion title="Resource Options">
569
+ Third argument to `store.resource(db, table, options)`:
570
+
571
+ | Option | Type | Default | Meaning |
572
+ | ---------- | --------- | -------------------------------- | ------------------------------------------------------------------------ |
573
+ | `in` | Schema | _(required)_ | Create body (`POST`) |
574
+ | `out` | Schema | _(required)_ | Item shape (get / list / update return) |
575
+ | `update` | Schema | `in` | Patch fields. Wire body is `{ id, ...patch }` |
576
+ | `idSchema` | Schema | `update`/`in` + `{ id: string }` | Replaces the update Flow `in` when set (include the id key) |
577
+ | `errors` | error map | `{ NotFound }` | Typed failures on get / update / remove |
578
+ | `id` | column | table PK | Column bound to `:id` |
579
+ | `list` | object | see List Options | List query grammar (`GET /notes`) |
580
+ | `breaking` | `boolean` | `false` | Marks the five Flows `breaking: true` (handwritten → resource migration) |
581
+ | `live` | `boolean` | omitted | Live query surface; see Resource Live |
582
+
583
+ </Accordion>
584
+
585
+ <Accordion title="List Options">
586
+ Nested on `store.resource(…, { list: { … } })`. Search / filter / order / select
587
+ use a column scope: `"all"` · column array · `"none"`.
588
+
589
+ | Option | Type | Default | Meaning |
590
+ | ----------- | ------------------------ | ------------------------------------------------ | ------------------------------ |
591
+ | `mode` | `"cursor"` \| `"offset"` | `"cursor"` when `cursor` is set, else `"offset"` | Pagination |
592
+ | `cursor` | columns | `[]` | Keyset columns |
593
+ | `direction` | `"asc"` \| `"desc"` | `"desc"` | Default sort when no `?order=` |
594
+ | `limit` | `number` | `20` | Default page size |
595
+ | `maxLimit` | `number` | `100` | Cap on `?limit=` |
596
+ | `count` | `"exact"` \| `"none"` | `"exact"` | Offset-only `COUNT(*)` |
597
+ | `search` | column scope | `"none"` | `?search=` / `?q=` |
598
+ | `filter` | column scope | `"none"` | `?col=eq.x` grammar |
599
+ | `order` | column scope | cursor columns, else `"all"` | `?order=` |
600
+ | `select` | column scope | `"all"` | `?select=` projection |
601
+
602
+ </Accordion>
603
+
604
+ <Accordion title="Resource Members">
605
+
606
+ | Member | Kind | Meaning |
607
+ | ----------------------------------------------- | ------------------- | ----------------------------------------------------------------------------------------- |
608
+ | `all()` | method, no args | Bag for `http.resource(path, notesResource.all())` — five Flows, plus `live` when enabled |
609
+ | `list` · `create` · `get` · `update` · `remove` | Flow | One verb. Bind with `http.get` / `http.post` / `http.patch` / `http.delete` |
610
+ | `page(input)` | method | Compile list-query input for a handwritten `fx.store(db).page` |
611
+ | `live` | `{ signal, flow }?` | Live surface when `live: true` (or the project default drained on) |
612
+
613
+ </Accordion>
614
+
615
+ <Accordion title="Resource Live">
616
+ A sixth route appears only when the resource is live. It is **not** a signal
617
+ firehose — each subscriber gets classified row events (RLS + list filters).
618
+
619
+ | `live` on the resource | Result |
620
+ | -------------------------------- | ---------------------------------------------------------------------------------------- |
621
+ | `{ live: true }` | Mount `GET <path>/live` now |
622
+ | omitted | Mount only if `oke({ store: { live: true } })` |
623
+ | `{ live: false }` | Never mount live for this resource |
624
+ | table `store.schema.live(false)` | Opts that table out of the project default. `{ live: true }` on the resource still wins. |
625
+
626
+ ```typescript title="src/flows/notes/resource.ts"
627
+ const notesResource = store.resource(db, notesTable, {
628
+ in: z.object({ title: z.string().min(1) }),
629
+ out: z.object({ id: z.string(), title: z.string() }),
630
+ live: true,
631
+ });
632
+
633
+ export const notes = on(http.resource("/notes", notesResource.all()).gate(member));
634
+ ```
635
+
636
+ **Consequence:** `GET /notes/live` rides the same `.gate(...)` chain as list/get.
637
+
638
+ Query-string filters use the resource list grammar; pagination cursors do **not**
639
+ gate membership — a row enters or leaves the window when filters / RLS change.
640
+
641
+ Wire events (consumed with `useLiveQuery` on the [typed client](/docs/client/react)):
642
+
643
+ | `kind` | Meaning |
644
+ | --------- | ----------------------------------------------------------- |
645
+ | `upsert` | Row visible under stamp + query — merge by primary key |
646
+ | `revoked` | Row left visibility (`reason: "rls"` or `"query"`) — remove |
647
+ | `delete` | Row deleted — remove |
648
+
649
+ Live queries need an RLS-capable SQL driver (`postgres` / `pglite`) and a gated
650
+ identity on the request. Extract fails without a primary key:
651
+
652
+ ```text
653
+ extract: live: true on table "notes" requires a primary key column (upsert/revoked/delete address rows by PK)
654
+ ```
655
+
656
+ Missing `updatedAt` / `updated_at`, or no RLS policies, warn at extract — they
657
+ do not fail the build.
658
+
659
+ </Accordion>
660
+
661
+ <Accordion title="Subset & Override">
662
+ `http.resource` always mounts all five CRUD keys. To expose only some verbs,
663
+ bind those Flows on individual triggers. To replace one verb, spread `.all()`
664
+ and override that key — the other four stay:
665
+
666
+ ```typescript title="src/flows/notes/list.ts"
667
+ import { on, http, store } from "okengine";
668
+ import { z } from "zod";
669
+ import { member } from "@/core/gate";
670
+ import { db, notesTable } from "@/schema";
671
+
672
+ const notesResource = store.resource(db, notesTable, {
673
+ in: z.object({ title: z.string().min(1) }),
674
+ out: z.object({ id: z.string(), title: z.string() }),
675
+ });
676
+
677
+ export const list = on(http.get().gate(member), notesResource.list);
678
+ ```
679
+
680
+ ```typescript title="src/flows/notes/[id]/get.ts"
681
+ export const get = on(http.get().gate(member), notesResource.get);
682
+ ```
683
+
684
+ Override one verb on a resource mount — path is required on `http.resource`:
685
+
686
+ ```typescript title="src/flows/notes/index.ts"
687
+ import { on, flow, http } from "okengine";
688
+ import { eq } from "drizzle-orm";
689
+
690
+ export const notes = on(
691
+ http
692
+ .resource("/notes", {
693
+ ...notesResource.all(),
694
+ remove: flow({
695
+ do: async ({ id }, fx) => {
696
+ await fx
697
+ .store(db)
698
+ .update(notesTable)
699
+ .set({ archived: true })
700
+ .where(eq(notesTable.id, id));
701
+ return fx.json.empty();
702
+ },
703
+ }),
704
+ })
705
+ .gate(member),
706
+ );
707
+ ```
708
+
709
+ A handwritten bag works the same way — each value must be a `flow(…)`:
710
+
711
+ ```typescript
712
+ on(
713
+ http.resource("/notes", {
714
+ list: flow("notes.list", { do: () => [] }),
715
+ create: flow("notes.create", { do: () => ({ id: "n1" }) }),
716
+ get: flow("notes.get", { do: () => ({ id: "n1" }) }),
717
+ update: flow("notes.update", { do: () => ({ id: "n1" }) }),
718
+ remove: flow("notes.remove", { do: (_, fx) => fx.json.empty() }),
719
+ }),
720
+ );
721
+ ```
722
+
723
+ Missing or non-Flow keys throw `on(http.resource(...)) expects the five CRUD FlowDefs`.
724
+ A `GET /notes` you also declared by hand collides at boot (**OKE1041**).
725
+
726
+ </Accordion>
727
+
728
+ </Accordions>
729
+
730
+ ## Live Streams
731
+
732
+ <Callout title="Detailed section">
733
+ If you only need the basic firehose, jump to the example below. `.live(…)` is GET-only —
734
+ `on(http.post("/x").live(signal))` throws `on(http.*.live(signal)): live exposure must be GET`.
735
+ </Callout>
736
+
737
+ `http.live(signal)` is one-arg `on()` — the engine synthesizes the stream Flow
738
+ (`fx.live` + `effects.reads: ["signal:<name>"]`). Chain `.gate(...)` like any GET.
739
+
740
+ ```typescript title="src/flows/orders/firehose.ts"
741
+ import { on, http, signal } from "okengine";
742
+ import { z } from "zod";
743
+ import { member } from "@/core/gate";
744
+
745
+ export const orderStatus = signal.live("order-status", {
746
+ optional: true,
747
+ schema: z.object({
748
+ orderId: z.string(),
749
+ status: z.enum(["placed", "fulfilling", "shipped"]),
750
+ }),
751
+ });
752
+
753
+ export const firehose = on(http.live(orderStatus).gate(member));
754
+ ```
755
+
756
+ ```bash
757
+ curl -N http://localhost:6530/_oke/live/order-status \
758
+ -H "accept: text/event-stream" \
759
+ -H "authorization: Bearer …"
760
+ ```
761
+
762
+ Response `Content-Type` is `text/event-stream`. Frames are JSON `data:` lines
763
+ (optional `id:` for resume), then `data: [DONE]`.
764
+
765
+ <Accordions>
766
+
767
+ <Accordion title="Exposure Shapes">
768
+ Three GET shapes expose a live SSE body. Pick the physics first, then the path.
769
+
770
+ | Declaration | Path | Physics |
771
+ | -------------------------------------------------- | ----------------------- | ---------------------------------------------- |
772
+ | `on(http.live(signal))` | `GET /_oke/live/{name}` | Signal tape — every event |
773
+ | `on(http.get(path).live(signal))` | Your path | Signal tape — auto-match on `:params` |
774
+ | `on(http.get(path).live(table), flow)` | Your path | Live **query** — `liveQuery(fx, table, input)` |
775
+ | `store.resource({ live: true })` + `http.resource` | `GET <path>/live` | Same live-query physics as `.live(table)` |
776
+
777
+ Signal names in the default firehose path are `encodeURIComponent`'d
778
+ (`chat.message` stays readable; slashes in internal names are escaped).
779
+
780
+ </Accordion>
781
+
782
+ <Accordion title="Filtered Paths">
783
+ Path params become a filter: an event is forwarded when each `:param` that
784
+ **exists on the payload** equals the request value. Params missing from the
785
+ payload are skipped (the event still flows). No params = firehose.
786
+
787
+ ```typescript title="src/flows/orders/events.ts"
788
+ import { on, http } from "okengine";
789
+ import { member } from "@/core/gate";
790
+ import { orderStatus } from "@/signals/orders";
791
+
792
+ export const events = on(http.get("/orders/:orderId/events").gate(member).live(orderStatus));
793
+ ```
794
+
795
+ `GET /orders/ord_1/events` receives `{ orderId: "ord_1", status: "shipped" }`
796
+ and drops events for other orders.
797
+
798
+ </Accordion>
799
+
800
+ <Accordion title="Custom Match">
801
+ Pass your own Flow as the second argument to `on()` when auto-match is not
802
+ enough. Return `fx.live(signal, { match })` from `do` — do not wrap it with
803
+ `fx.json.stream`.
804
+
805
+ ```typescript title="src/flows/orders/vip-feed.ts"
806
+ import { on, flow, http } from "okengine";
807
+ import { member } from "@/core/gate";
808
+ import { orderStatus } from "@/signals/orders";
809
+
810
+ export const vipFeed = on(
811
+ http.get("/orders/vip/events").gate(member).live(orderStatus),
812
+ flow("orders.vipFeed", {
813
+ do: (_input, fx) =>
814
+ fx.live(orderStatus, {
815
+ match: (payload) => payload.status === "shipped",
816
+ }),
817
+ }),
818
+ );
819
+ ```
820
+
821
+ **Consequence:** a custom Flow stamps a distinct match key, so it can coexist
822
+ with the auto-match route for the same signal (different path). Two synthesized
823
+ firehoses that share signal **and** gates fail uniqueness — see Uniqueness.
824
+
825
+ </Accordion>
826
+
827
+ <Accordion title="Live Queries">
828
+ For a handwritten list that should stream the same classified CDC as
829
+ `store.resource({ live: true })`, bind the table on GET and open the window
830
+ with `liveQuery`:
831
+
832
+ ```typescript title="src/flows/tasks/live.ts"
833
+ import { on, flow, http, liveQuery } from "okengine";
834
+ import { member } from "@/core/gate";
835
+ import { tasks } from "@/schema";
836
+
837
+ export const tasksLive = on(
838
+ http.get("/tasks/live").gate(member).live(tasks),
839
+ flow("tasks.live", {
840
+ do: async (input, fx) =>
841
+ liveQuery(fx, tasks, input, {
842
+ filter: [tasks.status],
843
+ search: [tasks.title],
844
+ order: "all",
845
+ }),
846
+ }),
847
+ );
848
+ ```
849
+
850
+ Same driver, identity, and extract guardrails as Resource Live. Prefer
851
+ `http.resource` + `{ live: true }` when you already mount the five CRUD ops.
852
+
853
+ </Accordion>
854
+
855
+ <Accordion title="Uniqueness">
856
+ Boot keys each live HTTP route as `(signal, gates, match)`. Match is the
857
+ sorted path-param names, or `custom:<flow>` when you passed a Flow, or
858
+ `(firehose)` when there are no params.
859
+
860
+ | Pair | Boots? |
861
+ | --------------------------------------- | ----------------------------------------- |
862
+ | Member `:orderId` + admin firehose | Yes — gates and match differ |
863
+ | Same params, different gates | Yes — the client disambiguates with `via` |
864
+ | Two member firehoses on different paths | No — **OKE1050** |
865
+ | Same method + path twice | No — **OKE1041** first |
866
+
867
+ **OKE1050** cause: `Live signal "{signal}" is exposed twice with the same gates ({gates}) and match ({match}).`
868
+ Fix: a different gate, a path-param filter, or drop the extra route.
869
+
870
+ </Accordion>
871
+
872
+ <Accordion title="Client Subscription">
873
+ `signal.live` is HTTP SSE. `for await` stays on the server; the browser
874
+ uses a callback.
875
+
876
+ The client picks the unique exposure whose `matchKey` fields are a subset of
877
+ the input, preferring the largest match (`{ orderId }` beats firehose). A tie
878
+ needs `via: "unit.flow"`.
879
+
880
+ ```typescript
881
+ const stop = api.live(
882
+ orderStatus,
883
+ { orderId: "ord_1" },
884
+ {
885
+ onEvent: (event) => {
886
+ /* { orderId, status } */
887
+ },
888
+ onError: (err) => {
889
+ /* 4xx, envelope, or drop */
890
+ },
891
+ autoResubscribe: false,
892
+ },
893
+ );
894
+ stop();
895
+ ```
896
+
897
+ `api.orders.events({ orderId }, { onEvent })` is the same shape on the exposing
898
+ Flow. Reconnects send `Last-Event-ID` from the last `id:` received.
899
+
900
+ A **410** `LiveResumeGap` (**OKE1210**) means that cursor is gone — drop it
901
+ and replay the remaining tape (`autoResubscribe: true`).
902
+
903
+ Resource live queries use `useLiveQuery` (snapshot + classified events), not
904
+ `api.live`. See [Client · Live](/docs/client/live).
905
+
906
+ </Accordion>
907
+
908
+ </Accordions>
909
+
910
+ ## Trigger Modifiers
911
+
912
+ Every HTTP trigger supports fluent modifier chaining before binding to `on()`.
913
+ Resource mounts accept `.gate(...)` and `.public()` only — live on a resource
914
+ comes from `store.resource({ live: true })`, not `.live()`.
915
+
916
+ **Gates** — attach policy and rate handles. They evaluate in declaration order; first denial wins:
917
+
918
+ ```typescript
919
+ import { gate } from "okengine";
920
+ import { member } from "@/core/gate";
921
+
922
+ http.post().gate(member, gate.scope("editor"), gate.rate({ max: 100, per: "1m", keyBy: "user" }));
923
+ ```
924
+
925
+ **Public** — explicitly marks the endpoint as open without authentication:
926
+
927
+ ```typescript
928
+ http.get().public();
929
+ ```
930
+
931
+ ## Response Envelopes
932
+
933
+ Every HTTP flow returns the same envelope shape. You choose status and optional `meta` — not a
934
+ custom wrapper.
935
+
936
+ <Callout title="Envelope is fixed">
937
+ Success and failure always use `{ data, error }` (optional top-level `meta`). There is no API to
938
+ replace that shape. Use `fx.json.*` for status codes and `meta`; use `fx.fail` for typed errors.
939
+ </Callout>
940
+
941
+ **Success** — returning a value from `do` produces `200 OK`:
942
+
943
+ ```json
944
+ { "data": { "id": "123" }, "error": null }
945
+ ```
946
+
947
+ Returning `undefined` produces a `204 No Content` response with an empty body.
948
+
949
+ **Custom status** — `fx.json.create` for `201 Created`, or `fx.json.ok` with optional `meta`:
950
+
951
+ ```typescript
952
+ return fx.json.create({ id: "ord_1" });
953
+ // or
954
+ return fx.json.ok({ id: "ord_1" }, { meta: { traceId: fx.runId } });
955
+ ```
956
+
957
+ **Typed failures** — `fx.fail(code, data)` formats the error envelope and maps status:
958
+
959
+ ```typescript
960
+ return fx.fail("NotFound", { id: "123" });
961
+ ```
962
+
963
+ ```json
964
+ {
965
+ "data": null,
966
+ "error": {
967
+ "code": "NotFound",
968
+ "message": "Resource not found",
969
+ "data": { "id": "123" }
970
+ }
971
+ }
972
+ ```
973
+
974
+ Standard status code mappings:
975
+
976
+ - `ValidationError` → `422 Unprocessable Entity`
977
+ - `Unauthorized` → `401 Unauthorized`
978
+ - `Forbidden` → `403 Forbidden`
979
+ - `RateLimited` → `429 Too Many Requests`
980
+ - Custom error codes → `400 Bad Request`
981
+
982
+ ## Troubleshooting
983
+
984
+ <Accordions>
985
+
986
+ <Accordion title="404 Not Found — route missing">
987
+ No Flow is bound to that method + path. Check the explicit path, or for pathless routes the
988
+ file-tree stamp (`src/flows/users/[id]/get.ts` → `GET /users/:id`). A bare `404` with body `Not
989
+ Found` means the router found no match.
990
+ </Accordion>
991
+
992
+ <Accordion title="405 Method Not Allowed on valid route">
993
+ The path exists but has not been bound to the requested HTTP verb. The response contains an
994
+ `Allow` header listing valid methods for that path.
995
+ </Accordion>
996
+
997
+ <Accordion title="415 Unsupported Media Type on QUERY">
998
+ RFC 10008 requires `Content-Type: application/json` for `http.query` requests. Ensure your client
999
+ sends this header with a valid JSON payload.
1000
+ </Accordion>
1001
+
1002
+ <Accordion title="422 ValidationError on request">
1003
+ The merged input payload failed validation against the trigger's `in` schema. Check the
1004
+ `error.data.issues` array for the specific field validation failure.
1005
+ </Accordion>
1006
+
1007
+ <Accordion title="Browser blocked by CORS / missing Access-Control-*">
1008
+ Cross-origin access is closed until you plug the [`cors`](/docs/plugins/cors) plugin with an
1009
+ explicit `origin`. Same-origin calls need no CORS headers. Preflight `OPTIONS` is answered by the
1010
+ plugin even when the path is bound to other methods.
1011
+ </Accordion>
1012
+
1013
+ <Accordion title="TypeError: on(http.resource(...)) takes no second argument">
1014
+ The ops bag already holds the five Flows. Call `on(http.resource("/notes",
1015
+ notesResource.all()).gate(member))` — do not pass a `flow(...)` as the second argument.
1016
+ </Accordion>
1017
+
1018
+ <Accordion title="TypeError: on(http.resource(...)) expects the five CRUD FlowDefs">
1019
+ The bag must include `list`, `create`, `get`, `update`, and `remove`, each a `flow(...)`. To
1020
+ expose fewer verbs, bind those Flows on `http.get` / `http.post` yourself instead of
1021
+ `http.resource`.
1022
+ </Accordion>
1023
+
1024
+ <Accordion title="OKE1041 — method + path bound twice">
1025
+ Cause: `{method} {path} is bound twice (flow "{flow}").` A resource mount plus a handwritten
1026
+ `http.get("/notes")` (or two mounts on the same base path) collide. Drop one binding.
1027
+ </Accordion>
1028
+
1029
+ <Accordion title="TypeError: live exposure must be GET">
1030
+ Live SSE feeds declared via `.live(signal)` can only be attached to `GET` triggers
1031
+ (`http.get(...)` or `http.live(...)`). Other verbs reject live stream synthesis.
1032
+ </Accordion>
1033
+
1034
+ <Accordion title="OKE1050 — live signal exposed twice">
1035
+ Cause: `Live signal "{signal}" is exposed twice with the same gates ({gates}) and match ({match}
1036
+ ).` Two firehoses (`http.live` or param-less `.live`) that share the signal and gates cannot boot.
1037
+ Change the gate, add a path-param filter, or remove a route.
1038
+ </Accordion>
1039
+
1040
+ <Accordion title="OKE1210 — 410 LiveResumeGap">
1041
+ Cause: `Cursor "{afterId}" missing on "{signal}".` That `Last-Event-ID` is gone from the tape.
1042
+ Reconnect without it; remaining events replay. `autoResubscribe: true` does this after backoff.
1043
+ </Accordion>
1044
+
1045
+ <Accordion title="live query requires a primary key / RLS driver">
1046
+ Extract: `live: true on table "…" requires a primary key column`. Runtime: `live query for "…"
1047
+ requires an RLS-capable SQL driver (postgres / pglite)` or `requires a gated identity`. Attach
1048
+ `.gate(...)` and declare a PK.
1049
+ </Accordion>
1050
+
1051
+ </Accordions>
1052
+
1053
+ ## Learn more
1054
+
1055
+ - [Store](/docs/elements/store) — `store.resource`, list query grammar, SQL facet
1056
+ - [Signal · Live](/docs/elements/signal/live) — `signal.live` tapes
1057
+ - [Client](/docs/client/live) — `api.live`, `useLive`, `useLiveQuery`
1058
+ - [fx](/docs/reference/fx) — `fx.live`, `fx.json.stream`, `fx.json.create`
1059
+ - [Gate](/docs/elements/gate) — `.gate(...)` / `.public()` on triggers
1060
+ - [Errors](/docs/reference/errors) — OKE1041 · OKE1050 · OKE1210
1061
+
1062
+ ## Next
1063
+
1064
+ <Cards>
1065
+ <Card
1066
+ title="Gate Element"
1067
+ description="Configure authentication, authorization, and rate limiting."
1068
+ href="/docs/elements/gate"
1069
+ />
1070
+ <Card
1071
+ title="Consumers"
1072
+ description="Signal workers, named Clock jobs, and SQL CDC — one Flow species."
1073
+ href="/docs/elements/flow/consumers"
1074
+ />
1075
+ <Card
1076
+ title="Durable Workflows"
1077
+ description="Step journaling and multi-step distributed execution."
1078
+ href="/docs/elements/flow/workflows"
1079
+ />
1080
+ </Cards>