@rebasepro/studio 0.1.2 → 0.2.3

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 (75) hide show
  1. package/LICENSE +17 -109
  2. package/dist/{ApiExplorer-DHVmWYfK.js → ApiExplorer-BmcdhAX0.js} +3 -4
  3. package/dist/ApiExplorer-BmcdhAX0.js.map +1 -0
  4. package/dist/{AuthSimulationSelector-CM488Eei.js → AuthSimulationSelector-DGoXkWSg.js} +2 -3
  5. package/dist/AuthSimulationSelector-DGoXkWSg.js.map +1 -0
  6. package/dist/{BranchesView-DcHZtvXo.js → BranchesView-BiTEwIhd.js} +2 -3
  7. package/dist/BranchesView-BiTEwIhd.js.map +1 -0
  8. package/dist/{CronJobsView-CijCToeK.js → CronJobsView-CNfz0etw.js} +2 -3
  9. package/dist/CronJobsView-CNfz0etw.js.map +1 -0
  10. package/dist/{JSEditor-CSHA0t_O.js → JSEditor-Ch8z8lJ4.js} +4 -5
  11. package/dist/JSEditor-Ch8z8lJ4.js.map +1 -0
  12. package/dist/{RLSEditor-BzDjqo6w.js → RLSEditor-CHEExeSB.js} +2 -3
  13. package/dist/RLSEditor-CHEExeSB.js.map +1 -0
  14. package/dist/{SQLEditor-Cr9Kg_Qg.js → SQLEditor-BELYJQRP.js} +75 -58
  15. package/dist/SQLEditor-BELYJQRP.js.map +1 -0
  16. package/dist/{StorageView-BYoslzBR.js → StorageView-B7AsN2qX.js} +2 -3
  17. package/dist/StorageView-B7AsN2qX.js.map +1 -0
  18. package/dist/common/src/data/query_builder.d.ts +51 -0
  19. package/dist/common/src/index.d.ts +1 -0
  20. package/dist/common/src/util/entities.d.ts +2 -2
  21. package/dist/common/src/util/relations.d.ts +1 -1
  22. package/dist/core/src/components/LoginView/LoginView.d.ts +1 -6
  23. package/dist/core/src/contexts/SnackbarProvider.d.ts +1 -1
  24. package/dist/core/src/hooks/data/save.d.ts +2 -2
  25. package/dist/core/src/hooks/data/useEntityFetch.d.ts +5 -0
  26. package/dist/core/src/hooks/useResolvedComponent.d.ts +1 -1
  27. package/dist/index.es.js +8 -9
  28. package/dist/index.es.js.map +1 -1
  29. package/dist/index.umd.js +168 -150
  30. package/dist/index.umd.js.map +1 -1
  31. package/dist/types/src/controllers/auth.d.ts +9 -8
  32. package/dist/types/src/controllers/client.d.ts +3 -0
  33. package/dist/types/src/controllers/data.d.ts +21 -0
  34. package/dist/types/src/types/auth_adapter.d.ts +356 -0
  35. package/dist/types/src/types/collections.d.ts +67 -2
  36. package/dist/types/src/types/database_adapter.d.ts +94 -0
  37. package/dist/types/src/types/entity_actions.d.ts +7 -1
  38. package/dist/types/src/types/entity_callbacks.d.ts +1 -1
  39. package/dist/types/src/types/entity_views.d.ts +36 -1
  40. package/dist/types/src/types/index.d.ts +2 -0
  41. package/dist/types/src/types/plugins.d.ts +1 -1
  42. package/dist/types/src/types/properties.d.ts +24 -5
  43. package/dist/types/src/types/property_config.d.ts +6 -2
  44. package/dist/types/src/types/relations.d.ts +1 -1
  45. package/dist/types/src/types/translations.d.ts +8 -0
  46. package/dist/types/src/users/user.d.ts +5 -0
  47. package/dist/ui/src/components/FilterChip.d.ts +42 -0
  48. package/dist/ui/src/components/index.d.ts +5 -0
  49. package/dist/ui/src/icons/index.d.ts +2 -0
  50. package/package.json +26 -18
  51. package/src/components/ApiExplorer/ApiExplorer.tsx +1 -1
  52. package/src/components/ApiExplorer/EndpointDetail.tsx +10 -2
  53. package/src/components/ApiExplorer/TryItPanel.tsx +17 -6
  54. package/src/components/AuthSimulationSelector.tsx +1 -2
  55. package/src/components/Branches/BranchesView.tsx +24 -2
  56. package/src/components/CronJobs/CronJobsView.tsx +19 -2
  57. package/src/components/JSEditor/JSEditor.tsx +37 -6
  58. package/src/components/JSEditor/JSEditorSidebar.tsx +12 -2
  59. package/src/components/JSEditor/JSMonacoEditor.tsx +16 -3
  60. package/src/components/RLSEditor/PolicyEditor.tsx +19 -2
  61. package/src/components/RLSEditor/RLSEditor.tsx +22 -2
  62. package/src/components/SQLEditor/SQLEditor.tsx +124 -68
  63. package/src/components/SQLEditor/SQLEditorSidebar.tsx +1 -2
  64. package/src/components/SQLEditor/SchemaBrowser.tsx +14 -2
  65. package/src/components/StorageView/StorageView.tsx +39 -2
  66. package/src/components/StudioHomePage.tsx +1 -2
  67. package/src/utils/sql_utils.ts +1 -1
  68. package/dist/ApiExplorer-DHVmWYfK.js.map +0 -1
  69. package/dist/AuthSimulationSelector-CM488Eei.js.map +0 -1
  70. package/dist/BranchesView-DcHZtvXo.js.map +0 -1
  71. package/dist/CronJobsView-CijCToeK.js.map +0 -1
  72. package/dist/JSEditor-CSHA0t_O.js.map +0 -1
  73. package/dist/RLSEditor-BzDjqo6w.js.map +0 -1
  74. package/dist/SQLEditor-Cr9Kg_Qg.js.map +0 -1
  75. package/dist/StorageView-BYoslzBR.js.map +0 -1
