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,636 @@
1
+ /**
2
+ * Secure-by-default auth orchestration over typed `/auth/*` Flows.
3
+ *
4
+ * @module
5
+ */
6
+
7
+ import { isSessionTokens, isTwoFactorRequired } from "./denials.ts";
8
+ import { memorySession, type MemorySession, type SessionUser } from "./session.ts";
9
+
10
+ /** Auth transport mode. */
11
+ export type AuthMode = "bearer" | "cookie";
12
+
13
+ /** Bearer persist strategy. */
14
+ export type AuthPersist = "memory" | "sessionStorage" | "localStorage";
15
+
16
+ /** Options for {@link createAuthClient} / `createClient({ auth })`. */
17
+ export interface CreateAuthClientOptions {
18
+ /**
19
+ * Session transport. Default: `"bearer"`.
20
+ * Prefer `"cookie"` when `gate.auth.cookies.enabled` (HttpOnly).
21
+ */
22
+ readonly mode?: AuthMode;
23
+ /**
24
+ * Bearer token storage. Default `"memory"`.
25
+ * `"localStorage"` is an explicit XSS tradeoff — never silent.
26
+ * Forbidden when `mode: "cookie"`.
27
+ */
28
+ readonly persist?: AuthPersist;
29
+ /** Storage key for bearer persist (default `oke.session`). */
30
+ readonly storageKey?: string;
31
+ /**
32
+ * Tenant header name for {@link AuthClient.setTenant} (default `x-oke-tenant`).
33
+ */
34
+ readonly tenantHeader?: string;
35
+ /**
36
+ * When `mode: "cookie"` and this is not `true`, warn in development that
37
+ * the `csrf` plugin should be installed (`allowNoHeader: false` for cookie-only).
38
+ */
39
+ readonly csrfConfigured?: boolean;
40
+ /** Custom session bag (tests). Ignored in cookie mode. */
41
+ readonly session?: MemorySession;
42
+ /** Override `globalThis` for Storage / warn. */
43
+ readonly env?: {
44
+ readonly localStorage?: Storage;
45
+ readonly sessionStorage?: Storage;
46
+ readonly warn?: (message: string) => void;
47
+ };
48
+ /**
49
+ * SSR / custom Bearer override. Cookie mode still sends credentials;
50
+ * this is ignored for Authorization when `mode: "cookie"` unless you
51
+ * need a non-null getToken for tests.
52
+ */
53
+ readonly getToken?: () => string | null | undefined | Promise<string | null | undefined>;
54
+ /** SSR / custom refresh after HTTP 401. */
55
+ readonly refresh?: () => Promise<string | null | undefined>;
56
+ }
57
+
58
+ /** UI authorize query — all required, or any sufficient. */
59
+ export type AuthorizeQuery =
60
+ | { readonly all: readonly string[]; readonly any?: undefined }
61
+ | { readonly any: readonly string[]; readonly all?: undefined };
62
+
63
+ /** UI authorize result — never a security boundary (Gate on Flows is). */
64
+ export type AuthorizeResult =
65
+ | { readonly status: "loading" }
66
+ | { readonly status: "unauthenticated" }
67
+ | { readonly status: "allowed" }
68
+ | { readonly status: "denied"; readonly missing: readonly string[] };
69
+
70
+ /** Minimal typed client surface used by the auth helper. */
71
+ export interface AuthApi {
72
+ auth: {
73
+ refresh?: (input: { refreshToken: string }) => Promise<{
74
+ data: {
75
+ accessToken: string;
76
+ refreshToken: string;
77
+ accessExpiresAt?: number;
78
+ userId?: string;
79
+ } | null;
80
+ error: unknown;
81
+ }>;
82
+ revoke?: (input?: { refreshToken?: string }) => Promise<{
83
+ data: unknown;
84
+ error: unknown;
85
+ }>;
86
+ me?: (input?: unknown) => Promise<{
87
+ data: {
88
+ userId: string;
89
+ email: string;
90
+ name: string;
91
+ emailVerified?: boolean;
92
+ scopes?: readonly string[];
93
+ tenantId?: string | null;
94
+ apiKeyId?: string | null;
95
+ sessionFresh?: boolean;
96
+ } | null;
97
+ error: unknown;
98
+ }>;
99
+ signInEmail?: (input: {
100
+ email: string;
101
+ password: string;
102
+ }) => Promise<{ data: unknown; error: unknown }>;
103
+ signUpEmail?: (input: {
104
+ email: string;
105
+ password: string;
106
+ name?: string;
107
+ }) => Promise<{ data: unknown; error: unknown }>;
108
+ signInUsername?: (input: {
109
+ username: string;
110
+ password: string;
111
+ }) => Promise<{ data: unknown; error: unknown }>;
112
+ signUpUsername?: (input: {
113
+ username: string;
114
+ password: string;
115
+ name?: string;
116
+ }) => Promise<{ data: unknown; error: unknown }>;
117
+ signInAnonymous?: (input?: unknown) => Promise<{ data: unknown; error: unknown }>;
118
+ oauthStart?: (input: { provider: string }) => Promise<{
119
+ data: { authorizationUrl: string; provider: string } | null;
120
+ error: unknown;
121
+ }>;
122
+ requestMagicLink?: (input: { email: string }) => Promise<{ data: unknown; error: unknown }>;
123
+ verifyMagicLink?: (input: { token: string }) => Promise<{ data: unknown; error: unknown }>;
124
+ requestOtp?: (input: Record<string, unknown>) => Promise<{ data: unknown; error: unknown }>;
125
+ verifyOtp?: (input: Record<string, unknown>) => Promise<{ data: unknown; error: unknown }>;
126
+ resendOtp?: (input: Record<string, unknown>) => Promise<{ data: unknown; error: unknown }>;
127
+ passkeyAuthenticateOptions?: (input?: {
128
+ email?: string;
129
+ }) => Promise<{ data: unknown; error: unknown }>;
130
+ passkeyAuthenticate?: (input: Record<string, unknown>) => Promise<{
131
+ data: unknown;
132
+ error: unknown;
133
+ }>;
134
+ passkeyRegisterOptions?: (input?: unknown) => Promise<{ data: unknown; error: unknown }>;
135
+ passkeyRegister?: (
136
+ input: Record<string, unknown>,
137
+ ) => Promise<{ data: unknown; error: unknown }>;
138
+ twoFactorVerify?: (input: {
139
+ challengeId: string;
140
+ code: string;
141
+ }) => Promise<{ data: unknown; error: unknown }>;
142
+ twoFactorStepUp?: (
143
+ input: Record<string, unknown>,
144
+ ) => Promise<{ data: unknown; error: unknown }>;
145
+ [key: string]: unknown;
146
+ };
147
+ }
148
+
149
+ /** Result of a sign-in attempt. */
150
+ export type SignInResult =
151
+ | { readonly ok: true; readonly user: SessionUser | null }
152
+ | {
153
+ readonly ok: false;
154
+ readonly error: unknown;
155
+ readonly twoFactor?: {
156
+ readonly challengeId: string;
157
+ readonly method: string;
158
+ readonly userId: string;
159
+ };
160
+ };
161
+
162
+ /** Auth client — convenience over existing Flows. */
163
+ export interface AuthClient {
164
+ readonly mode: AuthMode;
165
+ /** Session bag (memory; Storage only for explicit bearer persist). */
166
+ readonly session: MemorySession;
167
+ /**
168
+ * Rebind Flow calls to a credentialed `createClient` instance
169
+ * (after spreading {@link clientOptions}).
170
+ */
171
+ bind(api: AuthApi): void;
172
+ /** Options to spread into {@link createClient}. */
173
+ readonly clientOptions: {
174
+ readonly credentials?: "include" | "omit" | "same-origin";
175
+ readonly auth?: {
176
+ readonly getToken: () => string | null | undefined | Promise<string | null | undefined>;
177
+ readonly refresh: () => Promise<string | null | undefined>;
178
+ };
179
+ readonly headers?: () => Record<string, string>;
180
+ };
181
+ /** UI-only — never a security boundary. Gate on Flows is real authz. */
182
+ hasScope(scope: string): boolean;
183
+ /** UI-only — all scopes required. */
184
+ can(...scopes: string[]): boolean;
185
+ /** UI-only — any scope. */
186
+ hasAnyScope(...scopes: string[]): boolean;
187
+ /**
188
+ * UI-only authorize helper (loading / unauthenticated / allowed / denied).
189
+ * Prefer this over bare `hasScope` for chrome; Gate on Flows remains real authz.
190
+ */
191
+ authorize(query: AuthorizeQuery): AuthorizeResult;
192
+ getSession(): Promise<SessionUser | null>;
193
+ subscribe(listener: () => void): () => void;
194
+ signOut(): Promise<void>;
195
+ setTenant(tenantId: string | null): void;
196
+ switchTenant(
197
+ tenantId: string,
198
+ switchFlow?: (input: { tenantId: string }) => Promise<{ data: unknown; error: unknown }>,
199
+ ): Promise<{ data: unknown; error: unknown }>;
200
+ readonly signIn: {
201
+ email(input: { email: string; password: string }): Promise<SignInResult>;
202
+ username(input: { username: string; password: string }): Promise<SignInResult>;
203
+ anonymous(): Promise<SignInResult>;
204
+ social(input: { provider: string; assign?: (url: string) => void }): Promise<SignInResult>;
205
+ magicLink: {
206
+ request(input: { email: string }): Promise<{ data: unknown; error: unknown }>;
207
+ verify(input: { token: string }): Promise<SignInResult>;
208
+ };
209
+ otp: {
210
+ request(input: Record<string, unknown>): Promise<{ data: unknown; error: unknown }>;
211
+ verify(input: Record<string, unknown>): Promise<SignInResult>;
212
+ resend(input: Record<string, unknown>): Promise<{ data: unknown; error: unknown }>;
213
+ };
214
+ passkey(input?: { email?: string }): Promise<SignInResult>;
215
+ };
216
+ readonly signUp: {
217
+ email(input: { email: string; password: string; name?: string }): Promise<SignInResult>;
218
+ username(input: { username: string; password: string; name?: string }): Promise<SignInResult>;
219
+ };
220
+ completeChallenge(input: { challengeId: string; code: string }): Promise<SignInResult>;
221
+ }
222
+
223
+ /**
224
+ * Create a secure-by-default auth helper over an existing typed client.
225
+ *
226
+ * @param api - Client from {@link createClient} (must expose `auth.*` Flows)
227
+ * @param options - Mode / persist / CSRF checklist
228
+ */
229
+ export function createAuthClient(api: AuthApi, options: CreateAuthClientOptions = {}): AuthClient {
230
+ let apiRef = api;
231
+ const mode: AuthMode = options.mode ?? "bearer";
232
+ const persist: AuthPersist = options.persist ?? "memory";
233
+ const warn = options.env?.warn ?? ((m: string) => console.warn(m));
234
+
235
+ if (mode === "cookie" && options.persist !== undefined && options.persist !== "memory") {
236
+ throw new Error(
237
+ 'okengine/client/auth: cookie mode cannot persist tokens to Storage (dual CSRF+XSS). Use mode: "cookie" alone.',
238
+ );
239
+ }
240
+ if (mode === "cookie" && options.csrfConfigured !== true) {
241
+ warn(
242
+ "[okengine/client/auth] cookie mode: install the csrf plugin (allowNoHeader: false for cookie-only) and cors with credentials when cross-origin.",
243
+ );
244
+ }
245
+ if (persist === "localStorage") {
246
+ warn(
247
+ '[okengine/client/auth] persist: "localStorage" stores access/refresh where XSS can read them. Prefer cookie mode or memory / sessionStorage.',
248
+ );
249
+ }
250
+
251
+ /**
252
+ * Cookie mode keeps a memory-only refresh handle for `/auth/refresh` + revoke
253
+ * (HttpOnly refresh cookie is not JS-readable). Access rides on credentials cookies —
254
+ * never write tokens to Storage and never send Bearer when cookies own the session.
255
+ */
256
+ const session: MemorySession =
257
+ options.session ??
258
+ (mode === "cookie"
259
+ ? memorySession()
260
+ : memorySession(
261
+ persist === "memory"
262
+ ? undefined
263
+ : {
264
+ storage:
265
+ persist === "localStorage"
266
+ ? (options.env?.localStorage ?? globalThis.localStorage)
267
+ : (options.env?.sessionStorage ?? globalThis.sessionStorage),
268
+ key: options.storageKey,
269
+ },
270
+ ));
271
+
272
+ let tenantId: string | null = session.tenantId ?? null;
273
+ const tenantHeader = options.tenantHeader ?? "x-oke-tenant";
274
+ /** False until first getSession / sign-in / sign-out — drives authorize("loading"). */
275
+ let sessionResolved = session.user !== null || (mode === "bearer" && session.getToken() !== null);
276
+
277
+ async function applyAuthResult(data: unknown, error: unknown): Promise<SignInResult> {
278
+ sessionResolved = true;
279
+ if (error) return { ok: false, error };
280
+ if (isTwoFactorRequired(data)) {
281
+ return {
282
+ ok: false,
283
+ error: null,
284
+ twoFactor: {
285
+ challengeId: data.challengeId,
286
+ method: data.method,
287
+ userId: data.userId,
288
+ },
289
+ };
290
+ }
291
+ if (isSessionTokens(data)) {
292
+ session.set({
293
+ accessToken: data.accessToken,
294
+ refreshToken: data.refreshToken,
295
+ accessExpiresAt: data.accessExpiresAt,
296
+ userId: data.userId,
297
+ tenantId,
298
+ });
299
+ const user = await refreshMe();
300
+ return { ok: true, user };
301
+ }
302
+ return { ok: false, error: { code: "AuthFailed", data: { reason: "unexpected_response" } } };
303
+ }
304
+
305
+ async function refreshMe(): Promise<SessionUser | null> {
306
+ sessionResolved = true;
307
+ if (!apiRef.auth.me) return session.user ?? null;
308
+ const { data, error } = await apiRef.auth.me({});
309
+ if (error || !data) {
310
+ session.setUser(null);
311
+ return null;
312
+ }
313
+ const user: SessionUser = {
314
+ userId: data.userId,
315
+ email: data.email,
316
+ name: data.name,
317
+ emailVerified: data.emailVerified,
318
+ scopes: data.scopes ?? [],
319
+ tenantId: data.tenantId ?? tenantId,
320
+ apiKeyId: data.apiKeyId ?? null,
321
+ ...(data.sessionFresh !== undefined ? { sessionFresh: data.sessionFresh } : {}),
322
+ };
323
+ session.setUser(user);
324
+ if (user.tenantId !== undefined) tenantId = user.tenantId;
325
+ return user;
326
+ }
327
+
328
+ function scopesOf(): readonly string[] {
329
+ return session.user?.scopes ?? session.scopes ?? [];
330
+ }
331
+
332
+ function authorize(query: AuthorizeQuery): AuthorizeResult {
333
+ if (!sessionResolved) {
334
+ if (mode === "bearer" && !session.getToken()) return { status: "unauthenticated" };
335
+ return { status: "loading" };
336
+ }
337
+ if (!session.user) return { status: "unauthenticated" };
338
+ const have = new Set(scopesOf());
339
+ if ("all" in query && query.all) {
340
+ const missing = query.all.filter((s) => !have.has(s));
341
+ return missing.length === 0 ? { status: "allowed" } : { status: "denied", missing };
342
+ }
343
+ if ("any" in query && query.any) {
344
+ const ok = query.any.some((s) => have.has(s));
345
+ return ok ? { status: "allowed" } : { status: "denied", missing: [...query.any] };
346
+ }
347
+ return { status: "denied", missing: [] };
348
+ }
349
+
350
+ const signIn = {
351
+ async email(input: { email: string; password: string }): Promise<SignInResult> {
352
+ if (!apiRef.auth.signInEmail) {
353
+ return { ok: false, error: { code: "AuthFailed", data: { reason: "method_unavailable" } } };
354
+ }
355
+ const { data, error } = await apiRef.auth.signInEmail(input);
356
+ return applyAuthResult(data, error);
357
+ },
358
+ async username(input: { username: string; password: string }): Promise<SignInResult> {
359
+ if (!apiRef.auth.signInUsername) {
360
+ return { ok: false, error: { code: "AuthFailed", data: { reason: "method_unavailable" } } };
361
+ }
362
+ const { data, error } = await apiRef.auth.signInUsername(input);
363
+ return applyAuthResult(data, error);
364
+ },
365
+ async anonymous(): Promise<SignInResult> {
366
+ if (!apiRef.auth.signInAnonymous) {
367
+ return { ok: false, error: { code: "AuthFailed", data: { reason: "method_unavailable" } } };
368
+ }
369
+ const { data, error } = await apiRef.auth.signInAnonymous({});
370
+ return applyAuthResult(data, error);
371
+ },
372
+ async social(input: {
373
+ provider: string;
374
+ assign?: (url: string) => void;
375
+ }): Promise<SignInResult> {
376
+ if (!apiRef.auth.oauthStart) {
377
+ return { ok: false, error: { code: "AuthFailed", data: { reason: "method_unavailable" } } };
378
+ }
379
+ const { data, error } = await apiRef.auth.oauthStart({ provider: input.provider });
380
+ if (error || !data?.authorizationUrl) return { ok: false, error: error ?? null };
381
+ const assign =
382
+ input.assign ??
383
+ ((url: string) => {
384
+ const loc = (globalThis as { location?: { assign?: (href: string) => void } }).location;
385
+ loc?.assign?.(url);
386
+ });
387
+ assign(data.authorizationUrl);
388
+ return { ok: true, user: null };
389
+ },
390
+ magicLink: {
391
+ async request(input: { email: string }) {
392
+ if (!apiRef.auth.requestMagicLink) {
393
+ return {
394
+ data: null,
395
+ error: { code: "AuthFailed", data: { reason: "method_unavailable" } },
396
+ };
397
+ }
398
+ return apiRef.auth.requestMagicLink(input);
399
+ },
400
+ async verify(input: { token: string }): Promise<SignInResult> {
401
+ if (!apiRef.auth.verifyMagicLink) {
402
+ return {
403
+ ok: false,
404
+ error: { code: "AuthFailed", data: { reason: "method_unavailable" } },
405
+ };
406
+ }
407
+ const { data, error } = await apiRef.auth.verifyMagicLink(input);
408
+ return applyAuthResult(data, error);
409
+ },
410
+ },
411
+ otp: {
412
+ async request(input: Record<string, unknown>) {
413
+ if (!apiRef.auth.requestOtp) {
414
+ return {
415
+ data: null,
416
+ error: { code: "AuthFailed", data: { reason: "method_unavailable" } },
417
+ };
418
+ }
419
+ return apiRef.auth.requestOtp(input);
420
+ },
421
+ async verify(input: Record<string, unknown>): Promise<SignInResult> {
422
+ if (!apiRef.auth.verifyOtp) {
423
+ return {
424
+ ok: false,
425
+ error: { code: "AuthFailed", data: { reason: "method_unavailable" } },
426
+ };
427
+ }
428
+ const { data, error } = await apiRef.auth.verifyOtp(input);
429
+ return applyAuthResult(data, error);
430
+ },
431
+ async resend(input: Record<string, unknown>) {
432
+ if (!apiRef.auth.resendOtp) {
433
+ return {
434
+ data: null,
435
+ error: { code: "AuthFailed", data: { reason: "method_unavailable" } },
436
+ };
437
+ }
438
+ return apiRef.auth.resendOtp(input);
439
+ },
440
+ },
441
+ async passkey(input?: { email?: string }): Promise<SignInResult> {
442
+ return signInWithPasskey(apiRef, input, applyAuthResult);
443
+ },
444
+ };
445
+
446
+ const signUp = {
447
+ async email(input: { email: string; password: string; name?: string }): Promise<SignInResult> {
448
+ if (!apiRef.auth.signUpEmail) {
449
+ return { ok: false, error: { code: "AuthFailed", data: { reason: "method_unavailable" } } };
450
+ }
451
+ const { data, error } = await apiRef.auth.signUpEmail(input);
452
+ return applyAuthResult(data, error);
453
+ },
454
+ async username(input: {
455
+ username: string;
456
+ password: string;
457
+ name?: string;
458
+ }): Promise<SignInResult> {
459
+ if (!apiRef.auth.signUpUsername) {
460
+ return { ok: false, error: { code: "AuthFailed", data: { reason: "method_unavailable" } } };
461
+ }
462
+ const { data, error } = await apiRef.auth.signUpUsername(input);
463
+ return applyAuthResult(data, error);
464
+ },
465
+ };
466
+
467
+ return {
468
+ mode,
469
+ session,
470
+ bind(next) {
471
+ apiRef = next;
472
+ },
473
+ clientOptions: {
474
+ ...(mode === "cookie" ? { credentials: "include" as const } : {}),
475
+ auth: {
476
+ // Cookie mode: access is HttpOnly — do not attach Bearer (avoids dual surface).
477
+ getToken: options.getToken ?? (() => (mode === "cookie" ? null : session.getToken())),
478
+ refresh: options.refresh ?? (() => session.refresh(apiRef as never)),
479
+ },
480
+ headers: () => {
481
+ const h: Record<string, string> = {};
482
+ if (tenantId) h[tenantHeader] = tenantId;
483
+ return h;
484
+ },
485
+ },
486
+ hasScope(scope: string) {
487
+ return scopesOf().includes(scope);
488
+ },
489
+ can(...scopes: string[]) {
490
+ const have = new Set(scopesOf());
491
+ return scopes.every((s) => have.has(s));
492
+ },
493
+ hasAnyScope(...scopes: string[]) {
494
+ const have = new Set(scopesOf());
495
+ return scopes.some((s) => have.has(s));
496
+ },
497
+ authorize,
498
+ getSession: refreshMe,
499
+ subscribe(listener) {
500
+ return session.subscribe(listener);
501
+ },
502
+ async signOut() {
503
+ sessionResolved = true;
504
+ if (apiRef.auth.revoke) {
505
+ const refreshToken = session.refreshToken;
506
+ await apiRef.auth.revoke(refreshToken ? { refreshToken } : {});
507
+ }
508
+ session.clear();
509
+ tenantId = null;
510
+ },
511
+ setTenant(id) {
512
+ tenantId = id;
513
+ session.tenantId = id;
514
+ },
515
+ async switchTenant(id, switchFlow) {
516
+ if (!switchFlow) {
517
+ this.setTenant(id);
518
+ return { data: { tenantId: id }, error: null };
519
+ }
520
+ const result = await switchFlow({ tenantId: id });
521
+ if (!result.error) this.setTenant(id);
522
+ return result;
523
+ },
524
+ signIn,
525
+ signUp,
526
+ async completeChallenge(input) {
527
+ if (!apiRef.auth.twoFactorVerify) {
528
+ return { ok: false, error: { code: "AuthFailed", data: { reason: "method_unavailable" } } };
529
+ }
530
+ const { data, error } = await apiRef.auth.twoFactorVerify(input);
531
+ return applyAuthResult(data, error);
532
+ },
533
+ };
534
+ }
535
+
536
+ async function signInWithPasskey(
537
+ api: AuthApi,
538
+ input: { email?: string } | undefined,
539
+ applyAuthResult: (data: unknown, error: unknown) => Promise<SignInResult>,
540
+ ): Promise<SignInResult> {
541
+ if (!api.auth.passkeyAuthenticateOptions || !api.auth.passkeyAuthenticate) {
542
+ return { ok: false, error: { code: "AuthFailed", data: { reason: "method_unavailable" } } };
543
+ }
544
+ const opts = await api.auth.passkeyAuthenticateOptions(
545
+ input?.email ? { email: input.email } : {},
546
+ );
547
+ if (opts.error || !opts.data || typeof opts.data !== "object") {
548
+ return { ok: false, error: opts.error };
549
+ }
550
+ const o = opts.data as {
551
+ challenge: string;
552
+ sessionId: string;
553
+ rpId: string;
554
+ allowCredentials: readonly string[];
555
+ };
556
+ const nav = (
557
+ globalThis as {
558
+ navigator?: {
559
+ credentials?: {
560
+ get: (options: unknown) => Promise<unknown>;
561
+ };
562
+ };
563
+ }
564
+ ).navigator;
565
+ if (!nav?.credentials?.get) {
566
+ return { ok: false, error: { code: "AuthFailed", data: { reason: "passkey_unavailable" } } };
567
+ }
568
+ const cred = await nav.credentials.get({
569
+ publicKey: {
570
+ challenge: bufferFromBase64Url(o.challenge),
571
+ rpId: o.rpId,
572
+ userVerification: "required",
573
+ allowCredentials: o.allowCredentials.map((id) => ({
574
+ type: "public-key" as const,
575
+ id: bufferFromBase64Url(id),
576
+ })),
577
+ },
578
+ });
579
+ if (!isPasskeyAssertion(cred)) {
580
+ return { ok: false, error: { code: "AuthFailed", data: { reason: "passkey_cancelled" } } };
581
+ }
582
+ const { data, error } = await api.auth.passkeyAuthenticate({
583
+ credentialId: base64UrlFromBuffer(cred.rawId),
584
+ clientDataJSON: base64UrlFromBuffer(cred.response.clientDataJSON),
585
+ authenticatorData: base64UrlFromBuffer(cred.response.authenticatorData),
586
+ signature: base64UrlFromBuffer(cred.response.signature),
587
+ challenge: o.challenge,
588
+ sessionId: o.sessionId,
589
+ ...(input?.email ? { email: input.email } : {}),
590
+ });
591
+ return applyAuthResult(data, error);
592
+ }
593
+
594
+ /** Duck-type WebAuthn assertion without DOM `PublicKeyCredential` lib. */
595
+ function isPasskeyAssertion(value: unknown): value is {
596
+ readonly rawId: ArrayBuffer;
597
+ readonly response: {
598
+ readonly clientDataJSON: ArrayBuffer;
599
+ readonly authenticatorData: ArrayBuffer;
600
+ readonly signature: ArrayBuffer;
601
+ };
602
+ } {
603
+ if (value === null || typeof value !== "object") return false;
604
+ const bag = value as {
605
+ rawId?: unknown;
606
+ response?: {
607
+ clientDataJSON?: unknown;
608
+ authenticatorData?: unknown;
609
+ signature?: unknown;
610
+ };
611
+ };
612
+ return (
613
+ bag.rawId instanceof ArrayBuffer &&
614
+ bag.response !== null &&
615
+ typeof bag.response === "object" &&
616
+ bag.response.clientDataJSON instanceof ArrayBuffer &&
617
+ bag.response.authenticatorData instanceof ArrayBuffer &&
618
+ bag.response.signature instanceof ArrayBuffer
619
+ );
620
+ }
621
+
622
+ function bufferFromBase64Url(value: string): ArrayBuffer {
623
+ const pad = "=".repeat((4 - (value.length % 4)) % 4);
624
+ const b64 = (value + pad).replace(/-/g, "+").replace(/_/g, "/");
625
+ const bin = atob(b64);
626
+ const bytes = new Uint8Array(bin.length);
627
+ for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
628
+ return bytes.buffer;
629
+ }
630
+
631
+ function base64UrlFromBuffer(buf: ArrayBuffer): string {
632
+ const bytes = new Uint8Array(buf);
633
+ let s = "";
634
+ for (const b of bytes) s += String.fromCharCode(b);
635
+ return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
636
+ }