@rebasepro/studio 0.17.3 → 0.18.1

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 (81) hide show
  1. package/LICENSE +0 -1
  2. package/README.md +12 -4
  3. package/dist/{ApiKeysView-DiCurTEU.js → ApiKeysView-EIvegrUk.js} +54 -39
  4. package/dist/ApiKeysView-EIvegrUk.js.map +1 -0
  5. package/dist/{BackupsView-DNS6LdVg.js → BackupsView-DVkKzaPR.js} +29 -31
  6. package/dist/BackupsView-DVkKzaPR.js.map +1 -0
  7. package/dist/{BranchesView-CM3B-ER3.js → BranchesView-Df8hQ8wE.js} +85 -24
  8. package/dist/BranchesView-Df8hQ8wE.js.map +1 -0
  9. package/dist/{CronJobsView-BHtJZeJZ.js → CronJobsView-CLOGjb-L.js} +32 -27
  10. package/dist/CronJobsView-CLOGjb-L.js.map +1 -0
  11. package/dist/{RLSEditor-CBX4uD66.js → RLSEditor-Sa1D34ny.js} +724 -615
  12. package/dist/RLSEditor-Sa1D34ny.js.map +1 -0
  13. package/dist/{SQLEditor-ByecwX8B.js → SQLEditor-BfNA8xM3.js} +9 -4
  14. package/dist/{SQLEditor-ByecwX8B.js.map → SQLEditor-BfNA8xM3.js.map} +1 -1
  15. package/dist/{SchemaVisualizer-D6tMZgeC.js → SchemaVisualizer-DTDMYL6o.js} +84 -110
  16. package/dist/SchemaVisualizer-DTDMYL6o.js.map +1 -0
  17. package/dist/{StorageView-BGDeC5GI.js → StorageView-Ed9Q_VlB.js} +5 -32
  18. package/dist/StorageView-Ed9Q_VlB.js.map +1 -0
  19. package/dist/components/RLSEditor/saveRules.d.ts +38 -0
  20. package/dist/components/RebaseStudio.d.ts +1 -1
  21. package/dist/components/SchemaVisualizer/schema-visualizer.utils.d.ts +5 -2
  22. package/dist/components/SchemaVisualizer/useSchemaGraph.d.ts +0 -4
  23. package/dist/components/load-failure-view.d.ts +17 -0
  24. package/dist/components/load-failure.d.ts +47 -0
  25. package/dist/index.es.js +56 -36
  26. package/dist/index.es.js.map +1 -1
  27. package/dist/load-failure-C6OMJSGC.js +31 -0
  28. package/dist/load-failure-C6OMJSGC.js.map +1 -0
  29. package/dist/load-failure-view-CBTB_h-h.js +48 -0
  30. package/dist/load-failure-view-CBTB_h-h.js.map +1 -0
  31. package/package.json +36 -21
  32. package/dist/ApiKeysView-DiCurTEU.js.map +0 -1
  33. package/dist/BackupsView-DNS6LdVg.js.map +0 -1
  34. package/dist/BranchesView-CM3B-ER3.js.map +0 -1
  35. package/dist/CronJobsView-BHtJZeJZ.js.map +0 -1
  36. package/dist/RLSEditor-CBX4uD66.js.map +0 -1
  37. package/dist/SchemaVisualizer-D6tMZgeC.js.map +0 -1
  38. package/dist/StorageView-BGDeC5GI.js.map +0 -1
  39. package/dist/components/StorageView/storage-failure.d.ts +0 -34
  40. package/src/components/ApiExplorer/ApiExplorer.tsx +0 -299
  41. package/src/components/ApiExplorer/EndpointDetail.tsx +0 -279
  42. package/src/components/ApiExplorer/TryItPanel.tsx +0 -499
  43. package/src/components/ApiExplorer/parseSpec.ts +0 -104
  44. package/src/components/ApiExplorer/types.ts +0 -84
  45. package/src/components/ApiKeys/ApiKeysView.tsx +0 -479
  46. package/src/components/ApiKeys/CreateApiKeyDialog.tsx +0 -711
  47. package/src/components/ApiKeys/permissions.ts +0 -145
  48. package/src/components/AuthSimulationSelector.tsx +0 -72
  49. package/src/components/Backups/BackupsView.tsx +0 -185
  50. package/src/components/Branches/BranchesView.tsx +0 -386
  51. package/src/components/CronJobs/CronJobsView.tsx +0 -413
  52. package/src/components/JSEditor/JSEditor.tsx +0 -1100
  53. package/src/components/JSEditor/JSEditorSidebar.tsx +0 -350
  54. package/src/components/JSEditor/JSMonacoEditor.tsx +0 -410
  55. package/src/components/LogsExplorer/LogsExplorer.tsx +0 -273
  56. package/src/components/LogsExplorer/useLogTail.ts +0 -356
  57. package/src/components/RLSEditor/PolicyEditor.tsx +0 -399
  58. package/src/components/RLSEditor/RLSEditor.tsx +0 -1128
  59. package/src/components/RLSEditor/index.ts +0 -1
  60. package/src/components/RLSEditor/policy-presets.ts +0 -176
  61. package/src/components/RLSEditor/saveSecurityRules.ts +0 -48
  62. package/src/components/RebaseStudio.tsx +0 -174
  63. package/src/components/SQLEditor/ExplainVisualizer.tsx +0 -128
  64. package/src/components/SQLEditor/MonacoEditor.tsx +0 -203
  65. package/src/components/SQLEditor/SQLEditor.tsx +0 -1518
  66. package/src/components/SQLEditor/SQLEditorSidebar.tsx +0 -173
  67. package/src/components/SQLEditor/SchemaBrowser.tsx +0 -168
  68. package/src/components/SQLEditor/sql_editor_types.ts +0 -18
  69. package/src/components/SchemaVisualizer/RelationEdge.tsx +0 -102
  70. package/src/components/SchemaVisualizer/SchemaVisualizer.tsx +0 -710
  71. package/src/components/SchemaVisualizer/TableNode.tsx +0 -257
  72. package/src/components/SchemaVisualizer/schema-visualizer.utils.ts +0 -140
  73. package/src/components/SchemaVisualizer/useLiveRls.ts +0 -86
  74. package/src/components/SchemaVisualizer/useSchemaGraph.ts +0 -426
  75. package/src/components/StorageView/StorageView.tsx +0 -1588
  76. package/src/components/StorageView/storage-failure.ts +0 -63
  77. package/src/components/StudioHomePage.tsx +0 -430
  78. package/src/index.ts +0 -31
  79. package/src/utils/parseSpec.test.ts +0 -309
  80. package/src/utils/sql_utils.test.ts +0 -265
  81. package/src/utils/sql_utils.ts +0 -291
