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
@@ -13,6 +13,7 @@
13
13
  */
14
14
 
15
15
  import { flow, type AnyFlowDef, type FlowDef, type FlowErrorMap } from "../../kernel/flow.ts";
16
+ import { applyBoundaryContract, stampBoundaryContract } from "../../kernel/boundary-contract.ts";
16
17
  import { fail } from "../../kernel/errors.ts";
17
18
  import type { Fx } from "../../kernel/fx.ts";
18
19
  import {
@@ -260,11 +261,9 @@ export function resource(db: SqlStoreDecl, table: unknown, options: ResourceOpti
260
261
  }
261
262
 
262
263
  const listFlow = flow("list", {
263
- ...(breaking ? { breaking: true as const } : {}),
264
264
  // Loose record so the HTTP AoT infers `query` and lets every list URL
265
265
  // key through; real validation happens in parseListQuery (PostgREST
266
266
  // grammar).
267
- in: z.record(z.string(), z.unknown()) as never,
268
267
  effects: { reads: [db.ref] },
269
268
  do: async (input, fx) => {
270
269
  const result = await runList(input, fx);
@@ -272,10 +271,15 @@ export function resource(db: SqlStoreDecl, table: unknown, options: ResourceOpti
272
271
  return fx.json.with(result);
273
272
  },
274
273
  });
274
+ applyBoundaryContract(
275
+ listFlow,
276
+ stampBoundaryContract({
277
+ in: z.record(z.string(), z.unknown()) as never,
278
+ ...(breaking ? { breaking: true } : {}),
279
+ }),
280
+ );
275
281
 
276
282
  const createFlow = flow("create", {
277
- ...(breaking ? { breaking: true as const } : {}),
278
- in: options.in as never,
279
283
  effects: { writes: [db.ref] },
280
284
  do: async (input, fx) => {
281
285
  const store = fx.store(db) as {
@@ -288,10 +292,16 @@ export function resource(db: SqlStoreDecl, table: unknown, options: ResourceOpti
288
292
  return fx.json.create(row);
289
293
  },
290
294
  });
295
+ applyBoundaryContract(
296
+ createFlow,
297
+ stampBoundaryContract({
298
+ in: options.in as never,
299
+ out: options.out as never,
300
+ ...(breaking ? { breaking: true } : {}),
301
+ }),
302
+ );
291
303
 
292
304
  const getFlow = flow("get", {
293
- ...(breaking ? { breaking: true as const } : {}),
294
- errors,
295
305
  effects: { reads: [db.ref] },
296
306
  do: async (input, fx) => {
297
307
  const id = (input as Record<string, unknown>)[idKey];
@@ -303,6 +313,14 @@ export function resource(db: SqlStoreDecl, table: unknown, options: ResourceOpti
303
313
  return row;
304
314
  },
305
315
  });
316
+ applyBoundaryContract(
317
+ getFlow,
318
+ stampBoundaryContract({
319
+ out: options.out as never,
320
+ errors,
321
+ ...(breaking ? { breaking: true } : {}),
322
+ }),
323
+ );
306
324
 
307
325
  // Wire update body is `{ id, ...patch }`. The patch schema (`update`,
308
326
  // default `in`) describes the mutable fields; the path id rides along and
@@ -315,9 +333,6 @@ export function resource(db: SqlStoreDecl, table: unknown, options: ResourceOpti
315
333
  ? patchSchema.extend({ [idKey]: z.string() })
316
334
  : patchSchema;
317
335
  const updateFlow = flow("update", {
318
- ...(breaking ? { breaking: true as const } : {}),
319
- in: updateIn as never,
320
- errors,
321
336
  effects: { reads: [db.ref], writes: [db.ref] },
322
337
  do: async (input, fx) => {
323
338
  const { [idKey]: id, ...patch } = input as Record<string, unknown>;
@@ -338,10 +353,17 @@ export function resource(db: SqlStoreDecl, table: unknown, options: ResourceOpti
338
353
  return row;
339
354
  },
340
355
  });
356
+ applyBoundaryContract(
357
+ updateFlow,
358
+ stampBoundaryContract({
359
+ in: updateIn as never,
360
+ out: options.out as never,
361
+ errors,
362
+ ...(breaking ? { breaking: true } : {}),
363
+ }),
364
+ );
341
365
 
342
366
  const removeFlow = flow("remove", {
343
- ...(breaking ? { breaking: true as const } : {}),
344
- errors,
345
367
  effects: { writes: [db.ref] },
346
368
  do: async (input, fx) => {
347
369
  const id = (input as Record<string, unknown>)[idKey];
@@ -353,6 +375,13 @@ export function resource(db: SqlStoreDecl, table: unknown, options: ResourceOpti
353
375
  return fx.json.empty();
354
376
  },
355
377
  });
378
+ applyBoundaryContract(
379
+ removeFlow,
380
+ stampBoundaryContract({
381
+ errors,
382
+ ...(breaking ? { breaking: true } : {}),
383
+ }),
384
+ );
356
385
 
357
386
  const defs: ResourceFlowDefs = {
358
387
  list: listFlow as FlowDef<any, any, any>,
@@ -389,7 +418,6 @@ export function resource(db: SqlStoreDecl, table: unknown, options: ResourceOpti
389
418
  if (kind !== undefined) columnKinds[c.key] = kind;
390
419
  }
391
420
  const liveFlow = flow(`_live_${tableName}`, {
392
- ...(options.out !== undefined ? { out: options.out } : {}),
393
421
  effects: { reads: [db.ref, `signal:${signalName}`] },
394
422
  do: async (input: unknown, fx: Fx) => {
395
423
  const bridge = realtimeBridgeRuntime();
@@ -428,6 +456,9 @@ export function resource(db: SqlStoreDecl, table: unknown, options: ResourceOpti
428
456
  return fx.json.stream(stream.chunks);
429
457
  },
430
458
  }) as unknown as AnyFlowDef;
459
+ if (options.out !== undefined) {
460
+ applyBoundaryContract(liveFlow, stampBoundaryContract({ out: options.out as never }));
461
+ }
431
462
  return { signal: signalName, flow: liveFlow };
432
463
  };
433
464
 
@@ -167,6 +167,8 @@ export interface SchemaColumnDecl<
167
167
  readonly typeOptions?: FieldTypeOptions;
168
168
  /** Optional human description for Console / docs (falls back to the JS key). */
169
169
  readonly description?: string;
170
+ /** Hybrid search (BM25 ± LSH) when `.searchable()` / `.embed()` was chained. */
171
+ readonly search?: ColumnSearchDecl;
170
172
  /**
171
173
  * Drizzle `SQLWrapper` structural match — never invoked at runtime.
172
174
  *
@@ -215,6 +217,45 @@ export interface SchemaLiveDecl {
215
217
  readonly live: false;
216
218
  }
217
219
 
220
+ /**
221
+ * Options for {@link FieldBuilder.searchable}.
222
+ *
223
+ * `weight` is a BM25F field weight applied to term frequency **before**
224
+ * saturation / IDF — pure SQL math, zero AI cost. Default `1`.
225
+ */
226
+ export interface SearchableOptions {
227
+ readonly weight?: number;
228
+ }
229
+
230
+ /**
231
+ * Options for {@link FieldBuilder.embed} — separate from {@link SearchableOptions}
232
+ * because embedding is an async, costed AI pipeline.
233
+ *
234
+ * `model` / `dims` may be omitted when the project supplies
235
+ * `oke({ store: { search: { embed: { model, dims } } } })`. Per-field values
236
+ * override the project default.
237
+ */
238
+ export interface EmbedFieldOptions {
239
+ /** Embedding model handle or name (inherits project default when omitted). */
240
+ readonly model?: { readonly name: string } | string;
241
+ /**
242
+ * Vector dimensionality (inherits project default when omitted).
243
+ * Mismatch with a computed embedding fails loud at write time.
244
+ */
245
+ readonly dims?: number;
246
+ }
247
+
248
+ /** Column-level hybrid search declaration stamped on {@link SchemaColumnDecl}. */
249
+ export interface ColumnSearchDecl {
250
+ readonly searchable: true;
251
+ readonly weight: number;
252
+ readonly embed?: {
253
+ readonly model?: string;
254
+ /** Present after extract resolution; may be deferred to project default at declare time. */
255
+ readonly dims?: number;
256
+ };
257
+ }
258
+
218
259
  /** Third-arg extra for {@link schemaTable}. */
219
260
  export type SchemaTableExtra =
220
261
  | SchemaPolicyDecl
@@ -299,6 +340,23 @@ export interface FieldBuilder<TData = unknown, TNotNull extends boolean = false>
299
340
  pii(): FieldBuilder<TData, TNotNull>;
300
341
  sensitive(): FieldBuilder<TData, TNotNull>;
301
342
  retain(duration: string): FieldBuilder<TData, TNotNull>;
343
+ /**
344
+ * Participate in built-in BM25 full-text ranking on the primary SQL table.
345
+ * Zero AI dependency. Optional {@link SearchableOptions.weight} is BM25F
346
+ * field weight (default `1`).
347
+ *
348
+ * Chain `.embed()` separately when semantic LSH is also required.
349
+ */
350
+ searchable(options?: SearchableOptions): FieldBuilder<TData, TNotNull>;
351
+ /**
352
+ * Opt into async embedding + LSH for this searchable field.
353
+ * Requires a prior `.searchable()` and a configured `ai` element.
354
+ * Never a boolean flag inside searchable options — cost/mechanism differ.
355
+ *
356
+ * Omit `model` / `dims` to inherit `oke({ store: { search: { embed } } })`.
357
+ * Pass either to override the project default for this field only.
358
+ */
359
+ embed(options?: EmbedFieldOptions): FieldBuilder<TData, TNotNull>;
302
360
  /** Override snake_case SQL name. */
303
361
  as(sqlName: string): FieldBuilder<TData, TNotNull>;
304
362
  /** Optional human description for Console / docs (falls back to the JS key). */
@@ -342,8 +400,11 @@ interface FieldState {
342
400
  readonly description?: string;
343
401
  readonly references?: ColumnReference;
344
402
  readonly typeOptions?: FieldTypeOptions;
403
+ readonly search?: ColumnSearchDecl;
345
404
  }
346
405
 
406
+ const TEXT_SEARCHABLE_TYPES = new Set<FieldSqlType>(["text", "varchar", "char"]);
407
+
347
408
  function camelToSnake(key: string): string {
348
409
  return key
349
410
  .replace(/([a-z0-9])([A-Z])/g, "$1_$2")
@@ -428,6 +489,61 @@ function createBuilder<TData, TNotNull extends boolean>(
428
489
  next<TNotNull>({
429
490
  classification: mergeClassification(state.classification, { retain: duration }),
430
491
  }),
492
+ searchable: (options) => {
493
+ if (!TEXT_SEARCHABLE_TYPES.has(state.sqlType)) {
494
+ throw new Error(
495
+ `field.${state.sqlType}().searchable() is only valid on text / varchar / char columns`,
496
+ );
497
+ }
498
+ const weight = options?.weight ?? 1;
499
+ if (!(typeof weight === "number" && Number.isFinite(weight) && weight > 0)) {
500
+ throw new Error(
501
+ `searchable({ weight }) must be a finite number > 0 (got ${String(weight)})`,
502
+ );
503
+ }
504
+ return next<TNotNull>({
505
+ search: {
506
+ searchable: true,
507
+ weight,
508
+ ...(state.search?.embed ? { embed: state.search.embed } : {}),
509
+ },
510
+ });
511
+ },
512
+ embed: (options) => {
513
+ if (!state.search?.searchable) {
514
+ throw new Error(
515
+ `.embed() requires a prior .searchable() on the same field — weight is free SQL math; embed is an async AI pipeline`,
516
+ );
517
+ }
518
+ if (!TEXT_SEARCHABLE_TYPES.has(state.sqlType)) {
519
+ throw new Error(
520
+ `field.${state.sqlType}().embed() is only valid on text / varchar / char columns`,
521
+ );
522
+ }
523
+ const dims = options?.dims;
524
+ if (dims !== undefined && !(typeof dims === "number" && Number.isInteger(dims) && dims > 0)) {
525
+ throw new Error(
526
+ `.embed({ dims }) requires a positive integer when set (got ${String(dims)})`,
527
+ );
528
+ }
529
+ const modelRaw = options?.model;
530
+ const model =
531
+ typeof modelRaw === "string"
532
+ ? modelRaw
533
+ : modelRaw && typeof modelRaw === "object" && "name" in modelRaw
534
+ ? String(modelRaw.name)
535
+ : undefined;
536
+ return next<TNotNull>({
537
+ search: {
538
+ searchable: true,
539
+ weight: state.search.weight,
540
+ embed: {
541
+ ...(dims !== undefined ? { dims } : {}),
542
+ ...(model !== undefined ? { model } : {}),
543
+ },
544
+ },
545
+ });
546
+ },
431
547
  as: (sqlName) => next<TNotNull>({ sqlName }),
432
548
  describe: (description) => next<TNotNull>({ description }),
433
549
  type: () => createBuilder<unknown, TNotNull>(state) as FieldBuilder<never, TNotNull> as never,
@@ -450,6 +566,7 @@ function createBuilder<TData, TNotNull extends boolean>(
450
566
  ...(state.defaultFnKind ? { defaultFnKind: state.defaultFnKind } : {}),
451
567
  ...(state.classification ? { classification: state.classification } : {}),
452
568
  ...(state.description !== undefined ? { description: state.description } : {}),
569
+ ...(state.search ? { search: state.search } : {}),
453
570
  ...(state.references ? { references: state.references } : {}),
454
571
  ...(state.typeOptions !== undefined && Object.keys(state.typeOptions).length > 0
455
572
  ? { typeOptions: state.typeOptions }
@@ -0,0 +1,205 @@
1
+ /**
2
+ * `oke db search-backfill` — explicit, resumable corpus + embedding backfill.
3
+ * Never runs as a side effect of `oke db push`.
4
+ */
5
+
6
+ import type { SqlConnection } from "../../drivers/types.ts";
7
+ import type { DeclaredColumn, Manifest } from "../../manifest/types.ts";
8
+ import { SEARCH_LOW_CORPUS_WARN_N } from "./search-errors.ts";
9
+ import {
10
+ ensureHyperplaneInserts,
11
+ OKE_SEARCH_DF,
12
+ OKE_SEARCH_STATS,
13
+ OKE_TSV_COL,
14
+ searchDdlForTable,
15
+ } from "./search-ddl.ts";
16
+ import { tokenize } from "./search-bm25.ts";
17
+ import { embColumn, lshColumn, OKE_SEARCH_PLANES } from "./search-ddl.ts";
18
+ import { deserializePlanes, lshBucket, lshBucketToSql } from "./search-lsh.ts";
19
+ import { LSH_DEFAULT_K, SearchConfigError } from "./search-errors.ts";
20
+
21
+ export interface SearchBackfillOptions {
22
+ readonly table: string;
23
+ readonly batchSize?: number;
24
+ /** Sleep between embed batches (ms) — rate-limit pacing. */
25
+ readonly embedPauseMs?: number;
26
+ readonly embed?: (model: string, text: string, dims: number) => Promise<readonly number[]>;
27
+ /**
28
+ * Optional abort — mid-backfill kill. Re-running the same command is safe
29
+ * (idempotent UPDATEs + DF/stats rebuild) and completes the remaining work.
30
+ */
31
+ readonly signal?: AbortSignal;
32
+ }
33
+
34
+ export interface SearchBackfillResult {
35
+ readonly table: string;
36
+ readonly rows: number;
37
+ readonly embedded: number;
38
+ readonly warnedLowCorpus: boolean;
39
+ }
40
+
41
+ /**
42
+ * Apply search DDL + rebuild stats/DF + optional embeddings for one table.
43
+ * Cursor-paginated; caller may wrap steps in Journal for resumability.
44
+ *
45
+ * @param conn - SQL connection
46
+ * @param manifest - Project manifest
47
+ * @param options - Table + batching
48
+ */
49
+ export async function runSearchBackfill(
50
+ conn: SqlConnection,
51
+ manifest: Manifest,
52
+ options: SearchBackfillOptions,
53
+ ): Promise<SearchBackfillResult> {
54
+ const tableMeta = findTable(manifest, options.table);
55
+ if (!tableMeta) {
56
+ throw new Error(`search-backfill: table "${options.table}" not found in Manifest`);
57
+ }
58
+ const { columns, pk } = tableMeta;
59
+ const batchSize = options.batchSize ?? 32;
60
+ const pauseMs = options.embedPauseMs ?? 100;
61
+
62
+ for (const stmt of searchDdlForTable(options.table, columns)) {
63
+ await conn.exec(stmt);
64
+ }
65
+ for (const ins of ensureHyperplaneInserts(options.table, columns)) {
66
+ await conn.exec(ins.sql, ins.params);
67
+ }
68
+
69
+ // Rebuild DF + stats from full scan (pg_textsearch lesson: after data load).
70
+ await conn.exec(`DELETE FROM ${OKE_SEARCH_DF} WHERE table_name = ?`, [options.table]);
71
+ const df = new Map<string, number>();
72
+ let n = 0;
73
+ let totalLen = 0;
74
+ let cursor: string | null = null;
75
+ let embedded = 0;
76
+
77
+ const embedCols = Object.entries(columns)
78
+ .filter(([, c]) => c.embed)
79
+ .map(([key, c]) => ({
80
+ sqlName: c.sqlName ?? key,
81
+ dims: c.embed!.dims,
82
+ model: c.embed!.model ?? "default",
83
+ }));
84
+
85
+ for (;;) {
86
+ if (options.signal?.aborted) {
87
+ throw new DOMException("search-backfill aborted", "AbortError");
88
+ }
89
+ const params: unknown[] = [];
90
+ let sql = `SELECT * FROM ${options.table}`;
91
+ if (cursor !== null) {
92
+ sql += ` WHERE ${pk} > ?`;
93
+ params.push(cursor);
94
+ }
95
+ sql += ` ORDER BY ${pk} ASC LIMIT ${batchSize}`;
96
+ const rows = await conn.query(sql, params);
97
+ if (rows.length === 0) break;
98
+
99
+ for (const row of rows) {
100
+ if (options.signal?.aborted) {
101
+ throw new DOMException("search-backfill aborted", "AbortError");
102
+ }
103
+ n += 1;
104
+ const seenTerms = new Set<string>();
105
+ let docLen = 0;
106
+ for (const [key, col] of Object.entries(columns)) {
107
+ if (!col.searchable) continue;
108
+ const sqlName = col.sqlName ?? key;
109
+ const tokens = tokenize(String(row[sqlName] ?? ""));
110
+ docLen += tokens.length;
111
+ for (const t of new Set(tokens)) {
112
+ if (!seenTerms.has(t)) {
113
+ seenTerms.add(t);
114
+ df.set(t, (df.get(t) ?? 0) + 1);
115
+ }
116
+ }
117
+ }
118
+ totalLen += docLen;
119
+
120
+ if (embedCols.length > 0 && options.embed) {
121
+ const id = String(row[pk] ?? "");
122
+ for (const col of embedCols) {
123
+ const text = String(row[col.sqlName] ?? "");
124
+ const vector = await options.embed(col.model, text, col.dims);
125
+ if (vector.length !== col.dims) {
126
+ throw new SearchConfigError(
127
+ options.table,
128
+ col.sqlName,
129
+ `embedding length ${vector.length} !== declared dims ${col.dims}`,
130
+ );
131
+ }
132
+ const planesRows = await conn.query(
133
+ `SELECT k, planes FROM ${OKE_SEARCH_PLANES} WHERE table_name = ? AND column_name = ?`,
134
+ [options.table, col.sqlName],
135
+ );
136
+ const prow = planesRows[0];
137
+ if (!prow) {
138
+ throw new SearchConfigError(options.table, col.sqlName, "missing hyperplanes");
139
+ }
140
+ const k = Number(prow["k"] ?? LSH_DEFAULT_K);
141
+ const planes = deserializePlanes(Buffer.from(prow["planes"] as Buffer), k);
142
+ const bucket = lshBucket(vector, planes);
143
+ await conn.exec(
144
+ `UPDATE ${options.table} SET ${embColumn(col.sqlName)} = ?::real[], ${lshColumn(col.sqlName)} = ? WHERE ${pk} = ?`,
145
+ [`{${Array.from(vector).join(",")}}`, lshBucketToSql(bucket), id],
146
+ );
147
+ embedded += 1;
148
+ }
149
+ }
150
+ cursor = String(row[pk] ?? cursor);
151
+ }
152
+
153
+ if (embedCols.length > 0 && options.embed && pauseMs > 0) {
154
+ await new Promise((r) => setTimeout(r, pauseMs));
155
+ }
156
+ if (rows.length < batchSize) break;
157
+ }
158
+
159
+ const avgdl = n > 0 ? totalLen / n : 0;
160
+ await conn.exec(
161
+ `INSERT INTO ${OKE_SEARCH_STATS} (table_name, n, avgdl, updated_at)
162
+ VALUES (?, ?, ?, now())
163
+ ON CONFLICT (table_name) DO UPDATE SET n = EXCLUDED.n, avgdl = EXCLUDED.avgdl, updated_at = now()`,
164
+ [options.table, n, avgdl],
165
+ );
166
+ for (const [term, count] of df) {
167
+ await conn.exec(
168
+ `INSERT INTO ${OKE_SEARCH_DF} (table_name, term, df) VALUES (?, ?, ?)
169
+ ON CONFLICT (table_name, term) DO UPDATE SET df = EXCLUDED.df`,
170
+ [options.table, term, count],
171
+ );
172
+ }
173
+
174
+ const warnedLowCorpus = n < SEARCH_LOW_CORPUS_WARN_N;
175
+ if (warnedLowCorpus) {
176
+ console.warn(
177
+ `[oke db search-backfill] warn: table "${options.table}" has only ${n} rows — IDF/BM25 corpus statistics are not meaningful yet (threshold ${SEARCH_LOW_CORPUS_WARN_N})`,
178
+ );
179
+ }
180
+
181
+ // Touch generated tsv column by no-op update when needed — GENERATED ALWAYS is maintained by PG.
182
+ void OKE_TSV_COL;
183
+
184
+ return { table: options.table, rows: n, embedded, warnedLowCorpus };
185
+ }
186
+
187
+ function findTable(
188
+ manifest: Manifest,
189
+ tableName: string,
190
+ ): { columns: Record<string, DeclaredColumn>; pk: string } | undefined {
191
+ for (const store of Object.values(manifest.stores ?? {})) {
192
+ if (store.facet !== "sql" || !store.tables?.[tableName]) continue;
193
+ const table = store.tables[tableName]!;
194
+ const columns: Record<string, DeclaredColumn> = {};
195
+ let pk = "id";
196
+ for (const [key, col] of Object.entries(table.columns ?? {})) {
197
+ if (!col || typeof col !== "object") continue;
198
+ const c = col as DeclaredColumn;
199
+ columns[key] = c;
200
+ if (c.primaryKey) pk = c.sqlName ?? key;
201
+ }
202
+ return { columns, pk };
203
+ }
204
+ return undefined;
205
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Boot helper — register system-owned durable embed CDC consumers for every
3
+ * `.embed()` column in the Manifest. Writer flows stay free of `effects.embeds`.
4
+ */
5
+
6
+ import type { Manifest } from "../../manifest/types.ts";
7
+ import type { Binding } from "../../kernel/on.ts";
8
+ import { flow } from "../../kernel/flow.ts";
9
+ import {
10
+ applySearchEmbedCdc,
11
+ searchEmbedFlowName,
12
+ tablesNeedingSearchEmbed,
13
+ } from "./search-embed-flow.ts";
14
+
15
+ /**
16
+ * Adopt `_oke_search_embed_<table>` durable CDC bindings for each embed-declared
17
+ * table. Idempotent on flow name — safe to call once at boot after Manifest is
18
+ * known. Prefer {@link adopt} with real CDC {@link Binding}s so
19
+ * `dispatchCdc` runs them (bare `app.adopt([flow])` only registers the flow).
20
+ *
21
+ * @param adopt - App binding adopter (`adoptBinding` inside `oke()`)
22
+ * @param manifest - Project manifest
23
+ */
24
+ export function bindSearchEmbedFlows(
25
+ adopt: (binding: Binding) => void,
26
+ manifest: Manifest,
27
+ ): readonly string[] {
28
+ const tables = tablesNeedingSearchEmbed(manifest);
29
+ const names: string[] = [];
30
+ for (const t of tables) {
31
+ const name = searchEmbedFlowName(t.table);
32
+ names.push(name);
33
+ const models = [...new Set(t.columns.map((c) => c.model ?? "default"))];
34
+ const sqlRef = `sql:${t.store}` as const;
35
+ const embedFlow = flow(name, {
36
+ plane: "operator",
37
+ durable: true,
38
+ effects: {
39
+ reads: [sqlRef],
40
+ writes: [sqlRef],
41
+ embeds: models,
42
+ },
43
+ do: async (payload: { before: unknown; after: unknown }, fx) => {
44
+ await fx.step(`embed:${t.table}`, async () => {
45
+ await applySearchEmbedCdc(
46
+ fx,
47
+ t.table,
48
+ t.pk,
49
+ t.columns,
50
+ {
51
+ before: (payload.before ?? null) as Record<string, unknown> | null,
52
+ after: (payload.after ?? null) as Record<string, unknown> | null,
53
+ },
54
+ sqlRef,
55
+ );
56
+ });
57
+ },
58
+ });
59
+ const binding: Binding = {
60
+ trigger: { kind: "cdc", table: t.table, store: t.store },
61
+ flow: embedFlow,
62
+ };
63
+ adopt(binding);
64
+ }
65
+ return names;
66
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * BM25F scoring — Robertson–Zaragoza with field weights applied to TF
3
+ * **before** saturation (not post-hoc score summing).
4
+ */
5
+
6
+ import { BM25_B, BM25_K1 } from "./search-errors.ts";
7
+
8
+ /** One field's contribution to a document. */
9
+ export interface Bm25FieldTerms {
10
+ readonly weight: number;
11
+ /** term → raw term frequency in this field */
12
+ readonly tf: ReadonlyMap<string, number>;
13
+ }
14
+
15
+ /**
16
+ * BM25F score for one document given query terms and corpus stats.
17
+ *
18
+ * @param queryTerms - Tokenized query (unique terms scored once)
19
+ * @param fields - Per-field TF maps with BM25F weights
20
+ * @param docLen - Total document length (sum of field token counts)
21
+ * @param avgdl - Corpus average document length
22
+ * @param N - Corpus size
23
+ * @param df - Document frequency per term
24
+ */
25
+ export function bm25fScore(
26
+ queryTerms: readonly string[],
27
+ fields: readonly Bm25FieldTerms[],
28
+ docLen: number,
29
+ avgdl: number,
30
+ N: number,
31
+ df: ReadonlyMap<string, number>,
32
+ ): number {
33
+ if (N <= 0 || avgdl <= 0) return 0;
34
+ let score = 0;
35
+ const seen = new Set<string>();
36
+ for (const term of queryTerms) {
37
+ if (seen.has(term)) continue;
38
+ seen.add(term);
39
+ let weightedTf = 0;
40
+ for (const field of fields) {
41
+ const tf = field.tf.get(term) ?? 0;
42
+ if (tf > 0) weightedTf += field.weight * tf;
43
+ }
44
+ if (weightedTf <= 0) continue;
45
+ const n = df.get(term) ?? 0;
46
+ const idf = Math.log(1 + (N - n + 0.5) / (n + 0.5));
47
+ const denom = weightedTf + BM25_K1 * (1 - BM25_B + BM25_B * (docLen / avgdl));
48
+ score += idf * ((weightedTf * (BM25_K1 + 1)) / denom);
49
+ }
50
+ return score;
51
+ }
52
+
53
+ /**
54
+ * Simple whitespace + lower-case tokenizer for BM25 (English-oriented).
55
+ * Used for in-process scoring and tests; SQL path uses to_tsvector terms.
56
+ *
57
+ * @param text - Raw text
58
+ */
59
+ export function tokenize(text: string): string[] {
60
+ return text
61
+ .toLowerCase()
62
+ .replace(/[^\p{L}\p{N}\s]+/gu, " ")
63
+ .split(/\s+/)
64
+ .filter((t) => t.length > 0);
65
+ }
66
+
67
+ /**
68
+ * Build a TF map from tokens.
69
+ *
70
+ * @param tokens - Token list
71
+ */
72
+ export function termFrequencies(tokens: readonly string[]): Map<string, number> {
73
+ const tf = new Map<string, number>();
74
+ for (const t of tokens) {
75
+ tf.set(t, (tf.get(t) ?? 0) + 1);
76
+ }
77
+ return tf;
78
+ }