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
@@ -185,6 +185,59 @@ export type ClientFetch = (input: string | URL | Request, init?: RequestInit) =>
185
185
  /** Header bag accepted by the client. */
186
186
  export type ClientHeaders = Record<string, string> | [string, string][];
187
187
 
188
+ /**
189
+ * Fetch credentials mode (avoids DOM `RequestCredentials` coupling).
190
+ * Use `"include"` for cookie sessions.
191
+ */
192
+ export type ClientCredentials = "include" | "omit" | "same-origin";
193
+
194
+ /**
195
+ * Raw request body accepted by the transport (avoids DOM `BodyInit` coupling).
196
+ * Mirrors what {@link createTransport} treats as non-JSON via `instanceof` checks.
197
+ */
198
+ export type ClientBodyInit =
199
+ | Blob
200
+ | FormData
201
+ | ArrayBuffer
202
+ | ArrayBufferView
203
+ | ReadableStream
204
+ | string
205
+ | URLSearchParams;
206
+
207
+ /**
208
+ * Bare transport Bearer hooks (no {@link AuthClient}).
209
+ * Prefer session options (`mode` / `persist` / `csrfConfigured`) so
210
+ * `createClient` attaches `api.auth`.
211
+ */
212
+ export interface ClientTransportAuth {
213
+ /** Current access token (Bearer), or null. */
214
+ readonly getToken: () => string | null | undefined | Promise<string | null | undefined>;
215
+ /** Obtain a new access token after 401. */
216
+ readonly refresh: () => Promise<string | null | undefined>;
217
+ }
218
+
219
+ /**
220
+ * Session orchestration options for `createClient({ auth })` → `api.auth`.
221
+ * Mirrors {@link CreateAuthClientOptions} plus optional SSR token overrides.
222
+ */
223
+ export interface ClientSessionAuthOptions {
224
+ readonly mode?: "bearer" | "cookie";
225
+ readonly persist?: "memory" | "sessionStorage" | "localStorage";
226
+ readonly storageKey?: string;
227
+ readonly tenantHeader?: string;
228
+ readonly csrfConfigured?: boolean;
229
+ readonly session?: import("./auth/session.ts").MemorySession;
230
+ readonly env?: {
231
+ readonly localStorage?: Storage;
232
+ readonly sessionStorage?: Storage;
233
+ readonly warn?: (message: string) => void;
234
+ };
235
+ /** SSR / custom: override Bearer getToken (cookie mode still uses credentials). */
236
+ readonly getToken?: ClientTransportAuth["getToken"];
237
+ /** SSR / custom: override refresh after 401. */
238
+ readonly refresh?: ClientTransportAuth["refresh"];
239
+ }
240
+
188
241
  /**
189
242
  * Options for {@link createClient}.
190
243
  */
@@ -205,15 +258,20 @@ export interface ClientOptions {
205
258
  readonly backoff?: number;
206
259
  };
207
260
  /**
208
- * Auth token + refresh. On HTTP 401, `refresh` runs once and the
209
- * request is retried with the new token.
261
+ * Session orchestration (`mode` / `csrfConfigured` / …) attaches `api.auth`,
262
+ * or bare `{ getToken, refresh }` transport hooks only.
210
263
  */
211
- readonly auth?: {
212
- /** Current access token (Bearer), or null. */
213
- readonly getToken: () => string | null | undefined | Promise<string | null | undefined>;
214
- /** Obtain a new access token after 401. */
215
- readonly refresh: () => Promise<string | null | undefined>;
216
- };
264
+ readonly auth?: ClientTransportAuth | ClientSessionAuthOptions;
265
+ /**
266
+ * Fetch credentials mode. Use `"include"` for cookie sessions
267
+ * (`gate.auth.cookies` + `createClient({ auth: { mode: "cookie" } })`).
268
+ */
269
+ readonly credentials?: ClientCredentials;
270
+ /**
271
+ * Optional AbortSignal for all calls from this client (per-call override
272
+ * not yet exposed on the proxy).
273
+ */
274
+ readonly signal?: AbortSignal;
217
275
  /**
218
276
  * Runtime REST table (optional). Keys are `unit.flow`.
219
277
  * When absent, the client uses `POST /_oke/{unit}/{flow}` unless
@@ -234,6 +292,14 @@ export interface ClientOptions {
234
292
  */
