okengine 0.14.1 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (285) hide show
  1. package/package.json +3 -4
  2. package/site/content/docs/elements/ai.mdx +2 -1
  3. package/site/content/docs/elements/channel.mdx +1 -0
  4. package/site/content/docs/elements/clock.mdx +3 -0
  5. package/site/content/docs/elements/flow.mdx +9 -0
  6. package/site/content/docs/elements/gate.mdx +80 -22
  7. package/site/content/docs/elements/signal.mdx +4 -4
  8. package/site/content/docs/elements/store.mdx +84 -27
  9. package/site/content/docs/elements/vault.mdx +4 -0
  10. package/site/content/docs/get-started/basic-usage.mdx +22 -13
  11. package/site/content/docs/get-started/index.mdx +5 -0
  12. package/site/content/docs/get-started/introduction.mdx +11 -3
  13. package/site/content/docs/get-started/meta.json +1 -1
  14. package/site/content/docs/get-started/project-structure.mdx +932 -0
  15. package/site/content/docs/reference/cli.md +13 -10
  16. package/site/content/docs/reference/client.mdx +28 -21
  17. package/site/content/docs/reference/fx.mdx +29 -18
  18. package/site/content/docs/reference/security.md +2 -2
  19. package/src/auth/api-key-sql.test.ts +47 -0
  20. package/src/auth/api-key-sql.ts +254 -0
  21. package/src/auth/api-keys.ts +273 -14
  22. package/src/auth/auth.test.ts +56 -0
  23. package/src/auth/bindings.ts +14 -5
  24. package/src/auth/config.ts +5 -5
  25. package/src/auth/cross-plane.ts +10 -3
  26. package/src/auth/index.ts +25 -1
  27. package/src/auth/schema.ts +8 -0
  28. package/src/cli/ai-setup/ai-setup.test.ts +62 -0
  29. package/src/cli/ai-setup/apply.ts +32 -1
  30. package/src/cli/attach-host-console.test.ts +40 -2
  31. package/src/cli/attach-host-console.ts +9 -0
  32. package/src/cli/build.test.ts +4 -2
  33. package/src/cli/db-seed.ts +48 -15
  34. package/src/cli/db.test.ts +104 -0
  35. package/src/cli/db.ts +122 -10
  36. package/src/cli/dev-controls.test.ts +20 -0
  37. package/src/cli/dev-controls.ts +13 -0
  38. package/src/cli/dev.test.ts +15 -4
  39. package/src/cli/dev.ts +102 -29
  40. package/src/cli/ensure-drizzle-config.ts +2 -1
  41. package/src/cli/tui/DevLive.tsx +12 -2
  42. package/src/cli/tui/keys.test.ts +2 -1
  43. package/src/cli/tui/keys.ts +3 -2
  44. package/src/compiler/aot.test.ts +1 -1
  45. package/src/compiler/effects-infer.ts +17 -2
  46. package/src/compiler/extract.test.ts +409 -33
  47. package/src/compiler/extract.ts +318 -67
  48. package/src/compiler/flow-path.test.ts +96 -0
  49. package/src/compiler/flow-path.ts +173 -0
  50. package/src/compiler/generate-adopt.test.ts +112 -3
  51. package/src/compiler/generate-adopt.ts +280 -26
  52. package/src/compiler/schema-from-ast.ts +609 -0
  53. package/src/console/server/access.test.ts +116 -8
  54. package/src/console/server/access.ts +70 -23
  55. package/src/console/server/app.ts +1 -0
  56. package/src/console/server/bind.ts +2 -2
  57. package/src/console/server/console.test.ts +3 -0
  58. package/src/console/server/dev-identities.test.ts +127 -0
  59. package/src/console/server/dev-identities.ts +229 -0
  60. package/src/console/server/flows-invoke.test.ts +21 -4
  61. package/src/console/server/flows.ts +233 -98
  62. package/src/console/server/gates.test.ts +24 -0
  63. package/src/console/server/gates.ts +8 -21
  64. package/src/console/server/index.ts +1 -0
  65. package/src/console/server/invoke-user-flow.test.ts +3 -1
  66. package/src/console/server/invoke-user-flow.ts +17 -2
  67. package/src/console/server/iso-at.test.ts +9 -0
  68. package/src/console/server/iso-at.ts +11 -0
  69. package/src/console/server/live.test.ts +26 -1
  70. package/src/console/server/live.ts +4 -2
  71. package/src/console/server/operator-db.ts +2 -0
  72. package/src/console/server/security-headers.ts +6 -3
  73. package/src/console/server/spa-proxy.test.ts +1 -0
  74. package/src/console/server/sql-catalog.ts +147 -7
  75. package/src/console/server/sql-rls.test.ts +16 -0
  76. package/src/console/server/state.ts +37 -55
  77. package/src/console/server/store.test.ts +7 -4
  78. package/src/console/server/store.ts +142 -58
  79. package/src/console/ui-next/dist/assets/access-page-CAGHrA9H.js +4 -0
  80. package/src/console/ui-next/dist/assets/agent-disclosure-BP0Y0Sux.js +1 -0
  81. package/src/console/ui-next/dist/assets/cache-glyph-B5X-NM0-.js +1 -0
  82. package/src/console/ui-next/dist/assets/call-pii-button-ChAUiLo9.js +1 -0
  83. package/src/console/ui-next/dist/assets/collapsible-DGnOM2ph.js +1 -0
  84. package/src/console/ui-next/dist/assets/copy-inline-button-DBHhgHKP.js +1 -0
  85. package/src/console/ui-next/dist/assets/detail-header-DhHM1iaZ.js +1 -0
  86. package/src/console/ui-next/dist/assets/dropdown-menu-_NjTEo5_.js +1 -0
  87. package/src/console/ui-next/dist/assets/{duration-tone-oiRxPVsZ.js → duration-tone-sC3lGABz.js} +1 -1
  88. package/src/console/ui-next/dist/assets/element-icons-BVXtRyd3.js +1 -0
  89. package/src/console/ui-next/dist/assets/explorer-empty-2uIhBu0_.js +1 -0
  90. package/src/console/ui-next/dist/assets/flows-page-B-OUtiAu.js +1 -0
  91. package/src/console/ui-next/dist/assets/{highlighted-json-CvDPvveV.js → highlighted-json-Awq7gYdu.js} +1 -1
  92. package/src/console/ui-next/dist/assets/http-method-_UHM2ODJ.js +1 -0
  93. package/src/console/ui-next/dist/assets/index-CGoZkILK.js +66 -0
  94. package/src/console/ui-next/dist/assets/index-Ck88Jmv8.css +2 -0
  95. package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +1 -0
  96. package/src/console/ui-next/dist/assets/observability-page-BDyXalNR.js +4 -0
  97. package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +1 -0
  98. package/src/console/ui-next/dist/assets/replica-lag-DKRrbvdo.js +18 -0
  99. package/src/console/ui-next/dist/assets/request-meta-DV0ywz7t.js +1 -0
  100. package/src/console/ui-next/dist/assets/shortcut-JQIZlWfm.js +1 -0
  101. package/src/console/ui-next/dist/assets/shortcut-keys-DKxNTe_m.js +1 -0
  102. package/src/console/ui-next/dist/assets/{skeleton-CL_X0GCj.js → skeleton-D-czQJT6.js} +1 -1
  103. package/src/console/ui-next/dist/assets/store-page-Xh8Kn3rx.js +41 -0
  104. package/src/console/ui-next/dist/assets/trace-detail-sheet-B09O8rA6.js +2 -0
  105. package/src/console/ui-next/dist/assets/tree-expand-toggle-DkOXA12R.js +54 -0
  106. package/src/console/ui-next/dist/assets/units-page-Dpk40kOQ.js +1 -0
  107. package/src/console/ui-next/dist/assets/{use-vault-list-Cl79j_ku.js → use-vault-list-uk4WVboC.js} +1 -1
  108. package/src/console/ui-next/dist/assets/useMutation-B8EO02Ej.js +1 -0
  109. package/src/console/ui-next/dist/assets/useRender-BE2A9BWC.js +1 -0
  110. package/src/console/ui-next/dist/assets/vault-page-B1dB9Ft0.js +2 -0
  111. package/src/console/ui-next/dist/index.html +12 -10
  112. package/src/console/ui-next/index.html +3 -3
  113. package/src/console/ui-next/src/client.ts +202 -7
  114. package/src/console/ui-next/src/components/motion/table/index.tsx +16 -0
  115. package/src/console/ui-next/src/components/motion/table/types.ts +4 -0
  116. package/src/console/ui-next/src/components/shell/app-sidebar.tsx +2 -0
  117. package/src/console/ui-next/src/components/shell/command-items.test.ts +1 -0
  118. package/src/console/ui-next/src/components/shell/command-items.ts +8 -0
  119. package/src/console/ui-next/src/components/shell/shell-layout.tsx +2 -1
  120. package/src/console/ui-next/src/components/ui/kbd.tsx +2 -2
  121. package/src/console/ui-next/src/components/ui/sheet-form.tsx +8 -3
  122. package/src/console/ui-next/src/features/access/access-page.tsx +347 -0
  123. package/src/console/ui-next/src/features/access/data/use-access.ts +23 -0
  124. package/src/console/ui-next/src/features/access/detail/access-detail.tsx +552 -0
  125. package/src/console/ui-next/src/features/access/explorer/access-list.tsx +125 -0
  126. package/src/console/ui-next/src/features/access/lib/format-when.test.ts +139 -0
  127. package/src/console/ui-next/src/features/access/lib/format-when.ts +296 -0
  128. package/src/console/ui-next/src/features/access/lib/scope-groups.test.ts +34 -0
  129. package/src/console/ui-next/src/features/access/lib/scope-groups.ts +40 -0
  130. package/src/console/ui-next/src/features/access/sheets/access-allow-fields.tsx +236 -0
  131. package/src/console/ui-next/src/features/access/sheets/access-create-sheet.tsx +316 -0
  132. package/src/console/ui-next/src/features/access/sheets/access-edit-sheet.tsx +240 -0
  133. package/src/console/ui-next/src/features/access/sheets/access-expiry-fields.tsx +66 -0
  134. package/src/console/ui-next/src/features/access/sheets/access-refresh-sheet.tsx +104 -0
  135. package/src/console/ui-next/src/features/access/sheets/access-scope-field.tsx +115 -0
  136. package/src/console/ui-next/src/features/access/sheets/access-secret-sheet.tsx +89 -0
  137. package/src/console/ui-next/src/features/access/sheets/access-sheet-search.tsx +49 -0
  138. package/src/console/ui-next/src/features/access/state/access-selection.ts +84 -0
  139. package/src/console/ui-next/src/features/auth/auth-redirect.test.ts +5 -0
  140. package/src/console/ui-next/src/features/auth/auth-redirect.ts +18 -2
  141. package/src/console/ui-next/src/features/flows/traces/http-method.ts +19 -0
  142. package/src/console/ui-next/src/features/flows/traces/trace-detail.test.ts +19 -1
  143. package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +10 -3
  144. package/src/console/ui-next/src/features/not-found/not-found-page.tsx +1 -1
  145. package/src/console/ui-next/src/features/store/detail/browse-section.tsx +45 -1
  146. package/src/console/ui-next/src/features/store/detail/resource-panel.tsx +78 -16
  147. package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +155 -14
  148. package/src/console/ui-next/src/features/store/explorer/store-tree.tsx +33 -133
  149. package/src/console/ui-next/src/features/store/files/file-preview.tsx +4 -1
  150. package/src/console/ui-next/src/features/store/grid/rls-policy-sheet.tsx +326 -165
  151. package/src/console/ui-next/src/features/store/grid/sql-insert-sheet.tsx +6 -0
  152. package/src/console/ui-next/src/features/store/grid/store-data-grid.tsx +50 -3
  153. package/src/console/ui-next/src/features/store/lib/files-meta.test.ts +3 -0
  154. package/src/console/ui-next/src/features/store/lib/files-meta.ts +12 -0
  155. package/src/console/ui-next/src/features/store/lib/query-defaults.test.ts +25 -0
  156. package/src/console/ui-next/src/features/store/lib/query-defaults.ts +85 -3
  157. package/src/console/ui-next/src/features/store/lib/query-gate.ts +30 -4
  158. package/src/console/ui-next/src/features/store/lib/rls-policy.test.ts +139 -0
  159. package/src/console/ui-next/src/features/store/lib/rls-policy.ts +223 -21
  160. package/src/console/ui-next/src/features/store/lib/schema-graph.ts +4 -0
  161. package/src/console/ui-next/src/features/store/lib/sql-catalog.test.ts +24 -0
  162. package/src/console/ui-next/src/features/store/lib/sql-catalog.ts +19 -0
  163. package/src/console/ui-next/src/features/store/lib/store-tree.test.ts +6 -0
  164. package/src/console/ui-next/src/features/store/lib/store-tree.ts +13 -0
  165. package/src/console/ui-next/src/features/store/query/query-console.tsx +48 -14
  166. package/src/console/ui-next/src/features/store/query/query-gate-menu.tsx +19 -20
  167. package/src/console/ui-next/src/features/store/query/query-gate-parts.tsx +2 -1
  168. package/src/console/ui-next/src/features/store/query/query-results.tsx +110 -83
  169. package/src/console/ui-next/src/features/store/schema/schema-table-node.tsx +17 -0
  170. package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +112 -72
  171. package/src/console/ui-next/src/features/units/lib/contract-input.test.ts +206 -1
  172. package/src/console/ui-next/src/features/units/lib/contract-input.ts +55 -0
  173. package/src/console/ui-next/src/features/vault/lib/types.ts +1 -1
  174. package/src/console/ui-next/src/lib/document-meta.test.ts +1 -0
  175. package/src/console/ui-next/src/lib/document-meta.ts +2 -1
  176. package/src/console/ui-next/src/lib/last-module-search.test.ts +1 -0
  177. package/src/console/ui-next/src/lib/last-module-search.ts +2 -1
  178. package/src/console/ui-next/src/lib/shortcut.test.ts +2 -0
  179. package/src/console/ui-next/src/lib/shortcut.ts +3 -0
  180. package/src/console/ui-next/src/router.tsx +10 -1
  181. package/src/console/ui-next/ui-next-seed-app-schema.ts +88 -37
  182. package/src/console/ui-next/ui-next-seed-store.ts +43 -15
  183. package/src/console/xss.gate.test.ts +1 -0
  184. package/src/docker/compose-health.test.ts +27 -0
  185. package/src/docker/compose-health.ts +19 -3
  186. package/src/docker/docker.test.ts +3 -0
  187. package/src/docker/recipes/pgdog.ts +2 -0
  188. package/src/drivers/clock-postgres.ts +13 -9
  189. package/src/drivers/instances-postgres.ts +4 -6
  190. package/src/drivers/journal-postgres.ts +13 -9
  191. package/src/drivers/pg-rls.test.ts +87 -0
  192. package/src/drivers/pg-rls.ts +233 -7
  193. package/src/drivers/pglite.ts +26 -3
  194. package/src/drivers/postgres.test.ts +23 -0
  195. package/src/drivers/postgres.ts +133 -9
  196. package/src/drivers/types.ts +10 -0
  197. package/src/elements/clock/declare.ts +26 -1
  198. package/src/elements/gate/boot.ts +5 -5
  199. package/src/elements/gate/declare.ts +38 -4
  200. package/src/elements/gate/flatten.ts +1 -1
  201. package/src/elements/gate/runtime.ts +1 -1
  202. package/src/elements/store/cache.ts +1 -1
  203. package/src/elements/store/declare.ts +9 -6
  204. package/src/elements/store/emit-drizzle.ts +44 -8
  205. package/src/elements/store/resource.test.ts +1 -1
  206. package/src/elements/store/rls-identity.test.ts +95 -0
  207. package/src/elements/store/rls-identity.ts +116 -0
  208. package/src/elements/store/runtime.ts +18 -0
  209. package/src/elements/store/schema-decl.test.ts +37 -0
  210. package/src/elements/store/schema-decl.ts +260 -41
  211. package/src/elements/store/seed.ts +1 -1
  212. package/src/elements/store/sql-condition.test.ts +12 -0
  213. package/src/elements/store/sql-condition.ts +26 -2
  214. package/src/elements/store/sql-rls-isolation.test.ts +165 -0
  215. package/src/elements/store/sql-rls-stamp.test.ts +117 -0
  216. package/src/elements/store/sql-select.types.test.ts +71 -0
  217. package/src/elements/store/sql-session.ts +135 -35
  218. package/src/elements/store/upsert-app.test.ts +1 -1
  219. package/src/elements/store.ts +17 -0
  220. package/src/http.ts +20 -3
  221. package/src/index.ts +11 -1
  222. package/src/kernel/adopt-barrel-fresh.test.ts +1 -1
  223. package/src/kernel/adopt-routes.ts +30 -2
  224. package/src/kernel/api-key-host-persist.test.ts +143 -0
  225. package/src/kernel/app.ts +145 -16
  226. package/src/kernel/auth-resolve.ts +10 -5
  227. package/src/kernel/auto-cache.test.ts +8 -8
  228. package/src/kernel/auto-registry.test.ts +49 -9
  229. package/src/kernel/boot.test.ts +5 -5
  230. package/src/kernel/boot.ts +2 -2
  231. package/src/kernel/effects-stamping.test.ts +3 -3
  232. package/src/kernel/element-registries.ts +12 -5
  233. package/src/kernel/errors.ts +24 -0
  234. package/src/kernel/flow-units.ts +64 -0
  235. package/src/kernel/flow.ts +29 -17
  236. package/src/kernel/fx-auth-keys.test.ts +88 -0
  237. package/src/kernel/fx-auth-keys.ts +198 -0
  238. package/src/kernel/fx.test.ts +17 -1
  239. package/src/kernel/fx.ts +59 -18
  240. package/src/kernel/horizontal-child.ts +4 -4
  241. package/src/kernel/http-path-pending.ts +20 -0
  242. package/src/kernel/http-resource.ts +4 -0
  243. package/src/kernel/http-stream.test.ts +4 -4
  244. package/src/kernel/pipeline-api-key.test.ts +192 -7
  245. package/src/kernel/pipeline.test.ts +2 -2
  246. package/src/kernel/pipeline.ts +4 -2
  247. package/src/kernel/plugin-elements.test.ts +1 -1
  248. package/src/kernel/plugin-needs.test.ts +1 -1
  249. package/src/kernel/ready.test.ts +2 -2
  250. package/src/kernel/resource-mount.test.ts +14 -0
  251. package/src/kernel/run-duration.test.ts +2 -2
  252. package/src/kernel/stamp-http.test.ts +79 -0
  253. package/src/kernel/stamp-http.ts +75 -0
  254. package/src/kernel/triggers.ts +52 -27
  255. package/src/manifest/types.ts +22 -3
  256. package/src/release/build-lib.ts +1 -0
  257. package/src/release/measure.ts +2 -2
  258. package/src/runtime/json-code-block.test.ts +2 -2
  259. package/src/runtime/serve.test.ts +4 -4
  260. package/src/term.test.ts +52 -4
  261. package/src/term.ts +125 -17
  262. package/src/test/reset-element-registries.ts +8 -3
  263. package/src/cli/ask-seed.test.ts +0 -96
  264. package/src/cli/ask-seed.ts +0 -82
  265. package/src/console/ui-next/dist/assets/cache-glyph-BanhLsEY.js +0 -1
  266. package/src/console/ui-next/dist/assets/call-pii-button-bqkxMrJH.js +0 -1
  267. package/src/console/ui-next/dist/assets/collapsible-DYb0xU8C.js +0 -1
  268. package/src/console/ui-next/dist/assets/confirm-sheet-DDCRmG62.js +0 -1
  269. package/src/console/ui-next/dist/assets/explorer-empty-HjTnVQoR.js +0 -1
  270. package/src/console/ui-next/dist/assets/flows-page-DxDsOd4f.js +0 -1
  271. package/src/console/ui-next/dist/assets/http-method-CJCBYL2j.js +0 -1
  272. package/src/console/ui-next/dist/assets/index-Ce6WKWKM.js +0 -66
  273. package/src/console/ui-next/dist/assets/index-D4Ldtj79.css +0 -2
  274. package/src/console/ui-next/dist/assets/link-DF7SZ9Ek.js +0 -1
  275. package/src/console/ui-next/dist/assets/observability-page-BEZDzyYh.js +0 -4
  276. package/src/console/ui-next/dist/assets/query-gate-parts-1m8m1iNp.js +0 -2
  277. package/src/console/ui-next/dist/assets/react-B1ML8gxg.js +0 -1
  278. package/src/console/ui-next/dist/assets/replica-lag-C8_BRt2x.js +0 -18
  279. package/src/console/ui-next/dist/assets/sheet-form-D-ata7jy.js +0 -1
  280. package/src/console/ui-next/dist/assets/shortcut-Cgzuv4k1.js +0 -1
  281. package/src/console/ui-next/dist/assets/shortcut-keys-DO4IsVqv.js +0 -1
  282. package/src/console/ui-next/dist/assets/store-page-v3LXdYpr.js +0 -45
  283. package/src/console/ui-next/dist/assets/trace-detail-sheet-DFLFfUUX.js +0 -2
  284. package/src/console/ui-next/dist/assets/units-page-C0gW6Kdo.js +0 -1
  285. package/src/console/ui-next/dist/assets/vault-page-DuKzqwzW.js +0 -2
