okengine 0.18.4 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (512) hide show
  1. package/AGENTS.md +18 -10
  2. package/README.md +2 -2
  3. package/package.json +51 -50
  4. package/site/content/docs/ai/index.mdx +25 -6
  5. package/site/content/docs/ai/llms-txt.mdx +1 -1
  6. package/site/content/docs/ai/meta.json +1 -1
  7. package/site/content/docs/ai/skills.mdx +4 -1
  8. package/site/content/docs/ai/try-it.mdx +58 -0
  9. package/site/content/docs/client/auth.mdx +208 -0
  10. package/site/content/docs/client/calling.mdx +451 -0
  11. package/site/content/docs/client/index.mdx +209 -0
  12. package/site/content/docs/client/live.mdx +234 -0
  13. package/site/content/docs/client/meta.json +5 -0
  14. package/site/content/docs/client/react.mdx +249 -0
  15. package/site/content/docs/elements/ai/agents.mdx +232 -0
  16. package/site/content/docs/elements/ai/index.mdx +341 -0
  17. package/site/content/docs/elements/ai/mcp.mdx +275 -0
  18. package/site/content/docs/elements/ai/meta.json +5 -0
  19. package/site/content/docs/elements/ai/models.mdx +283 -0
  20. package/site/content/docs/elements/ai/prompts.mdx +256 -0
  21. package/site/content/docs/elements/channel/email.mdx +266 -0
  22. package/site/content/docs/elements/channel/index.mdx +344 -0
  23. package/site/content/docs/elements/channel/meta.json +5 -0
  24. package/site/content/docs/elements/channel/push.mdx +218 -0
  25. package/site/content/docs/elements/channel/receipts.mdx +206 -0
  26. package/site/content/docs/elements/channel/sms.mdx +264 -0
  27. package/site/content/docs/elements/channel/whatsapp.mdx +220 -0
  28. package/site/content/docs/elements/clock/index.mdx +300 -0
  29. package/site/content/docs/elements/clock/meta.json +5 -0
  30. package/site/content/docs/elements/clock/schedules.mdx +508 -0
  31. package/site/content/docs/elements/clock/sleep.mdx +445 -0
  32. package/site/content/docs/elements/flow/consumers.mdx +707 -0
  33. package/site/content/docs/elements/flow/http.mdx +1080 -0
  34. package/site/content/docs/elements/flow/index.mdx +618 -0
  35. package/site/content/docs/elements/flow/meta.json +5 -0
  36. package/site/content/docs/elements/flow/routing.mdx +559 -0
  37. package/site/content/docs/elements/flow/workflows.mdx +640 -0
  38. package/site/content/docs/elements/gate/auth.mdx +371 -0
  39. package/site/content/docs/elements/gate/authorization.mdx +298 -0
  40. package/site/content/docs/elements/gate/index.mdx +418 -0
  41. package/site/content/docs/elements/gate/meta.json +5 -0
  42. package/site/content/docs/elements/gate/rate-limits.mdx +365 -0
  43. package/site/content/docs/elements/gate/rls.mdx +303 -0
  44. package/site/content/docs/elements/gate/tenancy.mdx +372 -0
  45. package/site/content/docs/elements/index.mdx +52 -11
  46. package/site/content/docs/elements/signal/broadcast.mdx +517 -0
  47. package/site/content/docs/elements/signal/index.mdx +352 -0
  48. package/site/content/docs/elements/signal/live.mdx +590 -0
  49. package/site/content/docs/elements/signal/meta.json +5 -0
  50. package/site/content/docs/elements/signal/once.mdx +596 -0
  51. package/site/content/docs/elements/store/files.mdx +659 -0
  52. package/site/content/docs/elements/store/index.mdx +310 -0
  53. package/site/content/docs/elements/store/kv.mdx +629 -0
  54. package/site/content/docs/elements/store/meta.json +5 -0
  55. package/site/content/docs/elements/store/search.mdx +946 -0
  56. package/site/content/docs/elements/store/sql.mdx +937 -0
  57. package/site/content/docs/elements/vault/config.mdx +256 -0
  58. package/site/content/docs/elements/vault/index.mdx +356 -0
  59. package/site/content/docs/elements/vault/meta.json +5 -0
  60. package/site/content/docs/elements/vault/rotation.mdx +273 -0
  61. package/site/content/docs/elements/vault/secrets.mdx +285 -0
  62. package/site/content/docs/index.mdx +78 -30
  63. package/site/content/docs/meta.json +11 -8
  64. package/site/content/docs/plugins/cors.mdx +2 -1
  65. package/site/content/docs/plugins/csrf.mdx +4 -1
  66. package/site/content/docs/plugins/headers.mdx +2 -1
  67. package/site/content/docs/plugins/ip-allowlist.mdx +2 -1
  68. package/site/content/docs/plugins/magic-link.mdx +6 -0
  69. package/site/content/docs/plugins/maintenance-mode.mdx +2 -1
  70. package/site/content/docs/plugins/oauth.mdx +1 -1
  71. package/site/content/docs/plugins/otp.mdx +12 -1
  72. package/site/content/docs/plugins/passkey.mdx +39 -15
  73. package/site/content/docs/plugins/two-factor.mdx +91 -34
  74. package/site/content/docs/plugins/username.mdx +1 -1
  75. package/site/content/docs/recipes/caddy.mdx +3 -3
  76. package/site/content/docs/recipes/index.mdx +18 -28
  77. package/site/content/docs/recipes/mailpit.mdx +1 -1
  78. package/site/content/docs/recipes/meilisearch.mdx +3 -3
  79. package/site/content/docs/recipes/meta.json +1 -4
  80. package/site/content/docs/recipes/nginx.mdx +2 -2
  81. package/site/content/docs/recipes/openrouter.mdx +283 -0
  82. package/site/content/docs/recipes/pgdog.mdx +3 -3
  83. package/site/content/docs/recipes/postgres.mdx +1 -2
  84. package/site/content/docs/recipes/rustfs.mdx +2 -2
  85. package/site/content/docs/recipes/traefik.mdx +3 -3
  86. package/site/content/docs/reference/cli.mdx +231 -0
  87. package/site/content/docs/reference/configuration.mdx +30 -10
  88. package/site/content/docs/reference/environment-variables.mdx +93 -20
  89. package/site/content/docs/reference/errors.mdx +132 -29
  90. package/site/content/docs/reference/fx.mdx +137 -73
  91. package/site/content/docs/reference/index.mdx +13 -21
  92. package/site/content/docs/reference/meta.json +4 -5
  93. package/site/content/docs/reference/okid.mdx +42 -17
  94. package/site/content/docs/reference/plugins.mdx +4 -3
  95. package/site/content/docs/reference/security.mdx +197 -0
  96. package/site/content/docs/understand/meta.json +5 -0
  97. package/site/content/docs/understand/the-anatomy.mdx +132 -0
  98. package/site/content/docs/understand/the-model.mdx +32 -0
  99. package/site/content/docs/understand/the-problem.mdx +74 -0
  100. package/site/content/docs/understand/the-vocabulary.mdx +26 -0
  101. package/src/auth/api-keys.ts +2 -1
  102. package/src/auth/bindings.ts +64 -16
  103. package/src/auth/gate-auth.test.ts +6 -1
  104. package/src/auth/identity.ts +152 -5
  105. package/src/auth/index.ts +29 -0
  106. package/src/auth/invites.ts +2 -1
  107. package/src/auth/method-context.ts +41 -0
  108. package/src/auth/oauth-as/crypto.ts +2 -1
  109. package/src/auth/oauth-as/stores.ts +2 -1
  110. package/src/auth/operator.ts +2 -1
  111. package/src/auth/sessions-jwt.test.ts +77 -0
  112. package/src/auth/sessions.ts +20 -4
  113. package/src/auth/tenants.ts +3 -2
  114. package/src/auth/two-factor-challenge.test.ts +61 -0
  115. package/src/auth/two-factor-challenge.ts +284 -0
  116. package/src/auth/verification.ts +5 -0
  117. package/src/bench/REPORT.md +49 -0
  118. package/src/bench/g03-signal-once.bench.ts +1 -1
  119. package/src/bench/g10-observability-contention.bench.ts +1 -1
  120. package/src/bench/g17-hybrid-search.bench.ts +572 -0
  121. package/src/bench/load-app.ts +2 -10
  122. package/src/cli/ai-setup/ai-setup.test.ts +331 -42
  123. package/src/cli/ai-setup/apply.ts +294 -52
  124. package/src/cli/ai-setup/catalog.ts +238 -1342
  125. package/src/cli/ai-setup/index.ts +14 -99
  126. package/src/cli/ai-setup/prompts.ts +60 -443
  127. package/src/cli/ask-seed.test.ts +96 -0
  128. package/src/cli/ask-seed.ts +82 -0
  129. package/src/cli/ask-vault-gaps.test.ts +84 -0
  130. package/src/cli/ask-vault-gaps.ts +146 -0
  131. package/src/cli/build.ts +2 -2
  132. package/src/cli/client-add.test.ts +26 -1
  133. package/src/cli/client-add.ts +106 -27
  134. package/src/cli/competitor-mention-removal.test.ts +1 -1
  135. package/src/cli/db-seed.ts +2 -0
  136. package/src/cli/db.ts +150 -11
  137. package/src/cli/dev.ts +119 -211
  138. package/src/cli/docker-clean.ts +2 -2
  139. package/src/cli/docker-cli.test.ts +1 -1
  140. package/src/cli/doctor-diff.ts +4 -2
  141. package/src/cli/doctor-pii.test.ts +1 -1
  142. package/src/cli/doctor.ts +14 -1
  143. package/src/cli/hero-meta.test.ts +4 -2
  144. package/src/cli/load-config.images.test.ts +8 -10
  145. package/src/cli/load-config.ts +1 -1
  146. package/src/cli/registry.ts +22 -10
  147. package/src/cli/replay.ts +3 -1
  148. package/src/cli/start.ts +1 -1
  149. package/src/cli/tui/keys.ts +2 -3
  150. package/src/client/auth/cookies.ts +74 -0
  151. package/src/client/auth/create-auth-client.ts +636 -0
  152. package/src/client/auth/denials.ts +99 -0
  153. package/src/client/auth/session.ts +283 -0
  154. package/src/client/auth.test.ts +251 -0
  155. package/src/client/auth.ts +39 -114
  156. package/src/client/create-with-session.ts +245 -0
  157. package/src/client/create.ts +51 -5
  158. package/src/client/index.ts +11 -1
  159. package/src/client/live.ts +11 -79
  160. package/src/client/notes-contract.test.ts +5 -10
  161. package/src/client/sse.ts +137 -0
  162. package/src/client/stream.ts +147 -0
  163. package/src/client/transport.test.ts +32 -0
  164. package/src/client/transport.ts +123 -26
  165. package/src/client/types.ts +118 -14
  166. package/src/client-react/index.ts +185 -24
  167. package/src/client-react/use-live-query.ts +13 -3
  168. package/src/compiler/aot.test.ts +7 -4
  169. package/src/compiler/effects-embed.test.ts +56 -0
  170. package/src/compiler/effects-fetch.test.ts +43 -0
  171. package/src/compiler/effects-infer.ts +58 -2
  172. package/src/compiler/extract.test.ts +258 -44
  173. package/src/compiler/extract.ts +566 -48
  174. package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +7 -4
  175. package/src/compiler/fixtures/skyport/src/flows/bookings/signals.ts +2 -8
  176. package/src/compiler/fixtures/skyport.expected.json +4 -4
  177. package/src/compiler/fixtures/triggers/five-triggers.ts +14 -10
  178. package/src/compiler/response.ts +2 -2
  179. package/src/compiler/search-writer-isolation.test.ts +40 -0
  180. package/src/config/index.ts +11 -0
  181. package/src/console/server/app.ts +17 -23
  182. package/src/console/server/bind.ts +4 -0
  183. package/src/console/server/console.test.ts +2 -2
  184. package/src/console/server/flows-invoke.test.ts +50 -34
  185. package/src/console/server/flows.ts +436 -255
  186. package/src/console/server/runs-ingest.test.ts +6 -3
  187. package/src/console/server/serve.ts +1 -1
  188. package/src/console/server/signals.test.ts +1 -5
  189. package/src/console/server/signals.ts +8 -6
  190. package/src/console/server/state.ts +6 -1
  191. package/src/console/server/store.ts +1 -1
  192. package/src/console/ui-next/dist/assets/FileExportIcon-Ck-5od4R.js +1 -0
  193. package/src/console/ui-next/dist/assets/MoreHorizontalCircle01Icon-gMNGsE37.js +1 -0
  194. package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +1 -0
  195. package/src/console/ui-next/dist/assets/UnavailableIcon-D9cvHVPr.js +1 -0
  196. package/src/console/ui-next/dist/assets/UserIcon-DaE2PB5_.js +1 -0
  197. package/src/console/ui-next/dist/assets/access-page-DFeymU07.js +4 -0
  198. package/src/console/ui-next/dist/assets/agent-disclosure-U1rdfblp.js +1 -0
  199. package/src/console/ui-next/dist/assets/cache-glyph-BeFJeqBG.js +1 -0
  200. package/src/console/ui-next/dist/assets/call-pii-button-CVAONPii.js +1 -0
  201. package/src/console/ui-next/dist/assets/collapsible-D2A6NJ-3.js +1 -0
  202. package/src/console/ui-next/dist/assets/copy-inline-button-CAYD18cr.js +1 -0
  203. package/src/console/ui-next/dist/assets/dagre.esm-B1_XeuLP.js +1 -0
  204. package/src/console/ui-next/dist/assets/detail-header-DVWNjWjg.js +1 -0
  205. package/src/console/ui-next/dist/assets/dropdown-menu-4h2LOVXM.js +1 -0
  206. package/src/console/ui-next/dist/assets/duration-tone-Cgk_h5ja.js +9 -0
  207. package/src/console/ui-next/dist/assets/element-icons-BI8cJgdh.js +1 -0
  208. package/src/console/ui-next/dist/assets/explorer-empty-CJs5A-wm.js +1 -0
  209. package/src/console/ui-next/dist/assets/flows-page-Dss7941e.js +1 -0
  210. package/src/console/ui-next/dist/assets/highlighted-json-MYZQtRnw.js +154 -0
  211. package/src/console/ui-next/dist/assets/http-method-Jrh39p7A.js +1 -0
  212. package/src/console/ui-next/dist/assets/index-D0zS5rKO.css +2 -0
  213. package/src/console/ui-next/dist/assets/index-DXP2dBIF.js +63 -0
  214. package/src/console/ui-next/dist/assets/observability-page-HvolXxTI.js +4 -0
  215. package/src/console/ui-next/dist/assets/react-dom-Ddte4I-Q.js +9 -0
  216. package/src/console/ui-next/dist/assets/replica-lag-CSh2dzrb.js +18 -0
  217. package/src/console/ui-next/dist/assets/request-meta-BatF8KrK.js +1 -0
  218. package/src/console/ui-next/dist/assets/shortcut-keys-3ILd8oGn.js +1 -0
  219. package/src/console/ui-next/dist/assets/{sql-BskegiFM.js → sql-BsFa4tDR.js} +1 -1
  220. package/src/console/ui-next/dist/assets/store-page-eiKiHnNe.js +41 -0
  221. package/src/console/ui-next/dist/assets/trace-detail-sheet-CZkMeKS-.js +2 -0
  222. package/src/console/ui-next/dist/assets/tree-expand-toggle-CW8y5A2h.js +55 -0
  223. package/src/console/ui-next/dist/assets/units-page-B_RWJrEO.js +1 -0
  224. package/src/console/ui-next/dist/assets/{use-vault-list-uk4WVboC.js → use-vault-list-CT4-gajj.js} +1 -1
  225. package/src/console/ui-next/dist/assets/vault-page-CWrg-A68.js +2 -0
  226. package/src/console/ui-next/dist/assets/xyflow-CSyC6ryz.css +1 -0
  227. package/src/console/ui-next/dist/assets/xyflow-yApv7D4e.js +7 -0
  228. package/src/console/ui-next/dist/index.html +6 -13
  229. package/src/console/ui-next/seed-invoke-host.ts +21 -18
  230. package/src/console/ui-next/src/client.ts +7 -2
  231. package/src/console/ui-next/src/features/flows/graph/element-map.ts +2 -0
  232. package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +12 -2
  233. package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +4 -0
  234. package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +8 -0
  235. package/src/console/ui-next/src/features/flows/traces/waterfall-bars.ts +4 -0
  236. package/src/console/ui-next/src/features/flows/traces/waterfall-tooltip.ts +9 -3
  237. package/src/console/ui-next/src/features/store/detail/reveal-cell.tsx +7 -11
  238. package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +2 -2
  239. package/src/console/ui-next/src/features/store/lib/fields-from-table.ts +2 -0
  240. package/src/console/ui-next/src/features/store/lib/grid-model.test.ts +25 -1
  241. package/src/console/ui-next/src/features/store/lib/grid-model.ts +36 -1
  242. package/src/console/ui-next/src/features/store/query/query-results.tsx +3 -3
  243. package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +2 -0
  244. package/src/console/ui-next/src/features/units/lib/call-read-safe.ts +3 -1
  245. package/src/console/ui-next/src/features/units/lib/fields-from-schema.ts +6 -0
  246. package/src/docker/ai-model-status.test.ts +1 -12
  247. package/src/docker/ai-model-status.ts +10 -38
  248. package/src/docker/compose-up.test.ts +100 -0
  249. package/src/docker/compose-up.ts +292 -0
  250. package/src/docker/compose.ts +4 -14
  251. package/src/docker/derive.ts +9 -34
  252. package/src/docker/docker.test.ts +16 -261
  253. package/src/docker/images-config.test.ts +13 -25
  254. package/src/docker/index.ts +1 -24
  255. package/src/docker/recipes/index.ts +0 -23
  256. package/src/docker/recipes/pgdog.ts +6 -4
  257. package/src/docker/stack-id.test.ts +2 -2
  258. package/src/docker/stack-id.ts +0 -1
  259. package/src/docker/types.ts +2 -2
  260. package/src/drivers/ai-anthropic.ts +10 -0
  261. package/src/drivers/ai-openai-compatible.ts +34 -7
  262. package/src/drivers/ai-providers.test.ts +0 -107
  263. package/src/drivers/ai-stream.test.ts +0 -34
  264. package/src/drivers/ai-types.ts +17 -8
  265. package/src/drivers/channel-fcm.ts +6 -1
  266. package/src/drivers/channel-msegat.ts +6 -1
  267. package/src/drivers/channel-resend.ts +5 -1
  268. package/src/drivers/channel-smtp.ts +5 -1
  269. package/src/drivers/channel-sndr.ts +10 -1
  270. package/src/drivers/channel-taqnyat-mail.ts +5 -1
  271. package/src/drivers/channel-taqnyat-whatsapp.ts +6 -1
  272. package/src/drivers/channel-taqnyat.ts +6 -1
  273. package/src/drivers/channel-types.ts +10 -0
  274. package/src/drivers/channel-unifonic.ts +6 -1
  275. package/src/drivers/channel-wa-cloud.ts +6 -1
  276. package/src/drivers/channel-webpush.ts +6 -1
  277. package/src/drivers/external.ts +33 -0
  278. package/src/drivers/index.ts +0 -10
  279. package/src/drivers/meilisearch.ts +6 -0
  280. package/src/drivers/oauth-types.ts +4 -0
  281. package/src/drivers/postgres.test.ts +74 -3
  282. package/src/drivers/postgres.ts +175 -6
  283. package/src/drivers/signal-types.ts +5 -5
  284. package/src/drivers/types.ts +7 -0
  285. package/src/drivers/vault-types.ts +5 -0
  286. package/src/elements/ai/declare.ts +17 -4
  287. package/src/elements/ai/eval.ts +1 -1
  288. package/src/elements/ai/mcp-http.ts +1 -1
  289. package/src/elements/ai/pii.ts +1 -1
  290. package/src/elements/ai/providers.test.ts +289 -0
  291. package/src/elements/ai/providers.ts +169 -0
  292. package/src/elements/ai/runtime.ts +68 -5
  293. package/src/elements/ai/schema.ts +2 -2
  294. package/src/elements/ai.test.ts +7 -7
  295. package/src/elements/ai.ts +17 -1
  296. package/src/elements/channel/runtime.ts +105 -12
  297. package/src/elements/clock/cron-fields.test.ts +144 -0
  298. package/src/elements/clock/cron-fields.ts +185 -0
  299. package/src/elements/clock/declare.ts +247 -3
  300. package/src/elements/clock.ts +21 -1
  301. package/src/elements/index.ts +15 -0
  302. package/src/elements/signal/chaos-child.ts +1 -2
  303. package/src/elements/signal/declare.ts +65 -37
  304. package/src/elements/signal/delivery-modes.test.ts +11 -31
  305. package/src/elements/signal/dry-run-replay.test.ts +1 -7
  306. package/src/elements/signal/dry-run-write-isolation.test.ts +1 -7
  307. package/src/elements/signal/key-ordering.test.ts +6 -26
  308. package/src/elements/signal/lease-reclaim.test.ts +2 -6
  309. package/src/elements/signal/optional-emit.test.ts +6 -9
  310. package/src/elements/signal/order-lifecycle.test.ts +4 -17
  311. package/src/elements/signal/orphan-messages.test.ts +4 -15
  312. package/src/elements/signal/reconcile.test.ts +2 -2
  313. package/src/elements/signal/runtime.ts +1 -1
  314. package/src/elements/signal/schema-emit.test.ts +4 -8
  315. package/src/elements/signal.test.ts +24 -49
  316. package/src/elements/signal.ts +9 -2
  317. package/src/elements/store/domain-ddl.test.ts +1 -1
  318. package/src/elements/store/live-http.test.ts +9 -4
  319. package/src/elements/store/prepare-row.test.ts +123 -0
  320. package/src/elements/store/resource.ts +43 -12
  321. package/src/elements/store/schema-decl.ts +117 -0
  322. package/src/elements/store/search-backfill.ts +205 -0
  323. package/src/elements/store/search-bind.ts +66 -0
  324. package/src/elements/store/search-bm25.ts +78 -0
  325. package/src/elements/store/search-ddl.ts +156 -0
  326. package/src/elements/store/search-embed-flow.ts +141 -0
  327. package/src/elements/store/search-errors.ts +34 -0
  328. package/src/elements/store/search-fusion.ts +112 -0
  329. package/src/elements/store/search-lsh.ts +179 -0
  330. package/src/elements/store/search-runtime.ts +300 -0
  331. package/src/elements/store/search.test.ts +144 -0
  332. package/src/elements/store/sql-session.test.ts +1 -0
  333. package/src/elements/store/sql-session.ts +83 -3
  334. package/src/elements/store/table.ts +23 -2
  335. package/src/elements/store/upsert-app.test.ts +1 -3
  336. package/src/elements/store.ts +50 -0
  337. package/src/full.ts +4 -2
  338. package/src/http.ts +4 -1
  339. package/src/i18n/catalogs/ar.ts +14 -6
  340. package/src/i18n/catalogs/en.ts +14 -6
  341. package/src/index.ts +6 -1
  342. package/src/kernel/adopt-barrel-fresh.test.ts +7 -7
  343. package/src/kernel/adopt-routes.ts +9 -1
  344. package/src/kernel/app-auth.ts +5 -0
  345. package/src/kernel/app.ts +102 -9
  346. package/src/kernel/auto-cache.test.ts +6 -12
  347. package/src/kernel/auto-registry.test.ts +3 -3
  348. package/src/kernel/boot-bind/ai.test.ts +22 -19
  349. package/src/kernel/boot-bind/ai.ts +5 -43
  350. package/src/kernel/boot-bind/clock.ts +23 -5
  351. package/src/kernel/boot-bind/honor-config.test.ts +4 -4
  352. package/src/kernel/boot.test.ts +11 -6
  353. package/src/kernel/boot.ts +66 -16
  354. package/src/kernel/boundary-contract.ts +93 -0
  355. package/src/kernel/budget.test.ts +1 -1
  356. package/src/kernel/call.ts +89 -0
  357. package/src/kernel/capability.ts +6 -0
  358. package/src/kernel/client-descriptor.ts +123 -0
  359. package/src/kernel/clock-timezone.test.ts +80 -0
  360. package/src/kernel/correlation.test.ts +1 -1
  361. package/src/kernel/dry-run.ts +11 -8
  362. package/src/kernel/effects-stamping.test.ts +42 -9
  363. package/src/kernel/effects.test.ts +4 -2
  364. package/src/kernel/effects.ts +51 -9
  365. package/src/kernel/errors-channel.ts +17 -0
  366. package/src/kernel/errors-live-resume.ts +3 -2
  367. package/src/kernel/errors-tenant.ts +7 -4
  368. package/src/kernel/errors.registry-helpers.ts +96 -0
  369. package/src/kernel/errors.registry.test.ts +139 -25
  370. package/src/kernel/errors.ts +102 -23
  371. package/src/kernel/external-effects.test.ts +196 -0
  372. package/src/kernel/flow.test.ts +13 -4
  373. package/src/kernel/flow.ts +69 -71
  374. package/src/kernel/fx-ask-telemetry.test.ts +2 -2
  375. package/src/kernel/fx-dead-letters.test.ts +3 -3
  376. package/src/kernel/fx-fetch.ts +55 -0
  377. package/src/kernel/fx-live-stream.ts +2 -2
  378. package/src/kernel/fx-live.test.ts +8 -8
  379. package/src/kernel/fx.test.ts +17 -11
  380. package/src/kernel/fx.ts +180 -57
  381. package/src/kernel/horizontal-child.ts +1 -1
  382. package/src/kernel/http-query.test.ts +3 -6
  383. package/src/kernel/index.ts +13 -3
  384. package/src/kernel/instance-id.ts +1 -1
  385. package/src/kernel/instances.test.ts +40 -0
  386. package/src/kernel/instances.ts +47 -9
  387. package/src/kernel/live-http.test.ts +6 -7
  388. package/src/kernel/live-http.ts +6 -2
  389. package/src/kernel/live-resume.test.ts +1 -1
  390. package/src/kernel/mcp-tool.test.ts +3 -3
  391. package/src/kernel/on.ts +98 -4
  392. package/src/kernel/pipeline.test.ts +12 -15
  393. package/src/kernel/plugin-elements.test.ts +1 -1
  394. package/src/kernel/stamp-http.test.ts +3 -3
  395. package/src/kernel/triggers.ts +210 -95
  396. package/src/kernel-entry.ts +0 -1
  397. package/src/manifest/diff.ts +11 -1
  398. package/src/manifest/fixtures/skyport.excerpt.json +1 -1
  399. package/src/manifest/types.ts +46 -2
  400. package/src/mcp/docs-index.ts +3 -3
  401. package/src/mcp/docs-mcp.test.ts +2 -2
  402. package/src/mcp/docs-tools.ts +1 -1
  403. package/src/okid.test.ts +56 -0
  404. package/src/okid.ts +55 -11
  405. package/src/plugins/anonymous.ts +8 -4
  406. package/src/plugins/auth/shared.ts +27 -3
  407. package/src/plugins/auth-delivery.mailpit.integration.test.ts +1 -1
  408. package/src/plugins/auth-methods.security.test.ts +561 -23
  409. package/src/plugins/config-source.ts +1 -1
  410. package/src/plugins/csrf.test.ts +65 -0
  411. package/src/plugins/index.ts +2 -0
  412. package/src/plugins/magic-link.ts +36 -16
  413. package/src/plugins/oauth/shared.ts +3 -1
  414. package/src/plugins/oauth.ts +22 -14
  415. package/src/plugins/otp.ts +37 -21
  416. package/src/plugins/passkey-webauthn.ts +9 -6
  417. package/src/plugins/passkey.ts +98 -26
  418. package/src/plugins/pre-account-hijack.test.ts +223 -0
  419. package/src/plugins/two-factor.ts +466 -37
  420. package/src/plugins/username.ts +19 -7
  421. package/src/release/build-lib.ts +1 -0
  422. package/src/release/limits.ts +2 -2
  423. package/src/release/measure.ts +1 -1
  424. package/src/runs/collect.test.ts +0 -2
  425. package/src/runtime/json-code-block.test.ts +250 -20
  426. package/src/runtime/json-code-block.ts +1261 -41
  427. package/src/term.test.ts +1 -1
  428. package/src/test/create-test-app.test.ts +12 -12
  429. package/src/test/live-signals.test.ts +6 -6
  430. package/src/test/provisions.integration.test.ts +10 -14
  431. package/src/test/reset-element-registries.ts +1 -1
  432. package/src/test/tenant-isolation.test.ts +12 -6
  433. package/site/content/docs/deployment/docker-swarm.mdx +0 -164
  434. package/site/content/docs/deployment/docker.mdx +0 -227
  435. package/site/content/docs/deployment/index.mdx +0 -83
  436. package/site/content/docs/deployment/kubernetes.mdx +0 -176
  437. package/site/content/docs/deployment/meta.json +0 -5
  438. package/site/content/docs/deployment/reverse-proxy.mdx +0 -234
  439. package/site/content/docs/elements/ai.mdx +0 -385
  440. package/site/content/docs/elements/channel.mdx +0 -346
  441. package/site/content/docs/elements/clock.mdx +0 -244
  442. package/site/content/docs/elements/flow.mdx +0 -420
  443. package/site/content/docs/elements/gate.mdx +0 -436
  444. package/site/content/docs/elements/signal.mdx +0 -380
  445. package/site/content/docs/elements/store.mdx +0 -1099
  446. package/site/content/docs/elements/vault.mdx +0 -405
  447. package/site/content/docs/get-started/basic-usage.mdx +0 -173
  448. package/site/content/docs/get-started/index.mdx +0 -43
  449. package/site/content/docs/get-started/installation.mdx +0 -220
  450. package/site/content/docs/get-started/introduction.mdx +0 -132
  451. package/site/content/docs/get-started/meta.json +0 -13
  452. package/site/content/docs/get-started/project-structure.mdx +0 -925
  453. package/site/content/docs/get-started/testing.mdx +0 -328
  454. package/site/content/docs/get-started/why.mdx +0 -162
  455. package/site/content/docs/recipes/llama-cpp.mdx +0 -151
  456. package/site/content/docs/recipes/ollama.mdx +0 -142
  457. package/site/content/docs/recipes/sglang.mdx +0 -105
  458. package/site/content/docs/recipes/vllm.mdx +0 -106
  459. package/site/content/docs/reference/cli.md +0 -232
  460. package/site/content/docs/reference/client.mdx +0 -469
  461. package/site/content/docs/reference/security.md +0 -74
  462. package/src/cli/ai-setup/detect-ollama.ts +0 -213
  463. package/src/cli/ai-setup/recommend.test.ts +0 -225
  464. package/src/cli/ai-setup/recommend.ts +0 -225
  465. package/src/cli/dev-controls.test.ts +0 -122
  466. package/src/cli/dev-controls.ts +0 -164
  467. package/src/cli/tui/DevLive.tsx +0 -126
  468. package/src/console/ui-next/dist/assets/access-page-De7Lc2JC.js +0 -4
  469. package/src/console/ui-next/dist/assets/agent-disclosure-BP0Y0Sux.js +0 -1
  470. package/src/console/ui-next/dist/assets/cache-glyph-B5X-NM0-.js +0 -1
  471. package/src/console/ui-next/dist/assets/call-pii-button-ChAUiLo9.js +0 -1
  472. package/src/console/ui-next/dist/assets/collapsible-DGnOM2ph.js +0 -1
  473. package/src/console/ui-next/dist/assets/copy-inline-button-DBHhgHKP.js +0 -1
  474. package/src/console/ui-next/dist/assets/dagre.esm-ZwcdTuZZ.js +0 -1
  475. package/src/console/ui-next/dist/assets/detail-header-DhHM1iaZ.js +0 -1
  476. package/src/console/ui-next/dist/assets/dropdown-menu-_NjTEo5_.js +0 -1
  477. package/src/console/ui-next/dist/assets/duration-tone-sC3lGABz.js +0 -9
  478. package/src/console/ui-next/dist/assets/element-icons-BVXtRyd3.js +0 -1
  479. package/src/console/ui-next/dist/assets/explorer-empty-2uIhBu0_.js +0 -1
  480. package/src/console/ui-next/dist/assets/flows-page-RGy7VEA_.js +0 -1
  481. package/src/console/ui-next/dist/assets/highlighted-json-Awq7gYdu.js +0 -154
  482. package/src/console/ui-next/dist/assets/http-method-_UHM2ODJ.js +0 -1
  483. package/src/console/ui-next/dist/assets/index-Ck88Jmv8.css +0 -2
  484. package/src/console/ui-next/dist/assets/index-_rgpdVzo.js +0 -66
  485. package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +0 -1
  486. package/src/console/ui-next/dist/assets/observability-page-Ds6pcnh-.js +0 -4
  487. package/src/console/ui-next/dist/assets/preload-helper-oH4irX4C.js +0 -1
  488. package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +0 -1
  489. package/src/console/ui-next/dist/assets/react-dom-Bph1y7z7.js +0 -9
  490. package/src/console/ui-next/dist/assets/replica-lag-DKRrbvdo.js +0 -18
  491. package/src/console/ui-next/dist/assets/request-meta-DV0ywz7t.js +0 -1
  492. package/src/console/ui-next/dist/assets/shortcut-JQIZlWfm.js +0 -1
  493. package/src/console/ui-next/dist/assets/shortcut-keys-DKxNTe_m.js +0 -1
  494. package/src/console/ui-next/dist/assets/skeleton-D-czQJT6.js +0 -1
  495. package/src/console/ui-next/dist/assets/store-page-02xOiqIK.js +0 -41
  496. package/src/console/ui-next/dist/assets/trace-detail-sheet-B09O8rA6.js +0 -2
  497. package/src/console/ui-next/dist/assets/tree-expand-toggle-BtyhmWb4.js +0 -55
  498. package/src/console/ui-next/dist/assets/units-page-4rHOePuE.js +0 -1
  499. package/src/console/ui-next/dist/assets/useMutation-B8EO02Ej.js +0 -1
  500. package/src/console/ui-next/dist/assets/useRender-BE2A9BWC.js +0 -1
  501. package/src/console/ui-next/dist/assets/vault-page-DISPgxLM.js +0 -2
  502. package/src/console/ui-next/dist/assets/xyflow-D7n4g6go.js +0 -7
  503. package/src/console/ui-next/dist/assets/xyflow-DZ0Ws1xk.css +0 -1
  504. package/src/docker/ollama-pull.ts +0 -232
  505. package/src/docker/recipes/llama-cpp.ts +0 -298
  506. package/src/docker/recipes/ollama.ts +0 -44
  507. package/src/docker/recipes/sglang.ts +0 -55
  508. package/src/docker/recipes/vllm.ts +0 -44
  509. package/src/drivers/ai-ollama-tools.integration.test.ts +0 -109
  510. package/src/drivers/ai-ollama.integration.test.ts +0 -184
  511. package/src/drivers/ai-ollama.ts +0 -389
  512. package/src/drivers/ollama.ts +0 -14
