@rebasepro/studio 0.6.1 → 0.8.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 (37) hide show
  1. package/dist/ApiKeysView-D-_FSlNL.js +684 -0
  2. package/dist/ApiKeysView-D-_FSlNL.js.map +1 -0
  3. package/dist/{BranchesView-DncIRcZt.js → BranchesView-Dlg78EQ8.js} +4 -5
  4. package/dist/{BranchesView-DncIRcZt.js.map → BranchesView-Dlg78EQ8.js.map} +1 -1
  5. package/dist/{JSEditor-BhAbEjCP.js → JSEditor-Ca4XYGRp.js} +7 -7
  6. package/dist/JSEditor-Ca4XYGRp.js.map +1 -0
  7. package/dist/{LogsExplorer-CqtKILj8.js → LogsExplorer-J4xfsuv3.js} +49 -83
  8. package/dist/LogsExplorer-J4xfsuv3.js.map +1 -0
  9. package/dist/{RLSEditor-DpF1u9EC.js → RLSEditor-BM64laoW.js} +294 -107
  10. package/dist/RLSEditor-BM64laoW.js.map +1 -0
  11. package/dist/{SQLEditor-BLuq_zDM.js → SQLEditor-CuAhR-zr.js} +4 -4
  12. package/dist/{SQLEditor-BLuq_zDM.js.map → SQLEditor-CuAhR-zr.js.map} +1 -1
  13. package/dist/{SchemaVisualizer-BJK2u3C0.js → SchemaVisualizer-OibKoD3g.js} +2 -2
  14. package/dist/{SchemaVisualizer-BJK2u3C0.js.map → SchemaVisualizer-OibKoD3g.js.map} +1 -1
  15. package/dist/{StorageView-CvrnHmDG.js → StorageView-BMhD29YO.js} +33 -5
  16. package/dist/StorageView-BMhD29YO.js.map +1 -0
  17. package/dist/components/ApiKeys/ApiKeysView.d.ts +2 -0
  18. package/dist/index.es.js +141 -94
  19. package/dist/index.es.js.map +1 -1
  20. package/dist/index.umd.js +1221 -317
  21. package/dist/index.umd.js.map +1 -1
  22. package/package.json +8 -8
  23. package/src/components/ApiKeys/ApiKeysView.tsx +580 -0
  24. package/src/components/Branches/BranchesView.tsx +3 -4
  25. package/src/components/JSEditor/JSEditorSidebar.tsx +3 -3
  26. package/src/components/JSEditor/JSMonacoEditor.tsx +3 -3
  27. package/src/components/LogsExplorer/LogsExplorer.tsx +46 -84
  28. package/src/components/RLSEditor/RLSEditor.tsx +354 -117
  29. package/src/components/RebaseStudio.tsx +10 -1
  30. package/src/components/SQLEditor/SchemaBrowser.tsx +3 -3
  31. package/src/components/StorageView/StorageView.tsx +43 -4
  32. package/src/components/StudioHomePage.tsx +144 -85
  33. package/src/utils/pgColumnToProperty.ts +2 -2
  34. package/dist/JSEditor-BhAbEjCP.js.map +0 -1
  35. package/dist/LogsExplorer-CqtKILj8.js.map +0 -1
  36. package/dist/RLSEditor-DpF1u9EC.js.map +0 -1
  37. package/dist/StorageView-CvrnHmDG.js.map +0 -1
@@ -1,9 +1,10 @@
1
1
  import { t as MonacoEditor } from "./MonacoEditor-COZqrIJ1.js";
2
2
  import { ErrorView, useRebaseContext, useSnackbarController, useStudioCollectionRegistry, useTranslation } from "@rebasepro/core";
3
3
  import { useCallback, useEffect, useMemo, useState } from "react";
4
- import { Alert, AlertTriangleIcon, Button, Chip, CircularProgress, Dialog, DialogActions, DialogContent, DialogTitle, HelpCircleIcon, IconButton, KeyIcon, MultiSelect, MultiSelectItem, Paper, RefreshCwIcon, ResizablePanels, Select, SelectItem, ShieldIcon, Tab, Tabs, TextField, Tooltip, Trash2Icon, Typography, cls, defaultBorderMixin, iconSize } from "@rebasepro/ui";
4
+ import { Alert, AlertTriangleIcon, Button, Chip, CircularProgress, DatabaseIcon, Dialog, DialogActions, DialogContent, DialogTitle, HelpCircleIcon, IconButton, KeyIcon, Link2Icon, LockIcon, MultiSelect, MultiSelectItem, Paper, RefreshCwIcon, ResizablePanels, Select, SelectItem, ShieldIcon, Tab, Tabs, TextField, Tooltip, Trash2Icon, Typography, cls, defaultBorderMixin, iconSize } from "@rebasepro/ui";
5
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
6
  import { isPostgresCollection } from "@rebasepro/types";