235
293
  export type ClientThenableIterable<T> = PromiseLike<T> & AsyncIterable<T>;
236
294
 
295
+ /**
296
+ * Per-call options on a Flow invoke (binary decode / abort).
297
+ */
298
+ export interface ClientCallOpts {
299
+ readonly response?: "json" | "blob" | "arrayBuffer";
300
+ readonly signal?: AbortSignal;
301
+ }
302
+
237
303
  /**
238
304
  * Call signature for one flow — thenable (one page) and async-iterable (walk).
239
305
  *
@@ -241,11 +307,25 @@ export type ClientThenableIterable<T> = PromiseLike<T> & AsyncIterable<T>;
241
307
  * @typeParam O - Output
242
308
  * @typeParam E - Errors
243
309
  */
244
- type ClientCallFn<I, O, E extends Record<string, unknown>> = [I] extends [void]
245
- ? () => ClientThenableIterable<ClientResult<O, E>>
246
- : Partial<I> extends I
247
- ? (input?: I) => ClientThenableIterable<ClientResult<O, E>>
248
- : (input: I) => ClientThenableIterable<ClientResult<O, E>>;
310
+ type IsAnyInput<T> = 0 extends 1 & T ? true : false;
311
+
312
+ type ClientCallFn<I, O, E extends Record<string, unknown>> =
313
+ IsAnyInput<I> extends true
314
+ ? {
315
+ (input?: I, opts?: ClientCallOpts): ClientThenableIterable<ClientResult<O, E>>;
316
+ }
317
+ : [I] extends [void]
318
+ ? {
319
+ (): ClientThenableIterable<ClientResult<O, E>>;
320
+ (opts: ClientCallOpts): ClientThenableIterable<ClientResult<O, E>>;
321
+ }
322
+ : Partial<I> extends I
323
+ ? {
324
+ (input?: I, opts?: ClientCallOpts): ClientThenableIterable<ClientResult<O, E>>;
325
+ }
326
+ : {
327
+ (input: I, opts?: ClientCallOpts): ClientThenableIterable<ClientResult<O, E>>;
328
+ };
249
329
 
250
330
  /**
251
331
  * Call signature for one flow. No `.pages()` — iterate the call or the page.
@@ -311,6 +391,12 @@ type ClientLiveOverloads = {
311
391
 
312
392
  type IsLiveContract<C> = C extends { readonly live: string } ? true : false;
313
393
 
394
+ type IsStreamOnlyContract<C> = C extends { readonly stream: true }
395
+ ? IsLiveContract<C> extends true
396
+ ? false
397
+ : true
398
+ : false;
399
+
314
400
  type ClientLiveFlowCall<I, O> = [I] extends [void]
315
401
  ? (handlers: LiveHandlers<O>) => LiveUnsubscribe
316
402
  : Partial<I> extends I
@@ -320,10 +406,18 @@ type ClientLiveFlowCall<I, O> = [I] extends [void]
320
406
  }
321
407
  : (input: I, handlers: LiveHandlers<O>) => LiveUnsubscribe;
322
408
 
409
+ type ClientStreamCall<I, O> = [I] extends [void]
410
+ ? (opts?: { readonly signal?: AbortSignal }) => AsyncIterable<O>
411
+ : Partial<I> extends I
412
+ ? (input?: I, opts?: { readonly signal?: AbortSignal }) => AsyncIterable<O>
413
+ : (input: I, opts?: { readonly signal?: AbortSignal }) => AsyncIterable<O>;
414
+
323
415
  type ClientCallFor<C> =
324
416
  IsLiveContract<C> extends true
325
417
  ? ClientLiveFlowCall<ContractIn<C>, ContractOut<C>>
326
- : ClientCall<ContractIn<C>, ContractOut<C>, ContractErrors<C>>;
418
+ : IsStreamOnlyContract<C> extends true
419
+ ? ClientStreamCall<ContractIn<C>, ContractOut<C>>
420
+ : ClientCall<ContractIn<C>, ContractOut<C>, ContractErrors<C>>;
327
421
 
328
422
  /**
329
423
  * Pull input from a contract shape (supports required or phantom-optional `in`).
@@ -366,6 +460,12 @@ export type ClientFromRoutes<R extends ClientRouteMap> = {
366
460
  */