@@ -1,7 +1,24 @@
1
1
 
2
2
  import React, { useState, useEffect } from "react";
3
- import { Button, Paper, Typography, TextField, Select, SelectItem, MultiSelect, MultiSelectItem, cls, defaultBorderMixin, IconButton, Dialog, DialogTitle, DialogContent, DialogActions , iconSize } from "@rebasepro/ui";
4
- import { HelpCircleIcon } from "lucide-react";
3
+ import {
4
+ Button,
5
+ cls,
6
+ defaultBorderMixin,
7
+ Dialog,
8
+ DialogActions,
9
+ DialogContent,
10
+ DialogTitle,
11
+ HelpCircleIcon,
12
+ IconButton,
13
+ iconSize,
14
+ MultiSelect,
15
+ MultiSelectItem,
16
+ Paper,
17
+ Select,
18
+ SelectItem,
19
+ TextField,
20
+ Typography
21
+ } from "@rebasepro/ui";
5
22
  import { useTranslation } from "@rebasepro/core";
6
23
  import { MonacoEditor } from "../SQLEditor/MonacoEditor";
7
24
  import { PostgresPolicy } from "./RLSEditor";
@@ -1,8 +1,28 @@
1
1
 
2
2
  import { useStudioCollectionRegistry } from "@rebasepro/core";
3
3
  import React, { useState, useEffect, useCallback, useMemo } from "react";
4
- import { Paper, Typography, CircularProgress, cls, Alert, defaultBorderMixin, Card, Chip, Button, Tooltip, ResizablePanels, IconButton, Tabs, Tab , iconSize } from "@rebasepro/ui";
5
- import { ShieldIcon, RefreshCwIcon, AlertTriangleIcon, KeyIcon, Trash2Icon } from "lucide-react";
4
+ import {
5
+ Alert,
6
+ AlertTriangleIcon,
7
+ Button,
8
+ Card,
9
+ Chip,
10
+ CircularProgress,
11
+ cls,
12
+ defaultBorderMixin,
13
+ IconButton,
14
+ iconSize,
15
+ KeyIcon,
16
+ Paper,
17
+ RefreshCwIcon,
18
+ ResizablePanels,
19
+ ShieldIcon,
20
+ Tab,
21
+ Tabs,
22
+ Tooltip,
23
+ Trash2Icon,
24
+ Typography
25
+ } from "@rebasepro/ui";
6
26
  import { useRebaseContext, useSnackbarController, ErrorView, useTranslation } from "@rebasepro/core";
7
27
  import { isPostgresCollection } from "@rebasepro/types";
8
28
  import { PolicyEditor } from "./PolicyEditor";
@@ -3,16 +3,45 @@ import { IconForView } from "@rebasepro/core";
3
3
  import { useStudioCollectionRegistry, useStudioSideEntityController } from "@rebasepro/core";
4
4
  import React, { useState, useEffect, useCallback, useRef, useMemo } from "react";
5
5
  import { createPortal } from "react-dom";
