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
@@ -0,0 +1,75 @@
1
+ /**
2
+ * In-place HTTP path + Flow name stamps.
3
+ *
4
+ * `on()` already registered the trigger (possibly still on the pending-path
5
+ * sentinel). A wrapper that returned a new trigger would leave `listBindings()`
6
+ * on the sentinel — mutate the existing object.
7
+ */
8
+
9
+ import { isFlow, type AnyFlowDef, type FlowDef } from "./flow.ts";
10
+ import { isPendingHttpPath, type HttpPathPending } from "./http-path-pending.ts";
11
+ import type { HttpTrigger, Trigger } from "./triggers.ts";
12
+
13
+ /**
14
+ * Replace a pending HTTP path on {@link FlowDef} with the file-tree stamp.
15
+ *
16
+ * Explicit `http.get("/x")` wins — the pending sentinel is the only path
17
+ * this function overwrites. Returns the same object with a tighter trigger
18
+ * type when the original path was pending.
19
+ *
20
+ * @param flow - Bound flow (already passed through `on()`)
21
+ * @param path - Inferred URL (`/notes/:id`)
22
+ */
23
+ export function stampHttpPath<F extends AnyFlowDef, P extends string>(
24
+ flow: F,
25
+ path: P,
26
+ ): StampHttpPath<F, P> {
27
+ if (!isFlow(flow)) return flow as StampHttpPath<F, P>;
28
+ const apply = (trigger: Trigger): void => {
29
+ if (trigger.kind !== "http") return;
30
+ if (!isPendingHttpPath(trigger.path)) return;
31
+ (trigger as { path: string }).path = path;
32
+ };
33
+ if (flow.$trigger) apply(flow.$trigger);
34
+ for (const trigger of flow.triggers) apply(trigger);
35
+ return flow as StampHttpPath<F, P>;
36
+ }
37
+
38
+ /**
39
+ * Stamp `unit.export` onto a nameless (or `flow_*`) Flow.
40
+ *
41
+ * Explicit `flow("notes.get")` wins. Fills `unit` when missing.
42
+ *
43
+ * @param flow - Flow definition
44
+ * @param name - `notes.get`
45
+ */
46
+ export function stampFlowName<F extends AnyFlowDef>(flow: F, name: string): F {
47
+ if (!isFlow(flow)) return flow;
48
+ const f = flow as { name: string; unit: string | undefined };
49
+ if (!f.name || f.name.startsWith("flow_")) {
50
+ f.name = name;
51
+ }
52
+ if (!f.unit) {
53
+ const resolved = f.name || name;
54
+ const dot = resolved.indexOf(".");
55
+ f.unit = dot > 0 ? resolved.slice(0, dot) : resolved;
56
+ }
57
+ return flow;
58
+ }
59
+
60
+ /**
61
+ * Type-level path replacement — only when the bound trigger is still pending.
62
+ *
63
+ * @typeParam F - Flow definition
64
+ * @typeParam P - Stamped path
65
+ */
66
+ export type StampHttpPath<F, P extends string> =
67
+ F extends FlowDef<infer I, infer O, infer E, infer D, infer T>
68
+ ? T extends HttpTrigger<infer M, infer Cur>
69
+ ? [Cur] extends [HttpPathPending]
70
+ ? FlowDef<I, O, E, D, HttpTrigger<M, P>>
71
+ : F
72
+ : F
73
+ : F;
74
+
75
+ export { HTTP_PATH_PENDING, isPendingHttpPath } from "./http-path-pending.ts";
@@ -7,8 +7,11 @@
7
7
 
8
8
  import { flattenGateArgs, GATE_PUBLIC_NAME, type GateAllDecl } from "../elements/gate/flatten.ts";
9
9
  import type { NamedRef } from "./fx.ts";
10
+ import { HTTP_PATH_PENDING, type HttpPathPending } from "./http-path-pending.ts";
10
11
  import { lazyRequire } from "./lazy-require.ts";
11
12
 
