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,707 @@
1
+ ---
2
+ title: "Consumers"
3
+ description: "Asynchronous Flows for Signal emissions, named Clock ticks, and SQL row changes."
4
+ icon: "Radio"
5
+ source: "docs/spec/unified-theory.md"
6
+ ---
7
+
8
+ Consumers are Flows that run when something else happens — a Signal emit, a named Clock tick, or a SQL row change — instead of waiting for an HTTP request.
9
+
10
+ For developers wiring background work on okengine — bind the trigger, keep `do` on `fx`.
11
+
12
+ <Callout title="The one rule">
13
+ Bind with `on(signal)`, `on(clockDecl)`, or `on(db.table(…).changed())`. Delivery physics live on
14
+ the Signal; `cron` / `every` live on the Clock; CDC input is always `{ before, after }`. World
15
+ access still goes through `fx`.
16
+ </Callout>
17
+
18
+ ## Smallest Example
19
+
20
+ <Steps>
21
+
22
+ <Step>
23
+ ### Bind a Signal consumer
24
+
25
+ ```typescript title="src/flows/notifications/welcome.ts"
26
+ import { on, flow } from "okengine";
27
+ import { userSignedUp } from "@/signals";
28
+ import { welcomeEmail } from "@/channels/welcome";
29
+
30
+ export const sendWelcome = on(
31
+ userSignedUp,
32
+ flow("notifications.welcome", {
33
+ do: async ({ userId, email }, fx) => {
34
+ await fx.send(welcomeEmail, {
35
+ to: email,
36
+ data: { userId },
37
+ });
38
+ },
39
+ }),
40
+ );
41
+ ```
42
+
43
+ </Step>
44
+
45
+ <Step>
46
+ ### Emit from any Flow
47
+
48
+ ```typescript
49
+ await fx.emit(userSignedUp, { userId: "usr_123", email: "alice@example.com" });
50
+ ```
51
+
52
+ The compiler records `emits: ["users.signed-up"]` on the producer. The consumer runs after the
53
+ emit commits — the HTTP request does not wait for the welcome mail.
54
+
55
+ </Step>
56
+
57
+ </Steps>
58
+
59
+ <Callout title="Jobs are consumers">
60
+ A named Clock bound with `on(clockDecl, flow)` is the same species — an asynchronous Flow. There
61
+ is no separate job runner. See [Clock jobs](#clock-jobs).
62
+ </Callout>
63
+
64
+ ## Progressive Patterns
65
+
66
+ Explore consumers from a typed queue worker to a cron job and a table-change handler:
67
+
68
+ <Tabs items={["Signal", "Clock", "CDC", "Ordered"]}>
69
+
70
+ <Tab value="Signal">
71
+
72
+ Declare delivery physics on the Signal, then bind the worker with `on(handle, flow)`:
73
+
74
+ ```typescript title="src/signals/email.ts"
75
+ import { signal } from "okengine";
76
+ import { z } from "zod";
77
+
78
+ export const emailTask = signal.once("tasks.email", {
79
+ schema: z.object({ to: z.string().email(), body: z.string() }),
80
+ retries: 3,
81
+ deadLetter: true,
82
+ });
83
+ ```
84
+
85
+ ```typescript title="src/flows/workers/email.ts"
86
+ import { on, flow } from "okengine";
87
+ import { emailTask } from "@/signals/email";
88
+ import { rawEmail } from "@/channels/email";
89
+
90
+ export const processEmail = on(
91
+ emailTask,
92
+ flow("workers.email", {
93
+ do: async ({ to, body }, fx) => {
94
+ await fx.send(rawEmail, { to, body });
95
+ },
96
+ }),
97
+ );
98
+ ```
99
+
100
+ </Tab>
101
+
102
+ <Tab value="Clock">
103
+
104
+ Name the schedule, then bind it. Omit `timezone` for `"UTC"`:
105
+
106
+ ```typescript title="src/flows/metrics/cleanup.ts"
107
+ import { on, flow, clock } from "okengine";
108
+ import { lt } from "drizzle-orm";
109
+ import { db, metricLogs } from "@/schema";
110
+
111
+ export const cleanupClock = clock("metrics.cleanup", { every: "1h" });
112
+
113
+ export const cleanupMetrics = on(
114
+ cleanupClock,
115
+ flow("metrics.cleanup", {
116
+ do: async (_, fx) => {
117
+ await fx
118
+ .store(db)
119
+ .delete(metricLogs)
120
+ .where(lt(metricLogs.timestamp, fx.clock.ago("7d")));
121
+ },
122
+ }),
123
+ );
124
+ ```
125
+
126
+ </Tab>
127
+
128
+ <Tab value="CDC">
129
+
130
+ `db.table(handle).changed()` fires on insert, update, and delete. Input is `{ before, after }`:
131
+
132
+ ```typescript title="src/flows/audit/users.ts"
133
+ import { on, flow } from "okengine";
134
+ import { db } from "@/core";
135
+ import { users, auditLogs } from "@/schema";
136
+
137
+ export const onUserWrite = on(
138
+ db.table(users).changed(),
139
+ flow("audit.users", {
140
+ do: async ({ before, after }, fx) => {
141
+ const action = !before ? "created" : !after ? "deleted" : "updated";
142
+ const recordId = String(after?.id ?? before?.id ?? "");
143
+ await fx.store(db).insert(auditLogs).values({
144
+ table: "users",
145
+ recordId,
146
+ action,
147
+ });
148
+ },
149
+ }),
150
+ );
151
+ ```
152
+
153
+ </Tab>
154
+
155
+ <Tab value="Ordered">
156
+
157
+ `signal.once` plus `fx.emit(…, { key })` serializes work per key. Same key never runs
158
+ concurrently — the in-flight visibility lease is the lock:
159
+
160
+ ```typescript title="src/flows/orders/ship.ts"
161
+ import { on, flow } from "okengine";
162
+ import { orderPlaced } from "@/signals/orders";
163
+
164
+ export const shipOrder = on(
165
+ orderPlaced,
166
+ flow("orders.ship", {
167
+ do: async ({ orderId }, fx) => {
168
+ await fx.call(fulfillOrder, { orderId });
169
+ },
170
+ }),
171
+ );
172
+ ```
173
+
174
+ ```typescript
175
+ await fx.emit(orderPlaced, { orderId: "ord_1", userId: "usr_1" }, { key: "usr_1" });
176
+ ```
177
+
178
+ Omit `key` for competing consumers with no ordering.
179
+
180
+ </Tab>
181
+
182
+ </Tabs>
183
+
184
+ ## Trigger Reference
185
+
186
+ | Trigger | Signature | Purpose | `do` input |
187
+ | ---------- | -------------------------------------- | ------------------------------------------- | ------------------- |
188
+ | Signal | `on(signalHandle, flow)` | Queue (`once`) or fan-out (`broadcast`) | Payload (`schema`) |
189
+ | Clock | `on(clockDecl, flow)` | Interval or cron tick | none (`_`) |
190
+ | CDC any | `on(db.table(t).changed(), flow)` | Every insert / update / delete | `{ before, after }` |
191
+ | CDC column | `on(db.table(t).changed("col"), flow)` | Same writes; column stamped on the Manifest | `{ before, after }` |
192
+
193
+ `signal.live` is an HTTP SSE tape — bind it with [`http.live`](/docs/elements/flow/http#live-streams),
194
+ not as a worker. A Flow with no trigger is [call-only](/docs/elements/flow).
195
+
196
+ ## Signal Consumers
197
+
198
+ <Callout title="Detailed section">
199
+ If you only need a worker, jump to Once below. Physics live on `signal.once` / `broadcast` /
200
+ `live` — same idea as `http.get` / `http.post`. Defaults: `retries: 3`, `deadLetter: true`,
201
+ `optional: false`.
202
+ </Callout>
203
+
204
+ Each emit is handled according to the Signal helper you declared. The Flow is the subscriber.
205
+
206
+ <Tabs items={["Once", "Broadcast", "Optional"]}>
207
+
208
+ <Tab value="Once">
209
+
210
+ Competing workers — exactly one consumer processes each message. Failed attempts retry, then
211
+ dead-letter when `deadLetter` is true (default):
212
+
213
+ ```typescript title="src/signals/orders.ts"
214
+ import { signal } from "okengine";
215
+ import { z } from "zod";
216
+
217
+ export const orderPlaced = signal.once("orders.placed", {
218
+ schema: z.object({
219
+ orderId: z.string(),
220
+ amount: z.number(),
221
+ userId: z.string(),
222
+ }),
223
+ retries: 5,
224
+ deadLetter: true,
225
+ });
226
+ ```
227
+
228
+ ```typescript title="src/flows/orders/fulfill.ts"
229
+ import { on, flow } from "okengine";
230
+ import { orderPlaced } from "@/signals/orders";
231
+
232
+ export const fulfill = on(
233
+ orderPlaced,
234
+ flow("orders.fulfill", {
235
+ do: async ({ orderId }, fx) => {
236
+ await fx.call(chargeAndShip, { orderId });
237
+ },
238
+ }),
239
+ );
240
+ ```
241
+
242
+ Visibility lease defaults to **30s**. An inflight worker that dies is reclaimed on the next claim.
243
+
244
+ </Tab>
245
+
246
+ <Tab value="Broadcast">
247
+
248
+ Every subscribed Flow gets a copy. Offline listeners miss the event — there is no replay tape:
249
+
250
+ ```typescript title="src/signals/cache.ts"
251
+ import { signal } from "okengine";
252
+ import { z } from "zod";
253
+
254
+ export const catalogChanged = signal.broadcast("catalog.changed", {
255
+ schema: z.object({ sku: z.string() }),
256
+ });
257
+ ```
258
+
259
+ ```typescript title="src/flows/cache/invalidate.ts"
260
+ import { on, flow } from "okengine";
261
+ import { catalogChanged } from "@/signals/cache";
262
+
263
+ export const invalidate = on(
264
+ catalogChanged,
265
+ flow("cache.invalidate", {
266
+ do: async ({ sku }, fx) => {
267
+ await fx.cache.delete(`sku:${sku}`);
268
+ },
269
+ }),
270
+ );
271
+ ```
272
+
273
+ A second Flow bound to the same Signal also runs. That is the fan-out.
274
+
275
+ </Tab>
276
+
277
+ <Tab value="Optional">
278
+
279
+ Emit with zero subscribers throws **OKE1240** unless `optional: true`. Use that for live
280
+ firehoses and hook Signals that may have no worker yet:
281
+
282
+ ```typescript
283
+ export const webhook = signal.once("hooks.inbound", {
284
+ optional: true,
285
+ schema: z.object({ id: z.string() }),
286
+ });
287
+ ```
288
+
289
+ **OKE1240** cause: `Flow "{flow}" emits signal "{resource}" with no subscriber.`
290
+ Fix: add `on(signal, …)` or mark `{ optional: true }`.
291
+
292
+ </Tab>
293
+
294
+ </Tabs>
295
+
296
+ <Accordions>
297
+
298
+ <Accordion title="Signal Options">
299
+ Optional second argument to `signal.once` / `broadcast` / `live`. Delivery is the helper name.
300
+
301
+ | Option | Type | Default | Meaning |
302
+ | ------------- | ------------------------ | --------- | ------------------------------------------------------------ |
303
+ | `schema` | Standard Schema | omitted | Enforced at `fx.emit` (**OKE1250** on mismatch) |
304
+ | `retries` | `number` | `3` | Extra attempts after the first (`retries + 1` total) |
305
+ | `deadLetter` | `boolean` | `true` | Keep exhausted `once` messages; `false` marks them delivered |
306
+ | `optional` | `boolean` | `false` | Allow emit with zero subscribers |
307
+ | `retention` | `{ maxAge?, maxCount? }` | unbounded | **`signal.live` only** — type error on `once` / `broadcast` |
308
+ | `description` | `string` | the name | Console / docs blurb |
309
+
310
+ </Accordion>
311
+
312
+ <Accordion title="Ordering">
313
+ Pass `{ key }` on emit. No two `once` messages sharing `(signal, key)` are claimed at once.
314
+
315
+ ```typescript
316
+ await fx.emit(emailTask, payload, { key: user.id });
317
+ ```
318
+
319
+ Same key → FIFO. Different keys run in parallel. Omit `key` for a pure competing pool.
320
+
321
+ </Accordion>
322
+
323
+ <Accordion title="Retries & dead letters">
324
+ `once` retries then DLQ. After `retries + 1` handler invocations the message is dead when
325
+ `deadLetter: true`. Inspect with `fx.deadLetters(signal)`.
326
+
327
+ `deadLetter: false` marks the message delivered after the last attempt — nothing lands in the DLQ.
328
+
329
+ Broadcast does not use the `once` lease / DLQ path. Live uses the retained tape, not this worker.
330
+
331
+ </Accordion>
332
+
333
+ <Accordion title="Schema at emit">
334
+ Invalid payloads fail at `fx.emit` with **OKE1250** (`"{resource}": {detail}`) before any
335
+ consumer runs. Fix the payload or the Signal's `schema`. This is an **emit** contract — not
336
+ an HTTP invoke contract on `flow()`. Workers inherit the typed payload from the Signal
337
+ declaration; they do not declare `in` on `flow({ do })`.
338
+ </Accordion>
339
+
340
+ </Accordions>
341
+
342
+ ## Clock Jobs
343
+
344
+ <Callout title="Detailed section">
345
+ If you only need an interval, jump to the example below. `clock(name)` requires `cron` or `every`
346
+ — missing both throws `clock("name"): require cron or every`. Bind the returned handle with
347
+ `on(clockDecl, flow)`.
348
+ </Callout>
349
+
350
+ Named clocks reconcile into the Store at boot. The scheduler leader-elects so N instances do not
351
+ double-fire. `do` receives no payload — read time through `fx.clock`.
352
+
353
+ <Tabs items={["Interval", "Cron", "Per-tenant"]}>
354
+
355
+ <Tab value="Interval">
356
+
357
+ Human durations: `"200ms"` · `"30s"` · `"5m"` · `"1h"` · `"7d"` (integer + unit, no weeks):
358
+
359
+ ```typescript title="src/flows/health/ping.ts"
360
+ import { on, flow, clock } from "okengine";
361
+
362
+ export const pingClock = clock("health.pingExternal", { every: "30s" });
363
+
364
+ export const pingExternal = on(
365
+ pingClock,
366
+ flow("health.pingExternal", {
367
+ plane: "operator",
368
+ do: async (_, fx) => {
369
+ await fx.call(pingUpstream);
370
+ },
371
+ }),
372
+ );
373
+ ```
374
+
375
+ </Tab>
376
+
377
+ <Tab value="Cron">
378
+
379
+ Five-field cron (`m h dom mon dow`) plus an IANA `timezone` (default `"UTC"`):
380
+
381
+ ```typescript title="src/flows/reports/daily.ts"
382
+ import { on, flow, clock } from "okengine";
383
+
384
+ export const dailyReportClock = clock("reports.daily", {
385
+ cron: "0 6 * * *",
386
+ timezone: "Asia/Riyadh",
387
+ });
388
+
389
+ export const runDailyReport = on(
390
+ dailyReportClock,
391
+ flow("reports.runDaily", {
392
+ plane: "operator",
393
+ do: async (_, fx) => {
394
+ await fx.call(buildDailyReport, { at: fx.clock.now() });
395
+ },
396
+ }),
397
+ );
398
+ ```
399
+
400
+ You may set `cron` and `every` together. Extract records the cron expression as the Manifest
401
+ trigger when both are present.
402
+
403
+ </Tab>
404
+
405
+ <Tab value="Per-tenant">
406
+
407
+ `clock.perTenant` expands one Store row per tenant (`{name}#{tenantId}`). The bare template name
408
+ is never ticked:
409
+
410
+ ```typescript title="src/clocks/invoices.ts"
411
+ import { on, flow, clock } from "okengine";
412
+
413
+ export const invoicesClock = clock.perTenant("invoices", { every: "1h" });
414
+
415
+ export const runInvoices = on(
416
+ invoicesClock,
417
+ flow("billing.invoices", {
418
+ plane: "operator",
419
+ do: async (_, fx) => {
420
+ await fx.call(closeOpenInvoices);
421
+ },
422
+ }),
423
+ );
424
+ ```
425
+
426
+ Equivalent: `clock("invoices", { every: "1h", perTenant: true })`.
427
+
428
+ </Tab>
429
+
430
+ </Tabs>
431
+
432
+ <Accordions>
433
+
434
+ <Accordion title="Clock Options">
435
+ Second argument to `clock(name, options)` / `clock.perTenant(name, options)`.
436
+
437
+ | Option | Type | Default | Meaning |
438
+ | ------------- | ----------- | -------- | ---------------------------------------------- |
439
+ | `cron` | `string` | — | Five-field cron (`m h dom mon dow`) |
440
+ | `every` | `string` | — | Interval (`"10s"`, `"1h"`, `"7d"`, …) |
441
+ | `timezone` | IANA string | `"UTC"` | Zone for cron (intervals are duration-based) |
442
+ | `overridable` | `boolean` | `false` | Console may override the schedule in the Store |
443
+ | `perTenant` | `boolean` | `false` | Expand `{name}#{tenantId}` rows |
444
+ | `description` | `string` | the name | Console / docs blurb |
445
+
446
+ At least one of `cron` or `every` is required.
447
+
448
+ </Accordion>
449
+
450
+ <Accordion title="Leader lock">
451
+ Dev/prod clock driver is **postgres** (test is **frozen**). A short lease (default **30s**)
452
+ means only one instance runs each tick.
453
+
454
+ **Consequence:** three pods calling `runNow` still execute the Flow once. After the lease
455
+ expires, another instance may take the next tick.
456
+
457
+ `file` (`.oke/crons.json`) elects across processes on one machine. `memory` is single-process.
458
+
459
+ </Accordion>
460
+
461
+ <Accordion title="Catch-up policy one">
462
+ Catch-up is `"one"`: after 5 hours down on an hourly clock, the next tick fires **once**.
463
+ Missed slots are visible as `missedRuns` — they are not replayed as a burst.
464
+
465
+ **Consequence:** a digest that missed the night still runs once at boot, not 24 times.
466
+
467
+ </Accordion>
468
+
469
+ <Accordion title="DST & overrides">
470
+ Cron + a DST zone that lands in a spring-forward gap or fall-back overlap attaches a
471
+ **warning** on the Store row (`gap` / `overlap`). UTC never warns. The scheduler still ticks.
472
+
473
+ `overridable: true` lets Console edit the effective cron/every. Without it, a Console edit
474
+ fails with `ScheduleNotOverridableError`. Removed declarations become `orphaned` rows and
475
+ do not fire.
476
+
477
+ </Accordion>
478
+
479
+ </Accordions>
480
+
481
+ ## CDC
482
+
483
+ <Callout title="Detailed section">
484
+ If you only need any-write, jump to the example below. The handle is
485
+ `db.table(table).changed(column?)` — `db` is a `store.sql` declaration, `table` is a schema
486
+ handle. `changed("insert")` is **not** an op filter; it stamps a column named `insert`.
487
+ </Callout>
488
+
489
+ SQL writes through `fx.store` notify CDC after commit. The Flow input is always
490
+ `{ before, after }` (`CdcPayload`).
491
+
492
+ <Tabs items={["Any write", "Column", "Images"]}>
493
+
494
+ <Tab value="Any write">
495
+
496
+ Omit the argument to react to every insert, update, and delete on the table:
497
+
498
+ ```typescript title="src/flows/search/reindex.ts"
499
+ import { on, flow } from "okengine";
500
+ import { db } from "@/core";
501
+ import { notes } from "@/schema";
502
+
503
+ export const reindexNotes = on(
504
+ db.table(notes).changed(),
505
+ flow("search.reindexNotes", {
506
+ plane: "operator",
507
+ do: async ({ before, after }, fx) => {
508
+ const id = String(after?.id ?? before?.id ?? "");
509
+ if (!id) return;
510
+ if (!after) {
511
+ await fx.call(dropNoteIndex, { id });
512
+ return;
513
+ }
514
+ await fx.call(upsertNoteIndex, { id });
515
+ },
516
+ }),
517
+ );
518
+ ```
519
+
520
+ </Tab>
521
+
522
+ <Tab value="Column">
523
+
524
+ `changed("status")` stamps `trigger.cdc.column` on the Manifest (Console, extract). Still
525
+ receive `{ before, after }` — filter in `do` when you only care about that field:
526
+
527
+ ```typescript title="src/flows/tasks/on-status.ts"
528
+ import { on, flow } from "okengine";
529
+ import { db } from "@/core";
530
+ import { tasks } from "@/schema";
531
+
532
+ export const onStatus = on(
533
+ db.table(tasks).changed("status"),
534
+ flow("tasks.onStatus", {
535
+ plane: "operator",
536
+ do: async ({ before, after }, fx) => {
537
+ if (before?.status === after?.status) return;
538
+ const id = String(after?.id ?? before?.id ?? "");
539
+ if (!id) return;
540
+ await fx.emit(taskStatusChanged, {
541
+ id,
542
+ from: before?.status ?? null,
543
+ to: after?.status ?? null,
544
+ });
545
+ },
546
+ }),
547
+ );
548
+ ```
549
+
550
+ </Tab>
551
+
552
+ <Tab value="Images">
553
+
554
+ Op is inferred from which image is null:
555
+
556
+ | Write | `before` | `after` |
557
+ | ------ | ------------ | ------- |
558
+ | Insert | `null` | new row |
559
+ | Update | previous row | new row |
560
+ | Delete | previous row | `null` |
561
+
562
+ ```typescript
563
+ do: async ({ before, after }, fx) => {
564
+ if (!before && after) {
565
+ /* insert */
566
+ } else if (before && after) {
567
+ /* update */
568
+ } else if (before && !after) {
569
+ /* delete */
570
+ }
571
+ };
572
+ ```
573
+
574
+ </Tab>
575
+
576
+ </Tabs>
577
+
578
+ <Accordions>
579
+
580
+ <Accordion title="CDC payload">
581
+ Kernel input is `{ before, after }` — not `{ record }`, not `{ op }`. Read the primary key from
582
+ `after?.id ?? before?.id`.
583
+
584
+ Writes must go through `fx.store`. A raw SQL client bypasses the sink, so no consumer runs.
585
+
586
+ </Accordion>
587
+
588
+ <Accordion title="Outbox">
589
+ On RLS-capable SQL (`postgres` / `pglite`) the same write is appended to `oke_cdc_outbox`
590
+ for multi-host delivery. Pending backlog is a doctor finding (`cdc_outbox_backlog`).
591
+
592
+ Live **queries** (`store.resource({ live: true })` / `http.get(path).live(table)`) share this
593
+ CDC path but classify per subscriber — see [HTTP · Live Streams](/docs/elements/flow/http#live-streams).
594
+
595
+ </Accordion>
596
+
597
+ </Accordions>
598
+
599
+ ## Execution
600
+
601
+ Consumers share the Flow species with HTTP. The differences are the trigger and how failure
602
+ is retried.
603
+
604
+ | Kind | Start | Failure | Time |
605
+ | ------------------ | ------------------- | -------------------------------------- | ---------------- |
606
+ | Signal `once` | `fx.emit` | Signal `retries` then DLQ | `fx.clock.now()` |
607
+ | Signal `broadcast` | `fx.emit` | Per-subscriber; no `once` DLQ | `fx.clock.now()` |
608
+ | Clock | scheduler tick | Flow `retry` if set; no catch-up burst | `fx.clock.*` |
609
+ | CDC | committed SQL write | Flow `retry` if set | `fx.clock.now()` |
610
+
611
+ Mark long work `durable: true` and wrap side effects in `fx.step` — see
612
+ [Workflows](/docs/elements/flow/workflows).
613
+
614
+ Clock drivers: **postgres** in `dev`/`prod`, **frozen** in `test`. Signal drivers: **redis** in
615
+ `dev`/`prod`, **memory** in `test`.
616
+
617
+ ## Troubleshooting
618
+
619
+ <Accordions>
620
+
621
+ <Accordion title='TypeError: clock("name"): require cron or every'>
622
+ `clock(name)` needs `{cron}` and/or `{every}`. Empty options throw at declaration, before `on()`.
623
+ </Accordion>
624
+
625
+ <Accordion title="TypeError: on() expected a trigger or signal handle">
626
+ The first argument must be a Signal handle, a Clock handle, `db.table(…).changed()`, an HTTP
627
+ trigger, `internal`, or `mcp.tool(…)`. A bare interval string is not a trigger — wrap it in
628
+ `clock("name", { every: "1h" })`.
629
+ </Accordion>
630
+
631
+ <Accordion title="OKE1240 — emit with no subscriber">
632
+ Cause: `Flow "{flow}" emits signal "{resource}" with no subscriber.` Add `on(signal, flow)` or
633
+ set `{ optional: true }` on the Signal (live firehoses, unused hooks).
634
+ </Accordion>
635
+
636
+ <Accordion title="OKE1250 — emit failed schema">
637
+ Cause: `"{resource}": {detail}`. The payload failed the Signal's Standard Schema at emit. Fix the
638
+ payload; the consumer never ran.
639
+ </Accordion>
640
+
641
+ <Accordion title='changed("insert") never fires on inserts only'>
642
+ `changed()` takes an optional **column** name, not an op. `changed("insert")` waits for a column
643
+ named `insert`. Use `changed()` and branch on `before` / `after` being null.
644
+ </Accordion>
645
+
646
+ <Accordion title="CDC do never sees record / op">
647
+ Input is `{ before, after }`. There is no `record` field. Infer insert / update / delete from
648
+ which image is null.
649
+ </Accordion>
650
+
651
+ <Accordion title="Cron fired 24 times after overnight downtime">
652
+ It should not. Catch-up is `"one"` — one fire per overdue clock, then `nextRunAt` advances. If you
653
+ see a burst, you likely bound several clocks (or `clock.perTenant` expanded many tenants), not a
654
+ replay of missed hourly slots.
655
+ </Accordion>
656
+
657
+ <Accordion title="Two pods ran the same job">
658
+ Clock leader election needs a shared Store (`drivers.clock` postgres, or `file` on one host).
659
+ `memory` does not coordinate across processes. Check that both instances share `DATABASE_URL`.
660
+ </Accordion>
661
+
662
+ <Accordion title="ScheduleNotOverridableError from Console">
663
+ The clock was declared without `overridable: true`. Add it and redeploy, or edit the declaration
664
+ in source instead of Console.
665
+ </Accordion>
666
+
667
+ <Accordion title="tz is not a clock option">
668
+ The field is `timezone` (IANA), default `"UTC"`. `{ tz: "Asia/Riyadh" }` is ignored.
669
+ </Accordion>
670
+
671
+ </Accordions>
672
+
673
+ ## Learn more
674
+
675
+ - [Signal](/docs/elements/signal) — `once` / `broadcast` / `live` physics
676
+ - [Signal · Once](/docs/elements/signal/once) — leases, retries, partition keys
677
+ - [Clock](/docs/elements/clock) — schedules, `fx.clock.sleep`
678
+ - [Store · SQL](/docs/elements/store/sql) — tables CDC watches
679
+ - [HTTP · Live Streams](/docs/elements/flow/http#live-streams) — `signal.live` SSE
680
+ - [fx](/docs/reference/fx) — `fx.emit`, `fx.deadLetters`, `fx.clock`
681
+ - [Errors](/docs/reference/errors) — OKE1240 · OKE1250
682
+ - [Workflows](/docs/elements/flow/workflows) — `durable: true` + `fx.step` on a consumer
683
+
684
+ ## Next
685
+
686
+ <Cards>
687
+ <Card
688
+ title="Durable Workflows"
689
+ description="Step journaling and multi-step distributed execution."
690
+ href="/docs/elements/flow/workflows"
691
+ />
692
+ <Card
693
+ title="Signal Element"
694
+ description="Delivery physics — once, broadcast, and live tapes."
695
+ href="/docs/elements/signal"
696
+ />
697
+ <Card
698
+ title="Clock Element"
699
+ description="Named schedules, intervals, and durable sleep."
700
+ href="/docs/elements/clock"
701
+ />
702
+ <Card
703
+ title="HTTP"
704
+ description="Synchronous REST, QUERY, resources, and live SSE."
705
+ href="/docs/elements/flow/http"
706
+ />
707
+ </Cards>