okengine 0.18.5 → 0.19.0

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