okengine 0.18.4 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (512) hide show
  1. package/AGENTS.md +18 -10
  2. package/README.md +2 -2
  3. package/package.json +51 -50
  4. package/site/content/docs/ai/index.mdx +25 -6
  5. package/site/content/docs/ai/llms-txt.mdx +1 -1
  6. package/site/content/docs/ai/meta.json +1 -1
  7. package/site/content/docs/ai/skills.mdx +4 -1
  8. package/site/content/docs/ai/try-it.mdx +58 -0
  9. package/site/content/docs/client/auth.mdx +208 -0
  10. package/site/content/docs/client/calling.mdx +451 -0
  11. package/site/content/docs/client/index.mdx +209 -0
  12. package/site/content/docs/client/live.mdx +234 -0
  13. package/site/content/docs/client/meta.json +5 -0
  14. package/site/content/docs/client/react.mdx +249 -0
  15. package/site/content/docs/elements/ai/agents.mdx +232 -0
  16. package/site/content/docs/elements/ai/index.mdx +341 -0
  17. package/site/content/docs/elements/ai/mcp.mdx +275 -0
  18. package/site/content/docs/elements/ai/meta.json +5 -0
  19. package/site/content/docs/elements/ai/models.mdx +283 -0
  20. package/site/content/docs/elements/ai/prompts.mdx +256 -0
  21. package/site/content/docs/elements/channel/email.mdx +266 -0
  22. package/site/content/docs/elements/channel/index.mdx +344 -0
  23. package/site/content/docs/elements/channel/meta.json +5 -0
  24. package/site/content/docs/elements/channel/push.mdx +218 -0
  25. package/site/content/docs/elements/channel/receipts.mdx +206 -0
  26. package/site/content/docs/elements/channel/sms.mdx +264 -0
  27. package/site/content/docs/elements/channel/whatsapp.mdx +220 -0
  28. package/site/content/docs/elements/clock/index.mdx +300 -0
  29. package/site/content/docs/elements/clock/meta.json +5 -0
  30. package/site/content/docs/elements/clock/schedules.mdx +508 -0
  31. package/site/content/docs/elements/clock/sleep.mdx +445 -0
  32. package/site/content/docs/elements/flow/consumers.mdx +707 -0
  33. package/site/content/docs/elements/flow/http.mdx +1080 -0
  34. package/site/content/docs/elements/flow/index.mdx +618 -0
  35. package/site/content/docs/elements/flow/meta.json +5 -0
  36. package/site/content/docs/elements/flow/routing.mdx +559 -0
  37. package/site/content/docs/elements/flow/workflows.mdx +640 -0
  38. package/site/content/docs/elements/gate/auth.mdx +371 -0
  39. package/site/content/docs/elements/gate/authorization.mdx +298 -0
  40. package/site/content/docs/elements/gate/index.mdx +418 -0
  41. package/site/content/docs/elements/gate/meta.json +5 -0
  42. package/site/content/docs/elements/gate/rate-limits.mdx +365 -0
  43. package/site/content/docs/elements/gate/rls.mdx +303 -0
  44. package/site/content/docs/elements/gate/tenancy.mdx +372 -0
  45. package/site/content/docs/elements/index.mdx +52 -11
  46. package/site/content/docs/elements/signal/broadcast.mdx +517 -0
  47. package/site/content/docs/elements/signal/index.mdx +352 -0
  48. package/site/content/docs/elements/signal/live.mdx +590 -0
  49. package/site/content/docs/elements/signal/meta.json +5 -0
  50. package/site/content/docs/elements/signal/once.mdx +596 -0
  51. package/site/content/docs/elements/store/files.mdx +659 -0
  52. package/site/content/docs/elements/store/index.mdx +310 -0
  53. package/site/content/docs/elements/store/kv.mdx +629 -0
  54. package/site/content/docs/elements/store/meta.json +5 -0
  55. package/site/content/docs/elements/store/search.mdx +946 -0
  56. package/site/content/docs/elements/store/sql.mdx +937 -0
  57. package/site/content/docs/elements/vault/config.mdx +256 -0
  58. package/site/content/docs/elements/vault/index.mdx +356 -0
  59. package/site/content/docs/elements/vault/meta.json +5 -0
  60. package/site/content/docs/elements/vault/rotation.mdx +273 -0
  61. package/site/content/docs/elements/vault/secrets.mdx +285 -0
  62. package/site/content/docs/index.mdx +78 -30
  63. package/site/content/docs/meta.json +11 -8
  64. package/site/content/docs/plugins/cors.mdx +2 -1
  65. package/site/content/docs/plugins/csrf.mdx +4 -1
  66. package/site/content/docs/plugins/headers.mdx +2 -1
  67. package/site/content/docs/plugins/ip-allowlist.mdx +2 -1
  68. package/site/content/docs/plugins/magic-link.mdx +6 -0
  69. package/site/content/docs/plugins/maintenance-mode.mdx +2 -1
  70. package/site/content/docs/plugins/oauth.mdx +1 -1
  71. package/site/content/docs/plugins/otp.mdx +12 -1
  72. package/site/content/docs/plugins/passkey.mdx +39 -15
  73. package/site/content/docs/plugins/two-factor.mdx +91 -34
  74. package/site/content/docs/plugins/username.mdx +1 -1
  75. package/site/content/docs/recipes/caddy.mdx +3 -3
  76. package/site/content/docs/recipes/index.mdx +18 -28
  77. package/site/content/docs/recipes/mailpit.mdx +1 -1
  78. package/site/content/docs/recipes/meilisearch.mdx +3 -3
  79. package/site/content/docs/recipes/meta.json +1 -4
  80. package/site/content/docs/recipes/nginx.mdx +2 -2
  81. package/site/content/docs/recipes/openrouter.mdx +283 -0
  82. package/site/content/docs/recipes/pgdog.mdx +3 -3
  83. package/site/content/docs/recipes/postgres.mdx +1 -2
  84. package/site/content/docs/recipes/rustfs.mdx +2 -2
  85. package/site/content/docs/recipes/traefik.mdx +3 -3
  86. package/site/content/docs/reference/cli.mdx +231 -0
  87. package/site/content/docs/reference/configuration.mdx +30 -10
  88. package/site/content/docs/reference/environment-variables.mdx +93 -20
  89. package/site/content/docs/reference/errors.mdx +132 -29
  90. package/site/content/docs/reference/fx.mdx +137 -73
  91. package/site/content/docs/reference/index.mdx +13 -21
  92. package/site/content/docs/reference/meta.json +4 -5
  93. package/site/content/docs/reference/okid.mdx +42 -17
  94. package/site/content/docs/reference/plugins.mdx +4 -3
  95. package/site/content/docs/reference/security.mdx +197 -0
  96. package/site/content/docs/understand/meta.json +5 -0
  97. package/site/content/docs/understand/the-anatomy.mdx +132 -0
  98. package/site/content/docs/understand/the-model.mdx +32 -0
  99. package/site/content/docs/understand/the-problem.mdx +74 -0
  100. package/site/content/docs/understand/the-vocabulary.mdx +26 -0
  101. package/src/auth/api-keys.ts +2 -1
  102. package/src/auth/bindings.ts +64 -16
  103. package/src/auth/gate-auth.test.ts +6 -1
  104. package/src/auth/identity.ts +152 -5
  105. package/src/auth/index.ts +29 -0
  106. package/src/auth/invites.ts +2 -1
  107. package/src/auth/method-context.ts +41 -0
  108. package/src/auth/oauth-as/crypto.ts +2 -1
  109. package/src/auth/oauth-as/stores.ts +2 -1
  110. package/src/auth/operator.ts +2 -1
  111. package/src/auth/sessions-jwt.test.ts +77 -0
  112. package/src/auth/sessions.ts +20 -4
  113. package/src/auth/tenants.ts +3 -2
  114. package/src/auth/two-factor-challenge.test.ts +61 -0
  115. package/src/auth/two-factor-challenge.ts +284 -0
  116. package/src/auth/verification.ts +5 -0
  117. package/src/bench/REPORT.md +49 -0
  118. package/src/bench/g03-signal-once.bench.ts +1 -1
  119. package/src/bench/g10-observability-contention.bench.ts +1 -1
  120. package/src/bench/g17-hybrid-search.bench.ts +572 -0
  121. package/src/bench/load-app.ts +2 -10
  122. package/src/cli/ai-setup/ai-setup.test.ts +331 -42
  123. package/src/cli/ai-setup/apply.ts +294 -52
  124. package/src/cli/ai-setup/catalog.ts +238 -1342
  125. package/src/cli/ai-setup/index.ts +14 -99
  126. package/src/cli/ai-setup/prompts.ts +60 -443
  127. package/src/cli/ask-seed.test.ts +96 -0
  128. package/src/cli/ask-seed.ts +82 -0
  129. package/src/cli/ask-vault-gaps.test.ts +84 -0
  130. package/src/cli/ask-vault-gaps.ts +146 -0
  131. package/src/cli/build.ts +2 -2
  132. package/src/cli/client-add.test.ts +26 -1
  133. package/src/cli/client-add.ts +106 -27
  134. package/src/cli/competitor-mention-removal.test.ts +1 -1
  135. package/src/cli/db-seed.ts +2 -0
  136. package/src/cli/db.ts +150 -11
  137. package/src/cli/dev.ts +119 -211
  138. package/src/cli/docker-clean.ts +2 -2
  139. package/src/cli/docker-cli.test.ts +1 -1
  140. package/src/cli/doctor-diff.ts +4 -2
  141. package/src/cli/doctor-pii.test.ts +1 -1
  142. package/src/cli/doctor.ts +14 -1
  143. package/src/cli/hero-meta.test.ts +4 -2
  144. package/src/cli/load-config.images.test.ts +8 -10
  145. package/src/cli/load-config.ts +1 -1
  146. package/src/cli/registry.ts +22 -10
  147. package/src/cli/replay.ts +3 -1
  148. package/src/cli/start.ts +1 -1
  149. package/src/cli/tui/keys.ts +2 -3
  150. package/src/client/auth/cookies.ts +74 -0
  151. package/src/client/auth/create-auth-client.ts +636 -0
  152. package/src/client/auth/denials.ts +99 -0
  153. package/src/client/auth/session.ts +283 -0
  154. package/src/client/auth.test.ts +251 -0
  155. package/src/client/auth.ts +39 -114
  156. package/src/client/create-with-session.ts +245 -0
  157. package/src/client/create.ts +51 -5
  158. package/src/client/index.ts +11 -1
  159. package/src/client/live.ts +11 -79
  160. package/src/client/notes-contract.test.ts +5 -10
  161. package/src/client/sse.ts +137 -0
  162. package/src/client/stream.ts +147 -0
  163. package/src/client/transport.test.ts +32 -0
  164. package/src/client/transport.ts +123 -26
  165. package/src/client/types.ts +118 -14
  166. package/src/client-react/index.ts +185 -24
  167. package/src/client-react/use-live-query.ts +13 -3
  168. package/src/compiler/aot.test.ts +7 -4
  169. package/src/compiler/effects-embed.test.ts +56 -0
  170. package/src/compiler/effects-fetch.test.ts +43 -0
  171. package/src/compiler/effects-infer.ts +58 -2
  172. package/src/compiler/extract.test.ts +258 -44
  173. package/src/compiler/extract.ts +566 -48
  174. package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +7 -4
  175. package/src/compiler/fixtures/skyport/src/flows/bookings/signals.ts +2 -8
  176. package/src/compiler/fixtures/skyport.expected.json +4 -4
  177. package/src/compiler/fixtures/triggers/five-triggers.ts +14 -10
  178. package/src/compiler/response.ts +2 -2
  179. package/src/compiler/search-writer-isolation.test.ts +40 -0
  180. package/src/config/index.ts +11 -0
  181. package/src/console/server/app.ts +17 -23
  182. package/src/console/server/bind.ts +4 -0
  183. package/src/console/server/console.test.ts +2 -2
  184. package/src/console/server/flows-invoke.test.ts +50 -34
  185. package/src/console/server/flows.ts +436 -255
  186. package/src/console/server/runs-ingest.test.ts +6 -3
  187. package/src/console/server/serve.ts +1 -1
  188. package/src/console/server/signals.test.ts +1 -5
  189. package/src/console/server/signals.ts +8 -6
  190. package/src/console/server/state.ts +6 -1
  191. package/src/console/server/store.ts +1 -1
  192. package/src/console/ui-next/dist/assets/FileExportIcon-Ck-5od4R.js +1 -0
  193. package/src/console/ui-next/dist/assets/MoreHorizontalCircle01Icon-gMNGsE37.js +1 -0
  194. package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +1 -0
  195. package/src/console/ui-next/dist/assets/UnavailableIcon-D9cvHVPr.js +1 -0
  196. package/src/console/ui-next/dist/assets/UserIcon-DaE2PB5_.js +1 -0
  197. package/src/console/ui-next/dist/assets/access-page-DFeymU07.js +4 -0
  198. package/src/console/ui-next/dist/assets/agent-disclosure-U1rdfblp.js +1 -0
  199. package/src/console/ui-next/dist/assets/cache-glyph-BeFJeqBG.js +1 -0
  200. package/src/console/ui-next/dist/assets/call-pii-button-CVAONPii.js +1 -0
  201. package/src/console/ui-next/dist/assets/collapsible-D2A6NJ-3.js +1 -0
  202. package/src/console/ui-next/dist/assets/copy-inline-button-CAYD18cr.js +1 -0
  203. package/src/console/ui-next/dist/assets/dagre.esm-B1_XeuLP.js +1 -0
  204. package/src/console/ui-next/dist/assets/detail-header-DVWNjWjg.js +1 -0
  205. package/src/console/ui-next/dist/assets/dropdown-menu-4h2LOVXM.js +1 -0
  206. package/src/console/ui-next/dist/assets/duration-tone-Cgk_h5ja.js +9 -0
  207. package/src/console/ui-next/dist/assets/element-icons-BI8cJgdh.js +1 -0
  208. package/src/console/ui-next/dist/assets/explorer-empty-CJs5A-wm.js +1 -0
  209. package/src/console/ui-next/dist/assets/flows-page-Dss7941e.js +1 -0
  210. package/src/console/ui-next/dist/assets/highlighted-json-MYZQtRnw.js +154 -0
  211. package/src/console/ui-next/dist/assets/http-method-Jrh39p7A.js +1 -0
  212. package/src/console/ui-next/dist/assets/index-D0zS5rKO.css +2 -0
  213. package/src/console/ui-next/dist/assets/index-DXP2dBIF.js +63 -0
  214. package/src/console/ui-next/dist/assets/observability-page-HvolXxTI.js +4 -0
  215. package/src/console/ui-next/dist/assets/react-dom-Ddte4I-Q.js +9 -0
  216. package/src/console/ui-next/dist/assets/replica-lag-CSh2dzrb.js +18 -0
  217. package/src/console/ui-next/dist/assets/request-meta-BatF8KrK.js +1 -0
  218. package/src/console/ui-next/dist/assets/shortcut-keys-3ILd8oGn.js +1 -0
  219. package/src/console/ui-next/dist/assets/{sql-BskegiFM.js → sql-BsFa4tDR.js} +1 -1
  220. package/src/console/ui-next/dist/assets/store-page-eiKiHnNe.js +41 -0
  221. package/src/console/ui-next/dist/assets/trace-detail-sheet-CZkMeKS-.js +2 -0
  222. package/src/console/ui-next/dist/assets/tree-expand-toggle-CW8y5A2h.js +55 -0
  223. package/src/console/ui-next/dist/assets/units-page-B_RWJrEO.js +1 -0
  224. package/src/console/ui-next/dist/assets/{use-vault-list-uk4WVboC.js → use-vault-list-CT4-gajj.js} +1 -1
  225. package/src/console/ui-next/dist/assets/vault-page-CWrg-A68.js +2 -0
  226. package/src/console/ui-next/dist/assets/xyflow-CSyC6ryz.css +1 -0
  227. package/src/console/ui-next/dist/assets/xyflow-yApv7D4e.js +7 -0
  228. package/src/console/ui-next/dist/index.html +6 -13
  229. package/src/console/ui-next/seed-invoke-host.ts +21 -18
  230. package/src/console/ui-next/src/client.ts +7 -2
  231. package/src/console/ui-next/src/features/flows/graph/element-map.ts +2 -0
  232. package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +12 -2
  233. package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +4 -0
  234. package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +8 -0
  235. package/src/console/ui-next/src/features/flows/traces/waterfall-bars.ts +4 -0
  236. package/src/console/ui-next/src/features/flows/traces/waterfall-tooltip.ts +9 -3
  237. package/src/console/ui-next/src/features/store/detail/reveal-cell.tsx +7 -11
  238. package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +2 -2
  239. package/src/console/ui-next/src/features/store/lib/fields-from-table.ts +2 -0
  240. package/src/console/ui-next/src/features/store/lib/grid-model.test.ts +25 -1
  241. package/src/console/ui-next/src/features/store/lib/grid-model.ts +36 -1
  242. package/src/console/ui-next/src/features/store/query/query-results.tsx +3 -3
  243. package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +2 -0
  244. package/src/console/ui-next/src/features/units/lib/call-read-safe.ts +3 -1
  245. package/src/console/ui-next/src/features/units/lib/fields-from-schema.ts +6 -0
  246. package/src/docker/ai-model-status.test.ts +1 -12
  247. package/src/docker/ai-model-status.ts +10 -38
  248. package/src/docker/compose-up.test.ts +100 -0
  249. package/src/docker/compose-up.ts +292 -0
  250. package/src/docker/compose.ts +4 -14
  251. package/src/docker/derive.ts +9 -34
  252. package/src/docker/docker.test.ts +16 -261
  253. package/src/docker/images-config.test.ts +13 -25
  254. package/src/docker/index.ts +1 -24
  255. package/src/docker/recipes/index.ts +0 -23
  256. package/src/docker/recipes/pgdog.ts +6 -4
  257. package/src/docker/stack-id.test.ts +2 -2
  258. package/src/docker/stack-id.ts +0 -1
  259. package/src/docker/types.ts +2 -2
  260. package/src/drivers/ai-anthropic.ts +10 -0
  261. package/src/drivers/ai-openai-compatible.ts +34 -7
  262. package/src/drivers/ai-providers.test.ts +0 -107
  263. package/src/drivers/ai-stream.test.ts +0 -34
  264. package/src/drivers/ai-types.ts +17 -8
  265. package/src/drivers/channel-fcm.ts +6 -1
  266. package/src/drivers/channel-msegat.ts +6 -1
  267. package/src/drivers/channel-resend.ts +5 -1
  268. package/src/drivers/channel-smtp.ts +5 -1
  269. package/src/drivers/channel-sndr.ts +10 -1
  270. package/src/drivers/channel-taqnyat-mail.ts +5 -1
  271. package/src/drivers/channel-taqnyat-whatsapp.ts +6 -1
  272. package/src/drivers/channel-taqnyat.ts +6 -1
  273. package/src/drivers/channel-types.ts +10 -0
  274. package/src/drivers/channel-unifonic.ts +6 -1
  275. package/src/drivers/channel-wa-cloud.ts +6 -1
  276. package/src/drivers/channel-webpush.ts +6 -1
  277. package/src/drivers/external.ts +33 -0
  278. package/src/drivers/index.ts +0 -10
  279. package/src/drivers/meilisearch.ts +6 -0
  280. package/src/drivers/oauth-types.ts +4 -0
  281. package/src/drivers/postgres.test.ts +74 -3
  282. package/src/drivers/postgres.ts +175 -6
  283. package/src/drivers/signal-types.ts +5 -5
  284. package/src/drivers/types.ts +7 -0
  285. package/src/drivers/vault-types.ts +5 -0
  286. package/src/elements/ai/declare.ts +17 -4
  287. package/src/elements/ai/eval.ts +1 -1
  288. package/src/elements/ai/mcp-http.ts +1 -1
  289. package/src/elements/ai/pii.ts +1 -1
  290. package/src/elements/ai/providers.test.ts +289 -0
  291. package/src/elements/ai/providers.ts +169 -0
  292. package/src/elements/ai/runtime.ts +68 -5
  293. package/src/elements/ai/schema.ts +2 -2
  294. package/src/elements/ai.test.ts +7 -7
  295. package/src/elements/ai.ts +17 -1
  296. package/src/elements/channel/runtime.ts +105 -12
  297. package/src/elements/clock/cron-fields.test.ts +144 -0
  298. package/src/elements/clock/cron-fields.ts +185 -0
  299. package/src/elements/clock/declare.ts +247 -3
  300. package/src/elements/clock.ts +21 -1
  301. package/src/elements/index.ts +15 -0
  302. package/src/elements/signal/chaos-child.ts +1 -2
  303. package/src/elements/signal/declare.ts +65 -37
  304. package/src/elements/signal/delivery-modes.test.ts +11 -31
  305. package/src/elements/signal/dry-run-replay.test.ts +1 -7
  306. package/src/elements/signal/dry-run-write-isolation.test.ts +1 -7
  307. package/src/elements/signal/key-ordering.test.ts +6 -26
  308. package/src/elements/signal/lease-reclaim.test.ts +2 -6
  309. package/src/elements/signal/optional-emit.test.ts +6 -9
  310. package/src/elements/signal/order-lifecycle.test.ts +4 -17
  311. package/src/elements/signal/orphan-messages.test.ts +4 -15
  312. package/src/elements/signal/reconcile.test.ts +2 -2
  313. package/src/elements/signal/runtime.ts +1 -1
  314. package/src/elements/signal/schema-emit.test.ts +4 -8
  315. package/src/elements/signal.test.ts +24 -49
  316. package/src/elements/signal.ts +9 -2
  317. package/src/elements/store/domain-ddl.test.ts +1 -1
  318. package/src/elements/store/live-http.test.ts +9 -4
  319. package/src/elements/store/prepare-row.test.ts +123 -0
  320. package/src/elements/store/resource.ts +43 -12
  321. package/src/elements/store/schema-decl.ts +117 -0
  322. package/src/elements/store/search-backfill.ts +205 -0
  323. package/src/elements/store/search-bind.ts +66 -0
  324. package/src/elements/store/search-bm25.ts +78 -0
  325. package/src/elements/store/search-ddl.ts +156 -0
  326. package/src/elements/store/search-embed-flow.ts +141 -0
  327. package/src/elements/store/search-errors.ts +34 -0
  328. package/src/elements/store/search-fusion.ts +112 -0
  329. package/src/elements/store/search-lsh.ts +179 -0
  330. package/src/elements/store/search-runtime.ts +300 -0
  331. package/src/elements/store/search.test.ts +144 -0
  332. package/src/elements/store/sql-session.test.ts +1 -0
  333. package/src/elements/store/sql-session.ts +83 -3
  334. package/src/elements/store/table.ts +23 -2
  335. package/src/elements/store/upsert-app.test.ts +1 -3
  336. package/src/elements/store.ts +50 -0
  337. package/src/full.ts +4 -2
  338. package/src/http.ts +4 -1
  339. package/src/i18n/catalogs/ar.ts +14 -6
  340. package/src/i18n/catalogs/en.ts +14 -6
  341. package/src/index.ts +6 -1
  342. package/src/kernel/adopt-barrel-fresh.test.ts +7 -7
  343. package/src/kernel/adopt-routes.ts +9 -1
  344. package/src/kernel/app-auth.ts +5 -0
  345. package/src/kernel/app.ts +102 -9
  346. package/src/kernel/auto-cache.test.ts +6 -12
  347. package/src/kernel/auto-registry.test.ts +3 -3
  348. package/src/kernel/boot-bind/ai.test.ts +22 -19
  349. package/src/kernel/boot-bind/ai.ts +5 -43
  350. package/src/kernel/boot-bind/clock.ts +23 -5
  351. package/src/kernel/boot-bind/honor-config.test.ts +4 -4
  352. package/src/kernel/boot.test.ts +11 -6
  353. package/src/kernel/boot.ts +66 -16
  354. package/src/kernel/boundary-contract.ts +93 -0
  355. package/src/kernel/budget.test.ts +1 -1
  356. package/src/kernel/call.ts +89 -0
  357. package/src/kernel/capability.ts +6 -0
  358. package/src/kernel/client-descriptor.ts +123 -0
  359. package/src/kernel/clock-timezone.test.ts +80 -0
  360. package/src/kernel/correlation.test.ts +1 -1
  361. package/src/kernel/dry-run.ts +11 -8
  362. package/src/kernel/effects-stamping.test.ts +42 -9
  363. package/src/kernel/effects.test.ts +4 -2
  364. package/src/kernel/effects.ts +51 -9
  365. package/src/kernel/errors-channel.ts +17 -0
  366. package/src/kernel/errors-live-resume.ts +3 -2
  367. package/src/kernel/errors-tenant.ts +7 -4
  368. package/src/kernel/errors.registry-helpers.ts +96 -0
  369. package/src/kernel/errors.registry.test.ts +139 -25
  370. package/src/kernel/errors.ts +102 -23
  371. package/src/kernel/external-effects.test.ts +196 -0
  372. package/src/kernel/flow.test.ts +13 -4
  373. package/src/kernel/flow.ts +69 -71
  374. package/src/kernel/fx-ask-telemetry.test.ts +2 -2
  375. package/src/kernel/fx-dead-letters.test.ts +3 -3
  376. package/src/kernel/fx-fetch.ts +55 -0
  377. package/src/kernel/fx-live-stream.ts +2 -2
  378. package/src/kernel/fx-live.test.ts +8 -8
  379. package/src/kernel/fx.test.ts +17 -11
  380. package/src/kernel/fx.ts +180 -57
  381. package/src/kernel/horizontal-child.ts +1 -1
  382. package/src/kernel/http-query.test.ts +3 -6
  383. package/src/kernel/index.ts +13 -3
  384. package/src/kernel/instance-id.ts +1 -1
  385. package/src/kernel/instances.test.ts +40 -0
  386. package/src/kernel/instances.ts +47 -9
  387. package/src/kernel/live-http.test.ts +6 -7
  388. package/src/kernel/live-http.ts +6 -2
  389. package/src/kernel/live-resume.test.ts +1 -1
  390. package/src/kernel/mcp-tool.test.ts +3 -3
  391. package/src/kernel/on.ts +98 -4
  392. package/src/kernel/pipeline.test.ts +12 -15
  393. package/src/kernel/plugin-elements.test.ts +1 -1
  394. package/src/kernel/stamp-http.test.ts +3 -3
  395. package/src/kernel/triggers.ts +210 -95
  396. package/src/kernel-entry.ts +0 -1
  397. package/src/manifest/diff.ts +11 -1
  398. package/src/manifest/fixtures/skyport.excerpt.json +1 -1
  399. package/src/manifest/types.ts +46 -2
  400. package/src/mcp/docs-index.ts +3 -3
  401. package/src/mcp/docs-mcp.test.ts +2 -2
  402. package/src/mcp/docs-tools.ts +1 -1
  403. package/src/okid.test.ts +56 -0
  404. package/src/okid.ts +55 -11
  405. package/src/plugins/anonymous.ts +8 -4
  406. package/src/plugins/auth/shared.ts +27 -3
  407. package/src/plugins/auth-delivery.mailpit.integration.test.ts +1 -1
  408. package/src/plugins/auth-methods.security.test.ts +561 -23
  409. package/src/plugins/config-source.ts +1 -1
  410. package/src/plugins/csrf.test.ts +65 -0
  411. package/src/plugins/index.ts +2 -0
  412. package/src/plugins/magic-link.ts +36 -16
  413. package/src/plugins/oauth/shared.ts +3 -1
  414. package/src/plugins/oauth.ts +22 -14
  415. package/src/plugins/otp.ts +37 -21
  416. package/src/plugins/passkey-webauthn.ts +9 -6
  417. package/src/plugins/passkey.ts +98 -26
  418. package/src/plugins/pre-account-hijack.test.ts +223 -0
  419. package/src/plugins/two-factor.ts +466 -37
  420. package/src/plugins/username.ts +19 -7
  421. package/src/release/build-lib.ts +1 -0
  422. package/src/release/limits.ts +2 -2
  423. package/src/release/measure.ts +1 -1
  424. package/src/runs/collect.test.ts +0 -2
  425. package/src/runtime/json-code-block.test.ts +250 -20
  426. package/src/runtime/json-code-block.ts +1261 -41
  427. package/src/term.test.ts +1 -1
  428. package/src/test/create-test-app.test.ts +12 -12
  429. package/src/test/live-signals.test.ts +6 -6
  430. package/src/test/provisions.integration.test.ts +10 -14
  431. package/src/test/reset-element-registries.ts +1 -1
  432. package/src/test/tenant-isolation.test.ts +12 -6
  433. package/site/content/docs/deployment/docker-swarm.mdx +0 -164
  434. package/site/content/docs/deployment/docker.mdx +0 -227
  435. package/site/content/docs/deployment/index.mdx +0 -83
  436. package/site/content/docs/deployment/kubernetes.mdx +0 -176
  437. package/site/content/docs/deployment/meta.json +0 -5
  438. package/site/content/docs/deployment/reverse-proxy.mdx +0 -234
  439. package/site/content/docs/elements/ai.mdx +0 -385
  440. package/site/content/docs/elements/channel.mdx +0 -346
  441. package/site/content/docs/elements/clock.mdx +0 -244
  442. package/site/content/docs/elements/flow.mdx +0 -420
  443. package/site/content/docs/elements/gate.mdx +0 -436
  444. package/site/content/docs/elements/signal.mdx +0 -380
  445. package/site/content/docs/elements/store.mdx +0 -1099
  446. package/site/content/docs/elements/vault.mdx +0 -405
  447. package/site/content/docs/get-started/basic-usage.mdx +0 -173
  448. package/site/content/docs/get-started/index.mdx +0 -43
  449. package/site/content/docs/get-started/installation.mdx +0 -220
  450. package/site/content/docs/get-started/introduction.mdx +0 -132
  451. package/site/content/docs/get-started/meta.json +0 -13
  452. package/site/content/docs/get-started/project-structure.mdx +0 -925
  453. package/site/content/docs/get-started/testing.mdx +0 -328
  454. package/site/content/docs/get-started/why.mdx +0 -162
  455. package/site/content/docs/recipes/llama-cpp.mdx +0 -151
  456. package/site/content/docs/recipes/ollama.mdx +0 -142
  457. package/site/content/docs/recipes/sglang.mdx +0 -105
  458. package/site/content/docs/recipes/vllm.mdx +0 -106
  459. package/site/content/docs/reference/cli.md +0 -232
  460. package/site/content/docs/reference/client.mdx +0 -469
  461. package/site/content/docs/reference/security.md +0 -74
  462. package/src/cli/ai-setup/detect-ollama.ts +0 -213
  463. package/src/cli/ai-setup/recommend.test.ts +0 -225
  464. package/src/cli/ai-setup/recommend.ts +0 -225
  465. package/src/cli/dev-controls.test.ts +0 -122
  466. package/src/cli/dev-controls.ts +0 -164
  467. package/src/cli/tui/DevLive.tsx +0 -126
  468. package/src/console/ui-next/dist/assets/access-page-De7Lc2JC.js +0 -4
  469. package/src/console/ui-next/dist/assets/agent-disclosure-BP0Y0Sux.js +0 -1
  470. package/src/console/ui-next/dist/assets/cache-glyph-B5X-NM0-.js +0 -1
  471. package/src/console/ui-next/dist/assets/call-pii-button-ChAUiLo9.js +0 -1
  472. package/src/console/ui-next/dist/assets/collapsible-DGnOM2ph.js +0 -1
  473. package/src/console/ui-next/dist/assets/copy-inline-button-DBHhgHKP.js +0 -1
  474. package/src/console/ui-next/dist/assets/dagre.esm-ZwcdTuZZ.js +0 -1
  475. package/src/console/ui-next/dist/assets/detail-header-DhHM1iaZ.js +0 -1
  476. package/src/console/ui-next/dist/assets/dropdown-menu-_NjTEo5_.js +0 -1
  477. package/src/console/ui-next/dist/assets/duration-tone-sC3lGABz.js +0 -9
  478. package/src/console/ui-next/dist/assets/element-icons-BVXtRyd3.js +0 -1
  479. package/src/console/ui-next/dist/assets/explorer-empty-2uIhBu0_.js +0 -1
  480. package/src/console/ui-next/dist/assets/flows-page-RGy7VEA_.js +0 -1
  481. package/src/console/ui-next/dist/assets/highlighted-json-Awq7gYdu.js +0 -154
  482. package/src/console/ui-next/dist/assets/http-method-_UHM2ODJ.js +0 -1
  483. package/src/console/ui-next/dist/assets/index-Ck88Jmv8.css +0 -2
  484. package/src/console/ui-next/dist/assets/index-_rgpdVzo.js +0 -66
  485. package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +0 -1
  486. package/src/console/ui-next/dist/assets/observability-page-Ds6pcnh-.js +0 -4
  487. package/src/console/ui-next/dist/assets/preload-helper-oH4irX4C.js +0 -1
  488. package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +0 -1
  489. package/src/console/ui-next/dist/assets/react-dom-Bph1y7z7.js +0 -9
  490. package/src/console/ui-next/dist/assets/replica-lag-DKRrbvdo.js +0 -18
  491. package/src/console/ui-next/dist/assets/request-meta-DV0ywz7t.js +0 -1
  492. package/src/console/ui-next/dist/assets/shortcut-JQIZlWfm.js +0 -1
  493. package/src/console/ui-next/dist/assets/shortcut-keys-DKxNTe_m.js +0 -1
  494. package/src/console/ui-next/dist/assets/skeleton-D-czQJT6.js +0 -1
  495. package/src/console/ui-next/dist/assets/store-page-02xOiqIK.js +0 -41
  496. package/src/console/ui-next/dist/assets/trace-detail-sheet-B09O8rA6.js +0 -2
  497. package/src/console/ui-next/dist/assets/tree-expand-toggle-BtyhmWb4.js +0 -55
  498. package/src/console/ui-next/dist/assets/units-page-4rHOePuE.js +0 -1
  499. package/src/console/ui-next/dist/assets/useMutation-B8EO02Ej.js +0 -1
  500. package/src/console/ui-next/dist/assets/useRender-BE2A9BWC.js +0 -1
  501. package/src/console/ui-next/dist/assets/vault-page-DISPgxLM.js +0 -2
  502. package/src/console/ui-next/dist/assets/xyflow-D7n4g6go.js +0 -7
  503. package/src/console/ui-next/dist/assets/xyflow-DZ0Ws1xk.css +0 -1
  504. package/src/docker/ollama-pull.ts +0 -232
  505. package/src/docker/recipes/llama-cpp.ts +0 -298
  506. package/src/docker/recipes/ollama.ts +0 -44
  507. package/src/docker/recipes/sglang.ts +0 -55
  508. package/src/docker/recipes/vllm.ts +0 -44
  509. package/src/drivers/ai-ollama-tools.integration.test.ts +0 -109
  510. package/src/drivers/ai-ollama.integration.test.ts +0 -184
  511. package/src/drivers/ai-ollama.ts +0 -389
  512. package/src/drivers/ollama.ts +0 -14
