okengine 0.22.0 → 0.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (312) hide show
  1. package/AGENTS.md +7 -0
  2. package/manifest.v1.schema.json +44 -0
  3. package/package.json +32 -19
  4. package/site/content/docs/ai/mcp.mdx +14 -10
  5. package/site/content/docs/ai/skills.mdx +1 -1
  6. package/site/content/docs/elements/ai/agents.mdx +61 -13
  7. package/site/content/docs/elements/ai/decide.mdx +536 -0
  8. package/site/content/docs/elements/ai/events.mdx +373 -0
  9. package/site/content/docs/elements/ai/index.mdx +9 -3
  10. package/site/content/docs/elements/ai/meta.json +1 -1
  11. package/site/content/docs/elements/ai/prompts.mdx +4 -2
  12. package/site/content/docs/elements/channel/index.mdx +4 -4
  13. package/site/content/docs/elements/channel/receipts.mdx +7 -5
  14. package/site/content/docs/elements/channel/sms.mdx +2 -1
  15. package/site/content/docs/elements/flow/index.mdx +22 -18
  16. package/site/content/docs/elements/gate/auth.mdx +4 -1
  17. package/site/content/docs/elements/signal/index.mdx +15 -14
  18. package/site/content/docs/elements/store/index.mdx +1 -1
  19. package/site/content/docs/elements/store/kv.mdx +5 -5
  20. package/site/content/docs/plugins/otp.mdx +3 -3
  21. package/site/content/docs/recipes/dragonfly.mdx +5 -5
  22. package/site/content/docs/recipes/index.mdx +6 -6
  23. package/site/content/docs/recipes/postgres.mdx +12 -8
  24. package/site/content/docs/recipes/redis.mdx +4 -4
  25. package/site/content/docs/reference/cli.mdx +2 -1
  26. package/site/content/docs/reference/configuration.mdx +9 -7
  27. package/site/content/docs/reference/fx.mdx +19 -10
  28. package/src/auth/api-key-sql.test.ts +44 -0
  29. package/src/auth/api-key-sql.ts +1 -0
  30. package/src/auth/api-keys.ts +30 -12
  31. package/src/auth/config.ts +6 -2
  32. package/src/auth/gate-auth.test.ts +20 -0
  33. package/src/auth/tenants.ts +17 -0
  34. package/src/cli/agents-md.test.ts +25 -0
  35. package/src/cli/ask-vault-gaps.test.ts +38 -1
  36. package/src/cli/ask-vault-gaps.ts +35 -2
  37. package/src/cli/decide.ts +174 -0
  38. package/src/cli/decision-lock-watch.test.ts +45 -0
  39. package/src/cli/decision-lock-watch.ts +51 -0
  40. package/src/cli/dev-app-runner.ts +34 -8
  41. package/src/cli/dev-auth-secret.test.ts +49 -0
  42. package/src/cli/dev-auth-secret.ts +49 -0
  43. package/src/cli/dev-hot.test.ts +41 -0
  44. package/src/cli/dev-hot.ts +48 -0
  45. package/src/cli/dev.test.ts +1 -1
  46. package/src/cli/dev.ts +24 -6
  47. package/src/cli/doctor-diff.test.ts +29 -1
  48. package/src/cli/doctor-diff.ts +35 -0
  49. package/src/cli/eval.ts +142 -1
  50. package/src/cli/index.ts +5 -0
  51. package/src/cli/load-config.images.test.ts +2 -2
  52. package/src/cli/load-config.ts +1 -1
  53. package/src/cli/manifest-pr-diff.ts +16 -0
  54. package/src/cli/mcp-from-console.ts +48 -0
  55. package/src/cli/registry.ts +35 -1
  56. package/src/client/agent.ts +307 -0
  57. package/src/client/transport.test.ts +20 -0
  58. package/src/client/transport.ts +2 -1
  59. package/src/client/types.ts +1 -1
  60. package/src/client-react/index.ts +7 -1
  61. package/src/client-react/use-agent-run.test.ts +271 -0
  62. package/src/client-react/use-agent-run.ts +193 -0
  63. package/src/compiler/ai-approval.test.ts +36 -0
  64. package/src/compiler/ai-repair.test.ts +22 -0
  65. package/src/compiler/decisions.extract.test.ts +164 -0
  66. package/src/compiler/effects-fetch.test.ts +77 -0
  67. package/src/compiler/effects-infer.ts +114 -17
  68. package/src/compiler/effects-join.test.ts +48 -0
  69. package/src/compiler/extract.test.ts +28 -0
  70. package/src/compiler/extract.ts +279 -13
  71. package/src/compiler/fx-index.ts +58 -4
  72. package/src/compiler/response.ts +35 -2
  73. package/src/config/index.ts +21 -3
  74. package/src/console/index.ts +1 -1
  75. package/src/console/server/ai-runs-flows.ts +269 -0
  76. package/src/console/server/ai-runs.test.ts +323 -0
  77. package/src/console/server/ai-runs.ts +562 -0
  78. package/src/console/server/ai.ts +12 -1
  79. package/src/console/server/app.ts +10 -1
  80. package/src/console/server/decisions-flows.ts +157 -0
  81. package/src/console/server/decisions.test.ts +66 -0
  82. package/src/console/server/decisions.ts +229 -0
  83. package/src/console/server/flows.ts +50 -2
  84. package/src/console/server/invoke-user-flow.ts +9 -0
  85. package/src/console/server/runs-ingest.ts +16 -0
  86. package/src/console/server/serve.ts +5 -0
  87. package/src/console/server/state.ts +25 -1
  88. package/src/console/server/store-stats.test.ts +47 -0
  89. package/src/console/server/store-stats.ts +92 -18
  90. package/src/console/ui-next/dist/assets/access-page-C_N4qvai.js +4 -0
  91. package/src/console/ui-next/dist/assets/{agent-disclosure-Bkohc_8X.js → agent-disclosure-Cq1TxniW.js} +1 -1
  92. package/src/console/ui-next/dist/assets/{cache-glyph-rL0lHxat.js → cache-glyph-BaI4uxZz.js} +1 -1
  93. package/src/console/ui-next/dist/assets/{call-pii-button-Deo4PP18.js → call-pii-button-DNyK7IIY.js} +1 -1
  94. package/src/console/ui-next/dist/assets/{collapsible-CoJ6amHf.js → collapsible-CRhBJMZR.js} +1 -1
  95. package/src/console/ui-next/dist/assets/confirm-sheet-DZfuJXP4.js +1 -0
  96. package/src/console/ui-next/dist/assets/copy-inline-button-BTjPVjRT.js +1 -0
  97. package/src/console/ui-next/dist/assets/decisions-page-D9twj8Xy.js +1 -0
  98. package/src/console/ui-next/dist/assets/detail-header-Dk5LipF6.js +1 -0
  99. package/src/console/ui-next/dist/assets/dropdown-menu-CRGrj5JU.js +1 -0
  100. package/src/console/ui-next/dist/assets/duration-tone-CsHWlVPU.js +9 -0
  101. package/src/console/ui-next/dist/assets/element-icons-C6t8Vlml.js +1 -0
  102. package/src/console/ui-next/dist/assets/explorer-chrome-Dslh2yE8.js +1 -0
  103. package/src/console/ui-next/dist/assets/explorer-empty-CIVg7Jgb.js +1 -0
  104. package/src/console/ui-next/dist/assets/flows-page-ZaTJAhR9.js +1 -0
  105. package/src/console/ui-next/dist/assets/{highlighted-json-CPYBzDh0.js → highlighted-json-DQ-EMAfz.js} +1 -1
  106. package/src/console/ui-next/dist/assets/{http-method-DTPevKmM.js → http-method-Cn_Fl79s.js} +1 -1
  107. package/src/console/ui-next/dist/assets/index-BlrN49Hs.css +2 -0
  108. package/src/console/ui-next/dist/assets/index-CmRFeWdT.js +58 -0
  109. package/src/console/ui-next/dist/assets/observability-page-Cn6R_dXz.js +8 -0
  110. package/src/console/ui-next/dist/assets/{replica-lag-BM27lXld.js → replica-lag-Bx6EZhSe.js} +7 -7
  111. package/src/console/ui-next/dist/assets/request-meta-DmdiUrfh.js +1 -0
  112. package/src/console/ui-next/dist/assets/shortcut-keys-DTE1sjTC.js +1 -0
  113. package/src/console/ui-next/dist/assets/store-page-8P6vGXnn.js +41 -0
  114. package/src/console/ui-next/dist/assets/trace-detail-sheet-BfGvpFSv.js +2 -0
  115. package/src/console/ui-next/dist/assets/tree-expand-toggle-CWdUHbc0.js +55 -0
  116. package/src/console/ui-next/dist/assets/units-page-CZOFZzay.js +1 -0
  117. package/src/console/ui-next/dist/assets/use-vault-list-BAyQG8Re.js +1 -0
  118. package/src/console/ui-next/dist/assets/vault-page-ki_ishmX.js +2 -0
  119. package/src/console/ui-next/dist/index.html +4 -3
  120. package/src/console/ui-next/seed-parked-approval.ts +126 -0
  121. package/src/console/ui-next/src/client.ts +224 -1
  122. package/src/console/ui-next/src/components/explorer/detail-header.tsx +9 -0
  123. package/src/console/ui-next/src/components/explorer/explorer-start-toggle.tsx +5 -1
  124. package/src/console/ui-next/src/components/ui/sheet-form.tsx +21 -12
  125. package/src/console/ui-next/src/features/flows/decisions/decisions-page.tsx +216 -0
  126. package/src/console/ui-next/src/features/flows/decisions/resolve.test.ts +48 -0
  127. package/src/console/ui-next/src/features/flows/decisions/resolve.ts +111 -0
  128. package/src/console/ui-next/src/features/flows/flows-page.tsx +10 -0
  129. package/src/console/ui-next/src/features/flows/graph/element-map.ts +1 -0
  130. package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +6 -0
  131. package/src/console/ui-next/src/features/flows/traces/effect-summary.test.ts +24 -0
  132. package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +24 -0
  133. package/src/console/ui-next/src/features/flows/traces/http-status.ts +58 -0
  134. package/src/console/ui-next/src/features/flows/traces/request-client-card.tsx +355 -0
  135. package/src/console/ui-next/src/features/flows/traces/request-client.test.ts +114 -0
  136. package/src/console/ui-next/src/features/flows/traces/request-client.ts +423 -0
  137. package/src/console/ui-next/src/features/flows/traces/request-input-view.test.ts +52 -6
  138. package/src/console/ui-next/src/features/flows/traces/request-input-view.ts +85 -19
  139. package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +505 -80
  140. package/src/console/ui-next/src/features/flows/traces/trace-lanes.test.ts +61 -0
  141. package/src/console/ui-next/src/features/flows/traces/trace-lanes.ts +78 -0
  142. package/src/console/ui-next/src/features/flows/traces/trace-nav.test.ts +48 -0
  143. package/src/console/ui-next/src/features/flows/traces/trace-nav.ts +57 -0
  144. package/src/console/ui-next/src/features/flows/traces/trace-request-section.tsx +275 -96
  145. package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +16 -2
  146. package/src/console/ui-next/src/features/observability/detail/ai-runs.tsx +369 -0
  147. package/src/console/ui-next/src/features/observability/observability-page.tsx +31 -1
  148. package/src/console/ui-next/src/features/observability/state/observability-selection.ts +18 -3
  149. package/src/console/ui-next/src/features/store/performance/kv-performance-panel.tsx +12 -1
  150. package/src/console/ui-next/src/features/store/performance/performance-panel.tsx +14 -1
  151. package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +1 -1
  152. package/src/console/ui-next/src/features/units/detail/flow-contract-panel.tsx +6 -2
  153. package/src/console/ui-next/src/features/units/explorer/units-tree.tsx +73 -18
  154. package/src/console/ui-next/src/features/units/lib/unit-tree.test.ts +21 -0
  155. package/src/console/ui-next/src/features/units/lib/unit-tree.ts +82 -0
  156. package/src/console/ui-next/src/features/units/units-page.tsx +25 -7
  157. package/src/console/ui-next/src/router.tsx +17 -0
  158. package/src/console/ui-next/ui-next-seed-runs.ts +23 -0
  159. package/src/console/ui-next/vite-console-kernel-plugin.ts +43 -3
  160. package/src/console/ui-next/vite.config.ts +9 -0
  161. package/src/docker/recipes/dragonfly.ts +3 -2
  162. package/src/docker/recipes/redis.ts +1 -1
  163. package/src/drivers/ai-anthropic.ts +212 -21
  164. package/src/drivers/ai-mock.ts +34 -1
  165. package/src/drivers/ai-openai-compatible.ts +67 -26
  166. package/src/drivers/ai-preconnect.ts +18 -0
  167. package/src/drivers/ai-stream.test.ts +62 -0
  168. package/src/drivers/ai-types.ts +20 -0
  169. package/src/drivers/journal-postgres.test.ts +22 -1
  170. package/src/drivers/journal-postgres.ts +511 -13
  171. package/src/drivers/memory-ddl.test.ts +18 -0
  172. package/src/drivers/memory.ts +21 -1
  173. package/src/drivers/pg-rls.test.ts +31 -0
  174. package/src/drivers/pg-rls.ts +15 -0
  175. package/src/drivers/postgres.test.ts +9 -0
  176. package/src/drivers/postgres.ts +49 -11
  177. package/src/drivers/signal-compete.test.ts +234 -0
  178. package/src/drivers/signal-postgres.ts +39 -19
  179. package/src/drivers/signal-redis.ts +167 -11
  180. package/src/drivers/signal-types.ts +12 -0
  181. package/src/elements/ai/agent-event-slot.ts +27 -0
  182. package/src/elements/ai/approval-http.ts +207 -0
  183. package/src/elements/ai/approval.test.ts +741 -0
  184. package/src/elements/ai/approval.ts +228 -0
  185. package/src/elements/ai/decisions/bind.ts +163 -0
  186. package/src/elements/ai/decisions/certificate.test.ts +291 -0
  187. package/src/elements/ai/decisions/certificate.ts +377 -0
  188. package/src/elements/ai/decisions/certify.ts +314 -0
  189. package/src/elements/ai/decisions/decide.test.ts +953 -0
  190. package/src/elements/ai/decisions/e2e.test.ts +399 -0
  191. package/src/elements/ai/decisions/export.test.ts +120 -0
  192. package/src/elements/ai/decisions/export.ts +146 -0
  193. package/src/elements/ai/decisions/fixtures/openrouter-request.json +24 -0
  194. package/src/elements/ai/decisions/fixtures/openrouter-response.json +22 -0
  195. package/src/elements/ai/decisions/fixtures/typesafe-request.json +19 -0
  196. package/src/elements/ai/decisions/fixtures/typesafe-response.json +13 -0
  197. package/src/elements/ai/decisions/http.test.ts +154 -0
  198. package/src/elements/ai/decisions/http.ts +240 -0
  199. package/src/elements/ai/decisions/labels.test.ts +282 -0
  200. package/src/elements/ai/decisions/labels.ts +257 -0
  201. package/src/elements/ai/decisions/openrouter.live.test.ts +136 -0
  202. package/src/elements/ai/decisions/openrouter.ts +36 -0
  203. package/src/elements/ai/decisions/provider.ts +102 -0
  204. package/src/elements/ai/decisions/typesafe.ts +36 -0
  205. package/src/elements/ai/declare.ts +252 -1
  206. package/src/elements/ai/events.test.ts +379 -0
  207. package/src/elements/ai/events.ts +138 -0
  208. package/src/elements/ai/mcp-protocol.ts +5 -6
  209. package/src/elements/ai/run-events.test.ts +461 -0
  210. package/src/elements/ai/run-events.ts +522 -0
  211. package/src/elements/ai/runtime.ts +1187 -105
  212. package/src/elements/ai/stream-turn.ts +214 -0
  213. package/src/elements/ai/stream.live.test.ts +76 -0
  214. package/src/elements/ai/subagent.test.ts +136 -0
  215. package/src/elements/ai.test.ts +497 -3
  216. package/src/elements/ai.ts +16 -1
  217. package/src/elements/channel/runtime.ts +11 -0
  218. package/src/elements/channel/sql-ledger.ts +251 -0
  219. package/src/elements/clock/durable.ts +27 -1
  220. package/src/elements/signal/runtime.ts +9 -0
  221. package/src/elements/signal.test.ts +1 -0
  222. package/src/elements/store/cache.test.ts +120 -15
  223. package/src/elements/store/cache.ts +200 -39
  224. package/src/elements/store/prepare-row.test.ts +6 -2
  225. package/src/elements/store/sql-session.test.ts +53 -0
  226. package/src/elements/store/sql-session.ts +206 -10
  227. package/src/elements/store.ts +1 -0
  228. package/src/i18n/catalogs/ar.ts +5 -0
  229. package/src/i18n/catalogs/en.ts +5 -0
  230. package/src/index.ts +2 -0
  231. package/src/kernel/agent-event-store.ts +448 -0
  232. package/src/kernel/app.ts +135 -17
  233. package/src/kernel/auto-cache.test.ts +228 -0
  234. package/src/kernel/boot-bind/ai.ts +3 -0
  235. package/src/kernel/boot-bind/channel.ts +63 -2
  236. package/src/kernel/boot-bind/honor-config.test.ts +40 -10
  237. package/src/kernel/boot-bind/signal.ts +61 -25
  238. package/src/kernel/boot-bind/store.test.ts +26 -0
  239. package/src/kernel/boot-bind/store.ts +82 -6
  240. package/src/kernel/boot.ts +31 -6
  241. package/src/kernel/builtin-errors.ts +2 -0
  242. package/src/kernel/capability.ts +3 -0
  243. package/src/kernel/decision-label-store.ts +441 -0
  244. package/src/kernel/effects.test.ts +2 -1
  245. package/src/kernel/effects.ts +13 -1
  246. package/src/kernel/element-registries.ts +3 -0
  247. package/src/kernel/errors-compiler.ts +11 -0
  248. package/src/kernel/errors-text.ts +20 -0
  249. package/src/kernel/errors.ts +10 -0
  250. package/src/kernel/flow.test.ts +2 -3
  251. package/src/kernel/flow.ts +33 -3
  252. package/src/kernel/fx-call-types.test.ts +32 -0
  253. package/src/kernel/fx-decide.ts +765 -0
  254. package/src/kernel/fx-sql-handle.ts +46 -7
  255. package/src/kernel/fx.test.ts +13 -8
  256. package/src/kernel/fx.ts +339 -45
  257. package/src/kernel/http-frame.test.ts +94 -0
  258. package/src/kernel/http-frame.ts +203 -0
  259. package/src/kernel/idempotency.test.ts +2 -1
  260. package/src/kernel/journal.test.ts +114 -0
  261. package/src/kernel/journal.ts +471 -51
  262. package/src/kernel/json-result.ts +10 -0
  263. package/src/kernel/pipeline-tenant.ts +5 -1
  264. package/src/kernel/pipeline.test.ts +45 -0
  265. package/src/kernel/pipeline.ts +5 -0
  266. package/src/kernel/signal-tx.ts +28 -0
  267. package/src/kernel/sse-id.ts +32 -0
  268. package/src/kernel/store-transaction.test.ts +95 -0
  269. package/src/manifest/diff.ts +1 -0
  270. package/src/manifest/types.ts +29 -2
  271. package/src/mcp/ai-tools.test.ts +202 -0
  272. package/src/mcp/authorization.ts +66 -0
  273. package/src/mcp/docs-server.ts +19 -2
  274. package/src/mcp/mcp.test.ts +83 -0
  275. package/src/mcp/protocol.ts +8 -2
  276. package/src/mcp/server.ts +23 -2
  277. package/src/mcp/session.ts +3 -0
  278. package/src/mcp/tools.ts +168 -0
  279. package/src/mcp/versions.ts +60 -0
  280. package/src/plugins/index.ts +1 -0
  281. package/src/plugins/mena.ts +56 -0
  282. package/src/release/http-graph.test.ts +2 -0
  283. package/src/release/http-graph.ts +5 -0
  284. package/src/release/measure.ts +39 -14
  285. package/src/runs/collect.ts +4 -1
  286. package/src/runs/parquet.test.ts +27 -1
  287. package/src/runs/parquet.ts +19 -0
  288. package/src/runs/types.ts +31 -0
  289. package/src/runtime/json-code-block.test.ts +106 -47
  290. package/src/runtime/json-code-block.ts +1209 -46
  291. package/src/term.test.ts +13 -0
  292. package/src/term.ts +81 -49
  293. package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +0 -1
  294. package/src/console/ui-next/dist/assets/access-page-Ze6ckjdd.js +0 -4
  295. package/src/console/ui-next/dist/assets/copy-inline-button-CWG5_ZOh.js +0 -1
  296. package/src/console/ui-next/dist/assets/detail-header-b_ZzIsmU.js +0 -1
  297. package/src/console/ui-next/dist/assets/dropdown-menu-nwXEO1Ac.js +0 -1
  298. package/src/console/ui-next/dist/assets/duration-tone-BbQ_8z50.js +0 -9
  299. package/src/console/ui-next/dist/assets/element-icons-CwyLpVXz.js +0 -1
  300. package/src/console/ui-next/dist/assets/explorer-empty-C8sSCqYT.js +0 -1
  301. package/src/console/ui-next/dist/assets/flows-page-D2E7BtKK.js +0 -1
  302. package/src/console/ui-next/dist/assets/index-VxoEz295.css +0 -2
  303. package/src/console/ui-next/dist/assets/index-vTuwmeQz.js +0 -58
  304. package/src/console/ui-next/dist/assets/observability-page-SJTSzHFJ.js +0 -4
  305. package/src/console/ui-next/dist/assets/request-meta-BjFT7DNl.js +0 -1
  306. package/src/console/ui-next/dist/assets/shortcut-keys-CUNegEV4.js +0 -1
  307. package/src/console/ui-next/dist/assets/store-page-Qj9ThRE-.js +0 -41
  308. package/src/console/ui-next/dist/assets/trace-detail-sheet-Cmitq3da.js +0 -2
  309. package/src/console/ui-next/dist/assets/tree-expand-toggle-CbDIB-7x.js +0 -55
  310. package/src/console/ui-next/dist/assets/units-page-_PuYqFty.js +0 -1
  311. package/src/console/ui-next/dist/assets/use-vault-list-Cs45Czkt.js +0 -1
  312. package/src/console/ui-next/dist/assets/vault-page-BOMr0go9.js +0 -2
