okengine 0.18.5 → 0.19.1

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/llms-txt.mdx +1 -1
  5. package/site/content/docs/ai/skills.mdx +4 -1
  6. package/site/content/docs/client/auth.mdx +208 -0
  7. package/site/content/docs/client/calling.mdx +451 -0
  8. package/site/content/docs/client/index.mdx +208 -0
  9. package/site/content/docs/client/live.mdx +234 -0
  10. package/site/content/docs/client/meta.json +5 -0
  11. package/site/content/docs/client/react.mdx +249 -0
  12. package/site/content/docs/elements/ai/agents.mdx +235 -0
  13. package/site/content/docs/elements/ai/index.mdx +342 -0
  14. package/site/content/docs/elements/ai/mcp.mdx +275 -0
  15. package/site/content/docs/elements/ai/meta.json +5 -0
  16. package/site/content/docs/elements/ai/models.mdx +283 -0
  17. package/site/content/docs/elements/ai/prompts.mdx +257 -0
  18. package/site/content/docs/elements/channel/email.mdx +269 -0
  19. package/site/content/docs/elements/channel/index.mdx +344 -0
  20. package/site/content/docs/elements/channel/meta.json +5 -0
  21. package/site/content/docs/elements/channel/push.mdx +218 -0
  22. package/site/content/docs/elements/channel/receipts.mdx +206 -0
  23. package/site/content/docs/elements/channel/sms.mdx +264 -0
  24. package/site/content/docs/elements/channel/whatsapp.mdx +220 -0
  25. package/site/content/docs/elements/clock/index.mdx +301 -0
  26. package/site/content/docs/elements/clock/meta.json +5 -0
  27. package/site/content/docs/elements/clock/schedules.mdx +508 -0
  28. package/site/content/docs/elements/clock/sleep.mdx +448 -0
  29. package/site/content/docs/elements/flow/consumers.mdx +707 -0
  30. package/site/content/docs/elements/flow/http.mdx +1080 -0
  31. package/site/content/docs/elements/flow/index.mdx +618 -0
  32. package/site/content/docs/elements/flow/meta.json +5 -0
  33. package/site/content/docs/elements/flow/routing.mdx +559 -0
  34. package/site/content/docs/elements/flow/workflows.mdx +640 -0
  35. package/site/content/docs/elements/gate/auth.mdx +371 -0
  36. package/site/content/docs/elements/gate/authorization.mdx +298 -0
  37. package/site/content/docs/elements/gate/index.mdx +418 -0
  38. package/site/content/docs/elements/gate/meta.json +5 -0
  39. package/site/content/docs/elements/gate/rate-limits.mdx +365 -0
  40. package/site/content/docs/elements/gate/rls.mdx +303 -0
  41. package/site/content/docs/elements/gate/tenancy.mdx +375 -0
  42. package/site/content/docs/elements/index.mdx +20 -10
  43. package/site/content/docs/elements/signal/broadcast.mdx +519 -0
  44. package/site/content/docs/elements/signal/index.mdx +352 -0
  45. package/site/content/docs/elements/signal/live.mdx +591 -0
  46. package/site/content/docs/elements/signal/meta.json +5 -0
  47. package/site/content/docs/elements/signal/once.mdx +597 -0
  48. package/site/content/docs/elements/store/files.mdx +667 -0
  49. package/site/content/docs/elements/store/index.mdx +313 -0
  50. package/site/content/docs/elements/store/kv.mdx +637 -0
  51. package/site/content/docs/elements/store/meta.json +5 -0
  52. package/site/content/docs/elements/store/search.mdx +950 -0
  53. package/site/content/docs/elements/store/sql.mdx +941 -0
  54. package/site/content/docs/elements/vault/config.mdx +259 -0
  55. package/site/content/docs/elements/vault/index.mdx +356 -0
  56. package/site/content/docs/elements/vault/meta.json +5 -0
  57. package/site/content/docs/elements/vault/rotation.mdx +276 -0
  58. package/site/content/docs/elements/vault/secrets.mdx +288 -0
  59. package/site/content/docs/index.mdx +52 -40
  60. package/site/content/docs/meta.json +8 -7
  61. package/site/content/docs/plugins/cors.mdx +2 -1
  62. package/site/content/docs/plugins/csrf.mdx +4 -1
  63. package/site/content/docs/plugins/headers.mdx +2 -1
  64. package/site/content/docs/plugins/ip-allowlist.mdx +2 -1
  65. package/site/content/docs/plugins/magic-link.mdx +6 -0
  66. package/site/content/docs/plugins/maintenance-mode.mdx +2 -1
  67. package/site/content/docs/plugins/oauth.mdx +1 -1
  68. package/site/content/docs/plugins/otp.mdx +12 -1
  69. package/site/content/docs/plugins/passkey.mdx +39 -15
  70. package/site/content/docs/plugins/two-factor.mdx +91 -34
  71. package/site/content/docs/plugins/username.mdx +1 -1
  72. package/site/content/docs/recipes/caddy.mdx +3 -3
  73. package/site/content/docs/recipes/index.mdx +18 -28
  74. package/site/content/docs/recipes/mailpit.mdx +1 -1
  75. package/site/content/docs/recipes/meilisearch.mdx +3 -3
  76. package/site/content/docs/recipes/meta.json +1 -4
  77. package/site/content/docs/recipes/nginx.mdx +2 -2
  78. package/site/content/docs/recipes/openrouter.mdx +283 -0
  79. package/site/content/docs/recipes/pgdog.mdx +3 -3
  80. package/site/content/docs/recipes/postgres.mdx +1 -2
  81. package/site/content/docs/recipes/rustfs.mdx +2 -2
  82. package/site/content/docs/recipes/traefik.mdx +3 -3
  83. package/site/content/docs/reference/cli.mdx +231 -0
  84. package/site/content/docs/reference/configuration.mdx +30 -10
  85. package/site/content/docs/reference/environment-variables.mdx +93 -20
  86. package/site/content/docs/reference/errors.mdx +132 -29
  87. package/site/content/docs/reference/fx.mdx +138 -73
  88. package/site/content/docs/reference/index.mdx +13 -21
  89. package/site/content/docs/reference/meta.json +4 -5
  90. package/site/content/docs/reference/okid.mdx +42 -17
  91. package/site/content/docs/reference/plugins.mdx +4 -3
  92. package/site/content/docs/reference/security.mdx +197 -0
  93. package/site/content/docs/understand/meta.json +5 -0
  94. package/site/content/docs/understand/the-architecture.mdx +264 -0
  95. package/site/content/docs/understand/try-it.mdx +58 -0
  96. package/src/auth/api-keys.ts +2 -1
  97. package/src/auth/bindings.ts +64 -16
  98. package/src/auth/gate-auth.test.ts +6 -1
  99. package/src/auth/identity.ts +152 -5
  100. package/src/auth/index.ts +29 -0
  101. package/src/auth/invites.ts +2 -1
  102. package/src/auth/method-context.ts +41 -0
  103. package/src/auth/oauth-as/crypto.ts +2 -1
  104. package/src/auth/oauth-as/stores.ts +2 -1
  105. package/src/auth/operator.ts +2 -1
  106. package/src/auth/sessions-jwt.test.ts +77 -0
  107. package/src/auth/sessions.ts +20 -4
  108. package/src/auth/tenants.ts +3 -2
  109. package/src/auth/two-factor-challenge.test.ts +61 -0
  110. package/src/auth/two-factor-challenge.ts +284 -0
  111. package/src/auth/verification.ts +5 -0
  112. package/src/bench/REPORT.md +80 -0
  113. package/src/bench/g03-signal-once.bench.ts +1 -1
  114. package/src/bench/g10-observability-contention.bench.ts +1 -1
  115. package/src/bench/g17-hybrid-search.bench.ts +572 -0
  116. package/src/bench/load-app.ts +2 -10
  117. package/src/cli/ai-setup/ai-setup.test.ts +331 -42
  118. package/src/cli/ai-setup/apply.ts +294 -52
  119. package/src/cli/ai-setup/catalog.ts +238 -1342
  120. package/src/cli/ai-setup/index.ts +14 -99
  121. package/src/cli/ai-setup/prompts.ts +60 -443
  122. package/src/cli/ask-seed.test.ts +96 -0
  123. package/src/cli/ask-seed.ts +82 -0
  124. package/src/cli/ask-vault-gaps.test.ts +84 -0
  125. package/src/cli/ask-vault-gaps.ts +146 -0
  126. package/src/cli/build.ts +2 -2
  127. package/src/cli/client-add.test.ts +26 -1
  128. package/src/cli/client-add.ts +106 -27
  129. package/src/cli/db-seed.ts +2 -0
  130. package/src/cli/db.ts +150 -11
  131. package/src/cli/dev.ts +119 -211
  132. package/src/cli/docker-clean.ts +2 -2
  133. package/src/cli/docker-cli.test.ts +1 -1
  134. package/src/cli/doctor-diff.ts +4 -2
  135. package/src/cli/doctor-pii.test.ts +1 -1
  136. package/src/cli/doctor.ts +14 -1
  137. package/src/cli/hero-meta.test.ts +4 -2
  138. package/src/cli/load-config.images.test.ts +8 -10
  139. package/src/cli/load-config.ts +1 -1
  140. package/src/cli/registry.ts +22 -10
  141. package/src/cli/replay.ts +3 -1
  142. package/src/cli/start.ts +1 -1
  143. package/src/cli/tui/keys.ts +2 -3
  144. package/src/client/auth/cookies.ts +74 -0
  145. package/src/client/auth/create-auth-client.ts +636 -0
  146. package/src/client/auth/denials.ts +99 -0
  147. package/src/client/auth/session.ts +283 -0
  148. package/src/client/auth.test.ts +251 -0
  149. package/src/client/auth.ts +39 -114
  150. package/src/client/create-with-session.ts +245 -0
  151. package/src/client/create.ts +51 -5
  152. package/src/client/index.ts +11 -1
  153. package/src/client/live.ts +11 -79
  154. package/src/client/notes-contract.test.ts +5 -10
  155. package/src/client/sse.ts +137 -0
  156. package/src/client/stream.ts +147 -0
  157. package/src/client/transport.test.ts +32 -0
  158. package/src/client/transport.ts +123 -26
  159. package/src/client/types.ts +118 -14
  160. package/src/client-react/index.ts +185 -24
  161. package/src/client-react/use-live-query.ts +13 -3
  162. package/src/compiler/aot.test.ts +7 -4
  163. package/src/compiler/effects-embed.test.ts +56 -0
  164. package/src/compiler/effects-fetch.test.ts +43 -0
  165. package/src/compiler/effects-infer.ts +58 -2
  166. package/src/compiler/extract.test.ts +257 -43
  167. package/src/compiler/extract.ts +566 -48
  168. package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +7 -4
  169. package/src/compiler/fixtures/skyport/src/flows/bookings/signals.ts +2 -8
  170. package/src/compiler/fixtures/skyport.expected.json +4 -4
  171. package/src/compiler/fixtures/triggers/five-triggers.ts +14 -10
  172. package/src/compiler/response.ts +2 -2
  173. package/src/compiler/search-writer-isolation.test.ts +40 -0
  174. package/src/config/index.ts +11 -0
  175. package/src/console/server/app.ts +17 -23
  176. package/src/console/server/bind.ts +4 -0
  177. package/src/console/server/console.test.ts +2 -2
  178. package/src/console/server/flows-invoke.test.ts +50 -34
  179. package/src/console/server/flows.ts +436 -255
  180. package/src/console/server/runs-ingest.test.ts +6 -3
  181. package/src/console/server/serve.ts +1 -1
  182. package/src/console/server/signals.test.ts +1 -5
  183. package/src/console/server/signals.ts +8 -6
  184. package/src/console/server/state.ts +6 -1
  185. package/src/console/server/store.ts +1 -1
  186. package/src/console/ui-next/dist/assets/FileExportIcon-Ck-5od4R.js +1 -0
  187. package/src/console/ui-next/dist/assets/MoreHorizontalCircle01Icon-gMNGsE37.js +1 -0
  188. package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +1 -0
  189. package/src/console/ui-next/dist/assets/UnavailableIcon-D9cvHVPr.js +1 -0
  190. package/src/console/ui-next/dist/assets/UserIcon-DaE2PB5_.js +1 -0
  191. package/src/console/ui-next/dist/assets/access-page-C_qLDhTq.js +4 -0
  192. package/src/console/ui-next/dist/assets/agent-disclosure-BHVqr3TN.js +1 -0
  193. package/src/console/ui-next/dist/assets/cache-glyph-CKe92lRQ.js +1 -0
  194. package/src/console/ui-next/dist/assets/call-pii-button-DEwTl8ZX.js +1 -0
  195. package/src/console/ui-next/dist/assets/collapsible-BCBtDrCt.js +1 -0
  196. package/src/console/ui-next/dist/assets/copy-inline-button-CAYD18cr.js +1 -0
  197. package/src/console/ui-next/dist/assets/dagre.esm-B1_XeuLP.js +1 -0
  198. package/src/console/ui-next/dist/assets/detail-header-DVWNjWjg.js +1 -0
  199. package/src/console/ui-next/dist/assets/dropdown-menu-4h2LOVXM.js +1 -0
  200. package/src/console/ui-next/dist/assets/duration-tone-JroqeuCp.js +9 -0
  201. package/src/console/ui-next/dist/assets/element-icons-BI8cJgdh.js +1 -0
  202. package/src/console/ui-next/dist/assets/explorer-empty-CJs5A-wm.js +1 -0
  203. package/src/console/ui-next/dist/assets/flows-page-CVHa0RTt.js +1 -0
  204. package/src/console/ui-next/dist/assets/highlighted-json-DjJW6hqe.js +154 -0
  205. package/src/console/ui-next/dist/assets/http-method-DC5HBdLU.js +1 -0
  206. package/src/console/ui-next/dist/assets/index-CYjiZ3WO.js +63 -0
  207. package/src/console/ui-next/dist/assets/index-D0zS5rKO.css +2 -0
  208. package/src/console/ui-next/dist/assets/observability-page-CAYMyKb3.js +4 -0
  209. package/src/console/ui-next/dist/assets/react-dom-Ddte4I-Q.js +9 -0
  210. package/src/console/ui-next/dist/assets/replica-lag-yAQYLv75.js +18 -0
  211. package/src/console/ui-next/dist/assets/request-meta-D0yusGxJ.js +1 -0
  212. package/src/console/ui-next/dist/assets/shortcut-keys-3ILd8oGn.js +1 -0
  213. package/src/console/ui-next/dist/assets/{sql-BskegiFM.js → sql-BsFa4tDR.js} +1 -1
  214. package/src/console/ui-next/dist/assets/store-page-BTKJeJ02.js +41 -0
  215. package/src/console/ui-next/dist/assets/trace-detail-sheet-Bp-Yygs5.js +2 -0
  216. package/src/console/ui-next/dist/assets/tree-expand-toggle-DoaVDfAM.js +55 -0
  217. package/src/console/ui-next/dist/assets/units-page-BRz7xyYL.js +1 -0
  218. package/src/console/ui-next/dist/assets/{use-vault-list-uk4WVboC.js → use-vault-list-CT4-gajj.js} +1 -1
  219. package/src/console/ui-next/dist/assets/vault-page-3jQt-bOJ.js +2 -0
  220. package/src/console/ui-next/dist/assets/xyflow-CSyC6ryz.css +1 -0
  221. package/src/console/ui-next/dist/assets/xyflow-yApv7D4e.js +7 -0
  222. package/src/console/ui-next/dist/index.html +6 -13
  223. package/src/console/ui-next/seed-invoke-host.ts +21 -18
  224. package/src/console/ui-next/src/client.ts +7 -2
  225. package/src/console/ui-next/src/features/flows/graph/element-map.test.ts +1 -1
  226. package/src/console/ui-next/src/features/flows/graph/element-map.ts +5 -3
  227. package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +12 -2
  228. package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +4 -0
  229. package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +8 -0
  230. package/src/console/ui-next/src/features/flows/traces/trace-detail.test.ts +4 -5
  231. package/src/console/ui-next/src/features/flows/traces/trace-gates.ts +3 -6
  232. package/src/console/ui-next/src/features/flows/traces/waterfall-bars.ts +4 -0
  233. package/src/console/ui-next/src/features/flows/traces/waterfall-tooltip.ts +9 -3
  234. package/src/console/ui-next/src/features/store/detail/reveal-cell.tsx +7 -11
  235. package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +2 -2
  236. package/src/console/ui-next/src/features/store/lib/fields-from-table.ts +2 -0
  237. package/src/console/ui-next/src/features/store/lib/grid-model.test.ts +25 -1
  238. package/src/console/ui-next/src/features/store/lib/grid-model.ts +36 -1
  239. package/src/console/ui-next/src/features/store/query/query-results.tsx +3 -3
  240. package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +2 -0
  241. package/src/console/ui-next/src/features/units/lib/call-read-safe.ts +3 -1
  242. package/src/console/ui-next/src/features/units/lib/fields-from-schema.ts +6 -0
  243. package/src/docker/ai-model-status.test.ts +1 -12
  244. package/src/docker/ai-model-status.ts +10 -38
  245. package/src/docker/compose-up.test.ts +100 -0
  246. package/src/docker/compose-up.ts +292 -0
  247. package/src/docker/compose.ts +4 -14
  248. package/src/docker/derive.ts +9 -34
  249. package/src/docker/docker.test.ts +16 -261
  250. package/src/docker/images-config.test.ts +13 -25
  251. package/src/docker/index.ts +1 -24
  252. package/src/docker/recipes/index.ts +0 -23
  253. package/src/docker/recipes/pgdog.ts +6 -4
  254. package/src/docker/stack-id.test.ts +2 -2
  255. package/src/docker/stack-id.ts +0 -1
  256. package/src/docker/types.ts +2 -2
  257. package/src/drivers/ai-anthropic.ts +10 -0
  258. package/src/drivers/ai-openai-compatible.ts +34 -7
  259. package/src/drivers/ai-providers.test.ts +0 -107
  260. package/src/drivers/ai-stream.test.ts +0 -34
  261. package/src/drivers/ai-types.ts +17 -8
  262. package/src/drivers/channel-fcm.ts +6 -1
  263. package/src/drivers/channel-msegat.ts +6 -1
  264. package/src/drivers/channel-resend.ts +5 -1
  265. package/src/drivers/channel-smtp.ts +5 -1
  266. package/src/drivers/channel-sndr.ts +10 -1
  267. package/src/drivers/channel-taqnyat-mail.ts +5 -1
  268. package/src/drivers/channel-taqnyat-whatsapp.ts +6 -1
  269. package/src/drivers/channel-taqnyat.ts +6 -1
  270. package/src/drivers/channel-types.ts +10 -0
  271. package/src/drivers/channel-unifonic.ts +6 -1
  272. package/src/drivers/channel-wa-cloud.ts +6 -1
  273. package/src/drivers/channel-webpush.ts +6 -1
  274. package/src/drivers/external.ts +33 -0
  275. package/src/drivers/index.ts +0 -10
  276. package/src/drivers/meilisearch.ts +6 -0
  277. package/src/drivers/oauth-types.ts +4 -0
  278. package/src/drivers/postgres.test.ts +74 -3
  279. package/src/drivers/postgres.ts +175 -6
  280. package/src/drivers/signal-types.ts +5 -5
  281. package/src/drivers/types.ts +7 -0
  282. package/src/drivers/vault-types.ts +5 -0
  283. package/src/elements/ai/declare.ts +17 -4
  284. package/src/elements/ai/eval.ts +1 -1
  285. package/src/elements/ai/mcp-http.ts +1 -1
  286. package/src/elements/ai/pii.ts +1 -1
  287. package/src/elements/ai/providers.test.ts +289 -0
  288. package/src/elements/ai/providers.ts +169 -0
  289. package/src/elements/ai/runtime.ts +68 -5
  290. package/src/elements/ai/schema.ts +2 -2
  291. package/src/elements/ai.test.ts +7 -7
  292. package/src/elements/ai.ts +17 -1
  293. package/src/elements/channel/runtime.ts +105 -12
  294. package/src/elements/clock/cron-fields.test.ts +144 -0
  295. package/src/elements/clock/cron-fields.ts +185 -0
  296. package/src/elements/clock/declare.ts +247 -3
  297. package/src/elements/clock.ts +21 -1
  298. package/src/elements/gate/declare.ts +1 -1
  299. package/src/elements/gate.ts +1 -1
  300. package/src/elements/index.ts +15 -0
  301. package/src/elements/signal/chaos-child.ts +1 -2
  302. package/src/elements/signal/declare.ts +65 -37
  303. package/src/elements/signal/delivery-modes.test.ts +11 -31
  304. package/src/elements/signal/dry-run-replay.test.ts +1 -7
  305. package/src/elements/signal/dry-run-write-isolation.test.ts +1 -7
  306. package/src/elements/signal/key-ordering.test.ts +6 -26
  307. package/src/elements/signal/lease-reclaim.test.ts +2 -6
  308. package/src/elements/signal/optional-emit.test.ts +6 -9
  309. package/src/elements/signal/order-lifecycle.test.ts +4 -17
  310. package/src/elements/signal/orphan-messages.test.ts +4 -15
  311. package/src/elements/signal/reconcile.test.ts +2 -2
  312. package/src/elements/signal/runtime.ts +1 -1
  313. package/src/elements/signal/schema-emit.test.ts +4 -8
  314. package/src/elements/signal.test.ts +24 -49
  315. package/src/elements/signal.ts +9 -2
  316. package/src/elements/store/domain-ddl.test.ts +1 -1
  317. package/src/elements/store/live-http.test.ts +9 -4
  318. package/src/elements/store/prepare-row.test.ts +123 -0
  319. package/src/elements/store/resource.ts +43 -12
  320. package/src/elements/store/schema-decl.ts +117 -0
  321. package/src/elements/store/search-backfill.ts +205 -0
  322. package/src/elements/store/search-bind.ts +66 -0
  323. package/src/elements/store/search-bm25.ts +78 -0
  324. package/src/elements/store/search-ddl.ts +156 -0
  325. package/src/elements/store/search-embed-flow.ts +141 -0
  326. package/src/elements/store/search-errors.ts +34 -0
  327. package/src/elements/store/search-fusion.ts +112 -0
  328. package/src/elements/store/search-lsh.ts +214 -0
  329. package/src/elements/store/search-runtime.pglite.test.ts +186 -0
  330. package/src/elements/store/search-runtime.ts +314 -0
  331. package/src/elements/store/search.test.ts +226 -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 +52 -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 +2 -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 -144
  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 -114
  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-3-EFj-2G.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-cVFnA4HH.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-CMIgUbD0.js +0 -66
  484. package/src/console/ui-next/dist/assets/index-Ck88Jmv8.css +0 -2
  485. package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +0 -1
  486. package/src/console/ui-next/dist/assets/observability-page-CQ3p34ip.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-l8FeKfnP.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-CL-d_mLE.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,667 @@
