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
@@ -44,7 +44,7 @@ describe("automatic tier-1 cache from effects", () => {
44
44
  const db = store.sql("app", { schema: { notes } });
45
45
  let lists = 0;
46
46
  const list = on(
47
- http.get("/notes").gate.public,
47
+ http.get("/notes").public(),
48
48
  flow("notes.list", {
49
49
  out: z.array(NoteOut),
50
50
  effects: { reads: ["sql:notes"] },
@@ -80,7 +80,7 @@ describe("automatic tier-1 cache from effects", () => {
80
80
  resetFlowSeq();
81
81
  const db = store.sql("app", { schema: { notes } });
82
82
  const list = on(
83
- http.get("/notes").gate.public,
83
+ http.get("/notes").public(),
84
84
  flow("notes.list", {
85
85
  out: z.array(NoteOut),
86
86
  effects: { reads: ["sql:notes"] },
@@ -115,7 +115,7 @@ describe("automatic tier-1 cache from effects", () => {
115
115
  const db = store.sql("app", { schema: { notes } });
116
116
  let lists = 0;
117
117
  const list = on(
118
- http.get("/notes").gate.public,
118
+ http.get("/notes").public(),
119
119
  flow("notes.list", {
120
120
  cache: false,
121
121
  effects: { reads: ["sql:notes"] },
@@ -146,7 +146,7 @@ describe("automatic tier-1 cache from effects", () => {
146
146
  const db = store.sql("app", { schema: { notes } });
147
147
  let lists = 0;
148
148
  const list = on(
149
- http.get("/notes").gate.public,
149
+ http.get("/notes").public(),
150
150
  flow("notes.list", {
151
151
  out: z.array(NoteOut),
152
152
  do: async (_input, fx) => {
@@ -191,7 +191,7 @@ describe("automatic tier-1 cache from effects", () => {
191
191
  const db = store.sql("app", { schema: { notes } });
192
192
  let lists = 0;
193
193
  const list = on(
194
- http.get("/notes").gate.public,
194
+ http.get("/notes").public(),
195
195
  flow("notes.list", {
196
196
  effects: { reads: ["sql:notes"] },
197
197
  do: () => {
@@ -201,7 +201,7 @@ describe("automatic tier-1 cache from effects", () => {
201
201
  }),
202
202
  );
203
203
  const create = on(
204
- http.post("/notes").gate.public,
204
+ http.post("/notes").public(),
205
205
  flow("notes.create", {
206
206
  in: z.object({ title: z.string() }),
207
207
  effects: { writes: ["sql:notes"] },
@@ -231,7 +231,7 @@ describe("automatic tier-1 cache from effects", () => {
231
231
  const db = store.sql("app", { schema: { notes } });
232
232
  let lists = 0;
233
233
  const list = on(
234
- http.get("/notes").gate.public,
234
+ http.get("/notes").public(),
235
235
  flow("notes.list", {
236
236
  out: z.array(NoteOut),
237
237
  do: async (_input, fx) => {
@@ -242,7 +242,7 @@ describe("automatic tier-1 cache from effects", () => {
242
242
  }),
243
243
  );
244
244
  const create = on(
245
- http.post("/notes").gate.public,
245
+ http.post("/notes").public(),
246
246
  flow("notes.create", {
247
247
  in: z.object({ title: z.string() }),
248
248
  do: async (input, fx) => {
@@ -1,17 +1,21 @@
1
1
  /**
2
- * `oke({...})` auto-populates `stores` / `secrets` / `signals` /
3
- * `channel.templates` from the same kind of module-evaluation registry
4
- * already proven for `on()`'s trigger drain (`registry-isolation.test.ts`).
2
+ * `oke({...})` auto-populates `stores` / `secrets` / `signals` / `clocks` /
3
+ * `gate.policies` / `channel.templates` from the same kind of
4
+ * module-evaluation registry already proven for `on()`'s trigger drain
5
+ * (`registry-isolation.test.ts`).
5
6
  *
6
- * `store.sql` / `store.files`, `vault.secret`, `signal()`, and medium-binder
7
- * `.template()` push into a shared registry (`src/kernel/element-registries.ts`)
8
- * at call time — zero change to how developers call them. `oke()` drains it
9
- * at construction under the same `registry: "consume" | "keep" | "ignore"`
10
- * switch that already governs bindings.
7
+ * `store.*`, `vault.secret`, `signal()`, `clock()`, `gate.policy` / `.scope`
8
+ * / `.rate`, and medium-binder `.template()` push into a shared registry
9
+ * (`src/kernel/element-registries.ts`) at call time — zero change to how
10
+ * developers call them. `oke()` drains it at construction under the same
11
+ * `registry: "consume" | "keep" | "ignore"` switch that already governs
12
+ * bindings.
11
13
  */
12
14
 
13
15
  import { describe, expect, test } from "bun:test";
14
16
  import { channel } from "../elements/channel/declare.ts";
17
+ import { clock } from "../elements/clock/declare.ts";
18
+ import { gate } from "../elements/gate/declare.ts";
15
19
  import { signal } from "../elements/signal/declare.ts";
16
20
  import { store } from "../elements/store/declare.ts";
17
21
  import { vault } from "../elements/vault/declare.ts";
@@ -20,7 +24,7 @@ import { flow } from "./flow.ts";
20
24
  import { on, resetBindings } from "./on.ts";
21
25
  import { http } from "./triggers.ts";
22
26
 
23
- describe("oke() auto-registry — stores/secrets/signals/channel.templates/ai", () => {
27
+ describe("oke() auto-registry — stores/secrets/signals/clocks/gates/channel.templates/ai", () => {
24
28
  test("before/after: zero explicit arrays boots identically to the explicit-array form", async () => {
25
29
  resetBindings();
26
30
 
@@ -161,6 +165,42 @@ describe("oke() auto-registry — stores/secrets/signals/channel.templates/ai",
161
165
  expect(app.bootResult?.vault?.contracts.has("IGNORED_SECRET")).toBeFalsy();
162
166
  });
163
167
 
168
+ test("store.kv / store.index / clock() / gate.policy auto-drain — no explicit oke() arrays", async () => {
169
+ resetBindings();
170
+ store.kv("drafts", { description: "Compose drafts" });
171
+ store.index("tasks", { description: "Task search" });
172
+ clock("daily-digest", { cron: "0 8 * * *", every: "1d", timezone: "UTC" });
173
+ const member = gate.policy("member", ({ auth }) => !!auth.verified);
174
+ gate.scope("task:write");
175
+ gate.rate({ max: 60, per: "1m", keyBy: "user" });
176
+
177
+ const ping = flow("auto.ping", {
178
+ do: () => ({ ok: true as const }),
179
+ });
180
+ on(http.get("/auto-ping").gate(member), ping);
181
+
182
+ const app = oke({
183
+ name: "auto-kv-clock-gate",
184
+ autoBoot: false,
185
+ startScheduler: false,
186
+ });
187
+
188
+ expect(app.$options.stores?.some((s) => s.ref === "kv:drafts")).toBe(true);
189
+ expect(app.$options.stores?.some((s) => s.ref === "index:tasks")).toBe(true);
190
+ expect(app.$options.clocks?.some((c) => c.name === "daily-digest")).toBe(true);
191
+ expect(app.$options.gate?.policies?.some((g) => "name" in g && g.name === "member")).toBe(true);
192
+ expect(app.$options.gate?.policies?.some((g) => "name" in g && g.name === "task:write")).toBe(
193
+ true,
194
+ );
195
+
196
+ await app.boot({ env: "test" });
197
+ expect(app.bootResult?.store?.declarations.has("kv:drafts")).toBe(true);
198
+ expect(app.bootResult?.store?.declarations.has("index:tasks")).toBe(true);
199
+ expect(app.bootResult?.clock?.declarations.has("daily-digest")).toBe(true);
200
+ expect(app.bootResult?.gate?.gates.has("member")).toBe(true);
201
+ expect(app.bootResult?.gate?.gates.has("task:write")).toBe(true);
202
+ });
203
+
164
204
  test('registry: "consume" (default) drains stores/secrets/signals/channel.templates — a later app does not inherit them', async () => {
165
205
  store.sql("leak-app", { schema: {} });
166
206
  vault.secret("LEAK_SECRET", { dev: "dev-leak" });
@@ -79,7 +79,7 @@ describe("boot — lazy element needs", () => {
79
79
  expect(needs.signal).toBe(false);
80
80
  });
81
81
 
82
- test("oke() Store-only graph stays under the prior 48 kB baseline", async () => {
82
+ test("oke() Store-only graph stays under the prior 50 kB baseline", async () => {
83
83
  const dir = await mkdtemp(join(tmpdir(), "oke-store-only-"));
84
84
  const entry = join(dir, "entry.ts");
85
85
  const appPath = join(import.meta.dir, "app.ts");
@@ -116,9 +116,9 @@ describe("boot — lazy element needs", () => {
116
116
  if (raw.byteLength === 0) continue;
117
117
  total += Bun.gzipSync(new Uint8Array(raw)).byteLength;
118
118
  }
119
- // Rebased after 0.12.0 store/http graph growth
120
- // (~47.6 kB gzip with export externals).
121
- expect(total).toBeLessThan(48_000);
119
+ // Rebased after host API-key persist + allowlist on `oke()`.
120
+ // (~50.3 kB gzip with export externals).
121
+ expect(total).toBeLessThan(51_000);
122
122
  } finally {
123
123
  await rm(dir, { recursive: true, force: true });
124
124
  }
@@ -227,7 +227,7 @@ describe("boot — HTTP gate wiring on the default path", () => {
227
227
  resetBindings();
228
228
  resetFlowSeq();
229
229
  on(
230
- http.get("/ping").gate.public,
230
+ http.get("/ping").public(),
231
231
  flow("ping-public", {
232
232
  do: () => ({ ok: true }),
233
233
  }),
@@ -101,10 +101,10 @@ export interface BootOptions {
101
101
  readonly gates?: readonly GateDecl[];
102
102
  /**
103
103
  * HTTP auth-posture enforcement at boot.
104
- * - `"deny"` (default) — every HTTP trigger must carry a gate or `.gate.public`
104
+ * - `"deny"` (default) — every HTTP trigger must carry a gate or `.public()`
105
105
  * - `"allow"` — skips the audit **only** when {@link env} is `"test"`;
106
106
  * ignored in local/prod/docker (never a production-wide bypass).
107
- * Migrate real apps with per-trigger `.gate.public`.
107
+ * Migrate real apps with per-trigger `.public()`.
108
108
  */
109
109
  readonly unguardedHttp?: "deny" | "allow";
110
110
  /** Signal declarations. */
@@ -47,7 +47,7 @@ const CreateOut = z.object({ ok: z.boolean() });
47
47
 
48
48
  function buildUnannotatedCreateFlow(db: ReturnType<typeof store.sql>) {
49
49
  return on(
50
- http.post("/notes").gate.public,
50
+ http.post("/notes").public(),
51
51
  flow("notes.create", {
52
52
  in: CreateIn,
53
53
  out: CreateOut,
@@ -140,7 +140,7 @@ describe("boot-level: table-ref resolution stays backward compatible", () => {
140
140
  resetFlowSeq();
141
141
  const db = store.sql("app", { schema: { notes } });
142
142
  const create = on(
143
- http.post("/notes").gate.public,
143
+ http.post("/notes").public(),
144
144
  flow("notes.create", {
145
145
  in: CreateIn,
146
146
  out: CreateOut,
@@ -193,7 +193,7 @@ import { on, flow, http, gate } from "okengine";
193
193
  const db = {} as any;
194
194
  const notes = {} as any;
195
195
  export const create = on(
196
- http.post("/notes").gate.public,
196
+ http.post("/notes").public(),
197
197
  flow("notes.create", {
198
198
  do: async (input, fx) => {
199
199
  await fx.store(db).insert(notes).values({ id: input.id, title: input.title, createdAt: 1 });
@@ -1,8 +1,9 @@
1
1
  /**
2
- * Module-evaluation registries for `store.sql` / `store.files`, `vault.secret`,
3
- * `vault.env.required`, `signal()`, `channel.<medium>().template()`, and
4
- * `ai.model` / `.prompt` / `ai.embed` / `ai.agent` / `ai.mcpServer` — the plain arrays behind
5
- * each declare module's `listX()` / `resetX()` pair (mirrors `on.ts`'s
2
+ * Module-evaluation registries for `store.*`, `vault.secret`,
3
+ * `vault.env.required`, `signal()`, `clock()`, `gate.policy` / `.scope` /
4
+ * `.rate`, `channel.<medium>().template()`, and `ai.model` / `.prompt` /
5
+ * `ai.embed` / `ai.agent` / `ai.mcpServer` the plain arrays behind each
6
+ * declare module's `listX()` / `resetX()` pair (mirrors `on.ts`'s
6
7
  * trigger-drain `bindings` array).
7
8
  *
8
9
  * Lives here, not in the declare modules themselves, so {@link oke} can read
@@ -24,8 +25,10 @@ import type { StoreDecl } from "../elements/store/declare.ts";
24
25
  import type { VaultSecretDecl } from "../elements/vault/declare.ts";
25
26
  import type { SignalDecl } from "../elements/signal/declare.ts";
26
27
  import type { ChannelTemplateDecl } from "../elements/channel/declare.ts";
28
+ import type { ClockDecl } from "../elements/clock/declare.ts";
29
+ import type { GateDecl } from "../elements/gate/declare.ts";
27
30
 
28
- /** `store.sql` / `store.files` declarations since the last reset. */
31
+ /** `store.sql` / `store.kv` / `store.files` / `store.index` declarations since the last reset. */
29
32
  export const storeRegistry: StoreDecl[] = [];
30
33
  /** `vault.secret` declarations since the last reset. */
31
34
  export const secretRegistry: VaultSecretDecl[] = [];
@@ -33,6 +36,10 @@ export const secretRegistry: VaultSecretDecl[] = [];
33
36
  export const requiredEnvRegistry: string[] = [];
34
37
  /** `signal()` declarations since the last reset. */
35
38
  export const signalRegistry: SignalDecl[] = [];
39
+ /** `clock()` declarations since the last reset. */
40
+ export const clockRegistry: ClockDecl[] = [];
41
+ /** `gate.policy` / `gate.scope` / `gate.rate` declarations since the last reset. */
42
+ export const gateRegistry: GateDecl[] = [];
36
43
  /** Medium-binder `.template()` declarations since the last reset. */
37
44
  export const channelTemplateRegistry: ChannelTemplateDecl[] = [];
38
45
  /** `ai.model` declarations since the last reset. */
@@ -183,6 +183,30 @@ export const OKE_ERRORS = {
183
183
  cause: "src/flows/{unit} exists on disk but adopted no flows — the .adopt() barrel is stale.",
184
184
  fix: "Run `oke dev` or `oke build` to regenerate `src/flows/generated.ts`.",
185
185
  },
186
+ /**
187
+ * `http.get()` was never stamped from the file tree — refuse a silent `/`.
188
+ */
189
+ HTTP_PATH_UNRESOLVED: {
190
+ code: 1010,
191
+ cause: 'Flow "{flow}" bound {method} with no path — the file-tree stamp never ran.',
192
+ fix: 'Put the file under `src/flows/<unit>/` and import `@/flows/generated`, or pass an explicit path to `http.{method}("/…")`.',
193
+ },
194
+ /**
195
+ * Two HTTP bindings share method + path — last-add-wins is the opposite of this DX.
196
+ */
197
+ HTTP_ROUTE_DUPLICATE: {
198
+ code: 1011,
199
+ cause: '{method} {path} is bound twice (flow "{flow}").',
200
+ fix: "Give each HTTP flow a unique method + path.",
201
+ },
202
+ /**
203
+ * Adopted HTTP flow still has no name (`flow({ do })` outside a unit).
204
+ */
205
+ HTTP_FLOW_UNNAMED: {
206
+ code: 1012,
207
+ cause: "An HTTP flow on {method} {path} has no name.",
208
+ fix: 'Use `flow("unit.export", {…})` or export it from a `src/flows/<unit>/` file so the tree can stamp `unit.export`.',
209
+ },
186
210
  /**
187
211
  * Emit target has no subscriber (unified-theory §21).
188
212
  * Thrown at emit when `optional` is false and nobody is subscribed.
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Module-evaluation registry for `src/flows/generated.ts`.
3
+ *
4
+ * Same consume/ignore posture as `on()` / `listBindings()`: generated.ts
5
+ * calls {@link registerFlowUnits}; {@link oke} drains the bag into `$routes`
6
+ * unless `registry: "ignore"`.
7
+ */
8
+
9
+ import type { AnyFlowDef } from "./flow.ts";
10
+ import { isFlow } from "./flow.ts";
11
+
12
+ /** Unit name → export name → Flow. */
13
+ export type FlowUnitBag = {
14
+ readonly [exportName: string]: unknown;
15
+ };
16
+
17
+ const pending: Record<string, FlowUnitBag> = {};
18
+
19
+ /**
20
+ * Record generated flow units for the next {@link oke} construction.
21
+ *
22
+ * @param units - `{ notes, main, … }` from `generated.ts`
23
+ */
24
+ export function registerFlowUnits(units: Record<string, FlowUnitBag>): void {
25
+ Object.assign(pending, units);
26
+ }
27
+
28
+ /**
29
+ * Drain registered units (consume). Clears the registry.
30
+ */
31
+ export function consumeRegisteredFlowUnits(): Record<string, FlowUnitBag> {
32
+ const snap: Record<string, FlowUnitBag> = { ...pending };
33
+ resetRegisteredFlowUnits();
34
+ return snap;
35
+ }
36
+
37
+ /**
38
+ * Snapshot without clearing (keep / tests).
39
+ */
40
+ export function peekRegisteredFlowUnits(): Record<string, FlowUnitBag> {
41
+ return { ...pending };
42
+ }
43
+
44
+ /**
45
+ * Clear the unit registry (tests / `registry: "consume"` after drain).
46
+ */
47
+ export function resetRegisteredFlowUnits(): void {
48
+ for (const key of Object.keys(pending)) {
49
+ delete pending[key];
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Collect FlowDefs from a unit bag (skip non-flow exports).
55
+ *
56
+ * @param bag - Namespace object
57
+ */
58
+ export function flowsInUnitBag(bag: FlowUnitBag): AnyFlowDef[] {
59
+ const found: AnyFlowDef[] = [];
60
+ for (const value of Object.values(bag)) {
61
+ if (isFlow(value)) found.push(value);
62
+ }
63
+ return found;
64
+ }
@@ -239,13 +239,34 @@ export const flowBrand: unique symbol = Symbol("oke.flow");
239
239
  * Input / output types are inferred from Standard Schema `in` / `out` when
240
240
  * present; otherwise from the `do` handler signature.
241
241
  *
242
+ * A nameless `flow({ do })` is stamped `unit.export` by the file-tree
243
+ * generator, {@link unit}, or `.adopt()`. Explicit `flow("notes.get", {…})`
244
+ * still wins.
245
+ *
246
+ * @param options - Contract and handler (name stamped later)
247
+ */
248
+ export function flow<Opts extends FlowOptions<any, any, any>>(
249
+ options: Opts,
250
+ ): FlowDef<InferFlowIn<Opts>, InferFlowOut<Opts>, InferFlowErrors<Opts>>;
251
+ /**
242
252
  * @param name - Stable name (used by `fx.call` and the Manifest)
243
253
  * @param options - Contract and handler
244
254
  */
245
255
  export function flow<Opts extends FlowOptions<any, any, any>>(
246
256
  name: string,
247
257
  options: Opts & { readonly name?: never },
248
- ): FlowDef<InferFlowIn<Opts>, InferFlowOut<Opts>, InferFlowErrors<Opts>> {
258
+ ): FlowDef<InferFlowIn<Opts>, InferFlowOut<Opts>, InferFlowErrors<Opts>>;
259
+ export function flow(
260
+ nameOrOptions: string | FlowOptions<any, any, any>,
261
+ maybeOptions?: FlowOptions<any, any, any> & { readonly name?: never },
262
+ ): FlowDef {
263
+ const named = typeof nameOrOptions === "string";
264
+ const name = named ? nameOrOptions : "";
265
+ const options = (named ? maybeOptions : nameOrOptions) as FlowOptions<any, any, any> | undefined;
266
+ if (!options || typeof options.do !== "function") {
267
+ throw new TypeError("flow() expected an options bag with a do handler");
268
+ }
269
+
249
270
  const triggers: Trigger[] = [];
250
271
  const hooks: Partial<Record<HookStage, HookFn[]>> = {};
251
272
  const pendingPlugins: PluginDef[] = [];
@@ -253,13 +274,13 @@ export function flow<Opts extends FlowOptions<any, any, any>>(
253
274
  const dot = name.indexOf(".");
254
275
  const unit = dot > 0 ? name.slice(0, dot) : undefined;
255
276
 
256
- const def: FlowDef<InferFlowIn<Opts>, InferFlowOut<Opts>, InferFlowErrors<Opts>> = {
277
+ const def: FlowDef = {
257
278
  [flowBrand]: true,
258
279
  name,
259
280
  unit,
260
281
  in: options.in,
261
282
  out: options.out,
262
- errors: (options.errors ?? undefined) as InferFlowErrors<Opts> | undefined,
283
+ errors: (options.errors ?? undefined) as FlowErrorMap | undefined,
263
284
  effects: options.effects,
264
285
  durable: options.durable ?? false,
265
286
  retry: options.retry,
@@ -268,17 +289,8 @@ export function flow<Opts extends FlowOptions<any, any, any>>(
268
289
  slo: options.slo,
269
290
  plane: options.plane,
270
291
  breaking: options.breaking ?? false,
271
- compensate: options.compensate as
272
- | ((
273
- ctx: {
274
- readonly input: InferFlowIn<Opts>;
275
- readonly error: unknown;
276
- readonly completedSteps: readonly string[];
277
- },
278
- fx: import("./fx.ts").Fx,
279
- ) => unknown | Promise<unknown>)
280
- | undefined,
281
- do: options.do as FlowHandler<InferFlowIn<Opts>, InferFlowOut<Opts>>,
292
+ compensate: options.compensate as FlowDef["compensate"],
293
+ do: options.do as FlowHandler,
282
294
  triggers,
283
295
  $trigger: undefined,
284
296
  hooks,
@@ -320,9 +332,9 @@ export function isFlow(value: unknown): value is AnyFlowDef {
320
332
  }
321
333
 
322
334
  /**
323
- * No-op. `name` is a required positional argument now, so there is no
324
- * auto-name sequence left to reset — kept so existing test `beforeEach`
325
- * blocks don't need touching.
335
+ * No-op. Nameless flows start as `""` and are stamped by unit / adopt /
336
+ * the file-tree generator — kept so existing test `beforeEach` blocks
337
+ * don't need touching.
326
338
  *
327
339
  * @internal
328
340
  */
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Session-only `fx.auth` key methods — issuer derived, never from flow input.
3
+ */
4
+
5
+ import { describe, expect, test } from "bun:test";
6
+ import { AttenuationError } from "../auth/attenuation.ts";
7
+ import { createApiKey, createApiKeyStore } from "../auth/api-keys.ts";
8
+ import { isFlowFailure } from "./hooks.ts";
9
+ import { createFx } from "./fx.ts";
10
+
11
+ describe("fx.auth key methods", () => {
12
+ test("createApiKey derives creator from the live session and rejects broader scopes", async () => {
13
+ const store = createApiKeyStore();
14
+ const fx = createFx({
15
+ flow: "keys.create",
16
+ effects: { writes: ["auth:api-keys"] },
17
+ apiKeyStore: store,
18
+ auth: { userId: "u1", scopes: new Set(["member"]), apiKeyId: null },
19
+ });
20
+ const created = await fx.auth.createApiKey({ name: "mine", scopes: ["member"] });
21
+ expect(created.key.scopes).toEqual(["member"]);
22
+ expect(created.secret.startsWith("oke_")).toBe(true);
23
+ expect(store.keys.get(created.key.id)?.creatorId).toBe("u1");
24
+
25
+ await expect(fx.auth.createApiKey({ name: "wide", scopes: ["admin"] })).rejects.toBeInstanceOf(
26
+ AttenuationError,
27
+ );
28
+ });
29
+
30
+ test("key-authenticated caller cannot create / list / revoke / rotate / update", async () => {
31
+ const store = createApiKeyStore();
32
+ await createApiKey(store, {
33
+ plane: "user",
34
+ name: "existing",
35
+ scopes: ["member"],
36
+ creatorId: "u1",
37
+ creatorScopes: ["member"],
38
+ id: "key_existing",
39
+ });
40
+ const fx = createFx({
41
+ flow: "keys.as-key",
42
+ effects: { reads: ["auth:api-keys"], writes: ["auth:api-keys"] },
43
+ apiKeyStore: store,
44
+ auth: { userId: "u1", scopes: new Set(["member"]), apiKeyId: "key_existing" },
45
+ });
46
+ for (const call of [
47
+ () => fx.auth.createApiKey({ name: "nested", scopes: ["member"] }),
48
+ () => fx.auth.listApiKeys(),
49
+ () => fx.auth.revokeApiKey("key_existing"),
50
+ () => fx.auth.rotateApiKey("key_existing"),
51
+ () => fx.auth.updateApiKey("key_existing", { name: "nope" }),
52
+ ]) {
53
+ try {
54
+ await call();
55
+ throw new Error("expected Forbidden");
56
+ } catch (err) {
57
+ expect(isFlowFailure(err)).toBe(true);
58
+ if (isFlowFailure(err)) expect(err.error.code).toBe("Forbidden");
59
+ }
60
+ }
61
+ });
62
+
63
+ test("list is self-only; cross-user revoke is Forbidden", async () => {
64
+ const store = createApiKeyStore();
65
+ await createApiKey(store, {
66
+ plane: "user",
67
+ name: "theirs",
68
+ scopes: ["member"],
69
+ creatorId: "u2",
70
+ creatorScopes: ["member"],
71
+ id: "key_theirs",
72
+ });
73
+ const fx = createFx({
74
+ flow: "keys.list",
75
+ effects: { reads: ["auth:api-keys"], writes: ["auth:api-keys"] },
76
+ apiKeyStore: store,
77
+ auth: { userId: "u1", scopes: new Set(["member"]), apiKeyId: null },
78
+ });
79
+ expect(await fx.auth.listApiKeys()).toEqual([]);
80
+ try {
81
+ await fx.auth.revokeApiKey("key_theirs");
82
+ throw new Error("expected Forbidden");
83
+ } catch (err) {
84
+ expect(isFlowFailure(err)).toBe(true);
85
+ if (isFlowFailure(err)) expect(err.error.code).toBe("Forbidden");
86
+ }
87
+ });
88
+ });