7
+ import { JUNCTION_TABLES_SQL, REBASE_INTERNAL_PREFIXES, REBASE_INTERNAL_SCHEMAS } from "@rebasepro/common";
7
8
  //#region src/components/RLSEditor/PolicyEditor.tsx
8
9
  var COMMAND_OPTIONS = [
9
10
  "ALL",
@@ -573,6 +574,12 @@ var PolicyEditor = ({ policy, schema, table, onSave, onCancel }) => {
573
574
  };
574
575
  //#endregion
575
576
  //#region src/components/RLSEditor/RLSEditor.tsx
577
+ function classifyTableClient(tableName, schemaName, junctionTableNames, isMappedToCollection) {
578
+ if (REBASE_INTERNAL_SCHEMAS.includes(schemaName) || REBASE_INTERNAL_PREFIXES.some((prefix) => tableName.startsWith(prefix))) return "internal";
579
+ if (isMappedToCollection) return "collection";
580
+ if (junctionTableNames.has(tableName)) return "junction";
581
+ return "other";
582
+ }
576
583
  /**
577
584
  * Validates and double-quotes a SQL identifier to prevent injection.
578
585
  * Only allows safe Postgres identifiers (letters, digits, underscores).
@@ -582,6 +589,81 @@ function sanitizeSqlIdentifier(name) {
582
589
  if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) throw new Error(`Invalid SQL identifier: "${name}". Only letters, digits, and underscores are allowed.`);
583
590
  return `"${name}"`;
584
591
  }
592
+ function SidebarSection({ title, icon, expanded, onToggle, count, children }) {
593
+ return /* @__PURE__ */ jsxs("div", {
594
+ className: "mb-2",
595
+ children: [/* @__PURE__ */ jsxs("div", {
596
+ className: "flex items-center p-1.5 cursor-pointer hover:bg-surface-100 dark:hover:bg-surface-900 rounded transition-colors",
597
+ onClick: onToggle,
598
+ children: [
599
+ /* @__PURE__ */ jsx("svg", {
600
+ className: cls("w-3 h-3 mr-1.5 transition-transform text-text-disabled dark:text-text-disabled-dark", expanded ? "rotate-90" : ""),
601
+ fill: "currentColor",
602
+ viewBox: "0 0 20 20",
603
+ children: /* @__PURE__ */ jsx("path", { d: "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" })
604
+ }),
605
+ icon,
606
+ /* @__PURE__ */ jsx(Typography, {
607
+ variant: "body2",
608
+ className: "text-text-primary dark:text-text-primary-dark font-medium text-xs truncate flex-grow ml-1.5",
609
+ children: title
610
+ }),
611
+ count !== void 0 && /* @__PURE__ */ jsx("span", {
612
+ className: "text-[10px] text-text-disabled dark:text-text-disabled-dark font-medium tabular-nums mr-1",
613
+ children: count
614
+ })
615
+ ]
616
+ }), expanded && /* @__PURE__ */ jsx("div", {
617
+ className: "ml-3 mt-0.5 space-y-0.5",
618
+ children
619
+ })]
620
+ });
621
+ }
622
+ function SidebarTableRow({ table, isSelected, onSelect, badge, dimmed, t }) {
623
+ return /* @__PURE__ */ jsxs("div", {
624
+ onClick: onSelect,
625
+ className: cls("flex items-center p-1 cursor-pointer rounded transition-colors group relative", isSelected ? "bg-primary/10 text-primary dark:bg-primary/20 dark:text-primary-light" : "hover:bg-surface-100 dark:hover:bg-surface-900 text-text-secondary dark:text-text-secondary-dark", dimmed && !isSelected && "opacity-60"),
626
+ children: [
627
+ /* @__PURE__ */ jsx("svg", {
628
+ className: "w-3.5 h-3.5 mr-1 shrink-0 text-text-disabled dark:text-text-disabled-dark",
629
+ fill: "none",
630
+ stroke: "currentColor",
631
+ viewBox: "0 0 24 24",
632
+ children: /* @__PURE__ */ jsx("path", {
633
+ strokeLinecap: "round",
634
+ strokeLinejoin: "round",
635
+ strokeWidth: 2,
636
+ d: "M3 10h18M3 14h18m-9-4v8m-7 0h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
637
+ })
638
+ }),
639
+ /* @__PURE__ */ jsx(Typography, {
640
+ variant: "body2",
641
+ className: "text-xs truncate flex-1 min-w-0",
642
+ children: table.tableName
643
+ }),
644
+ /* @__PURE__ */ jsxs("div", {
645
+ className: "flex items-center gap-1.5 shrink-0 ml-2",
646
+ children: [
647
+ badge && /* @__PURE__ */ jsx("span", {
648
+ className: "text-[9px] uppercase tracking-wider font-semibold text-text-disabled dark:text-text-disabled-dark bg-surface-200 dark:bg-surface-800 rounded px-1 py-px",
649
+ children: badge
650
+ }),
651
+ table.rlsEnabled ? /* @__PURE__ */ jsx(Tooltip, {
652
+ title: t("studio_rls_enabled"),
653
+ children: /* @__PURE__ */ jsx("div", { className: "w-1.5 h-1.5 rounded-full bg-green-500" })
654
+ }) : /* @__PURE__ */ jsx(Tooltip, {
655
+ title: t("studio_rls_disabled"),
656
+ children: /* @__PURE__ */ jsx("div", { className: "w-1.5 h-1.5 rounded-full bg-orange-400 opacity-50" })
657
+ }),
658
+ /* @__PURE__ */ jsx("span", {
659
+ className: "text-[10px] opacity-40 group-hover:opacity-100 min-w-[1.2rem] text-right font-medium",
660
+ children: table.policies.length
661
+ })
662
+ ]
663
+ })
664
+ ]
665
+ });
666
+ }
585
667
  var RLSEditor = ({ apiUrl = "" }) => {
586
668
  const { databaseAdmin } = useRebaseContext();
587
669
  const snackbarController = useSnackbarController();
@@ -592,6 +674,7 @@ var RLSEditor = ({ apiUrl = "" }) => {
592
674
  const [tables, setTables] = useState([]);
593
675
  const [selectedTable, setSelectedTable] = useState(null);
594
676
  const [activeTab, setActiveTab] = useState(0);
677
+ const [junctionTableNames, setJunctionTableNames] = useState(/* @__PURE__ */ new Set());
595
678
  const [editingPolicy, setEditingPolicy] = useState(null);
596
679
  const [sidebarSize, setSidebarSize] = useState(() => {
597
680
  try {
@@ -601,7 +684,11 @@ var RLSEditor = ({ apiUrl = "" }) => {
601
684
  return 20;
602
685
  }
603
686
  });
604
- const [expandedSchemas, setExpandedSchemas] = useState({ public: true });
687
+ const [expandedSections, setExpandedSections] = useState({
688
+ collection: true,
689
+ other: true,
690
+ internal: false
691
+ });
605
692
  const [sidebarTab, setSidebarTab] = useState("tables");
606
693
  useEffect(() => {
607
694
  try {
@@ -683,7 +770,15 @@ var RLSEditor = ({ apiUrl = "" }) => {
683
770
  });
684
771
  const sortedTables = Object.values(tableMap).sort((a, b) => a.tableName.localeCompare(b.tableName));
685
772
  setTables(sortedTables);
686
- if (sortedTables.length > 0 && !selectedTable) setSelectedTable(`${sortedTables[0].schemaName}.${sortedTables[0].tableName}`);
773
+ try {
774
+ const jRows = extractRows(await databaseAdmin.executeSql(JUNCTION_TABLES_SQL));
775
+ setJunctionTableNames(new Set(jRows.map((r) => r.table_name).filter(Boolean)));
776
+ } catch {}
777
+ if (sortedTables.length > 0 && !selectedTable) {
778
+ const firstUserTable = sortedTables.find((t) => !REBASE_INTERNAL_SCHEMAS.includes(t.schemaName));
779
+ if (firstUserTable) setSelectedTable(`${firstUserTable.schemaName}.${firstUserTable.tableName}`);
780
+ else setSelectedTable(`${sortedTables[0].schemaName}.${sortedTables[0].tableName}`);
781
+ }
687
782
  } catch (e) {
688
783
  console.error("RLS fetch error:", e);
689
784
  setError("Failed to fetch RLS policies: " + (e instanceof Error ? e.message : String(e)));
@@ -701,18 +796,38 @@ var RLSEditor = ({ apiUrl = "" }) => {
701
796
  if (!selectedTable) return null;
702
797
  return tables.find((t) => `${t.schemaName}.${t.tableName}` === selectedTable) || null;
703
798
  }, [selectedTable, tables]);
704
- const groupedTables = useMemo(() => {
705
- const groups = {};
799
+ /** Categorize tables into 4 buckets for the sidebar. */
800
+ const categorizedTables = useMemo(() => {
801
+ const groups = {
802
+ collection: [],
803
+ junction: [],
804
+ internal: [],
805
+ other: []
806
+ };
706
807
  tables.forEach((table) => {
707
- if (!groups[table.schemaName]) groups[table.schemaName] = [];
708
- groups[table.schemaName].push(table);
808
+ const isMapped = !!collectionRegistry.collections?.find((c) => c.id === table.tableName || c.path === table.tableName || c.table === table.tableName || c.slug === table.tableName || c.collectionId === table.tableName);
809
+ groups[classifyTableClient(table.tableName, table.schemaName, junctionTableNames, isMapped)].push(table);
709
810
  });
710
811
  return groups;
711
- }, [tables]);
812
+ }, [
813
+ tables,
814
+ junctionTableNames,
815
+ collectionRegistry.collections
816
+ ]);
712
817
  const activeCollection = useMemo(() => {
713
818
  if (!activeTableData) return null;
714
819
  return collectionRegistry.collections?.find((c) => c.id === activeTableData.tableName || c.path === activeTableData.tableName || c.table === activeTableData.tableName || c.slug === activeTableData.tableName || c.collectionId === activeTableData.tableName) || null;
715
820
  }, [activeTableData, collectionRegistry.collections]);
821
+ /** The category of the currently selected table. */
822
+ const activeTableCategory = useMemo(() => {
823
+ if (!activeTableData) return null;
824
+ const isMapped = !!activeCollection;
825
+ return classifyTableClient(activeTableData.tableName, activeTableData.schemaName, junctionTableNames, isMapped);
826
+ }, [
827
+ activeTableData,
828
+ activeCollection,
829
+ junctionTableNames
830
+ ]);
716
831
  const mergedPolicies = useMemo(() => {
717
832
  if (!activeTableData) return [];
718
833
  const policiesMap = {};
@@ -810,74 +925,78 @@ var RLSEditor = ({ apiUrl = "" }) => {
810
925
  children: isLoading && tables.length === 0 ? /* @__PURE__ */ jsx("div", {
811
926
  className: "flex justify-center p-4",
812
927
  children: /* @__PURE__ */ jsx(CircularProgress, { size: "small" })
813
- }) : Object.keys(groupedTables).length === 0 ? /* @__PURE__ */ jsx("div", {
928
+ }) : tables.length === 0 ? /* @__PURE__ */ jsx("div", {
814
929
  className: "p-4 text-center",
815
930
  children: /* @__PURE__ */ jsx(Typography, {
816
931
  variant: "caption",
817
932
  className: "text-text-disabled dark:text-text-disabled-dark italic",
818
933
  children: t("studio_rls_no_tables")
819
934
  })
820
- }) : Object.entries(groupedTables).map(([schemaName, schemaTables]) => /* @__PURE__ */ jsxs("div", {
821
- className: "mb-2",
822
- children: [/* @__PURE__ */ jsxs("div", {
823
- className: "flex items-center p-1 cursor-pointer hover:bg-surface-100 dark:hover:bg-surface-950 rounded transition-colors",
824
- onClick: () => setExpandedSchemas((prev) => ({
935
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [
936
+ categorizedTables.collection.length > 0 && /* @__PURE__ */ jsx(SidebarSection, {
937
+ title: "Schema Collections",
938
+ icon: /* @__PURE__ */ jsx(DatabaseIcon, {
939
+ size: 12,
940
+ className: "text-primary dark:text-primary-light"
941
+ }),
942
+ expanded: expandedSections.collection ?? true,
943
+ onToggle: () => setExpandedSections((prev) => ({
825
944
  ...prev,
826
- [schemaName]: !prev[schemaName]
945
+ collection: !(prev.collection ?? true)
827
946
  })),
828
- children: [/* @__PURE__ */ jsx("svg", {
829
- className: cls("w-3 h-3 mr-1 transition-transform", expandedSchemas[schemaName] ? "rotate-90" : ""),
830
- fill: "currentColor",
831
- viewBox: "0 0 20 20",
832
- children: /* @__PURE__ */ jsx("path", { d: "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" })
833
- }), /* @__PURE__ */ jsx(Typography, {
834
- variant: "body2",
835
- className: "text-text-primary dark:text-text-primary-dark font-medium text-xs truncate flex-grow",
836
- children: schemaName
837
- })]
838
- }), expandedSchemas[schemaName] && /* @__PURE__ */ jsx("div", {
839
- className: "ml-3 mt-1 space-y-0.5",
840
- children: schemaTables.map((table) => {
841
- const key = `${table.schemaName}.${table.tableName}`;
842
- return /* @__PURE__ */ jsxs("div", {
843
- onClick: () => setSelectedTable(key),
844
- className: cls("flex items-center p-1 cursor-pointer rounded transition-colors group relative", selectedTable === key ? "bg-primary/10 text-primary dark:bg-primary/20 dark:text-primary-light" : "hover:bg-surface-100 dark:hover:bg-surface-950 text-text-secondary dark:text-text-secondary-dark"),
845
- children: [
846
- /* @__PURE__ */ jsx("svg", {
847
- className: "w-3.5 h-3.5 mr-1 shrink-0 text-text-disabled dark:text-text-disabled-dark",
848
- fill: "none",
849
- stroke: "currentColor",
850
- viewBox: "0 0 24 24",
851
- children: /* @__PURE__ */ jsx("path", {
852
- strokeLinecap: "round",
853
- strokeLinejoin: "round",
854
- strokeWidth: 2,
855
- d: "M3 10h18M3 14h18m-9-4v8m-7 0h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
856
- })
857
- }),
858
- /* @__PURE__ */ jsx(Typography, {
859
- variant: "body2",
860
- className: "text-xs truncate flex-1 min-w-0",
861
- children: table.tableName
862
- }),
863
- /* @__PURE__ */ jsxs("div", {
864
- className: "flex items-center gap-1.5 shrink-0 ml-2",
865
- children: [table.rlsEnabled ? /* @__PURE__ */ jsx(Tooltip, {
866
- title: t("studio_rls_enabled"),
867
- children: /* @__PURE__ */ jsx("div", { className: "w-1.5 h-1.5 rounded-full bg-green-500" })
868
- }) : /* @__PURE__ */ jsx(Tooltip, {
869
- title: t("studio_rls_disabled"),
870
- children: /* @__PURE__ */ jsx("div", { className: "w-1.5 h-1.5 rounded-full bg-orange-400 opacity-50" })
871
- }), /* @__PURE__ */ jsx("span", {
872
- className: "text-[10px] opacity-40 group-hover:opacity-100 min-w-[1.2rem] text-right font-medium",
873
- children: table.policies.length
874
- })]
875
- })
876
- ]
877
- }, key);
878
- })
879
- })]
880
- }, schemaName))
947
+ children: categorizedTables.collection.map((table) => /* @__PURE__ */ jsx(SidebarTableRow, {
948
+ table,
949
+ isSelected: selectedTable === `${table.schemaName}.${table.tableName}`,
950
+ onSelect: () => setSelectedTable(`${table.schemaName}.${table.tableName}`),
951
+ t
952
+ }, `${table.schemaName}.${table.tableName}`))
953
+ }),
954
+ (categorizedTables.other.length > 0 || categorizedTables.junction.length > 0) && /* @__PURE__ */ jsxs(SidebarSection, {
955
+ title: "Other Tables",
956
+ icon: /* @__PURE__ */ jsx(AlertTriangleIcon, {
957
+ size: 12,
958
+ className: "text-yellow-500 dark:text-yellow-400"
959
+ }),
960
+ expanded: expandedSections.other ?? true,
961
+ onToggle: () => setExpandedSections((prev) => ({
962
+ ...prev,
963
+ other: !(prev.other ?? true)
964
+ })),
965
+ children: [categorizedTables.other.map((table) => /* @__PURE__ */ jsx(SidebarTableRow, {
966
+ table,
967
+ isSelected: selectedTable === `${table.schemaName}.${table.tableName}`,
968
+ onSelect: () => setSelectedTable(`${table.schemaName}.${table.tableName}`),
969
+ t
970
+ }, `${table.schemaName}.${table.tableName}`)), categorizedTables.junction.map((table) => /* @__PURE__ */ jsx(SidebarTableRow, {
971
+ table,
972
+ isSelected: selectedTable === `${table.schemaName}.${table.tableName}`,
973
+ onSelect: () => setSelectedTable(`${table.schemaName}.${table.tableName}`),
974
+ badge: "Junction",
975
+ dimmed: true,
976
+ t
977
+ }, `${table.schemaName}.${table.tableName}`))]
978
+ }),
979
+ categorizedTables.internal.length > 0 && /* @__PURE__ */ jsx(SidebarSection, {
980
+ title: "Rebase Internal",
981
+ icon: /* @__PURE__ */ jsx(LockIcon, {
982
+ size: 12,
983
+ className: "text-text-disabled dark:text-text-disabled-dark"
984
+ }),
985
+ expanded: expandedSections.internal ?? false,
986
+ onToggle: () => setExpandedSections((prev) => ({
987
+ ...prev,
988
+ internal: !(prev.internal ?? false)
989
+ })),
990
+ count: categorizedTables.internal.length,
991
+ children: categorizedTables.internal.map((table) => /* @__PURE__ */ jsx(SidebarTableRow, {
992
+ table,
993
+ isSelected: selectedTable === `${table.schemaName}.${table.tableName}`,
994
+ onSelect: () => setSelectedTable(`${table.schemaName}.${table.tableName}`),
995
+ dimmed: true,
996
+ t
997
+ }, `${table.schemaName}.${table.tableName}`))
998
+ })
999
+ ] })
881
1000
  })]
882
1001
  }), sidebarTab === "info" && /* @__PURE__ */ jsxs("div", {
883
1002
  className: "flex flex-col h-full",
@@ -1040,10 +1159,11 @@ var RLSEditor = ({ apiUrl = "" }) => {
1040
1159
  size: "small",
1041
1160
  onClick: async () => {
1042
1161
  const table = activeTableData.tableName;
1162
+ const qualifiedTable = `${sanitizeSqlIdentifier(activeTableData.schemaName)}.${sanitizeSqlIdentifier(table)}`;
1043
1163
  const action = activeTableData.rlsEnabled ? "DISABLE" : "ENABLE";
1044
1164
  if (!confirm(`Are you sure you want to ${action.toLowerCase()} Row Level Security on "${table}"?`)) return;
1045
1165
  try {
1046
- await databaseAdmin.executeSql(`ALTER TABLE ${sanitizeSqlIdentifier(table)} ${action} ROW LEVEL SECURITY`);
1166
+ await databaseAdmin.executeSql(`ALTER TABLE ${qualifiedTable} ${action} ROW LEVEL SECURITY`);
1047
1167
  snackbarController.open({
1048
1168
  type: "success",
1049
1169
  message: `RLS ${action.toLowerCase()}d on ${table}`
@@ -1070,7 +1190,6 @@ var RLSEditor = ({ apiUrl = "" }) => {
1070
1190
  /* @__PURE__ */ jsx(Button, {
1071
1191
  size: "small",
1072
1192
  color: "primary",
1073
- disabled: !activeCollection,
1074
1193
  onClick: () => setEditingPolicy("new"),
1075
1194
  children: t("studio_rls_create_policy")
1076
1195
  })
@@ -1111,31 +1230,57 @@ var RLSEditor = ({ apiUrl = "" }) => {
1111
1230
  schema: activeTableData.schemaName,
1112
1231
  table: activeTableData.tableName,
1113
1232
  onSave: async (newPolicy) => {
1114
- if (!activeCollection) return;
1115
- const rule = {
1116
- name: newPolicy.policyname,
1117
- operation: newPolicy.cmd?.toLowerCase(),
1118
- mode: newPolicy.permissive?.toLowerCase(),
1119
- using: newPolicy.qual || void 0,
1120
- withCheck: newPolicy.with_check || void 0,
1121
- roles: newPolicy.roles
1122
- };
1123
- const existingRules = (isPostgresCollection(activeCollection) ? activeCollection.securityRules : void 0) || [];
1124
- let newRules;
1125
- if (editingPolicy === "new") newRules = [...existingRules, rule];
1126
- else newRules = existingRules.map((r) => r.name === editingPolicy.policyname ? rule : r);
1127
- try {
1128
- if (!(await fetch(`${apiUrl}/api/schema-editor/collection/save`, {
1129
- method: "POST",
1130
- headers: { "Content-Type": "application/json" },
1131
- body: JSON.stringify({
1132
- collectionId: activeCollection.id || activeCollection.path || activeCollection.alias || activeTableData.tableName,
1133
- collectionData: { securityRules: newRules }
1134
- })
1135
- })).ok) throw new Error("Failed to save policy");
1233
+ if (activeCollection) {
1234
+ const rule = {
1235
+ name: newPolicy.policyname,
1236
+ operation: newPolicy.cmd?.toLowerCase(),
1237
+ mode: newPolicy.permissive?.toLowerCase(),
1238
+ using: newPolicy.qual || void 0,
1239
+ withCheck: newPolicy.with_check || void 0,
1240
+ roles: newPolicy.roles
1241
+ };
1242
+ const existingRules = (isPostgresCollection(activeCollection) ? activeCollection.securityRules : void 0) || [];
1243
+ let newRules;
1244
+ if (editingPolicy === "new") newRules = [...existingRules, rule];
1245
+ else newRules = existingRules.map((r) => r.name === editingPolicy.policyname ? rule : r);
1246
+ try {
1247
+ if (!(await fetch(`${apiUrl}/api/schema-editor/collection/save`, {
1248
+ method: "POST",
1249
+ headers: { "Content-Type": "application/json" },
1250
+ body: JSON.stringify({
1251
+ collectionId: activeCollection.id || activeCollection.path || activeCollection.alias || activeTableData.tableName,
1252
+ collectionData: { securityRules: newRules }
1253
+ })
1254
+ })).ok) throw new Error("Failed to save policy");
1255
+ snackbarController.open({
1256
+ type: "success",
1257
+ message: "Policy saved successfully"
1258
+ });
1259
+ setEditingPolicy(null);
1260
+ fetchRLSData();
1261
+ } catch (e) {
1262
+ snackbarController.open({
1263
+ type: "error",
1264
+ message: e instanceof Error ? e.message : String(e)
1265
+ });
1266
+ }
1267
+ } else try {
1268
+ const qualifiedTable = `${sanitizeSqlIdentifier(activeTableData.schemaName)}.${sanitizeSqlIdentifier(activeTableData.tableName)}`;
1269
+ const policyName = sanitizeSqlIdentifier(newPolicy.policyname || "unnamed_policy");
1270
+ const cmd = newPolicy.cmd || "ALL";
1271
+ const permissive = (newPolicy.permissive || "PERMISSIVE") === "PERMISSIVE" ? "PERMISSIVE" : "RESTRICTIVE";
1272
+ const roles = newPolicy.roles && newPolicy.roles.length > 0 ? newPolicy.roles.map((r) => sanitizeSqlIdentifier(r)).join(", ") : "public";
1273
+ if (editingPolicy !== "new") await databaseAdmin.executeSql(`DROP POLICY IF EXISTS ${policyName} ON ${qualifiedTable}`);
1274
+ let sql = `CREATE POLICY ${policyName} ON ${qualifiedTable}`;
1275
+ sql += ` AS ${permissive}`;
1276
+ sql += ` FOR ${cmd}`;
1277
+ sql += ` TO ${roles}`;
1278
+ if (newPolicy.qual) sql += ` USING (${newPolicy.qual})`;
1279
+ if (newPolicy.with_check) sql += ` WITH CHECK (${newPolicy.with_check})`;
1280
+ await databaseAdmin.executeSql(sql);
1136
1281
  snackbarController.open({
1137
1282
  type: "success",
1138
- message: "Policy saved successfully"
1283
+ message: `Policy "${newPolicy.policyname}" applied directly via SQL`
1139
1284
  });
1140
1285
  setEditingPolicy(null);
1141
1286
  fetchRLSData();
@@ -1154,17 +1299,59 @@ var RLSEditor = ({ apiUrl = "" }) => {
1154
1299
  children: /* @__PURE__ */ jsxs("div", {
1155
1300
  className: "max-w-4xl mx-auto flex flex-col gap-6",
1156
1301
  children: [
1157
- activeTableData && !activeCollection && /* @__PURE__ */ jsxs(Alert, {
1302
+ activeTableData && activeTableCategory === "internal" && /* @__PURE__ */ jsx(Alert, {
1303
+ color: "info",
1304
+ children: /* @__PURE__ */ jsxs("div", {
1305
+ className: "flex items-start gap-2",
1306
+ children: [/* @__PURE__ */ jsx(LockIcon, {
1307
+ size: 16,
1308
+ className: "shrink-0 mt-0.5"
1309
+ }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Typography, {
1310
+ variant: "body2",
1311
+ className: "mb-1 font-semibold",
1312
+ children: "Rebase System Table"
1313
+ }), /* @__PURE__ */ jsx(Typography, {
1314
+ variant: "caption",
1315
+ className: "opacity-80",
1316
+ children: "This table is managed internally by Rebase. Its security policies are configured automatically. Editing policies on system tables is an advanced operation."
1317
+ })] })]
1318
+ })
1319
+ }),
1320
+ activeTableData && activeTableCategory === "junction" && /* @__PURE__ */ jsx(Alert, {
1321
+ color: "info",
1322
+ children: /* @__PURE__ */ jsxs("div", {
1323
+ className: "flex items-start gap-2",
1324
+ children: [/* @__PURE__ */ jsx(Link2Icon, {
1325
+ size: 16,
1326
+ className: "shrink-0 mt-0.5"
1327
+ }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Typography, {
1328
+ variant: "body2",
1329
+ className: "mb-1 font-semibold",
1330
+ children: "Junction Table"
1331
+ }), /* @__PURE__ */ jsx(Typography, {
1332
+ variant: "caption",
1333
+ className: "opacity-80",
1334
+ children: "This is an auto-generated junction table for a many-to-many relation. Its access is typically managed through the related collections' policies. You can still add RLS policies directly if needed."
1335
+ })] })]
1336
+ })
1337
+ }),
1338
+ activeTableData && activeTableCategory === "other" && /* @__PURE__ */ jsx(Alert, {
1158
1339
  color: "warning",
1159
- children: [/* @__PURE__ */ jsx(Typography, {
1160
- variant: "body2",
1161
- className: "mb-1",
1162
- children: "Table not managed by Rebase"
1163
- }), /* @__PURE__ */ jsx(Typography, {
1164
- variant: "caption",
1165
- className: "opacity-80",
1166
- children: "This table is not mapped to a Rebase Schema via code. To edit security policies visually, you must first import this table into a Schema configuration file."
1167
- })]
1340
+ children: /* @__PURE__ */ jsxs("div", {
1341
+ className: "flex items-start gap-2",
1342
+ children: [/* @__PURE__ */ jsx(AlertTriangleIcon, {
1343
+ size: 16,
1344
+ className: "shrink-0 mt-0.5"
1345
+ }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Typography, {
1346
+ variant: "body2",
1347
+ className: "mb-1 font-semibold",
1348
+ children: "Unmapped Table"
1349
+ }), /* @__PURE__ */ jsx(Typography, {
1350
+ variant: "caption",
1351
+ className: "opacity-80",
1352
+ children: "This table exists in the database but isn't mapped to a collection definition. Import it into a Schema configuration file to manage security policies visually."
1353
+ })] })]
1354
+ })
1168
1355
  }),
1169
1356
  activeTableData && !activeTableData.rlsEnabled && /* @__PURE__ */ jsxs("div", {
1170
1357
  className: cls("p-4 sm:p-5 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-900/50 rounded-lg flex flex-col sm:flex-row gap-4 items-start sm:items-center justify-between", defaultBorderMixin),
@@ -1278,7 +1465,6 @@ var RLSEditor = ({ apiUrl = "" }) => {
1278
1465
  variant: "text",
1279
1466
  color: "primary",
1280
1467
  onClick: () => setEditingPolicy(policy),
1281
- disabled: !activeCollection,
1282
1468
  children: t("studio_rls_edit")
1283
1469
  }),
1284
1470
  policy.status !== "code_only" && /* @__PURE__ */ jsx(Tooltip, {
@@ -1288,9 +1474,10 @@ var RLSEditor = ({ apiUrl = "" }) => {
1288
1474
  size: "small",
1289
1475
  onClick: async () => {
1290
1476
  const table = activeTableData.tableName;
1477
+ const qualifiedTable = `${sanitizeSqlIdentifier(activeTableData.schemaName)}.${sanitizeSqlIdentifier(table)}`;
1291
1478
  if (!confirm(`Drop policy "${policy.policyname}" from table "${table}"?`)) return;
1292
1479
  try {
1293
- await databaseAdmin.executeSql(`DROP POLICY ${sanitizeSqlIdentifier(policy.policyname)} ON ${sanitizeSqlIdentifier(table)}`);
1480
+ await databaseAdmin.executeSql(`DROP POLICY ${sanitizeSqlIdentifier(policy.policyname)} ON ${qualifiedTable}`);
1294
1481
  snackbarController.open({
1295
1482
  type: "success",
1296
1483
  message: `Policy "${policy.policyname}" dropped`
@@ -1327,7 +1514,7 @@ var RLSEditor = ({ apiUrl = "" }) => {
1327
1514
  className: "text-text-disabled dark:text-text-disabled-dark max-w-sm mb-4",
1328
1515
  children: "RLS is enabled on this table but no policies exist. All access is denied by default (Postgres deny-all). Create a policy to allow specific access."
1329
1516
  }),
1330
- activeCollection && /* @__PURE__ */ jsx(Button, {
1517
+ /* @__PURE__ */ jsx(Button, {
1331
1518
  size: "small",
1332
1519
  variant: "filled",
1333
1520
  color: "primary",
@@ -1366,4 +1553,4 @@ var RLSEditor = ({ apiUrl = "" }) => {
1366
1553
  //#endregion
1367
1554
  export { RLSEditor };
1368
1555
 
1369
- //# sourceMappingURL=RLSEditor-DpF1u9EC.js.map
1556
+ //# sourceMappingURL=RLSEditor-BM64laoW.js.map