okengine 0.18.5 → 0.19.0

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