@@ -51,6 +51,24 @@ export type { WhereMap } from "./sql-condition.ts";
51
51
  */
52
52
  export type InferSelectRow<T> = T extends { readonly $inferSelect: infer R } ? R : SqlRow;
53
53
 
54
+ /**
55
+ * Insert row from a Drizzle table's `$inferInsert`, or {@link SqlRow}.
56
+ *
57
+ * @typeParam T - Table handle or Drizzle table
58
+ */
59
+ export type InferInsertRow<T> = T extends { readonly $inferInsert: infer R } ? R : SqlRow;
60
+
61
+ /**
62
+ * Equality map or a Drizzle SQL fragment (`queryChunks`).
63
+ * A bare `unknown` would swallow the row type.
64
+ *
65
+ * @typeParam TRow - Selected or inserted row
66
+ */
67
+ export type SqlPredicate<TRow> =
68
+ | { readonly [K in keyof TRow]?: TRow[K] }
69
+ | { readonly queryChunks: readonly unknown[] }
70
+ | undefined;
71
+
54
72
  /** Serialize stamp frames on a shared connection so concurrent identities cannot interleave. */
55
73
  const rlsStampTails = new WeakMap<SqlConnection, Promise<unknown>>();
56
74
 
@@ -119,6 +137,9 @@ function notifySqlCdc(event: {
119
137
  */
120
138
  const cdcMutationStorage = new AsyncLocalStorage<{ readonly mutationId: string }>();
121
139
 
140
+ /** Pinned connection for {@link SqlStoreHandle.transaction}. */
141
+ const sqlTxStorage = new AsyncLocalStorage<SqlConnection>();
142
+
122
143
  /**
123
144
  * Read the ambient mutation id, or `undefined` outside a stamped request.
124
145
  */
@@ -274,7 +295,7 @@ export interface SelectFromBuilder<TRow = SqlRow> extends PromiseLike<TRow[]> {
274
295
  *
275
296
  * @param where - Condition
276
297
  */
277
- where(where: unknown): SelectWhereBuilder<TRow>;
298
+ where(where: SqlPredicate<TRow>): SelectWhereBuilder<TRow>;
278
299
  /**
279
300
  * Order rows with Drizzle `asc()` / `desc()` terms.
280
301
  *
@@ -312,14 +333,18 @@ export interface SelectBuilder<TLocked extends SqlRow | undefined = undefined> {
312
333
  ): SelectFromBuilder<TLocked extends undefined ? InferSelectRow<TTable> : TLocked>;
313
334
  }
314
335
 
315
- /** Fluent insert builder. */
316
- export interface InsertBuilder {
336
+ /**
337
+ * Fluent insert builder.
338
+ *
339
+ * @typeParam TRow - `$inferInsert` when the table is a Drizzle table
340
+ */
341
+ export interface InsertBuilder<TRow = SqlRow> {
317
342
  /**
318
343
  * Provide row values.
319
344
  *
320
345
  * @param row - Row to insert
321
346
  */
322
- values(row: SqlRow): InsertValuesBuilder;
347
+ values(row: TRow): InsertValuesBuilder;
323
348
  }
324
349
 
325
350
  /**
@@ -340,7 +365,7 @@ export interface DeleteBuilder {
340
365
  *
341
366
  * @param where - Equality map or Drizzle SQL
342
367
  */
343
- where(where: unknown): Promise<number>;
368
+ where(where: SqlPredicate<SqlRow>): Promise<number>;
344
369
  }
345
370
 
346
371
  /** Fluent update builder. */
@@ -360,7 +385,7 @@ export interface UpdateSetBuilder {
360
385
  *
361
386
  * @param where - Equality map or Drizzle SQL
362
387
  */
363
- where(where: unknown): Promise<number>;
388
+ where(where: SqlPredicate<SqlRow>): Promise<number>;
364
389
  }
365
390
 
366
391
  /**
@@ -393,7 +418,7 @@ export interface SqlStoreHandle {
393
418
  *
394
419
  * @param table - Target table
395
420
  */
396
- insert(table: TableHandle | unknown): InsertBuilder;
421
+ insert<TTable>(table: TTable): InsertBuilder<InferInsertRow<TTable>>;
397
422
  /**
398
423
  * Start an update on `table`.
399
424
  *
@@ -415,6 +440,22 @@ export interface SqlStoreHandle {
415
440
  */
416
441
  delete(table: TableHandle | unknown): DeleteBuilder;
417
442
  delete(table: TableHandle | unknown, id: string): Promise<boolean>;
443
+ /**
444
+ * Run `fn` on one pinned connection. `fx.emit` inside `fn` stages on the
445
+ * signal outbox and publishes only after this SQL transaction commits.
446
+ *
447
+ * @param fn - Transaction body. `tx` is this handle, bound to the pin.
448
+ */
449
+ transaction<T>(fn: (tx: SqlStoreHandle) => Promise<T>): Promise<T>;
450
+ /**
451
+ * Execute a Drizzle select, insert, update, or delete builder.
452
+ * Calls `toSQL()` so `drizzle-orm` stays off the edge graph.
453
+ *
454
+ * @param query - Builder with `toSQL()`
455
+ */
456
+ run<T extends SqlRow = SqlRow>(query: {
457
+ toSQL(): { readonly sql: string; readonly params: readonly unknown[] };
458
+ }): Promise<T[]>;
418
459
  /**
419
460
  * True when at least one row matches.
420
461
  *
@@ -424,7 +465,9 @@ export interface SqlStoreHandle {
424
465
  exists(table: TableHandle | unknown, idOrWhere: string | WhereMap): Promise<boolean>;
425
466
  /**
426
467
  * Insert when no row matches `matchOn`; never touches an existing match
427
- * unless `options.onExisting` is `"update"`.
468
+ * unless `options.onExisting` is `"update"`. A match that is only the
469
+ * primary key is one `INSERT … ON CONFLICT` round trip on postgres and
470
+ * pglite. Memory SQL, and any other predicate, stays a read then a write.
428
471
  *
429
472
  * @param table - Table
430
473
  * @param matchOn - Equality map or Drizzle condition identifying the row
@@ -562,6 +605,10 @@ export function createSqlStoreHandle(
562
605
  }
563
606
  }
564
607
 
608
+ function activeConnection(): SqlConnection {
609
+ return sqlTxStorage.getStore() ?? connection;
610
+ }
611
+
565
612
  function query(sql: string, params: readonly unknown[] = []): Promise<SqlRow[]> {
566
613
  return withSchemaGuard(() => withRlsStamp((conn) => conn.query(sql, params), sql));
567
614
  }
@@ -570,9 +617,29 @@ export function createSqlStoreHandle(
570
617
  return withSchemaGuard(() => withRlsStamp((conn) => conn.exec(sql, params), sql));
571
618
  }
572
619
 
620
+ /**
621
+ * Rewrite Drizzle `$1` placeholders to the connection's `?` form.
622
+ *
623
+ * @param sql - Dialect SQL
624
+ * @param params - Bindings in `$n` order (1-based)
625
+ */
626
+ function fromDialect(
627
+ sql: string,
628
+ params: readonly unknown[],
629
+ ): { sql: string; params: unknown[] } {
630
+ if (!sql.includes("$")) return { sql, params: [...params] };
631
+ const ordered: unknown[] = [];
632
+ const text = sql.replace(/\$(\d+)/g, (_match, index: string) => {
633
+ ordered.push(params[Number(index) - 1]);
634
+ return "?";
635
+ });
636
+ return { sql: text, params: ordered };
637
+ }
638
+
573
639
  async function withRlsStamp<T>(fn: (conn: SqlConnection) => Promise<T>, sql: string): Promise<T> {
574
- if (!rls || !RLS_CONTEXT_DRIVERS.has(connection.driverId)) return fn(connection);
575
- if (isRlsStampExemptSql(sql)) return fn(connection);
640
+ if (!rls || !RLS_CONTEXT_DRIVERS.has(connection.driverId)) return fn(activeConnection());
641
+ if (isRlsStampExemptSql(sql)) return fn(activeConnection());
642
+ if (sqlTxStorage.getStore()) return fn(activeConnection());
576
643
  const run = (): Promise<T> => applyRlsStamp(fn);
577
644
  // PGlite is one backend session — concurrent identities must not interleave.
578
645
  // Pooled postgres pins each stamp via `transaction()` instead.
@@ -685,6 +752,101 @@ export function createSqlStoreHandle(
685
752
  return coerceCompiledWhere(table, compileWhere(where));
686
753
  }
687
754
 
755
+ /**
756
+ * Primary-key equality only. Any other predicate cannot use `ON CONFLICT`.
757
+ *
758
+ * @param compiled - Match clause
759
+ * @param pkCol - Primary key SQL name
760
+ */
761
+ function primaryKeyEquality(
762
+ compiled: ReturnType<typeof compileWhere>,
763
+ pkCol: string,
764
+ ): { readonly value: unknown } | undefined {
765
+ if (compiled.predicates.length !== 1) return undefined;
766
+ const pred = compiled.predicates[0];
767
+ if (!pred || pred.op !== "=" || pred.column !== pkCol) return undefined;
768
+ return { value: pred.value };
769
+ }
770
+
771
+ /**
772
+ * One round trip: `INSERT … ON CONFLICT (pk)`.
773
+ *
774
+ * `DO NOTHING` when the existing row must stay. `DO UPDATE` captures the
775
+ * pre-image in a CTE so CDC still sees before and after.
776
+ *
777
+ * @param table - Table
778
+ * @param name - SQL table name
779
+ * @param pkCol - Primary key SQL name
780
+ * @param pkValue - Bound primary key
781
+ * @param values - Insert / update payload
782
+ * @param onExisting - `"update"` rewrites the existing row
783
+ */
784
+ async function upsertOnPrimaryKey(
785
+ table: TableHandle | unknown,
786
+ name: string,
787
+ pkCol: string,
788
+ pkValue: unknown,
789
+ values: SqlRow,
790
+ onExisting: "update" | undefined,
791
+ ): Promise<UpsertResult> {
792
+ const prepared = prepareInsertRow(table, values);
793
+ if (!(pkCol in prepared)) prepared[pkCol] = pkValue;
794
+ const cols = Object.keys(prepared);
795
+ const colList = cols.map(quoteIdent).join(", ");
796
+ const placeholders = cols.map(() => "?").join(", ");
797
+ const params = cols.map((col) => prepared[col]);
798
+ const quotedPk = quoteIdent(pkCol);
799
+ const quotedName = quoteIdent(name);
800
+ const setEntries =
801
+ onExisting === "update"
802
+ ? Object.entries(prepareUpdateRow(table, values)).filter(([col]) => col !== pkCol)
803
+ : [];
804
+ if (onExisting !== "update" || setEntries.length === 0) {
805
+ const rows = await query(
806
+ `INSERT INTO ${quotedName} (${colList}) VALUES (${placeholders})` +
807
+ ` ON CONFLICT (${quotedPk}) DO NOTHING RETURNING *`,
808
+ params,
809
+ );
810
+ if (rows.length === 0) {
811
+ return { status: onExisting === "update" ? "changed" : "already-existed" };
812
+ }
813
+ notifySqlCdc({
814
+ tableName: name,
815
+ op: "insert",
816
+ before: null,
817
+ after: toJs(table, rows)[0] ?? null,
818
+ });
819
+ return { status: "upserted" };
820
+ }
821
+ const setSql = setEntries
822
+ .map(([col]) => `${quoteIdent(col)} = EXCLUDED.${quoteIdent(col)}`)
823
+ .join(", ");
824
+ // The old row is a CTE, then read from an outer SELECT. INSERT …
825
+ // RETURNING cannot see that CTE on PGlite, so the before image would
826
+ // be null and an update would look like an insert.
827
+ const result = await query(
828
+ `WITH __oke_old AS (` +
829
+ `SELECT * FROM ${quotedName} WHERE ${quotedPk} = ? FOR UPDATE` +
830
+ `), __oke_new AS (` +
831
+ `INSERT INTO ${quotedName} (${colList}) VALUES (${placeholders})` +
832
+ ` ON CONFLICT (${quotedPk}) DO UPDATE SET ${setSql}` +
833
+ ` RETURNING *` +
834
+ `) SELECT (SELECT row_to_json(__oke_old) FROM __oke_old) AS __oke_before_data,` +
835
+ ` (SELECT row_to_json(__oke_new) FROM __oke_new) AS __oke_after_data`,
836
+ [pkValue, ...params],
837
+ );
838
+ const raw = result[0];
839
+ const before = raw ? jsonImageToJs(table, raw.__oke_before_data) : null;
840
+ const after = raw ? jsonImageToJs(table, raw.__oke_after_data) : null;
841
+ notifySqlCdc({
842
+ tableName: name,
843
+ op: before ? "update" : "insert",
844
+ before,
845
+ after,
846
+ });
847
+ return { status: before ? "changed" : "upserted" };
848
+ }
849
+
688
850
  /**
689
851
  * Postgres `row_to_json` images come back as JSON strings on pglite and
690
852
  * objects on Bun.SQL — normalize to the JS row mapping (snake→camel).
@@ -1026,6 +1188,19 @@ export function createSqlStoreHandle(
1026
1188
  if (!compiled.clause) {
1027
1189
  throw new Error("upsert() requires at least one matchOn predicate");
1028
1190
  }
1191
+ const pkCol = resolvePkColumn(table);
1192
+ const pkMatch = primaryKeyEquality(compiled, pkCol);
1193
+ // Memory SQL has no ON CONFLICT. Postgres and PGlite do.
1194
+ if (pkMatch && connection.driverId !== "memory") {
1195
+ return upsertOnPrimaryKey(
1196
+ table,
1197
+ name,
1198
+ pkCol,
1199
+ pkMatch.value,
1200
+ values,
1201
+ upsertOptions?.onExisting,
1202
+ );
1203
+ }
1029
1204
  const found = await query(
1030
1205
  `SELECT 1 AS "ok" FROM ${quoteIdent(name)} WHERE ${compiled.clause} LIMIT 1`,
1031
1206
  compiled.params,
@@ -1222,6 +1397,27 @@ export function createSqlStoreHandle(
1222
1397
  });
1223
1398
  },
1224
1399
 
1400
+ async transaction<T>(fn: (tx: SqlStoreHandle) => Promise<T>): Promise<T> {
1401
+ if (!connection.transaction) {
1402
+ throw new Error("fx.store().transaction needs SqlConnection.transaction");
1403
+ }
1404
+ return connection.transaction(async (txConn) =>
1405
+ sqlTxStorage.run(txConn, () => fn(handle as SqlStoreHandle)),
1406
+ );
1407
+ },
1408
+
1409
+ async run<T extends SqlRow = SqlRow>(builder: {
1410
+ toSQL(): { readonly sql: string; readonly params: readonly unknown[] };
1411
+ }): Promise<T[]> {
1412
+ const compiled = builder.toSQL();
1413
+ const dialect = fromDialect(compiled.sql, compiled.params);
1414
+ if (isSqlDml(dialect.sql)) {
1415
+ await exec(dialect.sql, dialect.params);
1416
+ return [];
1417
+ }
1418
+ return query(dialect.sql, dialect.params) as Promise<T[]>;
1419
+ },
1420
+
1225
1421
  async ensureTable(table: TableHandle) {
1226
1422
  const cols = Object.values(table.columns);
1227
1423
  const pk = resolvePkColumn(table);
@@ -137,6 +137,7 @@ export type {
137
137
  SelectWhereBuilder,
138
138
  SelectOrderBuilder,
139
139
  InferSelectRow,
140
+ SqlPredicate,
140
141
  InsertBuilder,
141
142
  InsertValuesBuilder,
142
143
  SqlSessionOptions,
@@ -40,6 +40,10 @@ export const builtinAr = {
40
40
  cause: 'التدفق "{flow}" يضمّن عبر "{resource}" دون الإعلان عنه.',
41
41
  fix: 'أضف "{resource}" إلى effects.embeds لهذا التدفق.',
42
42
  },
43
+ "1010": {
44
+ cause: 'التدفق "{flow}" يقرر "{resource}" دون الإعلان عنه.',
45
+ fix: 'أضف "{resource}" إلى effects.decides لهذا التدفق.',
46
+ },
43
47
  "1240": {
44
48
  cause: 'التدفق "{flow}" يُصدِر الإشارة "{resource}" بلا مشترك.',
45
49
  fix: "أضف on({resource}, …) أو عيّن الإشارة '{'optional: true'}'.",
@@ -90,6 +94,7 @@ export const builtinAr = {
90
94
  IdempotencyKeyInvalid: "يجب أن يكون Idempotency-Key من 16 إلى 255 حرفًا ASCII قابلًا للطباعة.",
91
95
  IdempotencyKeyReused: "استُخدم Idempotency-Key هذا من قبل مع طلب مختلف.",
92
96
  IdempotencyInProgress: "ما زال Idempotency-Key هذا قيد التنفيذ. أعد المحاولة بعد المهلة.",
97
+ JournalLeaseBusy: "هذا التشغيل مقفل لدى عامل آخر. أعد المحاولة بعد المهلة.",
93
98
  InvalidQuery: "الاستعلام غير صالح.",
94
99
  UnsupportedMediaType: "نوع المحتوى هذا غير مدعوم.",
95
100
  AuthFailed: "فشلت المصادقة.",
@@ -40,6 +40,10 @@ export const builtinEn = {
40
40
  cause: 'Flow "{flow}" embeds with "{resource}" without declaring it.',
41
41
  fix: "Add \"{resource}\" to this flow''s effects.embeds.",
42
42
  },
43
+ "1010": {
44
+ cause: 'Flow "{flow}" decides "{resource}" without declaring it.',
45
+ fix: "Add \"{resource}\" to this flow''s effects.decides.",
46
+ },
43
47
  "1240": {
44
48
  cause: 'Flow "{flow}" emits signal "{resource}" with no subscriber.',
45
49
  fix: "Add on({resource}, …) or mark the signal '{'optional: true'}'.",
@@ -90,6 +94,7 @@ export const builtinEn = {
90
94
  IdempotencyKeyInvalid: "Idempotency-Key must be 16–255 printable ASCII characters.",
91
95
  IdempotencyKeyReused: "This Idempotency-Key was already used with a different request.",
92
96
  IdempotencyInProgress: "This Idempotency-Key is still running. Retry after the given delay.",
97
+ JournalLeaseBusy: "This run is locked by another worker. Retry after the given delay.",
93
98
  InvalidQuery: "The query is not valid.",
94
99
  UnsupportedMediaType: "That content type is not supported.",
95
100
  AuthFailed: "Authentication failed.",
package/src/index.ts CHANGED
@@ -175,6 +175,8 @@ export {
175
175
  type AiModelDecl,
176
176
  type AiPromptDecl,
177
177
  type AiAgentDecl,
178
+ type AiDecisionDecl,
179
+ type AiDecisionOptions,
178
180
  type AiMcpServerDecl,
179
181
  type AiMcpServerOptions,
180
182
  type AiRuntime,