okengine 0.14.1 → 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 (205) 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 +3 -0
  6. package/site/content/docs/elements/gate.mdx +25 -22
  7. package/site/content/docs/elements/signal.mdx +4 -4
  8. package/site/content/docs/elements/store.mdx +86 -27
  9. package/site/content/docs/elements/vault.mdx +1 -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 +25 -21
  17. package/site/content/docs/reference/security.md +1 -1
  18. package/src/auth/bindings.ts +14 -5
  19. package/src/auth/cross-plane.ts +10 -3
  20. package/src/cli/ai-setup/ai-setup.test.ts +62 -0
  21. package/src/cli/ai-setup/apply.ts +32 -1
  22. package/src/cli/attach-host-console.test.ts +3 -2
  23. package/src/cli/attach-host-console.ts +3 -0
  24. package/src/cli/build.test.ts +4 -2
  25. package/src/cli/db-seed.ts +48 -15
  26. package/src/cli/db.test.ts +104 -0
  27. package/src/cli/db.ts +122 -10
  28. package/src/cli/dev-controls.test.ts +20 -0
  29. package/src/cli/dev-controls.ts +13 -0
  30. package/src/cli/dev.test.ts +15 -4
  31. package/src/cli/dev.ts +102 -29
  32. package/src/cli/ensure-drizzle-config.ts +2 -1
  33. package/src/cli/tui/DevLive.tsx +12 -2
  34. package/src/cli/tui/keys.test.ts +2 -1
  35. package/src/cli/tui/keys.ts +3 -2
  36. package/src/compiler/aot.test.ts +1 -1
  37. package/src/compiler/extract.test.ts +409 -33
  38. package/src/compiler/extract.ts +318 -67
  39. package/src/compiler/flow-path.test.ts +96 -0
  40. package/src/compiler/flow-path.ts +173 -0
  41. package/src/compiler/generate-adopt.test.ts +112 -3
  42. package/src/compiler/generate-adopt.ts +280 -26
  43. package/src/compiler/schema-from-ast.ts +609 -0
  44. package/src/console/server/bind.ts +2 -2
  45. package/src/console/server/console.test.ts +3 -0
  46. package/src/console/server/flows.ts +120 -40
  47. package/src/console/server/invoke-user-flow.test.ts +3 -1
  48. package/src/console/server/invoke-user-flow.ts +17 -2
  49. package/src/console/server/iso-at.test.ts +9 -0
  50. package/src/console/server/iso-at.ts +11 -0
  51. package/src/console/server/security-headers.ts +6 -3
  52. package/src/console/server/spa-proxy.test.ts +1 -0
  53. package/src/console/server/sql-catalog.ts +147 -7
  54. package/src/console/server/sql-rls.test.ts +16 -0
  55. package/src/console/server/state.ts +8 -0
  56. package/src/console/server/store.test.ts +7 -4
  57. package/src/console/server/store.ts +142 -58
  58. package/src/console/ui-next/dist/assets/agent-disclosure-C0X1fbWF.js +1 -0
  59. package/src/console/ui-next/dist/assets/{cache-glyph-BanhLsEY.js → cache-glyph-F1FI122b.js} +1 -1
  60. package/src/console/ui-next/dist/assets/{call-pii-button-bqkxMrJH.js → call-pii-button-DEDCl_j3.js} +1 -1
  61. package/src/console/ui-next/dist/assets/collapsible-LPqGvfoz.js +1 -0
  62. package/src/console/ui-next/dist/assets/{confirm-sheet-DDCRmG62.js → confirm-sheet-3ptDzXbz.js} +1 -1
  63. package/src/console/ui-next/dist/assets/{duration-tone-oiRxPVsZ.js → duration-tone-sC3lGABz.js} +1 -1
  64. package/src/console/ui-next/dist/assets/{explorer-empty-HjTnVQoR.js → explorer-empty-BvYrygyO.js} +1 -1
  65. package/src/console/ui-next/dist/assets/flows-page-Dluo1Bd7.js +1 -0
  66. package/src/console/ui-next/dist/assets/{highlighted-json-CvDPvveV.js → highlighted-json-CS_O8L-r.js} +1 -1
  67. package/src/console/ui-next/dist/assets/http-method--sWDdXSB.js +1 -0
  68. package/src/console/ui-next/dist/assets/index-Ca3HZMVq.js +66 -0
  69. package/src/console/ui-next/dist/assets/index-UtTDRQpS.css +2 -0
  70. package/src/console/ui-next/dist/assets/{link-DF7SZ9Ek.js → link-COeyggt-.js} +1 -1
  71. package/src/console/ui-next/dist/assets/{observability-page-BEZDzyYh.js → observability-page-HK9-BO8z.js} +2 -2
  72. package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +1 -0
  73. package/src/console/ui-next/dist/assets/replica-lag-DBIFFf7d.js +18 -0
  74. package/src/console/ui-next/dist/assets/sheet-form-Dnwa5oOR.js +1 -0
  75. package/src/console/ui-next/dist/assets/shortcut-keys-CoqwEIi0.js +1 -0
  76. package/src/console/ui-next/dist/assets/skeleton-BHcfDYcb.js +1 -0
  77. package/src/console/ui-next/dist/assets/store-page-BSohH3wM.js +41 -0
  78. package/src/console/ui-next/dist/assets/trace-detail-sheet-CFIGRnnA.js +2 -0
  79. package/src/console/ui-next/dist/assets/tree-expand-toggle-BgLl34w7.js +54 -0
  80. package/src/console/ui-next/dist/assets/units-page-B0cFE76A.js +1 -0
  81. package/src/console/ui-next/dist/assets/{use-vault-list-Cl79j_ku.js → use-vault-list-BLbZhzyF.js} +1 -1
  82. package/src/console/ui-next/dist/assets/vault-page-B1SbYe10.js +2 -0
  83. package/src/console/ui-next/dist/index.html +6 -6
  84. package/src/console/ui-next/src/client.ts +35 -7
  85. package/src/console/ui-next/src/components/motion/table/index.tsx +16 -0
  86. package/src/console/ui-next/src/components/motion/table/types.ts +4 -0
  87. package/src/console/ui-next/src/components/ui/kbd.tsx +2 -2
  88. package/src/console/ui-next/src/features/flows/traces/http-method.ts +19 -0
  89. package/src/console/ui-next/src/features/flows/traces/trace-detail.test.ts +19 -1
  90. package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +10 -3
  91. package/src/console/ui-next/src/features/store/detail/browse-section.tsx +45 -1
  92. package/src/console/ui-next/src/features/store/detail/resource-panel.tsx +78 -16
  93. package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +155 -14
  94. package/src/console/ui-next/src/features/store/explorer/store-tree.tsx +33 -133
  95. package/src/console/ui-next/src/features/store/files/file-preview.tsx +4 -1
  96. package/src/console/ui-next/src/features/store/grid/rls-policy-sheet.tsx +326 -165
  97. package/src/console/ui-next/src/features/store/grid/sql-insert-sheet.tsx +6 -0
  98. package/src/console/ui-next/src/features/store/grid/store-data-grid.tsx +50 -3
  99. package/src/console/ui-next/src/features/store/lib/files-meta.test.ts +3 -0
  100. package/src/console/ui-next/src/features/store/lib/files-meta.ts +12 -0
  101. package/src/console/ui-next/src/features/store/lib/query-defaults.test.ts +25 -0
  102. package/src/console/ui-next/src/features/store/lib/query-defaults.ts +85 -3
  103. package/src/console/ui-next/src/features/store/lib/query-gate.ts +30 -4
  104. package/src/console/ui-next/src/features/store/lib/rls-policy.test.ts +139 -0
  105. package/src/console/ui-next/src/features/store/lib/rls-policy.ts +223 -21
  106. package/src/console/ui-next/src/features/store/lib/schema-graph.ts +4 -0
  107. package/src/console/ui-next/src/features/store/lib/sql-catalog.test.ts +24 -0
  108. package/src/console/ui-next/src/features/store/lib/sql-catalog.ts +19 -0
  109. package/src/console/ui-next/src/features/store/lib/store-tree.test.ts +6 -0
  110. package/src/console/ui-next/src/features/store/lib/store-tree.ts +13 -0
  111. package/src/console/ui-next/src/features/store/query/query-console.tsx +48 -14
  112. package/src/console/ui-next/src/features/store/query/query-gate-menu.tsx +19 -20
  113. package/src/console/ui-next/src/features/store/query/query-gate-parts.tsx +2 -1
  114. package/src/console/ui-next/src/features/store/query/query-results.tsx +110 -83
  115. package/src/console/ui-next/src/features/store/schema/schema-table-node.tsx +17 -0
  116. package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +112 -72
  117. package/src/console/ui-next/src/features/units/lib/contract-input.test.ts +206 -1
  118. package/src/console/ui-next/src/features/units/lib/contract-input.ts +55 -0
  119. package/src/console/ui-next/src/features/vault/lib/types.ts +1 -1
  120. package/src/docker/compose-health.test.ts +27 -0
  121. package/src/docker/compose-health.ts +19 -3
  122. package/src/docker/docker.test.ts +3 -0
  123. package/src/docker/recipes/pgdog.ts +2 -0
  124. package/src/drivers/clock-postgres.ts +13 -9
  125. package/src/drivers/instances-postgres.ts +4 -6
  126. package/src/drivers/journal-postgres.ts +13 -9
  127. package/src/drivers/pg-rls.test.ts +87 -0
  128. package/src/drivers/pg-rls.ts +233 -7
  129. package/src/drivers/pglite.ts +26 -3
  130. package/src/drivers/postgres.test.ts +23 -0
  131. package/src/drivers/postgres.ts +133 -9
  132. package/src/drivers/types.ts +10 -0
  133. package/src/elements/clock/declare.ts +26 -1
  134. package/src/elements/gate/boot.ts +5 -5
  135. package/src/elements/gate/declare.ts +36 -4
  136. package/src/elements/gate/flatten.ts +1 -1
  137. package/src/elements/gate/runtime.ts +1 -1
  138. package/src/elements/store/declare.ts +9 -6
  139. package/src/elements/store/emit-drizzle.ts +44 -8
  140. package/src/elements/store/resource.test.ts +1 -1
  141. package/src/elements/store/rls-identity.test.ts +95 -0
  142. package/src/elements/store/rls-identity.ts +116 -0
  143. package/src/elements/store/runtime.ts +4 -0
  144. package/src/elements/store/schema-decl.test.ts +37 -0
  145. package/src/elements/store/schema-decl.ts +161 -0
  146. package/src/elements/store/seed.ts +1 -1
  147. package/src/elements/store/sql-condition.test.ts +12 -0
  148. package/src/elements/store/sql-condition.ts +26 -2
  149. package/src/elements/store/sql-rls-isolation.test.ts +165 -0
  150. package/src/elements/store/sql-rls-stamp.test.ts +117 -0
  151. package/src/elements/store/sql-session.ts +81 -3
  152. package/src/elements/store/upsert-app.test.ts +1 -1
  153. package/src/elements/store.ts +14 -0
  154. package/src/http.ts +20 -3
  155. package/src/index.ts +10 -1
  156. package/src/kernel/adopt-barrel-fresh.test.ts +1 -1
  157. package/src/kernel/adopt-routes.ts +30 -2
  158. package/src/kernel/app.ts +131 -14
  159. package/src/kernel/auto-cache.test.ts +8 -8
  160. package/src/kernel/auto-registry.test.ts +49 -9
  161. package/src/kernel/boot.test.ts +4 -4
  162. package/src/kernel/boot.ts +2 -2
  163. package/src/kernel/effects-stamping.test.ts +3 -3
  164. package/src/kernel/element-registries.ts +12 -5
  165. package/src/kernel/errors.ts +24 -0
  166. package/src/kernel/flow-units.ts +64 -0
  167. package/src/kernel/flow.ts +29 -17
  168. package/src/kernel/fx.ts +23 -0
  169. package/src/kernel/horizontal-child.ts +4 -4
  170. package/src/kernel/http-path-pending.ts +20 -0
  171. package/src/kernel/http-resource.ts +4 -0
  172. package/src/kernel/http-stream.test.ts +4 -4
  173. package/src/kernel/pipeline.test.ts +2 -2
  174. package/src/kernel/plugin-elements.test.ts +1 -1
  175. package/src/kernel/plugin-needs.test.ts +1 -1
  176. package/src/kernel/ready.test.ts +2 -2
  177. package/src/kernel/resource-mount.test.ts +14 -0
  178. package/src/kernel/run-duration.test.ts +2 -2
  179. package/src/kernel/stamp-http.test.ts +79 -0
  180. package/src/kernel/stamp-http.ts +75 -0
  181. package/src/kernel/triggers.ts +52 -27
  182. package/src/manifest/types.ts +13 -0
  183. package/src/release/measure.ts +2 -2
  184. package/src/runtime/json-code-block.test.ts +2 -2
  185. package/src/runtime/serve.test.ts +4 -4
  186. package/src/term.test.ts +52 -4
  187. package/src/term.ts +125 -17
  188. package/src/test/reset-element-registries.ts +8 -3
  189. package/src/cli/ask-seed.test.ts +0 -96
  190. package/src/cli/ask-seed.ts +0 -82
  191. package/src/console/ui-next/dist/assets/collapsible-DYb0xU8C.js +0 -1
  192. package/src/console/ui-next/dist/assets/flows-page-DxDsOd4f.js +0 -1
  193. package/src/console/ui-next/dist/assets/http-method-CJCBYL2j.js +0 -1
  194. package/src/console/ui-next/dist/assets/index-Ce6WKWKM.js +0 -66
  195. package/src/console/ui-next/dist/assets/index-D4Ldtj79.css +0 -2
  196. package/src/console/ui-next/dist/assets/query-gate-parts-1m8m1iNp.js +0 -2
  197. package/src/console/ui-next/dist/assets/react-B1ML8gxg.js +0 -1
  198. package/src/console/ui-next/dist/assets/replica-lag-C8_BRt2x.js +0 -18
  199. package/src/console/ui-next/dist/assets/sheet-form-D-ata7jy.js +0 -1
  200. package/src/console/ui-next/dist/assets/shortcut-keys-DO4IsVqv.js +0 -1
  201. package/src/console/ui-next/dist/assets/skeleton-CL_X0GCj.js +0 -1
  202. package/src/console/ui-next/dist/assets/store-page-v3LXdYpr.js +0 -45
  203. package/src/console/ui-next/dist/assets/trace-detail-sheet-DFLFfUUX.js +0 -2
  204. package/src/console/ui-next/dist/assets/units-page-C0gW6Kdo.js +0 -1
  205. package/src/console/ui-next/dist/assets/vault-page-DuKzqwzW.js +0 -2
