okengine 0.13.0 → 0.15.2

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 (277) hide show
  1. package/manifest.v1.schema.json +7 -1
  2. package/package.json +4 -5
  3. package/site/content/docs/ai/mcp.mdx +27 -2
  4. package/site/content/docs/elements/ai.mdx +59 -11
  5. package/site/content/docs/elements/channel.mdx +1 -0
  6. package/site/content/docs/elements/clock.mdx +30 -12
  7. package/site/content/docs/elements/flow.mdx +9 -3
  8. package/site/content/docs/elements/gate.mdx +25 -22
  9. package/site/content/docs/elements/signal.mdx +71 -25
  10. package/site/content/docs/elements/store.mdx +87 -28
  11. package/site/content/docs/elements/vault.mdx +1 -0
  12. package/site/content/docs/get-started/basic-usage.mdx +22 -13
  13. package/site/content/docs/get-started/index.mdx +5 -0
  14. package/site/content/docs/get-started/installation.mdx +2 -2
  15. package/site/content/docs/get-started/introduction.mdx +11 -3
  16. package/site/content/docs/get-started/meta.json +1 -1
  17. package/site/content/docs/get-started/project-structure.mdx +932 -0
  18. package/site/content/docs/reference/cli.md +13 -10
  19. package/site/content/docs/reference/client.mdx +25 -21
  20. package/site/content/docs/reference/fx.mdx +27 -22
  21. package/site/content/docs/reference/security.md +1 -1
  22. package/src/auth/bindings.ts +14 -5
  23. package/src/auth/cross-plane.ts +10 -3
  24. package/src/cli/ai-setup/ai-setup.test.ts +62 -0
  25. package/src/cli/ai-setup/apply.ts +32 -1
  26. package/src/cli/ai-setup/recommend.test.ts +25 -0
  27. package/src/cli/ai-setup/recommend.ts +8 -3
  28. package/src/cli/attach-host-console.test.ts +3 -2
  29. package/src/cli/attach-host-console.ts +3 -0
  30. package/src/cli/build.test.ts +4 -2
  31. package/src/cli/db-seed.ts +48 -15
  32. package/src/cli/db.test.ts +104 -0
  33. package/src/cli/db.ts +122 -10
  34. package/src/cli/dev-controls.test.ts +20 -0
  35. package/src/cli/dev-controls.ts +13 -0
  36. package/src/cli/dev.test.ts +15 -4
  37. package/src/cli/dev.ts +102 -29
  38. package/src/cli/ensure-drizzle-config.ts +2 -1
  39. package/src/cli/tui/DevLive.tsx +12 -2
  40. package/src/cli/tui/keys.test.ts +2 -1
  41. package/src/cli/tui/keys.ts +3 -2
  42. package/src/compiler/aot.test.ts +1 -1
  43. package/src/compiler/effects-infer.ts +58 -3
  44. package/src/compiler/extract.test.ts +458 -29
  45. package/src/compiler/extract.ts +387 -69
  46. package/src/compiler/flow-path.test.ts +96 -0
  47. package/src/compiler/flow-path.ts +173 -0
  48. package/src/compiler/generate-adopt.test.ts +112 -3
  49. package/src/compiler/generate-adopt.ts +280 -26
  50. package/src/compiler/response.ts +45 -1
  51. package/src/compiler/schema-from-ast.ts +609 -0
  52. package/src/console/server/ai.ts +5 -2
  53. package/src/console/server/bind.ts +2 -2
  54. package/src/console/server/console.test.ts +3 -0
  55. package/src/console/server/flows.ts +121 -40
  56. package/src/console/server/invoke-user-flow.test.ts +3 -1
  57. package/src/console/server/invoke-user-flow.ts +17 -2
  58. package/src/console/server/iso-at.test.ts +9 -0
  59. package/src/console/server/iso-at.ts +11 -0
  60. package/src/console/server/security-headers.ts +6 -3
  61. package/src/console/server/serve.ts +17 -2
  62. package/src/console/server/spa-proxy.test.ts +1 -0
  63. package/src/console/server/sql-catalog.ts +147 -7
  64. package/src/console/server/sql-rls.test.ts +16 -0
  65. package/src/console/server/state.ts +8 -0
  66. package/src/console/server/store.test.ts +7 -4
  67. package/src/console/server/store.ts +142 -58
  68. package/src/console/ui-next/dist/assets/agent-disclosure-C0X1fbWF.js +1 -0
  69. package/src/console/ui-next/dist/assets/cache-glyph-F1FI122b.js +1 -0
  70. package/src/console/ui-next/dist/assets/{call-pii-button-bqkxMrJH.js → call-pii-button-DEDCl_j3.js} +1 -1
  71. package/src/console/ui-next/dist/assets/collapsible-LPqGvfoz.js +1 -0
  72. package/src/console/ui-next/dist/assets/{confirm-sheet-DDCRmG62.js → confirm-sheet-3ptDzXbz.js} +1 -1
  73. package/src/console/ui-next/dist/assets/{duration-tone-oiRxPVsZ.js → duration-tone-sC3lGABz.js} +1 -1
  74. package/src/console/ui-next/dist/assets/{explorer-empty-HjTnVQoR.js → explorer-empty-BvYrygyO.js} +1 -1
  75. package/src/console/ui-next/dist/assets/flows-page-Dluo1Bd7.js +1 -0
  76. package/src/console/ui-next/dist/assets/{highlighted-json-CvDPvveV.js → highlighted-json-CS_O8L-r.js} +1 -1
  77. package/src/console/ui-next/dist/assets/http-method--sWDdXSB.js +1 -0
  78. package/src/console/ui-next/dist/assets/index-Ca3HZMVq.js +66 -0
  79. package/src/console/ui-next/dist/assets/index-UtTDRQpS.css +2 -0
  80. package/src/console/ui-next/dist/assets/{link-DF7SZ9Ek.js → link-COeyggt-.js} +1 -1
  81. package/src/console/ui-next/dist/assets/observability-page-HK9-BO8z.js +4 -0
  82. package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +1 -0
  83. package/src/console/ui-next/dist/assets/replica-lag-DBIFFf7d.js +18 -0
  84. package/src/console/ui-next/dist/assets/sheet-form-Dnwa5oOR.js +1 -0
  85. package/src/console/ui-next/dist/assets/shortcut-keys-CoqwEIi0.js +1 -0
  86. package/src/console/ui-next/dist/assets/skeleton-BHcfDYcb.js +1 -0
  87. package/src/console/ui-next/dist/assets/store-page-BSohH3wM.js +41 -0
  88. package/src/console/ui-next/dist/assets/trace-detail-sheet-CFIGRnnA.js +2 -0
  89. package/src/console/ui-next/dist/assets/tree-expand-toggle-BgLl34w7.js +54 -0
  90. package/src/console/ui-next/dist/assets/units-page-B0cFE76A.js +1 -0
  91. package/src/console/ui-next/dist/assets/{use-vault-list-Cl79j_ku.js → use-vault-list-BLbZhzyF.js} +1 -1
  92. package/src/console/ui-next/dist/assets/vault-page-B1SbYe10.js +2 -0
  93. package/src/console/ui-next/dist/index.html +6 -6
  94. package/src/console/ui-next/seed-invoke-host.ts +2 -0
  95. package/src/console/ui-next/src/client.ts +35 -7
  96. package/src/console/ui-next/src/components/motion/table/index.tsx +16 -0
  97. package/src/console/ui-next/src/components/motion/table/types.ts +4 -0
  98. package/src/console/ui-next/src/components/ui/kbd.tsx +2 -2
  99. package/src/console/ui-next/src/features/flows/graph/build-flow-graph.test.ts +18 -0
  100. package/src/console/ui-next/src/features/flows/graph/build-flow-graph.ts +14 -1
  101. package/src/console/ui-next/src/features/flows/graph/neighborhood.test.ts +17 -0
  102. package/src/console/ui-next/src/features/flows/graph/neighborhood.ts +16 -3
  103. package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +3 -1
  104. package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +24 -0
  105. package/src/console/ui-next/src/features/flows/traces/http-method.ts +19 -0
  106. package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +9 -3
  107. package/src/console/ui-next/src/features/flows/traces/trace-detail.test.ts +29 -2
  108. package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +10 -3
  109. package/src/console/ui-next/src/features/observability/lib/ask-count.test.ts +25 -0
  110. package/src/console/ui-next/src/features/observability/lib/ask-count.ts +4 -1
  111. package/src/console/ui-next/src/features/store/detail/browse-section.tsx +45 -1
  112. package/src/console/ui-next/src/features/store/detail/resource-panel.tsx +78 -16
  113. package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +155 -14
  114. package/src/console/ui-next/src/features/store/explorer/store-tree.tsx +33 -133
  115. package/src/console/ui-next/src/features/store/files/file-preview.tsx +4 -1
  116. package/src/console/ui-next/src/features/store/grid/rls-policy-sheet.tsx +326 -165
  117. package/src/console/ui-next/src/features/store/grid/sql-insert-sheet.tsx +6 -0
  118. package/src/console/ui-next/src/features/store/grid/store-data-grid.tsx +50 -3
  119. package/src/console/ui-next/src/features/store/lib/files-meta.test.ts +3 -0
  120. package/src/console/ui-next/src/features/store/lib/files-meta.ts +12 -0
  121. package/src/console/ui-next/src/features/store/lib/query-defaults.test.ts +25 -0
  122. package/src/console/ui-next/src/features/store/lib/query-defaults.ts +85 -3
  123. package/src/console/ui-next/src/features/store/lib/query-gate.ts +30 -4
  124. package/src/console/ui-next/src/features/store/lib/rls-policy.test.ts +139 -0
  125. package/src/console/ui-next/src/features/store/lib/rls-policy.ts +223 -21
  126. package/src/console/ui-next/src/features/store/lib/schema-graph.ts +4 -0
  127. package/src/console/ui-next/src/features/store/lib/sql-catalog.test.ts +24 -0
  128. package/src/console/ui-next/src/features/store/lib/sql-catalog.ts +19 -0
  129. package/src/console/ui-next/src/features/store/lib/store-tree.test.ts +6 -0
  130. package/src/console/ui-next/src/features/store/lib/store-tree.ts +13 -0
  131. package/src/console/ui-next/src/features/store/query/query-console.tsx +48 -14
  132. package/src/console/ui-next/src/features/store/query/query-gate-menu.tsx +19 -20
  133. package/src/console/ui-next/src/features/store/query/query-gate-parts.tsx +2 -1
  134. package/src/console/ui-next/src/features/store/query/query-results.tsx +110 -83
  135. package/src/console/ui-next/src/features/store/schema/schema-table-node.tsx +17 -0
  136. package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +112 -72
  137. package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +12 -4
  138. package/src/console/ui-next/src/features/units/lib/contract-input.test.ts +206 -1
  139. package/src/console/ui-next/src/features/units/lib/contract-input.ts +55 -0
  140. package/src/console/ui-next/src/features/vault/lib/types.ts +1 -1
  141. package/src/docker/compose-health.test.ts +27 -0
  142. package/src/docker/compose-health.ts +19 -3
  143. package/src/docker/docker.test.ts +4 -1
  144. package/src/docker/dockerfile.ts +1 -1
  145. package/src/docker/recipes/pgdog.ts +2 -0
  146. package/src/drivers/ai-anthropic.ts +5 -0
  147. package/src/drivers/ai-ollama.ts +49 -30
  148. package/src/drivers/ai-openai-compatible.ts +57 -46
  149. package/src/drivers/ai-providers.test.ts +3 -0
  150. package/src/drivers/bun-native-completeness.test.ts +7 -9
  151. package/src/drivers/clock-postgres.ts +13 -9
  152. package/src/drivers/instances-postgres.ts +4 -6
  153. package/src/drivers/journal-postgres.ts +13 -9
  154. package/src/drivers/pg-rls.test.ts +87 -0
  155. package/src/drivers/pg-rls.ts +233 -7
  156. package/src/drivers/pglite.ts +26 -3
  157. package/src/drivers/postgres.test.ts +23 -0
  158. package/src/drivers/postgres.ts +133 -9
  159. package/src/drivers/redis.ts +11 -4
  160. package/src/drivers/signal-redis.ts +24 -14
  161. package/src/drivers/signal-types.ts +2 -1
  162. package/src/drivers/types.ts +10 -0
  163. package/src/elements/ai/declare.ts +109 -0
  164. package/src/elements/ai/errors.test.ts +5 -1
  165. package/src/elements/ai/errors.ts +30 -2
  166. package/src/elements/ai/eval.ts +4 -6
  167. package/src/elements/ai/mcp-client.test.ts +206 -0
  168. package/src/elements/ai/mcp-client.ts +362 -0
  169. package/src/elements/ai/mcp-http.ts +159 -0
  170. package/src/elements/ai/mcp-mock.ts +134 -0
  171. package/src/elements/ai/mcp-protocol.ts +234 -0
  172. package/src/elements/ai/mcp-stdio.test.ts +50 -0
  173. package/src/elements/ai/mcp-stdio.ts +212 -0
  174. package/src/elements/ai/mcp-transport.ts +70 -0
  175. package/src/elements/ai/runtime.ts +159 -29
  176. package/src/elements/ai.test.ts +139 -0
  177. package/src/elements/ai.ts +16 -0
  178. package/src/elements/clock/declare.ts +26 -1
  179. package/src/elements/clock/health.test.ts +43 -0
  180. package/src/elements/clock/runtime.ts +55 -2
  181. package/src/elements/clock/schedule.ts +71 -142
  182. package/src/elements/clock.test.ts +9 -40
  183. package/src/elements/clock.ts +6 -1
  184. package/src/elements/gate/boot.ts +5 -5
  185. package/src/elements/gate/declare.ts +36 -4
  186. package/src/elements/gate/flatten.ts +1 -1
  187. package/src/elements/gate/runtime.ts +1 -1
  188. package/src/elements/index.ts +2 -0
  189. package/src/elements/signal/declare.ts +2 -1
  190. package/src/elements/signal/runtime.ts +16 -1
  191. package/src/elements/signal.ts +1 -0
  192. package/src/elements/store/cache.test.ts +2 -0
  193. package/src/elements/store/cache.ts +3 -3
  194. package/src/elements/store/declare.ts +9 -6
  195. package/src/elements/store/emit-drizzle.ts +44 -8
  196. package/src/elements/store/resource.test.ts +1 -1
  197. package/src/elements/store/rls-identity.test.ts +95 -0
  198. package/src/elements/store/rls-identity.ts +116 -0
  199. package/src/elements/store/runtime.ts +4 -0
  200. package/src/elements/store/schema-decl.test.ts +37 -0
  201. package/src/elements/store/schema-decl.ts +161 -0
  202. package/src/elements/store/seed.ts +1 -1
  203. package/src/elements/store/sql-condition.test.ts +12 -0
  204. package/src/elements/store/sql-condition.ts +26 -2
  205. package/src/elements/store/sql-rls-isolation.test.ts +165 -0
  206. package/src/elements/store/sql-rls-stamp.test.ts +117 -0
  207. package/src/elements/store/sql-session.ts +81 -3
  208. package/src/elements/store/upsert-app.test.ts +1 -1
  209. package/src/elements/store.ts +14 -0
  210. package/src/elements/vault/builtin-adapter.ts +17 -0
  211. package/src/full.ts +2 -0
  212. package/src/http.ts +20 -3
  213. package/src/index.ts +14 -1
  214. package/src/kernel/adopt-barrel-fresh.test.ts +1 -1
  215. package/src/kernel/adopt-routes.ts +30 -2
  216. package/src/kernel/app.ts +227 -77
  217. package/src/kernel/auto-cache.test.ts +8 -8
  218. package/src/kernel/auto-registry.test.ts +54 -9
  219. package/src/kernel/boot-bind/ai.ts +24 -0
  220. package/src/kernel/boot-bind/clock.ts +2 -0
  221. package/src/kernel/boot.test.ts +4 -4
  222. package/src/kernel/boot.ts +5 -3
  223. package/src/kernel/effects-stamping.test.ts +3 -3
  224. package/src/kernel/element-registries.ts +15 -5
  225. package/src/kernel/errors.ts +24 -0
  226. package/src/kernel/flow-units.ts +64 -0
  227. package/src/kernel/flow.ts +29 -17
  228. package/src/kernel/fx-dead-letters.test.ts +77 -0
  229. package/src/kernel/fx.test.ts +22 -0
  230. package/src/kernel/fx.ts +135 -6
  231. package/src/kernel/horizontal-child.ts +4 -4
  232. package/src/kernel/http-path-pending.ts +20 -0
  233. package/src/kernel/http-resource.ts +4 -0
  234. package/src/kernel/http-stream.test.ts +174 -0
  235. package/src/kernel/index.ts +2 -0
  236. package/src/kernel/pipeline.test.ts +2 -2
  237. package/src/kernel/plugin-elements.test.ts +1 -1
  238. package/src/kernel/plugin-needs.test.ts +1 -1
  239. package/src/kernel/ready.test.ts +2 -2
  240. package/src/kernel/resource-mount.test.ts +14 -0
  241. package/src/kernel/run-duration.test.ts +2 -2
  242. package/src/kernel/stamp-http.test.ts +79 -0
  243. package/src/kernel/stamp-http.ts +75 -0
  244. package/src/kernel/triggers.ts +52 -27
  245. package/src/manifest/mcp-ref.ts +88 -0
  246. package/src/manifest/types.ts +41 -2
  247. package/src/manifest/validate.test.ts +20 -0
  248. package/src/mcp/docs-server.ts +1 -1
  249. package/src/mcp/server.ts +1 -1
  250. package/src/plugins/compression.test.ts +21 -0
  251. package/src/plugins/compression.ts +1 -0
  252. package/src/release/measure.ts +2 -2
  253. package/src/runtime/bun.ts +41 -4
  254. package/src/runtime/json-code-block.test.ts +2 -2
  255. package/src/runtime/serve.test.ts +4 -4
  256. package/src/term.test.ts +52 -4
  257. package/src/term.ts +125 -17
  258. package/src/test/reset-element-registries.ts +10 -3
  259. package/src/cli/ask-seed.test.ts +0 -96
  260. package/src/cli/ask-seed.ts +0 -82
  261. package/src/console/ui-next/dist/assets/cache-glyph-CLPBqZeb.js +0 -1
  262. package/src/console/ui-next/dist/assets/collapsible-DYb0xU8C.js +0 -1
  263. package/src/console/ui-next/dist/assets/flows-page-Dg8CTE29.js +0 -1
  264. package/src/console/ui-next/dist/assets/http-method-BJ92Z_ke.js +0 -1
  265. package/src/console/ui-next/dist/assets/index-Bp-R7jtM.js +0 -66
  266. package/src/console/ui-next/dist/assets/index-D4Ldtj79.css +0 -2
  267. package/src/console/ui-next/dist/assets/observability-page-DAnpEaq1.js +0 -4
  268. package/src/console/ui-next/dist/assets/query-gate-parts-1m8m1iNp.js +0 -2
  269. package/src/console/ui-next/dist/assets/react-B1ML8gxg.js +0 -1
  270. package/src/console/ui-next/dist/assets/replica-lag-C8_BRt2x.js +0 -18
  271. package/src/console/ui-next/dist/assets/sheet-form-D-ata7jy.js +0 -1
  272. package/src/console/ui-next/dist/assets/shortcut-keys-DO4IsVqv.js +0 -1
  273. package/src/console/ui-next/dist/assets/skeleton-CL_X0GCj.js +0 -1
  274. package/src/console/ui-next/dist/assets/store-page-v3LXdYpr.js +0 -45
  275. package/src/console/ui-next/dist/assets/trace-detail-sheet-D16lWQMt.js +0 -2
  276. package/src/console/ui-next/dist/assets/units-page-BtQ0bqMe.js +0 -1
  277. package/src/console/ui-next/dist/assets/vault-page-Ca-MvcmJ.js +0 -2
