okengine 0.13.0 → 0.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (277) hide show
  1. package/manifest.v1.schema.json +7 -1
  2. package/package.json +4 -5
  3. package/site/content/docs/ai/mcp.mdx +27 -2
  4. package/site/content/docs/elements/ai.mdx +59 -11
  5. package/site/content/docs/elements/channel.mdx +1 -0
  6. package/site/content/docs/elements/clock.mdx +30 -12
  7. package/site/content/docs/elements/flow.mdx +9 -3
  8. package/site/content/docs/elements/gate.mdx +25 -22
  9. package/site/content/docs/elements/signal.mdx +71 -25
  10. package/site/content/docs/elements/store.mdx +87 -28
  11. package/site/content/docs/elements/vault.mdx +1 -0
  12. package/site/content/docs/get-started/basic-usage.mdx +22 -13
  13. package/site/content/docs/get-started/index.mdx +5 -0
  14. package/site/content/docs/get-started/installation.mdx +2 -2
  15. package/site/content/docs/get-started/introduction.mdx +11 -3
  16. package/site/content/docs/get-started/meta.json +1 -1
  17. package/site/content/docs/get-started/project-structure.mdx +932 -0
  18. package/site/content/docs/reference/cli.md +13 -10
  19. package/site/content/docs/reference/client.mdx +25 -21
  20. package/site/content/docs/reference/fx.mdx +27 -22
  21. package/site/content/docs/reference/security.md +1 -1
  22. package/src/auth/bindings.ts +14 -5
  23. package/src/auth/cross-plane.ts +10 -3
  24. package/src/cli/ai-setup/ai-setup.test.ts +62 -0
  25. package/src/cli/ai-setup/apply.ts +32 -1
  26. package/src/cli/ai-setup/recommend.test.ts +25 -0
  27. package/src/cli/ai-setup/recommend.ts +8 -3
  28. package/src/cli/attach-host-console.test.ts +3 -2
  29. package/src/cli/attach-host-console.ts +3 -0
  30. package/src/cli/build.test.ts +4 -2
  31. package/src/cli/db-seed.ts +48 -15
  32. package/src/cli/db.test.ts +104 -0
  33. package/src/cli/db.ts +122 -10
  34. package/src/cli/dev-controls.test.ts +20 -0
  35. package/src/cli/dev-controls.ts +13 -0
  36. package/src/cli/dev.test.ts +15 -4
  37. package/src/cli/dev.ts +102 -29
  38. package/src/cli/ensure-drizzle-config.ts +2 -1
  39. package/src/cli/tui/DevLive.tsx +12 -2
  40. package/src/cli/tui/keys.test.ts +2 -1
  41. package/src/cli/tui/keys.ts +3 -2
  42. package/src/compiler/aot.test.ts +1 -1
  43. package/src/compiler/effects-infer.ts +58 -3
  44. package/src/compiler/extract.test.ts +458 -29
  45. package/src/compiler/extract.ts +387 -69
  46. package/src/compiler/flow-path.test.ts +96 -0
  47. package/src/compiler/flow-path.ts +173 -0
  48. package/src/compiler/generate-adopt.test.ts +112 -3
  49. package/src/compiler/generate-adopt.ts +280 -26
  50. package/src/compiler/response.ts +45 -1
  51. package/src/compiler/schema-from-ast.ts +609 -0
  52. package/src/console/server/ai.ts +5 -2
  53. package/src/console/server/bind.ts +2 -2
  54. package/src/console/server/console.test.ts +3 -0
  55. package/src/console/server/flows.ts +121 -40
  56. package/src/console/server/invoke-user-flow.test.ts +3 -1
  57. package/src/console/server/invoke-user-flow.ts +17 -2
  58. package/src/console/server/iso-at.test.ts +9 -0
  59. package/src/console/server/iso-at.ts +11 -0
  60. package/src/console/server/security-headers.ts +6 -3
  61. package/src/console/server/serve.ts +17 -2
  62. package/src/console/server/spa-proxy.test.ts +1 -0
  63. package/src/console/server/sql-catalog.ts +147 -7
  64. package/src/console/server/sql-rls.test.ts +16 -0
  65. package/src/console/server/state.ts +8 -0
  66. package/src/console/server/store.test.ts +7 -4
  67. package/src/console/server/store.ts +142 -58
  68. package/src/console/ui-next/dist/assets/agent-disclosure-C0X1fbWF.js +1 -0
  69. package/src/console/ui-next/dist/assets/cache-glyph-F1FI122b.js +1 -0
  70. package/src/console/ui-next/dist/assets/{call-pii-button-bqkxMrJH.js → call-pii-button-DEDCl_j3.js} +1 -1
  71. package/src/console/ui-next/dist/assets/collapsible-LPqGvfoz.js +1 -0
  72. package/src/console/ui-next/dist/assets/{confirm-sheet-DDCRmG62.js → confirm-sheet-3ptDzXbz.js} +1 -1
  73. package/src/console/ui-next/dist/assets/{duration-tone-oiRxPVsZ.js → duration-tone-sC3lGABz.js} +1 -1
  74. package/src/console/ui-next/dist/assets/{explorer-empty-HjTnVQoR.js → explorer-empty-BvYrygyO.js} +1 -1
  75. package/src/console/ui-next/dist/assets/flows-page-Dluo1Bd7.js +1 -0
  76. package/src/console/ui-next/dist/assets/{highlighted-json-CvDPvveV.js → highlighted-json-CS_O8L-r.js} +1 -1
  77. package/src/console/ui-next/dist/assets/http-method--sWDdXSB.js +1 -0
  78. package/src/console/ui-next/dist/assets/index-Ca3HZMVq.js +66 -0
  79. package/src/console/ui-next/dist/assets/index-UtTDRQpS.css +2 -0
  80. package/src/console/ui-next/dist/assets/{link-DF7SZ9Ek.js → link-COeyggt-.js} +1 -1
  81. package/src/console/ui-next/dist/assets/observability-page-HK9-BO8z.js +4 -0
  82. package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +1 -0
  83. package/src/console/ui-next/dist/assets/replica-lag-DBIFFf7d.js +18 -0
  84. package/src/console/ui-next/dist/assets/sheet-form-Dnwa5oOR.js +1 -0
  85. package/src/console/ui-next/dist/assets/shortcut-keys-CoqwEIi0.js +1 -0
  86. package/src/console/ui-next/dist/assets/skeleton-BHcfDYcb.js +1 -0
  87. package/src/console/ui-next/dist/assets/store-page-BSohH3wM.js +41 -0
  88. package/src/console/ui-next/dist/assets/trace-detail-sheet-CFIGRnnA.js +2 -0
  89. package/src/console/ui-next/dist/assets/tree-expand-toggle-BgLl34w7.js +54 -0
  90. package/src/console/ui-next/dist/assets/units-page-B0cFE76A.js +1 -0
  91. package/src/console/ui-next/dist/assets/{use-vault-list-Cl79j_ku.js → use-vault-list-BLbZhzyF.js} +1 -1
  92. package/src/console/ui-next/dist/assets/vault-page-B1SbYe10.js +2 -0
  93. package/src/console/ui-next/dist/index.html +6 -6
  94. package/src/console/ui-next/seed-invoke-host.ts +2 -0
  95. package/src/console/ui-next/src/client.ts +35 -7
  96. package/src/console/ui-next/src/components/motion/table/index.tsx +16 -0
  97. package/src/console/ui-next/src/components/motion/table/types.ts +4 -0
  98. package/src/console/ui-next/src/components/ui/kbd.tsx +2 -2
  99. package/src/console/ui-next/src/features/flows/graph/build-flow-graph.test.ts +18 -0
  100. package/src/console/ui-next/src/features/flows/graph/build-flow-graph.ts +14 -1
  101. package/src/console/ui-next/src/features/flows/graph/neighborhood.test.ts +17 -0
  102. package/src/console/ui-next/src/features/flows/graph/neighborhood.ts +16 -3
  103. package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +3 -1
  104. package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +24 -0
  105. package/src/console/ui-next/src/features/flows/traces/http-method.ts +19 -0
  106. package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +9 -3
  107. package/src/console/ui-next/src/features/flows/traces/trace-detail.test.ts +29 -2
  108. package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +10 -3
  109. package/src/console/ui-next/src/features/observability/lib/ask-count.test.ts +25 -0
  110. package/src/console/ui-next/src/features/observability/lib/ask-count.ts +4 -1
  111. package/src/console/ui-next/src/features/store/detail/browse-section.tsx +45 -1
  112. package/src/console/ui-next/src/features/store/detail/resource-panel.tsx +78 -16
  113. package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +155 -14
  114. package/src/console/ui-next/src/features/store/explorer/store-tree.tsx +33 -133
  115. package/src/console/ui-next/src/features/store/files/file-preview.tsx +4 -1
  116. package/src/console/ui-next/src/features/store/grid/rls-policy-sheet.tsx +326 -165
  117. package/src/console/ui-next/src/features/store/grid/sql-insert-sheet.tsx +6 -0
  118. package/src/console/ui-next/src/features/store/grid/store-data-grid.tsx +50 -3
  119. package/src/console/ui-next/src/features/store/lib/files-meta.test.ts +3 -0
  120. package/src/console/ui-next/src/features/store/lib/files-meta.ts +12 -0
  121. package/src/console/ui-next/src/features/store/lib/query-defaults.test.ts +25 -0
  122. package/src/console/ui-next/src/features/store/lib/query-defaults.ts +85 -3
  123. package/src/console/ui-next/src/features/store/lib/query-gate.ts +30 -4
  124. package/src/console/ui-next/src/features/store/lib/rls-policy.test.ts +139 -0
  125. package/src/console/ui-next/src/features/store/lib/rls-policy.ts +223 -21
  126. package/src/console/ui-next/src/features/store/lib/schema-graph.ts +4 -0
  127. package/src/console/ui-next/src/features/store/lib/sql-catalog.test.ts +24 -0
  128. package/src/console/ui-next/src/features/store/lib/sql-catalog.ts +19 -0
  129. package/src/console/ui-next/src/features/store/lib/store-tree.test.ts +6 -0
  130. package/src/console/ui-next/src/features/store/lib/store-tree.ts +13 -0
  131. package/src/console/ui-next/src/features/store/query/query-console.tsx +48 -14
  132. package/src/console/ui-next/src/features/store/query/query-gate-menu.tsx +19 -20
  133. package/src/console/ui-next/src/features/store/query/query-gate-parts.tsx +2 -1
  134. package/src/console/ui-next/src/features/store/query/query-results.tsx +110 -83
  135. package/src/console/ui-next/src/features/store/schema/schema-table-node.tsx +17 -0
  136. package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +112 -72
  137. package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +12 -4
  138. package/src/console/ui-next/src/features/units/lib/contract-input.test.ts +206 -1
  139. package/src/console/ui-next/src/features/units/lib/contract-input.ts +55 -0
  140. package/src/console/ui-next/src/features/vault/lib/types.ts +1 -1
  141. package/src/docker/compose-health.test.ts +27 -0
  142. package/src/docker/compose-health.ts +19 -3
  143. package/src/docker/docker.test.ts +4 -1
  144. package/src/docker/dockerfile.ts +1 -1
  145. package/src/docker/recipes/pgdog.ts +2 -0
  146. package/src/drivers/ai-anthropic.ts +5 -0
  147. package/src/drivers/ai-ollama.ts +49 -30
  148. package/src/drivers/ai-openai-compatible.ts +57 -46
  149. package/src/drivers/ai-providers.test.ts +3 -0
  150. package/src/drivers/bun-native-completeness.test.ts +7 -9
  151. package/src/drivers/clock-postgres.ts +13 -9
  152. package/src/drivers/instances-postgres.ts +4 -6
  153. package/src/drivers/journal-postgres.ts +13 -9
  154. package/src/drivers/pg-rls.test.ts +87 -0
  155. package/src/drivers/pg-rls.ts +233 -7
  156. package/src/drivers/pglite.ts +26 -3
  157. package/src/drivers/postgres.test.ts +23 -0
  158. package/src/drivers/postgres.ts +133 -9
  159. package/src/drivers/redis.ts +11 -4
  160. package/src/drivers/signal-redis.ts +24 -14
  161. package/src/drivers/signal-types.ts +2 -1
  162. package/src/drivers/types.ts +10 -0
  163. package/src/elements/ai/declare.ts +109 -0
  164. package/src/elements/ai/errors.test.ts +5 -1
  165. package/src/elements/ai/errors.ts +30 -2
  166. package/src/elements/ai/eval.ts +4 -6
  167. package/src/elements/ai/mcp-client.test.ts +206 -0
  168. package/src/elements/ai/mcp-client.ts +362 -0
  169. package/src/elements/ai/mcp-http.ts +159 -0
  170. package/src/elements/ai/mcp-mock.ts +134 -0
  171. package/src/elements/ai/mcp-protocol.ts +234 -0
  172. package/src/elements/ai/mcp-stdio.test.ts +50 -0
  173. package/src/elements/ai/mcp-stdio.ts +212 -0
  174. package/src/elements/ai/mcp-transport.ts +70 -0
  175. package/src/elements/ai/runtime.ts +159 -29
  176. package/src/elements/ai.test.ts +139 -0
  177. package/src/elements/ai.ts +16 -0
  178. package/src/elements/clock/declare.ts +26 -1
  179. package/src/elements/clock/health.test.ts +43 -0
  180. package/src/elements/clock/runtime.ts +55 -2
  181. package/src/elements/clock/schedule.ts +71 -142
  182. package/src/elements/clock.test.ts +9 -40
  183. package/src/elements/clock.ts +6 -1
  184. package/src/elements/gate/boot.ts +5 -5
  185. package/src/elements/gate/declare.ts +36 -4
  186. package/src/elements/gate/flatten.ts +1 -1
  187. package/src/elements/gate/runtime.ts +1 -1
  188. package/src/elements/index.ts +2 -0
  189. package/src/elements/signal/declare.ts +2 -1
  190. package/src/elements/signal/runtime.ts +16 -1
  191. package/src/elements/signal.ts +1 -0
  192. package/src/elements/store/cache.test.ts +2 -0
  193. package/src/elements/store/cache.ts +3 -3
  194. package/src/elements/store/declare.ts +9 -6
  195. package/src/elements/store/emit-drizzle.ts +44 -8
  196. package/src/elements/store/resource.test.ts +1 -1
  197. package/src/elements/store/rls-identity.test.ts +95 -0
  198. package/src/elements/store/rls-identity.ts +116 -0
  199. package/src/elements/store/runtime.ts +4 -0
  200. package/src/elements/store/schema-decl.test.ts +37 -0
  201. package/src/elements/store/schema-decl.ts +161 -0
  202. package/src/elements/store/seed.ts +1 -1
  203. package/src/elements/store/sql-condition.test.ts +12 -0
  204. package/src/elements/store/sql-condition.ts +26 -2
  205. package/src/elements/store/sql-rls-isolation.test.ts +165 -0
  206. package/src/elements/store/sql-rls-stamp.test.ts +117 -0
  207. package/src/elements/store/sql-session.ts +81 -3
  208. package/src/elements/store/upsert-app.test.ts +1 -1
  209. package/src/elements/store.ts +14 -0
  210. package/src/elements/vault/builtin-adapter.ts +17 -0
  211. package/src/full.ts +2 -0
  212. package/src/http.ts +20 -3
  213. package/src/index.ts +14 -1
  214. package/src/kernel/adopt-barrel-fresh.test.ts +1 -1
  215. package/src/kernel/adopt-routes.ts +30 -2
  216. package/src/kernel/app.ts +227 -77
  217. package/src/kernel/auto-cache.test.ts +8 -8
  218. package/src/kernel/auto-registry.test.ts +54 -9
  219. package/src/kernel/boot-bind/ai.ts +24 -0
  220. package/src/kernel/boot-bind/clock.ts +2 -0
  221. package/src/kernel/boot.test.ts +4 -4
  222. package/src/kernel/boot.ts +5 -3
  223. package/src/kernel/effects-stamping.test.ts +3 -3
  224. package/src/kernel/element-registries.ts +15 -5
  225. package/src/kernel/errors.ts +24 -0
  226. package/src/kernel/flow-units.ts +64 -0
  227. package/src/kernel/flow.ts +29 -17
  228. package/src/kernel/fx-dead-letters.test.ts +77 -0
  229. package/src/kernel/fx.test.ts +22 -0
  230. package/src/kernel/fx.ts +135 -6
  231. package/src/kernel/horizontal-child.ts +4 -4
  232. package/src/kernel/http-path-pending.ts +20 -0
  233. package/src/kernel/http-resource.ts +4 -0
  234. package/src/kernel/http-stream.test.ts +174 -0
  235. package/src/kernel/index.ts +2 -0
  236. package/src/kernel/pipeline.test.ts +2 -2
  237. package/src/kernel/plugin-elements.test.ts +1 -1
  238. package/src/kernel/plugin-needs.test.ts +1 -1
  239. package/src/kernel/ready.test.ts +2 -2
  240. package/src/kernel/resource-mount.test.ts +14 -0
  241. package/src/kernel/run-duration.test.ts +2 -2
  242. package/src/kernel/stamp-http.test.ts +79 -0
  243. package/src/kernel/stamp-http.ts +75 -0
  244. package/src/kernel/triggers.ts +52 -27
  245. package/src/manifest/mcp-ref.ts +88 -0
  246. package/src/manifest/types.ts +41 -2
  247. package/src/manifest/validate.test.ts +20 -0
  248. package/src/mcp/docs-server.ts +1 -1
  249. package/src/mcp/server.ts +1 -1
  250. package/src/plugins/compression.test.ts +21 -0
  251. package/src/plugins/compression.ts +1 -0
  252. package/src/release/measure.ts +2 -2
  253. package/src/runtime/bun.ts +41 -4
  254. package/src/runtime/json-code-block.test.ts +2 -2
  255. package/src/runtime/serve.test.ts +4 -4
  256. package/src/term.test.ts +52 -4
  257. package/src/term.ts +125 -17
  258. package/src/test/reset-element-registries.ts +10 -3
  259. package/src/cli/ask-seed.test.ts +0 -96
  260. package/src/cli/ask-seed.ts +0 -82
  261. package/src/console/ui-next/dist/assets/cache-glyph-CLPBqZeb.js +0 -1
  262. package/src/console/ui-next/dist/assets/collapsible-DYb0xU8C.js +0 -1
  263. package/src/console/ui-next/dist/assets/flows-page-Dg8CTE29.js +0 -1
  264. package/src/console/ui-next/dist/assets/http-method-BJ92Z_ke.js +0 -1
  265. package/src/console/ui-next/dist/assets/index-Bp-R7jtM.js +0 -66
  266. package/src/console/ui-next/dist/assets/index-D4Ldtj79.css +0 -2
  267. package/src/console/ui-next/dist/assets/observability-page-DAnpEaq1.js +0 -4
  268. package/src/console/ui-next/dist/assets/query-gate-parts-1m8m1iNp.js +0 -2
  269. package/src/console/ui-next/dist/assets/react-B1ML8gxg.js +0 -1
  270. package/src/console/ui-next/dist/assets/replica-lag-C8_BRt2x.js +0 -18
  271. package/src/console/ui-next/dist/assets/sheet-form-D-ata7jy.js +0 -1
  272. package/src/console/ui-next/dist/assets/shortcut-keys-DO4IsVqv.js +0 -1
  273. package/src/console/ui-next/dist/assets/skeleton-CL_X0GCj.js +0 -1
  274. package/src/console/ui-next/dist/assets/store-page-v3LXdYpr.js +0 -45
  275. package/src/console/ui-next/dist/assets/trace-detail-sheet-D16lWQMt.js +0 -2
  276. package/src/console/ui-next/dist/assets/units-page-BtQ0bqMe.js +0 -1
  277. package/src/console/ui-next/dist/assets/vault-page-Ca-MvcmJ.js +0 -2