6
- import { Button, Paper, Typography, CircularProgress, cls, IconButton, InputLabel, Dialog, DialogContent, DialogActions, DialogTitle, TextField, Tooltip, Alert, Tabs, Tab, defaultBorderMixin, Select, SelectItem, Menu, MenuItem, ResizablePanels, Chip, VirtualTable, VirtualTableColumn , iconSize, Checkbox, TextareaAutosize } from "@rebasepro/ui";
7
- import { DatabaseIcon, TerminalIcon, XIcon, PlusIcon, PencilIcon, MoreVerticalIcon, MenuIcon, PlayIcon } from "lucide-react";
8
- // VirtualTableInput is conditionally loaded from CMS when available
9
- let VirtualTableInput: React.ComponentType<any> | null = null;
10
- try {
11
- // @ts-ignore — optional peer dependency
12
- // eslint-disable-next-line @typescript-eslint/no-require-imports
13
- const cms = require("@rebasepro/admin");
14
- VirtualTableInput = cms.VirtualTableInput;
15
- } catch { /* CMS not available */ }
6
+ import {
7
+ Alert,
8
+ Button,
9
+ Checkbox,
10
+ Chip,
11
+ CircularProgress,
12
+ cls,
13
+ DatabaseIcon,
14
+ defaultBorderMixin,
15
+ Dialog,
16
+ DialogActions,
17
+ DialogContent,
18
+ DialogTitle,
19
+ IconButton,
20
+ iconSize,
21
+ InputLabel,
22
+ Menu,
23
+ MenuIcon,
24
+ MenuItem,
25
+ MoreVerticalIcon,
26
+ Paper,
27
+ PencilIcon,
28
+ PlayIcon,
29
+ PlusIcon,
30
+ ResizablePanels,
31
+ Select,
32
+ SelectItem,
33
+ Tab,
34
+ Tabs,
35
+ TerminalIcon,
36
+ TextareaAutosize,
37
+ TextField,
38
+ Tooltip,
39
+ Typography,
40
+ VirtualTable,
41
+ VirtualTableColumn,
42
+ XIcon
43
+ } from "@rebasepro/ui";
44
+
16
45
  import { useRebaseContext, useSnackbarController, ConfirmationDialog, ErrorView, useTranslation } from "@rebasepro/core";
17
46
  import { MonacoEditor } from "./MonacoEditor";
18
47
  import { SQLEditorSidebar, Snippet } from "./SQLEditorSidebar";
@@ -152,14 +181,21 @@ const FixedEditorOverlay = ({
152
181
  );
153
182
  };
154
183
 
