@rebasepro/studio 0.22.0 → 0.23.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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CronJobsView-7hjgSYId.js","names":[],"sources":["../src/components/CronJobs/CronJobsView.tsx"],"sourcesContent":["\nimport React, { useState, useEffect, useRef } from \"react\";\nimport {\n AlertCircleIcon,\n Button,\n CalendarIcon,\n Card,\n CheckCircleIcon,\n Chip,\n CircularProgress,\n cls,\n defaultBorderMixin,\n HistoryIcon,\n IconButton,\n iconSize,\n Paper,\n PauseIcon,\n PlayIcon,\n RefreshCwIcon,\n Typography\n} from \"@rebasepro/ui\";\nimport { useRebaseClient, useSnackbarController, useTranslation } from \"@rebasepro/app\";\nimport type { CronJobStatus, CronJobLogEntry } from \"@rebasepro/types\";\nimport type { RebaseClient } from \"@rebasepro/types\";\n\nimport { classifyLoadFailure, type LoadFailure } from \"../load-failure\";\nimport { LoadFailureView } from \"../load-failure-view\";\n\nfunction formatDuration(ms: number): string {\n if (ms < 1000) return `${ms}ms`;\n if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;\n return `${(ms / 60000).toFixed(1)}m`;\n}\n\nfunction formatRelative(iso: string | undefined): string {\n if (!iso) return \"—\";\n const d = new Date(iso);\n const now = Date.now();\n const diff = d.getTime() - now;\n const abs = Math.abs(diff);\n if (abs < 60000) return diff > 0 ? \"in <1m\" : \"<1m ago\";\n if (abs < 3600000) { const m = Math.round(abs / 60000); return diff > 0 ? `in ${m}m` : `${m}m ago`; }\n if (abs < 86400000) { const h = Math.round(abs / 3600000); return diff > 0 ? `in ${h}h` : `${h}h ago`; }\n return d.toLocaleString();\n}\n\nconst stateColors: Record<string, string> = {\n idle: \"bg-emerald-500\",\nrunning: \"bg-blue-500\",\nsuccess: \"bg-emerald-500\",\n error: \"bg-red-500\",\ndisabled: \"bg-surface-400\"\n};\n\nexport function CronJobsView() {\n const client = useRebaseClient<RebaseClient>();\n const snackbar = useSnackbarController();\n const { t } = useTranslation();\n const [jobs, setJobs] = useState<CronJobStatus[]>([]);\n const [loading, setLoading] = useState(true);\n const [selectedId, setSelectedId] = useState<string | null>(null);\n const [logs, setLogs] = useState<CronJobLogEntry[]>([]);\n const [logsLoading, setLogsLoading] = useState(false);\n /**\n * Why the selected job's executions could not be read. Shown where the\n * list would be: an empty list reads as \"this job has never run\", and the\n * snackbar that used to carry the reason was gone in four seconds.\n */\n const [logsFailure, setLogsFailure] = useState<LoadFailure | null>(null);\n const [triggering, setTriggering] = useState<string | null>(null);\n /** Why the job listing failed, classified — see `load-failure.ts`. */\n const [failure, setFailure] = useState<LoadFailure | null>(null);\n\n // Refs so effects never re-fire due to identity changes\n const clientRef = useRef(client);\n clientRef.current = client;\n const snackbarRef = useRef(snackbar);\n snackbarRef.current = snackbar;\n\n // ── Fetch jobs on mount + poll every 15s ──\n useEffect(() => {\n let cancelled = false;\n\n async function load() {\n const c = clientRef.current;\n if (!c?.cron) {\n setLoading(false);\n return;\n }\n try {\n const res = await c.cron.listJobs();\n if (!cancelled) {\n setJobs(res.jobs);\n setFailure(null);\n }\n } catch (e: unknown) {\n // A snackbar over an empty list said \"No Cron Jobs Registered\"\n // about a project whose crons the caller may not read. This\n // view polls every 15s, so the toast is also gone long before\n // anyone looks.\n if (!cancelled) setFailure(classifyLoadFailure(e));\n } finally {\n if (!cancelled) setLoading(false);\n }\n }\n\n load();\n\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n const scheduleNext = () => {\n if (cancelled) return;\n timeoutId = setTimeout(async () => {\n if (document.visibilityState === \"visible\") {\n await load();\n }\n scheduleNext();\n }, 15_000);\n };\n\n scheduleNext();\n\n const handleVisibility = () => {\n if (document.visibilityState === \"visible\") {\n load();\n }\n };\n document.addEventListener(\"visibilitychange\", handleVisibility);\n\n return () => {\n cancelled = true;\n if (timeoutId) clearTimeout(timeoutId);\n document.removeEventListener(\"visibilitychange\", handleVisibility);\n };\n }, []); // runs once\n\n // ── Fetch logs when selection changes ──\n useEffect(() => {\n if (!selectedId) {\n setLogs([]);\n setLogsFailure(null);\n return;\n }\n let cancelled = false;\n const c = clientRef.current;\n if (!c?.cron) return;\n\n setLogsLoading(true);\n setLogsFailure(null);\n c.cron.getJobLogs(selectedId, { limit: 25 })\n .then(res => { if (!cancelled) setLogs(res.logs); })\n .catch((e: unknown) => {\n if (cancelled) return;\n // Same reasoning as `refreshLogs`: an empty log list is a claim.\n setLogs([]);\n setLogsFailure(classifyLoadFailure(e));\n })\n .finally(() => { if (!cancelled) setLogsLoading(false); });\n\n return () => { cancelled = true; };\n }, [selectedId]);\n\n // ── Imperative helpers (not in any dep array) ──\n async function refreshJobs() {\n const c = clientRef.current;\n if (!c?.cron) return;\n try {\n const res = await c.cron.listJobs();\n setJobs(res.jobs);\n setFailure(null);\n } catch (e: unknown) {\n // Swallowed before, which left the list showing whatever it last\n // held — or nothing — after a failed refresh. \"No cron jobs\" and\n // \"could not read the cron jobs\" are not the same statement, so the\n // reason stays on screen rather than passing through a snackbar.\n setFailure(classifyLoadFailure(e));\n }\n }\n\n async function refreshLogs(id: string) {\n const c = clientRef.current;\n if (!c?.cron) return;\n setLogsLoading(true);\n setLogsFailure(null);\n try {\n const res = await c.cron.getJobLogs(id, { limit: 25 });\n setLogs(res.logs);\n } catch (e: unknown) {\n // Clearing the list silently reads as \"this job has never run\".\n setLogs([]);\n setLogsFailure(classifyLoadFailure(e));\n }\n finally { setLogsLoading(false); }\n }\n\n const handleTrigger = async (id: string) => {\n const c = clientRef.current;\n if (!c?.cron) return;\n setTriggering(id);\n try {\n await c.cron.triggerJob(id);\n snackbarRef.current.open({ type: \"success\",\nmessage: \"Job triggered\" });\n await refreshJobs();\n if (selectedId === id) refreshLogs(id);\n } catch (e: unknown) {\n snackbarRef.current.open({ type: \"error\",\nmessage: e instanceof Error ? e.message : String(e) });\n } finally { setTriggering(null); }\n };\n\n const handleToggle = async (id: string, enabled: boolean) => {\n const c = clientRef.current;\n if (!c?.cron) return;\n try {\n await c.cron.toggleJob(id, enabled);\n snackbarRef.current.open({ type: \"success\",\nmessage: enabled ? \"Job enabled\" : \"Job paused\" });\n await refreshJobs();\n } catch (e: unknown) {\n snackbarRef.current.open({ type: \"error\",\nmessage: e instanceof Error ? e.message : String(e) });\n }\n };\n\n const selectedJob = jobs.find(j => j.id === selectedId);\n\n if (loading) return <div className=\"flex items-center justify-center h-full\"><CircularProgress/></div>;\n\n if (failure) return (\n <LoadFailureView\n failure={failure}\n title={t(\"studio_cron_read_failed\")}\n deniedTitle={t(\"studio_cron_denied_title\")}\n deniedHint={t(\"studio_cron_denied_hint\")}\n onRetry={refreshJobs}\n />\n );\n\n if (jobs.length === 0) return (\n <div className=\"flex flex-col items-center justify-center h-full gap-4 text-center p-8\">\n <CalendarIcon size={iconSize.medium} className=\"text-surface-300 dark:text-surface-600\"/>\n <Typography variant=\"h6\" color=\"secondary\">{t(\"studio_cron_empty_title\")}</Typography>\n <Typography variant=\"body2\" color=\"disabled\" className=\"max-w-md\">\n {t(\"studio_cron_empty_body\")}\n </Typography>\n <a\n href=\"https://rebase.pro/docs/backend/cron-jobs\"\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"text-primary text-sm underline\"\n >\n {t(\"studio_read_the_docs\")}\n </a>\n </div>\n );\n\n return (\n <div className=\"flex h-full w-full overflow-hidden bg-surface-card\">\n {/* ── Job List ── */}\n <div className={cls(\"flex flex-col w-[340px] min-w-[280px] border-r h-full\", defaultBorderMixin)}>\n <div className={cls(\"flex items-center justify-between px-4 py-2.5 border-b bg-surface-sheet min-h-[48px]\", defaultBorderMixin)}>\n <div className=\"flex items-center gap-2\">\n <CalendarIcon size={iconSize.smallest} className=\"text-primary\"/>\n <Typography variant=\"subtitle2\" className=\"font-semibold\">{t(\"studio_tool_cron\")}</Typography>\n <Chip size=\"smallest\" className=\"bg-surface-raised text-surface-600 dark:text-surface-300\">{jobs.length}</Chip>\n </div>\n <IconButton size=\"small\" onClick={refreshJobs} title=\"Refresh\"><RefreshCwIcon size={iconSize.smallest}/></IconButton>\n </div>\n <div className=\"flex-1 overflow-y-auto p-2 space-y-1\">\n {jobs.map(job => (\n <div\n key={job.id}\n onClick={() => setSelectedId(job.id)}\n className={cls(\n \"flex items-center gap-3 px-3 py-2.5 rounded-lg cursor-pointer transition-all\",\n selectedId === job.id\n ? \"bg-primary/10 dark:bg-primary/15 ring-1 ring-primary/30\"\n : \"hover:bg-surface-hover\"\n )}\n >\n <div className={cls(\"w-2 h-2 rounded-full shrink-0\", stateColors[job.state] || \"bg-surface-400\")}/>\n <div className=\"flex-1 min-w-0\">\n <Typography variant=\"body2\" className=\"truncate font-medium text-[13px]\">{job.name}</Typography>\n <Typography variant=\"caption\" color=\"secondary\" className=\"truncate text-[11px] font-mono\">{job.schedule}</Typography>\n </div>\n {job.state === \"running\" && <CircularProgress size=\"smallest\"/>}\n </div>\n ))}\n </div>\n </div>\n\n {/* ── Detail Panel ── */}\n <div className=\"flex-1 flex flex-col min-w-0 h-full overflow-hidden\">\n {!selectedJob ? (\n <div className=\"flex items-center justify-center h-full\">\n <Typography variant=\"body2\" color=\"disabled\">Select a cron job to view details</Typography>\n </div>\n ) : (\n <>\n {/* Header */}\n <div className={cls(\"flex items-center justify-between px-5 py-3 border-b bg-surface-card min-h-[56px]\", defaultBorderMixin)}>\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className={cls(\"w-2.5 h-2.5 rounded-full\", stateColors[selectedJob.state])}/>\n <div className=\"min-w-0\">\n <Typography variant=\"subtitle1\" className=\"font-semibold truncate\">{selectedJob.name}</Typography>\n {selectedJob.description && <Typography variant=\"caption\" color=\"secondary\" className=\"truncate\">{selectedJob.description}</Typography>}\n </div>\n </div>\n <div className=\"flex items-center gap-2 shrink-0\">\n <IconButton title={selectedJob.enabled ? \"Pause job\" : \"Enable job\"} size=\"small\" onClick={() => handleToggle(selectedJob.id, !selectedJob.enabled)}>\n {selectedJob.enabled ? <PauseIcon size={iconSize.small}/> : <PlayIcon size={iconSize.smallest}/>}\n </IconButton>\n <Button\n size=\"small\"\n color=\"primary\"\n onClick={() => handleTrigger(selectedJob.id)}\n disabled={triggering === selectedJob.id}\n startIcon={triggering === selectedJob.id ? <CircularProgress size=\"smallest\"/> : <PlayIcon size={iconSize.smallest}/>}\n >\n Run Now\n </Button>\n </div>\n </div>\n\n {/* Stats Cards */}\n <div className=\"px-5 py-4 bg-surface-sheet\">\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-3\">\n <StatCard label=\"Schedule\" value={selectedJob.schedule} mono/>\n <StatCard label=\"Last Run\" value={formatRelative(selectedJob.lastRunAt)}/>\n <StatCard label=\"Next Run\" value={selectedJob.enabled ? formatRelative(selectedJob.nextRunAt) : \"Paused\"}/>\n <StatCard label=\"Duration\" value={selectedJob.lastDurationMs !== undefined ? formatDuration(selectedJob.lastDurationMs) : \"—\"}/>\n </div>\n <div className=\"grid grid-cols-3 gap-3 mt-3\">\n <StatCard label=\"Status\" value={selectedJob.state.toUpperCase()} chipColor={selectedJob.state === \"error\" ? \"red\" : selectedJob.state === \"disabled\" ? \"gray\" : \"green\"}/>\n <StatCard label=\"Total Runs\" value={String(selectedJob.totalRuns)}/>\n <StatCard label=\"Failures\" value={String(selectedJob.totalFailures)} highlight={selectedJob.totalFailures > 0}/>\n </div>\n {selectedJob.lastError && (\n <div className=\"mt-3 p-3 rounded-lg bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800/50\">\n <div className=\"flex items-center gap-2 mb-1\">\n <AlertCircleIcon size={iconSize.smallest} className=\"text-red-500\"/>\n <Typography variant=\"caption\" className=\"font-semibold text-red-700 dark:text-red-400\">Last Error</Typography>\n </div>\n <Typography variant=\"caption\" className=\"font-mono text-red-600 dark:text-red-300 text-[11px] break-all\">{selectedJob.lastError}</Typography>\n </div>\n )}\n </div>\n\n {/* Logs Section */}\n <div className={cls(\"flex items-center justify-between px-5 py-2 border-y bg-surface-card\", defaultBorderMixin)}>\n <div className=\"flex items-center gap-2\">\n <HistoryIcon size={iconSize.smallest} className=\"text-surface-400\"/>\n <Typography variant=\"subtitle2\" className=\"font-semibold text-[13px]\">Execution History</Typography>\n </div>\n <IconButton size=\"small\" onClick={() => refreshLogs(selectedJob.id)} title=\"Refresh logs\"><RefreshCwIcon size={iconSize.smallest}/></IconButton>\n </div>\n <div className=\"flex-1 overflow-y-auto\">\n {logsLoading ? (\n <div className=\"flex justify-center p-8\"><CircularProgress size=\"small\"/></div>\n ) : logsFailure ? (\n <LoadFailureView\n failure={logsFailure}\n title={t(\"studio_cron_logs_read_failed\")}\n deniedTitle={t(\"studio_cron_logs_denied_title\")}\n deniedHint={t(\"studio_cron_denied_hint\")}\n onRetry={() => refreshLogs(selectedJob.id)}\n />\n ) : logs.length === 0 ? (\n <div className=\"flex items-center justify-center h-32\">\n <Typography variant=\"body2\" color=\"disabled\">No executions yet</Typography>\n </div>\n ) : (\n <div className=\"divide-y divide-surface-100 dark:divide-surface-950\">\n {logs.map((log, idx) => (\n <LogRow key={idx} log={log}/>\n ))}\n </div>\n )}\n </div>\n </>\n )}\n </div>\n </div>\n );\n}\n\nfunction StatCard({ label, value, mono, chipColor, highlight }: {\n label: string; value: string; mono?: boolean; chipColor?: string; highlight?: boolean;\n}) {\n return (\n <div className={cls(\"px-3 py-2 rounded-lg border bg-surface-card\", defaultBorderMixin)}>\n <Typography variant=\"caption\" color=\"secondary\" className=\"text-[10px] uppercase tracking-wider font-medium\">{label}</Typography>\n <Typography variant=\"body2\" className={cls(\n \"mt-0.5 font-semibold text-[13px]\",\n mono && \"font-mono\",\n highlight && \"text-red-500 dark:text-red-400\",\n chipColor === \"red\" && \"text-red-500\",\n chipColor === \"green\" && \"text-emerald-500\",\n chipColor === \"gray\" && \"text-surface-400\"\n )}>{value}</Typography>\n </div>\n );\n}\n\nfunction LogRow({ log }: { log: CronJobLogEntry }) {\n const [expanded, setExpanded] = useState(false);\n return (\n <div className=\"px-5 py-2.5 hover:bg-surface-hover transition-colors\">\n <div className=\"flex items-center gap-3 cursor-pointer\" onClick={() => setExpanded(!expanded)}>\n {log.success\n ? <CheckCircleIcon size={iconSize.smallest} className=\"text-emerald-500 shrink-0\"/>\n : <AlertCircleIcon size={iconSize.smallest} className=\"text-red-500 shrink-0\"/>}\n <div className=\"flex-1 min-w-0\">\n <Typography variant=\"caption\" className=\"font-mono text-[11px] text-surface-500\">{new Date(log.startedAt).toLocaleString()}</Typography>\n </div>\n <div className=\"flex items-center gap-2 shrink-0\">\n {log.manual && <Chip size=\"smallest\" className=\"bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300\">manual</Chip>}\n <Typography variant=\"caption\" className=\"font-mono text-[11px]\">{formatDuration(log.durationMs)}</Typography>\n <svg className={cls(\"w-3 h-3 transition-transform text-surface-400\", expanded && \"rotate-180\")} fill=\"currentColor\" viewBox=\"0 0 20 20\"><path d=\"M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z\"/></svg>\n </div>\n </div>\n {expanded && (\n <div className=\"mt-2 ml-6 space-y-2\">\n {log.error && (\n <div className=\"p-2 rounded bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800/50\">\n <Typography variant=\"caption\" className=\"font-mono text-[11px] text-red-600 dark:text-red-300 break-all\">{log.error}</Typography>\n </div>\n )}\n {log.logs.length > 0 && (\n <div className=\"p-2 rounded bg-surface-sheet border border-hairline-strong max-h-40 overflow-auto\">\n {log.logs.map((line, i) => (\n <div key={i} className=\"font-mono text-[11px] text-surface-600 dark:text-surface-400 leading-relaxed\">{line}</div>\n ))}\n </div>\n )}\n {log.result !== undefined && (\n <div className=\"p-2 rounded bg-surface-sheet border border-hairline-strong\">\n <Typography variant=\"caption\" className=\"text-[10px] uppercase tracking-wider text-surface-400 mb-1 block\">Result</Typography>\n <pre className=\"font-mono text-[11px] text-surface-600 dark:text-surface-400 whitespace-pre-wrap break-all\">{JSON.stringify(log.result, null, 2)}</pre>\n </div>\n )}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1,10 +1,16 @@
1
1
  import { useModeController } from "@rebasepro/app";
