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
@@ -105,22 +105,24 @@ describe("extractManifest — fx.raw", () => {
105
105
  });
106
106
 
107
107
  describe("extractManifest — performance", () => {
108
- test("200-flow synthetic app extracts in under 2s", async () => {
109
- const sources: Record<string, string> = {};
110
- const parts: string[] = [
111
- `import { on, flow, http, store } from "okengine";`,
112
- `export const db = store.sql("db");`,
113
- `export const items = { name: "items" };`,
114
- `export const app = { name: "synth" };`,
115
- ];
116
- // Declare oke name via a tiny app file pattern
117
- sources["src/app.ts"] = `
108
+ test(
109
+ "200-flow synthetic app extracts in under 2s",
110
+ async () => {
111
+ const sources: Record<string, string> = {};
112
+ const parts: string[] = [
113
+ `import { on, flow, http, store } from "okengine";`,
114
+ `export const db = store.sql("db");`,
115
+ `export const items = { name: "items" };`,
116
+ `export const app = { name: "synth" };`,
117
+ ];
118
+ // Declare oke name via a tiny app file pattern
119
+ sources["src/app.ts"] = `
118
120
  import { oke } from "okengine";
119
121
  export const app = oke({ name: "synth200" });
120
122
  `;
121
123
 
122
- for (let i = 0; i < 200; i++) {
123
- parts.push(`
124
+ for (let i = 0; i < 200; i++) {
125
+ parts.push(`
124
126
  export const flow_${i} = on(
125
127
  http.get("/f/${i}"),
126
128
  flow("synth.flow_${i}", {
@@ -132,19 +134,22 @@ export const flow_${i} = on(
132
134
  }),
133
135
  );
134
136
  `);
135
- }
136
- sources["src/flows/all.ts"] = parts.join("\n");
137
-
138
- await extractFromSources(sources);
139
- const start = performance.now();
140
- const manifest = await extractFromSources(sources);
141
- const elapsed = performance.now() - start;
142
-
143
- expect(Object.keys(manifest.flows ?? {}).length).toBe(200);
144
- // Local ~400ms after warmup. GHA shared runners measured ~2.9s cold.
145
- const budgetMs = process.env.CI ? 4_000 : 2_000;
146
- expect(elapsed).toBeLessThan(budgetMs);
147
- });
137
+ }
138
+ sources["src/flows/all.ts"] = parts.join("\n");
139
+
140
+ await extractFromSources(sources);
141
+ const start = performance.now();
142
+ const manifest = await extractFromSources(sources);
143
+ const elapsed = performance.now() - start;
144
+
145
+ expect(Object.keys(manifest.flows ?? {}).length).toBe(200);
146
+ // Local ~400ms after warmup. GHA shared runners measured ~2.9s cold.
147
+ // Warmup + extract exceeds Bun's 5s default (v0.15.0 tag timed out at 5.6s).
148
+ const budgetMs = process.env.CI ? 4_000 : 2_000;
149
+ expect(elapsed).toBeLessThan(budgetMs);
150
+ },
151
+ { timeout: 15_000 },
152
+ );
148
153
  });
149
154
 
150
155
  describe("serializeManifest stability", () => {
@@ -241,6 +246,32 @@ export const db = store.sql("app", { schema: { notes } });
241
246
  });
242
247
  });
243
248
 