13
+ export { HTTP_PATH_PENDING, isPendingHttpPath, type HttpPathPending } from "./http-path-pending.ts";
14
+
12
15
  /** HTTP methods accepted by {@link http}. */
13
16
  export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS" | "HEAD" | "QUERY";
14
17
 
@@ -18,15 +21,8 @@ export type GateRef = NamedRef;
18
21
  /** One `.gate(...)` argument — a named ref, an `all` handle, or an array. */
19
22
  export type GateArg = GateRef | GateAllDecl | readonly GateArg[];
20
23
 
21
- /**
22
- * Attach function on an HTTP trigger / resource mount.
23
- * Call it with members, or use `.public` for the unauthenticated sentinel.
24
- */
25
- export interface GateAttach<T> {
26
- (...gates: GateArg[]): T;
27
- /** Attach {@link gate.public} — same as `.gate(gate.public)`. */
28
- readonly public: T;
29
- }
24
+ /** Attach gates on an HTTP trigger / resource mount. */
25
+ export type GateAttach<T> = (...gates: GateArg[]) => T;
30
26
 
31
27
  /**
32
28
  * HTTP trigger value. Method and path are literal type parameters so
@@ -44,9 +40,12 @@ export interface HttpTrigger<M extends HttpMethod = HttpMethod, P extends string
44
40
  readonly isLive: boolean;
45
41
  /**
46
42
  * Attach gates (registration order). `gate.all` handles and arrays flatten.
47
- * `.gate.public` is the unauthenticated sentinel.
48
43
  */
49
44
  readonly gate: GateAttach<HttpTrigger<M, P>>;
45
+ /**
46
+ * Attach the unauthenticated public sentinel.
47
+ */
48
+ public(): HttpTrigger<M, P>;
50
49
  /**
51
50
  * Mark the HTTP trigger as live (push result to subscribers).
52
51
  */
@@ -93,7 +92,7 @@ export type Trigger = HttpTrigger | EveryTrigger | SignalAsTrigger | CdcTrigger
93
92
  export type TriggerKind = Trigger["kind"];
94
93
 
95
94
  /**
96
- * Callable `.gate(...)` plus `.gate.public` on a trigger or resource mount.
95
+ * Callable `.gate(...)` on a trigger or resource mount.
97
96
  *
98
97
  * @param apply - Rebuild the host with the flattened gate list
99
98
  * @param current - Gates already attached
@@ -102,19 +101,11 @@ export function createGateAttach<T>(
102
101
  apply: (next: readonly GateRef[]) => T,
103
102
  current: readonly GateRef[],
104
103
  ): GateAttach<T> {
105
- const attach = ((...next: GateArg[]) =>
106
- apply([...current, ...flattenGateArgs(next)])) as GateAttach<T>;
107
- Object.defineProperty(attach, "public", {
108
- get() {
109
- return apply([...current, GATE_PUBLIC_NAME]);
110
- },
111
- enumerable: true,
112
- });
113
- return attach;
104
+ return (...next: GateArg[]) => apply([...current, ...flattenGateArgs(next)]);
114
105
  }
115
106
 
116
107
  /**
117
- * Build an HTTP trigger with `.gate` / `.live`.
108
+ * Build an HTTP trigger with `.gate` / `.public` / `.live`.
118
109
  *
119
110
  * @param method - HTTP verb
120
111
  * @param path - Route path
@@ -134,6 +125,9 @@ export function createHttpTrigger<M extends HttpMethod, P extends string>(
134
125
  gates,
135
126
  isLive,
136
127
  gate: createGateAttach((next) => createHttpTrigger(method, path, next, isLive), gates),
128
+ public() {
129
+ return createHttpTrigger(method, path, [...gates, GATE_PUBLIC_NAME], isLive);
130
+ },
137
131
  live() {
138
132
  return createHttpTrigger(method, path, gates, true);
139
133
  },
@@ -156,7 +150,7 @@ export interface ResourceFlowBag {
156
150
  /**
157
151
  * A mounted resource — the single argument to the `on(http.resource(…))`
158
152
  * overload. Branded so `on` can tell it apart from a plain trigger.
159
- * Same chain as {@link HttpTrigger}: `.gate(...)` / `.live()`.
153
+ * Same chain as {@link HttpTrigger}: `.gate(...)` / `.public()` / `.live()`.
160
154
  */