@@ -17,6 +17,7 @@ import {
17
17
  } from "@hugeicons/core-free-icons";
18
18
  import { HugeiconsIcon } from "@hugeicons/react";
19
19
  import { AgentCode } from "@/components/agents/agent-code";
20
+ import { EXPLORER_ICON_BUTTON_CLASS } from "@/components/explorer/explorer-chrome.ts";
20
21
  import { Input } from "@/components/ui/input";
21
22
  import {
22
23
  SHEET_CONTROL,
@@ -39,16 +40,19 @@ import {
39
40
  } from "@/components/motion/select.tsx";
40
41
  import {
41
42
  Sheet,
43
+ SheetClose,
42
44
  SheetContent,
43
45
  SheetDescription,
44
46
  SheetFooter,
45
47
  SheetHeader,
46
48
  SheetTitle,
47
49
  } from "@/components/ui/sheet";
50
+ import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
48
51
  import { Switch } from "@/components/motion/switch.tsx";
49
52
  import { LayoutGroup, motion, useReducedMotion } from "@/lib/motion.ts";
50
53
  import { SPRING_LAYOUT } from "@/lib/ease.ts";
51
54
  import { cn } from "@/lib/utils.ts";
55
+ import { SchemaColumnMarks } from "../schema/schema-constraint-icon.tsx";
52
56
  import type { Manifest } from "../../../../../../manifest/types.ts";
53
57
  import { useGates } from "../data/use-gates.ts";
54
58
  import { useStoreEdit } from "../data/use-store-edit.ts";
@@ -70,11 +74,21 @@ import {
70
74
  parseSqlPolicySpec,
71
75
  RLS_POLICY_COMMANDS,
72
76
  RLS_POLICY_TEMPLATES,
77
+ rlsBindOwnerExpr,
73
78
  rlsCommandFromGateMode,
79
+ rlsExprNeedsOwnerColumn,
80
+ rlsExprUsesUserIdentity,
74
81
  rlsGateActionsForMode,
75
82
  rlsGateModeFromCommand,
83
+ rlsOwnerColumn,
76
84
  rlsPolicyPredicates,
77
85
  rlsPolicyPreviewSql,
86
+ rlsPolicyCodeSource,
87
+ rlsRewriteIdentityColumn,
88
+ rlsGatePredicateSql,
89
+ rlsTableColumns,
90
+ rlsTableSqlColumns,
91
+ rlsTemplateUsesOwner,
78
92
  type RlsGateMode,
79
93
  type RlsPolicyTemplate,
80
94
  type SqlPolicyBehavior,
@@ -117,7 +131,9 @@ export function RlsPolicySheet({
117
131
  const edit = useStoreEdit();
118
132
  const gatesQuery = useGates(open);
119
133
  const [name, setName] = useState("");
134
+ const [dock, setDock] = useState<"sql" | "code">("sql");
120
135
  const [table, setTable] = useState(tables[0] ?? "");
136
+ const [identityCol, setIdentityCol] = useState("");
121
137
  const [command, setCommand] = useState<SqlPolicyCommand>("SELECT");
122
138
  const [behavior, setBehavior] = useState<SqlPolicyBehavior>("PERMISSIVE");
123
139
  const [using, setUsing] = useState("true");
@@ -135,6 +151,24 @@ export function RlsPolicySheet({
135
151
  );
136
152
  const gateMode = rlsGateModeFromCommand(command);
137
153
  const predicates = rlsPolicyPredicates(command);
154
+ const tableColumns = useMemo(
155
+ () => rlsTableColumns(manifest, storeRef, table),
156
+ [manifest, storeRef, table],
157
+ );
158
+ const sqlColumns = useMemo(() => tableColumns.map((col) => col.sqlName), [tableColumns]);
159
+ const suggestedCol = rlsOwnerColumn(sqlColumns);
160
+ const identityValue =
161
+ identityCol !== "" && sqlColumns.includes(identityCol) ? identityCol : (suggestedCol ?? "");
162
+ const ownerStyle =
163
+ (templateId !== null &&
164
+ rlsTemplateUsesOwner(RLS_POLICY_TEMPLATES.find((tpl) => tpl.id === templateId) ?? {})) ||
165
+ (predicates.using && rlsExprUsesUserIdentity(using)) ||
166
+ (predicates.withCheck && rlsExprUsesUserIdentity(withCheck));
167
+ const showIdentitySelect = sqlColumns.length > 0 && ownerStyle;
168
+ const ownerUnbound =
169
+ ((predicates.using && rlsExprNeedsOwnerColumn(using)) ||
170
+ (predicates.withCheck && rlsExprNeedsOwnerColumn(withCheck))) &&
171
+ identityValue === "";
138
172
  const catalog = useMemo(
139
173
  () => mergeRlsGateCatalog(rlsGateCatalog(manifest), gatesQuery.data ?? null),
140
174
  [manifest, gatesQuery.data],
@@ -155,20 +189,48 @@ export function RlsPolicySheet({
155
189
  using: predicates.using ? using : "",
156
190
  withCheck: predicates.withCheck ? withCheck : "",
157
191
  });
158
- return rlsPolicyPreviewSql(spec, enableRls);
192
+ return {
193
+ sql: rlsPolicyPreviewSql(spec, enableRls),
194
+ code: rlsPolicyCodeSource(spec),
195
+ };
159
196
  } catch {
160
197
  return null;
161
198
  }
162
199
  }, [name, table, command, behavior, predicates, using, withCheck, enableRls]);
163
200
 
201
+ const bindIdentity = (expr: string, col: string): string =>
202
+ col !== "" ? rlsBindOwnerExpr(expr, col) : expr;
203
+
164
204
  const applyTemplate = (tpl: RlsPolicyTemplate): void => {
205
+ const col = rlsTemplateUsesOwner(tpl) ? identityValue : "";
165
206
  setTemplateId(tpl.id);
166
207
  setName(tpl.name);
167
208
  setCommand(tpl.command);
168
209
  setBehavior(tpl.behavior ?? "PERMISSIVE");
169
210
  setSelectedActions((prev) => rlsSyncActionsForCommand(prev, tpl.command));
170
- setUsing(tpl.using ?? "true");
171
- setWithCheck(tpl.withCheck ?? "true");
211
+ setUsing(bindIdentity(tpl.using ?? "true", col));
212
+ setWithCheck(bindIdentity(tpl.withCheck ?? "true", col));
213
+ if (col !== "") setIdentityCol(col);
214
+ };
215
+
216
+ const setPolicyTable = (next: string): void => {
217
+ const nextCols = rlsTableSqlColumns(manifest, storeRef, next);
218
+ const nextCol =
219
+ (identityCol !== "" && nextCols.includes(identityCol) ? identityCol : null) ??
220
+ rlsOwnerColumn(nextCols) ??
221
+ "";
222
+ setTable(next);
223
+ setIdentityCol(nextCol);
224
+ const from = identityValue || "owner";
225
+ setUsing((prev) => rlsRewriteIdentityColumn(prev, from, nextCol));
226
+ setWithCheck((prev) => rlsRewriteIdentityColumn(prev, from, nextCol));
227
+ };
228
+
229
+ const setIdentityColumn = (next: string): void => {
230
+ const from = identityValue || "owner";
231
+ setIdentityCol(next);
232
+ setUsing((prev) => rlsRewriteIdentityColumn(prev, from, next));
233
+ setWithCheck((prev) => rlsRewriteIdentityColumn(prev, from, next));
172
234
  };
173
235
 
174
236
  const setGateMode = (next: RlsGateMode): void => {
@@ -184,13 +246,21 @@ export function RlsPolicySheet({
184
246
  };
185
247
 
186
248
  const toggleGate = (id: string): void => {
187
- setSelectedGates((current) =>
188
- current.includes(id) ? current.filter((item) => item !== id) : [...current, id],
189
- );
249
+ setSelectedGates((current) => {
250
+ const next = current.includes(id) ? current.filter((item) => item !== id) : [...current, id];
251
+ const expr = rlsGatePredicateSql(next);
252
+ setUsing(expr);
253
+ setWithCheck(expr);
254
+ return next;
255
+ });
190
256
  };
191
257
 
192
258
  const submit = async (): Promise<void> => {
193
259
  setError(null);
260
+ if (ownerUnbound) {
261
+ setError(`Pick a column on ${table} that stores the user id.`);
262
+ return;
263
+ }
194
264
  try {
195
265
  const spec = parseSqlPolicySpec({
196
266
  name,
@@ -234,6 +304,7 @@ export function RlsPolicySheet({
234
304
  >
235
305
  <SheetContent
236
306
  side="right"
307
+ showCloseButton={false}
237
308
  className="flex-col gap-0 overflow-hidden rounded-none bg-popover p-0 shadow-lg sm:flex-row sm:items-stretch data-[side=right]:w-auto data-[side=right]:max-w-[calc(100vw-1.5rem)] data-[side=right]:sm:max-w-none"
238
309
  data-slot="rls-policy-sheet"
239
310
  >
@@ -241,8 +312,8 @@ export function RlsPolicySheet({
241
312
  className="relative flex min-h-0 w-full flex-1 flex-col sm:w-[28rem] sm:flex-none"
242
313
  data-slot="rls-policy-form-sheet"
243
314
  >
244
- <SheetHeader className="shrink-0 gap-2 border-b border-border/50">
245
- <div className="flex items-start gap-3 pr-8">
315
+ <SheetHeader className="flex-row items-stretch gap-0 border-b border-border/60 p-0">
316
+ <div className="flex min-w-0 flex-1 items-start gap-3 p-4">
246
317
  <div className="flex size-9 items-center justify-center rounded-full border border-border/50 bg-muted/20">
247
318
  <HugeiconsIcon icon={SecurityCheckIcon} className="size-4" aria-hidden />
248
319
  </div>
@@ -253,154 +324,229 @@ export function RlsPolicySheet({
253
324
  </SheetDescription>
254
325
  </div>
255
326
  </div>
327
+ <span className="w-px shrink-0 self-stretch bg-border/60" aria-hidden />
328
+ <SheetClose
329
+ className={cn(EXPLORER_ICON_BUTTON_CLASS, "min-w-11")}
330
+ data-slot="rls-policy-form-close"
331
+ aria-label="Close"
332
+ >
333
+ <HugeiconsIcon icon={Cancel01Icon} className="size-4" aria-hidden />
334
+ <span className="sr-only">Close</span>
335
+ </SheetClose>
256
336
  </SheetHeader>
257
- <div className="min-h-0 flex-1 overflow-y-auto" data-slot="rls-policy-form-scroll">
258
- <div className="border-b border-border/50">
259
- <div className="flex items-center justify-between gap-2 px-4 pt-2.5">
260
- <p className="text-[10px] font-semibold tracking-[0.12em] text-muted-foreground uppercase">
261
- Policy name
262
- </p>
263
- <SheetTextToggle
264
- active={templatesOpen}
265
- aria-expanded={templatesOpen}
266
- aria-controls={templatesOpen ? "rls-policy-templates-sheet" : undefined}
267
- onClick={() => setTemplatesOpen((open) => !open)}
268
- >
269
- <HugeiconsIcon icon={LeftToRightListBulletIcon} className="size-3" aria-hidden />
270
- Templates
271
- </SheetTextToggle>
272
- </div>
273
- <Input
274
- value={name}
275
- onChange={(event) => setName(event.target.value)}
276
- placeholder="read_all"
277
- aria-label="Policy name"
278
- flat
279
- className={cn(SHEET_CONTROL, "font-mono")}
280
- />
281
- </div>
282
- <SheetPair>
283
- <SheetField label="Table" split="start">
284
- <Select value={table} onValueChange={setTable}>
285
- <SelectTrigger flat className={cn(SHEET_CONTROL, "font-mono")}>
286
- <SelectValue placeholder="Select a table" />
287
- </SelectTrigger>
288
- <SelectContent>
289
- {tables.map((item) => (
290
- <SelectItem key={item} value={item} className="font-mono text-[12px]">
291
- {item}
292
- </SelectItem>
293
- ))}
294
- </SelectContent>
295
- </Select>
296
- </SheetField>
297
- <SheetField label="Behavior" split="end">
298
- <Select
299
- value={behavior}
300
- onValueChange={(next) => setBehavior(next as SqlPolicyBehavior)}
301
- >
302
- <SelectTrigger flat className={SHEET_CONTROL}>
303
- <SelectValue />
304
- </SelectTrigger>
305
- <SelectContent>
306
- <SelectItem value="PERMISSIVE">Permissive</SelectItem>
307
- <SelectItem value="RESTRICTIVE">Restrictive</SelectItem>
308
- </SelectContent>
309
- </Select>
310
- </SheetField>
311
- </SheetPair>
312
- <SheetChapter label="Gate" hint="Who this policy pairs with" />
313
- <GateSection
314
- advanced={advanced}
315
- onAdvancedChange={setAdvanced}
316
- mode={gateMode}
317
- onModeChange={setGateMode}
318
- catalog={catalog}
319
- query={gateQuery}
320
- onQueryChange={setGateQuery}
321
- selection={selection}
322
- onToggleGate={toggleGate}
323
- />
324
- {advanced || gateMode === "write" ? (
325
- <CommandRow
326
- commands={advanced ? RLS_POLICY_COMMANDS : WRITE_COMMANDS}
327
- value={command}
328
- onChange={setPolicyCommand}
329
- />
330
- ) : null}
331
- <SheetChapter
332
- label="Rows"
333
- hint={
334
- predicates.using && predicates.withCheck
335
- ? "Existing rows and new rows"
336
- : predicates.withCheck
337
- ? "New rows"
338
- : "Existing rows"
339
- }
340
- />
341
- {predicates.using ? (
342
- <SheetField label="USING">
343
- <Input
344
- value={using}
345
- onChange={(event) => setUsing(event.target.value)}
346
- placeholder="true"
347
- flat
348
- className={cn(SHEET_CONTROL, "font-mono")}
349
- />
350
- </SheetField>
351
- ) : null}
352
- {predicates.withCheck ? (
353
- <SheetField label="WITH CHECK">
354
- <Input
355
- value={withCheck}
356
- onChange={(event) => setWithCheck(event.target.value)}
357
- placeholder="true"
358
- flat
359
- className={cn(SHEET_CONTROL, "font-mono")}
360
- />
361
- </SheetField>
362
- ) : null}
363
- </div>
364
- <div
365
- className="shrink-0 border-t border-border/50 bg-muted/15"
366
- data-slot="rls-policy-sql-dock"
367
- >
368
- <div className="flex items-center justify-between gap-3 px-4 py-2">
369
- <p className="text-[10px] font-semibold tracking-[0.12em] text-muted-foreground uppercase">
370
- SQL
371
- </p>
372
- <label className="inline-flex items-center gap-2">
373
- <span className="text-[11px] text-muted-foreground">Enable RLS</span>
374
- <Switch
375
- size="sm"
376
- checked={enableRls}
377
- onCheckedChange={setEnableRls}
378
- ariaLabel="Enable RLS on this table"
337
+ <ResizablePanelGroup orientation="vertical" className="min-h-0 flex-1">
338
+ <ResizablePanel defaultSize="68%" minSize="28%" className="min-h-0">
339
+ <div className="h-full min-h-0 overflow-y-auto" data-slot="rls-policy-form-scroll">
340
+ <div className="border-b border-border/50">
341
+ <div className="flex items-center justify-between gap-2 px-4 pt-2.5">
342
+ <p className="text-[10px] font-semibold tracking-[0.12em] text-muted-foreground uppercase">
343
+ Policy name
344
+ </p>
345
+ <SheetTextToggle
346
+ active={templatesOpen}
347
+ aria-expanded={templatesOpen}
348
+ aria-controls={templatesOpen ? "rls-policy-templates-sheet" : undefined}
349
+ onClick={() => setTemplatesOpen((open) => !open)}
350
+ >
351
+ <HugeiconsIcon
352
+ icon={LeftToRightListBulletIcon}
353
+ className="size-3"
354
+ aria-hidden
355
+ />
356
+ Templates
357
+ </SheetTextToggle>
358
+ </div>
359
+ <Input
360
+ value={name}
361
+ onChange={(event) => setName(event.target.value)}
362
+ placeholder="read_all"
363
+ aria-label="Policy name"
364
+ flat
365
+ className={cn(SHEET_CONTROL, "font-mono")}
366
+ />
367
+ </div>
368
+ <SheetPair>
369
+ <SheetField label="Table" split="start">
370
+ <Select value={table} onValueChange={setPolicyTable}>
371
+ <SelectTrigger flat className={cn(SHEET_CONTROL, "font-mono")}>
372
+ <SelectValue placeholder="Select a table" />
373
+ </SelectTrigger>
374
+ <SelectContent>
375
+ {tables.map((item) => (
376
+ <SelectItem key={item} value={item} className="font-mono text-[12px]">
377
+ {item}
378
+ </SelectItem>
379
+ ))}
380
+ </SelectContent>
381
+ </Select>
382
+ </SheetField>
383
+ <SheetField label="Behavior" split="end">
384
+ <Select
385
+ value={behavior}
386
+ onValueChange={(next) => setBehavior(next as SqlPolicyBehavior)}
387
+ >
388
+ <SelectTrigger flat className={SHEET_CONTROL}>
389
+ <SelectValue />
390
+ </SelectTrigger>
391
+ <SelectContent>
392
+ <SelectItem value="PERMISSIVE">Permissive</SelectItem>
393
+ <SelectItem value="RESTRICTIVE">Restrictive</SelectItem>
394
+ </SelectContent>
395
+ </Select>
396
+ </SheetField>
397
+ </SheetPair>
398
+ <SheetChapter label="Gate" hint="Who this policy pairs with" />
399
+ <GateSection
400
+ advanced={advanced}
401
+ onAdvancedChange={setAdvanced}
402
+ mode={gateMode}
403
+ onModeChange={setGateMode}
404
+ catalog={catalog}
405
+ query={gateQuery}
406
+ onQueryChange={setGateQuery}
407
+ selection={selection}
408
+ onToggleGate={toggleGate}
379
409
  />
380
- </label>
381
- </div>
382
- {preview ? (
383
- <div className="max-h-28 overflow-y-auto px-4 pb-3">
384
- <AgentCode
385
- code={preview}
386
- language="sql"
387
- className="overflow-visible whitespace-pre-wrap break-words text-[11px] leading-relaxed"
410
+ {advanced || gateMode === "write" ? (
411
+ <CommandRow
412
+ commands={advanced ? RLS_POLICY_COMMANDS : WRITE_COMMANDS}
413
+ value={command}
414
+ onChange={setPolicyCommand}
415
+ />
416
+ ) : null}
417
+ <SheetChapter
418
+ label="Rows"
419
+ hint={
420
+ predicates.using && predicates.withCheck
421
+ ? "Existing rows and new rows"
422
+ : predicates.withCheck
423
+ ? "New rows"
424
+ : "Existing rows"
425
+ }
388
426
  />
427
+ {showIdentitySelect ? (
428
+ <SheetField label="Column" hint="compared to oke.user()">
429
+ <Select value={identityValue} onValueChange={setIdentityColumn}>
430
+ <SelectTrigger
431
+ flat
432
+ className={cn(SHEET_CONTROL, "font-mono")}
433
+ data-slot="rls-identity-column"
434
+ >
435
+ <SelectValue placeholder="Select a column" />
436
+ </SelectTrigger>
437
+ <SelectContent>
438
+ {tableColumns.map((col) => (
439
+ <SelectItem key={col.sqlName} value={col.sqlName}>
440
+ <span className="flex min-w-0 items-center gap-2">
441
+ <SchemaColumnMarks
442
+ primaryKey={col.primaryKey}
443
+ foreignKey={col.foreignKey}
444
+ unique={col.unique}
445
+ inferred={col.inferred}
446
+ />
447
+ <span className="min-w-0 truncate font-mono text-[12px]">
448
+ {col.sqlName}
449
+ </span>
450
+ </span>
451
+ </SelectItem>
452
+ ))}
453
+ </SelectContent>
454
+ </Select>
455
+ </SheetField>
456
+ ) : null}
457
+ {predicates.using ? (
458
+ <SheetField label="USING">
459
+ <Input
460
+ value={using}
461
+ onChange={(event) => setUsing(event.target.value)}
462
+ placeholder="true"
463
+ flat
464
+ className={cn(SHEET_CONTROL, "font-mono")}
465
+ />
466
+ </SheetField>
467
+ ) : null}
468
+ {predicates.withCheck ? (
469
+ <SheetField label="WITH CHECK">
470
+ <Input
471
+ value={withCheck}
472
+ onChange={(event) => setWithCheck(event.target.value)}
473
+ placeholder="true"
474
+ flat
475
+ className={cn(SHEET_CONTROL, "font-mono")}
476
+ />
477
+ </SheetField>
478
+ ) : null}
479
+ </div>
480
+ </ResizablePanel>
481
+ <ResizableHandle withHandle data-slot="rls-policy-sql-handle" />
482
+ <ResizablePanel defaultSize="32%" minSize="16%" className="min-h-0">
483
+ <div
484
+ className="flex h-full min-h-0 flex-col bg-muted/15"
485
+ data-slot="rls-policy-sql-dock"
486
+ >
487
+ <div className="flex items-center justify-between gap-3 px-4 py-2">
488
+ <div className="flex items-center gap-2">
489
+ <button
490
+ type="button"
491
+ className={cn(
492
+ "text-[10px] font-semibold tracking-[0.12em] uppercase",
493
+ dock === "sql" ? "text-foreground" : "text-muted-foreground",
494
+ )}
495
+ onClick={() => setDock("sql")}
496
+ >
497
+ SQL
498
+ </button>
499
+ <button
500
+ type="button"
501
+ className={cn(
502
+ "text-[10px] font-semibold tracking-[0.12em] uppercase",
503
+ dock === "code" ? "text-foreground" : "text-muted-foreground",
504
+ )}
505
+ onClick={() => setDock("code")}
506
+ >
507
+ Code
508
+ </button>
509
+ </div>
510
+ <label className="inline-flex items-center gap-2">
511
+ <span className="text-[11px] text-muted-foreground">Enable RLS</span>
512
+ <Switch
513
+ size="sm"
514
+ checked={enableRls}
515
+ onCheckedChange={setEnableRls}
516
+ ariaLabel="Enable RLS on this table"
517
+ />
518
+ </label>
519
+ </div>
520
+ {preview ? (
521
+ <div className="min-h-0 flex-1 overflow-y-auto px-4 pb-3">
522
+ <AgentCode
523
+ code={dock === "sql" ? preview.sql : preview.code}
524
+ language={dock === "sql" ? "sql" : "typescript"}
525
+ className="overflow-visible whitespace-pre-wrap break-words text-[11px] leading-relaxed"
526
+ />
527
+ </div>
528
+ ) : (
529
+ <p className="min-h-0 flex-1 px-4 pb-3 text-[11px] text-muted-foreground">
530
+ Fill name, table, and an expression.
531
+ </p>
532
+ )}
533
+ {ownerUnbound ? (
534
+ <SheetError slot="rls-owner-missing">
535
+ Pick a column on {table} — this table has no owner / owner_email / creator_email
536
+ guess.
537
+ </SheetError>
538
+ ) : null}
539
+ {error ? <SheetError>{error}</SheetError> : null}
389
540
  </div>
390
- ) : (
391
- <p className="px-4 pb-3 text-[11px] text-muted-foreground">
392
- Fill name, table, and an expression.
393
- </p>
394
- )}
395
- {error ? <SheetError>{error}</SheetError> : null}
396
- </div>
541
+ </ResizablePanel>
542
+ </ResizablePanelGroup>
397
543
  <SheetFooter>
398
544
  <SheetFooterButton split onClick={() => onOpenChange(false)}>
399
545
  Cancel
400
546
  </SheetFooterButton>
401
547
  <SheetFooterButton
402
548
  variant="default"
403
- disabled={edit.isPending || !preview}
549
+ disabled={edit.isPending || !preview || ownerUnbound}
404
550
  onClick={() => void submit()}
405
551
  >
406
552
  {edit.isPending ? "Creating…" : "Create policy"}
@@ -418,6 +564,7 @@ export function RlsPolicySheet({
418
564
  onQueryChange={setTemplateQuery}
419
565
  selectedId={templateId}
420
566
  onSelect={applyTemplate}
567
+ onClose={() => setTemplatesOpen(false)}
421
568
  />
422
569
  </aside>
423
570
  ) : null}
@@ -439,34 +586,48 @@ function PolicyTemplateLibrary({
439
586
  onQueryChange,
440
587
  selectedId,
441
588
  onSelect,
589
+ onClose,
442
590
  }: {
443
591
  readonly query: string;
444
592
  readonly onQueryChange: (query: string) => void;
445
593
  readonly selectedId: string | null;
446
594
  readonly onSelect: (tpl: RlsPolicyTemplate) => void;
595
+ readonly onClose: () => void;
447
596
  }): JSX.Element {
448
597
  const visible = filterRlsPolicyTemplates(RLS_POLICY_TEMPLATES, query);
449
598
  return (
450
599
  <div className="flex min-h-0 flex-1 flex-col" data-slot="rls-policy-templates">
451
- <div className="shrink-0 border-b border-border/50">
452
- <p className="px-3 pt-2.5 pr-10 text-[10px] font-semibold tracking-[0.12em] text-muted-foreground uppercase">
453
- Templates
454
- </p>
455
- <label className="relative block">
456
- <HugeiconsIcon
457
- icon={Search01Icon}
458
- className="pointer-events-none absolute top-1/2 left-3 size-3.5 -translate-y-1/2 text-muted-foreground"
459
- aria-hidden
460
- />
461
- <Input
462
- value={query}
463
- onChange={(event) => onQueryChange(event.target.value)}
464
- placeholder="Search templates"
465
- aria-label="Search templates"
466
- flat
467
- className={cn(SHEET_SEARCH, "font-mono")}
468
- />
469
- </label>
600
+ <div className="flex shrink-0 items-stretch border-b border-border/60">
601
+ <div className="min-w-0 flex-1">
602
+ <p className="px-3 pt-2.5 text-[10px] font-semibold tracking-[0.12em] text-muted-foreground uppercase">
603
+ Templates
604
+ </p>
605
+ <label className="relative block">
606
+ <HugeiconsIcon
607
+ icon={Search01Icon}
608
+ className="pointer-events-none absolute top-1/2 left-3 size-3.5 -translate-y-1/2 text-muted-foreground"
609
+ aria-hidden
610
+ />
611
+ <Input
612
+ value={query}
613
+ onChange={(event) => onQueryChange(event.target.value)}
614
+ placeholder="Search templates"
615
+ aria-label="Search templates"
616
+ flat
617
+ className={cn(SHEET_SEARCH, "font-mono")}
618
+ />
619
+ </label>
620
+ </div>
621
+ <span className="w-px shrink-0 self-stretch bg-border/60" aria-hidden />
622
+ <button
623
+ type="button"
624
+ className={cn(EXPLORER_ICON_BUTTON_CLASS, "min-w-11")}
625
+ onClick={onClose}
626
+ aria-label="Close templates"
627
+ data-slot="rls-policy-templates-close"
628
+ >
629
+ <HugeiconsIcon icon={Cancel01Icon} className="size-4" aria-hidden />
630
+ </button>
470
631
  </div>
471
632
  <ul className="min-h-0 flex-1 overflow-y-auto p-2">
472
633
  {visible.length === 0 ? (
@@ -33,6 +33,8 @@ export interface SqlInsertSheetProps {
33
33
  readonly childName: string;
34
34
  readonly tenant?: string | null;
35
35
  readonly columns: readonly StoreGridColumn[];
36
+ readonly asGate?: string | null;
37
+ readonly asUserId?: string | null;
36
38
  }
37
39
 
38
40
  /**
@@ -47,6 +49,8 @@ export function SqlInsertSheet({
47
49
  childName,
48
50
  tenant,
49
51
  columns,
52
+ asGate = null,
53
+ asUserId = null,
50
54
  }: SqlInsertSheetProps): JSX.Element {
51
55
  const { mutate, isPending, reset } = useStoreEdit();
52
56
  const fields = useMemo(() => insertFormColumns(columns), [columns]);
@@ -77,6 +81,8 @@ export function SqlInsertSheet({
77
81
  ref: storeRef,
78
82
  child: childName,
79
83
  ...(tenant ? { tenant } : {}),
84
+ ...(asGate ? { asGate } : {}),
85
+ ...(asUserId ? { asUserId } : {}),
80
86
  patch: built.patch,
81
87
  commit: true,
82
88
  },