249
+ test("extracts RLS helpers onto store.tables", async () => {
250
+ const source = `
251
+ import { store, field } from "okengine";
252
+
253
+ export const bookings = store.schema.table("bookings", {
254
+ id: field.text().primaryKey(),
255
+ owner: field.text().notNull(),
256
+ }, [
257
+ store.schema.policy.gate("member", { for: "select" }),
258
+ store.schema.policy.owner("owner"),
259
+ ]);
260
+
261
+ export const db = store.sql("app", { schema: { bookings } });
262
+ `;
263
+ const manifest = await extractFromSources({
264
+ "src/schema.decl.ts": source,
265
+ });
266
+ const table = manifest.stores?.app?.tables?.bookings;
267
+ expect(table?.rls).toBe(true);
268
+ expect(table?.policies?.gate_member_select).toMatchObject({
269
+ for: "select",
270
+ using: "oke.gate() = 'member'",
271
+ });
272
+ expect(table?.policies?.owner_owner_all?.using).toContain("oke.user()");
273
+ });
274
+
244
275
  test("extracts optional description fields additively", async () => {
245
276
  const source = `
246
277
  import { store, field, signal, channel, clock, gate, vault } from "okengine";
@@ -468,6 +499,27 @@ export const remove = mounted.remove;
468
499
  expect(manifest.flows?.update?.live).toBeUndefined();
469
500
  expect(manifest.flows?.remove?.live).toBeUndefined();
470
501
  });
502
+
503
+ test("chains .public().live() onto the five verbs", async () => {
504
+ const source = `
505
+ import { on, http, store } from "okengine";
506
+
507
+ export const db = store.sql("notes", { schema: {} });
508
+ const notesR = store.resource(db, {}, {});
509
+ const mounted = on(http.resource("/notes", notesR.all()).public().live());
510
+
511
+ export const list = mounted.list;
512
+ export const create = mounted.create;
513
+ export const get = mounted.get;
514
+ export const update = mounted.update;
515
+ export const remove = mounted.remove;
516
+ `;
517
+ const manifest = await extractFromSources({ "src/flows/notes.ts": source });
518
+ expect(manifest.flows?.list?.gates).toEqual(["public"]);
519
+ expect(manifest.flows?.create?.gates).toEqual(["public"]);
520
+ expect(manifest.flows?.get?.live).toBe(true);
521
+ expect(manifest.flows?.create?.live).toBeUndefined();
522
+ });
471
523
  });
472
524
 
473
525
  describe("extractManifest — vault.config", () => {
@@ -512,7 +564,7 @@ import { on, flow, http, gate, store } from "okengine";
512
564
  export const files = store.files("uploads");
513
565
 
514
566
  export const attach = on(
515
- http.post("/attach").gate.public,
567
+ http.post("/attach").public(),
516
568
  flow("notes.attach", {
517
569
  do: async (input, fx) => {
518
570
  await fx.store(files).put("key", input.text);
@@ -533,7 +585,7 @@ import { on, flow, http, gate, store } from "okengine";
533
585
  export const files = store.files("uploads");
534
586
 
535
587
  export const attach = on(
536
- http.post("/attach").gate.public,
588
+ http.post("/attach").public(),
537
589
  flow("notes.attachImage", {
538
590
  do: async (input, fx) => {
539
591
  await fx.store(files).putImage("key", input.bytes);
@@ -609,7 +661,7 @@ import { on, flow, http, signal } from "okengine";
609
661
  export const notify = signal("notify", { delivery: "once" });
610
662
 
611
663
  export const failed = on(
612
- http.get("/notifications/failed").gate.public,
664
+ http.get("/notifications/failed").public(),
613
665
  flow("notifications.failed", {
614
666
  do: async (input, fx) => fx.json.withQuery(await fx.deadLetters(notify), input),
615
667
  }),
@@ -632,7 +684,7 @@ export const noteCreatedMail = mail.template("note-created", {
632
684
  });
633
685
 
634
686
  export const onCreated = on(
635
- http.post("/hook").gate.public,
687
+ http.post("/hook").public(),
636
688
  flow("notes.onCreated", {
637
689
  do: async (payload, fx) => {
638
690
  await fx.send(noteCreatedMail, { to: "you@localhost", data: payload });
@@ -655,7 +707,7 @@ const otp = channel.sms({});
655
707
  export const otpTemplate = otp.template("otp-code", {});
656
708
 
657
709
  export const send = on(
658
- http.post("/otp").gate.public,
710
+ http.post("/otp").public(),
659
711
  flow("notes.sendOtp", {
660
712
  do: async (payload, fx) => {
661
713
  await fx.send(otpTemplate, { to: "+10000000000", data: payload });
@@ -738,19 +790,32 @@ export const decoy = flow("checkout.realName", {
738
790
  });
739
791
  });
740
792
 
741
- describe("extractManifest — .gate.public", () => {
742
- test("reads the public sentinel from a member chain", async () => {
793
+ describe("extractManifest — .public()", () => {
794
+ test("reads the public sentinel from .public()", async () => {
743
795
  const source = `
744
796
  import { on, flow, http } from "okengine";
745
797
 
746
798
  export const health = on(
747
- http.get("/health").gate.public,
799
+ http.get("/health").public(),
748
800
  flow("health.check", { do: () => ({ ok: true }) }),
749
801
  );
750
802
  `;
751
803
  const manifest = await extractFromSources({ "src/flows/health.ts": source });
752
804
  expect(manifest.flows?.["health.check"]?.gates).toEqual(["public"]);
753
805
  });
806
+
807
+ test("does not treat the removed .gate.public property as attach", async () => {
808
+ const source = `
809
+ import { on, flow, http } from "okengine";
810
+
811
+ export const health = on(
812
+ http.get("/health").gate.public,
813
+ flow("health.check", { do: () => ({ ok: true }) }),
814
+ );
815
+ `;
816
+ const manifest = await extractFromSources({ "src/flows/health.ts": source });
817
+ expect(manifest.flows?.["health.check"]?.gates).toBeUndefined();
818
+ });
754
819
  });
755
820
 
756
821
  describe("extractManifest — gate.all", () => {
@@ -829,3 +894,314 @@ export const triage = on(
829
894
  expect(manifest.flows?.["support.triage"]?.effects?.calls).toEqual(["mcp:github/create_issue"]);
830
895
  });
831
896
  });
897
+
898
+ describe("extractManifest — file-tree stamps", () => {
899
+ test("pathless http.get() in notes/[id]/get.ts infers /notes/:id and notes.get", async () => {
900
+ const source = `
901
+ import { on, flow, http } from "okengine";
902
+ export const get = on(
903
+ http.get().public(),
904
+ flow({ do: (input) => input }),
905
+ );
906
+ `;
907
+ const manifest = await extractFromSources({ "src/flows/notes/[id]/get.ts": source });
908
+ expect(manifest.flows?.["notes.get"]?.trigger).toEqual({
909
+ http: { method: "GET", path: "/notes/:id" },
910
+ });
911
+ });
912
+
913
+ test("nameless flow({ do }) still appears on the Manifest", async () => {
914
+ const source = `
915
+ import { flow } from "okengine";
916
+ export const ping = flow({ do: () => ({ ok: true }) });
917
+ `;
918
+ const manifest = await extractFromSources({ "src/flows/notes/ping.ts": source });
919
+ expect(manifest.flows?.["notes.ping"]).toBeDefined();
920
+ });
921
+
922
+ test("effects are identical for the same do body in a tree file vs a barrel", async () => {
923
+ const doBody = `
924
+ const rows = await fx.store(db).select().from(notes);
925
+ await fx.emit(noteCreated, { id: "1" });
926
+ await fx.call("notes.ping");
927
+ return rows;
928
+ `;
929
+ const barrel = `
930
+ import { on, flow, http, store, signal } from "okengine";
931
+ export const db = store.sql("db");
932
+ export const notes = { name: "notes" };
933
+ export const noteCreated = signal("note-created");
934
+ export const get = on(
935
+ http.get("/notes/:id").public(),
936
+ flow("notes.get", {
937
+ do: async (_input, fx) => {${doBody}},
938
+ }),
939
+ );
940
+ `;
941
+ const tree = `
942
+ import { on, flow, http, store, signal } from "okengine";
943
+ export const db = store.sql("db");
944
+ export const notes = { name: "notes" };
945
+ export const noteCreated = signal("note-created");
946
+ export const get = on(
947
+ http.get().public(),
948
+ flow({
949
+ do: async (_input, fx) => {${doBody}},
950
+ }),
951
+ );
952
+ `;
953
+ const barrelManifest = await extractFromSources({ "src/flows/notes/index.ts": barrel });
954
+ const treeManifest = await extractFromSources({ "src/flows/notes/[id]/get.ts": tree });
955
+ const barrelFx = barrelManifest.flows?.["notes.get"]?.effects;
956
+ const treeFx = treeManifest.flows?.["notes.get"]?.effects;
957
+ expect(treeFx).toEqual(barrelFx);
958
+ expect(treeFx?.reads).toBeDefined();
959
+ expect(treeFx?.emits).toBeDefined();
960
+ expect(treeFx?.calls).toBeDefined();
961
+ expect(treeFx?.reads?.length).toBeGreaterThan(0);
962
+ expect(treeFx?.emits?.length).toBeGreaterThan(0);
963
+ expect(treeFx?.calls?.length).toBeGreaterThan(0);
964
+ });
965
+ });
966
+
967
+ describe("extractManifest — flow in/out schema expansion", () => {
968
+ test("expands z.object identifiers into JSON Schema fields", async () => {
969
+ const source = `
970
+ import { flow } from "okengine";
971
+ import { z } from "zod";
972
+
973
+ export const TaskCreateIn = z.object({
974
+ title: z.string().min(1).max(500),
975
+ spaceKey: z.string().min(1),
976
+ priority: z.number().int().min(0).max(4).optional(),
977
+ });
978
+
979
+ export const TaskCreateOut = z.object({
980
+ id: z.string(),
981
+ identifier: z.string(),
982
+ });
983
+
984
+ export const create = flow("tasks.create", {
985
+ in: TaskCreateIn,
986
+ out: TaskCreateOut,
987
+ do: async () => ({ id: "1", identifier: "ENG-1" }),
988
+ });
989
+ `;
990
+ const manifest = await extractFromSources({ "src/flows/tasks/shapes.ts": source });
991
+ expect(manifest.flows?.["tasks.create"]?.in).toEqual({
992
+ type: "object",
993
+ properties: {
994
+ title: { type: "string", minLength: 1, maxLength: 500 },
995
+ spaceKey: { type: "string", minLength: 1 },
996
+ priority: { type: "integer", minimum: 0, maximum: 4 },
997
+ },
998
+ required: ["title", "spaceKey"],
999
+ });
1000
+ expect(manifest.flows?.["tasks.create"]?.out).toEqual({
1001
+ type: "object",
1002
+ properties: {
1003
+ id: { type: "string" },
1004
+ identifier: { type: "string" },
1005
+ },
1006
+ required: ["id", "identifier"],
1007
+ });
1008
+ });
1009
+
1010
+ test("expands drizzle-orm/zod createSelect/Insert/UpdateSchema from table columns", async () => {
1011
+ const schema = `
1012
+ import { store, field, id, now } from "okengine";
1013
+
1014
+ export const users = store.schema.table("users", {
1015
+ id: field.text().primaryKey().defaultFn(id),
1016
+ name: field.text().notNull(),
1017
+ age: field.integer().notNull(),
1018
+ bio: field.text(),
1019
+ createdAt: field.integer().notNull().defaultFn(now),
1020
+ });
1021
+ `;
1022
+ const flows = `
1023
+ import { flow } from "okengine";
1024
+ import { createInsertSchema, createSelectSchema, createUpdateSchema } from "drizzle-orm/zod";
1025
+ import { users } from "../schema.decl.ts";
1026
+
1027
+ export const userSelect = createSelectSchema(users);
1028
+ export const userInsert = createInsertSchema(users);
1029
+ export const userUpdate = createUpdateSchema(users);
1030
+
1031
+ export const create = flow("users.create", {
1032
+ in: userInsert,
1033
+ out: userSelect,
1034
+ do: async () => ({}),
1035
+ });
1036
+
1037
+ export const patch = flow("users.patch", {
1038
+ in: userUpdate,
1039
+ do: async () => ({}),
1040
+ });
1041
+ `;
1042
+ const manifest = await extractFromSources({
1043
+ "src/schema.decl.ts": schema,
1044
+ "src/flows/users.ts": flows,
1045
+ });
1046
+ const insert = manifest.flows?.["users.create"]?.in as {
1047
+ properties?: Record<string, unknown>;
1048
+ required?: string[];
1049
+ };
1050
+ expect(insert?.properties).toMatchObject({
1051
+ name: { type: "string" },
1052
+ age: { type: "integer" },
1053
+ bio: { type: "string" },
1054
+ createdAt: { type: "integer" },
1055
+ });
1056
+ expect(insert?.properties?.id).toBeUndefined();
1057
+ expect(insert?.required).toEqual(["name", "age"]);
1058
+
1059
+ const select = manifest.flows?.["users.create"]?.out as {
1060
+ properties?: Record<string, unknown>;
1061
+ required?: string[];
1062
+ };
1063
+ expect(select?.properties?.id).toMatchObject({ type: "string", primaryKey: true });
1064
+ expect(select?.required).toEqual(expect.arrayContaining(["id", "name", "age", "createdAt"]));
1065
+ expect(select?.required).not.toContain("bio");
1066
+
1067
+ const update = manifest.flows?.["users.patch"]?.in as {
1068
+ properties?: Record<string, unknown>;
1069
+ required?: string[];
1070
+ };
1071
+ expect(update?.properties?.name).toMatchObject({ type: "string" });
1072
+ expect(update?.properties?.id).toBeUndefined();
1073
+ expect(update?.required).toBeUndefined();
1074
+ });
1075
+
1076
+ test("expands tableZod(table).select / .insert and .pick()", async () => {
1077
+ const schema = `
1078
+ import { store, field, id } from "okengine";
1079
+ export const spaces = store.schema.table("spaces", {
1080
+ id: field.text().primaryKey().defaultFn(id),
1081
+ key: field.text().notNull().unique(),
1082
+ name: field.text().notNull(),
1083
+ color: field.text(),
1084
+ });
1085
+ `;
1086
+ const zod = `
1087
+ import { createInsertSchema, createSelectSchema, createUpdateSchema } from "drizzle-orm/zod";
1088
+ export function tableZod(table) {
1089
+ return {
1090
+ select: createSelectSchema(table),
1091
+ insert: createInsertSchema(table),
1092
+ update: createUpdateSchema(table),
1093
+ };
1094
+ }
1095
+ export const spacesZod = tableZod(spaces);
1096
+ `;
1097
+ const flows = `
1098
+ import { flow } from "okengine";
1099
+ export const create = flow("spaces.create", {
1100
+ in: spacesZod.insert,
1101
+ out: spacesZod.select.pick({ id: true, key: true }),
1102
+ do: async () => ({}),
1103
+ });
1104
+ `;
1105
+ const manifest = await extractFromSources({
1106
+ "src/schema.decl.ts": schema,
1107
+ "src/db/zod.ts": zod,
1108
+ "src/flows/spaces.ts": flows,
1109
+ });
1110
+ expect(manifest.flows?.["spaces.create"]?.in).toMatchObject({
1111
+ type: "object",
1112
+ properties: {
1113
+ key: { type: "string", unique: true },
1114
+ name: { type: "string" },
1115
+ color: { type: "string" },
1116
+ },
1117
+ required: ["key", "name"],
1118
+ });
1119
+ expect(manifest.flows?.["spaces.create"]?.out).toEqual({
1120
+ type: "object",
1121
+ properties: {
1122
+ id: { type: "string", primaryKey: true },
1123
+ key: { type: "string", unique: true },
1124
+ },
1125
+ required: ["id", "key"],
1126
+ });
1127
+ });
1128
+
1129
+ test("bindCrud stamps createIn / out onto CRUD flows", async () => {
1130
+ const source = `
1131
+ import { store, field, id } from "okengine";
1132
+ import { z } from "zod";
1133
+
1134
+ export const notes = store.schema.table("notes", {
1135
+ id: field.text().primaryKey().defaultFn(id),
1136
+ title: field.text().notNull(),
1137
+ });
1138
+
1139
+ const createIn = z.object({
1140
+ title: z.string().min(1),
1141
+ });
1142
+
1143
+ export const { list, create, get, update, remove } = bindCrud({
1144
+ unit: "notes",
1145
+ path: "/notes",
1146
+ table: notes,
1147
+ createIn,
1148
+ out: createSelectSchema(notes),
1149
+ });
1150
+ `;
1151
+ const manifest = await extractFromSources({ "src/flows/notes/index.ts": source });
1152
+ expect(manifest.flows?.["notes.create"]?.in).toEqual({
1153
+ type: "object",
1154
+ properties: { title: { type: "string", minLength: 1 } },
1155
+ required: ["title"],
1156
+ });
1157
+ expect(manifest.flows?.["notes.get"]?.out).toMatchObject({
1158
+ type: "object",
1159
+ properties: {
1160
+ id: { type: "string", primaryKey: true },
1161
+ title: { type: "string" },
1162
+ },
1163
+ });
1164
+ expect(manifest.flows?.["notes.list"]?.in).toMatchObject({
1165
+ type: "object",
1166
+ properties: { q: { type: "string" }, limit: { type: "integer" } },
1167
+ });
1168
+ expect((manifest.flows?.["notes.update"]?.in as { required?: string[] }).required).toContain(
1169
+ "id",
1170
+ );
1171
+ });
1172
+
1173
+ test("same-named createIn stays file-local (does not leak across units)", async () => {
1174
+ const spaces = `
1175
+ import { flow } from "okengine";
1176
+ import { z } from "zod";
1177
+ const createIn = z.object({ key: z.string(), name: z.string() });
1178
+ export const create = flow("spaces.create", {
1179
+ in: createIn,
1180
+ do: async () => ({}),
1181
+ });
1182
+ `;
1183
+ const views = `
1184
+ import { flow } from "okengine";
1185
+ import { z } from "zod";
1186
+ const createIn = z.object({ kind: z.enum(["list", "board"]) });
1187
+ export const create = flow("views.create", {
1188
+ in: createIn,
1189
+ do: async () => ({}),
1190
+ });
1191
+ `;
1192
+ const manifest = await extractFromSources({
1193
+ "src/flows/spaces/index.ts": spaces,
1194
+ "src/flows/views/index.ts": views,
1195
+ });
1196
+ expect(manifest.flows?.["spaces.create"]?.in).toEqual({
1197
+ type: "object",
1198
+ properties: { key: { type: "string" }, name: { type: "string" } },
1199
+ required: ["key", "name"],
1200
+ });
1201
+ expect(manifest.flows?.["views.create"]?.in).toEqual({
1202
+ type: "object",
1203
+ properties: { kind: { type: "string", enum: ["list", "board"] } },
1204
+ required: ["kind"],
1205
+ });
1206
+ });
1207
+ });