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,517 @@
1
+ ---
2
+ title: "Broadcast"
3
+ description: "Ephemeral fan-out — every active subscriber gets a copy; offline listeners miss the event."
4
+ icon: "Share2"
5
+ source: "docs/spec/unified-theory.md"
6
+ ---
7
+
8
+ `signal.broadcast` delivers each emission to every subscribed Flow at once. Use it when every
9
+ active listener should react and you do not need a retained history.
10
+
11
+ For developers invalidating caches or syncing in-process state — declare the Signal, bind one or
12
+ more `on(signal, flow)` subscribers, emit with `fx.emit`.
13
+
14
+ <Callout title="The one rule">
15
+ Broadcast is ephemeral. If a subscriber is offline or restarts during the emit, it does not
16
+ receive past events. Use [`live`](/docs/elements/signal/live) when clients need replay. Use
17
+ [`once`](/docs/elements/signal/once) when exactly one worker must claim the job.
18
+ </Callout>
19
+
20
+ <SignalBroadcastFanout />
21
+
22
+ ## Smallest Example
23
+
24
+ <Steps>
25
+
26
+ <Step>
27
+ ### Define the broadcast signal
28
+
29
+ ```typescript title="src/signals/cache.ts"
30
+ import { signal } from "okengine";
31
+ import { z } from "zod";
32
+
33
+ export const cacheInvalidated = signal.broadcast("cache.invalidated", {
34
+ schema: z.object({ key: z.string() }),
35
+ });
36
+ ```
37
+
38
+ </Step>
39
+
40
+ <Step>
41
+ ### Bind a subscriber
42
+
43
+ ```typescript title="src/flows/cache/purge.ts"
44
+ import { on, flow } from "okengine";
45
+ import { cacheInvalidated } from "@/signals/cache";
46
+
47
+ export const purgeLocalCache = on(
48
+ cacheInvalidated,
49
+ flow("cache.purgeLocal", {
50
+ do: async ({ key }, fx) => {
51
+ await fx.store.kv.delete(key);
52
+ },
53
+ }),
54
+ );
55
+ ```
56
+
57
+ </Step>
58
+
59
+ <Step>
60
+ ### Emit from any Flow
61
+
62
+ ```typescript title="src/flows/skus/[sku]/update.ts"
63
+ import { on, flow, http } from "okengine";
64
+ import { z } from "zod";
65
+ import { cacheInvalidated } from "@/signals/cache";
66
+
67
+ export const update = on(
68
+ http.patch(),
69
+ flow({
70
+ in: z.object({ sku: z.string(), title: z.string().min(1) }),
71
+ do: async ({ sku, title }, fx) => {
72
+ // … persist the change …
73
+ await fx.emit(cacheInvalidated, { key: `sku:${sku}` });
74
+ return { sku, title };
75
+ },
76
+ }),
77
+ );
78
+ ```
79
+
80
+ The compiler records `emits: ["cache.invalidated"]` on the producer. Emit resolves when the
81
+ outbox commits — the HTTP request does not wait for every subscriber to finish.
82
+
83
+ </Step>
84
+
85
+ </Steps>
86
+
87
+ <Callout title="Same handle everywhere">
88
+ Import the declared Signal handle (or the same name) in every subscriber and producer. A typo in
89
+ the name creates a different Manifest entry — fan-out never crosses names.
90
+ </Callout>
91
+
92
+ ## Progressive Patterns
93
+
94
+ Explore broadcast from one listener to multi-Flow fan-out, optional hooks, and schema-checked
95
+ payloads:
96
+
97
+ <Tabs items={["Minimal", "Fan-out", "Optional", "Schema"]}>
98
+
99
+ <Tab value="Minimal">
100
+
101
+ One Flow subscribed — still broadcast physics (no competing claim, no once DLQ path):
102
+
103
+ ```typescript title="src/signals/catalog.ts"
104
+ import { signal } from "okengine";
105
+ import { z } from "zod";
106
+
107
+ export const catalogChanged = signal.broadcast("catalog.changed", {
108
+ schema: z.object({ sku: z.string() }),
109
+ });
110
+ ```
111
+
112
+ ```typescript title="src/flows/cache/invalidate.ts"
113
+ import { on, flow } from "okengine";
114
+ import { catalogChanged } from "@/signals/catalog";
115
+
116
+ export const invalidate = on(
117
+ catalogChanged,
118
+ flow("cache.invalidate", {
119
+ do: async ({ sku }, fx) => {
120
+ await fx.store.kv.delete(`sku:${sku}`);
121
+ },
122
+ }),
123
+ );
124
+ ```
125
+
126
+ </Tab>
127
+
128
+ <Tab value="Fan-out">
129
+
130
+ Bind a second Flow to the same Signal — both run. That is the fan-out:
131
+
132
+ ```typescript title="src/flows/search/reindex.ts"
133
+ import { on, flow } from "okengine";
134
+ import { catalogChanged } from "@/signals/catalog";
135
+
136
+ export const reindexSku = on(
137
+ catalogChanged,
138
+ flow("search.reindexSku", {
139
+ do: async ({ sku }, fx) => {
140
+ await fx.call(rebuildSearchDoc, { sku });
141
+ },
142
+ }),
143
+ );
144
+ ```
145
+
146
+ **Consequence:** each subscribed Flow gets its own copy. A failure in one handler does not
147
+ claim-lock the message away from siblings the way `once` leases do.
148
+
149
+ </Tab>
150
+
151
+ <Tab value="Optional">
152
+
153
+ Hooks that may have no subscriber yet need `optional: true` or emit throws **OKE1240**:
154
+
155
+ ```typescript
156
+ export const clusterHint = signal.broadcast("cluster.hint", {
157
+ optional: true,
158
+ schema: z.object({ nodeId: z.string() }),
159
+ });
160
+ ```
161
+
162
+ **OKE1240** cause: `Flow "{flow}" emits signal "{resource}" with no subscriber.`
163
+ Fix: bind at least one `on(signal, flow)` or set `{ optional: true }`.
164
+
165
+ </Tab>
166
+
167
+ <Tab value="Schema">
168
+
169
+ Invalid payloads fail at `fx.emit` with **OKE1250** before any subscriber runs:
170
+
171
+ ```typescript
172
+ export const priceTick = signal.broadcast("prices.tick", {
173
+ schema: z.object({
174
+ sku: z.string(),
175
+ cents: z.number().int().nonnegative(),
176
+ }),
177
+ });
178
+ ```
179
+
180
+ Cause shape: `"{resource}": {detail}`. Align the payload with `schema` — no subscriber started.
181
+
182
+ </Tab>
183
+
184
+ </Tabs>
185
+
186
+ ## Delivery Reference
187
+
188
+ How broadcast sits next to the other helpers when you are choosing physics:
189
+
190
+ | Helper | Pattern | Who runs | Replay | Typical use |
191
+ | ------------------ | ---------- | ---------------------------- | ---------------------- | ------------------------------------- |
192
+ | `signal.once` | Work queue | Exactly one competing worker | Retries + DLQ | Emails, fulfillment, sync |
193
+ | `signal.broadcast` | Pub/sub | Every active subscriber | None (miss if offline) | Cache bust, multi-side-effect fan-out |
194
+ | `signal.live` | SSE tape | HTTP clients via `http.live` | `Last-Event-ID` resume | Status feeds, progress |
195
+
196
+ | Guarantee | Broadcast | Once | Live |
197
+ | ---------------------------------- | ---------------------- | ------------------ | ----------------- |
198
+ | Competing claim / visibility lease | No | Yes (default 30s) | No |
199
+ | Dead-letter queue path | No | Yes (`deadLetter`) | No |
200
+ | Retained history for late joiners | No | No | Yes |
201
+ | Multiple Flows on one emit | Yes — each gets a copy | One claimer | Not a Flow worker |
202
+
203
+ ## Options for broadcast
204
+
205
+ Second argument to `signal.broadcast(name, options?)`. Delivery is the helper name — not an
206
+ option.
207
+
208
+ | Option | Type | Default | Meaning |
209
+ | ------------- | --------------- | -------- | -------------------------------------------------------- |
210
+ | `schema` | Standard Schema | omitted | **Emit** contract — validated at `fx.emit` (**OKE1250**) |
211
+ | `optional` | `boolean` | `false` | Allow emit with zero subscribers |
212
+ | `description` | `string` | the name | Console / docs blurb |
213
+ | `retries` | `number` | `3` | Declared; not the `once` lease / DLQ path |
214
+ | `deadLetter` | `boolean` | `true` | Declared; broadcast does not use once DLQ |
215
+
216
+ `retention` is a type error on `signal.broadcast` — that option is live-only:
217
+
218
+ ```text
219
+ signal.broadcast("…"): retention is only valid with signal.live
220
+ ```
221
+
222
+ ## Fan-out Physics
223
+
224
+ <Callout title="Detailed section">
225
+ If you only need one subscriber, jump to Emit. Fan-out is the distinctive physics: one emit, N
226
+ independent Flow runs, no competing claim.
227
+ </Callout>
228
+
229
+ Bind any number of Flows with `on(theSameSignal, flow(...))`. At dispatch, every matching
230
+ binding runs with the same payload.
231
+
232
+ ```typescript title="src/flows/orders/side-effects.ts"
233
+ import { on, flow } from "okengine";
234
+ import { orderChanged } from "@/signals/orders";
235
+
236
+ export const bustOrderCache = on(
237
+ orderChanged,
238
+ flow("orders.bustCache", {
239
+ do: async ({ orderId }, fx) => {
240
+ await fx.store.kv.delete(`order:${orderId}`);
241
+ },
242
+ }),
243
+ );
244
+
245
+ export const notifyOrderWatchers = on(
246
+ orderChanged,
247
+ flow("orders.notifyWatchers", {
248
+ do: async ({ orderId }, fx) => {
249
+ await fx.call(pushOrderWatchers, { orderId });
250
+ },
251
+ }),
252
+ );
253
+ ```
254
+
255
+ ```typescript
256
+ await fx.emit(orderChanged, { orderId: "ord_42", kind: "placed" });
257
+ ```
258
+
259
+ | Concept | Meaning |
260
+ | ---------- | ---------------------------------------------------------- |
261
+ | Subscriber | An adopted Flow bound with `on(broadcastSignal, flow)` |
262
+ | Copy | Each subscriber receives the payload independently |
263
+ | Isolation | One handler's failure does not lease-lock siblings |
264
+ | Offline | A process that was down at emit time does not get a replay |
265
+
266
+ **Consequence:** treat broadcast handlers as best-effort side effects. If the work must run
267
+ exactly once with retries and a DLQ, declare a separate `signal.once` for that job.
268
+
269
+ ## Emit and Effects
270
+
271
+ <Callout title="Detailed section">
272
+ If you only need `fx.emit(signal, payload)`, jump to the example. Emit commits the outbox when the
273
+ call resolves. Subscribers run asynchronously afterward.
274
+ </Callout>
275
+
276
+ | Call | Records | Use |
277
+ | ----------------------------------- | ------- | ----------------------------------------------------------- |
278
+ | `fx.emit(signal, payload?)` | `emits` | Publish to every active subscriber |
279
+ | `fx.emit(signal, payload, { key })` | `emits` | `key` is for `once` ordering — ignore for broadcast fan-out |
280
+
281
+ ```typescript title="src/flows/catalog/create.ts"
282
+ import { on, flow, http } from "okengine";
283
+ import { z } from "zod";
284
+ import { catalogChanged } from "@/signals/catalog";
285
+
286
+ export const create = on(
287
+ http.post(),
288
+ flow({
289
+ in: z.object({ sku: z.string(), title: z.string().min(1) }),
290
+ do: async ({ sku, title }, fx) => {
291
+ // … write …
292
+ await fx.emit(catalogChanged, { sku });
293
+ return { sku, title };
294
+ },
295
+ }),
296
+ );
297
+ ```
298
+
299
+ The producer WideEvent / run id is stamped as `parentRunId` for Console trace chains. Schema
300
+ mismatches fail at emit (**OKE1250**) before any subscriber runs.
301
+
302
+ ## Subscribers
303
+
304
+ Each verb of fan-out is a normal Flow. Bind with `on(signal, flow(...))` — same species as
305
+ [Consumers](/docs/elements/flow/consumers).
306
+
307
+ <Tabs items={["Cache", "Notify", "Reindex", "Compose"]}>
308
+
309
+ <Tab value="Cache">
310
+
311
+ Drop hot keys when the source of truth changes:
312
+
313
+ ```typescript title="src/flows/cache/on-catalog.ts"
314
+ import { on, flow } from "okengine";
315
+ import { catalogChanged } from "@/signals/catalog";
316
+
317
+ export const onCatalogCache = on(
318
+ catalogChanged,
319
+ flow("cache.onCatalog", {
320
+ do: async ({ sku }, fx) => {
321
+ await fx.store.kv.delete(`sku:${sku}`);
322
+ await fx.store.kv.delete(`sku:${sku}:meta`);
323
+ },
324
+ }),
325
+ );
326
+ ```
327
+
328
+ </Tab>
329
+
330
+ <Tab value="Notify">
331
+
332
+ Fan a domain event into a Channel or another Flow without competing for a lease:
333
+
334
+ ```typescript title="src/flows/notify/on-order.ts"
335
+ import { on, flow } from "okengine";
336
+ import { orderChanged } from "@/signals/orders";
337
+ import { orderWatchers } from "@/channels/orders";
338
+
339
+ export const onOrderNotify = on(
340
+ orderChanged,
341
+ flow("notify.onOrder", {
342
+ do: async ({ orderId }, fx) => {
343
+ await fx.send(orderWatchers, {
344
+ to: "ops@example.com",
345
+ data: { orderId },
346
+ });
347
+ },
348
+ }),
349
+ );
350
+ ```
351
+
352
+ </Tab>
353
+
354
+ <Tab value="Reindex">
355
+
356
+ Kick search / projection work as a side effect of the same emit:
357
+
358
+ ```typescript title="src/flows/search/on-catalog.ts"
359
+ import { on, flow } from "okengine";
360
+ import { catalogChanged } from "@/signals/catalog";
361
+
362
+ export const onCatalogReindex = on(
363
+ catalogChanged,
364
+ flow("search.onCatalog", {
365
+ do: async ({ sku }, fx) => {
366
+ await fx.call(rebuildSearchDoc, { sku });
367
+ },
368
+ }),
369
+ );
370
+ ```
371
+
372
+ </Tab>
373
+
374
+ <Tab value="Compose">
375
+
376
+ Need durable retries for one side effect? Emit a `once` job from the broadcast handler (or from
377
+ the producer) — do not stretch broadcast into a queue:
378
+
379
+ ```typescript title="src/flows/orders/on-changed.ts"
380
+ import { on, flow } from "okengine";
381
+ import { orderChanged, orderSyncJob } from "@/signals/orders";
382
+
383
+ export const onOrderChanged = on(
384
+ orderChanged,
385
+ flow("orders.onChanged", {
386
+ do: async ({ orderId }, fx) => {
387
+ await fx.store.kv.delete(`order:${orderId}`);
388
+ await fx.emit(orderSyncJob, { orderId });
389
+ },
390
+ }),
391
+ );
392
+ ```
393
+
394
+ `orderSyncJob` is `signal.once(...)` — competing workers, retries, and DLQ live there.
395
+
396
+ </Tab>
397
+
398
+ </Tabs>
399
+
400
+ ## Choosing Physics
401
+
402
+ <Callout title="Detailed section">
403
+ Pick physics from the guarantee you need. Broadcast is the wrong tool when work must be claimed
404
+ once, or when a late client must catch up.
405
+ </Callout>
406
+
407
+ | Need | Use instead |
408
+ | ------------------------------------- | ---------------------------------------------------- |
409
+ | Exactly one worker processes the job | [`once`](/docs/elements/signal/once) |
410
+ | Browser / SSE resume after disconnect | [`live`](/docs/elements/signal/live) |
411
+ | Durable multi-step work with journal | [Durable Workflows](/docs/elements/flow/workflows) |
412
+ | HTTP SSE firehose on a path | [`http.live`](/docs/elements/flow/http#live-streams) |
413
+
414
+ Broadcast does **not** use the `once` visibility lease. There is no competing claim and no
415
+ dead-letter queue for exhausted retries in the queue sense — see
416
+ [Once · Retries](/docs/elements/signal/once#retries-and-dead-letters).
417
+
418
+ <Accordions>
419
+
420
+ <Accordion title="Orphan emit (OKE1240)">
421
+ Cause: `Flow "{flow}" emits signal "{resource}" with no subscriber.`
422
+ Fix: bind at least one `on(signal, flow)` or set `{ optional: true }`.
423
+ </Accordion>
424
+
425
+ <Accordion title="Schema (OKE1250)">
426
+ Cause: `"{resource}": {detail}`. Fix the payload or the `schema`. No subscriber ran.
427
+ </Accordion>
428
+
429
+ <Accordion title="Missed while offline">
430
+ Expected. Restarted processes do not replay broadcast history. Emit again after boot if the
431
+ listener must refresh state, or switch to `live` for a retained tape.
432
+ </Accordion>
433
+
434
+ <Accordion title="retries / deadLetter on the declaration">
435
+ Those fields exist on the shared options type (defaults `3` / `true`) so Console and Manifest stay
436
+ uniform. Broadcast delivery does not follow the once lease + DLQ path — do not expect
437
+ `fx.deadLetters(broadcastSignal)` to behave like a queue operator surface.
438
+ </Accordion>
439
+
440
+ <Accordion title="retention on broadcast">
441
+ Throws at declare: `signal.broadcast("…"): retention is only valid with signal.live`. Cap a
442
+ client-visible tape with `signal.live({retention})` instead.
443
+ </Accordion>
444
+
445
+ </Accordions>
446
+
447
+ ## Troubleshooting
448
+
449
+ <Accordions>
450
+
451
+ <Accordion title="Only one of two subscribers runs">
452
+ Confirm both Flows import the same Signal handle / name and are adopted into the app. Names must
453
+ match the Manifest entry exactly. A second `signal.broadcast("catalog.changed")` with a different
454
+ spelling is a different signal.
455
+ </Accordion>
456
+
457
+ <Accordion title="Subscriber missed an event after restart">
458
+ Broadcast has no tape. Re-emit on boot, hydrate from Store, or use
459
+ [`signal.live`](/docs/elements/signal/live).
460
+ </Accordion>
461
+
462
+ <Accordion title="OKE1240 on emit">
463
+ Cause: `Flow "{flow}" emits signal "{resource}" with no subscriber.`
464
+ No subscriber is bound. Add `on(signal, …)` or `{ optional: true }`.
465
+ </Accordion>
466
+
467
+ <Accordion title="OKE1250 on emit">
468
+ Cause: `"{resource}": {detail}`. Align the payload with `schema` — no subscriber started.
469
+ </Accordion>
470
+
471
+ <Accordion title="Expecting retries / DLQ like a queue">
472
+ Use `signal.once`. Broadcast fan-out is not the lease + DLQ path documented under
473
+ [Once](/docs/elements/signal/once).
474
+ </Accordion>
475
+
476
+ <Accordion title="TypeError: retention is only valid with signal.live">
477
+ You passed `retention` to `signal.broadcast`. Drop it, or switch the helper to `signal.live` if
478
+ you need a retained SSE tape.
479
+ </Accordion>
480
+
481
+ <Accordion title="HTTP client never sees the event">
482
+ Broadcast is for Flow subscribers, not browsers. Expose a
483
+ [`signal.live`](/docs/elements/signal/live) tape with
484
+ [`http.live`](/docs/elements/flow/http#live-streams) (or a gated GET `.live(signal)`).
485
+ </Accordion>
486
+
487
+ </Accordions>
488
+
489
+ ## Learn more
490
+
491
+ - [Signal Overview](/docs/elements/signal) — delivery matrix and drivers
492
+ - [Once](/docs/elements/signal/once) — competing workers and DLQ
493
+ - [Live](/docs/elements/signal/live) — retained SSE
494
+ - [Consumers](/docs/elements/flow/consumers) — binding `on(signal)`
495
+ - [HTTP · Live Streams](/docs/elements/flow/http#live-streams) — when the consumer is a browser
496
+ - [fx](/docs/reference/fx) — `fx.emit`
497
+ - [Errors](/docs/reference/errors) — OKE1240 · OKE1250
498
+
499
+ ## Next
500
+
501
+ <Cards>
502
+ <Card
503
+ title="Live"
504
+ description="Retained event tapes over HTTP SSE."
505
+ href="/docs/elements/signal/live"
506
+ />
507
+ <Card
508
+ title="Once"
509
+ description="Competing workers with leases and dead letters."
510
+ href="/docs/elements/signal/once"
511
+ />
512
+ <Card
513
+ title="Consumers"
514
+ description="Signal workers, Clock jobs, and SQL CDC."
515
+ href="/docs/elements/flow/consumers"
516
+ />
517
+ </Cards>