okengine 0.18.5 → 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 (510) 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/llms-txt.mdx +1 -1
  5. package/site/content/docs/ai/meta.json +1 -1
  6. package/site/content/docs/ai/skills.mdx +4 -1
  7. package/site/content/docs/ai/try-it.mdx +58 -0
  8. package/site/content/docs/client/auth.mdx +208 -0
  9. package/site/content/docs/client/calling.mdx +451 -0
  10. package/site/content/docs/client/index.mdx +209 -0
  11. package/site/content/docs/client/live.mdx +234 -0
  12. package/site/content/docs/client/meta.json +5 -0
  13. package/site/content/docs/client/react.mdx +249 -0
  14. package/site/content/docs/elements/ai/agents.mdx +232 -0
  15. package/site/content/docs/elements/ai/index.mdx +341 -0
  16. package/site/content/docs/elements/ai/mcp.mdx +275 -0
  17. package/site/content/docs/elements/ai/meta.json +5 -0
  18. package/site/content/docs/elements/ai/models.mdx +283 -0
  19. package/site/content/docs/elements/ai/prompts.mdx +256 -0
  20. package/site/content/docs/elements/channel/email.mdx +266 -0
  21. package/site/content/docs/elements/channel/index.mdx +344 -0
  22. package/site/content/docs/elements/channel/meta.json +5 -0
  23. package/site/content/docs/elements/channel/push.mdx +218 -0
  24. package/site/content/docs/elements/channel/receipts.mdx +206 -0
  25. package/site/content/docs/elements/channel/sms.mdx +264 -0
  26. package/site/content/docs/elements/channel/whatsapp.mdx +220 -0
  27. package/site/content/docs/elements/clock/index.mdx +300 -0
  28. package/site/content/docs/elements/clock/meta.json +5 -0
  29. package/site/content/docs/elements/clock/schedules.mdx +508 -0
  30. package/site/content/docs/elements/clock/sleep.mdx +445 -0
  31. package/site/content/docs/elements/flow/consumers.mdx +707 -0
  32. package/site/content/docs/elements/flow/http.mdx +1080 -0
  33. package/site/content/docs/elements/flow/index.mdx +618 -0
  34. package/site/content/docs/elements/flow/meta.json +5 -0
  35. package/site/content/docs/elements/flow/routing.mdx +559 -0
  36. package/site/content/docs/elements/flow/workflows.mdx +640 -0
  37. package/site/content/docs/elements/gate/auth.mdx +371 -0
  38. package/site/content/docs/elements/gate/authorization.mdx +298 -0
  39. package/site/content/docs/elements/gate/index.mdx +418 -0
  40. package/site/content/docs/elements/gate/meta.json +5 -0
  41. package/site/content/docs/elements/gate/rate-limits.mdx +365 -0
  42. package/site/content/docs/elements/gate/rls.mdx +303 -0
  43. package/site/content/docs/elements/gate/tenancy.mdx +372 -0
  44. package/site/content/docs/elements/index.mdx +20 -10
  45. package/site/content/docs/elements/signal/broadcast.mdx +517 -0
  46. package/site/content/docs/elements/signal/index.mdx +352 -0
  47. package/site/content/docs/elements/signal/live.mdx +590 -0
  48. package/site/content/docs/elements/signal/meta.json +5 -0
  49. package/site/content/docs/elements/signal/once.mdx +596 -0
  50. package/site/content/docs/elements/store/files.mdx +659 -0
  51. package/site/content/docs/elements/store/index.mdx +310 -0
  52. package/site/content/docs/elements/store/kv.mdx +629 -0
  53. package/site/content/docs/elements/store/meta.json +5 -0
  54. package/site/content/docs/elements/store/search.mdx +946 -0
  55. package/site/content/docs/elements/store/sql.mdx +937 -0
  56. package/site/content/docs/elements/vault/config.mdx +256 -0
  57. package/site/content/docs/elements/vault/index.mdx +356 -0
  58. package/site/content/docs/elements/vault/meta.json +5 -0
  59. package/site/content/docs/elements/vault/rotation.mdx +273 -0
  60. package/site/content/docs/elements/vault/secrets.mdx +285 -0
  61. package/site/content/docs/index.mdx +63 -36
  62. package/site/content/docs/meta.json +8 -7
  63. package/site/content/docs/plugins/cors.mdx +2 -1
  64. package/site/content/docs/plugins/csrf.mdx +4 -1
  65. package/site/content/docs/plugins/headers.mdx +2 -1
  66. package/site/content/docs/plugins/ip-allowlist.mdx +2 -1
  67. package/site/content/docs/plugins/magic-link.mdx +6 -0
  68. package/site/content/docs/plugins/maintenance-mode.mdx +2 -1
  69. package/site/content/docs/plugins/oauth.mdx +1 -1
  70. package/site/content/docs/plugins/otp.mdx +12 -1
  71. package/site/content/docs/plugins/passkey.mdx +39 -15
  72. package/site/content/docs/plugins/two-factor.mdx +91 -34
  73. package/site/content/docs/plugins/username.mdx +1 -1
  74. package/site/content/docs/recipes/caddy.mdx +3 -3
  75. package/site/content/docs/recipes/index.mdx +18 -28
  76. package/site/content/docs/recipes/mailpit.mdx +1 -1
  77. package/site/content/docs/recipes/meilisearch.mdx +3 -3
  78. package/site/content/docs/recipes/meta.json +1 -4
  79. package/site/content/docs/recipes/nginx.mdx +2 -2
  80. package/site/content/docs/recipes/openrouter.mdx +283 -0
  81. package/site/content/docs/recipes/pgdog.mdx +3 -3
  82. package/site/content/docs/recipes/postgres.mdx +1 -2
  83. package/site/content/docs/recipes/rustfs.mdx +2 -2
  84. package/site/content/docs/recipes/traefik.mdx +3 -3
  85. package/site/content/docs/reference/cli.mdx +231 -0
  86. package/site/content/docs/reference/configuration.mdx +30 -10
  87. package/site/content/docs/reference/environment-variables.mdx +93 -20
  88. package/site/content/docs/reference/errors.mdx +132 -29
  89. package/site/content/docs/reference/fx.mdx +137 -73
  90. package/site/content/docs/reference/index.mdx +13 -21
  91. package/site/content/docs/reference/meta.json +4 -5
  92. package/site/content/docs/reference/okid.mdx +42 -17
  93. package/site/content/docs/reference/plugins.mdx +4 -3
  94. package/site/content/docs/reference/security.mdx +197 -0
  95. package/site/content/docs/understand/meta.json +5 -0
  96. package/site/content/docs/understand/the-anatomy.mdx +132 -0
  97. package/site/content/docs/understand/the-model.mdx +32 -0
  98. package/site/content/docs/understand/the-problem.mdx +74 -0
  99. package/site/content/docs/understand/the-vocabulary.mdx +26 -0
  100. package/src/auth/api-keys.ts +2 -1
  101. package/src/auth/bindings.ts +64 -16
  102. package/src/auth/gate-auth.test.ts +6 -1
  103. package/src/auth/identity.ts +152 -5
  104. package/src/auth/index.ts +29 -0
  105. package/src/auth/invites.ts +2 -1
  106. package/src/auth/method-context.ts +41 -0
  107. package/src/auth/oauth-as/crypto.ts +2 -1
  108. package/src/auth/oauth-as/stores.ts +2 -1
  109. package/src/auth/operator.ts +2 -1
  110. package/src/auth/sessions-jwt.test.ts +77 -0
  111. package/src/auth/sessions.ts +20 -4
  112. package/src/auth/tenants.ts +3 -2
  113. package/src/auth/two-factor-challenge.test.ts +61 -0
  114. package/src/auth/two-factor-challenge.ts +284 -0
  115. package/src/auth/verification.ts +5 -0
  116. package/src/bench/REPORT.md +49 -0
  117. package/src/bench/g03-signal-once.bench.ts +1 -1
  118. package/src/bench/g10-observability-contention.bench.ts +1 -1
  119. package/src/bench/g17-hybrid-search.bench.ts +572 -0
  120. package/src/bench/load-app.ts +2 -10
  121. package/src/cli/ai-setup/ai-setup.test.ts +331 -42
  122. package/src/cli/ai-setup/apply.ts +294 -52
  123. package/src/cli/ai-setup/catalog.ts +238 -1342
  124. package/src/cli/ai-setup/index.ts +14 -99
  125. package/src/cli/ai-setup/prompts.ts +60 -443
  126. package/src/cli/ask-seed.test.ts +96 -0
  127. package/src/cli/ask-seed.ts +82 -0
  128. package/src/cli/ask-vault-gaps.test.ts +84 -0
  129. package/src/cli/ask-vault-gaps.ts +146 -0
  130. package/src/cli/build.ts +2 -2
  131. package/src/cli/client-add.test.ts +26 -1
  132. package/src/cli/client-add.ts +106 -27
  133. package/src/cli/db-seed.ts +2 -0
  134. package/src/cli/db.ts +150 -11
  135. package/src/cli/dev.ts +119 -211
  136. package/src/cli/docker-clean.ts +2 -2
  137. package/src/cli/docker-cli.test.ts +1 -1
  138. package/src/cli/doctor-diff.ts +4 -2
  139. package/src/cli/doctor-pii.test.ts +1 -1
  140. package/src/cli/doctor.ts +14 -1
  141. package/src/cli/hero-meta.test.ts +4 -2
  142. package/src/cli/load-config.images.test.ts +8 -10
  143. package/src/cli/load-config.ts +1 -1
  144. package/src/cli/registry.ts +22 -10
  145. package/src/cli/replay.ts +3 -1
  146. package/src/cli/start.ts +1 -1
  147. package/src/cli/tui/keys.ts +2 -3
  148. package/src/client/auth/cookies.ts +74 -0
  149. package/src/client/auth/create-auth-client.ts +636 -0
  150. package/src/client/auth/denials.ts +99 -0
  151. package/src/client/auth/session.ts +283 -0
  152. package/src/client/auth.test.ts +251 -0
  153. package/src/client/auth.ts +39 -114
  154. package/src/client/create-with-session.ts +245 -0
  155. package/src/client/create.ts +51 -5
  156. package/src/client/index.ts +11 -1
  157. package/src/client/live.ts +11 -79
  158. package/src/client/notes-contract.test.ts +5 -10
  159. package/src/client/sse.ts +137 -0
  160. package/src/client/stream.ts +147 -0
  161. package/src/client/transport.test.ts +32 -0
  162. package/src/client/transport.ts +123 -26
  163. package/src/client/types.ts +118 -14
  164. package/src/client-react/index.ts +185 -24
  165. package/src/client-react/use-live-query.ts +13 -3
  166. package/src/compiler/aot.test.ts +7 -4
  167. package/src/compiler/effects-embed.test.ts +56 -0
  168. package/src/compiler/effects-fetch.test.ts +43 -0
  169. package/src/compiler/effects-infer.ts +58 -2
  170. package/src/compiler/extract.test.ts +257 -43
  171. package/src/compiler/extract.ts +566 -48
  172. package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +7 -4
  173. package/src/compiler/fixtures/skyport/src/flows/bookings/signals.ts +2 -8
  174. package/src/compiler/fixtures/skyport.expected.json +4 -4
  175. package/src/compiler/fixtures/triggers/five-triggers.ts +14 -10
  176. package/src/compiler/response.ts +2 -2
  177. package/src/compiler/search-writer-isolation.test.ts +40 -0
  178. package/src/config/index.ts +11 -0
  179. package/src/console/server/app.ts +17 -23
  180. package/src/console/server/bind.ts +4 -0
  181. package/src/console/server/console.test.ts +2 -2
  182. package/src/console/server/flows-invoke.test.ts +50 -34
  183. package/src/console/server/flows.ts +436 -255
  184. package/src/console/server/runs-ingest.test.ts +6 -3
  185. package/src/console/server/serve.ts +1 -1
  186. package/src/console/server/signals.test.ts +1 -5
  187. package/src/console/server/signals.ts +8 -6
  188. package/src/console/server/state.ts +6 -1
  189. package/src/console/server/store.ts +1 -1
  190. package/src/console/ui-next/dist/assets/FileExportIcon-Ck-5od4R.js +1 -0
  191. package/src/console/ui-next/dist/assets/MoreHorizontalCircle01Icon-gMNGsE37.js +1 -0
  192. package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +1 -0
  193. package/src/console/ui-next/dist/assets/UnavailableIcon-D9cvHVPr.js +1 -0
  194. package/src/console/ui-next/dist/assets/UserIcon-DaE2PB5_.js +1 -0
  195. package/src/console/ui-next/dist/assets/access-page-DFeymU07.js +4 -0
  196. package/src/console/ui-next/dist/assets/agent-disclosure-U1rdfblp.js +1 -0
  197. package/src/console/ui-next/dist/assets/cache-glyph-BeFJeqBG.js +1 -0
  198. package/src/console/ui-next/dist/assets/call-pii-button-CVAONPii.js +1 -0
  199. package/src/console/ui-next/dist/assets/collapsible-D2A6NJ-3.js +1 -0
  200. package/src/console/ui-next/dist/assets/copy-inline-button-CAYD18cr.js +1 -0
  201. package/src/console/ui-next/dist/assets/dagre.esm-B1_XeuLP.js +1 -0
  202. package/src/console/ui-next/dist/assets/detail-header-DVWNjWjg.js +1 -0
  203. package/src/console/ui-next/dist/assets/dropdown-menu-4h2LOVXM.js +1 -0
  204. package/src/console/ui-next/dist/assets/duration-tone-Cgk_h5ja.js +9 -0
  205. package/src/console/ui-next/dist/assets/element-icons-BI8cJgdh.js +1 -0
  206. package/src/console/ui-next/dist/assets/explorer-empty-CJs5A-wm.js +1 -0
  207. package/src/console/ui-next/dist/assets/flows-page-Dss7941e.js +1 -0
  208. package/src/console/ui-next/dist/assets/highlighted-json-MYZQtRnw.js +154 -0
  209. package/src/console/ui-next/dist/assets/http-method-Jrh39p7A.js +1 -0
  210. package/src/console/ui-next/dist/assets/index-D0zS5rKO.css +2 -0
  211. package/src/console/ui-next/dist/assets/index-DXP2dBIF.js +63 -0
  212. package/src/console/ui-next/dist/assets/observability-page-HvolXxTI.js +4 -0
  213. package/src/console/ui-next/dist/assets/react-dom-Ddte4I-Q.js +9 -0
  214. package/src/console/ui-next/dist/assets/replica-lag-CSh2dzrb.js +18 -0
  215. package/src/console/ui-next/dist/assets/request-meta-BatF8KrK.js +1 -0
  216. package/src/console/ui-next/dist/assets/shortcut-keys-3ILd8oGn.js +1 -0
  217. package/src/console/ui-next/dist/assets/{sql-BskegiFM.js → sql-BsFa4tDR.js} +1 -1
  218. package/src/console/ui-next/dist/assets/store-page-eiKiHnNe.js +41 -0
  219. package/src/console/ui-next/dist/assets/trace-detail-sheet-CZkMeKS-.js +2 -0
  220. package/src/console/ui-next/dist/assets/tree-expand-toggle-CW8y5A2h.js +55 -0
  221. package/src/console/ui-next/dist/assets/units-page-B_RWJrEO.js +1 -0
  222. package/src/console/ui-next/dist/assets/{use-vault-list-uk4WVboC.js → use-vault-list-CT4-gajj.js} +1 -1
  223. package/src/console/ui-next/dist/assets/vault-page-CWrg-A68.js +2 -0
  224. package/src/console/ui-next/dist/assets/xyflow-CSyC6ryz.css +1 -0
  225. package/src/console/ui-next/dist/assets/xyflow-yApv7D4e.js +7 -0
  226. package/src/console/ui-next/dist/index.html +6 -13
  227. package/src/console/ui-next/seed-invoke-host.ts +21 -18
  228. package/src/console/ui-next/src/client.ts +7 -2
  229. package/src/console/ui-next/src/features/flows/graph/element-map.ts +2 -0
  230. package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +12 -2
  231. package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +4 -0
  232. package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +8 -0
  233. package/src/console/ui-next/src/features/flows/traces/waterfall-bars.ts +4 -0
  234. package/src/console/ui-next/src/features/flows/traces/waterfall-tooltip.ts +9 -3
  235. package/src/console/ui-next/src/features/store/detail/reveal-cell.tsx +7 -11
  236. package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +2 -2
  237. package/src/console/ui-next/src/features/store/lib/fields-from-table.ts +2 -0
  238. package/src/console/ui-next/src/features/store/lib/grid-model.test.ts +25 -1
  239. package/src/console/ui-next/src/features/store/lib/grid-model.ts +36 -1
  240. package/src/console/ui-next/src/features/store/query/query-results.tsx +3 -3
  241. package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +2 -0
  242. package/src/console/ui-next/src/features/units/lib/call-read-safe.ts +3 -1
  243. package/src/console/ui-next/src/features/units/lib/fields-from-schema.ts +6 -0
  244. package/src/docker/ai-model-status.test.ts +1 -12
  245. package/src/docker/ai-model-status.ts +10 -38
  246. package/src/docker/compose-up.test.ts +100 -0
  247. package/src/docker/compose-up.ts +292 -0
  248. package/src/docker/compose.ts +4 -14
  249. package/src/docker/derive.ts +9 -34
  250. package/src/docker/docker.test.ts +16 -261
  251. package/src/docker/images-config.test.ts +13 -25
  252. package/src/docker/index.ts +1 -24
  253. package/src/docker/recipes/index.ts +0 -23
  254. package/src/docker/recipes/pgdog.ts +6 -4
  255. package/src/docker/stack-id.test.ts +2 -2
  256. package/src/docker/stack-id.ts +0 -1
  257. package/src/docker/types.ts +2 -2
  258. package/src/drivers/ai-anthropic.ts +10 -0
  259. package/src/drivers/ai-openai-compatible.ts +34 -7
  260. package/src/drivers/ai-providers.test.ts +0 -107
  261. package/src/drivers/ai-stream.test.ts +0 -34
  262. package/src/drivers/ai-types.ts +17 -8
  263. package/src/drivers/channel-fcm.ts +6 -1
  264. package/src/drivers/channel-msegat.ts +6 -1
  265. package/src/drivers/channel-resend.ts +5 -1
  266. package/src/drivers/channel-smtp.ts +5 -1
  267. package/src/drivers/channel-sndr.ts +10 -1
  268. package/src/drivers/channel-taqnyat-mail.ts +5 -1
  269. package/src/drivers/channel-taqnyat-whatsapp.ts +6 -1
  270. package/src/drivers/channel-taqnyat.ts +6 -1
  271. package/src/drivers/channel-types.ts +10 -0
  272. package/src/drivers/channel-unifonic.ts +6 -1
  273. package/src/drivers/channel-wa-cloud.ts +6 -1
  274. package/src/drivers/channel-webpush.ts +6 -1
  275. package/src/drivers/external.ts +33 -0
  276. package/src/drivers/index.ts +0 -10
  277. package/src/drivers/meilisearch.ts +6 -0
  278. package/src/drivers/oauth-types.ts +4 -0
  279. package/src/drivers/postgres.test.ts +74 -3
  280. package/src/drivers/postgres.ts +175 -6
  281. package/src/drivers/signal-types.ts +5 -5
  282. package/src/drivers/types.ts +7 -0
  283. package/src/drivers/vault-types.ts +5 -0
  284. package/src/elements/ai/declare.ts +17 -4
  285. package/src/elements/ai/eval.ts +1 -1
  286. package/src/elements/ai/mcp-http.ts +1 -1
  287. package/src/elements/ai/pii.ts +1 -1
  288. package/src/elements/ai/providers.test.ts +289 -0
  289. package/src/elements/ai/providers.ts +169 -0
  290. package/src/elements/ai/runtime.ts +68 -5
  291. package/src/elements/ai/schema.ts +2 -2
  292. package/src/elements/ai.test.ts +7 -7
  293. package/src/elements/ai.ts +17 -1
  294. package/src/elements/channel/runtime.ts +105 -12
  295. package/src/elements/clock/cron-fields.test.ts +144 -0
  296. package/src/elements/clock/cron-fields.ts +185 -0
  297. package/src/elements/clock/declare.ts +247 -3
  298. package/src/elements/clock.ts +21 -1
  299. package/src/elements/index.ts +15 -0
  300. package/src/elements/signal/chaos-child.ts +1 -2
  301. package/src/elements/signal/declare.ts +65 -37
  302. package/src/elements/signal/delivery-modes.test.ts +11 -31
  303. package/src/elements/signal/dry-run-replay.test.ts +1 -7
  304. package/src/elements/signal/dry-run-write-isolation.test.ts +1 -7
  305. package/src/elements/signal/key-ordering.test.ts +6 -26
  306. package/src/elements/signal/lease-reclaim.test.ts +2 -6
  307. package/src/elements/signal/optional-emit.test.ts +6 -9
  308. package/src/elements/signal/order-lifecycle.test.ts +4 -17
  309. package/src/elements/signal/orphan-messages.test.ts +4 -15
  310. package/src/elements/signal/reconcile.test.ts +2 -2
  311. package/src/elements/signal/runtime.ts +1 -1
  312. package/src/elements/signal/schema-emit.test.ts +4 -8
  313. package/src/elements/signal.test.ts +24 -49
  314. package/src/elements/signal.ts +9 -2
  315. package/src/elements/store/domain-ddl.test.ts +1 -1
  316. package/src/elements/store/live-http.test.ts +9 -4
  317. package/src/elements/store/prepare-row.test.ts +123 -0
  318. package/src/elements/store/resource.ts +43 -12
  319. package/src/elements/store/schema-decl.ts +117 -0
  320. package/src/elements/store/search-backfill.ts +205 -0
  321. package/src/elements/store/search-bind.ts +66 -0
  322. package/src/elements/store/search-bm25.ts +78 -0
  323. package/src/elements/store/search-ddl.ts +156 -0
  324. package/src/elements/store/search-embed-flow.ts +141 -0
  325. package/src/elements/store/search-errors.ts +34 -0
  326. package/src/elements/store/search-fusion.ts +112 -0
  327. package/src/elements/store/search-lsh.ts +179 -0
  328. package/src/elements/store/search-runtime.ts +300 -0
  329. package/src/elements/store/search.test.ts +144 -0
  330. package/src/elements/store/sql-session.test.ts +1 -0
  331. package/src/elements/store/sql-session.ts +83 -3
  332. package/src/elements/store/table.ts +23 -2
  333. package/src/elements/store/upsert-app.test.ts +1 -3
  334. package/src/elements/store.ts +50 -0
  335. package/src/full.ts +4 -2
  336. package/src/http.ts +4 -1
  337. package/src/i18n/catalogs/ar.ts +14 -6
  338. package/src/i18n/catalogs/en.ts +14 -6
  339. package/src/index.ts +6 -1
  340. package/src/kernel/adopt-barrel-fresh.test.ts +7 -7
  341. package/src/kernel/adopt-routes.ts +9 -1
  342. package/src/kernel/app-auth.ts +5 -0
  343. package/src/kernel/app.ts +102 -9
  344. package/src/kernel/auto-cache.test.ts +6 -12
  345. package/src/kernel/auto-registry.test.ts +3 -3
  346. package/src/kernel/boot-bind/ai.test.ts +22 -19
  347. package/src/kernel/boot-bind/ai.ts +5 -43
  348. package/src/kernel/boot-bind/clock.ts +23 -5
  349. package/src/kernel/boot-bind/honor-config.test.ts +4 -4
  350. package/src/kernel/boot.test.ts +11 -6
  351. package/src/kernel/boot.ts +66 -16
  352. package/src/kernel/boundary-contract.ts +93 -0
  353. package/src/kernel/budget.test.ts +1 -1
  354. package/src/kernel/call.ts +89 -0
  355. package/src/kernel/capability.ts +6 -0
  356. package/src/kernel/client-descriptor.ts +123 -0
  357. package/src/kernel/clock-timezone.test.ts +80 -0
  358. package/src/kernel/correlation.test.ts +1 -1
  359. package/src/kernel/dry-run.ts +11 -8
  360. package/src/kernel/effects-stamping.test.ts +42 -9
  361. package/src/kernel/effects.test.ts +4 -2
  362. package/src/kernel/effects.ts +51 -9
  363. package/src/kernel/errors-channel.ts +17 -0
  364. package/src/kernel/errors-live-resume.ts +3 -2
  365. package/src/kernel/errors-tenant.ts +7 -4
  366. package/src/kernel/errors.registry-helpers.ts +96 -0
  367. package/src/kernel/errors.registry.test.ts +139 -25
  368. package/src/kernel/errors.ts +102 -23
  369. package/src/kernel/external-effects.test.ts +196 -0
  370. package/src/kernel/flow.test.ts +13 -4
  371. package/src/kernel/flow.ts +69 -71
  372. package/src/kernel/fx-ask-telemetry.test.ts +2 -2
  373. package/src/kernel/fx-dead-letters.test.ts +3 -3
  374. package/src/kernel/fx-fetch.ts +55 -0
  375. package/src/kernel/fx-live-stream.ts +2 -2
  376. package/src/kernel/fx-live.test.ts +8 -8
  377. package/src/kernel/fx.test.ts +17 -11
  378. package/src/kernel/fx.ts +180 -57
  379. package/src/kernel/horizontal-child.ts +1 -1
  380. package/src/kernel/http-query.test.ts +3 -6
  381. package/src/kernel/index.ts +13 -3
  382. package/src/kernel/instance-id.ts +1 -1
  383. package/src/kernel/instances.test.ts +40 -0
  384. package/src/kernel/instances.ts +47 -9
  385. package/src/kernel/live-http.test.ts +6 -7
  386. package/src/kernel/live-http.ts +6 -2
  387. package/src/kernel/live-resume.test.ts +1 -1
  388. package/src/kernel/mcp-tool.test.ts +3 -3
  389. package/src/kernel/on.ts +98 -4
  390. package/src/kernel/pipeline.test.ts +12 -15
  391. package/src/kernel/plugin-elements.test.ts +1 -1
  392. package/src/kernel/stamp-http.test.ts +3 -3
  393. package/src/kernel/triggers.ts +210 -95
  394. package/src/kernel-entry.ts +0 -1
  395. package/src/manifest/diff.ts +11 -1
  396. package/src/manifest/fixtures/skyport.excerpt.json +1 -1
  397. package/src/manifest/types.ts +46 -2
  398. package/src/mcp/docs-index.ts +3 -3
  399. package/src/mcp/docs-mcp.test.ts +2 -2
  400. package/src/mcp/docs-tools.ts +1 -1
  401. package/src/okid.test.ts +56 -0
  402. package/src/okid.ts +55 -11
  403. package/src/plugins/anonymous.ts +8 -4
  404. package/src/plugins/auth/shared.ts +27 -3
  405. package/src/plugins/auth-delivery.mailpit.integration.test.ts +1 -1
  406. package/src/plugins/auth-methods.security.test.ts +561 -23
  407. package/src/plugins/config-source.ts +1 -1
  408. package/src/plugins/csrf.test.ts +65 -0
  409. package/src/plugins/index.ts +2 -0
  410. package/src/plugins/magic-link.ts +36 -16
  411. package/src/plugins/oauth/shared.ts +3 -1
  412. package/src/plugins/oauth.ts +22 -14
  413. package/src/plugins/otp.ts +37 -21
  414. package/src/plugins/passkey-webauthn.ts +9 -6
  415. package/src/plugins/passkey.ts +98 -26
  416. package/src/plugins/pre-account-hijack.test.ts +223 -0
  417. package/src/plugins/two-factor.ts +466 -37
  418. package/src/plugins/username.ts +19 -7
  419. package/src/release/build-lib.ts +1 -0
  420. package/src/release/limits.ts +2 -2
  421. package/src/release/measure.ts +1 -1
  422. package/src/runs/collect.test.ts +0 -2
  423. package/src/runtime/json-code-block.test.ts +250 -20
  424. package/src/runtime/json-code-block.ts +1261 -41
  425. package/src/term.test.ts +1 -1
  426. package/src/test/create-test-app.test.ts +12 -12
  427. package/src/test/live-signals.test.ts +6 -6
  428. package/src/test/provisions.integration.test.ts +10 -14
  429. package/src/test/reset-element-registries.ts +1 -1
  430. package/src/test/tenant-isolation.test.ts +12 -6
  431. package/site/content/docs/deployment/docker-swarm.mdx +0 -164
  432. package/site/content/docs/deployment/docker.mdx +0 -227
  433. package/site/content/docs/deployment/index.mdx +0 -83
  434. package/site/content/docs/deployment/kubernetes.mdx +0 -176
  435. package/site/content/docs/deployment/meta.json +0 -5
  436. package/site/content/docs/deployment/reverse-proxy.mdx +0 -234
  437. package/site/content/docs/elements/ai.mdx +0 -385
  438. package/site/content/docs/elements/channel.mdx +0 -346
  439. package/site/content/docs/elements/clock.mdx +0 -244
  440. package/site/content/docs/elements/flow.mdx +0 -420
  441. package/site/content/docs/elements/gate.mdx +0 -436
  442. package/site/content/docs/elements/signal.mdx +0 -380
  443. package/site/content/docs/elements/store.mdx +0 -1099
  444. package/site/content/docs/elements/vault.mdx +0 -405
  445. package/site/content/docs/get-started/basic-usage.mdx +0 -173
  446. package/site/content/docs/get-started/index.mdx +0 -43
  447. package/site/content/docs/get-started/installation.mdx +0 -220
  448. package/site/content/docs/get-started/introduction.mdx +0 -144
  449. package/site/content/docs/get-started/meta.json +0 -13
  450. package/site/content/docs/get-started/project-structure.mdx +0 -925
  451. package/site/content/docs/get-started/testing.mdx +0 -328
  452. package/site/content/docs/get-started/why.mdx +0 -114
  453. package/site/content/docs/recipes/llama-cpp.mdx +0 -151
  454. package/site/content/docs/recipes/ollama.mdx +0 -142
  455. package/site/content/docs/recipes/sglang.mdx +0 -105
  456. package/site/content/docs/recipes/vllm.mdx +0 -106
  457. package/site/content/docs/reference/cli.md +0 -232
  458. package/site/content/docs/reference/client.mdx +0 -469
  459. package/site/content/docs/reference/security.md +0 -74
  460. package/src/cli/ai-setup/detect-ollama.ts +0 -213
  461. package/src/cli/ai-setup/recommend.test.ts +0 -225
  462. package/src/cli/ai-setup/recommend.ts +0 -225
  463. package/src/cli/dev-controls.test.ts +0 -122
  464. package/src/cli/dev-controls.ts +0 -164
  465. package/src/cli/tui/DevLive.tsx +0 -126
  466. package/src/console/ui-next/dist/assets/access-page-3-EFj-2G.js +0 -4
  467. package/src/console/ui-next/dist/assets/agent-disclosure-BP0Y0Sux.js +0 -1
  468. package/src/console/ui-next/dist/assets/cache-glyph-B5X-NM0-.js +0 -1
  469. package/src/console/ui-next/dist/assets/call-pii-button-ChAUiLo9.js +0 -1
  470. package/src/console/ui-next/dist/assets/collapsible-DGnOM2ph.js +0 -1
  471. package/src/console/ui-next/dist/assets/copy-inline-button-DBHhgHKP.js +0 -1
  472. package/src/console/ui-next/dist/assets/dagre.esm-ZwcdTuZZ.js +0 -1
  473. package/src/console/ui-next/dist/assets/detail-header-DhHM1iaZ.js +0 -1
  474. package/src/console/ui-next/dist/assets/dropdown-menu-_NjTEo5_.js +0 -1
  475. package/src/console/ui-next/dist/assets/duration-tone-sC3lGABz.js +0 -9
  476. package/src/console/ui-next/dist/assets/element-icons-BVXtRyd3.js +0 -1
  477. package/src/console/ui-next/dist/assets/explorer-empty-2uIhBu0_.js +0 -1
  478. package/src/console/ui-next/dist/assets/flows-page-cVFnA4HH.js +0 -1
  479. package/src/console/ui-next/dist/assets/highlighted-json-Awq7gYdu.js +0 -154
  480. package/src/console/ui-next/dist/assets/http-method-_UHM2ODJ.js +0 -1
  481. package/src/console/ui-next/dist/assets/index-CMIgUbD0.js +0 -66
  482. package/src/console/ui-next/dist/assets/index-Ck88Jmv8.css +0 -2
  483. package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +0 -1
  484. package/src/console/ui-next/dist/assets/observability-page-CQ3p34ip.js +0 -4
  485. package/src/console/ui-next/dist/assets/preload-helper-oH4irX4C.js +0 -1
  486. package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +0 -1
  487. package/src/console/ui-next/dist/assets/react-dom-Bph1y7z7.js +0 -9
  488. package/src/console/ui-next/dist/assets/replica-lag-DKRrbvdo.js +0 -18
  489. package/src/console/ui-next/dist/assets/request-meta-DV0ywz7t.js +0 -1
  490. package/src/console/ui-next/dist/assets/shortcut-JQIZlWfm.js +0 -1
  491. package/src/console/ui-next/dist/assets/shortcut-keys-DKxNTe_m.js +0 -1
  492. package/src/console/ui-next/dist/assets/skeleton-D-czQJT6.js +0 -1
  493. package/src/console/ui-next/dist/assets/store-page-02xOiqIK.js +0 -41
  494. package/src/console/ui-next/dist/assets/trace-detail-sheet-B09O8rA6.js +0 -2
  495. package/src/console/ui-next/dist/assets/tree-expand-toggle-BtyhmWb4.js +0 -55
  496. package/src/console/ui-next/dist/assets/units-page-l8FeKfnP.js +0 -1
  497. package/src/console/ui-next/dist/assets/useMutation-B8EO02Ej.js +0 -1
  498. package/src/console/ui-next/dist/assets/useRender-BE2A9BWC.js +0 -1
  499. package/src/console/ui-next/dist/assets/vault-page-CL-d_mLE.js +0 -2
  500. package/src/console/ui-next/dist/assets/xyflow-D7n4g6go.js +0 -7
  501. package/src/console/ui-next/dist/assets/xyflow-DZ0Ws1xk.css +0 -1
  502. package/src/docker/ollama-pull.ts +0 -232
  503. package/src/docker/recipes/llama-cpp.ts +0 -298
  504. package/src/docker/recipes/ollama.ts +0 -44
  505. package/src/docker/recipes/sglang.ts +0 -55
  506. package/src/docker/recipes/vllm.ts +0 -44
  507. package/src/drivers/ai-ollama-tools.integration.test.ts +0 -109
  508. package/src/drivers/ai-ollama.integration.test.ts +0 -184
  509. package/src/drivers/ai-ollama.ts +0 -389
  510. package/src/drivers/ollama.ts +0 -14
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Auth / gate denial narrowers — envelope values, not throws.
3
+ *
4
+ * @module
5
+ */
6
+
7
+ /** Common auth Flow error codes (server `fail` codes). */
8
+ export const AUTH_ERROR_CODES = {
9
+ AuthFailed: "AuthFailed",
10
+ AuthRateLimited: "AuthRateLimited",
11
+ Unauthorized: "Unauthorized",
12
+ Forbidden: "Forbidden",
13
+ } as const;
14
+
15
+ function errorCode(error: unknown): string | undefined {
16
+ if (error === null || typeof error !== "object") return undefined;
17
+ if (!("code" in error)) return undefined;
18
+ const code = (error as { code: unknown }).code;
19
+ return typeof code === "string" ? code : undefined;
20
+ }
21
+
22
+ function errorReason(error: unknown): string | undefined {
23
+ if (error === null || typeof error !== "object") return undefined;
24
+ const data = (error as { data?: unknown }).data;
25
+ if (data === null || typeof data !== "object") return undefined;
26
+ const reason = (data as { reason?: unknown }).reason;
27
+ return typeof reason === "string" ? reason : undefined;
28
+ }
29
+
30
+ /** True when the envelope error is `Unauthorized`. */
31
+ export function isUnauthorized(error: unknown): boolean {
32
+ return errorCode(error) === AUTH_ERROR_CODES.Unauthorized;
33
+ }
34
+
35
+ /** True when the envelope error is `Forbidden` (policy / CSRF / etc.). */
36
+ export function isForbidden(error: unknown): boolean {
37
+ return errorCode(error) === AUTH_ERROR_CODES.Forbidden;
38
+ }
39
+
40
+ /** True when the envelope error is `AuthRateLimited` or HTTP `RateLimited`. */
41
+ export function isAuthRateLimited(error: unknown): boolean {
42
+ const code = errorCode(error);
43
+ return code === AUTH_ERROR_CODES.AuthRateLimited || code === "RateLimited";
44
+ }
45
+
46
+ /** True when Forbidden reason is CSRF. */
47
+ export function isCsrf(error: unknown): boolean {
48
+ return isForbidden(error) && errorReason(error) === "csrf";
49
+ }
50
+
51
+ /**
52
+ * Best-effort scopes named in a Forbidden envelope (`gate` / `reason` / `missing`).
53
+ * UI hint only — Gate on Flows remains the security boundary.
54
+ *
55
+ * @param error - Envelope error
56
+ */
57
+ export function forbiddenScopes(error: unknown): readonly string[] {
58
+ if (!isForbidden(error)) return [];
59
+ if (error === null || typeof error !== "object") return [];
60
+ const data = (error as { data?: unknown }).data;
61
+ if (data === null || typeof data !== "object") return [];
62
+ const bag = data as Record<string, unknown>;
63
+ if (Array.isArray(bag.missing)) {
64
+ return bag.missing.filter((s): s is string => typeof s === "string");
65
+ }
66
+ if (typeof bag.gate === "string" && bag.gate.includes(":")) return [bag.gate];
67
+ if (typeof bag.reason === "string" && bag.reason.includes(":")) return [bag.reason];
68
+ return [];
69
+ }
70
+
71
+ /** True when sign-in data asks for a second factor. */
72
+ export function isTwoFactorRequired(data: unknown): data is {
73
+ readonly twoFactorRequired: true;
74
+ readonly challengeId: string;
75
+ readonly method: string;
76
+ readonly userId: string;
77
+ } {
78
+ return (
79
+ data !== null &&
80
+ typeof data === "object" &&
81
+ (data as { twoFactorRequired?: unknown }).twoFactorRequired === true &&
82
+ typeof (data as { challengeId?: unknown }).challengeId === "string"
83
+ );
84
+ }
85
+
86
+ /** True when data looks like issued session tokens. */
87
+ export function isSessionTokens(data: unknown): data is {
88
+ readonly accessToken: string;
89
+ readonly refreshToken: string;
90
+ readonly accessExpiresAt?: number;
91
+ readonly userId?: string;
92
+ } {
93
+ return (
94
+ data !== null &&
95
+ typeof data === "object" &&
96
+ typeof (data as { accessToken?: unknown }).accessToken === "string" &&
97
+ typeof (data as { refreshToken?: unknown }).refreshToken === "string"
98
+ );
99
+ }
@@ -0,0 +1,283 @@
1
+ /**
2
+ * Session token bag — memory / Storage persist with subscribe.
3
+ *
4
+ * @module
5
+ */
6
+
7
+ /** Tokens written into a session store. */
8
+ export interface SessionTokens {
9
+ readonly accessToken: string;
10
+ readonly refreshToken: string;
11
+ readonly accessExpiresAt?: number;
12
+ readonly userId?: string;
13
+ readonly scopes?: readonly string[];
14
+ readonly tenantId?: string | null;
15
+ }
16
+
17
+ /** Snapshot of identity for UI (from `auth.me` / sign-in). */
18
+ export interface SessionUser {
19
+ readonly userId: string;
20
+ readonly email?: string;
21
+ readonly name?: string;
22
+ readonly emailVerified?: boolean;
23
+ readonly scopes: readonly string[];
24
+ readonly tenantId: string | null;
25
+ readonly apiKeyId: string | null;
26
+ readonly sessionFresh?: boolean;
27
+ }
28
+
29
+ /** Listener notified on session mutations. */
30
+ export type SessionListener = () => void;
31
+
32
+ /** In-memory / persisted session token bag for Bearer `createClient` wiring. */
33
+ export interface MemorySession {
34
+ accessToken: string | null;
35
+ refreshToken: string | null;
36
+ accessExpiresAt: number | null;
37
+ userId: string | null;
38
+ scopes: readonly string[];
39
+ tenantId: string | null;
40
+ user: SessionUser | null;
41
+ set(tokens: SessionTokens): void;
42
+ setUser(user: SessionUser | null): void;
43
+ clear(): void;
44
+ /** Wire into `createClient` `auth.getToken`. */
45
+ getToken(): string | null;
46
+ /** Subscribe to mutations; returns unsubscribe. */
47
+ subscribe(listener: SessionListener): () => void;
48
+ /**
49
+ * Wire into `createClient` `auth.refresh` — calls the refresh Flow via the client.
50
+ *
51
+ * @param api - Client with `auth.refresh` route
52
+ */
53
+ refresh(api: {
54
+ auth: {
55
+ refresh: (input: { refreshToken: string }) => Promise<{
56
+ data: {
57
+ accessToken: string;
58
+ refreshToken: string;
59
+ accessExpiresAt?: number;
60
+ userId?: string;
61
+ } | null;
62
+ error: unknown;
63
+ }>;
64
+ };
65
+ }): Promise<string | null>;
66
+ }
67
+
68
+ /** Options for {@link memorySession} / {@link persistSession}. */
69
+ export interface MemorySessionOptions {
70
+ /**
71
+ * Web Storage backend. Prefer `sessionStorage` over `localStorage`
72
+ * (XSS can still steal; `localStorage` survives tabs — explicit opt-in only).
73
+ */
74
+ readonly storage?: Storage;
75
+ /** Storage key (default `oke.session`). */
76
+ readonly key?: string;
77
+ }
78
+
79
+ const DEFAULT_KEY = "oke.session";
80
+
81
+ interface StoredBag {
82
+ readonly accessToken: string;
83
+ readonly refreshToken: string;
84
+ readonly accessExpiresAt: number | null;
85
+ readonly userId: string | null;
86
+ readonly scopes: readonly string[];
87
+ readonly tenantId: string | null;
88
+ readonly user: SessionUser | null;
89
+ }
90
+
91
+ /**
92
+ * Create an in-memory session helper for Bearer `createClient` wiring.
93
+ *
94
+ * @param options - Optional Storage persist (memory when omitted)
95
+ */
96
+ export function memorySession(options?: MemorySessionOptions): MemorySession {
97
+ const storage = options?.storage;
98
+ const key = options?.key ?? DEFAULT_KEY;
99
+ const listeners = new Set<SessionListener>();
100
+
101
+ const state: {
102
+ accessToken: string | null;
103
+ refreshToken: string | null;
104
+ accessExpiresAt: number | null;
105
+ userId: string | null;
106
+ scopes: readonly string[];
107
+ tenantId: string | null;
108
+ user: SessionUser | null;
109
+ } = {
110
+ accessToken: null,
111
+ refreshToken: null,
112
+ accessExpiresAt: null,
113
+ userId: null,
114
+ scopes: [],
115
+ tenantId: null,
116
+ user: null,
117
+ };
118
+
119
+ if (storage) {
120
+ try {
121
+ const raw = storage.getItem(key);
122
+ if (raw) {
123
+ const parsed = JSON.parse(raw) as StoredBag;
124
+ state.accessToken = parsed.accessToken ?? null;
125
+ state.refreshToken = parsed.refreshToken ?? null;
126
+ state.accessExpiresAt = parsed.accessExpiresAt ?? null;
127
+ state.userId = parsed.userId ?? null;
128
+ state.scopes = parsed.scopes ?? [];
129
+ state.tenantId = parsed.tenantId ?? null;
130
+ state.user = parsed.user ?? null;
131
+ }
132
+ } catch {
133
+ /* ignore corrupt storage */
134
+ }
135
+ }
136
+
137
+ function persist(): void {
138
+ if (!storage) return;
139
+ if (!state.accessToken || !state.refreshToken) {
140
+ storage.removeItem(key);
141
+ return;
142
+ }
143
+ const bag: StoredBag = {
144
+ accessToken: state.accessToken,
145
+ refreshToken: state.refreshToken,
146
+ accessExpiresAt: state.accessExpiresAt,
147
+ userId: state.userId,
148
+ scopes: state.scopes,
149
+ tenantId: state.tenantId,
150
+ user: state.user,
151
+ };
152
+ storage.setItem(key, JSON.stringify(bag));
153
+ }
154
+
155
+ function notify(): void {
156
+ for (const l of listeners) l();
157
+ }
158
+
159
+ return {
160
+ get accessToken() {
161
+ return state.accessToken;
162
+ },
163
+ set accessToken(v) {
164
+ state.accessToken = v;
165
+ persist();
166
+ notify();
167
+ },
168
+ get refreshToken() {
169
+ return state.refreshToken;
170
+ },
171
+ set refreshToken(v) {
172
+ state.refreshToken = v;
173
+ persist();
174
+ notify();
175
+ },
176
+ get accessExpiresAt() {
177
+ return state.accessExpiresAt;
178
+ },
179
+ set accessExpiresAt(v) {
180
+ state.accessExpiresAt = v;
181
+ persist();
182
+ notify();
183
+ },
184
+ get userId() {
185
+ return state.userId;
186
+ },
187
+ set userId(v) {
188
+ state.userId = v;
189
+ persist();
190
+ notify();
191
+ },
192
+ get scopes() {
193
+ return state.scopes;
194
+ },
195
+ set scopes(v) {
196
+ state.scopes = v;
197
+ persist();
198
+ notify();
199
+ },
200
+ get tenantId() {
201
+ return state.tenantId;
202
+ },
203
+ set tenantId(v) {
204
+ state.tenantId = v;
205
+ persist();
206
+ notify();
207
+ },
208
+ get user() {
209
+ return state.user;
210
+ },
211
+ set(tokens) {
212
+ state.accessToken = tokens.accessToken;
213
+ state.refreshToken = tokens.refreshToken;
214
+ state.accessExpiresAt = tokens.accessExpiresAt ?? null;
215
+ state.userId = tokens.userId ?? null;
216
+ if (tokens.scopes) state.scopes = tokens.scopes;
217
+ if (tokens.tenantId !== undefined) state.tenantId = tokens.tenantId;
218
+ persist();
219
+ notify();
220
+ },
221
+ setUser(user) {
222
+ state.user = user;
223
+ if (user) {
224
+ state.userId = user.userId;
225
+ state.scopes = user.scopes;
226
+ state.tenantId = user.tenantId;
227
+ }
228
+ persist();
229
+ notify();
230
+ },
231
+ clear() {
232
+ state.accessToken = null;
233
+ state.refreshToken = null;
234
+ state.accessExpiresAt = null;
235
+ state.userId = null;
236
+ state.scopes = [];
237
+ state.tenantId = null;
238
+ state.user = null;
239
+ persist();
240
+ notify();
241
+ },
242
+ getToken() {
243
+ return state.accessToken;
244
+ },
245
+ subscribe(listener) {
246
+ listeners.add(listener);
247
+ return () => {
248
+ listeners.delete(listener);
249
+ };
250
+ },
251
+ async refresh(api) {
252
+ if (!state.refreshToken) return null;
253
+ const { data, error } = await api.auth.refresh({ refreshToken: state.refreshToken });
254
+ if (error || !data) {
255
+ state.accessToken = null;
256
+ persist();
257
+ notify();
258
+ return null;
259
+ }
260
+ state.accessToken = data.accessToken;
261
+ state.refreshToken = data.refreshToken;
262
+ state.accessExpiresAt = data.accessExpiresAt ?? null;
263
+ if (data.userId) state.userId = data.userId;
264
+ persist();
265
+ notify();
266
+ return data.accessToken;
267
+ },
268
+ };
269
+ }
270
+
271
+ /**
272
+ * Bearer session persisted to Storage.
273
+ *
274
+ * **Security:** XSS can read Storage. Prefer cookie mode or memory.
275
+ * Use `sessionStorage` before `localStorage`; `localStorage` is an explicit
276
+ * multi-tab convenience tradeoff — never the silent default.
277
+ *
278
+ * @param storage - `sessionStorage` or `localStorage`
279
+ * @param key - Optional storage key
280
+ */
281
+ export function persistSession(storage: Storage, key?: string): MemorySession {
282
+ return memorySession({ storage, key });
283
+ }
@@ -0,0 +1,251 @@
1
+ /**
2
+ * Auth helpers tests — session subscribe, createAuthClient secure defaults.
3
+ */
4
+
5
+ import { describe, expect, test } from "bun:test";
6
+ import {
7
+ createAuthClient,
8
+ isForbidden,
9
+ isSessionTokens,
10
+ isTwoFactorRequired,
11
+ isUnauthorized,
12
+ memorySession,
13
+ persistSession,
14
+ } from "./auth.ts";
15
+
16
+ describe("memorySession", () => {
17
+ test("subscribe notifies on set and clear", () => {
18
+ const s = memorySession();
19
+ let n = 0;
20
+ const unsub = s.subscribe(() => {
21
+ n += 1;
22
+ });
23
+ s.set({ accessToken: "a", refreshToken: "r", userId: "u1" });
24
+ expect(n).toBe(1);
25
+ s.clear();
26
+ expect(n).toBe(2);
27
+ unsub();
28
+ s.set({ accessToken: "a2", refreshToken: "r2" });
29
+ expect(n).toBe(2);
30
+ });
31
+
32
+ test("persistSession round-trips via Storage", () => {
33
+ const store = new Map<string, string>();
34
+ const storage: Storage = {
35
+ get length() {
36
+ return store.size;
37
+ },
38
+ clear() {
39
+ store.clear();
40
+ },
41
+ getItem(k) {
42
+ return store.get(k) ?? null;
43
+ },
44
+ key() {
45
+ return null;
46
+ },
47
+ removeItem(k) {
48
+ store.delete(k);
49
+ },
50
+ setItem(k, v) {
51
+ store.set(k, v);
52
+ },
53
+ };
54
+ const a = persistSession(storage);
55
+ a.set({ accessToken: "tok", refreshToken: "ref", userId: "u" });
56
+ const b = persistSession(storage);
57
+ expect(b.getToken()).toBe("tok");
58
+ expect(b.refreshToken).toBe("ref");
59
+ });
60
+ });
61
+
62
+ describe("denials", () => {
63
+ test("narrowers", () => {
64
+ expect(isUnauthorized({ code: "Unauthorized", data: {} })).toBe(true);
65
+ expect(isForbidden({ code: "Forbidden", data: { reason: "csrf" } })).toBe(true);
66
+ expect(
67
+ isTwoFactorRequired({
68
+ twoFactorRequired: true,
69
+ challengeId: "c",
70
+ method: "totp",
71
+ userId: "u",
72
+ }),
73
+ ).toBe(true);
74
+ expect(isSessionTokens({ accessToken: "a", refreshToken: "r" })).toBe(true);
75
+ });
76
+ });
77
+
78
+ describe("createAuthClient", () => {
79
+ test("refuses cookie mode with localStorage persist", () => {
80
+ expect(() =>
81
+ createAuthClient(
82
+ { auth: {} },
83
+ { mode: "cookie", persist: "localStorage", csrfConfigured: true },
84
+ ),
85
+ ).toThrow(/cannot persist/);
86
+ });
87
+
88
+ test("warns when cookie mode without csrfConfigured", () => {
89
+ const warnings: string[] = [];
90
+ createAuthClient(
91
+ { auth: {} },
92
+ {
93
+ mode: "cookie",
94
+ env: { warn: (m) => warnings.push(m) },
95
+ },
96
+ );
97
+ expect(warnings.some((w) => w.includes("csrf"))).toBe(true);
98
+ });
99
+
100
+ test("signIn.email sets session and hasScope is UI-only from me", async () => {
101
+ const api = {
102
+ auth: {
103
+ signInEmail: async () => ({
104
+ data: {
105
+ accessToken: "a",
106
+ refreshToken: "r",
107
+ accessExpiresAt: Date.now() + 60_000,
108
+ userId: "u1",
109
+ },
110
+ error: null,
111
+ }),
112
+ me: async () => ({
113
+ data: {
114
+ userId: "u1",
115
+ email: "a@b.co",
116
+ name: "A",
117
+ emailVerified: true,
118
+ scopes: ["orders:write"],
119
+ tenantId: null,
120
+ apiKeyId: null,
121
+ },
122
+ error: null,
123
+ }),
124
+ revoke: async () => ({ data: { ok: true }, error: null }),
125
+ },
126
+ };
127
+ const auth = createAuthClient(api, { csrfConfigured: true });
128
+ const result = await auth.signIn.email({ email: "a@b.co", password: "x" });
129
+ expect(result.ok).toBe(true);
130
+ expect(auth.hasScope("orders:write")).toBe(true);
131
+ expect(auth.can("orders:write")).toBe(true);
132
+ expect(auth.hasScope("admin")).toBe(false);
133
+ await auth.signOut();
134
+ expect(auth.session.getToken()).toBeNull();
135
+ });
136
+
137
+ test("twoFactorRequired discrimination", async () => {
138
+ const api = {
139
+ auth: {
140
+ signInEmail: async () => ({
141
+ data: {
142
+ twoFactorRequired: true as const,
143
+ challengeId: "ch1",
144
+ method: "totp",
145
+ userId: "u1",
146
+ },
147
+ error: null,
148
+ }),
149
+ },
150
+ };
151
+ const auth = createAuthClient(api);
152
+ const result = await auth.signIn.email({ email: "a@b.co", password: "x" });
153
+ expect(result.ok).toBe(false);
154
+ if (!result.ok) expect(result.twoFactor?.challengeId).toBe("ch1");
155
+ });
156
+
157
+ test("setTenant adds header via clientOptions", () => {
158
+ const auth = createAuthClient({ auth: {} });
159
+ auth.setTenant("t_1");
160
+ const headers = auth.clientOptions.headers?.();
161
+ expect(headers?.["x-oke-tenant"]).toBe("t_1");
162
+ });
163
+
164
+ test("authorize reports allowed / denied / unauthenticated", async () => {
165
+ const api = {
166
+ auth: {
167
+ signInEmail: async () => ({
168
+ data: {
169
+ accessToken: "a",
170
+ refreshToken: "r",
171
+ userId: "u1",
172
+ },
173
+ error: null,
174
+ }),
175
+ me: async () => ({
176
+ data: {
177
+ userId: "u1",
178
+ email: "a@b.co",
179
+ name: "A",
180
+ scopes: ["notes:write"],
181
+ tenantId: null,
182
+ apiKeyId: null,
183
+ },
184
+ error: null,
185
+ }),
186
+ },
187
+ };
188
+ const auth = createAuthClient(api);
189
+ expect(auth.authorize({ all: ["notes:write"] }).status).toBe("unauthenticated");
190
+ await auth.signIn.email({ email: "a@b.co", password: "x" });
191
+ expect(auth.authorize({ all: ["notes:write"] }).status).toBe("allowed");
192
+ const denied = auth.authorize({ all: ["notes:write", "admin"] });
193
+ expect(denied.status).toBe("denied");
194
+ if (denied.status === "denied") expect(denied.missing).toEqual(["admin"]);
195
+ expect(auth.authorize({ any: ["admin", "notes:write"] }).status).toBe("allowed");
196
+ });
197
+ });
198
+
199
+ describe("tokenFromRequestCookies", () => {
200
+ test("parses access cookie from header / Request", async () => {
201
+ const { tokenFromRequestCookies } = await import("./auth.ts");
202
+ const header = "oke.session_token=abc%20123; other=1";
203
+ expect(tokenFromRequestCookies(header)).toBe("abc 123");
204
+ expect(tokenFromRequestCookies(new Headers({ cookie: header }))).toBe("abc 123");
205
+ expect(
206
+ tokenFromRequestCookies(new Request("http://localhost", { headers: { cookie: header } })),
207
+ ).toBe("abc 123");
208
+ expect(tokenFromRequestCookies("")).toBeNull();
209
+ });
210
+ });
211
+
212
+ describe("createClient session auth", () => {
213
+ test("attaches api.auth and preserves auth unit flows", async () => {
214
+ const { createClient } = await import("./create-with-session.ts");
215
+ const calls: string[] = [];
216
+ const api = createClient("http://example.test", {
217
+ $routes: {
218
+ auth: {
219
+ me: { method: "GET", path: "/auth/me" },
220
+ },
221
+ },
222
+ auth: { mode: "bearer", csrfConfigured: true },
223
+ fetch: async (input) => {
224
+ calls.push(String(input));
225
+ return new Response(
226
+ JSON.stringify({
227
+ data: {
228
+ userId: "u1",
229
+ email: "a@b.co",
230
+ name: "A",
231
+ scopes: ["notes:write"],
232
+ tenantId: null,
233
+ apiKeyId: null,
234
+ },
235
+ error: null,
236
+ }),
237
+ { status: 200, headers: { "content-type": "application/json" } },
238
+ );
239
+ },
240
+ });
241
+ expect(api.auth).toBeDefined();
242
+ expect(typeof api.auth?.authorize).toBe("function");
243
+ // Runtime merge keeps unit Flows (`me`) on the AuthClient proxy.
244
+ expect(
245
+ api.auth && "me" in api.auth && typeof (api.auth as { me: unknown }).me === "function",
246
+ ).toBe(true);
247
+ const user = await api.auth!.getSession();
248
+ expect(user?.scopes).toEqual(["notes:write"]);
249
+ expect(calls.some((u) => u.includes("/auth/me"))).toBe(true);
250
+ });
251
+ });