1
+ ---
2
+ title: "Files"
3
+ description: "Object storage buckets — put/get, putImage variants, LQIP placeholders, and chainable Bun.Image transforms."
4
+ icon: "Files"
5
+ source: "docs/spec/unified-theory.md"
6
+ ---
7
+
8
+ The Files facet is a typed object bucket for uploads, exports, and media. Declare with `store.files(name)`, then call `fx.store(decl)`.
9
+
10
+ For developers storing blobs on okengine — put bytes, optionally derive image variants, serve keys from Flows.
11
+
12
+ <Callout title="The one rule">
13
+ Pass the **declaration** into `fx.store(uploads)`. There is no `fx.store.files` namespace, no
14
+ `presignPut`, and no `transform(…)` helper — use `put` / `get` / `putImage` / `image(…)`.
15
+ </Callout>
16
+
17
+ ## Smallest Example
18
+
19
+ <Steps>
20
+
21
+ <Step>
22
+ ### Declare a bucket
23
+
24
+ ```typescript title="src/core.ts"
25
+ import { store } from "okengine";
26
+
27
+ export const uploads = store.files("uploads");
28
+ ```
29
+
30
+ </Step>
31
+
32
+ <Step>
33
+ ### Put and get in a Flow
34
+
35
+ ```typescript title="src/flows/uploads/create.ts"
36
+ import { on, flow, http } from "okengine";
37
+ import { z } from "zod";
38
+ import { uploads } from "@/core";
39
+
40
+ export const create = on(
41
+ http.post({
42
+ in: z.object({
43
+ name: z.string().min(1),
44
+ bytes: z.string(), // base64 for the demo
45
+ }),
46
+ out: z.object({ key: z.string() }),
47
+ }),
48
+ flow({
49
+ do: async ({ name, bytes }, fx) => {
50
+ const key = `uploads/${fx.id()}-${name}`;
51
+ const data = Uint8Array.from(atob(bytes), (c) => c.charCodeAt(0));
52
+ await fx.store(uploads).put(key, data);
53
+ return { key };
54
+ },
55
+ }),
56
+ );
57
+ ```
58
+
59
+ </Step>
60
+
61
+ <Step>
62
+ ### Call the endpoint
63
+
64
+ ```bash
65
+ curl -X POST http://localhost:6530/uploads \
66
+ -H "content-type: application/json" \
67
+ -d '{"name":"note.txt","bytes":"aGVsbG8="}'
68
+ ```
69
+
70
+ Response:
71
+
72
+ ```json
73
+ {
74
+ "data": { "key": "uploads/…-note.txt" },
75
+ "error": null
76
+ }
77
+ ```
78
+
79
+ </Step>
80
+
81
+ </Steps>
82
+
83
+ <Callout title="Effects are inferred">
84
+ Every `fx.store(uploads)` touch stamps `reads` / `writes` as `files:uploads` on the Flow. That
85
+ powers the Manifest, Console, and least privilege — no hand-written effect lists.
86
+ </Callout>
87
+
88
+ ## Progressive Patterns
89
+
90
+ From a bare put to list/delete, image variants, and the chainable pipeline:
91
+
92
+ <Tabs items={["Put / get", "List / delete", "putImage", "Pipeline"]}>
93
+
94
+ <Tab value="Put / get">
95
+
96
+ `put` accepts `Uint8Array` or UTF-8 `string`. `get` returns bytes or `null`:
97
+
98
+ ```typescript title="src/flows/exports/create.ts"
99
+ import { on, flow, http } from "okengine";
100
+ import { uploads } from "@/core";
101
+
102
+ export const create = on(
103
+ http.post(),
104
+ flow({
105
+ do: async (_, fx) => {
106
+ const key = `exports/${fx.id()}.csv`;
107
+ await fx.store(uploads).put(key, "id,title\n1,Ship\n");
108
+ const bytes = await fx.store(uploads).get(key);
109
+ return { key, bytes: bytes?.byteLength ?? 0 };
110
+ },
111
+ }),
112
+ );
113
+ ```
114
+
115
+ </Tab>
116
+
117
+ <Tab value="List / delete">
118
+
119
+ Prefix `list` for browsing; `delete` returns whether an object was removed:
120
+
121
+ ```typescript title="src/flows/avatars/route.ts"
122
+ import { on, flow, http } from "okengine";
123
+ import { z } from "zod";
124
+ import { uploads } from "@/core";
125
+
126
+ export const cleanup = on(
127
+ http.delete({
128
+ in: z.object({ prefix: z.string().default("avatars/") }),
129
+ }),
130
+ flow({
131
+ do: async ({ prefix }, fx) => {
132
+ const keys = await fx.store(uploads).list(prefix);
133
+ let removed = 0;
134
+ for (const key of keys) {
135
+ if (await fx.store(uploads).delete(key)) removed += 1;
136
+ }
137
+ return { listed: keys.length, removed };
138
+ },
139
+ }),
140
+ );
141
+ ```
142
+
143
+ Prefer stable, printable-ASCII object keys. Avoid `..` and leading `/` — the
144
+ `fs` driver rejects those with `Invalid object key: …`.
145
+
146
+ </Tab>
147
+
148
+ <Tab value="putImage">
149
+
150
+ Write the original plus named derivatives and an optional ThumbHash LQIP data
151
+ URL on the **result** (not a stored object):
152
+
153
+ ```typescript title="src/flows/photos/create.ts"
154
+ import { on, flow, http } from "okengine";
155
+ import { z } from "zod";
156
+ import { uploads } from "@/core";
157
+
158
+ export const create = on(
159
+ http.post({
160
+ in: z.object({ bytes: z.string() }),
161
+ }),
162
+ flow({
163
+ do: async ({ bytes }, fx) => {
164
+ const key = `photos/${fx.id()}.jpg`;
165
+ const data = Uint8Array.from(atob(bytes), (c) => c.charCodeAt(0));
166
+ const result = await fx.store(uploads).putImage(key, data, {
167
+ placeholder: true,
168
+ variants: {
169
+ thumb: {
170
+ resize: [320],
171
+ webp: { quality: 80 },
172
+ },
173
+ },
174
+ });
175
+ return {
176
+ key: result.key,
177
+ thumb: result.variants.thumb,
178
+ placeholder: result.placeholder,
179
+ width: result.meta.width,
180
+ height: result.meta.height,
181
+ };
182
+ },
183
+ }),
184
+ );
185
+ ```
186
+
187
+ Variant key shape: `photos/x.jpg` + `thumb` + `webp` → `photos/x.thumb.webp`.
188
+
189
+ </Tab>
190
+
191
+ <Tab value="Pipeline">
192
+
193
+ Chain Bun.Image ops, then `put` / `bytes` / `blob` / `placeholder` / `metadata`:
194
+
195
+ ```typescript title="src/flows/photos/card.ts"
196
+ import { on, flow, http } from "okengine";
197
+ import { z } from "zod";
198
+ import { uploads } from "@/core";
199
+
200
+ export const card = on(
201
+ http.post({
202
+ in: z.object({ sourceKey: z.string() }),
203
+ }),
204
+ flow({
205
+ do: async ({ sourceKey }, fx) => {
206
+ const outKey = sourceKey.replace(/\.[^.]+$/, ".card.webp");
207
+ await fx
208
+ .store(uploads)
209
+ .image(sourceKey)
210
+ .resize(400, 400, { fit: "inside" })
211
+ .webp({ quality: 80 })
212
+ .put(outKey);
213
+ return { outKey };
214
+ },
215
+ }),
216
+ );
217
+ ```
218
+
219
+ `image(source)` accepts an object key **or** raw bytes. Resize `fit` is
220
+ `"fill"` \| `"inside"` (Bun.Image — not CSS `cover`).
221
+
222
+ </Tab>
223
+
224
+ </Tabs>
225
+
226
+ ## Method Reference
227
+
228
+ `fx.store(filesDecl)`:
229
+
230
+ | Method | Signature | Meaning |
231
+ | ---------- | ---------------------------- | ----------------------------------- |
232
+ | `put` | `put(key, data)` | Store bytes or UTF-8 string |
233
+ | `get` | `get(key)` | Read bytes, or `null` if missing |
234
+ | `delete` | `delete(key)` | Remove; returns whether deleted |
235
+ | `list` | `list(prefix?)` | List object keys (optional prefix) |
236
+ | `image` | `image(source, options?)` | Chainable Bun.Image pipeline |
237
+ | `putImage` | `putImage(key, data, opts?)` | Original + variants + optional LQIP |
238
+
239
+ Handle also exposes `ref` (`files:name`) and `driverId` (`memory` · `fs` · `s3`).
240
+
241
+ There is **no** public `presignPut`, `presignGet`, `copy`, `head`, or `transform`
242
+ alias on the fx handle.
243
+
244
+ ## Object Keys
245
+
246
+ Keys are opaque strings the driver stores as-is. Pick a stable prefix + unique
247
+ suffix; keep SQL rows pointing at the key, not the bytes.
248
+
249
+ **Explicit key** — build the path in the Flow:
250
+
251
+ ```typescript
252
+ const key = `avatars/${fx.auth.userId}/${fx.id()}.png`;
253
+ await fx.store(uploads).put(key, data);
254
+ ```
255
+
256
+ **Content hash** — when the same bytes should collapse to one object, hash
257
+ yourself (SHA-256 hex) and put under that digest. The facet does not auto-
258
+ dedupe on `put`.
259
+
260
+ **Driver guards** — the `fs` driver throws on path escape:
261
+
262
+ ```text
263
+ Invalid object key: ../secret
264
+ ```
265
+
266
+ | Rule | Why |
267
+ | ---------------------- | --------------------------------------------------------- |
268
+ | Prefer printable ASCII | S3-compatible signed URLs are fragile with non-ASCII keys |
269
+ | No leading `/` | Absolute paths are rejected on `fs` |
270
+ | No `..` segments | Path escape is rejected on `fs` |
271
+ | Stable extensions | MIME / Console kind inference uses the suffix |
272
+
273
+ ## Blob Operations
274
+
275
+ Each verb binds through `fx.store(decl)` inside `flow()`:
276
+
277
+ <Tabs items={["put", "get", "delete", "list"]}>
278
+
279
+ <Tab value="put">
280
+
281
+ Write bytes or a UTF-8 string. Overwrites an existing key:
282
+
283
+ ```typescript title="src/flows/notes/[id]/attachment/create.ts"
284
+ import { on, flow, http } from "okengine";
285
+ import { z } from "zod";
286
+ import { uploads } from "@/core";
287
+
288
+ export const create = on(
289
+ http.post({
290
+ in: z.object({
291
+ id: z.string(),
292
+ name: z.string().min(1),
293
+ bytes: z.string(),
294
+ }),
295
+ }),
296
+ flow({
297
+ do: async ({ id, name, bytes }, fx) => {
298
+ const key = `notes/${id}/${name}`;
299
+ const data = Uint8Array.from(atob(bytes), (c) => c.charCodeAt(0));
300
+ await fx.store(uploads).put(key, data);
301
+ return { key };
302
+ },
303
+ }),
304
+ );
305
+ ```
306
+
307
+ </Tab>
308
+
309
+ <Tab value="get">
310
+
311
+ Read bytes, or `null` when the key is missing — treat `null` as not found:
312
+
313
+ ```typescript title="src/flows/notes/[id]/attachment/get.ts"
314
+ import { on, flow, http } from "okengine";
315
+ import { z } from "zod";
316
+ import { uploads } from "@/core";
317
+
318
+ export const get = on(
319
+ http.get({
320
+ in: z.object({ id: z.string(), name: z.string() }),
321
+ errors: { NotFound: z.object({ key: z.string() }) },
322
+ }),
323
+ flow({
324
+ do: async ({ id, name }, fx) => {
325
+ const key = `notes/${id}/${name}`;
326
+ const bytes = await fx.store(uploads).get(key);
327
+ if (!bytes) return fx.fail("NotFound", { key });
328
+ return { key, size: bytes.byteLength };
329
+ },
330
+ }),
331
+ );
332
+ ```
333
+
334
+ </Tab>
335
+
336
+ <Tab value="delete">
337
+
338
+ Remove one key. Returns `true` when an object was deleted, `false` if missing:
339
+
340
+ ```typescript title="src/flows/notes/[id]/attachment/remove.ts"
341
+ import { on, flow, http } from "okengine";
342
+ import { z } from "zod";
343
+ import { uploads } from "@/core";
344
+
345
+ export const remove = on(
346
+ http.delete({
347
+ in: z.object({ id: z.string(), name: z.string() }),
348
+ }),
349
+ flow({
350
+ do: async ({ id, name }, fx) => {
351
+ const key = `notes/${id}/${name}`;
352
+ const removed = await fx.store(uploads).delete(key);
353
+ return { key, removed };
354
+ },
355
+ }),
356
+ );
357
+ ```
358
+
359
+ </Tab>
360
+
361
+ <Tab value="list">
362
+
363
+ List keys under an optional prefix. Use for Console browsing and admin Flows —
364
+ not as a relational index:
365
+
366
+ ```typescript title="src/flows/notes/[id]/attachments.ts"
367
+ import { on, flow, http } from "okengine";
368
+ import { z } from "zod";
369
+ import { uploads } from "@/core";
370
+
371
+ export const attachments = on(
372
+ http.get({
373
+ in: z.object({ id: z.string() }),
374
+ }),
375
+ flow({
376
+ do: async ({ id }, fx) => {
377
+ const keys = await fx.store(uploads).list(`notes/${id}/`);
378
+ return { keys };
379
+ },
380
+ }),
381
+ );
382
+ ```
383
+
384
+ </Tab>
385
+
386
+ </Tabs>
387
+
388
+ ## putImage
389
+
390
+ <Callout title="Detailed section">
391
+ If you only need the original + one thumb, jump to Progressive Patterns → putImage. This section
392
+ covers options, variant key naming, and decode guards.
393
+ </Callout>
394
+
395
+ `putImage(key, data, opts?)` writes the **original** at `key`, then optional
396
+ named variants beside it, then optionally returns a ThumbHash LQIP **data URL**
397
+ on the result (the placeholder is not stored as an object).
398
+
399
+ ```typescript
400
+ const result = await fx.store(uploads).putImage(key, data, {
401
+ placeholder: true,
402
+ variants: {
403
+ thumb: { resize: [320], webp: { quality: 80 } },
404
+ card: { resize: [800, 600, { fit: "inside" }], jpeg: { quality: 85 } },
405
+ },
406
+ maxPixels: 4096 * 4096,
407
+ autoOrient: true,
408
+ });
409
+ ```
410
+
411
+ <Accordions>
412
+
413
+ <Accordion title="putImage Options">
414
+
415
+ | Option | Type | Default | Meaning |
416
+ | ------------- | ---------------------------------- | ------------- | ------------------------------------- |
417
+ | `variants` | `Record<string, ImageVariantSpec>` | omitted | Named derivatives beside the original |
418
+ | `placeholder` | `boolean` | omitted | ThumbHash **data URL** on the result |
419
+ | `maxPixels` | `number` \| `false` | `4096 * 4096` | Decode ceiling; `false` disables |
420
+ | `autoOrient` | `boolean` | `true` | Respect EXIF orientation |
421
+
422
+ </Accordion>
423
+
424
+ <Accordion title="ImageVariantSpec">
425
+ Each variant may set geometry and **exactly one** encode target:
426
+
427
+ | Field | Meaning |
428
+ | ----------------------------------------- | ----------------------------------------------------------- |
429
+ | `resize` | `[width]`, `[width, height]`, or `[width, height, options]` |
430
+ | `rotate` · `flip` · `flop` | Geometry |
431
+ | `modulate` | `{ brightness?, saturation? }` |
432
+ | `jpeg` / `png` / `webp` / `heic` / `avif` | Encode — set **one** only |
433
+
434
+ Resize options: `filter`, `fit` (`"fill"` \| `"inside"`), `withoutEnlargement`.
435
+
436
+ If no encode field is set, the variant keeps the source format when it is
437
+ `jpeg` / `png` / `webp` / `heic` / `avif`; decode-only sources (e.g. gif)
438
+ default to `webp`.
439
+
440
+ Setting two encode fields throws:
441
+
442
+ ```text
443
+ files image variant: set only one of jpeg/png/webp/heic/avif (got jpeg, webp)
444
+ ```
445
+
446
+ </Accordion>
447
+
448
+ <Accordion title="Variant key naming">
449
+ Keys are derived from the original stem + variant name + encode extension
450
+ (`jpeg` → `jpg`):
451
+
452
+ | Original | Variant | Format | Stored key |
453
+ | ------------------- | ------- | ------ | --------------------- |
454
+ | `photos/x.jpg` | `thumb` | `webp` | `photos/x.thumb.webp` |
455
+ | `photos/x.png` | `card` | `jpeg` | `photos/x.card.jpg` |
456
+ | `a/b/hero` (no ext) | `sm` | `png` | `a/b/hero.sm.png` |
457
+
458
+ **Consequence:** `get(result.key)` is the original; read derivatives from
459
+ `result.variants.thumb` (and friends), not by guessing.
460
+
461
+ </Accordion>
462
+
463
+ <Accordion title="PutImageResult">
464
+
465
+ | Field | Type | Meaning |
466
+ | ------------- | --------------------------- | --------------------------------- |
467
+ | `key` | `string` | Original object key |
468
+ | `meta` | `{ width, height, format }` | Header metadata of the original |
469
+ | `variants` | `Record<string, string>` | Variant name → object key |
470
+ | `placeholder` | `string?` | ThumbHash data URL when requested |
471
+
472
+ </Accordion>
473
+
474
+ </Accordions>
475
+
476
+ ## Image Pipeline
477
+
478
+ <Callout title="Detailed section">
479
+ Prefer `putImage` when you want original + named variants in one call. Use `image(…)` when you
480
+ need a one-off transform, metadata probe, or custom out key.
481
+ </Callout>
482
+
483
+ <StoreFilesVariants />
484
+
485
+ Chain on `fx.store(uploads).image(source, options?)`:
486
+
487
+ | Step | Meaning |
488
+ | ---------------------------------------------- | -------------------------------- |
489
+ | `.resize(w, h?, opts?)` | Resize (Bun fit options) |
490
+ | `.rotate(degrees)` · `.flip()` · `.flop()` | Geometry |
491
+ | `.modulate({ brightness?, saturation? })` | Color |
492
+ | `.jpeg` / `.png` / `.webp` / `.heic` / `.avif` | Encode |
493
+ | `.metadata()` | Width / height / format (header) |
494
+ | `.bytes()` · `.blob()` | Materialize |
495
+ | `.placeholder()` | ThumbHash data URL |
496
+ | `.put(outKey)` | Write the transformed bytes |
497
+
498
+ Decode guards (`maxPixels`, `autoOrient`) pass as the second argument to
499
+ `image(…)`, same defaults as `putImage`.
500
+
501
+ <Accordions>
502
+
503
+ <Accordion title="Source resolution">
504
+ `image(source)` accepts an object key **or** raw `Uint8Array`.
505
+
506
+ | Source | Behavior |
507
+ | ------------ | ----------------------------------------------------------------- |
508
+ | `string` key | Loads via `get`; missing → `files image: object not found: {key}` |
509
+ | `Uint8Array` | Transforms in memory (no prior put required) |
510
+
511
+ ```typescript
512
+ const webp = await fx
513
+ .store(uploads)
514
+ .image(rawBytes, { maxPixels: 2048 * 2048 })
515
+ .resize(800)
516
+ .webp({ quality: 80 })
517
+ .bytes();
518
+ ```
519
+
520
+ </Accordion>
521
+
522
+ <Accordion title="Encode & platform codecs">
523
+ Call one encode step before materializing. HEIC / AVIF may fall back to WebP
524
+ when the platform throws `ERR_IMAGE_FORMAT_UNSUPPORTED` (quality preserved
525
+ when set).
526
+
527
+ ```typescript
528
+ await fx
529
+ .store(uploads)
530
+ .image("photos/hero.jpg")
531
+ .resize(1200, undefined, { fit: "inside", withoutEnlargement: true })
532
+ .avif({ quality: 70 })
533
+ .put("photos/hero.avif");
534
+ ```
535
+
536
+ </Accordion>
537
+
538
+ <Accordion title="Decode guards">
539
+
540
+ | Option | Default | Meaning |
541
+ | ------------ | --------------------- | ------------------------------------------------ |
542
+ | `maxPixels` | `4096 * 4096` (16 MP) | Reject oversized width×height before pixel alloc |
543
+ | `autoOrient` | `true` | Apply JPEG EXIF Orientation first |
544
+
545
+ Exceeding the ceiling surfaces Bun’s `ERR_IMAGE_TOO_MANY_PIXELS`. Pass a higher
546
+ ceiling, or `maxPixels: false` only when you trust the source.
547
+
548
+ </Accordion>
549
+
550
+ </Accordions>
551
+
552
+ ## Declare Options
553
+
554
+ | Option | Type | Default | Meaning |
555
+ | ------------- | -------- | ------- | ------------------------ |
556
+ | `description` | `string` | omitted | Console / Manifest label |
557
+
558
+ ```typescript
559
+ export const uploads = store.files("uploads", {
560
+ description: "User uploads and derived image variants",
561
+ });
562
+ ```
563
+
564
+ ## Drivers
565
+
566
+ | Driver | Runs as | Best for |
567
+ | -------- | --------------------------------- | ------------------------ |
568
+ | `s3` | S3-compatible (RustFS in Compose) | Dev + prod default |
569
+ | `fs` | Local filesystem root | Single-node laptop paths |
570
+ | `memory` | Process map | Test default |
571
+
572
+ Defaults: `s3` / `memory` / `s3` (dev / test / prod). Image pins (e.g.
573
+ RustFS) live under `images.store.files` — the driver id stays `s3`.
574
+
575
+ ```typescript title="oke.config.ts"
576
+ import { defineConfig } from "okengine/config";
577
+
578
+ export default defineConfig({
579
+ drivers: {
580
+ // omit store.files to use defaults — pin only overrides
581
+ // store: { files: { dev: "fs", test: "memory", prod: "s3" } },
582
+ },
583
+ images: {
584
+ store: {
585
+ files: "rustfs/rustfs:1.0.0-rc.5",
586
+ },
587
+ },
588
+ });
589
+ ```
590
+
591
+ For `s3`, Compose / env typically supply `S3_ENDPOINT`, `S3_ACCESS_KEY_ID`,
592
+ `S3_SECRET_ACCESS_KEY`, and optional `S3_REGION` / `S3_SESSION_TOKEN`. Without
593
+ `S3_ENDPOINT`, CreateBucket is skipped so real AWS is not auto-provisioned.
594
+
595
+ ## Troubleshooting
596
+
597
+ <Accordions>
598
+
599
+ <Accordion title="No files driver configured">
600
+ Boot needs `drivers.store.files` (or `DRIVER_DEFAULTS`). For `s3`, set the bucket / endpoint env
601
+ your Compose stack expects (`S3_ENDPOINT`, `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY`).
602
+ </Accordion>
603
+
604
+ <Accordion title="Unknown store ref for files">
605
+ Cause: `Unknown store ref: …`. Import the module that calls `store.files(…)` before Flows run
606
+ (starters load `@/core`).
607
+ </Accordion>
608
+
609
+ <Accordion title="Invalid object key">
610
+ Cause: `Invalid object key: …` on the `fs` driver when the key starts with `/` or contains `..`.
611
+ Use relative, non-escaping keys.
612
+ </Accordion>
613
+
614
+ <Accordion title="ERR_IMAGE_TOO_MANY_PIXELS">
615
+ Decode exceeded `maxPixels` (default `4096 * 4096`). Pass a higher ceiling or `maxPixels: false`
616
+ only when you trust the source.
617
+ </Accordion>
618
+
619
+ <Accordion title="files image: object not found">
620
+ Cause: `files image: object not found: {key}`. `image(key)` loads via `get` first — put the
621
+ original, or pass raw `Uint8Array` as the source.
622
+ </Accordion>
623
+
624
+ <Accordion title="files image variant: set only one of jpeg/png/…">
625
+ Each `ImageVariantSpec` may set at most one encode field. Pick `webp` **or** `jpeg`, not both.
626
+ </Accordion>
627
+
628
+ <Accordion title="get returns null after putImage variant">
629
+ Read the **variant key** from `result.variants.thumb`, not the original key. Naming is `{stem}.
630
+ {variant}.{ext}` beside the original.
631
+ </Accordion>
632
+
633
+ <Accordion title="I expected presignPut / transform">
634
+ Those helpers are not on the fx handle. Upload with `put` / `putImage`, and transform with
635
+ `image(…).resize(…).webp(…).put(…)` (or `putImage` variants).
636
+ </Accordion>
637
+
638
+ </Accordions>
639
+
640
+ ## Learn more
641
+
642
+ - [Store](/docs/elements/store) — four facets; driver defaults
643
+ - [SQL](/docs/elements/store/sql) — relational data beside blob keys
644
+ - [HTTP](/docs/elements/flow/http) — routes that accept uploads
645
+ - [Vault](/docs/elements/vault) — credentials for S3 when you configure bindings
646
+ - [fx](/docs/reference/fx) — `fx.store(decl)`
647
+ - [Configuration](/docs/reference/configuration) — `drivers.store.files`
648
+
649
+ ## Next
650
+
651
+ <Cards>
652
+ <Card
653
+ title="Search"
654
+ description="BM25 ± LSH on SQL columns, plus store.index."
655
+ href="/docs/elements/store/search"
656
+ />
657
+ <Card
658
+ title="KV"
659
+ description="Namespaced get/set with duration TTL."
660
+ href="/docs/elements/store/kv"
661
+ />
662
+ <Card
663
+ title="Store Overview"
664
+ description="SQL · KV · files · index — one handle."
665
+ href="/docs/elements/store"
666
+ />
667
+ </Cards>