@@ -14,6 +14,7 @@ describe("dev-controls", () => {
14
14
  expect(parseDevControlKey("?")).toBe("?");
15
15
  expect(parseDevControlKey("h")).toBe("?");
16
16
  expect(parseDevControlKey("r")).toBe("r");
17
+ expect(parseDevControlKey("s")).toBe("s");
17
18
  expect(parseDevControlKey("u")).toBe("u");
18
19
  expect(parseDevControlKey("x")).toBe("x");
19
20
  expect(parseDevControlKey("c")).toBeNull();
@@ -33,6 +34,9 @@ describe("dev-controls", () => {
33
34
  expect(formatDevControlsHint(false)).toContain("quit");
34
35
  expect(formatDevControlsHint(false)).not.toContain("services");
35
36
  expect(formatDevControlsHelp(false)).toContain("refresh");
37
+ expect(formatDevControlsHint(false)).toContain("seed");
38
+ expect(formatDevControlsHint(false)).not.toContain("restart");
39
+ expect(formatDevControlsHelp(false)).toContain("oke db seed");
36
40
  expect(formatDevControlsHelp(false)).not.toContain("select service");
37
41
  });
38
42
 
@@ -77,6 +81,22 @@ describe("dev-controls", () => {
77
81
  d.stop();
78
82
  });
79
83
 
84
+ test("dispatcher s runs onSeed", async () => {
85
+ let seeded = 0;
86
+ const d = createDevControlDispatcher({
87
+ write: () => {},
88
+ onQuit: () => {},
89
+ onSeed: () => {
90
+ seeded += 1;
91
+ },
92
+ composeAction: async () => {},
93
+ });
94
+ d.handleKey("s");
95
+ await Bun.sleep(10);
96
+ expect(seeded).toBe(1);
97
+ d.stop();
98
+ });
99
+
80
100
  test("busy compose action ignores a concurrent key press", async () => {
81
101
  let running = 0;
82
102
  let maxConcurrent = 0;
@@ -30,6 +30,10 @@ export type StartDevControlsOptions = {
30
30
  * After a compose up/stop — sync health into the board.
31
31
  */
32
32
  readonly onComposeSettled?: () => void | Promise<void>;
33
+ /**
34
+ * Run `oke db seed` (tests / non-Ink dispatcher).
35
+ */
36
+ readonly onSeed?: () => void | Promise<void>;
33
37
  /**
34
38
  * Run a compose control action against the whole stack.
35
39
  *
@@ -50,6 +54,7 @@ export function formatDevControlsHint(color: boolean = termColorEnabled()): stri
50
54
  `${s.dim}│${s.reset} ${s.dim}keys${s.reset} ` +
51
55
  `${s.cyan}?${s.reset} help · ` +
52
56
  `${s.cyan}r${s.reset} refresh · ` +
57
+ `${s.cyan}s${s.reset} seed · ` +
53
58
  `${s.cyan}q${s.reset} quit · ` +
54
59
  `${s.cyan}u${s.reset} up · ` +
55
60
  `${s.cyan}x${s.reset} stop` +
@@ -72,6 +77,7 @@ export function formatDevControlsHelp(color: boolean = termColorEnabled()): stri
72
77
  `${s.green}◇${s.reset} ${s.bold}Keys${s.reset}`,
73
78
  k("?", "help"),
74
79
  k("r", "refresh — clear logs, show latest ●"),
80
+ k("s", "oke db seed"),
75
81
  k("q", "quit oke dev"),
76
82
  k("u", "compose up -d (all)"),
77
83
  k("x", "compose stop (all)"),
@@ -140,6 +146,13 @@ export function createDevControlDispatcher(opts: StartDevControlsOptions): DevCo
140
146
  void Promise.resolve(opts.onShowPanel?.(body) ?? opts.write(body));
141
147
  return;
142
148
  }
149
+ if (key === "s") {
150
+ void Promise.resolve(opts.onSeed?.()).catch((err: unknown) => {
151
+ const msg = err instanceof Error ? err.message : String(err);
152
+ opts.write(formatStatusLine(`seed failed — ${msg}`, color, "error"));
153
+ });
154
+ return;
155
+ }
143
156
  if (key === "u" || key === "x") {
144
157
  void runAction(key === "u" ? "up" : "stop");
145
158
  }
@@ -19,7 +19,7 @@ import type { Manifest } from "../manifest/types.ts";
19
19
  import type { WideEvent } from "../runs/types.ts";
20
20
  import { isDataEnvelope, MCP_DATA_KIND } from "../mcp/data.ts";
21
21
  import { mintMcpSession } from "../mcp/session.ts";
22
- import { runDev, type DevOptions, type DevSession } from "./dev.ts";
22
+ import { isFlowsTreeWatchPath, runDev, type DevOptions, type DevSession } from "./dev.ts";
23
23
  import { mcpContextFromConsole } from "./mcp-from-console.ts";
24
24
 
25
25
  /** Repo public entry — absolute import so temp apps need no install. */
@@ -519,11 +519,11 @@ async function writePingApp(dir: string, version: string): Promise<void> {
519
519
  join(dir, "src/flows/ping.ts"),
520
520
  `import { on, flow, http, gate } from ${JSON.stringify(OKE_INDEX)};
521
521
 
522
- export const ping = on(http.get("/ping").gate.public, flow("ping", {
522
+ export const ping = on(http.get("/ping").public(), flow("ping", {
523
523
  do: () => ({ version: ${JSON.stringify(version)} as const }),
524
524
  }));
525
525
 
526
- export const slow = on(http.get("/slow").gate.public, flow("slow", {
526
+ export const slow = on(http.get("/slow").public(), flow("slow", {
527
527
  do: async () => {
528
528
  const started = ${JSON.stringify(version)};
529
529
  await Bun.sleep(800);
@@ -725,7 +725,8 @@ describe("oke dev syncAdoptBarrel atomic write", () => {
725
725
 
726
726
  const barrelPath = join(dir, "src/flows", ADOPT_BARREL_FILE);
727
727
  const text = await Bun.file(barrelPath).text();
728
- expect(text).toContain('export * as notes from "./notes/index.ts";');
728
+ expect(text).toContain('import * as notes from "./notes/index.ts";');
729
+ expect(text).toContain("export { notes }");
729
730
  expect(text).not.toContain("stale stub");
730
731
  expect(await Bun.file(`${barrelPath}.tmp`).exists()).toBe(false);
731
732
  const leftovers = (await readdir(join(dir, "src/flows"))).filter((f) => f.includes(".tmp"));
@@ -789,6 +790,16 @@ describe("oke dev syncAdoptBarrel atomic write", () => {
789
790
  });
790
791
  });
791
792
 
793
+ describe("isFlowsTreeWatchPath", () => {
794
+ test("flows tree files trigger regen; generated.ts does not", () => {
795
+ expect(isFlowsTreeWatchPath("flows/notes/[id]/get.ts")).toBe(true);
796
+ expect(isFlowsTreeWatchPath("flows\\notes\\list.ts")).toBe(true);
797
+ expect(isFlowsTreeWatchPath("flows/generated.ts")).toBe(false);
798
+ expect(isFlowsTreeWatchPath("flows/generated.ts.tmp")).toBe(false);
799
+ expect(isFlowsTreeWatchPath("core.ts")).toBe(false);
800
+ });
801
+ });
802
+
792
803
  describe("oke dev Docker-first", () => {
793
804
  test("dryRun always plans compose (no mode ask)", async () => {
794
805
  const dir = await mkdtemp(join(tmpdir(), "oke-dev-always-compose-"));
package/src/cli/dev.ts CHANGED
@@ -31,8 +31,8 @@ import {
31
31
  createAnchoredBoard,
32
32
  createBootProgress,
33
33
  devStatusFromAiPhase,
34
- formatAppReadyLine,
35
34
  formatBootWarn,
35
+ formatBoundSurfaces,
36
36
  formatClaimNote,
37
37
  formatCliChrome,
38
38
  formatDevBanner,
@@ -83,7 +83,7 @@ export interface DevSurfaceHandle {
83
83
 
84
84
  /** App surface returned by {@link DevOptions.startApp}. */
85
85
  export interface DevAppHandle {
86
- readonly stop: () => void;
86
+ readonly stop: () => void | Promise<void>;
87
87
  /** Bound listen port (set when serve chooses an ephemeral port). */
88
88
  readonly port?: number;
89
89
  /** Base URL of the app server. */
@@ -181,12 +181,9 @@ export interface DevOptions {
181
181
  /**
182
182
  * Injectable `.adopt()` barrel regeneration (tests). Default: real
183
183
  * `generateAdoptBarrel` + atomic write to `<cwd>/src/flows/generated.ts`
184
- * (`generated.ts.tmp` → rename). Runs once per `oke dev` session, before
185
- * the entry is resolved/imported a real file on disk, so `oke dev`'s
186
- * runtime `import()` and `oke build`'s `Bun.build()` resolve it
187
- * identically (unlike a virtual-module Bun plugin, which does not — see
188
- * `compiler/generate-adopt.ts`). Atomic so a concurrent import during
189
- * `bun --hot` never sees a torn mid-write file.
184
+ * (`generated.ts.tmp` → rename). Runs at session start and again on
185
+ * `src/flows/**` changes except `generated.ts` itself. Write-if-changed
186
+ * so the `src/` watcher + bun `--hot` do not loop.
190
187
  *
191
188
  * @param cwd - Project root
192
189
  */
@@ -481,6 +478,7 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
481
478
  files: readonly string[];
482
479
  cwd: string;
483
480
  env: Record<string, string>;
481
+ aiServiceName?: string;
484
482
  } | null = null;
485
483
  let stackSqlDriver = "postgres";
486
484
  let stackKvDriver = "redis";
@@ -658,6 +656,7 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
658
656
  files: composeFiles,
659
657
  cwd: dockerOut,
660
658
  env: { ...stackEnv! },
659
+ aiServiceName: derived.specs.find((s) => s.role === "ai")?.serviceName,
661
660
  };
662
661
 
663
662
  // Stream compose health until infra is up (AI may stay pending — model load).
@@ -942,7 +941,9 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
942
941
  return { code: 0, plan };
943
942
  }
944
943
 
944
+ let boardPaused = false;
945
945
  const repaintBoard = (aiStatus?: DevStatus): void => {
946
+ if (boardPaused) return;
946
947
  bootBoard.paint(paintBootBoard(aiStatus ?? heroAiStatus));
947
948
  };
948
949
 
@@ -1026,15 +1027,6 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
1026
1027
  `oke db push (dev · ${result.dialect}) ${result.code === 0 ? "ok" : "failed"}`,
1027
1028
  ),
1028
1029
  );
1029
- if (result.code === 0) {
1030
- const { maybeAskSeed } = await import("./ask-seed.ts");
1031
- await maybeAskSeed({
1032
- cwd,
1033
- env: configEnv,
1034
- write,
1035
- stdinIsTTY: options.stdinIsTTY ?? process.stdin.isTTY,
1036
- });
1037
- }
1038
1030
  } catch (err) {
1039
1031
  write(await formatDevSchemaSyncFailure(cwd, err, "boot"));
1040
1032
  }