161
155
  export interface ResourceMount {
162
156
  readonly [resourceMountBrand]: true;
@@ -166,9 +160,12 @@ export interface ResourceMount {
166
160
  readonly isLive: boolean;
167
161
  /**
168
162
  * Attach gates to every verb (registration order). `gate.all` and arrays flatten.
169
- * `.gate.public` is the unauthenticated sentinel.
170
163
  */
171
164
  readonly gate: GateAttach<ResourceMount>;
165
+ /**
166
+ * Attach the unauthenticated public sentinel to every verb.
167
+ */
168
+ public(): ResourceMount;
172
169
  /**
173
170
  * Mark list and get as live (GET mounts). Mutations stay request/response.
174
171
  */
@@ -200,34 +197,55 @@ function loadHttpResource(): typeof import("./http-resource.ts") {
200
197
  * types for param extraction.
201
198
  */
202
199
  export interface HttpTriggerNamespace {
200
+ /**
201
+ * Pathless — file-tree stamp fills the URL. Unresolved sentinel fails boot.
202
+ */
203
+ get(): HttpTrigger<"GET", HttpPathPending>;
203
204
  /**
204
205
  * @param path - Route path (`/:id` params supported)
205
206
  */
206
207
  get<P extends string>(path: P): HttpTrigger<"GET", P>;
208
+ /** Pathless — file-tree stamp fills the URL. */
209
+ post(): HttpTrigger<"POST", HttpPathPending>;
207
210
  /**
208
211
  * @param path - Route path
209
212
  */
210
213
  post<P extends string>(path: P): HttpTrigger<"POST", P>;
214
+ /** Pathless — file-tree stamp fills the URL. */
215
+ put(): HttpTrigger<"PUT", HttpPathPending>;
211
216
  /**
212
217
  * @param path - Route path
213
218
  */
214
219
  put<P extends string>(path: P): HttpTrigger<"PUT", P>;
220
+ /** Pathless — file-tree stamp fills the URL. */
221
+ patch(): HttpTrigger<"PATCH", HttpPathPending>;
215
222
  /**
216
223
  * @param path - Route path
217
224
  */
218
225
  patch<P extends string>(path: P): HttpTrigger<"PATCH", P>;
226
+ /** Pathless — file-tree stamp fills the URL. */
227
+ delete(): HttpTrigger<"DELETE", HttpPathPending>;
219
228
  /**
220
229
  * @param path - Route path
221
230
  */
222
231
  delete<P extends string>(path: P): HttpTrigger<"DELETE", P>;
232
+ /** Pathless — file-tree stamp fills the URL. */
233
+ options(): HttpTrigger<"OPTIONS", HttpPathPending>;
223
234
  /**
224
235
  * @param path - Route path
225
236
  */
226
237
  options<P extends string>(path: P): HttpTrigger<"OPTIONS", P>;
238
+ /** Pathless — file-tree stamp fills the URL. */
239
+ head(): HttpTrigger<"HEAD", HttpPathPending>;
227
240
  /**
228
241
  * @param path - Route path
229
242
  */
230
243
  head<P extends string>(path: P): HttpTrigger<"HEAD", P>;
244
+ /**
245
+ * Safe, idempotent read that carries a JSON body (RFC 10008).
246
+ * Pathless — file-tree stamp fills the URL.
247
+ */
248
+ query(): HttpTrigger<"QUERY", HttpPathPending>;
231
249
  /**
232
250
  * Safe, idempotent read that carries a JSON body (RFC 10008).
233
251
  *
@@ -236,8 +254,8 @@ export interface HttpTriggerNamespace {
236
254
  query<P extends string>(path: P): HttpTrigger<"QUERY", P>;
237
255
  /**
238
256
  * Mount a CRUD resource (list/create on `path`, get/update/remove on
239
- * `path/:id`) for the `on(http.resource(…))` overload. Chain `.gate(...)`
240
- * and `.live()` like {@link HttpTrigger}.
257
+ * `path/:id`) for the `on(http.resource(…))` overload. Chain `.gate(...)`,
258
+ * `.public()`, and `.live()` like {@link HttpTrigger}.
241
259
  */
242
260
  resource<P extends string>(path: P, ops: ResourceFlowBag): ResourceMount;
243
261
  }
@@ -245,8 +263,15 @@ export interface HttpTriggerNamespace {
245
263
  /** Bind an HTTP verb constructor (`http.get`, `http.query`, …). */
246
264
  function httpVerb<M extends HttpMethod>(
247
265
  method: M,
248
- ): <P extends string>(path: P) => HttpTrigger<M, P> {
249
- return (path) => createHttpTrigger(method, path);
266
+ ): {
267
+ (): HttpTrigger<M, HttpPathPending>;
268
+ <P extends string>(path: P): HttpTrigger<M, P>;
269
+ } {
270
+ return ((path?: string) =>
271
+ createHttpTrigger(method, path === undefined ? HTTP_PATH_PENDING : path)) as {
272
+ (): HttpTrigger<M, HttpPathPending>;
273
+ <P extends string>(path: P): HttpTrigger<M, P>;
274
+ };
250
275
  }
251
276
 
252
277
  /**
@@ -0,0 +1,88 @@
1
+ /**
2
+ * MCP tool resource refs — `mcp:<server>/<tool>` (capability / Manifest)
3
+ * and `<server>__<tool>` (provider-safe model tool name).
4
+ */
5
+
6
+ /** Capability / Manifest ref for one declared MCP tool. */
7
+ export type McpToolRef = `mcp:${string}/${string}`;
8
+
9
+ /** Parsed `mcp:<server>/<tool>` ref. */
10
+ export interface ParsedMcpToolRef {
11
+ readonly server: string;
12
+ readonly tool: string;
13
+ }
14
+
15
+ /**
16
+ * Build a capability ref.
17
+ *
18
+ * @param server - Declared MCP server name
19
+ * @param tool - Allowlisted tool name on that server
20
+ */
21
+ export function mcpToolRef(server: string, tool: string): McpToolRef {
22
+ return `mcp:${server}/${tool}`;
23
+ }
24
+
25
+ /**
26
+ * Parse `mcp:<server>/<tool>`. Null when the prefix or parts are missing.
27
+ *
28
+ * @param ref - Effect / tool resource string
29
+ */
30
+ export function parseMcpToolRef(ref: string): ParsedMcpToolRef | null {
31
+ if (!ref.startsWith("mcp:")) return null;
32
+ const rest = ref.slice(4);
33
+ const slash = rest.indexOf("/");
34
+ if (slash <= 0 || slash === rest.length - 1) return null;
35
+ return { server: rest.slice(0, slash), tool: rest.slice(slash + 1) };
36
+ }
37
+
38
+ /**
39
+ * True when `ref` is a well-formed MCP capability ref.
40
+ *
41
+ * @param ref - Candidate
42
+ */
43
+ export function isMcpToolRef(ref: string): boolean {
44
+ return parseMcpToolRef(ref) !== null;
45
+ }
46
+
47
+ /**
48
+ * Graph node id for one MCP server (`mcp:github`).
49
+ *
50
+ * @param server - Declared server name
51
+ */
52
+ export function mcpServerNodeId(server: string): string {
53
+ return `mcp:${server}`;
54
+ }
55
+
56
+ /**
57
+ * Provider-safe function name (`github__create_issue`).
58
+ *
59
+ * @param server - Declared server name
60
+ * @param tool - MCP tool name
61
+ */
62
+ export function mcpModelToolName(server: string, tool: string): string {
63
+ return `${server}__${tool}`;
64
+ }
65
+
66
+ /**
67
+ * Inverse of {@link mcpModelToolName}.
68
+ *
69
+ * @param name - Model-facing tool name
70
+ */
71
+ export function parseMcpModelToolName(name: string): ParsedMcpToolRef | null {
72
+ const sep = name.indexOf("__");
73
+ if (sep <= 0 || sep === name.length - 2) return null;
74
+ return { server: name.slice(0, sep), tool: name.slice(sep + 2) };
75
+ }
76
+
77
+ /**
78
+ * Capability ref from either `mcp:server/tool` or `server__tool`.
79
+ *
80
+ * @param name - Model-facing or capability name
81
+ */
82
+ export function mcpCapabilityRefFromName(name: string): McpToolRef | null {
83
+ const fromRef = parseMcpToolRef(name);
84
+ if (fromRef) return mcpToolRef(fromRef.server, fromRef.tool);
85
+ const fromModel = parseMcpModelToolName(name);
86
+ if (fromModel) return mcpToolRef(fromModel.server, fromModel.tool);
87
+ return null;
88
+ }
@@ -46,6 +46,12 @@ export type ResourceRef = `${StoreFacet}:${string}`;
46
46
  */
47
47
  export type RunsResourceRef = "runs";
48
48
 
49
+ /**
50
+ * Dead-letter read capability for `fx.deadLetters` — not a store facet.
51
+ * Declare on `effects.reads` (never `writes`).
52
+ */
53
+ export type SignalResourceRef = `signal:${string}`;
54
+
49
55
  /** Signal name reference. */
50
56
  export type SignalRef = string;
51
57
 
@@ -74,8 +80,8 @@ export type JsonSchema = string | Record<string, unknown>;
74
80
  * `sends` / `asks` are irreversible (asks also nondeterministic + cost).
75
81
  */
76
82
  export interface Effects {
77
- /** Store reads, plus optional `"runs"` for `fx.runs`. */
78
- reads?: Array<ResourceRef | RunsResourceRef>;
83
+ /** Store reads, plus `"runs"` for `fx.runs` and `signal:name` for `fx.deadLetters`. */
84
+ reads?: Array<ResourceRef | RunsResourceRef | SignalResourceRef>;
79
85
  /** Store writes. */
80
86
  writes?: ResourceRef[];
81
87
  /** Emitted signals. */
@@ -218,10 +224,23 @@ export interface DeclaredColumn extends ColumnClassification {
218
224
  /** Field classification value forms. */
219
225
  export type ClassificationValue = string | string[] | ColumnClassification;
220
226
 
227
+ /** Declared Postgres RLS policy on a {@link Table}. */
228
+ export interface TablePolicy {
229
+ as?: "permissive" | "restrictive";
230
+ to?: string | string[];
231
+ for?: "all" | "select" | "insert" | "update" | "delete";
232
+ using?: string;
233
+ withCheck?: string;
234
+ }
235
+
221
236
  /** SQL table metadata. */
222
237
  export interface Table {
223
238
  columns?: Record<string, DeclaredColumn | ColumnClassification>;
224
239
  classifications?: Record<string, ClassificationValue>;
240
+ /** RLS enabled (`pgTable.withRLS` or any policy). */
241
+ rls?: boolean;
242
+ /** Policy name → Drizzle-shaped spec. */
243
+ policies?: Record<string, TablePolicy>;
225
244
  }
226
245
 
227
246
  /** One Store declaration. */
@@ -297,6 +316,8 @@ export interface AiModel {
297
316
  provider?: string;
298
317
  tier?: string;
299
318
  model?: string;
319
+ /** Protocol driver override for this logical binding. */
320
+ driverId?: string;
300
321
  }
301
322
 
302
323
  /** Prompt / agent budget. */
@@ -327,11 +348,29 @@ export interface AiAgent {
327
348
  budget?: AiBudget;
328
349
  }
329
350
 
351
+ /**
352
+ * External MCP server the app consumes — tools join the existing `fx.call`
353
+ * / `toolLoop` path as `mcp:<server>/<tool>` capability refs.
354
+ */
355
+ export interface AiMcpServer {
356
+ /** Streamable HTTP endpoint. */
357
+ url?: string;
358
+ /** stdio executable (no shell string). */
359
+ command?: string;
360
+ /** Arguments for {@link command}. */
361
+ args?: string[];
362
+ /** Bearer secret contract name (never the value). */
363
+ auth?: SecretRef;
364
+ /** Required allowlist — never trust whatever `tools/list` exposes. */
365
+ tools: string[];
366
+ }
367
+
330
368
  /** AI element catalogue. */
331
369
  export interface Ai {
332
370
  models?: Record<string, AiModel>;
333
371
  prompts?: Record<string, AiPrompt>;
334
372
  agents?: Record<string, AiAgent>;
373
+ mcpServers?: Record<string, AiMcpServer>;
335
374
  }
336
375
 
337
376
  /** Per-table metadata on a Manifest {@link Plugin}. */
@@ -75,6 +75,26 @@ describe("validateManifest", () => {
75
75
  expect(result.ok).toBe(false);
76
76
  });
77
77
 
78
+ test("accepts signal resource refs on reads only", async () => {
79
+ const reads = await validateManifest({
80
+ oke: "1.0",
81
+ app: "x",
82
+ flows: {
83
+ f: { effects: { reads: ["signal:notify"] } },
84
+ },
85
+ });
86
+ expect(reads.ok).toBe(true);
87
+
88
+ const writes = await validateManifest({
89
+ oke: "1.0",
90
+ app: "x",
91
+ flows: {
92
+ f: { effects: { writes: ["signal:notify"] } },
93
+ },
94
+ });
95
+ expect(writes.ok).toBe(false);
96
+ });
97
+
78
98
  test("parseManifest throws ManifestValidationError on invalid JSON", async () => {
79
99
  expect(parseManifest("{")).rejects.toBeInstanceOf(ManifestValidationError);
80
100
  });
@@ -197,7 +197,7 @@ export async function serveDocsMcp(
197
197
  hostname: boundHost,
198
198
  fetch: mcp.fetch,
199
199
  stop(closeActive = false) {
200
- server.stop(closeActive);
200
+ return server.stop(closeActive);
201
201
  },
202
202
  };
203
203
  }
package/src/mcp/server.ts CHANGED
@@ -268,7 +268,7 @@ export async function serveMcp(options: ServeMcpOptions): Promise<McpServerHandl
268
268
  hostname: boundHost,
269
269
  fetch: mcp.fetch,
270
270
  stop(closeActive = false) {
271
- server.stop(closeActive);
271
+ return server.stop(closeActive);
272
272
  },
273
273
  };
274
274
  }
@@ -122,4 +122,25 @@ describe("compression plugin", () => {
122
122
  );
123
123
  expect(nt.headers.get("content-encoding")).toBeNull();
124
124
  });
125
+
126
+ test("skips text/event-stream even when Accept-Encoding is gzip", async () => {
127
+ on(
128
+ http.get("/sse"),
129
+ flow("sse.get", {
130
+ do: (_input, fx) =>
131
+ fx.json.stream(
132
+ (async function* () {
133
+ yield "hello";
134
+ })(),
135
+ ),
136
+ }),
137
+ );
138
+ const app = oke({ autoBoot: false, name: "zip-sse" }).plug(compression({ minSize: 0 }));
139
+ const res = await app.fetch(
140
+ new Request("http://localhost/sse", { headers: { "accept-encoding": "gzip" } }),
141
+ );
142
+ expect(res.headers.get("content-type")).toMatch(/text\/event-stream/);
143
+ expect(res.headers.get("content-encoding")).toBeNull();
144
+ expect(await res.text()).toContain('data: "hello"');
145
+ });
125
146
  });