@@ -0,0 +1,937 @@
1
+ ---
2
+ title: "SQL"
3
+ description: "Relational schemas, field classification, store.resource CRUD, RLS policies, and the fx.store session handle."
4
+ icon: "Table"
5
+ source: "docs/spec/unified-theory.md"
6
+ ---
7
+
8
+ The SQL facet is how you declare relational tables and read or write them from Flows. Schemas use `store.schema.table` + `field.*`; I/O goes through `fx.store(db)`.
9
+
10
+ For developers modeling domain data on okengine — mark columns, push schema, query with the session handle (or mount `store.resource`).
11
+
12
+ <Callout title="The one rule">
13
+ Declare tables with `store.schema.table`. Touch them only via `fx.store(db)` — `select` / `insert`
14
+ / `update` / `delete` / `page` / `search`. There is no `fx.store(db).query.…` Relational Query
15
+ Builder on the handle.
16
+ </Callout>
17
+
18
+ ## Smallest Example
19
+
20
+ <Steps>
21
+
22
+ <Step>
23
+ ### Define a table and bind the store
24
+
25
+ ```typescript title="src/db/schema.decl.ts"
26
+ import { store, field } from "okengine";
27
+
28
+ export const posts = store.schema.table("posts", {
29
+ id: field.id().primaryKey(),
30
+ title: field.text().notNull(),
31
+ authorId: field.text().notNull(),
32
+ publishedAt: field.timestamp(),
33
+ createdAt: field.timestamp().notNull().now(),
34
+ });
35
+
36
+ export const db = store.sql("app", { schema: { posts } });
37
+ ```
38
+
39
+ </Step>
40
+
41
+ <Step>
42
+ ### Query in a Flow
43
+
44
+ ```typescript title="src/flows/posts/list.ts"
45
+ import { on, flow, http } from "okengine";
46
+ import { db, posts } from "@/schema";
47
+
48
+ export const list = on(
49
+ http.get(),
50
+ flow({
51
+ do: async (_, fx) => {
52
+ return await fx.store(db).page(posts, {
53
+ orderBy: [posts.createdAt],
54
+ limit: 20,
55
+ });
56
+ },
57
+ }),
58
+ );
59
+ ```
60
+
61
+ </Step>
62
+
63
+ <Step>
64
+ ### Push schema and call
65
+
66
+ ```bash
67
+ oke db push
68
+ curl -X GET http://localhost:6530/posts -H "accept: application/json"
69
+ ```
70
+
71
+ Response:
72
+
73
+ ```json
74
+ {
75
+ "data": [{ "id": "…", "title": "…", "authorId": "…", "publishedAt": null, "createdAt": "…" }],
76
+ "error": null
77
+ }
78
+ ```
79
+
80
+ </Step>
81
+
82
+ </Steps>
83
+
84
+ <Callout title="Pathless declare">
85
+ Starters often pass the whole decl module: `store.sql("app", {schema})` where `schema` is `import
86
+ * as schema from "@/db/schema.decl"`. Named keys and the exported table handles are equivalent.
87
+ </Callout>
88
+
89
+ ## Progressive Patterns
90
+
91
+ Explore SQL from a bare table to classified columns, CRUD factory, and RLS:
92
+
93
+ <Tabs items={["Schema", "Classify", "Resource", "RLS"]}>
94
+
95
+ <Tab value="Schema">
96
+
97
+ `field.id()` is `text` + auto id on insert. Use `.okid()` on an existing text
98
+ column when you want the same default without the sugar factory:
99
+
100
+ ```typescript title="src/db/schema.decl.ts"
101
+ import { store, field } from "okengine";
102
+
103
+ export const notes = store.schema.table("notes", {
104
+ id: field.id().primaryKey(),
105
+ title: field.text().notNull(),
106
+ body: field.text().notNull(),
107
+ archivedAt: field.timestamp(),
108
+ createdAt: field.timestamp().notNull().now(),
109
+ });
110
+ ```
111
+
112
+ </Tab>
113
+
114
+ <Tab value="Classify">
115
+
116
+ `.pii()` / `.sensitive()` / `.retain(duration)` tag columns for posture audits
117
+ and AI egress masking:
118
+
119
+ ```typescript
120
+ export const users = store.schema.table("users", {
121
+ id: field.id().primaryKey(),
122
+ email: field.text().unique().pii().notNull(),
123
+ passwordHash: field.text().sensitive().notNull(),
124
+ lastLoginAt: field.timestamp().retain("90d"),
125
+ });
126
+ ```
127
+
128
+ **Consequence:** AI prompts and Console redaction read these tags — they are not
129
+ decorative comments.
130
+
131
+ </Tab>
132
+
133
+ <Tab value="Resource">
134
+
135
+ `store.resource` builds five Flows. Mount with
136
+ [`http.resource`](/docs/elements/flow/http#resources) — the factory registers no routes:
137
+
138
+ ```typescript title="src/flows/notes/resource.ts"
139
+ import { store } from "okengine";
140
+ import { z } from "zod";
141
+ import { db, notes } from "@/schema";
142
+
143
+ export const notesResource = store.resource(db, notes, {
144
+ in: z.object({ title: z.string().min(1), body: z.string() }),
145
+ out: z.object({
146
+ id: z.string(),
147
+ title: z.string(),
148
+ body: z.string(),
149
+ }),
150
+ list: {
151
+ search: [notes.title],
152
+ filter: [notes.archivedAt],
153
+ cursor: [notes.createdAt, notes.id],
154
+ },
155
+ });
156
+ ```
157
+
158
+ ```typescript title="src/flows/notes/index.ts"
159
+ import { on, http } from "okengine";
160
+ import { member } from "@/core/gate";
161
+ import { notesResource } from "./resource";
162
+
163
+ export const notesApi = on(http.resource("/notes", notesResource.all()).gate(member));
164
+ ```
165
+
166
+ </Tab>
167
+
168
+ <Tab value="RLS">
169
+
170
+ Pass extras as the **third argument array**. Helpers take SQL/JS column
171
+ **names** (strings) and stamp `oke.gate()` / `oke.user()` / `oke.has_scope()`
172
+ predicates:
173
+
174
+ ```typescript title="src/db/schema.decl.ts"
175
+ import { store, field } from "okengine";
176
+
177
+ export const tasks = store.schema.table(
178
+ "tasks",
179
+ {
180
+ id: field.id().primaryKey(),
181
+ owner: field.text().notNull(),
182
+ title: field.text().notNull(),
183
+ },
184
+ [
185
+ store.schema.policy.gate("member", { for: "select" }),
186
+ store.schema.policy.owner("owner", { for: "all" }),
187
+ ],
188
+ );
189
+ ```
190
+
191
+ When `gate.auth.tenant` is on, every table needs
192
+ `store.schema.policy.tenant(…)` or `store.schema.unscoped()` — extract fails
193
+ otherwise.
194
+
195
+ </Tab>
196
+
197
+ </Tabs>
198
+
199
+ ## Field Reference
200
+
201
+ Factories mirror Drizzle pg-core names. Chain modifiers after the factory:
202
+
203
+ | Factory | Infers | Notes |
204
+ | -------------------------------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
205
+ | `field.id()` | `string` | `text` + auto id on insert (`≡ field.text().okid()`) |
206
+ | `field.okid()` | `string` | Pins OK ID on a text column |
207
+ | `field.text()` / `varchar` / `char` | `string` | Optional `{ length?, enum? }` |
208
+ | `field.boolean()` | `boolean` | |
209
+ | `field.smallint()` / `integer()` | `number` | |
210
+ | `field.bigint({ mode? })` | `number` (default) | `mode`: `"number"` · `"bigint"` · `"string"` |
211
+ | `field.serial()` / `smallserial()` / `bigserial()` | `number` | NOT NULL by SQL physics |
212
+ | `field.numeric()` / `decimal()` | `string` (default) | Exact decimal; `{ precision?, scale?, mode? }` |
213
+ | `field.real()` / `doublePrecision()` | `number` | Float4 / float8 |
214
+ | `field.json()` / `jsonb()` | generic | Narrow with `field.json<MyShape>()` |
215
+ | `field.uuid()` | `string` | |
216
+ | `field.time()` / `timestamp()` / `date()` / `interval()` | see type | `timestamp` / `date` default to `Date`; `{ mode: "string" }` for ISO. On write, finite epoch-ms numbers (e.g. `fx.clock.now()`) coerce to `Date` for Postgres |
217
+ | `field.point()` / `line()` | tuple | `{ mode: "xy" }` / `"abc"` for objects |
218
+ | `field.bytea()` | `Buffer` | |
219
+ | `field.inet()` / `cidr()` / `macaddr()` / `macaddr8()` | `string` | |
220
+
221
+ | Chain | Meaning |
222
+ | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
223
+ | `.primaryKey()` · `.notNull()` · `.unique()` | Constraints |
224
+ | `.default(v)` · `.defaultFn(fn)` · `.now()` · `.okid()` | Defaults |
225
+ | `.pii()` · `.sensitive()` · `.retain(duration)` | Classification tags |
226
+ | `.searchable({ weight? })` · `.embed({ model?, dims? })` | Hybrid search — see [Search](/docs/elements/store/search) |
227
+ | `.as(sqlName)` · `.describe(text)` · `.type<T>()` | SQL name, docs, TS override |
228
+ | `.references(() => col, actions?)` | FK (`onDelete` / `onUpdate`: `cascade` · `restrict` · `no action` · `set null` · `set default`) |
229
+
230
+ ## Declaring Stores
231
+
232
+ Bind tables once with `store.sql(name, options)`. The name becomes the resource
233
+ suffix (`sql:app`).
234
+
235
+ | Option | Type | Default | Meaning |
236
+ | ------------- | ----------------------- | ---------- | --------------------------------------------------- |
237
+ | `schema` | table map or module | _(omit)_ | Tables for push / migrate / `fx.store` |
238
+ | `classify` | `table → column → tags` | `{}` | Explicit tags; wins over schema-derived on conflict |
239
+ | `description` | `string` | store name | Console / docs label |
240
+
241
+ **Named keys** — pass the tables you care about:
242
+
243
+ ```typescript
244
+ export const db = store.sql("app", { schema: { posts, notes } });
245
+ ```
246
+
247
+ **Module star** — starters re-export everything from the decl file:
248
+
249
+ ```typescript title="src/core.ts"
250
+ import { store } from "okengine";
251
+ import * as schema from "@/db/schema.decl";
252
+
253
+ export const db = store.sql("app", { schema });
254
+ ```
255
+
256
+ **Consequence:** every `fx.store(db)` touch stamps `reads` / `writes` as
257
+ `sql:app` on the Flow — Manifest, Console, and least privilege follow from that.
258
+
259
+ ## Session Handle
260
+
261
+ `fx.store(db)` returns a `SqlStoreHandle`. Every method goes through the same
262
+ masking / RLS / CDC boundary.
263
+
264
+ | Method | Purpose |
265
+ | ------------------------------------------------------------------- | -------------------------------------------------- |
266
+ | `select()` / `select(cols).from(t).where?.().orderBy?.().limit?.()` | Fluent select |
267
+ | `insert(t).values(row)` | Insert (`returning()` optional) |
268
+ | `update(t).set(row).where(cond)` | Update — `where` required |
269
+ | `findById(t, id)` | PK lookup |
270
+ | `delete(t, id)` / `delete(t).where(cond)` | Delete by PK or predicate |
271
+ | `exists(t, idOrWhere)` | Presence check |
272
+ | `upsert(t, matchOn, values, { onExisting? })` | Insert-or-skip; `"update"` opts into overwrite |
273
+ | `increment(t, id, column, by?)` | Atomic numeric bump (`by` default `1`) |
274
+ | `count(t, where?)` | `COUNT(*)` |
275
+ | `page(t, options)` | Offset or keyset page |
276
+ | `search(t, options)` | BM25 ± LSH — [Search](/docs/elements/store/search) |
277
+ | `raw(sql, params?)` | Parameterized SQL (`?` placeholders) |
278
+
279
+ Each verb binds with `await fx.store(db).<method>(…)` inside `do`:
280
+
281
+ <Tabs items={["Select", "Insert", "Update", "Delete", "Page", "Upsert"]}>
282
+
283
+ <Tab value="Select">
284
+
285
+ Fluent select, or `findById` for a PK:
286
+
287
+ ```typescript title="src/flows/notes/[id]/get.ts"
288
+ import { on, flow, http } from "okengine";
289
+ import { z } from "zod";
290
+ import { eq } from "drizzle-orm";
291
+ import { db, notes } from "@/schema";
292
+
293
+ export const get = on(
294
+ http.get(),
295
+ flow({
296
+ in: z.object({ id: z.string() }),
297
+ out: z.object({ id: z.string(), title: z.string() }),
298
+ errors: { NotFound: z.object({ id: z.string() }) },
299
+ do: async ({ id }, fx) => {
300
+ const [note] = await fx.store(db).select().from(notes).where(eq(notes.id, id));
301
+ if (!note) return fx.fail("NotFound", { id });
302
+ return note;
303
+ },
304
+ }),
305
+ );
306
+ ```
307
+
308
+ `findById(notes, id)` is the same PK path without a fluent chain.
309
+
310
+ </Tab>
311
+
312
+ <Tab value="Insert">
313
+
314
+ ```typescript title="src/flows/notes/create.ts"
315
+ import { on, flow, http } from "okengine";
316
+ import { z } from "zod";
317
+ import { db, notes } from "@/schema";
318
+
319
+ export const create = on(
320
+ http.post(),
321
+ flow({
322
+ in: z.object({ title: z.string().min(1) }),
323
+ out: z.object({ id: z.string(), title: z.string() }),
324
+ do: async ({ title }, fx) => {
325
+ const id = fx.id();
326
+ const [row] = await fx.store(db).insert(notes).values({ id, title }).returning();
327
+ return fx.json.create(row);
328
+ },
329
+ }),
330
+ );
331
+ ```
332
+
333
+ </Tab>
334
+
335
+ <Tab value="Update">
336
+
337
+ `where` is required — bare updates throw
338
+ `update().set().where(): condition required`:
339
+
340
+ ```typescript title="src/flows/notes/[id]/update.ts"
341
+ import { on, flow, http } from "okengine";
342
+ import { z } from "zod";
343
+ import { eq } from "drizzle-orm";
344
+ import { db, notes } from "@/schema";
345
+
346
+ export const update = on(
347
+ http.patch(),
348
+ flow({
349
+ in: z.object({
350
+ id: z.string(),
351
+ title: z.string().min(1).optional(),
352
+ }),
353
+ errors: { NotFound: z.object({ id: z.string() }) },
354
+ do: async ({ id, title }, fx) => {
355
+ if (title !== undefined) {
356
+ await fx.store(db).update(notes).set({ title }).where(eq(notes.id, id));
357
+ }
358
+ const row = await fx.store(db).findById(notes, id);
359
+ if (!row) return fx.fail("NotFound", { id });
360
+ return row;
361
+ },
362
+ }),
363
+ );
364
+ ```
365
+
366
+ </Tab>
367
+
368
+ <Tab value="Delete">
369
+
370
+ Two-arg form deletes by PK. Fluent `.where(cond)` also requires a condition
371
+ (`delete().where(): condition required`):
372
+
373
+ ```typescript title="src/flows/notes/[id]/remove.ts"
374
+ import { on, flow, http } from "okengine";
375
+ import { z } from "zod";
376
+ import { db, notes } from "@/schema";
377
+
378
+ export const remove = on(
379
+ http.delete(),
380
+ flow({
381
+ in: z.object({ id: z.string() }),
382
+ do: async ({ id }, fx) => {
383
+ await fx.store(db).delete(notes, id);
384
+ return fx.json.empty();
385
+ },
386
+ }),
387
+ );
388
+ ```
389
+
390
+ </Tab>
391
+
392
+ <Tab value="Page">
393
+
394
+ `page` options: `where` · `orderBy` · `limit` · `offset` · `after` · `before`.
395
+ `after` / `before` cannot combine with each other or with `offset`.
396
+
397
+ ```typescript title="src/flows/notes/list.ts"
398
+ import { on, flow, http } from "okengine";
399
+ import { db, notes } from "@/schema";
400
+
401
+ export const list = on(
402
+ http.get(),
403
+ flow({
404
+ do: async (_, fx) => {
405
+ return await fx.store(db).page(notes, {
406
+ where: { archivedAt: null },
407
+ orderBy: [notes.createdAt],
408
+ limit: 20,
409
+ });
410
+ },
411
+ }),
412
+ );
413
+ ```
414
+
415
+ **Consequence:** keyset pages (`after` / `before`) stay stable under inserts —
416
+ prefer them for live feeds. Offset is fine for admin tables.
417
+
418
+ </Tab>
419
+
420
+ <Tab value="Upsert">
421
+
422
+ Default is insert-once. Pass `{ onExisting: "update" }` to overwrite a match:
423
+
424
+ ```typescript title="src/flows/notes/ensure.ts"
425
+ import { flow } from "okengine";
426
+ import { z } from "zod";
427
+ import { db, notes } from "@/schema";
428
+
429
+ export const ensureWelcome = flow("notes.ensureWelcome", {
430
+ in: z.object({ title: z.string() }),
431
+ do: async ({ title }, fx) => {
432
+ const result = await fx.store(db).upsert(
433
+ notes,
434
+ { id: "welcome" },
435
+ {
436
+ id: "welcome",
437
+ title,
438
+ body: "Your Notes API is ready.",
439
+ archivedAt: null,
440
+ createdAt: new Date("2026-01-15T10:00:00.000Z"),
441
+ },
442
+ // { onExisting: "update" },
443
+ );
444
+ return result; // { status: "upserted" | "changed" | "already-existed" }
445
+ },
446
+ });
447
+ ```
448
+
449
+ Empty `matchOn` throws `upsert() requires at least one matchOn predicate`.
450
+
451
+ </Tab>
452
+
453
+ </Tabs>
454
+
455
+ Also on the handle: `exists` · `increment` · `count` · `raw` · `search`. See
456
+ [Search](/docs/elements/store/search) for hybrid ranking.
457
+
458
+ ## Resources
459
+
460
+ <Callout title="Detailed section">
461
+ Mount details, live SSE, and verb tables live on [HTTP ·
462
+ Resources](/docs/elements/flow/http#resources). This section is the factory contract —
463
+ `store.resource(db, table, options)`.
464
+ </Callout>
465
+
466
+ `store.resource(db, table, options)` builds five Flows. Pass
467
+ `notesResource.all()` (or any bag with `list` · `create` · `get` · `update` ·
468
+ `remove`) to `on(http.resource(path, ops))`.
469
+
470
+ <Tabs items={["Define", "Mount"]}>
471
+
472
+ <Tab value="Define">
473
+
474
+ The factory registers no routes:
475
+
476
+ ```typescript title="src/flows/notes/resource.ts"
477
+ import { store } from "okengine";
478
+ import { z } from "zod";
479
+ import { db, notes } from "@/schema";
480
+
481
+ export const notesResource = store.resource(db, notes, {
482
+ in: z.object({ title: z.string().min(1), body: z.string() }),
483
+ out: z.object({
484
+ id: z.string(),
485
+ title: z.string(),
486
+ body: z.string(),
487
+ }),
488
+ list: {
489
+ search: [notes.title],
490
+ filter: [notes.archivedAt],
491
+ cursor: [notes.createdAt, notes.id],
492
+ },
493
+ });
494
+ ```
495
+
496
+ </Tab>
497
+
498
+ <Tab value="Mount">
499
+
500
+ `.gate(member)` stamps every verb. The client sees `api.notes.list` / `.create` /
501
+ `.get` / `.update` / `.remove` after `oke({ name: "app" }).adopt({ notes })`.
502
+
503
+ ```typescript title="src/flows/notes/index.ts"
504
+ import { on, http } from "okengine";
505
+ import { member } from "@/core/gate";
506
+ import { notesResource } from "./resource";
507
+
508
+ export const notes = on(http.resource("/notes", notesResource.all()).gate(member));
509
+ ```
510
+
511
+ </Tab>
512
+
513
+ </Tabs>
514
+
515
+ The URL id segment is always `:id`. Update is **PATCH**, not PUT.
516
+
517
+ | Op | Method | Path | Typical status |
518
+ | -------- | -------- | ------------- | ---------------------------------- |
519
+ | `list` | `GET` | `/notes` | `200` + `{ data, error, meta }` |
520
+ | `create` | `POST` | `/notes` | `201 Created` (`fx.json.create`) |
521
+ | `get` | `GET` | `/notes/:id` | `200`, or `NotFound` |
522
+ | `update` | `PATCH` | `/notes/:id` | `200`, or `NotFound` |
523
+ | `remove` | `DELETE` | `/notes/:id` | `204 No Content` (`fx.json.empty`) |
524
+ | `live` | `GET` | `/notes/live` | SSE — only when live is on |
525
+
526
+ <Accordions>
527
+
528
+ <Accordion title="Resource Options">
529
+ Third argument to `store.resource(db, table, options)`:
530
+
531
+ | Option | Type | Default | Meaning |
532
+ | ---------- | --------- | -------------------------------- | ------------------------------------------------------------------------ |
533
+ | `in` | Schema | _(required)_ | Create body (`POST`) |
534
+ | `out` | Schema | _(required)_ | Item shape (get / list / update return) |
535
+ | `update` | Schema | `in` | Patch fields. Wire body is `{ id, …patch }` |
536
+ | `idSchema` | Schema | `update`/`in` + `{ id: string }` | Replaces the update Flow `in` when set (include the id key) |
537
+ | `errors` | error map | `{ NotFound }` | Typed failures on get / update / remove |
538
+ | `id` | column | table PK | Column bound to `:id` |
539
+ | `list` | object | see List Options | List query grammar (`GET /notes`) |
540
+ | `breaking` | `boolean` | `false` | Marks the five Flows `breaking: true` (handwritten → resource migration) |
541
+ | `live` | `boolean` | omitted | Live query surface — see Resource Live |
542
+
543
+ </Accordion>
544
+
545
+ <Accordion title="List Options">
546
+ Nested on `store.resource(…, { list: { … } })`. Search / filter / order / select
547
+ use a column scope: `"all"` · column array · `"none"`.
548
+
549
+ | Option | Type | Default | Meaning |
550
+ | ----------- | ------------------------ | ------------------------------------------------ | ----------------------------------- |
551
+ | `mode` | `"cursor"` \| `"offset"` | `"cursor"` when `cursor` is set, else `"offset"` | Pagination |
552
+ | `cursor` | columns | `[]` | Keyset columns |
553
+ | `direction` | `"asc"` \| `"desc"` | `"desc"` | Default sort when no `?order=` |
554
+ | `limit` | `number` | `20` | Default page size |
555
+ | `maxLimit` | `number` | `100` | Cap on `?limit=` |
556
+ | `count` | `"exact"` \| `"none"` | `"exact"` | Offset-only `COUNT(*)` |
557
+ | `search` | column scope | `"none"` | `?search=` / `?q=` substring `LIKE` |
558
+ | `filter` | column scope | `"none"` | `?col=eq.x` grammar |
559
+ | `order` | column scope | cursor columns, else `"all"` | `?order=` |
560
+ | `select` | column scope | `"all"` | `?select=` projection |
561
+
562
+ </Accordion>
563
+
564
+ <Accordion title="List URL grammar">
565
+ Shared by resource lists, `liveQuery`, and handwritten pages that call
566
+ `parseListQuery`:
567
+
568
+ | Param | Example | Meaning |
569
+ | ----------------------------- | ----------------------- | --------------------------------------------------------------------------------------------- |
570
+ | `limit` / `offset` / `cursor` | `?limit=20` | Pagination |
571
+ | `search` / `q` | `?search=refund` | Substring `LIKE` (not BM25) |
572
+ | `order` | `?order=createdAt.desc` | Sort |
573
+ | `select` | `?select=id,title` | Projection |
574
+ | column filter | `?status=eq.active` | `eq` · `neq` · `gt` · `gte` · `lt` · `lte` · `like` · `ilike` · `in` · `is` (+ `not.` prefix) |
575
+ | `or` / `and` | `?or=(…)` | Nested boolean groups |
576
+
577
+ **Consequence:** list `?search=` is substring filter. Hybrid BM25 ranking is
578
+ `fx.store(db).search(table, { query })` — see [Two Surfaces](/docs/elements/store/search#two-surfaces).
579
+
580
+ </Accordion>
581
+
582
+ <Accordion title="Resource Members">
583
+
584
+ | Member | Kind | Meaning |
585
+ | ----------------------------------------------- | ------------------- | ----------------------------------------------------------------------------------------- |
586
+ | `all()` | method, no args | Bag for `http.resource(path, notesResource.all())` — five Flows, plus `live` when enabled |
587
+ | `list` · `create` · `get` · `update` · `remove` | Flow | One verb. Bind with `http.get` / `http.post` / `http.patch` / `http.delete` |
588
+ | `page(input)` | method | Compile list-query input for a handwritten `fx.store(db).page` |
589
+ | `live` | `{ signal, flow }?` | Live surface when `live: true` (or the project default drained on) |
590
+
591
+ </Accordion>
592
+
593
+ <Accordion title="Resource Live">
594
+ A sixth route appears only when the resource is live. It is **not** a signal
595
+ firehose — each subscriber gets classified row events (RLS + list filters).
596
+
597
+ | `live` on the resource | Result |
598
+ | -------------------------------- | ---------------------------------------------------------------------------------------- |
599
+ | `{ live: true }` | Mount `GET <path>/live` now |
600
+ | omitted | Mount only if `oke({ store: { live: true } })` |
601
+ | `{ live: false }` | Never mount live for this resource |
602
+ | table `store.schema.live(false)` | Opts that table out of the project default. `{ live: true }` on the resource still wins. |
603
+
604
+ ```typescript title="src/flows/notes/resource.ts"
605
+ const notesResource = store.resource(db, notes, {
606
+ in: z.object({ title: z.string().min(1) }),
607
+ out: z.object({ id: z.string(), title: z.string() }),
608
+ live: true,
609
+ });
610
+
611
+ export const notes = on(http.resource("/notes", notesResource.all()).gate(member));
612
+ ```
613
+
614
+ **Consequence:** `GET /notes/live` rides the same `.gate(...)` chain as list/get.
615
+
616
+ Wire events (consumed with `useLiveQuery` on the [typed client](/docs/client/react)):
617
+
618
+ | `kind` | Meaning |
619
+ | --------- | ----------------------------------------------------------- |
620
+ | `upsert` | Row visible under stamp + query — merge by primary key |
621
+ | `revoked` | Row left visibility (`reason: "rls"` or `"query"`) — remove |
622
+ | `delete` | Row deleted — remove |
623
+
624
+ Live queries need an RLS-capable SQL driver (`postgres` / `pglite`) and a gated
625
+ identity on the request. Extract fails without a primary key:
626
+
627
+ ```text
628
+ extract: live: true on table "notes" requires a primary key column (upsert/revoked/delete address rows by PK)
629
+ ```
630
+
631
+ Missing `updatedAt` / `updated_at`, or no RLS policies, warn at extract — they
632
+ do not fail the build. Full mount physics: [HTTP · Resource Live](/docs/elements/flow/http#resources).
633
+
634
+ </Accordion>
635
+
636
+ <Accordion title="Subset & Override">
637
+ `http.resource` always mounts all five CRUD keys. Bind individual Flows for a subset; spread
638
+ `.all()` and override one key to replace a verb. Collision errors (**OKE1041**): [HTTP ·
639
+ Resources](/docs/elements/flow/http#resources).
640
+ </Accordion>
641
+
642
+ </Accordions>
643
+
644
+ ## Schema Extras & RLS
645
+
646
+ <Callout title="Detailed section">
647
+ If you only need a gate + owner policy, jump to Progressive Patterns → RLS. Extras are the **third
648
+ argument array** on `store.schema.table(name, cols, extras)`.
649
+ </Callout>
650
+
651
+ Pass extras after the column map:
652
+
653
+ | Extra | Meaning |
654
+ | ---------------------------------- | ---------------------------------------------------- |
655
+ | `store.schema.rls()` | Enable RLS on the table (no policies yet) |
656
+ | `store.schema.policy(name, opts?)` | Named policy (`for`, `as`, `using`, `withCheck`) |
657
+ | `store.schema.policy.gate(name)` | Default `for: "select"` — `oke.gate() = '…'` |
658
+ | `store.schema.policy.owner(col)` | Default `for: "all"` — `col = oke.user()` |
659
+ | `store.schema.policy.scope(scope)` | Default `for: "insert"` — `oke.has_scope('…')` |
660
+ | `store.schema.policy.tenant(col)` | Tenant predicate when tenancy is on |
661
+ | `store.schema.unscoped()` | Opt out of tenant requirement |
662
+ | `store.schema.live(false)` | Opt this table out of project live default |
663
+ | `store.schema.relations(…)` | Relation metadata for Drizzle emit — not a query API |
664
+
665
+ <Accordions>
666
+
667
+ <Accordion title="Policy helpers">
668
+ Helpers take **string** column / gate / scope names and stamp predicates:
669
+
670
+ ```typescript title="src/db/schema.decl.ts"
671
+ export const bookings = store.schema.table(
672
+ "bookings",
673
+ {
674
+ id: field.id().primaryKey(),
675
+ owner: field.text().notNull(),
676
+ tenantId: field.text().notNull(),
677
+ },
678
+ [
679
+ store.schema.policy.gate("member", { for: "select" }),
680
+ store.schema.policy.owner("owner", { for: "all" }),
681
+ store.schema.policy.scope("booking:create", { for: "insert" }),
682
+ store.schema.policy.tenant("tenantId"),
683
+ ],
684
+ );
685
+ ```
686
+
687
+ `for` accepts SQL commands (`select` · `insert` · `update` · `delete` · `all`).
688
+ Raw `store.schema.policy(name, { using, withCheck })` is available when helpers
689
+ are not enough.
690
+
691
+ </Accordion>
692
+
693
+ <Accordion title="Tenant requirement">
694
+ When `gate.auth.tenant` is on, every table needs a tenant policy or an
695
+ explicit opt-out. Extract fails otherwise:
696
+
697
+ ```text
698
+ extract: table "{store}.{table}" needs store.schema.policy.tenant(...) or store.schema.unscoped() when gate.auth.tenant is on
699
+ ```
700
+
701
+ ```typescript
702
+ export const shared = store.schema.table(
703
+ "shared_flags",
704
+ { id: field.id().primaryKey(), key: field.text().notNull() },
705
+ [store.schema.unscoped()],
706
+ );
707
+ ```
708
+
709
+ </Accordion>
710
+
711
+ <Accordion title="Foreign keys & relations">
712
+ Column FKs use `.references(() => other.col, { onDelete?, onUpdate? })`.
713
+ `store.schema.relations` records one/many links for Drizzle emit — it does
714
+ **not** add `fx.store(db).query.…`:
715
+
716
+ ```typescript title="src/db/schema.decl.ts"
717
+ export const links = store.schema.table("links", {
718
+ id: field.id().primaryKey(),
719
+ code: field.text().notNull().unique(),
720
+ });
721
+
722
+ export const daily = store.schema.table("daily", {
723
+ id: field.id().primaryKey(),
724
+ code: field
725
+ .text()
726
+ .notNull()
727
+ .references(() => links.code, { onDelete: "cascade" }),
728
+ day: field.text().notNull(),
729
+ });
730
+
731
+ export const relations = store.schema.relations({ links, daily }, (r) => ({
732
+ links: {
733
+ daily: r.many.daily({ from: r.links.code, to: r.daily.code }),
734
+ },
735
+ daily: {
736
+ link: r.one.links({
737
+ from: r.daily.code,
738
+ to: r.links.code,
739
+ optional: false,
740
+ }),
741
+ },
742
+ }));
743
+ ```
744
+
745
+ Join in Flows with fluent `select` / `where`, or mount related tables as their
746
+ own resources.
747
+
748
+ </Accordion>
749
+
750
+ </Accordions>
751
+
752
+ ## Seeding
753
+
754
+ <StoreSeeding />
755
+
756
+ Seed never runs at boot without confirmation — only via `oke db seed`, or the
757
+ first-boot confirm when a seed module exists and `.oke/state.json` has not
758
+ recorded that identity yet. Categories:
759
+
760
+ | Block | Runs when |
761
+ | -------------- | ----------------------- |
762
+ | `essential` | Every env |
763
+ | `dev` | `dev` ConfigEnv only |
764
+ | `prod` | `prod` only |
765
+ | _(none extra)_ | `test` — essential only |
766
+
767
+ ```typescript title="src/db/seed/index.ts"
768
+ import { defineSeed, type Fx } from "okengine";
769
+ import { db } from "@/core";
770
+ import { notes } from "@/db/schema.decl";
771
+
772
+ async function welcomeNote(fx: Fx) {
773
+ await fx.store(db).upsert(
774
+ notes,
775
+ { id: "welcome" },
776
+ {
777
+ id: "welcome",
778
+ title: "Welcome",
779
+ body: "Your Notes API is ready.",
780
+ archivedAt: null,
781
+ createdAt: new Date("2026-01-15T10:00:00.000Z"),
782
+ },
783
+ );
784
+ }
785
+
786
+ export default defineSeed({
787
+ name: "notes",
788
+ description: "Welcome + sample notes",
789
+ essential: welcomeNote,
790
+ // dev: sampleNotes,
791
+ // prod: async (fx) => { /* prod-only rows */ },
792
+ });
793
+ ```
794
+
795
+ `upsert` returns `{ status: "upserted" | "changed" | "already-existed" }`.
796
+ Default is insert-once; pass `{ onExisting: "update" }` to overwrite.
797
+
798
+ ## CDC
799
+
800
+ <Callout title="Detailed section">
801
+ Full consumer patterns live on [Consumers · CDC](/docs/elements/flow/consumers#cdc). Here is the
802
+ SQL-side trigger: `db.table(handle).changed(column?)`.
803
+ </Callout>
804
+
805
+ `db.table(orders).changed(column?)` builds a CDC trigger for `on(…)`. Input is
806
+ always `{ before, after }`. `changed("status")` stamps a **column** name on the
807
+ Manifest — it is not an op filter.
808
+
809
+ ```typescript title="src/flows/orders/on-status.ts"
810
+ import { on, flow } from "okengine";
811
+ import { db, orders } from "@/schema";
812
+
813
+ export const onStatus = on(
814
+ db.table(orders).changed("status"),
815
+ flow("orders.statusChanged", {
816
+ do: async ({ before, after }, fx) => {
817
+ if (before?.status === after?.status) return;
818
+ /* committed write */
819
+ },
820
+ }),
821
+ );
822
+ ```
823
+
824
+ **Consequence:** writes must go through `fx.store`. A raw SQL client bypasses
825
+ the CDC sink, so no consumer runs.
826
+
827
+ ## Drivers
828
+
829
+ | Driver | Runs as | Best for |
830
+ | ---------- | ------------------------- | ------------------------------ |
831
+ | `postgres` | Docker / managed Postgres | Dev + prod default; RLS + live |
832
+ | `pglite` | In-process WASM | Test default; RLS-capable |
833
+ | `memory` | Process map | Tiny ephemeral tests |
834
+
835
+ Standard starters inherit `DRIVER_DEFAULTS` (`postgres` / `pglite` /
836
+ `postgres`). Pin only when you diverge; vendor choice lives under `images`, not
837
+ driver ids:
838
+
839
+ ```typescript title="oke.config.ts"
840
+ import { defineConfig } from "okengine/config";
841
+
842
+ export default defineConfig({
843
+ drivers: {
844
+ // omit store.sql to use defaults — pin only overrides
845
+ },
846
+ images: {
847
+ store: {
848
+ sql: "postgres:18-alpine",
849
+ },
850
+ },
851
+ });
852
+ ```
853
+
854
+ ## Troubleshooting
855
+
856
+ <Accordions>
857
+
858
+ <Accordion title="OKE1110 — domain table not found">
859
+ Cause: `domain table not found — migrations have not been applied.` Run `oke db push` locally or
860
+ `oke db migrate` in that environment.
861
+ </Accordion>
862
+
863
+ <Accordion title="update().set().where(): condition required">
864
+ Updates and deletes without a `where` are rejected (`delete().where(): condition required`). Pass
865
+ a Drizzle condition or equality map — or use `delete(table, id)` for PK deletes.
866
+ </Accordion>
867
+
868
+ <Accordion title="page(): after and before / offset and keyset cannot combine">
869
+ Keyset (`after` / `before`) and `offset` are mutually exclusive. Pick one pagination mode per
870
+ call.
871
+ </Accordion>
872
+
873
+ <Accordion title="upsert() requires at least one matchOn predicate">
874
+ Pass a non-empty equality map or Drizzle condition that identifies the row.
875
+ </Accordion>
876
+
877
+ <Accordion title="increment(): no row with …">
878
+ The PK was missing. Insert first, or guard with `exists` / `findById`.
879
+ </Accordion>
880
+
881
+ <Accordion title="extract: table needs tenant or unscoped">
882
+ Cause: `extract: table "{store}.{table}" needs store.schema.policy.tenant(...) or
883
+ store.schema.unscoped() when gate.auth.tenant is on`. Add a tenant policy or mark the table
884
+ unscoped.
885
+ </Accordion>
886
+
887
+ <Accordion title="live: true requires a primary key / RLS driver">
888
+ Extract: `live: true on table "…" requires a primary key column`. Runtime needs `postgres` /
889
+ `pglite` and a gated identity on the request.
890
+ </Accordion>
891
+
892
+ <Accordion title="unknown list param / search is not enabled">
893
+ Default `filter: "none"` / `search: "none"` rejects extra keys. Whitelist
894
+ columns on `store.resource(…, { list: { … } })` or your `liveQuery` options.
895
+ </Accordion>
896
+
897
+ <Accordion title="fx.store(db).query is undefined">
898
+ There is no Relational Query Builder on the session handle. Use fluent `select` / `insert` /
899
+ `update` / `page`, or `store.resource` for CRUD. `store.schema.relations` is emit metadata only.
900
+ </Accordion>
901
+
902
+ <Accordion title="OKE1041 — method + path bound twice">
903
+ A resource mount plus a handwritten `http.get("/notes")` (or two mounts on the same base path)
904
+ collide. Drop one binding — see [HTTP ·
905
+ Troubleshooting](/docs/elements/flow/http#troubleshooting).
906
+ </Accordion>
907
+
908
+ </Accordions>
909
+
910
+ ## Learn more
911
+
912
+ - [Store](/docs/elements/store) — four facets; driver defaults
913
+ - [Search](/docs/elements/store/search) — `.searchable()` / `.embed()` / `fx.store(db).search`
914
+ - [HTTP · Resources](/docs/elements/flow/http#resources) — mount, live SSE, verb table
915
+ - [Consumers · CDC](/docs/elements/flow/consumers#cdc) — `db.table(…).changed()`
916
+ - [fx](/docs/reference/fx) — `fx.store` session
917
+ - [Errors](/docs/reference/errors) — OKE1110 · OKE1041
918
+
919
+ ## Next
920
+
921
+ <Cards>
922
+ <Card
923
+ title="KV"
924
+ description="Namespaced get/set with duration TTL."
925
+ href="/docs/elements/store/kv"
926
+ />
927
+ <Card
928
+ title="Search"
929
+ description="BM25 ± LSH on SQL columns."
930
+ href="/docs/elements/store/search"
931
+ />
932
+ <Card
933
+ title="HTTP · Resources"
934
+ description="Mount store.resource as five CRUD verbs + live."
935
+ href="/docs/elements/flow/http#resources"
936
+ />
937
+ </Cards>