@@ -1,346 +0,0 @@
1
- ---
2
- title: "Channel"
3
- description: "Reaching humans — email, SMS, WhatsApp, and push with consent, locale, receipts, and fallback chains built in."
4
- icon: "Mail"
5
- source: "docs/spec/unified-theory.md"
6
- ---
7
-
8
- Channel is how your app **reaches humans**: the order-confirmation email, the OTP text, the WhatsApp notification. Reaching a person is not the same problem as moving data between machines — it needs consent (did they opt out?), locale (which language?), receipts (did it land?), and fallback (first driver failed, try the next). Those physics are built into the element, so every flow gets them for free.
9
-
10
- <Callout title="The one rule">
11
- Sends go through `fx.send` and declared templates — never through a raw SMTP or provider client
12
- inside a flow. That is what makes consent, locale, and receipts unavoidable rather than optional.
13
- </Callout>
14
-
15
- ## Quick start
16
-
17
- <Steps>
18
-
19
- <Step>
20
- ### Declare a medium and a template
21
-
22
- A medium binder carries your defaults; a template names the message and types its data:
23
-
24
- ```typescript title="src/channels.ts"
25
- import { channel } from "okengine";
26
- import { z } from "zod";
27
-
28
- export const mail = channel.email({ from: "Provisions <no-reply@provisions.sa>" });
29
-
30
- export const orderConfirmed = mail.template("order-confirmed", {
31
- schema: z.object({ name: z.string(), orderId: z.string(), total: z.number() }),
32
- locales: ["en", "ar"],
33
- });
34
- ```
35
-
36
- </Step>
37
-
38
- <Step>
39
- ### Send it from a Flow
40
-
41
- One call — consent, locale resolution, and the receipt happen around it:
42
-
43
- ```typescript title="src/flows/orders/confirm.ts"
44
- do: async (input, fx) => {
45
- await fx.send(orderConfirmed, {
46
- to: input.email,
47
- data: { name: input.name, orderId: input.id, total: input.total },
48
- });
49
- };
50
- ```
51
-
52
- </Step>
53
-
54
- <Step>
55
- ### Read it in development
56
-
57
- Under `oke test` the `console` driver captures mail into an inbox instead of sending; under `oke
58
- dev` Compose runs **Mailpit**, a real SMTP catcher with a web UI — so you see the exact rendered
59
- message without ever touching a real mailbox.
60
-
61
- </Step>
62
-
63
- </Steps>
64
-
65
- ## Mediums and templates
66
-
67
- | Declaration | Produces |
68
- | ------------------------------ | ----------------------------------------------- |
69
- | `channel.email({ from })` | Email binder (default sender) |
70
- | `channel.sms({ sender })` | SMS binder (sender id) |
71
- | `channel.whatsapp()` | WhatsApp binder |
72
- | `channel.push()` | Push binder |
73
- | `binder.template(name, opts)` | Typed template bound to that medium |
74
- | `channel.template(name, opts)` | Medium-agnostic template (one body, any medium) |
75
-
76
- | Template option | Type | Meaning |
77
- | --------------- | --------------------- | ---------------------------------------------------------- |
78
- | `description` | string | Human title in the Console (falls back to the template id) |
79
- | `schema` | zod / Standard Schema | The data the template may reference — typed sends |
80
- | `locales` | string[] | Languages this template is rendered in |
81
-
82
- ## The human physics
83
-
84
- <ChannelPhysics />
85
-
86
- ### Consent and hard-bounce suppression
87
-
88
- Opt-out is first-class: a subject who opted out of a medium is **suppressed** — the send resolves without contacting the provider, and the receipt status is `suppressed/opted-out`.
89
-
90
- Hard bounces are protective too. After a provider outcome of `hard-bounce` is ingested, the address is auto-added to suppression; the **next** send to that address is blocked before any transport runs (`suppressed/prior-bounce`). Soft bounces and complaints do **not** auto-suppress.
91
-
92
- ```typescript
93
- // After a provider webhook (or Console ingest):
94
- runtime.ingestOutcome({
95
- messageId,
96
- state: "hard-bounce",
97
- to: user.email,
98
- medium: "email",
99
- });
100
- // Next fx.send / runtime.send to that address → ok: false, driverId: "suppression"
101
- ```
102
-
103
- **Known limit:** default suppression, consent, and receipt stores are **process-local memory**. Opt-out / hard-bounce / receipt on replica A is invisible to replica B. Boot prints a one-shot warning when those defaults are used.
104
-
105
- ### Inject shared stores (multi-instance workaround)
106
-
107
- Until a durable driver ships, inject your own stores that implement the same surfaces — boot skips the process-local warn when both `suppression` and `receipts` are provided:
108
-
109
- ```typescript
110
- import { createConsentStore, createReceiptLedger, createSuppressionStore } from "okengine";
111
-
112
- const consent = createConsentStore(); // or a wrapper over shared storage
113
- const suppression = createSuppressionStore({ consent });
114
- const receipts = createReceiptLedger();
115
-
116
- await app.boot({
117
- channel: { suppression, receipts /* templates, catalog, drivers… */ },
118
- });
119
- ```
120
-
121
- `consent` alone is wrapped into the default suppression store when `suppression` is omitted. Prefer passing `suppression` when you share bounce + opt-out state.
122
-
123
- ### Locale resolves, then the catalog
124
-
125
- `fx.send` picks a locale: explicit `locale` → `profileLocale` → first
126
- `Accept-Language` tag → `defaultLocale`. The body is the catalog entry for
127
- that exact key, else the default locale, else `en`.
128
-
129
- Locales come from `oke.config.ts` `i18n`. Catalog bodies use `{{field}}` — not
130
- ICU ([i18n](/docs/reference/i18n) / `fx.t` owns flow copy). Pass `locale` /
131
- `profileLocale` / `acceptLanguage` on `fx.send`, or omit them for `fx.locale`.
132
-
133
- ```typescript
134
- // Catalog (template → locale → body)
135
- {
136
- "order-confirmed": {
137
- en: { subject: "Order {{orderId}}", text: "Hello {{name}}" },
138
- ar: { subject: "طلب {{orderId}}", text: "مرحبًا {{name}}" },
139
- },
140
- }
141
- // locale: "ar" + data { name: "علي", orderId: "ORD-42" }
142
- // → subject "طلب ORD-42", text "مرحبًا علي"
143
- ```
144
-
145
- **Consequence:** `Accept-Language: ar-SA` does **not** match a catalog key named `ar`. Provide both keys, or send `locale: "ar"`.
146
-
147
- ### Fallback chains are same-medium
148
-
149
- `via` orders **driver ids** for the template's medium — first success wins,
150
- and **every** attempt is recorded:
151
-
152
- ```typescript
153
- await fx.send(orderConfirmed, {
154
- to: user.email,
155
- data: { name, orderId, total },
156
- via: ["smtp", "resend"],
157
- });
158
- // smtp provider-error → resend tried → receipt status "fallback"
159
- ```
160
-
161
- Permanent client errors do **not** advance the chain (invalid address, HTTP
162
- 400/401/403) — the same distinction OTP cross-medium delivery uses. A provider
163
- timeout or 5xx does failover.
164
-
165
- ### Receipt ledger
166
-
167
- Each send records attempts — driver, ok/error, timestamp, message id — so "did
168
- the user get it?" is a Console query. Dry run: kernel records a would-have-fired
169
- send and never opens a provider — no Channel receipt.
170
-
171
- | Status | Meaning |
172
- | --------------------------------- | --------------------------------------------- |
173
- | `sent` | Delivered on the first successful driver |
174
- | `fallback` | Earlier drivers failed; a later one succeeded |
175
- | `suppressed/opted-out` | Consent blocked the send (no provider call) |
176
- | `suppressed/prior-bounce` | Prior hard bounce blocked the send |
177
- | `blocked/invalid-address` | Permanent client error (no useful failover) |
178
- | `hard-bounce` / `soft-bounce` / … | Post-send outcomes via `ingestOutcome` |
179
-
180
- ### WhatsApp: template vs session (Known gap)
181
-
182
- WhatsApp Business allows free-form ("session") messages only inside a
183
- customer-initiated **24h** window; outside that window you must send a
184
- pre-approved **template**.
185
-
186
- OKE drivers choose payload shape by whether a template name is present on the
187
- message (`template` / `data.template` → template API; otherwise free text).
188
-
189
- There is **no** session-window clock or gate on the Channel runtime today.
190
- Sending free-form outside the window risks provider rejection or account
191
- action — handle that yourself until a real design ships.
192
-
193
- OTP WhatsApp paths often send free text via provider failover.
194
-
195
- ## Per-environment drivers
196
-
197
- Boot opens the email driver from `drivers.channel.email`, and appends an SMS
198
- driver when `drivers.channel.sms` is set (other than `console`). WhatsApp and
199
- push drivers are passed via `BootOptions.channel.drivers` when you need them.
200
-
201
- ```typescript title="oke.config.ts"
202
- drivers: {
203
- channel: {
204
- email: { dev: "smtp", test: "console", prod: "smtp" },
205
- // optional — omit until you need SMS at boot
206
- sms: { test: "console", prod: "taqnyat" },
207
- },
208
- },
209
- images: {
210
- channel: { email: "axllent/mailpit:v1.30.7" },
211
- },
212
- ```
213
-
214
- | Driver | Medium | Behavior |
215
- | -------------- | -------- | ------------------------------------------------------------------- |
216
- | `console` | any | Captures into a readable inbox — tests |
217
- | `smtp` | email | Real SMTP — Mailpit under `oke dev`, your provider in prod |
218
- | `resend` | email | Resend HTTP API (`RESEND_API_KEY`) |
219
- | `sndr` | email | SNDR HTTP API (`SNDR_API_KEY`) |
220
- | `taqnyat-mail` | email | Taqnyat Mail API (`TAQNYAT_MAIL_TOKEN` + `TAQNYAT_CAMPAIGN`) |
221
- | `taqnyat` | sms | Taqnyat SMS (`TAQNYAT_BEARER_TOKEN` + `TAQNYAT_SENDER`) |
222
- | `msegat` | sms | Msegat SMS (`MSEGAT_USERNAME` + `MSEGAT_API_KEY` + `MSEGAT_SENDER`) |
223
- | `unifonic` | sms | Unifonic el.cloud SMS (`UNIFONIC_APPSID`) |
224
- | `wa-cloud` | whatsapp | Meta WhatsApp Cloud API |
225
- | `webpush` | push | Web Push + VAPID (`createPushSender` → `WebPushTransport`) |
226
- | `fcm` | push | Firebase Cloud Messaging HTTP v1 |
227
-
228
- Email fallback uses ordered email transports; SMS with multiple SMS drivers uses
229
- the same failover path. Credentials for boot-opened drivers are listed under
230
- [Environment Variables](/docs/reference/environment-variables).
231
-
232
- ### Provider OTP (`fx.sendOtp` / `fx.verifyOtp`)
233
-
234
- Flows can send a provider-generated SMS code with `fx.sendOtp({ to, requestId, lang? })`
235
- and check it with `fx.verifyOtp({ to, requestId, code })` — Taqnyat's Verify API. Both are
236
- capability-gated `send` effects and dry-run safe. No `.plug()` required.
237
-
238
- They dispatch only when the bound SMS driver supports Verify (`taqnyat`); any other SMS
239
- driver fails loudly instead of silently falling back to a self-generated code.
240
-
241
- [OTP](/docs/plugins/otp) in provider mode is the full auth feature built on this same
242
- path (routes, sessions, rates). Custom shape? Call `fx.sendOtp` / `fx.verifyOtp` in
243
- your own flow — skipping the plugin does not drop the provider connection.
244
-
245
- `webpush` needs VAPID keys — open it yourself and include it in
246
- `BootOptions.channel.drivers` (boot does not open push from env):
247
-
248
- ```typescript
249
- import { openWebPushChannel } from "okengine/drivers/channel-webpush";
250
-
251
- openWebPushChannel({
252
- vapidPublicKey: process.env.VAPID_PUBLIC_KEY!,
253
- vapidPrivateKey: process.env.VAPID_PRIVATE_KEY!,
254
- vapidSubject: process.env.VAPID_SUBJECT,
255
- });
256
- ```
257
-
258
- ## Troubleshooting
259
-
260
- <Accordions>
261
- <Accordion title="I sent an email in dev but nothing arrived">
262
-
263
- Nothing _should_ arrive — the `console` driver captures mail instead of sending. Read the dev inbox, or run `oke dev` and open Mailpit's web UI to see the rendered message.
264
-
265
- </Accordion>
266
- <Accordion title="A user says they stopped receiving messages">
267
-
268
- Check consent first: if they opted out, sends to them are suppressed by design. After a hard bounce, later sends are `suppressed/prior-bounce` without contacting the provider. The Console shows the suppression on the receipt — it is a delivered-as-intended outcome, not a bug.
269
-
270
- </Accordion>
271
- <Accordion title="Opt-out on replica A, still sending on replica B">
272
-
273
- Default stores are process-local. Inject shared `suppression` + `receipts` on boot (see above), or run a single Channel consumer, until a durable driver ships.
274
-
275
- </Accordion>
276
- <Accordion title="Primary failed with invalid address and didn't try secondary">
277
-
278
- By design. Permanent client errors (invalid address, HTTP 400/401/403) do not failover — every provider would reject the same address. Provider outages and 5xx do advance the chain.
279
-
280
- </Accordion>
281
- <Accordion title="The Arabic version didn't render">
282
-
283
- The send still succeeds: the body falls back to your default locale, then `en`, when the catalog misses that exact key. Check that an Arabic entry exists under the same tag you send (`ar` vs `ar-SA`) and that the template lists it in `locales`. Catalog interpolation is `{{field}}` only — ICU plurals live in [i18n](/docs/reference/i18n).
284
-
285
- </Accordion>
286
- <Accordion title="How do I ingest SNDR / Unifonic delivery webhooks?">
287
-
288
- ```typescript
289
- import {
290
- parseSndrWebhook,
291
- verifySndrSignature,
292
- parseUnifonicWebhook,
293
- toDeliveryEvent,
294
- } from "okengine/channel";
295
- ```
296
-
297
- Verify the signature when the provider sends one, parse the body, then call
298
- `ingestOutcome` on your channel runtime with the bounce/complaint fields.
299
-
300
- Hard bounce auto-adds suppression. Console reads the receipt ledger — never
301
- raw webhook JSON.
302
-
303
- </Accordion>
304
- <Accordion title="How do I know which driver finally delivered?">
305
-
306
- The receipt keeps every attempt in order with its outcome. A send that succeeded on a later driver reports status `fallback` — you can see the full chain in Console → Flows (Traces).
307
-
308
- </Accordion>
309
- <Accordion title="How do I verify Web Push against a real browser?">
310
-
311
- Subscribe in a real browser with sently’s `examples/webpush-interop` page
312
- (paste the same `VAPID_PUBLIC_KEY`), copy the `PushSubscription` JSON, then:
313
-
314
- ```sh
315
- export VAPID_PUBLIC_KEY='…'
316
- export VAPID_PRIVATE_KEY='…'
317
- bun scripts/webpush-interop.ts /tmp/push-sub.json
318
- ```
319
-
320
- That path is Channel → `webpush` → sently `createPushSender` →
321
- `WebPushTransport`, and it prints the push-service HTTP response.
322
-
323
- Confirm a visible OS notification titled `okengine webpush interop`.
324
-
325
- </Accordion>
326
- </Accordions>
327
-
328
- ## Learn more
329
-
330
- - [Project structure](/docs/get-started/project-structure) — `flow({…})` names as `unit.export`; explicit `flow("unit.action")` still wins
331
- - [Flow](/docs/elements/flow) — `fx.send` inside `do`
332
- - [Environment Variables](/docs/reference/environment-variables) — Channel boot credentials
333
- - [Configuration](/docs/reference/configuration) — `drivers.channel.*` maps
334
- - [i18n](/docs/reference/i18n) — ICU / `fx.t` vs Channel `{{field}}` catalogs
335
- - [Signal](/docs/elements/signal) — machine-to-machine messaging, the other side of the line
336
-
337
- ## Next
338
-
339
- <Cards>
340
- <Card title="AI" description="Continue to AI." href="/docs/elements/ai" />
341
- <Card
342
- title="Introduction"
343
- description="Eight elements overview."
344
- href="/docs/get-started/introduction"
345
- />
346
- </Cards>
@@ -1,244 +0,0 @@
1
- ---
2
- title: "Clock"
3
- description: "Time — recurring schedules, intervals, and durable sleeps as first-class declarations, not a bolted-on cron library."
4
- icon: "Clock"
5
- source: "docs/spec/unified-theory.md"
6
- ---
7
-
8
- Clock is how your app deals with **time**: cleanup jobs that run every hour, reports due at 9am Riyadh time, a flow that pauses for seven days and wakes up even after a deploy. There is no separate scheduler to install — a schedule is a trigger, and the flow it fires is the same species as every other flow.
9
-
10
- <Callout title="The one rule">
11
- Flow code never calls `Date.now()` — it asks `fx.clock.now()`. Time is injected, which makes it
12
- deterministic in tests and auditable in traces.
13
- </Callout>
14
-
15
- ## Quick start
16
-
17
- <Steps>
18
-
19
- <Step>
20
- ### Fire a flow on an interval
21
-
22
- The simplest schedule is an anonymous interval trigger:
23
-
24
- ```typescript title="src/flows/links/purge.ts"
25
- import { on, flow, every } from "okengine";
26
-
27
- export const purgeOld = on(
28
- every("1h"),
29
- flow("links.purgeOld", {
30
- do: async (_, fx) => {
31
- const cutoff = fx.clock.ago("30d");
32
- await fx.store(db).delete(links).where(lt(links.createdAt, cutoff));
33
- },
34
- }),
35
- );
36
- ```
37
-
38
- </Step>
39
-
40
- <Step>
41
- ### Or declare a named schedule
42
-
43
- A named `clock()` shows up in the Console, supports cron expressions and timezones, and can be paused or retuned at runtime:
44
-
45
- ```typescript title="src/clocks.ts"
46
- import { clock } from "okengine";
47
-
48
- export const dailyReport = clock("daily-report", {
49
- cron: "0 9 * * *", // 09:00
50
- timezone: "Asia/Riyadh",
51
- overridable: true, // the Console may edit this schedule
52
- });
53
- ```
54
-
55
- Import the module before `oke()` — named clocks auto-register. No `oke({ clocks })` list.
56
-
57
- ```typescript title="src/flows/reports/daily.ts"
58
- export const sendDaily = on(
59
- dailyReport,
60
- flow("reports.sendDaily", {
61
- do: async (_, fx) => {
62
- /* … */
63
- },
64
- }),
65
- );
66
- ```
67
-
68
- </Step>
69
-
70
- <Step>
71
- ### Ask for time inside flows
72
-
73
- `fx.clock` is the only clock a flow knows. Instants stay epoch-ms; spans use the same
74
- duration strings as `every()` and `sleep()`:
75
-
76
- ```typescript
77
- do: async (input, fx) => {
78
- const cutoff = fx.clock.ago("30d");
79
- const expiresAt = fx.clock.fromNow("14d");
80
- const window = input.createdAt + fx.clock.duration("7d");
81
- await fx.clock.sleep("wait-for-payment", "7d");
82
- };
83
- ```
84
-
85
- </Step>
86
-
87
- </Steps>
88
-
89
- ## Two kinds of schedules
90
-
91
- <ClockSchedules />
92
-
93
- Both are triggers consumed with the same `on(trigger, flow)` — the flow underneath does not know the
94
- difference.
95
-
96
- ### `clock()` options
97
-
98
- | Option | Type | Default | Meaning |
99
- | ------------- | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------ |
100
- | `cron` | string | — | Five-field crontab (`m h dom mon dow`), plus steps, ranges, lists, names, `@hourly`, `@daily` (this or `every` required) |
101
- | `every` | string | — | Fixed interval: `"30s"` · `"10m"` · `"1h"` · `"7d"` |
102
- | `timezone` | string | `"UTC"` | IANA timezone for cron evaluation |
103
- | `overridable` | boolean | `false` | Allow the Console to **edit** the schedule (pause is separate) |
104
- | `description` | string | — | Human title in the Console (falls back to the clock name) |
105
- | `perTenant` | boolean | `false` | Expand to one `oke_crons` row per tenant (`{name}#{tenantId}`). Prefer `clock.perTenant(name, opts)` |
106
-
107
- <Callout title="Per-tenant clocks never tick the template">
108
- Bind `on(clock.perTenant("invoices", { every: "1h" }), flow(...))`. Rows are `invoices#acme`;
109
- tenant create/delete adds or orphans them. Fire stamps `fx.tenant.id`.
110
- </Callout>
111
-
112
- ### `fx.clock`
113
-
114
- | Call | Returns | Meaning |
115
- | -------------------- | ------- | ------------------------------------------------ |
116
- | `now()` | instant | Injected epoch-ms |
117
- | `ago("30d")` | instant | now − duration |
118
- | `fromNow("14d")` | instant | now + duration |
119
- | `duration("7d")` | span ms | Offset a stored instant (`createdAt + duration`) |
120
- | `sleep(label, "7d")` | — | Durable sleep |
121
-
122
- Same duration strings as `every()`: `"200ms"` · `"30s"` · `"2m"` · `"1h"` · `"7d"`. A `"d"` is 86_400_000 ms, not a calendar day. Unknown strings parse as `0`.
123
-
124
- ## Sleeping inside a flow
125
-
126
- <ClockSleep />
127
-
128
- `fx.clock.sleep(label, duration)` is a **durable** sleep: in a `durable: true` flow the wake time is
129
- journaled — with a shared journal (`drivers.journal: "postgres"`) the flow resumes after restarts and
130
- deploys; the `memory` default resumes within the same process only. The `label` names the step — the Console shows it on sleeping runs.
131
-
132
- In a non-durable flow the same call resolves immediately, so code reads identically in tests.
133
-
134
- Completed journal steps never re-run on resume — with a shared journal, a process killed mid-flow is
135
- reclaimed after its lease expires and continues at the next unfinished step.
136
-
137
- ## What the runtime guarantees
138
-
139
- <ClockCatchUp />
140
-
141
- | Guarantee | What it means |
142
- | ------------------ | -------------------------------------------------------------------------------------------------- |
143
- | Leader election | Instances that share one CronStore fire a given tick once (lease TTL, default 30s) |
144
- | Process identity | Boot mints one `instanceId` (`inst-<okid>`) for Clock, Journal, and the fleet registry |
145
- | Catch-up `"one"` | Health counts every missed slot; the runtime still fires **once** when overdue — not once per miss |
146
- | Reconciled at boot | Named clocks land in `oke_crons`; the scheduler reads the Store, not the source |
147
- | DST detection | Ambiguous local times get a Console warning — schedules are not rewritten or blocked |
148
-
149
- **Leader election needs a shared store.** The default in-memory CronStore is per process. Multi-replica
150
- exclusivity holds when every instance opens the same durable CronStore — `postgres` (SKIP LOCKED +
151
- lease reclaim, `dev`/`prod` default) or `file` (shared volume, single-host).
152
-
153
- **Catch-up example:** an hourly clock down for five hours reports `missedRuns: 5` and
154
- `catchUp: "one"`, then a single tick runs the handler once.
155
-
156
- ## DST warnings (detect only)
157
-
158
- Simple daily crons (`M H * * *`) in a DST-observing zone can hit a **gap** (spring forward — local time skipped) or an **overlap** (fall back — local time occurs twice).
159
-
160
- Detection attaches `dstAmbiguity` on the cron row and the Console shows it. `oke doctor` does not check DST. The scheduler does not rewrite the expression.
161
-
162
- **Consequence:** firing follows crontab. A spring gap runs at the next valid instant (shifted). A fall overlap fires **once** (first occurrence). Prefer UTC when a shifted spring fire would hurt.
163
-
164
- ## Per-environment drivers
165
-
166
- ```typescript title="oke.config.ts"
167
- drivers: {
168
- clock: { dev: "postgres", test: "frozen", prod: "postgres" },
169
- },
170
- ```
171
-
172
- | Driver | Behavior |
173
- | ---------- | --------------------------------------------------------------------------------------------- |
174
- | `memory` | In-process CronStore + timers — single process; lost on exit |
175
- | `postgres` | Postgres CronStore (`oke_crons`) — multi-host leader election via SKIP LOCKED + lease reclaim |
176
- | `file` | On-disk CronStore (`.oke/crons.json`) — multi-process on a shared volume (single-host) |
177
- | `frozen` | Deterministic test clock — time advances only when the test says so |
178
-
179
- `frozen` is why the no-`Date.now()` rule pays off: tests inject time travel through `fx.clock` and every flow obeys it automatically.
180
-
181
- ## Operating schedules from the Console
182
-
183
- The Console (`:6533` → `/flows`) lists clock-triggered flows in the cron / every bands.
184
-
185
- Named clocks still carry health — drift, overdue, missed runs + catch-up `"one"`, and the lease holder.
186
-
187
- `/observability` adds an Instances chip from `GET /console/instances` (alive ⇔ presence TTL not expired). A cron lease holder is not a fleet census.
188
-
189
- From there you can **pause** any cron, **wake early** a sleeping durable run, or **edit the schedule** when `overridable: true`. Edit on a locked clock throws `ScheduleNotOverridableError` (`clock "…" is not overridable`).
190
-
191
- ## Troubleshooting
192
-
193
- <Accordions>
194
- <Accordion title="I used Date.now() and tests are flaky">
195
-
196
- Replace it with `fx.clock.now()`. Direct time calls bypass the injected clock, so the `frozen` test driver cannot control them — that is exactly the class of bug the rule exists to remove.
197
-
198
- </Accordion>
199
- <Accordion title="The server was down and the cron didn't catch up">
200
-
201
- That is by design: catch-up policy is `"one"` — the schedule fires a single time after downtime, never a storm of one-run-per-missed-tick. Health still shows how many slots were missed. If you genuinely need backfill, trigger the flow from the Console.
202
-
203
- </Accordion>
204
- <Accordion title="Console won't let me edit a schedule">
205
-
206
- The clock was declared without `overridable: true`. Add it and redeploy — the restriction is deliberate, so only schedules you marked as operator-tunable can drift from code. Pause does not require `overridable`.
207
-
208
- </Accordion>
209
- <Accordion title="How do I run something once, later — not recurring?">
210
-
211
- Emit it from inside a flow with `fx.clock.sleep(label, duration)` before the work, in a `durable: true` flow. The sleep survives restarts on a shared journal, so "remind me in 7 days" is one line, not a cron row.
212
-
213
- </Accordion>
214
- <Accordion title="Two replicas both ran my cron">
215
-
216
- They are not sharing one CronStore. In-memory leases are per process. Point every replica at the same
217
- `postgres` CronStore (`DATABASE_URL`), or use `file` on a shared volume for single-host multi-process.
218
-
219
- </Accordion>
220
- <Accordion title="Observability says no fleet">
221
-
222
- The registry is off in `test` and when there is no shared SQL URL. `dev`/`prod` write `oke_instances` on the same Postgres as Clock. Unbound is empty — not `0 alive`.
223
-
224
- </Accordion>
225
- </Accordions>
226
-
227
- ## Learn more
228
-
229
- - [Project structure](/docs/get-started/project-structure) — clock flows in the tree get a name, never a URL
230
- - [Flow](/docs/elements/flow) — `on(trigger, flow)` and the `fx` surface
231
- - [Gate](/docs/elements/gate) — `clock.perTenant` when `gate.auth.tenant` is on
232
- - [fx · Runs](/docs/reference/fx#runs-observability-read) — native SLO checkers via `on(every(…))` + `fx.runs`
233
- - [Signal](/docs/elements/signal) — reacting to events instead of time
234
-
235
- ## Next
236
-
237
- <Cards>
238
- <Card title="Gate" description="Continue to Gate." href="/docs/elements/gate" />
239
- <Card
240
- title="Introduction"
241
- description="Eight elements overview."
242
- href="/docs/get-started/introduction"
243
- />
244
- </Cards>