184
+ const getStoragePrefix = (baseUrl?: string) => {
185
+ if (!baseUrl) return "default";
186
+ return baseUrl.replace(/^https?:\/\//, "").replace(/[^a-zA-Z0-9]/g, "_");
187
+ };
188
+
155
189
  export const SQLEditor = () => {
156
- const { databaseAdmin } = useRebaseContext();
190
+ const { databaseAdmin, client } = useRebaseContext();
157
191
  const sideEntityController = useStudioSideEntityController();
158
192
  const snackbarController = useSnackbarController();
159
193
  const collectionRegistry = useStudioCollectionRegistry();
160
194
 
161
195
  const { t } = useTranslation();
162
196
 
197
+ const projectPrefix = useMemo(() => getStoragePrefix(client?.baseUrl), [client?.baseUrl]);
198
+
163
199
  // Schema state
164
200
  const [schemas, setSchemas] = useState<Record<string, TableInfo[]>>({});
165
201
  const [isSchemaLoading, setIsSchemaLoading] = useState(true);
@@ -168,10 +204,12 @@ export const SQLEditor = () => {
168
204
 
169
205
  // Connection state
170
206
  const [selectedDatabase, setSelectedDatabase] = useState<string | undefined>(() => {
171
- return localStorage.getItem("rebase_sql_selected_db") || undefined;
207
+ const projectPrefixSync = client?.baseUrl ? client.baseUrl.replace(/^https?:\/\//, "").replace(/[^a-zA-Z0-9]/g, "_") : "default";
208
+ return localStorage.getItem(`rebase_sql_selected_db_${projectPrefixSync}`) || undefined;
172
209
  });
173
210
  const [selectedRole, setSelectedRole] = useState<string | undefined>(() => {
174
- return localStorage.getItem("rebase_sql_selected_role") || undefined;
211
+ const projectPrefixSync = client?.baseUrl ? client.baseUrl.replace(/^https?:\/\//, "").replace(/[^a-zA-Z0-9]/g, "_") : "default";
212
+ return localStorage.getItem(`rebase_sql_selected_role_${projectPrefixSync}`) || undefined;
175
213
  });
176
214
 
177
215
  const [availableDatabases, setAvailableDatabases] = useState<string[]>([]);
@@ -192,7 +230,8 @@ export const SQLEditor = () => {
192
230
  execTime: number | null,
193
231
  lastExecutedSql: string | null
194
232
  }>>(() => {
195
- const saved = localStorage.getItem(STORAGE_KEY_TABS);
233
+ const projectPrefixSync = client?.baseUrl ? client.baseUrl.replace(/^https?:\/\//, "").replace(/[^a-zA-Z0-9]/g, "_") : "default";
234
+ const saved = localStorage.getItem(`rebase_sql_tabs_${projectPrefixSync}`);
196
235
  if (saved) {
197
236
  const parsed = JSON.parse(saved);
198
237
  return parsed.map((t: Record<string, unknown>) => ({
@@ -208,8 +247,8 @@ export const SQLEditor = () => {
208
247
  id: "1",
209
248
  name: "Query 1",
210
249
  sql: "SELECT * FROM ",
211
- database: localStorage.getItem("rebase_sql_selected_db") || undefined,
212
- role: localStorage.getItem("rebase_sql_selected_role") || undefined,
250
+ database: localStorage.getItem(`rebase_sql_selected_db_${projectPrefixSync}`) || undefined,
251
+ role: localStorage.getItem(`rebase_sql_selected_role_${projectPrefixSync}`) || undefined,
213
252
  results: null,
214
253
  loading: false,
215
254
  error: null,
@@ -218,7 +257,8 @@ export const SQLEditor = () => {
218
257
  }];
219
258
  });
220
259
  const [activeTabId, setActiveTabId] = useState<string>(() => {
221
- return localStorage.getItem(STORAGE_KEY_ACTIVE_TAB) || "1";
260
+ const projectPrefixSync = client?.baseUrl ? client.baseUrl.replace(/^https?:\/\//, "").replace(/[^a-zA-Z0-9]/g, "_") : "default";
261
+ return localStorage.getItem(`rebase_sql_active_tab_${projectPrefixSync}`) || "1";
222
262
  });
223
263
 
224
264
  const activeTab = tabs.find(t => t.id === activeTabId) || tabs[0];
@@ -243,30 +283,31 @@ export const SQLEditor = () => {
243
283
  useEffect(() => {
244
284
  let mounted = true;
245
285
  const fetchConnectionConfig = async () => {
246
- if (!databaseAdmin?.fetchAvailableDatabases || !databaseAdmin?.fetchAvailableRoles) {
286
+ if (!databaseAdmin?.fetchAvailableDatabases || !databaseAdmin?.fetchAvailableRoles || !databaseAdmin?.executeSql) {
247
287
  setConnectionConfigError(t("studio_sql_sql_not_supported"));
248
288
  setIsLoadingConfig(false);
249
289
  return;
250
290
  }
251
291
 
252
292
  try {
253
- const [dbs, roles, currentDbFromApi] = await Promise.all([
293
+ const [dbs, roles, currentDbFromApi, currentUserResult] = await Promise.all([
254
294
  databaseAdmin.fetchAvailableDatabases(),
255
295
  databaseAdmin.fetchAvailableRoles(),
256
- typeof databaseAdmin?.fetchCurrentDatabase === "function" ? databaseAdmin.fetchCurrentDatabase() : Promise.resolve(undefined)
296
+ typeof databaseAdmin?.fetchCurrentDatabase === "function" ? databaseAdmin.fetchCurrentDatabase() : Promise.resolve(undefined),
297
+ databaseAdmin.executeSql("SELECT current_user AS role").catch(() => [])
257
298
  ]);
258
299
 
259
300
  if (mounted) {
260
301
  setAvailableDatabases(dbs);
261
302
  setAvailableRoles(roles);
262
303
 
263
- const loadedDb = localStorage.getItem("rebase_sql_selected_db") || undefined;
264
- const loadedRole = localStorage.getItem("rebase_sql_selected_role") || undefined;
304
+ const loadedDb = localStorage.getItem(`rebase_sql_selected_db_${projectPrefix}`) || undefined;
305
+ const loadedRole = localStorage.getItem(`rebase_sql_selected_role_${projectPrefix}`) || undefined;
265
306
 
266
- const initialActiveTabId = localStorage.getItem(STORAGE_KEY_ACTIVE_TAB) || "1";
267
- let initialTabs: any[] = [];
307
+ const initialActiveTabId = localStorage.getItem(`rebase_sql_active_tab_${projectPrefix}`) || "1";
308
+ let initialTabs: Array<{ id?: string; database?: string; role?: string }> = [];
268
309
  try {
269
- const savedTabs = localStorage.getItem(STORAGE_KEY_TABS);
310
+ const savedTabs = localStorage.getItem(`rebase_sql_tabs_${projectPrefix}`);
270
311
  if (savedTabs) initialTabs = JSON.parse(savedTabs);
271
312
  } catch (e) { /* ignore */ }
272
313
  const currentActiveTab = initialTabs.find(t => t.id === initialActiveTabId);
@@ -279,22 +320,26 @@ export const SQLEditor = () => {
279
320
 
280
321
  if (actualDb) {
281
322
  setSelectedDatabase(actualDb);
282
- localStorage.setItem("rebase_sql_selected_db", actualDb);
283
- setTabs(prev => prev.map(t => t.id === initialActiveTabId && !t.database ? { ...t,
284
- database: actualDb } : t));
323
+ localStorage.setItem(`rebase_sql_selected_db_${projectPrefix}`, actualDb);
324
+ setTabs(prev => prev.map(t => t.id === initialActiveTabId && (!t.database || !dbs.includes(t.database)) ? { ...t, database: actualDb } : t));
285
325
  }
286
326
 
327
+ const currentUser = (currentUserResult?.[0] as Record<string, unknown> | undefined)?.role as string | undefined;
287
328
  let actualRole = currentActiveTab?.role || loadedRole;
329
+
288
330
  if (actualRole && !roles.includes(actualRole)) actualRole = undefined;
289
331
  if (!actualRole && roles.length > 0) {
290
- actualRole = roles.includes("postgres") ? "postgres" : roles[0];
332
+ if (currentUser && roles.includes(currentUser)) {
333
+ actualRole = currentUser;
334
+ } else {
335
+ actualRole = roles.includes("postgres") ? "postgres" : roles[0];
336
+ }
291
337
  }
292
338
 
293
339
  if (actualRole) {
294
340
  setSelectedRole(actualRole);
295
- localStorage.setItem("rebase_sql_selected_role", actualRole);
296
- setTabs(prev => prev.map(t => t.id === initialActiveTabId && !t.role ? { ...t,
297
- role: actualRole } : t));
341
+ localStorage.setItem(`rebase_sql_selected_role_${projectPrefix}`, actualRole);
342
+ setTabs(prev => prev.map(t => t.id === initialActiveTabId && (!t.role || !roles.includes(t.role)) ? { ...t, role: actualRole } : t));
298
343
  }
299
344
  }
300
345
  } catch (err: unknown) {
@@ -313,22 +358,20 @@ role: actualRole } : t));
313
358
  fetchConnectionConfig();
314
359
 
315
360
  return () => { mounted = false; };
316
- }, [databaseAdmin]);
361
+ }, [databaseAdmin, projectPrefix]);
317
362
 
318
363
  const handleDatabaseChange = (db: string, tabId?: string) => {
319
364
  setSelectedDatabase(db);
320
- localStorage.setItem("rebase_sql_selected_db", db);
321
- setTabs(prev => prev.map(t => t.id === (tabId || activeTabId) ? { ...t,
322
- database: db } : t));
365
+ localStorage.setItem(`rebase_sql_selected_db_${projectPrefix}`, db);
366
+ setTabs(prev => prev.map(t => t.id === (tabId || activeTabId) ? { ...t, database: db } : t));
323
367
  // Reset so the schema will be re-fetched for the new database
324
368
  schemaFetchedRef.current = false;
325
369
  };
326
370
 
327
371
  const handleRoleChange = (role: string, tabId?: string) => {
328
372
  setSelectedRole(role);
329
- localStorage.setItem("rebase_sql_selected_role", role);
330
- setTabs(prev => prev.map(t => t.id === (tabId || activeTabId) ? { ...t,
331
- role } : t));
373
+ localStorage.setItem(`rebase_sql_selected_role_${projectPrefix}`, role);
374
+ setTabs(prev => prev.map(t => t.id === (tabId || activeTabId) ? { ...t, role } : t));
332
375
  };
333
376
 
334
377
  const handleTabChange = useCallback((newTabId: string) => {
@@ -337,22 +380,20 @@ role } : t));
337
380
  if (newTab) {
338
381
  if (newTab.database && newTab.database !== selectedDatabase) {
339
382
  setSelectedDatabase(newTab.database);
340
- localStorage.setItem("rebase_sql_selected_db", newTab.database);
383
+ localStorage.setItem(`rebase_sql_selected_db_${projectPrefix}`, newTab.database);
341
384
  schemaFetchedRef.current = false;
342
385
  } else if (!newTab.database && selectedDatabase) {
343
- setTabs(prev => prev.map(t => t.id === newTabId ? { ...t,
344
- database: selectedDatabase } : t));
386
+ setTabs(prev => prev.map(t => t.id === newTabId ? { ...t, database: selectedDatabase } : t));
345
387
  }
346
388
 
347
389
  if (newTab.role && newTab.role !== selectedRole) {
348
390
  setSelectedRole(newTab.role);
349
- localStorage.setItem("rebase_sql_selected_role", newTab.role);
391
+ localStorage.setItem(`rebase_sql_selected_role_${projectPrefix}`, newTab.role);
350
392
  } else if (!newTab.role && selectedRole) {
351
- setTabs(prev => prev.map(t => t.id === newTabId ? { ...t,
352
- role: selectedRole } : t));
393
+ setTabs(prev => prev.map(t => t.id === newTabId ? { ...t, role: selectedRole } : t));
353
394
  }
354
395
  }
355
- }, [tabs, selectedDatabase, selectedRole]);
396
+ }, [tabs, selectedDatabase, selectedRole, projectPrefix]);
356
397
 
357
398
  const fetchSchema = useCallback(async () => {
358
399
  if (!databaseAdmin?.executeSql) {
@@ -566,7 +607,8 @@ role: selectedRole });
566
607
  }, [editingCell, schemas, activeTab.lastExecutedSql, activeTab.results, databaseAdmin, updateActiveTab, snackbarController, selectedDatabase, selectedRole]);
567
608
 
568
609
  const [columnWidths, setColumnWidths] = useState<Record<string, Record<string, number>>>(() => {
569
- const saved = localStorage.getItem("rebase_sql_column_widths");
610
+ const projectPrefixSync = client?.baseUrl ? client.baseUrl.replace(/^https?:\/\//, "").replace(/[^a-zA-Z0-9]/g, "_") : "default";
611
+ const saved = localStorage.getItem(`rebase_sql_column_widths_${projectPrefixSync}`);
570
612
  return saved ? JSON.parse(saved) : {};
571
613
  });
572
614
  const [snippets, setSnippets] = useState<Snippet[]>([]);
@@ -576,12 +618,20 @@ role: selectedRole });
576
618
 
577
619
  // Load from local storage
578
620
  useEffect(() => {
579
- const savedSnippets = localStorage.getItem("rebase_sql_snippets");
580
- if (savedSnippets) setSnippets(JSON.parse(savedSnippets));
621
+ const savedSnippets = localStorage.getItem(`rebase_sql_snippets_${projectPrefix}`);
622
+ if (savedSnippets) {
623
+ setSnippets(JSON.parse(savedSnippets));
624
+ } else {
625
+ setSnippets([]);
626
+ }
581
627
 
582
- const savedHistory = localStorage.getItem("rebase_sql_history");
583
- if (savedHistory) setHistory(JSON.parse(savedHistory));
584
- }, []);
628
+ const savedHistory = localStorage.getItem(`rebase_sql_history_${projectPrefix}`);
629
+ if (savedHistory) {
630
+ setHistory(JSON.parse(savedHistory));
631
+ } else {
632
+ setHistory([]);
633
+ }
634
+ }, [projectPrefix]);
585
635
 
586
636
  // Save tabs and active tab to local storage
587
637
  useEffect(() => {
@@ -592,21 +642,21 @@ role: selectedRole });
592
642
  database: t.database,
593
643
  role: t.role
594
644
  }));
595
- localStorage.setItem(STORAGE_KEY_TABS, JSON.stringify(sanitizedTabs));
596
- }, [tabs]);
645
+ localStorage.setItem(`rebase_sql_tabs_${projectPrefix}`, JSON.stringify(sanitizedTabs));
646
+ }, [tabs, projectPrefix]);
597
647
 