@@ -1323,14 +1315,14 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
1323
1315
  write(formatBootWarn(notice));
1324
1316
  }
1325
1317
  bootNotices.length = 0;
1326
- write(formatAppReadyLine(`http://127.0.0.1:${boundAppPort}`));
1327
- write(formatServiceLine("Console", `http://127.0.0.1:${boundConsolePort}`));
1328
- if (mcpServer) {
1329
- write(formatServiceLine("MCP", `http://127.0.0.1:${boundMcpPort}`));
1330
- }
1331
- if (docsMcpServer) {
1332
- write(formatServiceLine("Docs MCP", `http://127.0.0.1:${boundDocsMcpPort}`));
1333
- }
1318
+ write(
1319
+ formatBoundSurfaces({
1320
+ appUrl: `http://127.0.0.1:${boundAppPort}`,
1321
+ consoleUrl: `http://127.0.0.1:${boundConsolePort}`,
1322
+ mcpUrl: mcpServer ? `http://127.0.0.1:${boundMcpPort}` : null,
1323
+ docsMcpUrl: docsMcpServer ? `http://127.0.0.1:${boundDocsMcpPort}` : null,
1324
+ }),
1325
+ );
1334
1326
  write(formatDevLogSeparator());
1335
1327
  chromeReady = true;