367
461
  export type Client<App = never> = ClientFromRoutes<RoutesOf<ResolveApp<App>>> & {
368
462
  readonly live: ClientLive<ResolveApp<App>>;
463
+ /**
464
+ * Present when `createClient` was constructed with session `auth` options
465
+ * (`mode` / `persist` / `csrfConfigured` / …). Merged over the `auth` unit
466
+ * Flows at runtime (`api.auth.me` still works).
467
+ */
468
+ readonly auth?: import("./auth/create-auth-client.ts").AuthClient;
369
469
  };
370
470
 
371
471
  /**
@@ -388,6 +488,10 @@ export interface ClientDescriptor {
388
488
  readonly errors?: Readonly<Record<string, string>>;
389
489
  readonly method?: string;
390
490
  readonly path?: string;
491
+ readonly live?: string;
492
+ readonly stream?: true;
493
+ readonly matchKey?: readonly string[];
494
+ readonly gates?: readonly string[];
391
495
  };
392
496
  };
393
497
  };
@@ -18,11 +18,23 @@ export {
18
18
  type UseLiveQueryState,
19
19
  } from "./use-live-query.ts";
20
20
 
21
- import { useCallback, useEffect, useState, useSyncExternalStore } from "react";
22
- import type { MemorySession } from "../client/auth.ts";
21
+ import {
22
+ useCallback,
23
+ useEffect,
24
+ useMemo,
25
+ useState,
26
+ useSyncExternalStore,
27
+ type ReactNode,
28
+ } from "react";
29
+ import type {
30
+ AuthClient,
31
+ AuthorizeQuery,
32
+ AuthorizeResult,
33
+ } from "../client/auth/create-auth-client.ts";
34
+ import type { MemorySession, SessionUser } from "../client/auth/session.ts";
23
35
  import type { ClientLive, LiveInput, LiveSignalHandle } from "../client/types.ts";
24
36
 
25
- /** Minimal client surface for session reading. */
37
+ /** Minimal client surface for session reading (legacy). */
26
38
  export interface SessionClient {
27
39
  auth: {
28
40
  me: (input?: unknown) => Promise<{
@@ -31,6 +43,10 @@ export interface SessionClient {
31
43
  email: string;
32
44
  name: string;
33
45
  emailVerified?: boolean;
46
+ scopes?: readonly string[];
47
+ tenantId?: string | null;
48
+ apiKeyId?: string | null;
49
+ sessionFresh?: boolean;
34
50
  } | null;
35
51
  error: unknown;
36
52
  }>;
@@ -40,38 +56,57 @@ export interface SessionClient {
40
56
  /** Session snapshot returned by {@link useSession}. */
41
57
  export interface SessionState {
42
58
  readonly status: "loading" | "authenticated" | "unauthenticated";
43
- readonly user: {
44
- readonly userId: string;
45
- readonly email: string;
46
- readonly name: string;
47
- readonly emailVerified?: boolean;
48
- } | null;
59
+ readonly user: SessionUser | null;
60
+ /** UI-only scopes from last `auth.me` — Gate on Flows is real authz. */
61
+ readonly scopes: readonly string[];
49
62
  readonly accessToken: string | null;
50
63
  refresh(): Promise<void>;
51
- signOut(): void;
64
+ signOut(): void | Promise<void>;
65
+ /** UI-only — never a security boundary. */
66
+ hasScope(scope: string): boolean;
67
+ can(...scopes: string[]): boolean;
52
68
  }
53
69
 
54
70
  /**
55
- * React hook: read session via `auth.me` + optional {@link memorySession} helper.
71
+ * React hook over {@link AuthClient} (preferred) or legacy `api` + `memorySession`.
56
72
  *
57
- * @param api - Typed client from `createClient`
58
- * @param session - Optional memory session (Bearer storage)
73
+ * @param apiOrAuth - AuthClient or typed client with `auth.me`
74
+ * @param session - Optional memory session when using raw client
59
75
  */
60
- export function useSession(api: SessionClient, session?: MemorySession): SessionState {
76
+ export function useSession(
77
+ apiOrAuth: AuthClient | SessionClient,
78
+ session?: MemorySession,
79
+ ): SessionState {
80
+ const authClient = isAuthClient(apiOrAuth) ? apiOrAuth : null;
81
+ const api = authClient ? null : (apiOrAuth as SessionClient);
82
+ const bag = authClient?.session ?? session;
83
+
61
84
  const token = useSyncExternalStore(
62
85
  (onStoreChange) => {
63
- if (!session) return () => {};
64
- const id = setInterval(onStoreChange, 250);
65
- return () => clearInterval(id);
86
+ if (authClient) return authClient.subscribe(onStoreChange);
87
+ if (!bag?.subscribe) {
88
+ const id = setInterval(onStoreChange, 250);
89
+ return () => clearInterval(id);
90
+ }
91
+ return bag.subscribe(onStoreChange);
66
92
  },
67
- () => session?.accessToken ?? null,
68
- () => session?.accessToken ?? null,
93
+ () => bag?.accessToken ?? null,
94
+ () => bag?.accessToken ?? null,
69
95
  );
70
96
 
71
- const [user, setUser] = useState<SessionState["user"]>(null);
97
+ const [user, setUser] = useState<SessionUser | null>(authClient?.session.user ?? null);
72
98
  const [status, setStatus] = useState<SessionState["status"]>("loading");
73
99
 
74
100
  const refresh = useCallback(async () => {
101
+ if (authClient) {
102
+ setStatus("loading");
103
+ const next = await authClient.getSession();
104
+ setUser(next);
105
+ setStatus(next ? "authenticated" : "unauthenticated");
106
+ return;
107
+ }
108
+ if (!api) return;
109
+ // Legacy SessionClient path — cookie sessions always use AuthClient above.
75
110
  if (!token) {
76
111
  setUser(null);
77
112
  setStatus("unauthenticated");
@@ -84,29 +119,155 @@ export function useSession(api: SessionClient, session?: MemorySession): Session
84
119
  setStatus("unauthenticated");
85
120
  return;
86
121
  }
87
- setUser(data);
122
+ const next: SessionUser = {
123
+ userId: data.userId,
124
+ email: data.email,
125
+ name: data.name,
126
+ emailVerified: data.emailVerified,
127
+ scopes: data.scopes ?? [],
128
+ tenantId: data.tenantId ?? null,
129
+ apiKeyId: data.apiKeyId ?? null,
130
+ ...(data.sessionFresh !== undefined ? { sessionFresh: data.sessionFresh } : {}),
131
+ };
132
+ bag?.setUser(next);
133
+ setUser(next);
88
134
  setStatus("authenticated");
89
- }, [api, token]);
135
+ }, [api, authClient, bag, token]);
90
136
 
91
137
  useEffect(() => {
92
138
  void refresh();
93
139
  }, [refresh]);
94
140
 
95
141
  const signOut = useCallback(() => {
96
- session?.clear();
142
+ if (authClient) return authClient.signOut();
143
+ bag?.clear();
97
144
  setUser(null);
98
145
  setStatus("unauthenticated");
99
- }, [session]);
146
+ }, [authClient, bag]);
147
+
148
+ const scopes = user?.scopes ?? bag?.scopes ?? [];
100
149
 
101
150
  return {
102
151
  status,
103
152
  user,
153
+ scopes,
104
154
  accessToken: token,
105
155
  refresh,
106
156
  signOut,
157
+ hasScope: (scope: string) => scopes.includes(scope),
158
+ can: (...needed: string[]) => {
159
+ const have = new Set(scopes);
160
+ return needed.every((s) => have.has(s));
161
+ },
107
162
  };
108
163
  }