@@ -21,6 +21,7 @@ import {
21
21
  queryGateToolbarLabel,
22
22
  queryGateUserChoices,
23
23
  type QueryGateMode,
24
+ type RlsPickerValue,
24
25
  } from "../lib/query-gate.ts";
25
26
  import { mergeRlsGateCatalog, rlsGateCatalog } from "../lib/rls-gate-catalog.ts";
26
27
  import {
@@ -35,8 +36,8 @@ import {
35
36
  /** Props for {@link QueryGateMenu}. */
36
37
  export interface QueryGateMenuProps {
37
38
  readonly manifest: Manifest | null;
38
- readonly asGate: string | null;
39
- readonly onChange: (asGate: string | null) => void;
39
+ readonly value: RlsPickerValue;
40
+ readonly onChange: (next: RlsPickerValue) => void;
40
41
  }
41
42
 
42
43
  /**
@@ -44,7 +45,8 @@ export interface QueryGateMenuProps {
44
45
  *
45
46
  * @param props - Manifest + current pick
46
47
  */
47
- export function QueryGateMenu({ manifest, asGate, onChange }: QueryGateMenuProps): JSX.Element {
48
+ export function QueryGateMenu({ manifest, value, onChange }: QueryGateMenuProps): JSX.Element {
49
+ const { asGate, asUserId } = value;
48
50
  const gatesQuery = useGates(true);
49
51
  const identitiesQuery = useFlowsIdentities();
50
52
  const catalog = useMemo(
@@ -61,33 +63,30 @@ export function QueryGateMenu({ manifest, asGate, onChange }: QueryGateMenuProps
61
63
  "Intentionally unauthenticated.";
62
64
  const mode = queryGateMode(asGate);
63
65
  const active = asGate !== null;
64
- const [asUserId, setAsUserId] = useState<string | null>(null);
65
- const [asTab, setAsTab] = useState<GateAsTab>("policy");
66
+ const [asTab, setAsTab] = useState<GateAsTab>(asUserId ? "user" : "policy");
66
67
  const selectedPolicy = policies.find((policy) => policy.id === asGate) ?? null;
67
68
  const selectedUser = users.find((user) => user.id === asUserId && user.gate === asGate) ?? null;
68
69
  const asEnabled = policies.length > 0 || users.length > 0;
69
70
 
70
71
  const selectMode = (next: QueryGateMode): void => {
71
72
  if (next === "operator") {
72
- setAsUserId(null);
73
- onChange(null);
73
+ onChange({ asGate: null, asUserId: null });
74
74
  return;
75
75
  }
76
76
  if (next === "public") {
77
- setAsUserId(null);
78
- onChange("public");
77
+ onChange({ asGate: "public", asUserId: null });
79
78
  return;
80
79
  }
81
80
  if (mode === "as") return;
82
81
  const fallback = selectedPolicy?.id ?? policies[0]?.id ?? users[0]?.gate ?? null;
83
82
  if (fallback === null) return;
84
83
  if (users[0] && fallback === users[0].gate && policies.length === 0) {
85
- setAsUserId(users[0].id);
86
84
  setAsTab("user");
87
- } else {
88
- setAsTab("policy");
85
+ onChange({ asGate: users[0].gate, asUserId: users[0].id });
86
+ return;
89
87
  }
90
- onChange(fallback);
88
+ setAsTab("policy");
89
+ onChange({ asGate: fallback, asUserId: null });
91
90
  };
92
91
 
93
92
  return (
@@ -111,7 +110,9 @@ export function QueryGateMenu({ manifest, asGate, onChange }: QueryGateMenuProps
111
110
  data-icon="inline-start"
112
111
  className="size-3.5"
113
112
  />
114
- <span className="truncate">{queryGateToolbarLabel(asGate)}</span>
113
+ <span className="truncate">
114
+ {queryGateToolbarLabel(asGate, asUserId, selectedUser?.label)}
115
+ </span>
115
116
  </Button>
116
117
  )}
117
118
  />
@@ -143,12 +144,12 @@ export function QueryGateMenu({ manifest, asGate, onChange }: QueryGateMenuProps
143
144
  </GateModeStrip>
144
145
  {mode === "operator" ? (
145
146
  <GateModeDetail title="Operator" badge="Default">
146
- Bypasses RLS. No <span className="font-mono">oke.gate</span> is set.
147
+ Bypasses RLS. No <span className="font-mono">oke.gate()</span> stamp.
147
148
  </GateModeDetail>
148
149
  ) : null}
149
150
  {mode === "public" ? (
150
151
  <GateModeDetail title="Public">
151
- {publicDetail} Sets <span className="font-mono">oke.gate</span> to{" "}
152
+ {publicDetail} Sets <span className="font-mono">oke.gate()</span> to{" "}
152
153
  <span className="font-mono">public</span>.
153
154
  </GateModeDetail>
154
155
  ) : null}
@@ -161,14 +162,12 @@ export function QueryGateMenu({ manifest, asGate, onChange }: QueryGateMenuProps
161
162
  asGate={asGate}
162
163
  asUserId={selectedUser?.id ?? null}
163
164
  onSelectPolicy={(policy) => {
164
- setAsUserId(null);
165
165
  setAsTab("policy");
166
- onChange(policy.id);
166
+ onChange({ asGate: policy.id, asUserId: null });
167
167
  }}
168
168
  onSelectUser={(user) => {
169
- setAsUserId(user.id);
170
169
  setAsTab("user");
171
- onChange(user.gate);
170
+ onChange({ asGate: user.gate, asUserId: user.id });
172
171
  }}
173
172
  />
174
173
  ) : null}
@@ -263,7 +263,8 @@ export function GateAsPicker({
263
263
  </>
264
264
  ) : (
265
265
  <>
266
- {selected.detail} Sets <span className="font-mono">oke.gate</span> for RLS.
266
+ {selected.detail} Sets <span className="font-mono">oke.gate()</span> /{" "}
267
+ <span className="font-mono">oke.user()</span> for RLS.
267
268
  </>
268
269
  )}
269
270
  </p>
@@ -2,7 +2,7 @@
2
2
  * Query console results — table / JSON / raw, copy, download, cell inspect.
3
3
  */
4
4
 
5
- import { useMemo, useState, type JSX } from "react";
5
+ import { useEffect, useMemo, useRef, useState, type JSX } from "react";
6
6
  import {
7
7
  ArrowDown01Icon,
8
8
  ArrowUp01Icon,
@@ -30,6 +30,8 @@ import {
30
30
  EmptyMedia,
31
31
  EmptyTitle,
32
32
  } from "@/components/ui/empty";
33
+ import { EASE_OUT } from "@/lib/ease.ts";
34
+ import { motion, useReducedMotion } from "@/lib/motion.ts";
33
35
  import { cn } from "@/lib/utils.ts";
34
36
  import { JsonValueSheet } from "../detail/json-value-sheet.tsx";
35
37
  import { ToolbarTip } from "@/components/ui/toolbar-tip.tsx";
@@ -104,6 +106,14 @@ export function QueryResults({
104
106
  );
105
107
  }, [rows, columns]);
106
108
 
109
+ const reduce = useReducedMotion() ?? false;
110
+ const wasPending = useRef(pending);
111
+ const [appearId, setAppearId] = useState(0);
112
+ useEffect(() => {
113
+ if (wasPending.current && !pending) setAppearId((n) => n + 1);
114
+ wasPending.current = pending;
115
+ }, [pending]);
116
+
107
117
  const copy = (kind: "json" | "csv"): void => {
108
118
  const text = kind === "csv" ? csvText : jsonText;
109
119
  if (!navigator.clipboard || text.length === 0) return;
@@ -210,93 +220,110 @@ export function QueryResults({
210
220
 
211
221
  {collapsed ? null : (
212
222
  <div className="min-h-0 flex-1 overflow-auto">
213
- {error ? (
214
- <p
215
- className="px-3 py-4 font-mono text-xs leading-relaxed text-destructive"
216
- role="alert"
217
- >
218
- {error}
219
- </p>
220
- ) : pending ? (
223
+ {pending ? (
221
224
  <p className="px-3 py-8 text-center text-sm text-muted-foreground">Running…</p>
222
- ) : rows === null && view !== "raw" ? (
223
- <Empty className="h-full min-h-40 border-0">
224
- <EmptyHeader>
225
- <EmptyMedia variant="icon">
226
- <HugeiconsIcon icon={PlayIcon} className="size-4" />
227
- </EmptyMedia>
228
- <EmptyTitle>No results yet</EmptyTitle>
229
- <EmptyDescription>Run a query to see results here.</EmptyDescription>
230
- </EmptyHeader>
231
- </Empty>
232
- ) : view === "raw" ? (
233
- executed ? (
234
- <div className="p-3" data-slot="store-query-raw">
235
- <QueryHighlightView
236
- code={executed}
237
- language={executedLanguage}
238
- className="text-xs leading-5"
239
- />
240
- </div>
241
- ) : (
242
- <p className="px-3 py-8 text-center text-sm text-muted-foreground">
243
- Nothing executed yet.
244
- </p>
245
- )
246
- ) : view === "json" ? (
247
- <HighlightedJson
248
- json={jsonText}
249
- dataSlot="store-query-json"
250
- className="min-h-full p-3"
251
- />
252
- ) : rows && rows.length === 0 ? (
253
- <p className="px-3 py-8 text-center text-sm text-muted-foreground">
254
- Query returned 0 rows.
255
- </p>
256
225
  ) : (
257
- <table className="w-full min-w-max border-collapse text-left text-[12px]">
258
- <thead className="sticky top-0 z-10 bg-background">
259
- <tr>
260
- {columns.map((col) => (
261
- <th
262
- key={col}
263
- scope="col"
264
- className="border-b border-border/60 px-3 py-1.5 font-mono text-[10px] font-semibold tracking-[0.08em] text-muted-foreground uppercase"
265
- >
266
- {col}
267
- </th>
268
- ))}
269
- </tr>
270
- </thead>
271
- <tbody>
272
- {(rows ?? []).map((row, i) => (
273
- <tr key={i} className="border-b border-border/60 hover:bg-muted/50">
274
- {columns.map((col) => {
275
- const value = row[col];
276
- const inspectable = asInspectableJson(value) !== null;
277
- return (
278
- <td
226
+ <motion.div
227
+ key={appearId}
228
+ initial={
229
+ appearId === 0
230
+ ? false
231
+ : reduce
232
+ ? { opacity: 0 }
233
+ : { opacity: 0, transform: "translateY(4px)" }
234
+ }
235
+ animate={reduce ? { opacity: 1 } : { opacity: 1, transform: "translateY(0px)" }}
236
+ transition={{ duration: reduce ? 0.12 : 0.18, ease: EASE_OUT }}
237
+ >
238
+ {error ? (
239
+ <p
240
+ className="px-3 py-4 font-mono text-xs leading-relaxed text-destructive"
241
+ role="alert"
242
+ >
243
+ {error}
244
+ </p>
245
+ ) : rows === null && view !== "raw" ? (
246
+ <Empty className="h-full min-h-40 border-0">
247
+ <EmptyHeader>
248
+ <EmptyMedia variant="icon">
249
+ <HugeiconsIcon icon={PlayIcon} className="size-4" />
250
+ </EmptyMedia>
251
+ <EmptyTitle>No results yet</EmptyTitle>
252
+ <EmptyDescription>Run a query to see results here.</EmptyDescription>
253
+ </EmptyHeader>
254
+ </Empty>
255
+ ) : view === "raw" ? (
256
+ executed ? (
257
+ <div className="p-3" data-slot="store-query-raw">
258
+ <QueryHighlightView
259
+ code={executed}
260
+ language={executedLanguage}
261
+ className="text-xs leading-5"
262
+ />
263
+ </div>
264
+ ) : (
265
+ <p className="px-3 py-8 text-center text-sm text-muted-foreground">
266
+ Nothing executed yet.
267
+ </p>
268
+ )
269
+ ) : view === "json" ? (
270
+ <HighlightedJson
271
+ json={jsonText}
272
+ dataSlot="store-query-json"
273
+ className="min-h-full p-3"
274
+ />
275
+ ) : rows && rows.length === 0 ? (
276
+ <p className="px-3 py-8 text-center text-sm text-muted-foreground">
277
+ Query returned 0 rows.
278
+ </p>
279
+ ) : (
280
+ <table className="w-full min-w-max border-collapse text-left text-[12px]">
281
+ <thead className="sticky top-0 z-10 bg-background">
282
+ <tr>
283
+ {columns.map((col) => (
284
+ <th
279
285
  key={col}
280
- className="max-w-xs truncate px-3 py-1.5 font-mono text-foreground/90"
286
+ scope="col"
287
+ className="border-b border-border/60 px-3 py-1.5 font-mono text-[10px] font-semibold tracking-[0.08em] text-muted-foreground uppercase"
281
288
  >
282
- {inspectable ? (
283
- <button
284
- type="button"
285
- className="max-w-full truncate text-left underline decoration-border/70 underline-offset-2 hover:text-foreground"
286
- onClick={() => setInspect({ rowId: String(i), column: col, value })}
289
+ {col}
290
+ </th>
291
+ ))}
292
+ </tr>
293
+ </thead>
294
+ <tbody>
295
+ {(rows ?? []).map((row, i) => (
296
+ <tr key={i} className="border-b border-border/60 hover:bg-muted/50">
297
+ {columns.map((col) => {
298
+ const value = row[col];
299
+ const inspectable = asInspectableJson(value) !== null;
300
+ return (
301
+ <td
302
+ key={col}
303
+ className="max-w-xs truncate px-3 py-1.5 font-mono text-foreground/90"
287
304
  >
288
- {formatGridCell(value)}
289
- </button>
290
- ) : (
291
- formatGridCell(value)
292
- )}
293
- </td>
294
- );
295
- })}
296
- </tr>
297
- ))}
298
- </tbody>
299
- </table>
305
+ {inspectable ? (
306
+ <button
307
+ type="button"
308
+ className="max-w-full truncate text-left underline decoration-border/70 underline-offset-2 hover:text-foreground"
309
+ onClick={() =>
310
+ setInspect({ rowId: String(i), column: col, value })
311
+ }
312
+ >
313
+ {formatGridCell(value)}
314
+ </button>
315
+ ) : (
316
+ formatGridCell(value)
317
+ )}
318
+ </td>
319
+ );
320
+ })}
321
+ </tr>
322
+ ))}
323
+ </tbody>
324
+ </table>
325
+ )}
326
+ </motion.div>
300
327
  )}
301
328
  </div>
302
329
  )}
@@ -4,6 +4,8 @@
4
4
 
5
5
  import { Handle, Position, type Node, type NodeProps } from "@xyflow/react";
6
6
  import type { JSX } from "react";
7
+ import { ShieldEnergyIcon, ShieldOff } from "@hugeicons/core-free-icons";
8
+ import { HugeiconsIcon } from "@hugeicons/react";
7
9
  import { cn } from "@/lib/utils.ts";
8
10
  import {
9
11
  schemaRelationLabel,
@@ -47,6 +49,21 @@ export function SchemaTableNode({ data }: NodeProps<SchemaTableNode>): JSX.Eleme
47
49
  >
48
50
  <span className="size-2 shrink-0 rounded-full" style={{ background: hex }} aria-hidden />
49
51
  <span className="min-w-0 truncate font-semibold text-foreground">{table.name}</span>
52
+ <span
53
+ data-slot="store-schema-rls"
54
+ data-rls={table.rls === true ? "on" : "off"}
55
+ title={table.rls === true ? "RLS enabled" : "RLS disabled"}
56
+ className={cn(
57
+ "shrink-0",
58
+ table.rls === true ? "text-emerald-500" : "text-muted-foreground/35",
59
+ )}
60
+ >
61
+ <HugeiconsIcon
62
+ icon={table.rls === true ? ShieldEnergyIcon : ShieldOff}
63
+ className="size-3"
64
+ aria-hidden
65
+ />
66
+ </span>
50
67
  {showStore ? (
51
68
  <span className="ml-auto shrink-0 font-mono text-[9px] tracking-[0.08em] text-muted-foreground uppercase">
52
69
  {table.storeName}
@@ -53,8 +53,10 @@ import { cn } from "@/lib/utils";
53
53
  import type { RunCache } from "@/features/flows/traces/cache-icon.ts";
54
54
  import { CacheGlyph } from "@/features/flows/traces/cache-glyph.tsx";
55
55
  import { formatDuration } from "@/features/flows/traces/format-duration.ts";
56
+ import { httpMethodHasBody } from "@/features/flows/traces/http-method.ts";
56
57
  import { useStoreQuery } from "@/features/store/data/use-store-query.ts";
57
58
  import { useClockRunNow, useFlowsIdentities, useFlowsInvoke } from "../data/use-flows-invoke.ts";
59
+ import { pickSeedFields, splitCallApiInput } from "../lib/contract-input.ts";
58
60
  import {
59
61
  fieldConstraintHint,
60
62
  fieldsFromSchema,
@@ -313,33 +315,41 @@ function InvokeBodyPanel({
313
315
  () => (kind === "http" && row.path ? pathParamNames(row.path) : []),
314
316
  [kind, row.path],
315
317
  );
318
+ const split = useMemo(
319
+ () =>
320
+ splitCallApiInput(fields, {
321
+ http: kind === "http",
322
+ method: row.method,
323
+ pathParams: params,
324
+ }),
325
+ [fields, kind, row.method, params],
326
+ );
327
+ const showBody = kind !== "http" || httpMethodHasBody(row.method);
316
328
 
317
329
  const [invokeAs, setInvokeAs] = useState<CallInvokeAs>({ asGate: null, asUserId: null });
318
330
  const [piiMasked, setPiiMasked] = useState(true);
319
331
  const [pathValues, setPathValues] = useState<Record<string, string>>(() =>
320
332
  seedPathValues(row.path, params),
321
333
  );
322
- const [body, setBody] = useState<Record<string, unknown>>(
334
+ const seedAll = useMemo(
323
335
  () => (seedFromSchema(inSchema) as Record<string, unknown>) ?? {},
336
+ [inSchema],
324
337
  );
338
+ const seedQuery = useMemo(() => pickSeedFields(seedAll, split.query), [seedAll, split.query]);
339
+ const seedBody = useMemo(() => pickSeedFields(seedAll, split.body), [seedAll, split.body]);
340
+ const [query, setQuery] = useState<Record<string, unknown>>(seedQuery);
341
+ const [body, setBody] = useState<Record<string, unknown>>(seedBody);
325
342
  const [bodyView, setBodyView] = useState<BodyView>("fields");
326
- const [rawText, setRawText] = useState(() =>
327
- stringifyBody((seedFromSchema(inSchema) as Record<string, unknown>) ?? {}),
328
- );
343
+ const [rawText, setRawText] = useState(() => stringifyBody(seedBody));
329
344
  const [rawError, setRawError] = useState<string | null>(null);
330
345
  const [localErrors, setLocalErrors] = useState<readonly { path: string; message: string }[]>([]);
331
346
  const [startedAt, setStartedAt] = useState<number | null>(null);
332
347
  const [elapsedMs, setElapsedMs] = useState<number | null>(null);
333
348
 
334
- const seedBody = useMemo(
335
- () => (seedFromSchema(inSchema) as Record<string, unknown>) ?? {},
336
- [inSchema],
337
- );
338
-
339
349
  useEffect(() => {
340
- const next = (seedFromSchema(inSchema) as Record<string, unknown>) ?? {};
341
- setBody(next);
342
- setRawText(stringifyBody(next));
350
+ setQuery(seedQuery);
351
+ setBody(seedBody);
352
+ setRawText(stringifyBody(seedBody));
343
353
  setRawError(null);
344
354
  setBodyView("fields");
345
355
  setPathValues(seedPathValues(row.path, params));
@@ -388,8 +398,8 @@ function InvokeBodyPanel({
388
398
  }
389
399
 
390
400
  async function onCall(includePii = !piiMasked): Promise<void> {
391
- let nextBody = body;
392
- if (bodyView === "raw") {
401
+ let nextBody = showBody ? body : {};
402
+ if (showBody && bodyView === "raw") {
393
403
  if (!commitRawText(rawText)) return;
394
404
  try {
395
405
  nextBody = JSON.parse(rawText) as Record<string, unknown>;
@@ -397,7 +407,8 @@ function InvokeBodyPanel({
397
407
  return;
398
408
  }
399
409
  }
400
- const local = validateContract(inSchema, nextBody);
410
+ const invokeBody = { ...query, ...nextBody };
411
+ const local = validateContract(inSchema, { ...pathValues, ...invokeBody });
401
412
  if (!local.ok) {
402
413
  setLocalErrors(local.errors);
403
414
  return;
@@ -408,7 +419,7 @@ function InvokeBodyPanel({
408
419
  try {
409
420
  await invoke.mutateAsync({
410
421
  flowId: row.id,
411
- body: nextBody,
422
+ body: invokeBody,
412
423
  ...(invokeAs.asUserId ? { asUserId: invokeAs.asUserId } : {}),
413
424
  ...(invokeAs.asGate ? { asGate: invokeAs.asGate } : {}),
414
425
  ...(params.length > 0 ? { pathParams: pathValues } : {}),
@@ -420,6 +431,7 @@ function InvokeBodyPanel({
420
431
  }
421
432
 
422
433
  function onReset(): void {
434
+ setQuery(seedQuery);
423
435
  setBody(seedBody);
424
436
  setRawText(stringifyBody(seedBody));
425
437
  setRawError(null);
@@ -471,8 +483,12 @@ function InvokeBodyPanel({
471
483
  }
472
484
  }}
473
485
  />
474
- <DockSep />
475
- <BodyViewToggle view={bodyView} onChange={onBodyViewChange} />
486
+ {showBody ? (
487
+ <>
488
+ <DockSep />
489
+ <BodyViewToggle view={bodyView} onChange={onBodyViewChange} />
490
+ </>
491
+ ) : null}
476
492
  <DockSep />
477
493
  <Tooltip>
478
494
  <TooltipTrigger
@@ -492,7 +508,7 @@ function InvokeBodyPanel({
492
508
  )}
493
509
  />
494
510
  <TooltipContent side="bottom" className="text-[11px]">
495
- Reset body to the schema example
511
+ {showBody ? "Reset body to the schema example" : "Reset to schema examples"}
496
512
  </TooltipContent>
497
513
  </Tooltip>
498
514
  <DockSep />
@@ -566,7 +582,7 @@ function InvokeBodyPanel({
566
582
  ) : null}
567
583
 
568
584
  {params.length > 0 ? (
569
- <section className="min-w-0" aria-label="Path params">
585
+ <section className="min-w-0" aria-label="Path params" data-slot="call-api-path">
570
586
  <DockChapter title="Path params" hint={row.path ?? undefined} />
571
587
  {params.map((name) => (
572
588
  <SheetField key={name} label={name} className="[&>span]:px-3">
@@ -583,67 +599,89 @@ function InvokeBodyPanel({
583
599
  </section>
584
600
  ) : null}
585
601
 
586
- <section className="min-w-0" aria-label="Body">
587
- <DockChapter
588
- title="Body"
589
- hint={bodyView === "raw" ? "raw JSON" : "seeded from schema"}
590
- />
591
- {bodyView === "raw" ? (
592
- <div className="border-b border-border/50">
593
- <textarea
594
- data-slot="call-api-body-raw"
595
- aria-label="Raw JSON body"
596
- spellCheck={false}
597
- value={rawText}
598
- onChange={(e) => {
599
- setRawText(e.target.value);
600
- if (rawError) setRawError(null);
601
- }}
602
- className={cn(
603
- "min-h-40 w-full resize-y rounded-none border-0 bg-transparent px-3 py-2 font-mono text-xs leading-relaxed text-foreground outline-none",
604
- "placeholder:text-muted-foreground focus-visible:bg-muted/20",
605
- rawError && "text-destructive",
606
- )}
607
- />
608
- {rawError ? (
609
- <p
610
- role="alert"
611
- className="px-3 pb-2 text-xs text-destructive"
612
- data-slot="call-api-raw-error"
613
- >
614
- {rawError}
615
- </p>
616
- ) : null}
617
- </div>
618
- ) : fields.length === 0 ? (
619
- <p className="border-b border-border/50 px-3 py-2 text-xs text-muted-foreground">
620
- No body fields (empty object).
621
- </p>
622
- ) : (
602
+ {split.query.length > 0 ? (
603
+ <section className="min-w-0" aria-label="Query params" data-slot="call-api-query">
604
+ <DockChapter title="Query params" hint="seeded from schema" />
623
605
  <SheetGrid>
624
- {fields.map((f) => (
606
+ {split.query.map((f) => (
625
607
  <BodyField
626
608
  key={f.path}
627
609
  field={f}
628
- value={body[f.name]}
629
- onChange={(v) => setBody((prev) => ({ ...prev, [f.name]: v }))}
610
+ idPrefix="query"
611
+ value={query[f.name]}
612
+ onChange={(v) => setQuery((prev) => ({ ...prev, [f.name]: v }))}
630
613
  error={localErrors.find((e) => e.path === `/${f.name}` || e.path === f.path)}
631
614
  wide={f.type === "object" || f.type === "array"}
632
615
  manifest={manifest}
633
616
  />
634
617
  ))}
635
618
  </SheetGrid>
636
- )}
637
- {localErrors.length > 0 ? (
638
- <p
639
- role="alert"
640
- className="px-3 py-2 text-xs text-destructive"
641
- data-slot="call-api-validation"
642
- >
643
- Fix contract errors before sending.
644
- </p>
645
- ) : null}
646
- </section>
619
+ </section>
620
+ ) : null}
621
+
622
+ {showBody ? (
623
+ <section className="min-w-0" aria-label="Body" data-slot="call-api-body">
624
+ <DockChapter
625
+ title="Body"
626
+ hint={bodyView === "raw" ? "raw JSON" : "seeded from schema"}
627
+ />
628
+ {bodyView === "raw" ? (
629
+ <div className="border-b border-border/50">
630
+ <textarea
631
+ data-slot="call-api-body-raw"
632
+ aria-label="Raw JSON body"
633
+ spellCheck={false}
634
+ value={rawText}
635
+ onChange={(e) => {
636
+ setRawText(e.target.value);
637
+ if (rawError) setRawError(null);
638
+ }}
639
+ className={cn(
640
+ "min-h-40 w-full resize-y rounded-none border-0 bg-transparent px-3 py-2 font-mono text-xs leading-relaxed text-foreground outline-none",
641
+ "placeholder:text-muted-foreground focus-visible:bg-muted/20",
642
+ rawError && "text-destructive",
643
+ )}
644
+ />
645
+ {rawError ? (
646
+ <p
647
+ role="alert"
648
+ className="px-3 pb-2 text-xs text-destructive"
649
+ data-slot="call-api-raw-error"
650
+ >
651
+ {rawError}
652
+ </p>
653
+ ) : null}
654
+ </div>
655
+ ) : split.body.length === 0 ? (
656
+ <p className="border-b border-border/50 px-3 py-2 text-xs text-muted-foreground">
657
+ No body fields (empty object).
658
+ </p>
659
+ ) : (
660
+ <SheetGrid>
661
+ {split.body.map((f) => (
662
+ <BodyField
663
+ key={f.path}
664
+ field={f}
665
+ value={body[f.name]}
666
+ onChange={(v) => setBody((prev) => ({ ...prev, [f.name]: v }))}
667
+ error={localErrors.find((e) => e.path === `/${f.name}` || e.path === f.path)}
668
+ wide={f.type === "object" || f.type === "array"}
669
+ manifest={manifest}
670
+ />
671
+ ))}
672
+ </SheetGrid>
673
+ )}
674
+ </section>
675
+ ) : null}
676
+ {localErrors.length > 0 ? (
677
+ <p
678
+ role="alert"
679
+ className="px-3 py-2 text-xs text-destructive"
680
+ data-slot="call-api-validation"
681
+ >
682
+ Fix contract errors before sending.
683
+ </p>
684
+ ) : null}
647
685
  </div>
648
686
 
649
687
  <ResponseBlock
@@ -1057,6 +1095,7 @@ function BodyField({
1057
1095
  error,
1058
1096
  wide = false,
1059
1097
  manifest,
1098
+ idPrefix = "body",
1060
1099
  }: {
1061
1100
  readonly field: FormField;
1062
1101
  readonly value: unknown;
@@ -1064,8 +1103,9 @@ function BodyField({
1064
1103
  readonly error?: { path: string; message: string };
1065
1104
  readonly wide?: boolean;
1066
1105
  readonly manifest: Manifest | null;
1106
+ readonly idPrefix?: string;
1067
1107
  }): JSX.Element {
1068
- const id = `body-${field.name}`;
1108
+ const id = `${idPrefix}-${field.name}`;
1069
1109
  const fk = useFkFieldOptions(field, manifest);
1070
1110
  const hint = [
1071
1111
  !field.required ? "optional" : null,