@@ -1,386 +0,0 @@
1
-
2
- import React, { useState, useEffect, useRef, useCallback } from "react";
3
- import {
4
- Alert,
5
- Button,
6
- Chip,
7
- CircularProgress,
8
- cls,
9
- CopyIcon,
10
- defaultBorderMixin,
11
- Dialog,
12
- DialogActions,
13
- DialogContent,
14
- DialogTitle,
15
- GitBranchIcon,
16
- IconButton,
17
- iconSize,
18
- Paper,
19
- PlusIcon,
20
- RefreshCwIcon,
21
- Select,
22
- SelectItem,
23
- TextField,
24
- Trash2Icon,
25
- Typography
26
- } from "@rebasepro/ui";
27
- import { useRebaseContext, useSnackbarController, ConfirmationDialog } from "@rebasepro/app";
28
- import { isBranchAdmin } from "@rebasepro/types";
29
- import type { BranchInfo } from "@rebasepro/types";
30
- import { formatRelativeTime } from "@rebasepro/utils";
31
-
32
- function formatSize(bytes: number | undefined): string {
33
- if (bytes === undefined || bytes === null) return "—";
34
- if (bytes < 1024) return `${bytes} B`;
35
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
36
- if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
37
- return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
38
- }
39
-
40
- function formatRelative(date: Date | string | undefined): string {
41
- if (!date) return "—";
42
- const d = date instanceof Date ? date : new Date(date);
43
- if (isNaN(d.getTime())) return "—";
44
- return formatRelativeTime(d) ?? d.toLocaleDateString();
45
- }
46
-
47
- export function BranchesView() {
48
- const { databaseAdmin } = useRebaseContext();
49
- const snackbar = useSnackbarController();
50
-
51
- const [branches, setBranches] = useState<BranchInfo[]>([]);
52
- const [loading, setLoading] = useState(true);
53
- const [selectedBranch, setSelectedBranch] = useState<string | null>(null);
54
-
55
- // Create dialog
56
- const [createOpen, setCreateOpen] = useState(false);
57
- const [newBranchName, setNewBranchName] = useState("");
58
- const [sourceBranch, setSourceBranch] = useState<string | undefined>(undefined);
59
- const [creating, setCreating] = useState(false);
60
-
61
- // Delete confirm
62
- const [deleteTarget, setDeleteTarget] = useState<string | null>(null);
63
- const [deleting, setDeleting] = useState(false);
64
-
65
- // Refs
66
- const snackbarRef = useRef(snackbar);
67
- snackbarRef.current = snackbar;
68
-
69
- const branchAdmin = isBranchAdmin(databaseAdmin) ? databaseAdmin : undefined;
70
-
71
- const loadBranches = useCallback(async () => {
72
- if (!branchAdmin) {
73
- setLoading(false);
74
- return;
75
- }
76
- try {
77
- const result = await branchAdmin.listBranches();
78
- setBranches(result);
79
- } catch (e: unknown) {
80
- snackbarRef.current.open({
81
- type: "error",
82
- message: e instanceof Error ? e.message : String(e)
83
- });
84
- } finally {
85
- setLoading(false);
86
- }
87
- }, [branchAdmin]);
88
-
89
- useEffect(() => {
90
- loadBranches();
91
- }, [loadBranches]);
92
-
93
- const handleCreate = async () => {
94
- if (!branchAdmin || !newBranchName.trim()) return;
95
- setCreating(true);
96
- try {
97
- await branchAdmin.createBranch(newBranchName.trim(), sourceBranch ? { source: sourceBranch } : undefined);
98
- snackbarRef.current.open({
99
- type: "success",
100
- message: `Branch "${newBranchName.trim()}" created successfully`
101
- });
102
- setCreateOpen(false);
103
- setNewBranchName("");
104
- setSourceBranch(undefined);
105
- await loadBranches();
106
- } catch (e: unknown) {
107
- snackbarRef.current.open({
108
- type: "error",
109
- message: e instanceof Error ? e.message : String(e)
110
- });
111
- } finally {
112
- setCreating(false);
113
- }
114
- };
115
-
116
- const handleDelete = async () => {
117
- if (!branchAdmin || !deleteTarget) return;
118
- setDeleting(true);
119
- try {
120
- await branchAdmin.deleteBranch(deleteTarget);
121
- snackbarRef.current.open({
122
- type: "success",
123
- message: `Branch "${deleteTarget}" deleted`
124
- });
125
- if (selectedBranch === deleteTarget) setSelectedBranch(null);
126
- setDeleteTarget(null);
127
- await loadBranches();
128
- } catch (e: unknown) {
129
- snackbarRef.current.open({
130
- type: "error",
131
- message: e instanceof Error ? e.message : String(e)
132
- });
133
- } finally {
134
- setDeleting(false);
135
- }
136
- };
137
-
138
- // Not supported
139
- if (!branchAdmin) {
140
- return (
141
- <div className="flex flex-col items-center justify-center h-full gap-4 text-center p-8">
142
- <GitBranchIcon size={iconSize.large} className="text-surface-300 dark:text-surface-600"/>
143
- <Typography variant="h6" color="secondary">Database Branching Not Available</Typography>
144
- <Typography variant="body2" color="disabled" className="max-w-md">
145
- Branching requires a PostgreSQL backend with an admin connection configured.
146
- Set <code className="text-xs bg-surface-100 dark:bg-surface-950 px-1.5 py-0.5 rounded font-mono">adminConnectionString</code> in your server configuration.
147
- </Typography>
148
- </div>
149
- );
150
- }
151
-
152
- if (loading) {
153
- return (
154
- <div className="flex items-center justify-center h-full">
155
- <CircularProgress/>
156
- </div>
157
- );
158
- }
159
-
160
- const selected = branches.find(b => b.name === selectedBranch);
161
-
162
- return (
163
- <div className="flex h-full w-full overflow-hidden bg-white dark:bg-surface-950">
164
- {/* ── Branch List ── */}
165
- <div className={cls("flex flex-col w-[340px] min-w-[280px] border-r h-full", defaultBorderMixin)}>
166
- <div className={cls("flex items-center justify-between px-4 py-2.5 border-b bg-surface-50 dark:bg-surface-900 min-h-[48px]", defaultBorderMixin)}>
167
- <div className="flex items-center gap-2">
168
- <GitBranchIcon size={iconSize.small} className="text-primary"/>
169
- <Typography variant="subtitle2" className="font-semibold">Branches</Typography>
170
- <Chip size="smallest" className="bg-surface-200 dark:bg-surface-700 text-surface-600 dark:text-surface-300">{branches.length}</Chip>
171
- </div>
172
- <div className="flex items-center gap-1">
173
- <IconButton size="small" onClick={loadBranches} title="Refresh">
174
- <RefreshCwIcon size={iconSize.smallest}/>
175
- </IconButton>
176
- <IconButton size="small" onClick={() => setCreateOpen(true)} title="Create branch" className="text-primary">
177
- <PlusIcon size={iconSize.smallest}/>
178
- </IconButton>
179
- </div>
180
- </div>
181
- <div className="flex-1 overflow-y-auto p-2 space-y-1">
182
- {branches.length === 0 ? (
183
- <div className="flex flex-col items-center justify-center h-full gap-3 text-center px-4">
184
- <CopyIcon size={iconSize.small} className="text-surface-300 dark:text-surface-600"/>
185
- <Typography variant="body2" color="disabled" className="text-[13px]">
186
- No branches yet. Create one to start working with an isolated database copy.
187
- </Typography>
188
- <Button
189
- size="small"
190
- variant="outlined"
191
- onClick={() => setCreateOpen(true)}
192
- startIcon={<PlusIcon size={iconSize.smallest}/>}
193
- >
194
- Create Branch
195
- </Button>
196
- </div>
197
- ) : (
198
- branches.map(branch => (
199
- <div
200
- key={branch.name}
201
- onClick={() => setSelectedBranch(branch.name)}
202
- className={cls(
203
- "flex items-center gap-3 px-3 py-2.5 rounded-lg cursor-pointer transition-all",
204
- selectedBranch === branch.name
205
- ? "bg-primary/10 dark:bg-primary/15 ring-1 ring-primary/30"
206
- : "hover:bg-surface-100 dark:hover:bg-surface-950"
207
- )}
208
- >
209
- <div className="w-2 h-2 rounded-full shrink-0 bg-emerald-500"/>
210
- <div className="flex-1 min-w-0">
211
- <Typography variant="body2" className="truncate font-medium text-[13px]">{branch.name}</Typography>
212
- <Typography variant="caption" color="secondary" className="truncate text-[11px]">
213
- from {branch.parentDatabase} · {formatRelative(branch.createdAt)}
214
- </Typography>
215
- </div>
216
- {branch.sizeBytes !== undefined && (
217
- <Typography variant="caption" color="disabled" className="font-mono text-[10px] shrink-0">
218
- {formatSize(branch.sizeBytes)}
219
- </Typography>
220
- )}
221
- </div>
222
- ))
223
- )}
224
- </div>
225
- </div>
226
-
227
- {/* ── Detail Panel ── */}
228
- <div className="flex-1 flex flex-col min-w-0 h-full overflow-hidden">
229
- {!selected ? (
230
- <div className="flex flex-col items-center justify-center h-full gap-3">
231
- <GitBranchIcon size={iconSize.large} className="text-surface-200 dark:text-surface-700"/>
232
- <Typography variant="body2" color="disabled">
233
- {branches.length === 0 ? "Create a branch to get started" : "Select a branch to view details"}
234
- </Typography>
235
- </div>
236
- ) : (
237
- <>
238
- {/* Header */}
239
- <div className={cls("flex items-center justify-between px-5 py-3 border-b bg-white dark:bg-surface-950 min-h-[56px]", defaultBorderMixin)}>
240
- <div className="flex items-center gap-3 min-w-0">
241
- <div className="w-2.5 h-2.5 rounded-full bg-emerald-500"/>
242
- <div className="min-w-0">
243
- <Typography variant="subtitle1" className="font-semibold truncate">{selected.name}</Typography>
244
- <Typography variant="caption" color="secondary" className="truncate">
245
- Created from <span className="font-mono text-[11px]">{selected.parentDatabase}</span>
246
- </Typography>
247
- </div>
248
- </div>
249
- <div className="flex items-center gap-2 shrink-0">
250
- <Button
251
- size="small"
252
- color="error"
253
- variant="outlined"
254
- onClick={() => setDeleteTarget(selected.name)}
255
- startIcon={<Trash2Icon size={iconSize.smallest}/>}
256
- >
257
- Delete
258
- </Button>
259
- </div>
260
- </div>
261
-
262
- {/* Info Cards */}
263
- <div className="px-5 py-4 bg-surface-50 dark:bg-surface-900/50">
264
- <div className="grid grid-cols-2 md:grid-cols-4 gap-3">
265
- <StatCard label="Branch Name" value={selected.name} mono/>
266
- <StatCard label="Source Database" value={selected.parentDatabase} mono/>
267
- <StatCard label="Created" value={formatRelative(selected.createdAt)}/>
268
- <StatCard label="Size" value={formatSize(selected.sizeBytes)}/>
269
- </div>
270
- </div>
271
-
272
- {/* Usage Info */}
273
- <div className="flex-1 overflow-y-auto px-5 py-4">
274
- <Alert color="info">
275
- <Typography variant="body2" className="text-[13px]">
276
- <strong>How to use this branch:</strong> Switch your application&apos;s database connection to
277
- <code className="mx-1 px-1.5 py-0.5 rounded bg-surface-100 dark:bg-surface-950 font-mono text-[12px]">{selected.name}</code>
278
- to work with an isolated copy of your data. Changes made to this branch won&apos;t affect your main database.
279
- </Typography>
280
- </Alert>
281
- <div className="mt-4 p-4 rounded-lg border bg-surface-50 dark:bg-surface-900 border-surface-200 dark:border-surface-700">
282
- <Typography variant="caption" className="text-[10px] uppercase tracking-wider text-surface-400 mb-2 block font-medium">Connection Details</Typography>
283
- <div className="space-y-2">
284
- <div className="flex items-center gap-2">
285
- <Typography variant="caption" color="secondary" className="w-24 shrink-0 text-[11px]">Database:</Typography>
286
- <Typography variant="body2" className="font-mono text-[12px]">{selected.name}</Typography>
287
- </div>
288
- <div className="flex items-center gap-2">
289
- <Typography variant="caption" color="secondary" className="w-24 shrink-0 text-[11px]">Branched from:</Typography>
290
- <Typography variant="body2" className="font-mono text-[12px]">{selected.parentDatabase}</Typography>
291
- </div>
292
- <div className="flex items-center gap-2">
293
- <Typography variant="caption" color="secondary" className="w-24 shrink-0 text-[11px]">Created at:</Typography>
294
- <Typography variant="body2" className="font-mono text-[12px]">
295
- {selected.createdAt instanceof Date
296
- ? selected.createdAt.toLocaleString()
297
- : new Date(selected.createdAt).toLocaleString()}
298
- </Typography>
299
- </div>
300
- </div>
301
- </div>
302
- </div>
303
- </>
304
- )}
305
- </div>
306
-
307
- {/* ── Create Dialog ── */}
308
- <Dialog open={createOpen} onOpenChange={setCreateOpen}>
309
- <DialogTitle>Create New Branch</DialogTitle>
310
- <DialogContent className="space-y-4 min-w-[400px]">
311
- <Typography variant="body2" color="secondary" className="text-[13px]">
312
- Create an isolated database copy. The branch will be a full clone of the source database at this point in time.
313
- </Typography>
314
- <div>
315
- <TextField
316
- label="Branch Name"
317
- value={newBranchName}
318
- onChange={(e) => setNewBranchName(e.target.value)}
319
- placeholder="e.g. feature-auth, staging, preview-pr-42"
320
- size="small"
321
- autoFocus
322
- />
323
- </div>
324
- {branches.length > 0 && (
325
- <div>
326
- <Select
327
- label="Source Database"
328
- value={sourceBranch ?? "__main__"}
329
- onValueChange={(v) => setSourceBranch(v === "__main__" ? undefined : v)}
330
- placeholder="Default (main database)"
331
- size="small"
332
- >
333
- <SelectItem value="__main__">Default (main database)</SelectItem>
334
- {branches.map(b => (
335
- <SelectItem key={b.name} value={b.name}>{b.name}</SelectItem>
336
- ))}
337
- </Select>
338
- </div>
339
- )}
340
- </DialogContent>
341
- <DialogActions>
342
- <Button variant="text" onClick={() => setCreateOpen(false)} disabled={creating}>
343
- Cancel
344
- </Button>
345
- <Button
346
- color="primary"
347
- onClick={handleCreate}
348
- disabled={!newBranchName.trim() || creating}
349
- startIcon={creating ? <CircularProgress size="smallest"/> : <PlusIcon size={iconSize.smallest}/>}
350
- >
351
- {creating ? "Creating..." : "Create Branch"}
352
- </Button>
353
- </DialogActions>
354
- </Dialog>
355
-
356
- {/* ── Delete Confirm ── */}
357
- <ConfirmationDialog
358
- open={!!deleteTarget}
359
- onAccept={handleDelete}
360
- onCancel={() => setDeleteTarget(null)}
361
- title="Delete Branch"
362
- body={
363
- <Typography variant="body2">
364
- Are you sure you want to permanently delete the branch <strong>{deleteTarget}</strong>?
365
- This action cannot be undone and all data in this branch will be lost.
366
- </Typography>
367
- }
368
- loading={deleting}
369
- />
370
- </div>
371
- );
372
- }
373
-
374
- function StatCard({ label, value, mono }: {
375
- label: string; value: string; mono?: boolean;
376
- }) {
377
- return (
378
- <div className={cls("px-3 py-2 rounded-lg border bg-white dark:bg-surface-900", defaultBorderMixin)}>
379
- <Typography variant="caption" color="secondary" className="text-[10px] uppercase tracking-wider font-medium">{label}</Typography>
380
- <Typography variant="body2" className={cls(
381
- "mt-0.5 font-semibold text-[13px] truncate",
382
- mono && "font-mono"
383
- )}>{value}</Typography>
384
- </div>
385
- );
386
- }