109
164
 
165
+ function isAuthClient(value: unknown): value is AuthClient {
166
+ return (
167
+ value !== null &&
168
+ typeof value === "object" &&
169
+ "signIn" in value &&
170
+ "getSession" in value &&
171
+ "clientOptions" in value
172
+ );
173
+ }
174
+
175
+ /**
176
+ * UI-only scope check hook.
177
+ *
178
+ * @param auth - AuthClient or session state scopes
179
+ * @param scope - Required scope
180
+ */
181
+ export function useScope(auth: AuthClient | SessionState, scope: string): boolean {
182
+ const scopes = useMemo(() => {
183
+ if (
184
+ "hasScope" in auth &&
185
+ typeof (auth as AuthClient).hasScope === "function" &&
186
+ "signIn" in auth
187
+ ) {
188
+ return (auth as AuthClient).session.user?.scopes ?? (auth as AuthClient).session.scopes;
189
+ }
190
+ return (auth as SessionState).scopes;
191
+ }, [auth]);
192
+ return scopes.includes(scope);
193
+ }
194
+
195
+ /**
196
+ * UI-only multi-scope check.
197
+ *
198
+ * @param auth - AuthClient or session state
199
+ * @param scopes - Required scopes (all)
200
+ */
201
+ export function useCan(auth: AuthClient | SessionState, ...scopes: string[]): boolean {
202
+ const have = useMemo(() => {
203
+ if ("can" in auth && "signIn" in auth) {
204
+ return new Set(
205
+ (auth as AuthClient).session.user?.scopes ?? (auth as AuthClient).session.scopes,
206
+ );
207
+ }
208
+ return new Set((auth as SessionState).scopes);
209
+ }, [auth]);
210
+ return scopes.every((s) => have.has(s));
211
+ }
212
+
213
+ /**
214
+ * UI-only authorize hook — prefers {@link AuthClient.authorize}.
215
+ * Gate on Flows remains the security boundary.
216
+ *
217
+ * @param auth - AuthClient
218
+ * @param query - all / any scopes
219
+ */
220
+ export function useAuthorize(auth: AuthClient, query: AuthorizeQuery): AuthorizeResult {
221
+ useSyncExternalStore(
222
+ (onStoreChange) => auth.subscribe(onStoreChange),
223
+ () => auth.session.user,
224
+ () => auth.session.user,
225
+ );
226
+ // Re-read on every render after subscribe notifies.
227
+ return auth.authorize(query);
228
+ }
229
+
230
+ /** Props for {@link Can} / {@link Cannot}. */
231
+ export interface CanProps {
232
+ readonly auth: AuthClient;
233
+ readonly all?: readonly string[];
234
+ readonly any?: readonly string[];
235
+ readonly children?: ReactNode;
236
+ readonly fallback?: ReactNode;
237
+ /** Rendered while authorize status is `loading` (default null). */
238
+ readonly loading?: ReactNode;
239
+ }
240
+
241
+ function authorizeQueryOf(props: CanProps): AuthorizeQuery {
242
+ if (props.all) return { all: props.all };
243
+ if (props.any) return { any: props.any };
244
+ return { all: [] };
245
+ }
246
+
247
+ /**
248
+ * UI-only — render children when authorize status is `allowed`.
249
+ * Not a security boundary.
250
+ */
251
+ export function Can(props: CanProps): ReactNode {
252
+ const query = authorizeQueryOf(props);
253
+ const result = useAuthorize(props.auth, query);
254
+ if (result.status === "loading") return props.loading ?? null;
255
+ if (result.status === "allowed") return props.children ?? null;
256
+ return props.fallback ?? null;
257
+ }
258
+
259
+ /**
260
+ * UI-only — render children when authorize status is not `allowed`
261
+ * (denied / unauthenticated). Loading uses `loading` prop.
262
+ */
263
+ export function Cannot(props: CanProps): ReactNode {
264
+ const query = authorizeQueryOf(props);
265
+ const result = useAuthorize(props.auth, query);
266
+ if (result.status === "loading") return props.loading ?? null;
267
+ if (result.status === "allowed") return props.fallback ?? null;
268
+ return props.children ?? null;
269
+ }
270
+
110
271
  /** Options for {@link useLive}. */