598
648
  useEffect(() => {
599
- localStorage.setItem(STORAGE_KEY_ACTIVE_TAB, activeTabId);
600
- }, [activeTabId]);
649
+ localStorage.setItem(`rebase_sql_active_tab_${projectPrefix}`, activeTabId);
650
+ }, [activeTabId, projectPrefix]);
601
651
 
602
652
  const saveSnippets = (newSnippets: Snippet[]) => {
603
653
  setSnippets(newSnippets);
604
- localStorage.setItem("rebase_sql_snippets", JSON.stringify(newSnippets));
654
+ localStorage.setItem(`rebase_sql_snippets_${projectPrefix}`, JSON.stringify(newSnippets));
605
655
  };
606
656
 
607
657
  const saveHistory = (newHistory: string[]) => {
608
658
  setHistory(newHistory);
609
- localStorage.setItem("rebase_sql_history", JSON.stringify(newHistory.slice(-50)));
659
+ localStorage.setItem(`rebase_sql_history_${projectPrefix}`, JSON.stringify(newHistory.slice(-50)));
610
660
  };
611
661
 
612
662
  const handleDeleteSnippet = (id: string) => {
@@ -667,10 +717,10 @@ role: selectedRole });
667
717
  [key]: width
668
718
  }
669
719
  };