@@ -89,6 +89,7 @@ export async function openOpenaiCompatible(options: AiOpenOptions = {}): Promise
89
89
  const model = options.model ?? "gpt-4o-mini";
90
90
  const fetchFn = options.fetch ?? globalThis.fetch;
91
91
  const extraHeaders = options.headers;
92
+ preconnectFetch(fetchFn, baseUrl);
92
93
 
93
94
  return {
94
95
  driverId: "openai-compatible",
@@ -162,10 +163,7 @@ export async function openOpenaiCompatible(options: AiOpenOptions = {}): Promise
162
163
  const msg = raw.error?.message ?? `openai-compatible HTTP ${res.status}`;
163
164
  throwHttp(`openai-compatible: ${msg}`, res.status);
164
165
  }
165
- if (!res.body) {
166
- throw new Error("openai-compatible: stream response has no body");
167
- }
168
- yield* readOpenaiSse(res.body, opts.signal);
166
+ yield* readOpenaiSse(res, opts.signal);
169
167
  },
170
168
  async embed(opts: AiEmbedOptions): Promise<AiEmbedResult> {
171
169
  const resolvedModel = opts.model ?? model;
@@ -262,57 +260,70 @@ function parseToolCalls(
262
260
  /**
263
261
  * Parse OpenAI SSE chat.completion.chunk stream.
264
262
  *
265
- * @param body - Response body
263
+ * @param res - Streaming response
266
264
  * @param signal - Optional abort
267
265
  */
268
- async function* readOpenaiSse(
269
- body: ReadableStream<Uint8Array>,
270
- signal?: AbortSignal,
271
- ): AsyncGenerator<AiStreamChunk> {
272
- const reader = body.getReader();
273
- const decoder = new TextDecoder();
266
+ async function* readOpenaiSse(res: Response, signal?: AbortSignal): AsyncGenerator<AiStreamChunk> {
274
267
  let buffer = "";
275
- try {
276
- while (true) {
277
- if (signal?.aborted) {
278
- throw abortAsError(signal.reason);
268
+ for await (const piece of responseTextStream(res)) {
269
+ if (signal?.aborted) throw abortAsError(signal.reason);
270
+ buffer += piece;
271
+ const lines = buffer.split("\n");
272
+ buffer = lines.pop() ?? "";
273
+ for (const line of lines) {
274
+ const trimmed = line.trim();
275
+ if (!trimmed.startsWith("data:")) continue;
276
+ const data = trimmed.slice(5).trim();
277
+ if (data === "[DONE]") {
278
+ yield { text: "", done: true };
279
+ return;
279
280
  }
280
- const { done, value } = await reader.read();
281
- if (done) break;
282
- buffer += decoder.decode(value, { stream: true });
283
- const lines = buffer.split("\n");
284
- buffer = lines.pop() ?? "";
285
- for (const line of lines) {
286
- const trimmed = line.trim();
287
- if (!trimmed.startsWith("data:")) continue;
288
- const data = trimmed.slice(5).trim();
289
- if (data === "[DONE]") {
281
+ try {
282
+ const chunk = JSON.parse(data) as {
283
+ choices?: readonly {
284
+ delta?: { content?: string | null };
285
+ finish_reason?: string | null;
286
+ }[];
287
+ };
288
+ const delta = chunk.choices?.[0]?.delta?.content;
289
+ if (typeof delta === "string" && delta.length > 0) {
290
+ yield { text: delta };
291
+ }
292
+ if (chunk.choices?.[0]?.finish_reason) {
290
293
  yield { text: "", done: true };
291
294
  return;
292
295
  }
293
- try {
294
- const chunk = JSON.parse(data) as {
295
- choices?: readonly {
296
- delta?: { content?: string | null };
297
- finish_reason?: string | null;
298
- }[];
299
- };
300
- const delta = chunk.choices?.[0]?.delta?.content;
301
- if (typeof delta === "string" && delta.length > 0) {
302
- yield { text: delta };
303
- }
304
- if (chunk.choices?.[0]?.finish_reason) {
305
- yield { text: "", done: true };
306
- return;
307
- }
308
- } catch {
309
- // ignore malformed SSE lines
310
- }
296
+ } catch {
297
+ // ignore malformed SSE lines
311
298
  }
312
299
  }
313
- yield { text: "", done: true };
314
- } finally {
315
- reader.releaseLock();
300
+ }
301
+ yield { text: "", done: true };
302
+ }
303
+
304
+ /**
305
+ * UTF-8 text chunks from a Response (`textStream` on Bun ≥1.4).
306
+ *
307
+ * @param res - HTTP response
308
+ */
309
+ function responseTextStream(res: Response): AsyncIterable<string> {
310
+ const stream = (res as Response & { textStream?: () => AsyncIterable<string> }).textStream;
311
+ if (typeof stream !== "function") {
312
+ throw new Error("openai-compatible: Response.textStream is required (Bun >= 1.4.0)");
313
+ }
314
+ return stream.call(res);
315
+ }
316
+
317
+ /**
318
+ * Warm DNS+TCP+TLS for a cloud origin. No-op when `fetch` is a test stub.
319
+ *
320
+ * @param fetchFn - Fetch implementation
321
+ * @param url - Provider base URL
322
+ */
323
+ function preconnectFetch(fetchFn: typeof fetch, url: string): void {
324
+ const preconnect = (fetchFn as { preconnect?: (href: string) => void }).preconnect;
325
+ if (typeof preconnect === "function") {
326
+ preconnect(url);
316
327
  }
317
328
  }
318
329
 
@@ -43,12 +43,14 @@ describe("anthropic driver", () => {
43
43
  model: "claude-test",
44
44
  fetch: fetchFn,
45
45
  });
46
+ const ac = new AbortController();
46
47
  const result = await client.complete({
47
48
  messages: [
48
49
  { role: "system", content: "be brief" },
49
50
  { role: "user", content: "hi" },
50
51
  ],
51
52
  maxTokens: 64,
53
+ signal: ac.signal,
52
54
  });
53
55
 
54
56
  expect(result.driverId).toBe("anthropic");
@@ -62,6 +64,7 @@ describe("anthropic driver", () => {
62
64
  };
63
65
  expect(body.system).toBe("be brief");
64
66
  expect(body.messages).toEqual([{ role: "user", content: "hi" } as { role: string }]);
67
+ expect(calls[0]!.init?.signal).toBe(ac.signal);
65
68
  });
66
69
 
67
70
  test("HTTP error surfaces provider message", async () => {
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Bun native-client completeness — gaps closed vs platform limitations.
3
3
  *
4
- * Checked against Bun 1.3.14 (runtime + bun-types). Do not invent custom
5
- * protocol clients where Bun still lacks an API.
4
+ * Checked against Bun 1.4 (runtime). Do not invent custom protocol clients
5
+ * where Bun still lacks an API.
6
6
  */
7
7
 
8
8
  import { describe, expect, test } from "bun:test";
@@ -46,7 +46,7 @@ export function bunNativeCompletenessReport(): readonly BunNativeGapRow[] {
46
46
  status: hasTypedEval ? "closed" : "bun_limitation",
47
47
  note: hasTypedEval
48
48
  ? "typed eval bound"
49
- : "Bun 1.3.14 has no typed eval driver uses send(EVAL)",
49
+ : "Bun.RedisClient.eval unavailablewould keep send(EVAL)",
50
50
  },
51
51
  {
52
52
  surface: "redis signal",
@@ -63,7 +63,7 @@ export function bunNativeCompletenessReport(): readonly BunNativeGapRow[] {
63
63
  status: hasTypedXadd ? "closed" : "bun_limitation",
64
64
  note: hasTypedXadd
65
65
  ? "typed stream helpers bound"
66
- : "Bun 1.3.14 has no typed xadd — Streams use send (native, not a custom client)",
66
+ : "Bun.RedisClient.xadd unavailable — Streams would keep send",
67
67
  },
68
68
  {
69
69
  surface: "redis signal",
@@ -95,23 +95,21 @@ export function bunNativeCompletenessReport(): readonly BunNativeGapRow[] {
95
95
  }
96
96
 
97
97
  describe("Bun native client completeness", () => {
98
- test("report table is exact and matches runtime Bun 1.3.x", () => {
98
+ test("report table is exact and matches runtime Bun 1.4", () => {
99
99
  const rows = bunNativeCompletenessReport();
100
100
  expect(rows.length).toBe(8);
101
101
 
102
102
  const closed = rows.filter((r) => r.status === "closed").map((r) => r.capability);
103
103
  const limited = rows.filter((r) => r.status === "bun_limitation").map((r) => r.capability);
104
104
 
105
- // Closed on Bun 1.3.14
106
105
  expect(closed).toContain("SCAN (typed)");
106
+ expect(closed).toContain("EVAL (typed)");
107
107
  expect(closed).toContain("native Bun bind (publish/subscribe)");
108
+ expect(closed).toContain("XADD / XREADGROUP (typed)");
108
109
  expect(closed).toContain("send escape hatch");
109
110
  expect(closed).toContain("Bun.SQL.unsafe query/exec");
110
111
  expect(closed).toContain("Bun.S3Client file/list");
111
112
 
112
- // Genuine Bun platform limitations (do not invent workarounds)
113
- expect(limited).toContain("EVAL (typed)");
114
- expect(limited).toContain("XADD / XREADGROUP (typed)");
115
113
  expect(limited).toContain("LISTEN / NOTIFY on Bun.SQL");
116
114
  });
117
115
 
@@ -7,7 +7,13 @@
7
7
  */
8
8
 
9
9
  import type { CronRow, CronStatus, CronStore } from "../elements/clock/reconcile.ts";
10
- import { toPostgresParams } from "./postgres.ts";
10
+ import {
11
+ resolvePostgresUrl,
12
+ sharedPostgresClient,
13
+ toPostgresParams,
14
+ withPinnedPostgres,
15
+ type PostgresClientLike,
16
+ } from "./postgres.ts";
11
17
 
12
18
  /** Row shape in `oke_crons` (lease columns mirror `oke_signal_messages`). */
13
19
  interface CronDbRow {
@@ -69,16 +75,16 @@ export interface BunCronClient {
69
75
  close?(options?: { timeout?: number }): Promise<void>;
70
76
  }
71
77
 
72
- function wrapBunClient(client: BunCronClient): PostgresCronSql {
78
+ function wrapBunClient(client: PostgresClientLike): PostgresCronSql {
73
79
  const api: PostgresCronSql = {
74
80
  async query(sql, params = []) {
75
- const pg = toPostgresParams(sql);
81
+ const pg = toPostgresParams(sql, params);
76
82
  const result = await client.unsafe(pg, [...params]);
77
83
  if (Array.isArray(result)) return result as Record<string, unknown>[];
78
84
  return Array.from(result as ArrayLike<Record<string, unknown>>);
79
85
  },
80
86
  async exec(sql, params = []) {
81
- const pg = toPostgresParams(sql);
87
+ const pg = toPostgresParams(sql, params);
82
88
  const result = await client.unsafe(pg, [...params]);
83
89
  if (
84
90
  result &&
@@ -92,7 +98,7 @@ function wrapBunClient(client: BunCronClient): PostgresCronSql {
92
98
  return { changes: 0 };
93
99
  },
94
100
  async begin(fn) {
95
- return client.begin(async (tx) => fn(wrapBunClient(tx)));
101
+ return withPinnedPostgres(client, (tx) => fn(wrapBunClient(tx)));
96
102
  },
97
103
  async close() {
98
104
  await client.close?.();
@@ -365,10 +371,8 @@ export async function createPostgresCronStore(
365
371
  const sql =
366
372
  options.sql ??
367
373
  wrapBunClient(
368
- options.client ??
369
- (new Bun.SQL(
370
- options.url ?? process.env.DATABASE_URL ?? "postgres://localhost:5432/oke",
371
- ) as unknown as BunCronClient),
374
+ (options.client ??
375
+ sharedPostgresClient(resolvePostgresUrl(options.url))) as PostgresClientLike,
372
376
  );
373
377
 
374
378
  await ensureSchema(sql);
@@ -7,7 +7,7 @@
7
7
 
8
8
  import type { ConfigEnv } from "../config/index.ts";
9
9
  import type { InstanceRow, InstanceStore } from "../kernel/instances.ts";
10
- import { toPostgresParams } from "./postgres.ts";
10
+ import { resolvePostgresUrl, sharedPostgresClient, toPostgresParams } from "./postgres.ts";
11
11
 
12
12
  /** Minimal SQL surface for the postgres instance store. */
13
13
  export interface PostgresInstanceSql {
@@ -40,13 +40,13 @@ const UPSERT_SQL = `INSERT INTO oke_instances (id, started_at, heartbeat_at, lea
40
40
  function wrapBunClient(client: BunInstanceClient): PostgresInstanceSql {
41
41
  return {
42
42
  async query(sql, params = []) {
43
- const pg = toPostgresParams(sql);
43
+ const pg = toPostgresParams(sql, params);
44
44
  const result = await client.unsafe(pg, [...params]);
45
45
  if (Array.isArray(result)) return result as Record<string, unknown>[];
46
46
  return Array.from(result as ArrayLike<Record<string, unknown>>);
47
47
  },
48
48
  async exec(sql, params = []) {
49
- const pg = toPostgresParams(sql);
49
+ const pg = toPostgresParams(sql, params);
50
50
  const result = await client.unsafe(pg, [...params]);
51
51
  if (
52
52
  result &&
@@ -109,9 +109,7 @@ export async function createPostgresInstanceStore(
109
109
  options.sql ??
110
110
  wrapBunClient(
111
111
  options.client ??
112
- (new Bun.SQL(
113
- options.url ?? process.env.DATABASE_URL ?? "postgres://localhost:5432/oke",
114
- ) as unknown as BunInstanceClient),
112
+ (sharedPostgresClient(resolvePostgresUrl(options.url)) as unknown as BunInstanceClient),
115
113
  );
116
114
 
117
115
  await ensureSchema(sql);
@@ -14,7 +14,13 @@ import {
14
14
  type JournalRun,
15
15
  type JournalStore,
16
16
  } from "../kernel/journal.ts";
17
- import { toPostgresParams } from "./postgres.ts";
17
+ import {
18
+ resolvePostgresUrl,
19
+ sharedPostgresClient,
20
+ toPostgresParams,
21
+ withPinnedPostgres,
22
+ type PostgresClientLike,
23
+ } from "./postgres.ts";
18
24
 
19
25
  /** Row shape in `oke_journal_runs` (lease columns mirror `oke_crons`). */
20
26
  interface JournalDbRow {
@@ -87,16 +93,16 @@ export interface BunJournalClient {
87
93
  close?(options?: { timeout?: number }): Promise<void>;
88
94
  }
89
95
 
90
- function wrapBunClient(client: BunJournalClient): PostgresJournalSql {
96
+ function wrapBunClient(client: PostgresClientLike): PostgresJournalSql {
91
97
  const api: PostgresJournalSql = {
92
98
  async query(sql, params = []) {
93
- const pg = toPostgresParams(sql);
99
+ const pg = toPostgresParams(sql, params);
94
100
  const result = await client.unsafe(pg, [...params]);
95
101
  if (Array.isArray(result)) return result as Record<string, unknown>[];
96
102
  return Array.from(result as ArrayLike<Record<string, unknown>>);
97
103
  },
98
104
  async exec(sql, params = []) {
99
- const pg = toPostgresParams(sql);
105
+ const pg = toPostgresParams(sql, params);
100
106
  const result = await client.unsafe(pg, [...params]);
101
107
  if (
102
108
  result &&
@@ -110,7 +116,7 @@ function wrapBunClient(client: BunJournalClient): PostgresJournalSql {
110
116
  return { changes: 0 };
111
117
  },
112
118
  async begin(fn) {
113
- return client.begin(async (tx) => fn(wrapBunClient(tx)));
119
+ return withPinnedPostgres(client, (tx) => fn(wrapBunClient(tx)));
114
120
  },
115
121
  async close() {
116
122
  await client.close?.();
@@ -437,10 +443,8 @@ export async function createPostgresJournalStore(
437
443
  const sql =
438
444
  options.sql ??
439
445
  wrapBunClient(
440
- options.client ??
441
- (new Bun.SQL(
442
- options.url ?? process.env.DATABASE_URL ?? "postgres://localhost:5432/oke",
443
- ) as unknown as BunJournalClient),
446
+ (options.client ??
447
+ sharedPostgresClient(resolvePostgresUrl(options.url))) as PostgresClientLike,
444
448
  );
445
449
 
446
450
  await ensureSchema(sql);
@@ -2,7 +2,13 @@ import { describe, expect, test } from "bun:test";
2
2
  import {
3
3
  buildAlterPolicySql,
4
4
  buildCreatePolicySql,
5
+ buildRlsIdentityPreludeSql,
6
+ emitPgPolicySource,
7
+ formatPolicyRole,
8
+ OKE_RLS_HELPER_STATEMENTS,
9
+ OKE_RLS_ROLE,
5
10
  parseSqlPolicySpec,
11
+ rlsScopesJson,
6
12
  sqlPolicyRowId,
7
13
  } from "./pg-rls.ts";
8
14
 
@@ -36,6 +42,22 @@ describe("parseSqlPolicySpec", () => {
36
42
  expect(spec.withCheck).toBe("true");
37
43
  });
38
44
 
45
+ test("accepts Drizzle as/to/for aliases", () => {
46
+ const spec = parseSqlPolicySpec({
47
+ name: "gate_read",
48
+ table: "bookings",
49
+ for: "select",
50
+ as: "permissive",
51
+ to: "current_user",
52
+ using: "oke.gate() = 'member'",
53
+ });
54
+ expect(spec.command).toBe("SELECT");
55
+ expect(spec.behavior).toBe("PERMISSIVE");
56
+ expect(spec.roles).toEqual(["current_user"]);
57
+ expect(buildCreatePolicySql(spec)).toContain("TO current_user");
58
+ expect(buildCreatePolicySql(spec)).not.toContain('"current_user"');
59
+ });
60
+
39
61
  test("rejects stacked statements in USING", () => {
40
62
  expect(() =>
41
63
  parseSqlPolicySpec({
@@ -69,3 +91,68 @@ describe("buildAlterPolicySql", () => {
69
91
  ).toThrow("invalid USING");
70
92
  });
71
93
  });
94
+
95
+ describe("formatPolicyRole", () => {
96
+ test("leaves Drizzle special roles unquoted", () => {
97
+ expect(formatPolicyRole("public")).toBe("public");
98
+ expect(formatPolicyRole("current_user")).toBe("current_user");
99
+ expect(formatPolicyRole("member")).toBe('"member"');
100
+ });
101
+ });
102
+
103
+ describe("emitPgPolicySource", () => {
104
+ test("emits drizzle pgPolicy with oke helpers", () => {
105
+ const spec = parseSqlPolicySpec({
106
+ name: "gate_member_select",
107
+ table: "bookings",
108
+ command: "SELECT",
109
+ using: "oke.gate() = 'member'",
110
+ });
111
+ expect(emitPgPolicySource(spec)).toContain('pgPolicy("gate_member_select"');
112
+ expect(emitPgPolicySource(spec)).toContain('for: "select"');
113
+ expect(emitPgPolicySource(spec)).toContain("oke.gate() = 'member'");
114
+ });
115
+ });
116
+
117
+ describe("rlsScopesJson", () => {
118
+ test("sorts unique scopes", () => {
119
+ expect(rlsScopesJson(["booking:create", "member", "member"])).toBe(
120
+ '["booking:create","member"]',
121
+ );
122
+ });
123
+ });
124
+
125
+ describe("buildRlsIdentityPreludeSql", () => {
126
+ test("emits one statement per exec — never a multi-command batch", () => {
127
+ const stmts = buildRlsIdentityPreludeSql({
128
+ gate: "member",
129
+ userId: "alice",
130
+ scopes: ["member"],
131
+ });
132
+ expect(stmts.map((s) => s.sql)).toEqual([
133
+ `SET LOCAL ROLE ${OKE_RLS_ROLE}`,
134
+ "SET LOCAL row_security = on",
135
+ "SELECT set_config('oke.gate', ?, true), set_config('oke.user', ?, true), set_config('oke.scopes', ?, true)",
136
+ ]);
137
+ for (const stmt of stmts) {
138
+ expect(stmt.sql.includes(";")).toBe(false);
139
+ }
140
+ expect(stmts[2]?.params).toEqual(["member", "alice", '["member"]']);
141
+ });
142
+ });
143
+
144
+ describe("OKE_RLS_HELPER_STATEMENTS", () => {
145
+ test("has_scope uses jsonb_exists so ? is not a placeholder", () => {
146
+ const hasScope = OKE_RLS_HELPER_STATEMENTS.find((s) => s.includes("oke.has_scope"));
147
+ expect(hasScope).toBeDefined();
148
+ expect(hasScope).toContain("jsonb_exists(");
149
+ expect(hasScope).not.toMatch(/jsonb\s+\?/);
150
+ });
151
+
152
+ test("pins search_path to public so schema oke is not the table home", () => {
153
+ expect(OKE_RLS_HELPER_STATEMENTS).toContain("SET search_path TO public, oke");
154
+ expect(
155
+ OKE_RLS_HELPER_STATEMENTS.some((s) => s.includes("ALTER TABLE oke.%I SET SCHEMA public")),
156
+ ).toBe(true);
157
+ });
158
+ });