111
272
  export interface UseLiveOptions {
112
273
  readonly autoResubscribe?: boolean;
@@ -123,17 +123,27 @@ export interface UseLiveQueryState<Row> {
123
123
  * @param args.api - Typed client from `createClient`
124
124
  * @param args.listFlow - The resource's list call (`api.tasks.list`)
125
125
  * @param args.query - Same input as the list Flow (filters)
126
- * @param args.live - SSE route from `$routes`
126
+ * @param args.live - SSE route from `$routes` (optional when `listPath` given)
127
+ * @param args.listPath - List REST path; live defaults to `` `${listPath}/live` ``
127
128
  * @param args.options - PK/version extractors, refresh key, `enabled`
128
129
  */
129
130
  export function useLiveQuery<Row extends Record<string, unknown>, I = void>(args: {
130
131
  readonly api: object;
131
132
  readonly listFlow: ClientCall<I, Row[], Record<string, never>>;
132
133
  readonly query?: I;
133
- readonly live: LiveRouteContract;
134
+ readonly live?: LiveRouteContract;
135
+ /** List HTTP path — when `live` is omitted, SSE uses `GET ${listPath}/live`. */
136
+ readonly listPath?: string;
134
137
  readonly options?: UseLiveQueryOptions<Row>;
135
138
  }): UseLiveQueryState<Row> {
136
- const { api, listFlow, query, live, options } = args;
139
+ const { api, listFlow, query, options } = args;
140
+ const live: LiveRouteContract = args.live ?? {
141
+ method: "GET",
142
+ path: `${(args.listPath ?? "").replace(/\/$/, "")}/live`,
143
+ };
144
+ if (!args.live && !args.listPath) {
145
+ throw new Error("useLiveQuery requires `live` or `listPath` to derive the SSE route");
146
+ }
137
147
  const opts = options ?? {};
138
148
  const enabled = opts.enabled ?? true;
139
149
  const defaultIdOf = useCallback((row: Row) => String((row as Record<string, unknown>).id), []);
@@ -129,11 +129,14 @@ describe("typed error narrowing end-to-end", () => {
129
129
  const FlightFull = z.object({ seatsLeft: z.number() });
130
130
 
131
131
  on(
132
- http.post("/bookings").public(),
132
+ http
133
+ .post("/bookings", {
134
+ in: Booking,
135
+ out: z.object({ id: z.string() }),
136
+ errors: { FlightFull },
137
+ })
138
+ .public(),
133
139
  flow("bookings.create", {
134
- in: Booking,
135
- out: z.object({ id: z.string() }),
136
- errors: { FlightFull },
137
140
  do: (input: { flightId: string; seats: number }, fx) => {
138
141
  if (input.seats > 2) {
139
142
  return fx.fail("FlightFull", { seatsLeft: 2 });
@@ -0,0 +1,56 @@
1
+ /**
2
+ * fx.embed is a distinct effect kind from fx.ask — capability / Manifest honesty.
3
+ */
4
+
5
+ import { describe, expect, test } from "bun:test";
6
+ import { parseSync } from "oxc-parser";
7
+ import { inferEffects, type AstNode, type InferBinding } from "./effects-infer.ts";
8
+
9
+ function doNodeFrom(source: string): AstNode {
10
+ const wrapped = `const __fn = ${source}`;
11
+ const result = parseSync("t.ts", wrapped);
12
+ const program = result.program as unknown as AstNode & { body: AstNode[] };
13
+ const decl = program.body[0] as AstNode & {
14
+ declarations: Array<{ init: AstNode }>;
15
+ };
16
+ return decl.declarations[0]!.init;
17
+ }
18
+
19
+ describe("inferEffects — fx.embed ≠ fx.ask", () => {
20
+ test("fx.embed alone → effects.embeds, never asks", () => {
21
+ const doNode = doNodeFrom(`async (_input, fx) => {
22
+ return fx.embed("embedder", "hello");
23
+ }`);
24
+ const bindings = new Map<string, InferBinding>([
25
+ ["embedder", { kind: "unknown", ref: "embedder" }],
26
+ ]);
27
+ const inferred = inferEffects({ doNode, bindings, hasExplicitEffects: false });
28
+ expect(inferred.effects.embeds).toEqual(["embedder"]);
29
+ expect(inferred.effects.asks).toBeUndefined();
30
+ expect(inferred.nondeterministic).toBe(true);
31
+ });
32
+
33
+ test("fx.ask alone → effects.asks, never embeds", () => {
34
+ const doNode = doNodeFrom(`async (_input, fx) => {
35
+ return fx.ask("triage", { q: "x" });
36
+ }`);
37
+ const bindings = new Map<string, InferBinding>([["triage", { kind: "prompt", ref: "triage" }]]);
38
+ const inferred = inferEffects({ doNode, bindings, hasExplicitEffects: false });
39
+ expect(inferred.effects.asks).toEqual(["triage"]);
40
+ expect(inferred.effects.embeds).toBeUndefined();
41
+ });
42
+
43
+ test("both ask and embed keep separate bags", () => {
44
+ const doNode = doNodeFrom(`async (_input, fx) => {
45
+ await fx.embed("embedder", "t");
46
+ return fx.ask("rerank", { query: "q", docs: [] });
47
+ }`);
48
+ const inferred = inferEffects({
49
+ doNode,
50
+ bindings: new Map(),
51
+ hasExplicitEffects: false,
52
+ });
53
+ expect(inferred.effects.embeds).toEqual(["embedder"]);
54
+ expect(inferred.effects.asks).toEqual(["rerank"]);
55
+ });
56
+ });
@@ -0,0 +1,43 @@
1
+ /**
2
+ * fx.fetch is a distinct effect kind — capability / Manifest honesty.
3
+ */
4
+
5
+ import { describe, expect, test } from "bun:test";
6
+ import { inferEffects, type AstNode, type InferBinding } from "./effects-infer.ts";
7
+
8
+ function parseDo(src: string): AstNode {
9
+ // Minimal CallExpression-shaped tree for fx.fetch("https://…")
10
+ const match = /fx\.fetch\(\s*"([^"]+)"/.exec(src);
11
+ if (!match) throw new Error(`no fx.fetch in ${src}`);
12
+ return {
13
+ type: "BlockStatement",
14
+ body: [
15
+ {
16
+ type: "ExpressionStatement",
17
+ expression: {
18
+ type: "CallExpression",
19
+ callee: {
20
+ type: "MemberExpression",
21
+ object: { type: "Identifier", name: "fx" },
22
+ property: { type: "Identifier", name: "fetch" },
23
+ computed: false,
24
+ },
25
+ arguments: [{ type: "Literal", value: match[1] }],
26
+ },
27
+ },
28
+ ],
29
+ } as AstNode;
30
+ }
31
+
32
+ describe("inferEffects — fx.fetch → effects.fetches", () => {
33
+ test("fx.fetch URL literal → host in fetches", () => {
34
+ const inferred = inferEffects({
35
+ doNode: parseDo(`async (fx) => { await fx.fetch("https://api.stripe.com/v1"); }`),
36
+ bindings: new Map<string, InferBinding>(),
37
+ hasExplicitEffects: false,
38
+ });
39
+ expect(inferred.effects.fetches).toEqual(["api.stripe.com"]);
40
+ expect(inferred.effects.calls).toBeUndefined();
41
+ expect(inferred.effects.sends).toBeUndefined();
42
+ });
43
+ });