670
- localStorage.setItem("rebase_sql_column_widths", JSON.stringify(newWidths));
720
+ localStorage.setItem(`rebase_sql_column_widths_${projectPrefix}`, JSON.stringify(newWidths));
671
721
  return newWidths;
672
722
  });
673
- }, [activeTab.sql]);
723
+ }, [activeTab.sql, projectPrefix]);
674
724
 
675
725
  const handlePrettify = () => {
676
726
  // Simple formatting for now
@@ -743,7 +793,7 @@ role: selectedRole });
743
793
  } finally {
744
794
  updateActiveTab({ loading: false });
745
795
  }
746
- }, [activeTab.sql, autoLimit, databaseAdmin, history, updateActiveTab]);
796
+ }, [activeTab.sql, autoLimit, databaseAdmin, history, updateActiveTab, selectedDatabase, selectedRole]);
747
797
 
748
798
  const handleRun = useCallback(async (selectedText?: string) => {
749
799
  const sqlTarget = selectedText || activeTab.sql;
@@ -1142,7 +1192,8 @@ id: String(ra.entityId) })}
1142
1192
 
1143
1193
  const [sidebarSize, setSidebarSize] = useState(() => {
1144
1194
  try {
1145
- const saved = localStorage.getItem("rebase_sql_editor_sidebar_size");
1195
+ const projectPrefixSync = client?.baseUrl ? client.baseUrl.replace(/^https?:\/\//, "").replace(/[^a-zA-Z0-9]/g, "_") : "default";
1196
+ const saved = localStorage.getItem(`rebase_sql_editor_sidebar_size_${projectPrefixSync}`);
1146
1197
  return saved !== null ? parseFloat(saved) : 20;
1147
1198
  } catch (e) {
1148
1199
  return 20;
@@ -1150,7 +1201,8 @@ id: String(ra.entityId) })}
1150
1201
  });