2
- import { useRef } from "react";
2
+ import { useImperativeHandle, useRef } from "react";
3
3
  import { cls } from "@rebasepro/ui";
4
4
  import { jsx } from "react/jsx-runtime";
5
5
  import Editor from "@monaco-editor/react";
6
6
  //#region src/components/SQLEditor/MonacoEditor.tsx
7
- var MonacoEditor = ({ value, onChange, onRun, className, readOnly = false, autoFocus = true, schemas }) => {
7
+ /** The editor's selection, trimmed — `undefined` when there is none. */
8
+ function selectedTextOf(instance) {
9
+ const selection = instance.getSelection();
10
+ if (!selection || selection.isEmpty()) return void 0;
11
+ return instance.getModel()?.getValueInRange(selection)?.trim() || void 0;
12
+ }
13
+ var MonacoEditor = ({ ref, value, onChange, onRun, className, readOnly = false, autoFocus = true, schemas }) => {
8
14
  const { mode } = useModeController();
9
15
  const editorRef = useRef(null);
10
16
  const monacoRef = useRef(null);
@@ -12,6 +18,7 @@ var MonacoEditor = ({ value, onChange, onRun, className, readOnly = false, autoF
12
18
  onRunRef.current = onRun;
13
19
  const schemasRef = useRef(schemas);
14
20
  schemasRef.current = schemas;
21
+ useImperativeHandle(ref, () => ({ getSelectedText: () => editorRef.current ? selectedTextOf(editorRef.current) : void 0 }), []);
15
22
  const handleEditorOnMount = (editor, monaco) => {
16
23
  editorRef.current = editor;
17
24
  monacoRef.current = monaco;
@@ -22,12 +29,7 @@ var MonacoEditor = ({ value, onChange, onRun, className, readOnly = false, autoF
22
29
  contextMenuGroupId: "operation",
23
30
  contextMenuOrder: 0,
24
31
  run: () => {
25
- if (onRunRef.current) {
26
- const selection = editor.getSelection();
27
- let selectedText = void 0;
28
- if (selection && !selection.isEmpty()) selectedText = editor.getModel()?.getValueInRange(selection)?.trim();
29
- onRunRef.current(selectedText || void 0);
30
- }
32
+ onRunRef.current?.(selectedTextOf(editor));
31
33
  }
32
34
  });
33
35
  const sqlKeywords = [
@@ -243,4 +245,4 @@ var MonacoEditor = ({ value, onChange, onRun, className, readOnly = false, autoF
243
245
  //#endregion
244
246
  export { MonacoEditor as t };
245
247
 
246
- //# sourceMappingURL=MonacoEditor-D0_QljcX.js.map
248
+ //# sourceMappingURL=MonacoEditor-CUxy5_k5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MonacoEditor-CUxy5_k5.js","names":[],"sources":["../src/components/SQLEditor/MonacoEditor.tsx"],"sourcesContent":["import React, { useImperativeHandle, useRef } from \"react\";\nimport Editor, { Monaco, OnMount } from \"@monaco-editor/react\";\nimport type { editor, Position, IRange } from \"monaco-editor\";\nimport { cls, defaultBorderMixin } from \"@rebasepro/ui\";\nimport { useModeController } from \"@rebasepro/app\";\n\n/**\n * What a host can ask of the editor outside its own key bindings.\n *\n * The toolbar's buttons need the selection that Cmd+Enter already reads: a\n * button that runs the whole buffer while the shortcut beside it runs only the\n * highlighted statement is two answers to one question.\n */\nexport interface MonacoEditorHandle {\n /** The selected text, trimmed — `undefined` when nothing is selected. */\n getSelectedText: () => string | undefined;\n}\n\n/** The editor's selection, trimmed — `undefined` when there is none. */\nfunction selectedTextOf(instance: editor.IStandaloneCodeEditor): string | undefined {\n const selection = instance.getSelection();\n if (!selection || selection.isEmpty()) return undefined;\n return instance.getModel()?.getValueInRange(selection)?.trim() || undefined;\n}\n\nexport type MonacoEditorProps = {\n ref?: React.Ref<MonacoEditorHandle>;\n value: string;\n onChange: (value: string | undefined) => void;\n onRun?: (selectedText?: string) => void;\n className?: string;\n readOnly?: boolean;\n autoFocus?: boolean;\n schemas?: Record<string, { schemaName: string, tableName: string, columns: { name: string, dataType: string }[] }[]>;\n};\n\nexport const MonacoEditor = ({\n ref,\n value,\n onChange,\n onRun,\n className,\n readOnly = false,\n autoFocus = true,\n schemas\n}: MonacoEditorProps) => {\n const { mode } = useModeController();\n const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);\n const monacoRef = useRef<Monaco | null>(null);\n const onRunRef = useRef(onRun);\n onRunRef.current = onRun;\n\n const schemasRef = useRef(schemas);\n schemasRef.current = schemas;\n\n useImperativeHandle(ref, () => ({\n getSelectedText: () => (editorRef.current ? selectedTextOf(editorRef.current) : undefined)\n }), []);\n\n const handleEditorOnMount: OnMount = (editor, monaco) => {\n editorRef.current = editor;\n monacoRef.current = monaco;\n\n editor.addAction({\n id: \"run-query\",\n label: \"Run Query\",\n keybindings: [monaco.KeyMod.CtrlCmd + monaco.KeyCode.Enter],\n contextMenuGroupId: \"operation\",\n contextMenuOrder: 0,\n run: () => {\n onRunRef.current?.(selectedTextOf(editor));\n }\n });\n\n // Register custom autocomplete for tables, columns, and SQL keywords\n const sqlKeywords = [\n \"SELECT\", \"FROM\", \"WHERE\", \"INSERT INTO\", \"VALUES\", \"UPDATE\", \"SET\",\n \"DELETE FROM\", \"JOIN\", \"INNER JOIN\", \"LEFT JOIN\", \"RIGHT JOIN\", \"FULL JOIN\",\n \"ON\", \"GROUP BY\", \"HAVING\", \"ORDER BY\", \"ASC\", \"DESC\", \"LIMIT\", \"OFFSET\",\n \"AS\", \"AND\", \"OR\", \"NOT\", \"IN\", \"BETWEEN\", \"LIKE\", \"IS NULL\", \"IS NOT NULL\",\n \"CREATE TABLE\", \"ALTER TABLE\", \"DROP TABLE\", \"PRIMARY KEY\", \"FOREIGN KEY\",\n \"COUNT\", \"SUM\", \"AVG\", \"MIN\", \"MAX\", \"DISTINCT\", \"UNION\", \"ALL\", \"EXISTS\",\n \"CASE\", \"WHEN\", \"THEN\", \"ELSE\", \"END\", \"CAST\", \"COALESCE\"\n ];\n\n monaco.languages.registerCompletionItemProvider(\"pgsql\", {\n triggerCharacters: [\".\", \" \", \"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"G\", \"H\", \"I\", \"J\", \"K\", \"L\", \"M\", \"N\", \"O\", \"P\", \"Q\", \"R\", \"S\", \"T\", \"U\", \"V\", \"W\", \"X\", \"Y\", \"Z\", \"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\", \"h\", \"i\", \"j\", \"k\", \"l\", \"m\", \"n\", \"o\", \"p\", \"q\", \"r\", \"s\", \"t\", \"u\", \"v\", \"w\", \"x\", \"y\", \"z\"],\n provideCompletionItems: (model: editor.ITextModel, position: Position) => {\n const word = model.getWordUntilPosition(position);\n const range = {\n startLineNumber: position.lineNumber,\n endLineNumber: position.lineNumber,\n startColumn: word.startColumn,\n endColumn: word.endColumn\n };\n\n const textUntilPosition = model.getValueInRange({\n startLineNumber: position.lineNumber,\n startColumn: 1,\n endLineNumber: position.lineNumber,\n endColumn: position.column\n });\n\n // Check if user is typing `tableName.`\n const match = textUntilPosition.match(/([a-zA-Z0-9_]+)\\.$/);\n const tableNameMatch = match ? match[1] : null;\n\n const suggestions: { label: string; kind: number; insertText: string; range: IRange; detail: string; sortText: string }[] = [];\n const allSchemas = schemasRef.current || {};\n\n if (tableNameMatch) {\n // Provide columns for the matched table\n for (const [, tables] of Object.entries(allSchemas)) {\n const table = tables.find(t => t.tableName === tableNameMatch);\n if (table) {\n table.columns.forEach(col => {\n suggestions.push({\n label: col.name,\n kind: monaco.languages.CompletionItemKind.Field,\n insertText: col.name,\n range,\n detail: \"Column\",\n sortText: \"1\"\n });\n });\n }\n }\n } else {\n // Provide table names\n for (const [sName, tables] of Object.entries(allSchemas)) {\n tables.forEach(table => {\n suggestions.push({\n label: table.tableName,\n kind: monaco.languages.CompletionItemKind.Class,\n insertText: table.tableName,\n range,\n detail: `Table (${sName})`,\n sortText: \"2\"\n });\n });\n }\n\n // Provide SQL keywords\n sqlKeywords.forEach(keyword => {\n suggestions.push({\n label: keyword,\n kind: monaco.languages.CompletionItemKind.Keyword,\n insertText: keyword + \" \",\n range,\n detail: \"SQL Keyword\",\n sortText: \"3\"\n });\n });\n\n // Heuristic: Suggest columns for tables that are mentioned in the current editor text\n const fullText = model.getValue();\n const activeTables = new Set<string>();\n for (const [, tables] of Object.entries(allSchemas)) {\n tables.forEach(table => {\n const regex = new RegExp(`\\\\b${table.tableName}\\\\b`, \"i\");\n if (regex.test(fullText)) {\n activeTables.add(table.tableName);\n }\n });\n }\n\n const addedColumns = new Set<string>();\n for (const [, tables] of Object.entries(allSchemas)) {\n tables.forEach(table => {\n if (activeTables.has(table.tableName)) {\n table.columns.forEach(col => {\n if (!addedColumns.has(col.name)) {\n addedColumns.add(col.name);\n suggestions.push({\n label: col.name,\n kind: monaco.languages.CompletionItemKind.Field,\n insertText: col.name,\n range,\n detail: `Column (${table.tableName})`,\n sortText: \"1\" // Prioritize columns from active tables\n });\n }\n });\n }\n });\n }\n }\n\n return { suggestions };\n\n }\n });\n\n if (autoFocus) {\n editor.focus();\n }\n };\n\n return (\n <div className={cls(\"relative w-full h-full overflow-hidden\", className)}>\n <Editor\n height=\"100%\"\n defaultLanguage=\"pgsql\"\n value={value}\n onChange={onChange}\n onMount={handleEditorOnMount}\n theme={mode === \"dark\" ? \"vs-dark\" : \"vs\"}\n options={{\n minimap: { enabled: false },\n fontSize: 14,\n lineNumbers: \"on\",\n scrollBeyondLastLine: false,\n automaticLayout: true,\n readOnly,\n tabSize: 2,\n wordWrap: \"on\"\n }}\n />\n </div>\n );\n};\n"],"mappings":";;;;;;;AAmBA,SAAS,eAAe,UAA4D;CAChF,MAAM,YAAY,SAAS,aAAa;CACxC,IAAI,CAAC,aAAa,UAAU,QAAQ,GAAG,OAAO,KAAA;CAC9C,OAAO,SAAS,SAAS,CAAC,EAAE,gBAAgB,SAAS,CAAC,EAAE,KAAK,KAAK,KAAA;AACtE;AAaA,IAAa,gBAAgB,EACzB,KACA,OACA,UACA,OACA,WACA,WAAW,OACX,YAAY,MACZ,cACqB;CACrB,MAAM,EAAE,SAAS,kBAAkB;CACnC,MAAM,YAAY,OAA4C,IAAI;CAClE,MAAM,YAAY,OAAsB,IAAI;CAC5C,MAAM,WAAW,OAAO,KAAK;CAC7B,SAAS,UAAU;CAEnB,MAAM,aAAa,OAAO,OAAO;CACjC,WAAW,UAAU;CAErB,oBAAoB,YAAY,EAC5B,uBAAwB,UAAU,UAAU,eAAe,UAAU,OAAO,IAAI,KAAA,EACpF,IAAI,CAAC,CAAC;CAEN,MAAM,uBAAgC,QAAQ,WAAW;EACrD,UAAU,UAAU;EACpB,UAAU,UAAU;EAEpB,OAAO,UAAU;GACb,IAAI;GACJ,OAAO;GACP,aAAa,CAAC,OAAO,OAAO,UAAU,OAAO,QAAQ,KAAK;GAC1D,oBAAoB;GACpB,kBAAkB;GAClB,WAAW;IACP,SAAS,UAAU,eAAe,MAAM,CAAC;GAC7C;EACJ,CAAC;EAGD,MAAM,cAAc;GAChB;GAAU;GAAQ;GAAS;GAAe;GAAU;GAAU;GAC9D;GAAe;GAAQ;GAAc;GAAa;GAAc;GAChE;GAAM;GAAY;GAAU;GAAY;GAAO;GAAQ;GAAS;GAChE;GAAM;GAAO;GAAM;GAAO;GAAM;GAAW;GAAQ;GAAW;GAC9D;GAAgB;GAAe;GAAc;GAAe;GAC5D;GAAS;GAAO;GAAO;GAAO;GAAO;GAAY;GAAS;GAAO;GACjE;GAAQ;GAAQ;GAAQ;GAAQ;GAAO;GAAQ;EACnD;EAEA,OAAO,UAAU,+BAA+B,SAAS;GACrD,mBAAmB;IAAC;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;GAAG;GAChS,yBAAyB,OAA0B,aAAuB;IACtE,MAAM,OAAO,MAAM,qBAAqB,QAAQ;IAChD,MAAM,QAAQ;KACV,iBAAiB,SAAS;KAC1B,eAAe,SAAS;KACxB,aAAa,KAAK;KAClB,WAAW,KAAK;IACpB;IAUA,MAAM,QARoB,MAAM,gBAAgB;KAC5C,iBAAiB,SAAS;KAC1B,aAAa;KACb,eAAe,SAAS;KACxB,WAAW,SAAS;IACxB,CAGc,CAAA,CAAkB,MAAM,oBAAoB;IAC1D,MAAM,iBAAiB,QAAQ,MAAM,KAAK;IAE1C,MAAM,cAAsH,CAAC;IAC7H,MAAM,aAAa,WAAW,WAAW,CAAC;IAE1C,IAAI,gBAEA,KAAK,MAAM,GAAG,WAAW,OAAO,QAAQ,UAAU,GAAG;KACjD,MAAM,QAAQ,OAAO,MAAK,MAAK,EAAE,cAAc,cAAc;KAC7D,IAAI,OACA,MAAM,QAAQ,SAAQ,QAAO;MACzB,YAAY,KAAK;OACb,OAAO,IAAI;OACX,MAAM,OAAO,UAAU,mBAAmB;OAC1C,YAAY,IAAI;OAChB;OACA,QAAQ;OACR,UAAU;MACd,CAAC;KACL,CAAC;IAET;SACG;KAEH,KAAK,MAAM,CAAC,OAAO,WAAW,OAAO,QAAQ,UAAU,GACnD,OAAO,SAAQ,UAAS;MACpB,YAAY,KAAK;OACb,OAAO,MAAM;OACb,MAAM,OAAO,UAAU,mBAAmB;OAC1C,YAAY,MAAM;OAClB;OACA,QAAQ,UAAU,MAAM;OACxB,UAAU;MACd,CAAC;KACL,CAAC;KAIL,YAAY,SAAQ,YAAW;MAC3B,YAAY,KAAK;OACb,OAAO;OACP,MAAM,OAAO,UAAU,mBAAmB;OAC1C,YAAY,UAAU;OACtB;OACA,QAAQ;OACR,UAAU;MACd,CAAC;KACL,CAAC;KAGD,MAAM,WAAW,MAAM,SAAS;KAChC,MAAM,+BAAe,IAAI,IAAY;KACrC,KAAK,MAAM,GAAG,WAAW,OAAO,QAAQ,UAAU,GAC9C,OAAO,SAAQ,UAAS;MAEpB,IAAI,IADc,OAAO,MAAM,MAAM,UAAU,MAAM,GACjD,CAAA,CAAM,KAAK,QAAQ,GACnB,aAAa,IAAI,MAAM,SAAS;KAExC,CAAC;KAGL,MAAM,+BAAe,IAAI,IAAY;KACrC,KAAK,MAAM,GAAG,WAAW,OAAO,QAAQ,UAAU,GAC9C,OAAO,SAAQ,UAAS;MACpB,IAAI,aAAa,IAAI,MAAM,SAAS,GAChC,MAAM,QAAQ,SAAQ,QAAO;OACzB,IAAI,CAAC,aAAa,IAAI,IAAI,IAAI,GAAG;QAC7B,aAAa,IAAI,IAAI,IAAI;QACzB,YAAY,KAAK;SACb,OAAO,IAAI;SACX,MAAM,OAAO,UAAU,mBAAmB;SAC1C,YAAY,IAAI;SAChB;SACA,QAAQ,WAAW,MAAM,UAAU;SACnC,UAAU;QACd,CAAC;OACL;MACJ,CAAC;KAET,CAAC;IAET;IAEA,OAAO,EAAE,YAAY;GAEzB;EACJ,CAAC;EAED,IAAI,WACA,OAAO,MAAM;CAErB;CAEA,OACI,oBAAC,OAAD;EAAK,WAAW,IAAI,0CAA0C,SAAS;YACnE,oBAAC,QAAD;GACI,QAAO;GACP,iBAAgB;GACT;GACG;GACV,SAAS;GACT,OAAO,SAAS,SAAS,YAAY;GACrC,SAAS;IACL,SAAS,EAAE,SAAS,MAAM;IAC1B,UAAU;IACV,aAAa;IACb,sBAAsB;IACtB,iBAAiB;IACjB;IACA,SAAS;IACT,UAAU;GACd;EACH,CAAA;CACA,CAAA;AAEb"}
@@ -1,11 +1,11 @@
1
- import { t as MonacoEditor } from "./MonacoEditor-D0_QljcX.js";
1
+ import { t as MonacoEditor } from "./MonacoEditor-CUxy5_k5.js";
2
2
  import { ConfirmationDialog, ErrorView, useApiBase, useApiConfig, useRebaseContext, useSnackbarController, useStudioCapabilities, useStudioCollectionRegistry, useStudioSchemaEditing, useTranslation } from "@rebasepro/app";
3
3
  import { useCallback, useEffect, useMemo, useState } from "react";
4
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 { RLS_JWT_SQL, RLS_ROLES_SQL, RLS_UID_SQL, isPostgresCollectionConfig, policy } from "@rebasepro/types";
7
7
  import { getPolicyNamesForRule, getPolicyNamesForRules, getPolicyOperations } from "@rebasepro/utils";
8
- import { JUNCTION_TABLES_SQL, REBASE_INTERNAL_PREFIXES, REBASE_INTERNAL_SCHEMAS, REBASE_USER_ROLE, getEffectiveSecurityRules, getJunctionSecurityRules, policyToPostgres, resolveJunctionSpecs } from "@rebasepro/common";
8
+ import { JUNCTION_TABLES_SQL, REBASE_INTERNAL_PREFIXES, REBASE_INTERNAL_SCHEMAS, REBASE_USER_ROLE, getEffectiveSecurityRules, getJunctionSecurityRules, getTableName, policyToPostgres, policyToSecurityRule, resolveJunctionSpecs } from "@rebasepro/common";
9
9
  //#region src/components/RLSEditor/policy-presets.ts
10
10
  /**
11
11
  * What the RLS editor offers before the user types anything: the roles a `TO`
@@ -760,7 +760,104 @@ function isCancellation(err) {
760
760
  return err instanceof Error && err.name === "SchemaChangeCancelled";
761
761
  }
762
762
  //#endregion
763
+ //#region src/components/RLSEditor/policyRules.ts
764
+ /**
765
+ * Write an edit of one policy back to the declared rule it compiles from.
766
+ *
767
+ * The rule is found by the policy names it compiles to, not by `rule.name`:
768
+ * an unnamed rule's policy is `<table>_<op>_<hash>`, and a rule over several
769
+ * operations names each `<name>_<op>`. Matching on `name` missed both, saved
770
+ * the rules unchanged and reported success.
771
+ *
772
+ * A rule over several operations is refused rather than split: taking one
773
+ * operation out renames every policy the rule compiles to.
774
+ *
775
+ * @param declared The collection's `securityRules`, as written.
776
+ * @param effective Every rule the generator compiles for the table — the
777
+ * declared ones plus what Rebase adds — to tell a generated
778
+ * policy from one that exists only in the database.
779
+ */
780
+ function applyPolicyEdit(declared, effective, tableName, original, edited) {
781
+ const compilesTo = (rule) => getPolicyNamesForRule(rule, tableName).includes(original.policyname);
782
+ const index = declared.findIndex(compilesTo);
783
+ if (index === -1) return {
784
+ ok: false,
785
+ reason: effective.some(compilesTo) ? "generated" : "not_declared"
786
+ };
787
+ const rule = declared[index];
788
+ if (getPolicyOperations(rule).length > 1) return {
789
+ ok: false,
790
+ reason: "several_operations"
791
+ };
792
+ return {
793
+ ok: true,
794
+ rules: declared.map((r, i) => i === index ? editedRule(r, original, edited) : r)
795
+ };
796
+ }
797
+ /**
798
+ * The rule with what the editor changed, and nothing else.
799
+ *
800
+ * The editor shows a rule as a policy — name, command, mode, `TO` list and raw
801
+ * USING / WITH CHECK — and an `ownerField` or `access` rule has no raw clause
802
+ * to show. So a field is only written when it differs from what the editor was
803
+ * opened with: renaming an `access: "public"` rule keeps it public, and an
804
+ * unnamed rule stays unnamed unless the name was changed. A condition typed in
805
+ * replaces the rule's condition whole — the forms are exclusive.
806
+ */
807
+ function editedRule(rule, original, edited) {
808
+ const next = policyToSecurityRule(edited);
809
+ const result = { ...rule };
810
+ if ((edited.policyname ?? "") !== original.policyname) if (next.name) result.name = next.name;
811
+ else delete result.name;
812
+ if (edited.cmd !== original.cmd) {
813
+ delete result.operations;
814
+ result.operation = next.operation;
815
+ }
816
+ if (edited.permissive !== original.permissive) result.mode = next.mode;
817
+ if (!sameRoles(edited.roles, original.roles)) if (next.pgRoles) result.pgRoles = next.pgRoles;
818
+ else delete result.pgRoles;
819
+ if ((edited.qual || null) !== (original.qual || null) || (edited.with_check || null) !== (original.with_check || null)) {
820
+ delete result.ownerField;
821
+ delete result.access;
822
+ delete result.condition;
823
+ delete result.check;
824
+ delete result.using;
825
+ delete result.withCheck;
826
+ if (next.using) result.using = next.using;
827
+ if (next.withCheck) result.withCheck = next.withCheck;
828
+ }
829
+ return result;
830
+ }
831
+ function sameRoles(a, b) {
832
+ const left = [...a ?? []].sort();
833
+ const right = [...b ?? []].sort();
834
+ return left.length === right.length && left.every((role, i) => role === right[i]);
835
+ }
836
+ //#endregion
763
837
  //#region src/components/RLSEditor/RLSEditor.tsx
838
+ /**
839
+ * Whether `collection` is the one `db push` writes `table` for: the table
840
+ * `getTableName` derives, in the schema the collection declares or `public` —
841
+ * the planner's own resolution.
842
+ *
843
+ * This matched a table to any collection whose `id`, `path`, `table`, `slug`
844
+ * or `collectionId` equalled its name, in any schema. So an unmapped table
845
+ * named like another collection's slug, or a same-named table in another
846
+ * schema, was taken for that collection: it listed the collection's policies
847
+ * as missing from the table, and offered to import the table's own policies
848
+ * into the collection. And a collection that leaves `table` to a slug that is
849
+ * not already snake case mapped nothing.
850
+ */
851
+ function mapsTable(collection, table) {
852
+ if (!isPostgresCollectionConfig(collection)) return false;
853
+ return getTableName(collection) === table.tableName && (collection.schema || "public") === table.schemaName;
854
+ }
855
+ /** What the editor says when an edit has no declared rule to go back to. */
856
+ var POLICY_EDIT_REFUSALS = {
857
+ generated: "studio_rls_edit_generated",
858
+ not_declared: "studio_rls_edit_not_declared",
859
+ several_operations: "studio_rls_edit_several_operations"
860
+ };
764
861
  function classifyTableClient(tableName, schemaName, junctionTableNames, isMappedToCollection) {
765
862
  if (isMappedToCollection) return "collection";
766
863
  if (REBASE_INTERNAL_SCHEMAS.includes(schemaName) || REBASE_INTERNAL_PREFIXES.some((prefix) => tableName.startsWith(prefix))) return "internal";
@@ -1092,7 +1189,7 @@ var RLSEditor = ({ apiUrl = "" }) => {
1092
1189
  other: []
1093
1190
  };
1094
1191
  tables.forEach((table) => {
1095
- 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);
1192
+ const isMapped = !!collectionRegistry.collections?.some((c) => mapsTable(c, table));
1096
1193
  const cat = classifyTableClient(table.tableName, table.schemaName, junctionTableNames, isMapped);
1097
1194
  groups[cat].push(table);
1098
1195
  });
@@ -1104,7 +1201,7 @@ var RLSEditor = ({ apiUrl = "" }) => {
1104
1201
  ]);
1105
1202
  const activeCollection = useMemo(() => {
1106
1203
  if (!activeTableData) return null;
1107
- 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;
1204
+ return collectionRegistry.collections?.find((c) => mapsTable(c, activeTableData)) || null;
1108
1205
  }, [activeTableData, collectionRegistry.collections]);
1109
1206
  /** The category of the currently selected table. */
1110
1207
  const activeTableCategory = useMemo(() => {
@@ -1509,20 +1606,22 @@ var RLSEditor = ({ apiUrl = "" }) => {
1509
1606
  roleOptions: pgRoleOptions,
1510
1607
  onSave: async (newPolicy) => {
1511
1608
  if (activeCollection && hasCodebase) {
1512
- const rule = {
1513
- name: newPolicy.policyname,
1514
- operation: newPolicy.cmd?.toLowerCase(),
1515
- mode: newPolicy.permissive?.toLowerCase(),
1516
- using: newPolicy.qual || void 0,
1517
- withCheck: newPolicy.with_check || void 0,
1518
- ...newPolicy.roles && !(newPolicy.roles.length === 1 && newPolicy.roles[0] === "public") ? { pgRoles: newPolicy.roles } : {}
1519
- };
1520
1609
  const existingRules = (isPostgresCollectionConfig(activeCollection) ? activeCollection.securityRules : void 0) || [];
1521
1610
  let newRules;
1522
- if (editingPolicy === "new") newRules = [...existingRules, rule];
1523
- else newRules = existingRules.map((r) => r.name === editingPolicy.policyname ? rule : r);
1611
+ if (editingPolicy === "new") newRules = [...existingRules, policyToSecurityRule(newPolicy)];
1612
+ else {
1613
+ const edit = applyPolicyEdit(existingRules, getEffectiveSecurityRules(activeCollection), activeTableData.tableName, editingPolicy, newPolicy);
1614
+ if (!edit.ok) {
1615
+ snackbarController.open({
1616
+ type: "error",
1617
+ message: t(POLICY_EDIT_REFUSALS[edit.reason], { policy: editingPolicy.policyname })
1618
+ });
1619
+ return;
1620
+ }
1621
+ newRules = edit.rules;
1622
+ }
1524
1623
  try {
1525
- await saveSecurityRules(activeCollection.id || activeCollection.path || activeCollection.alias || activeTableData.tableName, newRules);
1624
+ await saveSecurityRules(activeCollection.slug, newRules);
1526
1625
  snackbarController.open({
1527
1626
  type: "success",
1528
1627
  message: "Policy saved successfully"
@@ -1538,13 +1637,13 @@ var RLSEditor = ({ apiUrl = "" }) => {
1538
1637
  const cmd = newPolicy.cmd || "ALL";
1539
1638
  const permissive = (newPolicy.permissive || "PERMISSIVE") === "PERMISSIVE" ? "PERMISSIVE" : "RESTRICTIVE";
1540
1639
  const roles = newPolicy.roles && newPolicy.roles.length > 0 ? newPolicy.roles.map((r) => sanitizeSqlIdentifier(r)).join(", ") : "public";
1541
- if (editingPolicy !== "new") await databaseAdmin.executeSql(`DROP POLICY IF EXISTS ${policyName} ON ${qualifiedTable}`);
1542
1640
  let sql = `CREATE POLICY ${policyName} ON ${qualifiedTable}`;
1543
1641
  sql += ` AS ${permissive}`;
1544
1642
  sql += ` FOR ${cmd}`;
1545
1643
  sql += ` TO ${roles}`;
1546
1644
  if (newPolicy.qual) sql += ` USING (${newPolicy.qual})`;
1547
1645
  if (newPolicy.with_check) sql += ` WITH CHECK (${newPolicy.with_check})`;
1646
+ if (editingPolicy !== "new") sql = `DROP POLICY IF EXISTS ${sanitizeSqlIdentifier(editingPolicy.policyname)} ON ${qualifiedTable}; ${sql}`;
1548
1647
  await databaseAdmin.executeSql(sql);
1549
1648
  snackbarController.open({
1550
1649
  type: "success",
@@ -1696,17 +1795,10 @@ var RLSEditor = ({ apiUrl = "" }) => {
1696
1795
  variant: "outlined",
1697
1796
  color: "primary",
1698
1797
  onClick: async () => {
1699
- const rule = {
1700
- name: policy.policyname,
1701
- operation: policy.cmd?.toLowerCase(),
1702
- mode: policy.permissive?.toLowerCase(),
1703
- using: policy.qual || void 0,
1704
- withCheck: policy.with_check || void 0,
1705
- roles: policy.roles
1706
- };
1798
+ const rule = policyToSecurityRule(policy);
1707
1799
  const newRules = [...(isPostgresCollectionConfig(activeCollection) ? activeCollection.securityRules : void 0) || [], rule];
1708
1800
  try {
1709
- await saveSecurityRules(activeCollection.id || activeCollection.path || activeCollection.alias || activeTableData.tableName, newRules);
1801
+ await saveSecurityRules(activeCollection.slug, newRules);
1710
1802
  snackbarController.open({
1711
1803
  type: "success",
1712
1804
  message: "Policy imported successfully"
@@ -1818,4 +1910,4 @@ var RLSEditor = ({ apiUrl = "" }) => {
1818
1910
  //#endregion
1819
1911
  export { RLSEditor };
1820
1912
 
1821
- //# sourceMappingURL=RLSEditor-B-qrgdHy.js.map
1913
+ //# sourceMappingURL=RLSEditor-Djh43lBE.js.map