@@ -73,6 +73,7 @@ export function compression(
73
73
  if (cacheControl !== null && /\bno-transform\b/i.test(cacheControl)) return;
74
74
 
75
75
  const contentType = headers.get("content-type") ?? "";
76
+ if (/^text\/event-stream\b/i.test(contentType.split(";")[0]?.trim() ?? "")) return;
76
77
  if (!match.test(contentType)) return;
77
78
 
78
79
  const body = await ctx.response.arrayBuffer();
@@ -201,7 +201,7 @@ export async function measureHttpPingAppBytes(): Promise<HttpPingBudgetSample> {
201
201
  await Bun.write(
202
202
  anchor,
203
203
  `import { on, flow, http, gate, oke, createBunRuntime } from ${JSON.stringify(httpEntry)};\n` +
204
- `on(http.get("/").gate.public, flow("ping", { do: () => "Hi" }));\n` +
204
+ `on(http.get("/").public(), flow("ping", { do: () => "Hi" }));\n` +
205
205
  `export const app = oke({ name: "ping" });\n` +
206
206
  `export const rt = createBunRuntime;\n`,
207
207
  );
@@ -362,7 +362,7 @@ const t0 = performance.now();
362
362
  const { createBunRuntime, oke, on, flow, http, gate } = await import(${JSON.stringify(httpEntry)});
363
363
  const { resetBindings } = await import(${JSON.stringify(`${import.meta.dir}/../kernel/on.ts`)});
364
364
  resetBindings();
365
- on(http.get("/ping").gate.public, flow("ping", { do: () => ({ ok: true }) }));
365
+ on(http.get("/ping").public(), flow("ping", { do: () => ({ ok: true }) }));
366
366
  const app = oke({ name: "cold-start" });
367
367
  const rt = createBunRuntime();
368
368
  const server = rt.serve(app, { port: 0, hostname: "127.0.0.1" });
@@ -81,7 +81,29 @@ export function isBunNativeMethod(method: string): boolean {
81
81
  );
82
82
  }
83
83
 
84
- type MethodHandlers = Partial<Record<string, (req: Request) => Response | Promise<Response>>>;
84
+ /** Bun.serve `fetch` / route handler second arg is the live server. */
85
+ type ServeFetch = (
86
+ req: Request,
87
+ server: { timeout(request: Request, seconds: number): void },
88
+ ) => Response | Promise<Response>;
89
+
90
+ type MethodHandlers = Partial<Record<string, ServeFetch>>;
91
+
92
+ /**
93
+ * Disable the 10s idle timeout for SSE so a quiet token stream is not reset.
94
+ *
95
+ * @param inner - App fetch
96
+ */
97
+ function holdSseIdle(inner: (request: Request) => Promise<Response>): ServeFetch {
98
+ return async (req, server) => {
99
+ const res = await inner(req);
100
+ const ct = (res.headers.get("content-type") ?? "").split(";")[0]?.trim() ?? "";
101
+ if (/^text\/event-stream$/i.test(ct)) {
102
+ server.timeout(req, 0);
103
+ }
104
+ return res;
105
+ };
106
+ }
85
107
 
86
108
  /**
87
109
  * Build Bun.serve `routes` from app HTTP bindings.
@@ -103,7 +125,7 @@ export function buildBunRoutes(
103
125
  if (typeof path !== "string" || typeof method !== "string") continue;
104
126
  if (!isBunNativePath(path) || !isBunNativeMethod(method)) continue;
105
127
  const methods = routes[path] ?? (routes[path] = {});
106
- methods[method] = (req) => fetchHandler(req);
128
+ methods[method] = holdSseIdle(fetchHandler);
107
129
  }
108
130
  return routes;
109
131
  }
@@ -128,10 +150,15 @@ export function serveBunHttp(options: {
128
150
  readonly routes?: Record<string, MethodHandlers>;
129
151
  readonly id?: string;
130
152
  }): ReturnType<typeof Bun.serve> {
153
+ const fetch = holdSseIdle(
154
+ typeof options.fetch === "function"
155
+ ? (req: Request) => Promise.resolve(options.fetch(req))
156
+ : options.fetch,
157
+ );
131
158
  const base = {
132
159
  port: options.port,
133
160
  hostname: options.hostname,
134
- fetch: options.fetch,
161
+ fetch,
135
162
  ...(options.id !== undefined ? { id: options.id } : {}),
136
163
  };
137
164
  const table = options.routes;
@@ -160,6 +187,15 @@ function listenBun(app: FetchApp, options?: ServeOptions): ServerHandle {
160
187
  ...(options?.id !== undefined ? { id: options.id } : {}),
161
188
  });
162
189
 
190
+ const closeIdle = (
191
+ server as typeof server & { closeIdleConnections(): void }
192
+ ).closeIdleConnections.bind(server);
193
+ const onPressure = (): void => {
194
+ closeIdle();
195
+ };
196
+ const proc = process as NodeJS.EventEmitter;
197
+ proc.on("memoryPressure", onPressure);
198
+
163
199
  const boundPort = server.port ?? port;
164
200
  const boundHost = server.hostname ?? hostname;
165
201
  const url = new URL(`http://${formatHostForUrl(boundHost)}:${boundPort}/`);
@@ -170,7 +206,8 @@ function listenBun(app: FetchApp, options?: ServeOptions): ServerHandle {
170
206
  hostname: boundHost,
171
207
  fetch: fetchHandler,
172
208
  stop(closeActiveConnections = false) {
173
- server.stop(closeActiveConnections);
209
+ proc.off("memoryPressure", onPressure);
210
+ return server.stop(closeActiveConnections);
174
211
  },
175
212
  };
176
213
  }
@@ -255,7 +255,7 @@ describe("httpGetNavPaths", () => {
255
255
  describe("asBrowserJsonCodeBlock — HTTP", () => {
256
256
  test("Chrome GET / becomes the code block; curl stays JSON", async () => {
257
257
  on(
258
- http.get("/").gate.public,
258
+ http.get("/").public(),
259
259
  flow("main.root", {
260
260
  do: () => ({ ok: true as const, app: "keel" }),
261
261
  }),
@@ -286,7 +286,7 @@ describe("asBrowserJsonCodeBlock — HTTP", () => {
286
286
  });
287
287
 
288
288
  test("?raw=1 stays on the traces page; format=json and Accept stay the envelope", async () => {
289
- on(http.get("/health").gate.public, flow("main.health", { do: () => ({ ok: true as const }) }));
289
+ on(http.get("/health").public(), flow("main.health", { do: () => ({ ok: true as const }) }));
290
290
  const app = oke({ autoBoot: false, name: "keel" });
291
291
 
292
292
  const raw = await app.fetch(
@@ -55,13 +55,13 @@ function rawHttp(port: number, payload: string): Promise<string> {
55
55
 
56
56
  function buildApp() {
57
57
  on(
58
- http.get("/ping").gate.public,
58
+ http.get("/ping").public(),
59
59
  flow("ping", {
60
60
  do: () => ({ ok: true as const, n: 1 }),
61
61
  }),
62
62
  );
63
63
  on(
64
- http.get("/notes/:id").gate.public,
64
+ http.get("/notes/:id").public(),
65
65
  flow("notes.get", {
66
66
  do: ({ id }: { id: string }) => ({ id }),
67
67
  }),
@@ -157,13 +157,13 @@ describe("Bun.serve — real HTTP", () => {
157
157
 
158
158
  test("QUERY is omitted from native routes so Bun.serve still boots", async () => {
159
159
  on(
160
- http.query("/search").gate.public,
160
+ http.query("/search").public(),
161
161
  flow("search.query", {
162
162
  do: () => ({ hits: 1 }),
163
163
  }),
164
164
  );
165
165
  on(
166
- http.get("/search/suggest").gate.public,
166
+ http.get("/search/suggest").public(),
167
167
  flow("search.suggest", {
168
168
  do: () => ({ hits: 0 }),
169
169
  }),