1151
1202
  const [editorHeight, setEditorHeight] = useState(() => {
1152
1203
  try {
1153
- const saved = localStorage.getItem("rebase_sql_editor_height");
1204
+ const projectPrefixSync = client?.baseUrl ? client.baseUrl.replace(/^https?:\/\//, "").replace(/[^a-zA-Z0-9]/g, "_") : "default";
1205
+ const saved = localStorage.getItem(`rebase_sql_editor_height_${projectPrefixSync}`);
1154
1206
  return saved !== null ? parseFloat(saved) : 50;
1155
1207
  } catch (e) {
1156
1208
  return 50;
@@ -1159,15 +1211,15 @@ id: String(ra.entityId) })}
1159
1211
 
1160
1212
  useEffect(() => {
1161
1213
  try {
1162
- localStorage.setItem("rebase_sql_editor_sidebar_size", sidebarSize.toString());
1214
+ localStorage.setItem(`rebase_sql_editor_sidebar_size_${projectPrefix}`, sidebarSize.toString());
1163
1215
  } catch (e) { /* ignore */ }
1164
- }, [sidebarSize]);
1216
+ }, [sidebarSize, projectPrefix]);
1165
1217
 
1166
1218
  useEffect(() => {
1167
1219
  try {
1168
- localStorage.setItem("rebase_sql_editor_height", editorHeight.toString());
1220
+ localStorage.setItem(`rebase_sql_editor_height_${projectPrefix}`, editorHeight.toString());
1169
1221
  } catch (e) { /* ignore */ }
1170
- }, [editorHeight]);
1222
+ }, [editorHeight, projectPrefix]);
1171
1223
 
1172
1224
  const activeSnippet = snippets.find(s => s.sql === activeTab.sql);
1173
1225
  const isFavorite = activeSnippet?.isFavorite || false;
@@ -1296,7 +1348,11 @@ isFavorite: !s.isFavorite } : s));
1296
1348
  className="text-text-secondary dark:text-text-secondary-dark font-medium mr-2"
1297
1349
  >
1298
1350
  <DatabaseIcon size={iconSize.small} className="mr-1.5 text-text-disabled dark:text-text-disabled-dark"/>
1299
- <span className="max-w-[80px] truncate">{isLoadingConfig ? "..." : (selectedDatabase || t("studio_sql_select_db"))}</span>
1351
+ <span className="max-w-[160px] truncate">
1352
+ {isLoadingConfig
1353
+ ? "..."
1354
+ : `${selectedDatabase || t("studio_sql_select_db")}${selectedRole ? ` (${selectedRole})` : ""}`}
1355
+ </span>
1300
1356
  </Button>
1301
1357
  }
1302
1358
  >
@@ -1,7 +1,6 @@
1
1
 
2
2
  import React, { useState } from "react";
3
- import { Typography, cls, defaultBorderMixin, Tabs, Tab, IconButton , iconSize } from "@rebasepro/ui";
4
- import { Trash2Icon } from "lucide-react";
3
+ import { cls, defaultBorderMixin, IconButton, iconSize, Tab, Tabs, Trash2Icon, Typography } from "@rebasepro/ui";
5
4
  import { useTranslation } from "@rebasepro/core";
