okengine 0.18.4 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (512) hide show
  1. package/AGENTS.md +18 -10
  2. package/README.md +2 -2
  3. package/package.json +51 -50
  4. package/site/content/docs/ai/index.mdx +25 -6
  5. package/site/content/docs/ai/llms-txt.mdx +1 -1
  6. package/site/content/docs/ai/meta.json +1 -1
  7. package/site/content/docs/ai/skills.mdx +4 -1
  8. package/site/content/docs/ai/try-it.mdx +58 -0
  9. package/site/content/docs/client/auth.mdx +208 -0
  10. package/site/content/docs/client/calling.mdx +451 -0
  11. package/site/content/docs/client/index.mdx +209 -0
  12. package/site/content/docs/client/live.mdx +234 -0
  13. package/site/content/docs/client/meta.json +5 -0
  14. package/site/content/docs/client/react.mdx +249 -0
  15. package/site/content/docs/elements/ai/agents.mdx +232 -0
  16. package/site/content/docs/elements/ai/index.mdx +341 -0
  17. package/site/content/docs/elements/ai/mcp.mdx +275 -0
  18. package/site/content/docs/elements/ai/meta.json +5 -0
  19. package/site/content/docs/elements/ai/models.mdx +283 -0
  20. package/site/content/docs/elements/ai/prompts.mdx +256 -0
  21. package/site/content/docs/elements/channel/email.mdx +266 -0
  22. package/site/content/docs/elements/channel/index.mdx +344 -0
  23. package/site/content/docs/elements/channel/meta.json +5 -0
  24. package/site/content/docs/elements/channel/push.mdx +218 -0
  25. package/site/content/docs/elements/channel/receipts.mdx +206 -0
  26. package/site/content/docs/elements/channel/sms.mdx +264 -0
  27. package/site/content/docs/elements/channel/whatsapp.mdx +220 -0
  28. package/site/content/docs/elements/clock/index.mdx +300 -0
  29. package/site/content/docs/elements/clock/meta.json +5 -0
  30. package/site/content/docs/elements/clock/schedules.mdx +508 -0
  31. package/site/content/docs/elements/clock/sleep.mdx +445 -0
  32. package/site/content/docs/elements/flow/consumers.mdx +707 -0
  33. package/site/content/docs/elements/flow/http.mdx +1080 -0
  34. package/site/content/docs/elements/flow/index.mdx +618 -0
  35. package/site/content/docs/elements/flow/meta.json +5 -0
  36. package/site/content/docs/elements/flow/routing.mdx +559 -0
  37. package/site/content/docs/elements/flow/workflows.mdx +640 -0
  38. package/site/content/docs/elements/gate/auth.mdx +371 -0
  39. package/site/content/docs/elements/gate/authorization.mdx +298 -0
  40. package/site/content/docs/elements/gate/index.mdx +418 -0
  41. package/site/content/docs/elements/gate/meta.json +5 -0
  42. package/site/content/docs/elements/gate/rate-limits.mdx +365 -0
  43. package/site/content/docs/elements/gate/rls.mdx +303 -0
  44. package/site/content/docs/elements/gate/tenancy.mdx +372 -0
  45. package/site/content/docs/elements/index.mdx +52 -11
  46. package/site/content/docs/elements/signal/broadcast.mdx +517 -0
  47. package/site/content/docs/elements/signal/index.mdx +352 -0
  48. package/site/content/docs/elements/signal/live.mdx +590 -0
  49. package/site/content/docs/elements/signal/meta.json +5 -0
  50. package/site/content/docs/elements/signal/once.mdx +596 -0
  51. package/site/content/docs/elements/store/files.mdx +659 -0
  52. package/site/content/docs/elements/store/index.mdx +310 -0
  53. package/site/content/docs/elements/store/kv.mdx +629 -0
  54. package/site/content/docs/elements/store/meta.json +5 -0
  55. package/site/content/docs/elements/store/search.mdx +946 -0
  56. package/site/content/docs/elements/store/sql.mdx +937 -0
  57. package/site/content/docs/elements/vault/config.mdx +256 -0
  58. package/site/content/docs/elements/vault/index.mdx +356 -0
  59. package/site/content/docs/elements/vault/meta.json +5 -0
  60. package/site/content/docs/elements/vault/rotation.mdx +273 -0
  61. package/site/content/docs/elements/vault/secrets.mdx +285 -0
  62. package/site/content/docs/index.mdx +78 -30
  63. package/site/content/docs/meta.json +11 -8
  64. package/site/content/docs/plugins/cors.mdx +2 -1
  65. package/site/content/docs/plugins/csrf.mdx +4 -1
  66. package/site/content/docs/plugins/headers.mdx +2 -1
  67. package/site/content/docs/plugins/ip-allowlist.mdx +2 -1
  68. package/site/content/docs/plugins/magic-link.mdx +6 -0
  69. package/site/content/docs/plugins/maintenance-mode.mdx +2 -1
  70. package/site/content/docs/plugins/oauth.mdx +1 -1
  71. package/site/content/docs/plugins/otp.mdx +12 -1
  72. package/site/content/docs/plugins/passkey.mdx +39 -15
  73. package/site/content/docs/plugins/two-factor.mdx +91 -34
  74. package/site/content/docs/plugins/username.mdx +1 -1
  75. package/site/content/docs/recipes/caddy.mdx +3 -3
  76. package/site/content/docs/recipes/index.mdx +18 -28
  77. package/site/content/docs/recipes/mailpit.mdx +1 -1
  78. package/site/content/docs/recipes/meilisearch.mdx +3 -3
  79. package/site/content/docs/recipes/meta.json +1 -4
  80. package/site/content/docs/recipes/nginx.mdx +2 -2
  81. package/site/content/docs/recipes/openrouter.mdx +283 -0
  82. package/site/content/docs/recipes/pgdog.mdx +3 -3
  83. package/site/content/docs/recipes/postgres.mdx +1 -2
  84. package/site/content/docs/recipes/rustfs.mdx +2 -2
  85. package/site/content/docs/recipes/traefik.mdx +3 -3
  86. package/site/content/docs/reference/cli.mdx +231 -0
  87. package/site/content/docs/reference/configuration.mdx +30 -10
  88. package/site/content/docs/reference/environment-variables.mdx +93 -20
  89. package/site/content/docs/reference/errors.mdx +132 -29
  90. package/site/content/docs/reference/fx.mdx +137 -73
  91. package/site/content/docs/reference/index.mdx +13 -21
  92. package/site/content/docs/reference/meta.json +4 -5
  93. package/site/content/docs/reference/okid.mdx +42 -17
  94. package/site/content/docs/reference/plugins.mdx +4 -3
  95. package/site/content/docs/reference/security.mdx +197 -0
  96. package/site/content/docs/understand/meta.json +5 -0
  97. package/site/content/docs/understand/the-anatomy.mdx +132 -0
  98. package/site/content/docs/understand/the-model.mdx +32 -0
  99. package/site/content/docs/understand/the-problem.mdx +74 -0
  100. package/site/content/docs/understand/the-vocabulary.mdx +26 -0
  101. package/src/auth/api-keys.ts +2 -1
  102. package/src/auth/bindings.ts +64 -16
  103. package/src/auth/gate-auth.test.ts +6 -1
  104. package/src/auth/identity.ts +152 -5
  105. package/src/auth/index.ts +29 -0
  106. package/src/auth/invites.ts +2 -1
  107. package/src/auth/method-context.ts +41 -0
  108. package/src/auth/oauth-as/crypto.ts +2 -1
  109. package/src/auth/oauth-as/stores.ts +2 -1
  110. package/src/auth/operator.ts +2 -1
  111. package/src/auth/sessions-jwt.test.ts +77 -0
  112. package/src/auth/sessions.ts +20 -4
  113. package/src/auth/tenants.ts +3 -2
  114. package/src/auth/two-factor-challenge.test.ts +61 -0
  115. package/src/auth/two-factor-challenge.ts +284 -0
  116. package/src/auth/verification.ts +5 -0
  117. package/src/bench/REPORT.md +49 -0
  118. package/src/bench/g03-signal-once.bench.ts +1 -1
  119. package/src/bench/g10-observability-contention.bench.ts +1 -1
  120. package/src/bench/g17-hybrid-search.bench.ts +572 -0
  121. package/src/bench/load-app.ts +2 -10
  122. package/src/cli/ai-setup/ai-setup.test.ts +331 -42
  123. package/src/cli/ai-setup/apply.ts +294 -52
  124. package/src/cli/ai-setup/catalog.ts +238 -1342
  125. package/src/cli/ai-setup/index.ts +14 -99
  126. package/src/cli/ai-setup/prompts.ts +60 -443
  127. package/src/cli/ask-seed.test.ts +96 -0
  128. package/src/cli/ask-seed.ts +82 -0
  129. package/src/cli/ask-vault-gaps.test.ts +84 -0
  130. package/src/cli/ask-vault-gaps.ts +146 -0
  131. package/src/cli/build.ts +2 -2
  132. package/src/cli/client-add.test.ts +26 -1
  133. package/src/cli/client-add.ts +106 -27
  134. package/src/cli/competitor-mention-removal.test.ts +1 -1
  135. package/src/cli/db-seed.ts +2 -0
  136. package/src/cli/db.ts +150 -11
  137. package/src/cli/dev.ts +119 -211
  138. package/src/cli/docker-clean.ts +2 -2
  139. package/src/cli/docker-cli.test.ts +1 -1
  140. package/src/cli/doctor-diff.ts +4 -2
  141. package/src/cli/doctor-pii.test.ts +1 -1
  142. package/src/cli/doctor.ts +14 -1
  143. package/src/cli/hero-meta.test.ts +4 -2
  144. package/src/cli/load-config.images.test.ts +8 -10
  145. package/src/cli/load-config.ts +1 -1
  146. package/src/cli/registry.ts +22 -10
  147. package/src/cli/replay.ts +3 -1
  148. package/src/cli/start.ts +1 -1
  149. package/src/cli/tui/keys.ts +2 -3
  150. package/src/client/auth/cookies.ts +74 -0
  151. package/src/client/auth/create-auth-client.ts +636 -0
  152. package/src/client/auth/denials.ts +99 -0
  153. package/src/client/auth/session.ts +283 -0
  154. package/src/client/auth.test.ts +251 -0
  155. package/src/client/auth.ts +39 -114
  156. package/src/client/create-with-session.ts +245 -0
  157. package/src/client/create.ts +51 -5
  158. package/src/client/index.ts +11 -1
  159. package/src/client/live.ts +11 -79
  160. package/src/client/notes-contract.test.ts +5 -10
  161. package/src/client/sse.ts +137 -0
  162. package/src/client/stream.ts +147 -0
  163. package/src/client/transport.test.ts +32 -0
  164. package/src/client/transport.ts +123 -26
  165. package/src/client/types.ts +118 -14
  166. package/src/client-react/index.ts +185 -24
  167. package/src/client-react/use-live-query.ts +13 -3
  168. package/src/compiler/aot.test.ts +7 -4
  169. package/src/compiler/effects-embed.test.ts +56 -0
  170. package/src/compiler/effects-fetch.test.ts +43 -0
  171. package/src/compiler/effects-infer.ts +58 -2
  172. package/src/compiler/extract.test.ts +258 -44
  173. package/src/compiler/extract.ts +566 -48
  174. package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +7 -4
  175. package/src/compiler/fixtures/skyport/src/flows/bookings/signals.ts +2 -8
  176. package/src/compiler/fixtures/skyport.expected.json +4 -4
  177. package/src/compiler/fixtures/triggers/five-triggers.ts +14 -10
  178. package/src/compiler/response.ts +2 -2
  179. package/src/compiler/search-writer-isolation.test.ts +40 -0
  180. package/src/config/index.ts +11 -0
  181. package/src/console/server/app.ts +17 -23
  182. package/src/console/server/bind.ts +4 -0
  183. package/src/console/server/console.test.ts +2 -2
  184. package/src/console/server/flows-invoke.test.ts +50 -34
  185. package/src/console/server/flows.ts +436 -255
  186. package/src/console/server/runs-ingest.test.ts +6 -3
  187. package/src/console/server/serve.ts +1 -1
  188. package/src/console/server/signals.test.ts +1 -5
  189. package/src/console/server/signals.ts +8 -6
  190. package/src/console/server/state.ts +6 -1
  191. package/src/console/server/store.ts +1 -1
  192. package/src/console/ui-next/dist/assets/FileExportIcon-Ck-5od4R.js +1 -0
  193. package/src/console/ui-next/dist/assets/MoreHorizontalCircle01Icon-gMNGsE37.js +1 -0
  194. package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +1 -0
  195. package/src/console/ui-next/dist/assets/UnavailableIcon-D9cvHVPr.js +1 -0
  196. package/src/console/ui-next/dist/assets/UserIcon-DaE2PB5_.js +1 -0
  197. package/src/console/ui-next/dist/assets/access-page-DFeymU07.js +4 -0
  198. package/src/console/ui-next/dist/assets/agent-disclosure-U1rdfblp.js +1 -0
  199. package/src/console/ui-next/dist/assets/cache-glyph-BeFJeqBG.js +1 -0
  200. package/src/console/ui-next/dist/assets/call-pii-button-CVAONPii.js +1 -0
  201. package/src/console/ui-next/dist/assets/collapsible-D2A6NJ-3.js +1 -0
  202. package/src/console/ui-next/dist/assets/copy-inline-button-CAYD18cr.js +1 -0
  203. package/src/console/ui-next/dist/assets/dagre.esm-B1_XeuLP.js +1 -0
  204. package/src/console/ui-next/dist/assets/detail-header-DVWNjWjg.js +1 -0
  205. package/src/console/ui-next/dist/assets/dropdown-menu-4h2LOVXM.js +1 -0
  206. package/src/console/ui-next/dist/assets/duration-tone-Cgk_h5ja.js +9 -0
  207. package/src/console/ui-next/dist/assets/element-icons-BI8cJgdh.js +1 -0
  208. package/src/console/ui-next/dist/assets/explorer-empty-CJs5A-wm.js +1 -0
  209. package/src/console/ui-next/dist/assets/flows-page-Dss7941e.js +1 -0
  210. package/src/console/ui-next/dist/assets/highlighted-json-MYZQtRnw.js +154 -0
  211. package/src/console/ui-next/dist/assets/http-method-Jrh39p7A.js +1 -0
  212. package/src/console/ui-next/dist/assets/index-D0zS5rKO.css +2 -0
  213. package/src/console/ui-next/dist/assets/index-DXP2dBIF.js +63 -0
  214. package/src/console/ui-next/dist/assets/observability-page-HvolXxTI.js +4 -0
  215. package/src/console/ui-next/dist/assets/react-dom-Ddte4I-Q.js +9 -0
  216. package/src/console/ui-next/dist/assets/replica-lag-CSh2dzrb.js +18 -0
  217. package/src/console/ui-next/dist/assets/request-meta-BatF8KrK.js +1 -0
  218. package/src/console/ui-next/dist/assets/shortcut-keys-3ILd8oGn.js +1 -0
  219. package/src/console/ui-next/dist/assets/{sql-BskegiFM.js → sql-BsFa4tDR.js} +1 -1
  220. package/src/console/ui-next/dist/assets/store-page-eiKiHnNe.js +41 -0
  221. package/src/console/ui-next/dist/assets/trace-detail-sheet-CZkMeKS-.js +2 -0
  222. package/src/console/ui-next/dist/assets/tree-expand-toggle-CW8y5A2h.js +55 -0
  223. package/src/console/ui-next/dist/assets/units-page-B_RWJrEO.js +1 -0
  224. package/src/console/ui-next/dist/assets/{use-vault-list-uk4WVboC.js → use-vault-list-CT4-gajj.js} +1 -1
  225. package/src/console/ui-next/dist/assets/vault-page-CWrg-A68.js +2 -0
  226. package/src/console/ui-next/dist/assets/xyflow-CSyC6ryz.css +1 -0
  227. package/src/console/ui-next/dist/assets/xyflow-yApv7D4e.js +7 -0
  228. package/src/console/ui-next/dist/index.html +6 -13
  229. package/src/console/ui-next/seed-invoke-host.ts +21 -18
  230. package/src/console/ui-next/src/client.ts +7 -2
  231. package/src/console/ui-next/src/features/flows/graph/element-map.ts +2 -0
  232. package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +12 -2
  233. package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +4 -0
  234. package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +8 -0
  235. package/src/console/ui-next/src/features/flows/traces/waterfall-bars.ts +4 -0
  236. package/src/console/ui-next/src/features/flows/traces/waterfall-tooltip.ts +9 -3
  237. package/src/console/ui-next/src/features/store/detail/reveal-cell.tsx +7 -11
  238. package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +2 -2
  239. package/src/console/ui-next/src/features/store/lib/fields-from-table.ts +2 -0
  240. package/src/console/ui-next/src/features/store/lib/grid-model.test.ts +25 -1
  241. package/src/console/ui-next/src/features/store/lib/grid-model.ts +36 -1
  242. package/src/console/ui-next/src/features/store/query/query-results.tsx +3 -3
  243. package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +2 -0
  244. package/src/console/ui-next/src/features/units/lib/call-read-safe.ts +3 -1
  245. package/src/console/ui-next/src/features/units/lib/fields-from-schema.ts +6 -0
  246. package/src/docker/ai-model-status.test.ts +1 -12
  247. package/src/docker/ai-model-status.ts +10 -38
  248. package/src/docker/compose-up.test.ts +100 -0
  249. package/src/docker/compose-up.ts +292 -0
  250. package/src/docker/compose.ts +4 -14
  251. package/src/docker/derive.ts +9 -34
  252. package/src/docker/docker.test.ts +16 -261
  253. package/src/docker/images-config.test.ts +13 -25
  254. package/src/docker/index.ts +1 -24
  255. package/src/docker/recipes/index.ts +0 -23
  256. package/src/docker/recipes/pgdog.ts +6 -4
  257. package/src/docker/stack-id.test.ts +2 -2
  258. package/src/docker/stack-id.ts +0 -1
  259. package/src/docker/types.ts +2 -2
  260. package/src/drivers/ai-anthropic.ts +10 -0
  261. package/src/drivers/ai-openai-compatible.ts +34 -7
  262. package/src/drivers/ai-providers.test.ts +0 -107
  263. package/src/drivers/ai-stream.test.ts +0 -34
  264. package/src/drivers/ai-types.ts +17 -8
  265. package/src/drivers/channel-fcm.ts +6 -1
  266. package/src/drivers/channel-msegat.ts +6 -1
  267. package/src/drivers/channel-resend.ts +5 -1
  268. package/src/drivers/channel-smtp.ts +5 -1
  269. package/src/drivers/channel-sndr.ts +10 -1
  270. package/src/drivers/channel-taqnyat-mail.ts +5 -1
  271. package/src/drivers/channel-taqnyat-whatsapp.ts +6 -1
  272. package/src/drivers/channel-taqnyat.ts +6 -1
  273. package/src/drivers/channel-types.ts +10 -0
  274. package/src/drivers/channel-unifonic.ts +6 -1
  275. package/src/drivers/channel-wa-cloud.ts +6 -1
  276. package/src/drivers/channel-webpush.ts +6 -1
  277. package/src/drivers/external.ts +33 -0
  278. package/src/drivers/index.ts +0 -10
  279. package/src/drivers/meilisearch.ts +6 -0
  280. package/src/drivers/oauth-types.ts +4 -0
  281. package/src/drivers/postgres.test.ts +74 -3
  282. package/src/drivers/postgres.ts +175 -6
  283. package/src/drivers/signal-types.ts +5 -5
  284. package/src/drivers/types.ts +7 -0
  285. package/src/drivers/vault-types.ts +5 -0
  286. package/src/elements/ai/declare.ts +17 -4
  287. package/src/elements/ai/eval.ts +1 -1
  288. package/src/elements/ai/mcp-http.ts +1 -1
  289. package/src/elements/ai/pii.ts +1 -1
  290. package/src/elements/ai/providers.test.ts +289 -0
  291. package/src/elements/ai/providers.ts +169 -0
  292. package/src/elements/ai/runtime.ts +68 -5
  293. package/src/elements/ai/schema.ts +2 -2
  294. package/src/elements/ai.test.ts +7 -7
  295. package/src/elements/ai.ts +17 -1
  296. package/src/elements/channel/runtime.ts +105 -12
  297. package/src/elements/clock/cron-fields.test.ts +144 -0
  298. package/src/elements/clock/cron-fields.ts +185 -0
  299. package/src/elements/clock/declare.ts +247 -3
  300. package/src/elements/clock.ts +21 -1
  301. package/src/elements/index.ts +15 -0
  302. package/src/elements/signal/chaos-child.ts +1 -2
  303. package/src/elements/signal/declare.ts +65 -37
  304. package/src/elements/signal/delivery-modes.test.ts +11 -31
  305. package/src/elements/signal/dry-run-replay.test.ts +1 -7
  306. package/src/elements/signal/dry-run-write-isolation.test.ts +1 -7
  307. package/src/elements/signal/key-ordering.test.ts +6 -26
  308. package/src/elements/signal/lease-reclaim.test.ts +2 -6
  309. package/src/elements/signal/optional-emit.test.ts +6 -9
  310. package/src/elements/signal/order-lifecycle.test.ts +4 -17
  311. package/src/elements/signal/orphan-messages.test.ts +4 -15
  312. package/src/elements/signal/reconcile.test.ts +2 -2
  313. package/src/elements/signal/runtime.ts +1 -1
  314. package/src/elements/signal/schema-emit.test.ts +4 -8
  315. package/src/elements/signal.test.ts +24 -49
  316. package/src/elements/signal.ts +9 -2
  317. package/src/elements/store/domain-ddl.test.ts +1 -1
  318. package/src/elements/store/live-http.test.ts +9 -4
  319. package/src/elements/store/prepare-row.test.ts +123 -0
  320. package/src/elements/store/resource.ts +43 -12
  321. package/src/elements/store/schema-decl.ts +117 -0
  322. package/src/elements/store/search-backfill.ts +205 -0
  323. package/src/elements/store/search-bind.ts +66 -0
  324. package/src/elements/store/search-bm25.ts +78 -0
  325. package/src/elements/store/search-ddl.ts +156 -0
  326. package/src/elements/store/search-embed-flow.ts +141 -0
  327. package/src/elements/store/search-errors.ts +34 -0
  328. package/src/elements/store/search-fusion.ts +112 -0
  329. package/src/elements/store/search-lsh.ts +179 -0
  330. package/src/elements/store/search-runtime.ts +300 -0
  331. package/src/elements/store/search.test.ts +144 -0
  332. package/src/elements/store/sql-session.test.ts +1 -0
  333. package/src/elements/store/sql-session.ts +83 -3
  334. package/src/elements/store/table.ts +23 -2
  335. package/src/elements/store/upsert-app.test.ts +1 -3
  336. package/src/elements/store.ts +50 -0
  337. package/src/full.ts +4 -2
  338. package/src/http.ts +4 -1
  339. package/src/i18n/catalogs/ar.ts +14 -6
  340. package/src/i18n/catalogs/en.ts +14 -6
  341. package/src/index.ts +6 -1
  342. package/src/kernel/adopt-barrel-fresh.test.ts +7 -7
  343. package/src/kernel/adopt-routes.ts +9 -1
  344. package/src/kernel/app-auth.ts +5 -0
  345. package/src/kernel/app.ts +102 -9
  346. package/src/kernel/auto-cache.test.ts +6 -12
  347. package/src/kernel/auto-registry.test.ts +3 -3
  348. package/src/kernel/boot-bind/ai.test.ts +22 -19
  349. package/src/kernel/boot-bind/ai.ts +5 -43
  350. package/src/kernel/boot-bind/clock.ts +23 -5
  351. package/src/kernel/boot-bind/honor-config.test.ts +4 -4
  352. package/src/kernel/boot.test.ts +11 -6
  353. package/src/kernel/boot.ts +66 -16
  354. package/src/kernel/boundary-contract.ts +93 -0
  355. package/src/kernel/budget.test.ts +1 -1
  356. package/src/kernel/call.ts +89 -0
  357. package/src/kernel/capability.ts +6 -0
  358. package/src/kernel/client-descriptor.ts +123 -0
  359. package/src/kernel/clock-timezone.test.ts +80 -0
  360. package/src/kernel/correlation.test.ts +1 -1
  361. package/src/kernel/dry-run.ts +11 -8
  362. package/src/kernel/effects-stamping.test.ts +42 -9
  363. package/src/kernel/effects.test.ts +4 -2
  364. package/src/kernel/effects.ts +51 -9
  365. package/src/kernel/errors-channel.ts +17 -0
  366. package/src/kernel/errors-live-resume.ts +3 -2
  367. package/src/kernel/errors-tenant.ts +7 -4
  368. package/src/kernel/errors.registry-helpers.ts +96 -0
  369. package/src/kernel/errors.registry.test.ts +139 -25
  370. package/src/kernel/errors.ts +102 -23
  371. package/src/kernel/external-effects.test.ts +196 -0
  372. package/src/kernel/flow.test.ts +13 -4
  373. package/src/kernel/flow.ts +69 -71
  374. package/src/kernel/fx-ask-telemetry.test.ts +2 -2
  375. package/src/kernel/fx-dead-letters.test.ts +3 -3
  376. package/src/kernel/fx-fetch.ts +55 -0
  377. package/src/kernel/fx-live-stream.ts +2 -2
  378. package/src/kernel/fx-live.test.ts +8 -8
  379. package/src/kernel/fx.test.ts +17 -11
  380. package/src/kernel/fx.ts +180 -57
  381. package/src/kernel/horizontal-child.ts +1 -1
  382. package/src/kernel/http-query.test.ts +3 -6
  383. package/src/kernel/index.ts +13 -3
  384. package/src/kernel/instance-id.ts +1 -1
  385. package/src/kernel/instances.test.ts +40 -0
  386. package/src/kernel/instances.ts +47 -9
  387. package/src/kernel/live-http.test.ts +6 -7
  388. package/src/kernel/live-http.ts +6 -2
  389. package/src/kernel/live-resume.test.ts +1 -1
  390. package/src/kernel/mcp-tool.test.ts +3 -3
  391. package/src/kernel/on.ts +98 -4
  392. package/src/kernel/pipeline.test.ts +12 -15
  393. package/src/kernel/plugin-elements.test.ts +1 -1
  394. package/src/kernel/stamp-http.test.ts +3 -3
  395. package/src/kernel/triggers.ts +210 -95
  396. package/src/kernel-entry.ts +0 -1
  397. package/src/manifest/diff.ts +11 -1
  398. package/src/manifest/fixtures/skyport.excerpt.json +1 -1
  399. package/src/manifest/types.ts +46 -2
  400. package/src/mcp/docs-index.ts +3 -3
  401. package/src/mcp/docs-mcp.test.ts +2 -2
  402. package/src/mcp/docs-tools.ts +1 -1
  403. package/src/okid.test.ts +56 -0
  404. package/src/okid.ts +55 -11
  405. package/src/plugins/anonymous.ts +8 -4
  406. package/src/plugins/auth/shared.ts +27 -3
  407. package/src/plugins/auth-delivery.mailpit.integration.test.ts +1 -1
  408. package/src/plugins/auth-methods.security.test.ts +561 -23
  409. package/src/plugins/config-source.ts +1 -1
  410. package/src/plugins/csrf.test.ts +65 -0
  411. package/src/plugins/index.ts +2 -0
  412. package/src/plugins/magic-link.ts +36 -16
  413. package/src/plugins/oauth/shared.ts +3 -1
  414. package/src/plugins/oauth.ts +22 -14
  415. package/src/plugins/otp.ts +37 -21
  416. package/src/plugins/passkey-webauthn.ts +9 -6
  417. package/src/plugins/passkey.ts +98 -26
  418. package/src/plugins/pre-account-hijack.test.ts +223 -0
  419. package/src/plugins/two-factor.ts +466 -37
  420. package/src/plugins/username.ts +19 -7
  421. package/src/release/build-lib.ts +1 -0
  422. package/src/release/limits.ts +2 -2
  423. package/src/release/measure.ts +1 -1
  424. package/src/runs/collect.test.ts +0 -2
  425. package/src/runtime/json-code-block.test.ts +250 -20
  426. package/src/runtime/json-code-block.ts +1261 -41
  427. package/src/term.test.ts +1 -1
  428. package/src/test/create-test-app.test.ts +12 -12
  429. package/src/test/live-signals.test.ts +6 -6
  430. package/src/test/provisions.integration.test.ts +10 -14
  431. package/src/test/reset-element-registries.ts +1 -1
  432. package/src/test/tenant-isolation.test.ts +12 -6
  433. package/site/content/docs/deployment/docker-swarm.mdx +0 -164
  434. package/site/content/docs/deployment/docker.mdx +0 -227
  435. package/site/content/docs/deployment/index.mdx +0 -83
  436. package/site/content/docs/deployment/kubernetes.mdx +0 -176
  437. package/site/content/docs/deployment/meta.json +0 -5
  438. package/site/content/docs/deployment/reverse-proxy.mdx +0 -234
  439. package/site/content/docs/elements/ai.mdx +0 -385
  440. package/site/content/docs/elements/channel.mdx +0 -346
  441. package/site/content/docs/elements/clock.mdx +0 -244
  442. package/site/content/docs/elements/flow.mdx +0 -420
  443. package/site/content/docs/elements/gate.mdx +0 -436
  444. package/site/content/docs/elements/signal.mdx +0 -380
  445. package/site/content/docs/elements/store.mdx +0 -1099
  446. package/site/content/docs/elements/vault.mdx +0 -405
  447. package/site/content/docs/get-started/basic-usage.mdx +0 -173
  448. package/site/content/docs/get-started/index.mdx +0 -43
  449. package/site/content/docs/get-started/installation.mdx +0 -220
  450. package/site/content/docs/get-started/introduction.mdx +0 -132
  451. package/site/content/docs/get-started/meta.json +0 -13
  452. package/site/content/docs/get-started/project-structure.mdx +0 -925
  453. package/site/content/docs/get-started/testing.mdx +0 -328
  454. package/site/content/docs/get-started/why.mdx +0 -162
  455. package/site/content/docs/recipes/llama-cpp.mdx +0 -151
  456. package/site/content/docs/recipes/ollama.mdx +0 -142
  457. package/site/content/docs/recipes/sglang.mdx +0 -105
  458. package/site/content/docs/recipes/vllm.mdx +0 -106
  459. package/site/content/docs/reference/cli.md +0 -232
  460. package/site/content/docs/reference/client.mdx +0 -469
  461. package/site/content/docs/reference/security.md +0 -74
  462. package/src/cli/ai-setup/detect-ollama.ts +0 -213
  463. package/src/cli/ai-setup/recommend.test.ts +0 -225
  464. package/src/cli/ai-setup/recommend.ts +0 -225
  465. package/src/cli/dev-controls.test.ts +0 -122
  466. package/src/cli/dev-controls.ts +0 -164
  467. package/src/cli/tui/DevLive.tsx +0 -126
  468. package/src/console/ui-next/dist/assets/access-page-De7Lc2JC.js +0 -4
  469. package/src/console/ui-next/dist/assets/agent-disclosure-BP0Y0Sux.js +0 -1
  470. package/src/console/ui-next/dist/assets/cache-glyph-B5X-NM0-.js +0 -1
  471. package/src/console/ui-next/dist/assets/call-pii-button-ChAUiLo9.js +0 -1
  472. package/src/console/ui-next/dist/assets/collapsible-DGnOM2ph.js +0 -1
  473. package/src/console/ui-next/dist/assets/copy-inline-button-DBHhgHKP.js +0 -1
  474. package/src/console/ui-next/dist/assets/dagre.esm-ZwcdTuZZ.js +0 -1
  475. package/src/console/ui-next/dist/assets/detail-header-DhHM1iaZ.js +0 -1
  476. package/src/console/ui-next/dist/assets/dropdown-menu-_NjTEo5_.js +0 -1
  477. package/src/console/ui-next/dist/assets/duration-tone-sC3lGABz.js +0 -9
  478. package/src/console/ui-next/dist/assets/element-icons-BVXtRyd3.js +0 -1
  479. package/src/console/ui-next/dist/assets/explorer-empty-2uIhBu0_.js +0 -1
  480. package/src/console/ui-next/dist/assets/flows-page-RGy7VEA_.js +0 -1
  481. package/src/console/ui-next/dist/assets/highlighted-json-Awq7gYdu.js +0 -154
  482. package/src/console/ui-next/dist/assets/http-method-_UHM2ODJ.js +0 -1
  483. package/src/console/ui-next/dist/assets/index-Ck88Jmv8.css +0 -2
  484. package/src/console/ui-next/dist/assets/index-_rgpdVzo.js +0 -66
  485. package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +0 -1
  486. package/src/console/ui-next/dist/assets/observability-page-Ds6pcnh-.js +0 -4
  487. package/src/console/ui-next/dist/assets/preload-helper-oH4irX4C.js +0 -1
  488. package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +0 -1
  489. package/src/console/ui-next/dist/assets/react-dom-Bph1y7z7.js +0 -9
  490. package/src/console/ui-next/dist/assets/replica-lag-DKRrbvdo.js +0 -18
  491. package/src/console/ui-next/dist/assets/request-meta-DV0ywz7t.js +0 -1
  492. package/src/console/ui-next/dist/assets/shortcut-JQIZlWfm.js +0 -1
  493. package/src/console/ui-next/dist/assets/shortcut-keys-DKxNTe_m.js +0 -1
  494. package/src/console/ui-next/dist/assets/skeleton-D-czQJT6.js +0 -1
  495. package/src/console/ui-next/dist/assets/store-page-02xOiqIK.js +0 -41
  496. package/src/console/ui-next/dist/assets/trace-detail-sheet-B09O8rA6.js +0 -2
  497. package/src/console/ui-next/dist/assets/tree-expand-toggle-BtyhmWb4.js +0 -55
  498. package/src/console/ui-next/dist/assets/units-page-4rHOePuE.js +0 -1
  499. package/src/console/ui-next/dist/assets/useMutation-B8EO02Ej.js +0 -1
  500. package/src/console/ui-next/dist/assets/useRender-BE2A9BWC.js +0 -1
  501. package/src/console/ui-next/dist/assets/vault-page-DISPgxLM.js +0 -2
  502. package/src/console/ui-next/dist/assets/xyflow-D7n4g6go.js +0 -7
  503. package/src/console/ui-next/dist/assets/xyflow-DZ0Ws1xk.css +0 -1
  504. package/src/docker/ollama-pull.ts +0 -232
  505. package/src/docker/recipes/llama-cpp.ts +0 -298
  506. package/src/docker/recipes/ollama.ts +0 -44
  507. package/src/docker/recipes/sglang.ts +0 -55
  508. package/src/docker/recipes/vllm.ts +0 -44
  509. package/src/drivers/ai-ollama-tools.integration.test.ts +0 -109
  510. package/src/drivers/ai-ollama.integration.test.ts +0 -184
  511. package/src/drivers/ai-ollama.ts +0 -389
  512. package/src/drivers/ollama.ts +0 -14
@@ -0,0 +1,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
+ });