1336
1328
  };
@@ -1358,6 +1350,10 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
1358
1350
  const watchFs: DevWatchFn =
1359
1351
  options.watchFs ?? ((path, watchOptions, listener) => watch(path, watchOptions, listener));
1360
1352
  const watcher = watchFs(resolve(cwd, "src"), { recursive: true }, (_event, filename) => {
1353
+ const rel = (filename?.toString() ?? "").replace(/\\/g, "/");
1354
+ if (isFlowsTreeWatchPath(rel)) {
1355
+ void syncAdoptBarrel(cwd);
1356
+ }
1361
1357
  void regen(appUrl);
1362
1358
  // Only live-extract when the host did not pin a Manifest (tests).
1363
1359
  if (options.manifest === undefined) {
@@ -1388,7 +1384,7 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
1388
1384
  const host = attachedHost;
1389
1385
  attachedHost = null;
1390
1386
  void host?.stop();
1391
- app.stop();
1387
+ void Promise.resolve(app.stop());
1392
1388
  consoleServer.stop();
1393
1389
  mcpServer?.stop();
1394
1390
  docsMcpServer?.stop();
@@ -1489,6 +1485,25 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
1489
1485
  await syncComposeBoard();
1490
1486
  paintReadyChrome();
1491
1487
  };
1488
+ const waitComposeReady = async (): Promise<void> => {
1489
+ const { watchComposeHealth } = await import("../docker/compose-health.ts");
1490
+ const aiServiceName = started.aiServiceName;
1491
+ liveComposeHealth = await watchComposeHealth({
1492
+ files: started.files,
1493
+ cwd: started.cwd,
1494
+ env: started.env,
1495
+ run: composeHealthRun,
1496
+ timeoutMs: 20_000,
1497
+ isDone: (map) => {
1498
+ if (map.size === 0) return true;
1499
+ return [...map.entries()].every(
1500
+ ([name, s]) => name === aiServiceName || s === "ready" || s === "error",
1501
+ );
1502
+ },
1503
+ });
1504
+ if (liveComposeHealth.get("ai") === "error") heroAiStatus = "error";
1505
+ };
1506
+
1492
1507
  const composeAction = async (action: DevComposeControlAction): Promise<void> => {
1493
1508
  const files = started.files;
1494
1509
  const finalArgs =
@@ -1510,6 +1525,7 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
1510
1525
  const detail = [stdout.trim(), stderr.trim()].filter(Boolean).join("\n");
1511
1526
  throw new Error(`docker compose ${action} exited ${code}` + (detail ? `\n${detail}` : ""));
1512
1527
  }
1528
+ if (action === "up") await waitComposeReady();
1513
1529
  await syncComposeBoard();
1514
1530
  };
1515
1531
 
@@ -1517,6 +1533,25 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
1517
1533
  const { launchDevLiveControls } = await import("./tui/DevLive.tsx");
1518
1534
  await launchDevLiveControls({
1519
1535
  onRefresh: () => refreshChrome(),
1536
+ onSeed: async () => {
1537
+ const { runSlashCli } = await import("./tui/slash-run.ts");
1538
+ boardPaused = true;
1539
+ const lines: string[] = [];
1540
+ try {
1541
+ const code = await runSlashCli(cwd, ["db", "seed", "--force"], (line) => {
1542
+ lines.push(line);
1543
+ write(line);
1544
+ });
1545
+ if (code === 0) return;
1546
+ const last = [...lines]
1547
+ .reverse()
1548
+ .map((line) => line.trim())
1549
+ .find((line) => line.length > 0 && !line.startsWith("✗") && !line.startsWith("$"));
1550
+ throw new Error(last || "oke db seed failed");
1551
+ } finally {
1552
+ boardPaused = false;
1553
+ }
1554
+ },
1520
1555
  onComposeUp: () => composeAction("up"),
1521
1556
  onComposeStop: () => composeAction("stop"),
1522
1557
  onQuit: () => {
@@ -1589,6 +1624,20 @@ function isSchemaDeclDefinitionError(err: unknown, declarePath: string): boolean
1589
1624
  return declarePath.length > 0 && blob.includes(declarePath);
1590
1625
  }
1591
1626
 
1627
+ /**
1628
+ * True when a `src/` watcher event is a flows tree change that should
1629
+ * regenerate `generated.ts` (never the generated file itself).
1630
+ *
1631
+ * @param rel - Watcher filename (POSIX or Windows)
1632
+ */
1633
+ export function isFlowsTreeWatchPath(rel: string): boolean {
1634
+ const posix = rel.replace(/\\/g, "/");
1635
+ if (!posix.includes("flows/")) return false;
1636
+ const base = posix.split("/").pop() ?? posix;
1637
+ if (base === "generated.ts" || base === "generated.ts.tmp") return false;
1638
+ return true;
1639
+ }
1640
+
1592
1641
  /**
1593
1642
  * Frame a schema-sync failure for the `oke dev` loop: warn loud for developer
1594
1643
  * bugs, soft-skip only known environmental gaps. Never crashes the session.
@@ -1652,6 +1701,29 @@ async function tryLoadProjectManifest(cwd: string): Promise<Manifest | null | un
1652
1701
  return extracted;
1653
1702
  }
1654
1703
 
1704
+ /**
1705
+ * SIGKILL a pid and every child (`bun --hot` leaves a worker).
1706
+ *
1707
+ * @param pid - Root pid
1708
+ */
1709
+ function killProcessTree(pid: number): void {
1710
+ const listed = Bun.spawnSync(["pgrep", "-P", String(pid)], {
1711
+ stdout: "pipe",
1712
+ stderr: "pipe",
1713
+ });
1714
+ if (listed.exitCode === 0) {
1715
+ for (const line of listed.stdout.toString().split("\n")) {
1716
+ const child = Number(line.trim());
1717
+ if (Number.isInteger(child) && child > 0) killProcessTree(child);
1718
+ }
1719
+ }
1720
+ try {
1721
+ process.kill(pid, "SIGKILL");
1722
+ } catch {
1723
+ // Already gone.
1724
+ }
1725
+ }
1726
+
1655
1727
  /**
1656
1728
  * Default app boot: `bun --hot` on the shipped runner (socket-preserving).
1657
1729
  *
@@ -1687,17 +1759,18 @@ async function startAppHot(
1687
1759
  });
1688
1760
 
1689
1761
  let stopped = false;
1690
- const stop = () => {
1691
- if (stopped) return;
1762
+ const stop = (): Promise<void> => {
1763
+ if (stopped) return proc.exited.then(() => {});
1692
1764
  stopped = true;
1693
1765
  try {
1694
1766
  // SIGKILL so `bun --hot` cannot linger and hold the project directory
1695
1767
  // open (create-oke afterEach `rmSync` otherwise races the child exit).
1696
- proc.kill("SIGKILL");
1768
+ killProcessTree(proc.pid);
1697
1769
  } catch {
1698
1770
  // Inconsequential: process already exited — kill is idempotent best-effort.
1699
1771
  }
1700
1772
  void unlink(readyPath).catch(() => {});
1773
+ return proc.exited.then(() => {});
1701
1774
  };
1702
1775
 
1703
1776
  try {
@@ -24,7 +24,8 @@ export default defineConfig({
24
24
  dialect: "postgresql",
25
25
  schema: "./src/db/schema.drizzle.ts",
26
26
  out: "./src/db/migrations",
27
- // Core runtime tables (oke_crons, …) are created by drivers not domain schema.
27
+ // Domain tables live in public. Schema \`oke\` (RLS) and \`oke_console\` are engine-owned.
28
+ schemaFilter: ["public"],
28
29
  tablesFilter: ["!oke_*"],
29
30
  dbCredentials: {
30
31
  url: process.env.DATABASE_URL ?? process.env.OKE_STORE_SQL_URL!,
@@ -13,6 +13,7 @@ import { TUI_BRAND, TUI_HINT, TUI_MUTED, TUI_WARN } from "./theme.ts";
13
13
  /** Props for {@link DevLiveControls}. */
14
14
  export type DevLiveControlsProps = {
15
15
  readonly onRefresh: () => void | Promise<void>;
16
+ readonly onSeed: () => void | Promise<void>;
16
17
  readonly onComposeUp: () => void | Promise<void>;
17
18
  readonly onComposeStop: () => void | Promise<void>;
18
19
  readonly onQuit: () => void;
@@ -49,6 +50,14 @@ export function DevLiveControls(props: DevLiveControlsProps): ReactElement {
49
50
  .finally(() => setBusy(false));
50
51
  return;
51
52
  }
53
+ if (ctrl === "s") {
54
+ setBusy(true);
55
+ void Promise.resolve(props.onSeed())
56
+ .then(() => setStatus("seeded"))
57
+ .catch((err: unknown) => setStatus(err instanceof Error ? err.message : String(err)))
58
+ .finally(() => setBusy(false));
59
+ return;
60
+ }
52
61
  if (ctrl === "u" || ctrl === "x") {
53
62
  setBusy(true);
54
63
  const run = ctrl === "u" ? props.onComposeUp : props.onComposeStop;
@@ -66,13 +75,13 @@ export function DevLiveControls(props: DevLiveControlsProps): ReactElement {
66
75
  oke dev
67
76
  </Text>
68
77
  <Text color={TUI_MUTED}> · controls · </Text>
69
- <Text color={TUI_HINT}>? help · r refresh · q quit · u up · x stop</Text>
78
+ <Text color={TUI_HINT}>? help · r refresh · s seed · q quit · u up · x stop</Text>
70
79
  {busy ? <Text color={TUI_WARN}> · working…</Text> : null}
71
80
  {status ? <Text color={TUI_MUTED}>{` · ${status}`}</Text> : null}
72
81
  </Text>
73
82
  {help ? (
74
83
  <Text color={TUI_MUTED}>
75
- r clear/refresh board · q quit · u compose up · x compose stop
84
+ r clear/refresh board · s oke db seed · q quit · u compose up · x compose stop
76
85
  </Text>
77
86
  ) : null}
78
87
  </Box>
@@ -104,6 +113,7 @@ export async function launchDevLiveControls(props: DevLiveControlsProps): Promis
104
113
 
105
114
  const bound: DevLiveControlsProps = {
106
115
  onRefresh: () => afterStdout(props.onRefresh),
116
+ onSeed: () => afterStdout(props.onSeed),
107
117
  onComposeUp: () => afterStdout(props.onComposeUp),
108
118
  onComposeStop: () => afterStdout(props.onComposeStop),
109
119
  onQuit: props.onQuit,
@@ -2,10 +2,11 @@ import { describe, expect, test } from "bun:test";
2
2
  import { mapDevControlInput } from "./keys.ts";
3
3
 
4
4
  describe("mapDevControlInput", () => {
5
- test("maps ? r q u x and aliases", () => {
5
+ test("maps ? r s q u x and aliases", () => {
6
6
  expect(mapDevControlInput("?", {})).toBe("?");
7
7
  expect(mapDevControlInput("h", {})).toBe("?");
8
8
  expect(mapDevControlInput("r", {})).toBe("r");
9
+ expect(mapDevControlInput("s", {})).toBe("s");
9
10
  expect(mapDevControlInput("q", {})).toBe("q");
10
11
  expect(mapDevControlInput("u", {})).toBe("u");
11
12
  expect(mapDevControlInput("x", {})).toBe("x");
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * Shared keyboard mapping for Ink `useInput` and pure tests.
3
- * Parity with legacy `parseDevControlKey` (`? r q u x`).
3
+ * Parity with legacy `parseDevControlKey` (`? r s q u x`).
4
4
  */
5
5
 
6
6
  /** Dev control action from a keypress. */
7
- export type DevControlKey = "?" | "r" | "q" | "u" | "x";
7
+ export type DevControlKey = "?" | "r" | "s" | "q" | "u" | "x";
8
8
 
9
9
  /**
10
10
  * Map Ink input (+ ctrl) to a control key.
@@ -20,6 +20,7 @@ export function mapDevControlInput(
20
20
  if (input === "?" || input === "h" || input === "H") return "?";
21
21
  if (input === "q" || input === "Q") return "q";
22
22
  if (input === "r" || input === "R") return "r";
23
+ if (input === "s" || input === "S") return "s";
23
24
  if (input === "u" || input === "U") return "u";
24
25
  if (input === "x" || input === "X") return "x";
25
26
  return null;
@@ -129,7 +129,7 @@ describe("typed error narrowing end-to-end", () => {
129
129
  const FlightFull = z.object({ seatsLeft: z.number() });
130
130
 
131
131
  on(
132
- http.post("/bookings").gate.public,
132
+ http.post("/bookings").public(),
133
133
  flow("bookings.create", {
134
134
  in: Booking,
135
135
  out: z.object({ id: z.string() }),
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import type {
10
+ AuthApiKeysResourceRef,
10
11
  Effects,
11
12
  PromptRef,
12
13
  ResourceRef,
@@ -145,8 +146,8 @@ const TABLE_ARG_METHODS = new Set([
145
146
  * @param options - Handler AST, bindings, and annotation flag
146
147
  */
147
148
  export function inferEffects(options: InferEffectsOptions): InferredEffects {
148
- const reads = new Set<ResourceRef | SignalResourceRef>();
149
- const writes = new Set<ResourceRef>();
149
+ const reads = new Set<ResourceRef | SignalResourceRef | AuthApiKeysResourceRef>();
150
+ const writes = new Set<ResourceRef | AuthApiKeysResourceRef>();
150
151
  const emits = new Set<SignalRef>();
151
152
  const sends = new Set<TemplateRef>();
152
153
  const asks = new Set<PromptRef>();
@@ -217,6 +218,20 @@ export function inferEffects(options: InferEffectsOptions): InferredEffects {
217
218
  continue;
218
219
  }
219
220
 
221
+ if (chain.rootMethod === "auth" && call === chain.rootCall) {
222
+ const method = chain.methods[1];
223
+ if (method === "listApiKeys") reads.add("auth:api-keys");
224
+ if (
225
+ method === "createApiKey" ||
226
+ method === "revokeApiKey" ||
227
+ method === "rotateApiKey" ||
228
+ method === "updateApiKey"
229
+ ) {
230
+ writes.add("auth:api-keys");
231
+ }
232
+ continue;
233
+ }
234
+
220
235
  if (chain.rootMethod === "search" && call === chain.rootCall) {
221
236
  const ref = resolveNamed(call.arguments[0], options.bindings, "embed");
222
237
  if (ref) {