6
5
  import { SchemaBrowser } from "./SchemaBrowser";
7
6
  import { TableInfo } from "./SQLEditor";
@@ -1,7 +1,19 @@
1
1
 
2
2
  import React, { useState } from "react";
3
- import { Typography, CircularProgress, cls, IconButton, defaultBorderMixin, Menu, MenuItem, Button , iconSize } from "@rebasepro/ui";
4
- import { CopyIcon, MoreVerticalIcon, RefreshCwIcon } from "lucide-react";
3
+ import {
4
+ Button,
5
+ CircularProgress,
6
+ cls,
7
+ CopyIcon,
8
+ defaultBorderMixin,
9
+ IconButton,
10
+ iconSize,
11
+ Menu,
12
+ MenuItem,
13
+ MoreVerticalIcon,
14
+ RefreshCwIcon,
15
+ Typography
16
+ } from "@rebasepro/ui";
5
17
  import { TableInfo } from "./SQLEditor";
6
18
  import { ErrorView, useTranslation } from "@rebasepro/core";
7
19
 
@@ -1,7 +1,44 @@
1
1
 
2
2
  import React, { useState, useEffect, useCallback, useMemo, useRef } from "react";
3
- import { Typography, cls, defaultBorderMixin, Button, IconButton, Tooltip, CircularProgress, ResizablePanels, Chip, Dialog, DialogTitle, DialogContent, DialogActions, FileUpload, iconSize, Checkbox, LoadingButton, TextField } from "@rebasepro/ui";
4
- import { VideoIcon, Music2Icon, RefreshCwIcon, Trash2Icon, XIcon, PlusIcon, DownloadIcon, UploadCloudIcon, FolderIcon, FolderPlusIcon, FileTextIcon, ImageIcon, ArrowLeftIcon, FileIcon, HomeIcon, LayoutGridIcon, ListIcon, CopyIcon, CheckIcon } from "lucide-react";
3
+ import {
4
+ ArrowLeftIcon,
5
+ Button,
6
+ Checkbox,
7
+ CheckIcon,
8
+ Chip,
9
+ CircularProgress,
10
+ cls,
11
+ CopyIcon,
12
+ defaultBorderMixin,
13
+ Dialog,
14
+ DialogActions,
15
+ DialogContent,
16
+ DialogTitle,
17
+ DownloadIcon,
18
+ FileIcon,
19
+ FileTextIcon,
20
+ FileUpload,
21
+ FolderIcon,
22
+ FolderPlusIcon,
23
+ HomeIcon,
24
+ IconButton,
25
+ iconSize,
26
+ ImageIcon,
27
+ LayoutGridIcon,
28
+ ListIcon,
29
+ LoadingButton,
30
+ Music2Icon,
31
+ PlusIcon,
32
+ RefreshCwIcon,
33
+ ResizablePanels,
34
+ TextField,
35
+ Tooltip,
36
+ Trash2Icon,
37
+ Typography,
38
+ UploadCloudIcon,
39
+ VideoIcon,
40
+ XIcon
41
+ } from "@rebasepro/ui";
5
42
  import { useStorageSource, useSnackbarController, ErrorView, useApiConfig } from "@rebasepro/core";
6
43
  import type { StorageListResult } from "@rebasepro/types";
7
44
  import { useSearchParams } from "react-router-dom";
@@ -1,8 +1,7 @@
1
1
  import type { HomePageSection, PluginGenericProps } from "@rebasepro/types";
2
2
  import React, { useEffect } from "react";
3
3
  import { Card, cls, Container, Typography } from "@rebasepro/ui";
4
- import { ArrowRightIcon } from "lucide-react";
5
- import { iconSize } from "@rebasepro/ui";
4
+ import { ArrowRightIcon, iconSize } from "@rebasepro/ui";
6
5
  import { IconForView, useRebaseContext, useRestoreScroll, useSlot } from "@rebasepro/core";
7
6
  import { useNavigate } from "react-router-dom";
8
7
  import { useStudioBreadcrumbs, BootstrapAdminBanner } from "@rebasepro/core";
@@ -105,7 +105,7 @@ export function resolveQueryCollections(
105
105
  for (const table of tables) {
106
106
  // Match table name against collection table or slug->snake_case
107
107
  const matched = collections.find(c => {
108
- const tableName = (c as any).table || toSnakeCase(c.slug);
108
+ const tableName = (c as EntityCollection & { table?: string }).table || toSnakeCase(c.slug);
109
109
  return tableName === table.name;
110
110
  });
111
111