dbdiff-app 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +73 -0
  2. package/bin/cli.js +83 -0
  3. package/bin/install-local.js +57 -0
  4. package/electron/generate-icon.mjs +54 -0
  5. package/electron/icon.icns +0 -0
  6. package/electron/icon.png +0 -0
  7. package/electron/icon.svg +21 -0
  8. package/electron/main.js +169 -0
  9. package/electron/patch-dev-plist.js +31 -0
  10. package/electron/preload.cjs +18 -0
  11. package/electron/wait-for-vite.js +43 -0
  12. package/index.html +13 -0
  13. package/package.json +91 -0
  14. package/public/favicon.svg +15 -0
  15. package/public/vite.svg +1 -0
  16. package/server/export.ts +57 -0
  17. package/server/index.ts +392 -0
  18. package/src/App.css +1 -0
  19. package/src/App.tsx +543 -0
  20. package/src/assets/react.svg +1 -0
  21. package/src/components/CommandPalette.tsx +243 -0
  22. package/src/components/ConnectedView.tsx +78 -0
  23. package/src/components/ConnectionPicker.tsx +381 -0
  24. package/src/components/ConsoleView.tsx +360 -0
  25. package/src/components/CsvExportModal.tsx +144 -0
  26. package/src/components/DataGrid/DataGrid.tsx +262 -0
  27. package/src/components/DataGrid/DataGridCell.tsx +73 -0
  28. package/src/components/DataGrid/DataGridHeader.tsx +89 -0
  29. package/src/components/DataGrid/index.ts +20 -0
  30. package/src/components/DataGrid/types.ts +63 -0
  31. package/src/components/DataGrid/useColumnResize.ts +153 -0
  32. package/src/components/DataGrid/useDataGridSelection.ts +340 -0
  33. package/src/components/DataGrid/utils.ts +184 -0
  34. package/src/components/DatabaseMenu.tsx +93 -0
  35. package/src/components/DatabaseSwitcher.tsx +208 -0
  36. package/src/components/DiffView.tsx +215 -0
  37. package/src/components/EditConnectionModal.tsx +417 -0
  38. package/src/components/ErrorBoundary.tsx +69 -0
  39. package/src/components/GlobalShortcuts.tsx +201 -0
  40. package/src/components/InnerTabBar.tsx +129 -0
  41. package/src/components/JsonTreeViewer.tsx +387 -0
  42. package/src/components/MemberAccessEditor.tsx +443 -0
  43. package/src/components/MembersModal.tsx +446 -0
  44. package/src/components/NewConnectionModal.tsx +274 -0
  45. package/src/components/Resizer.tsx +66 -0
  46. package/src/components/ScanSuccessModal.tsx +113 -0
  47. package/src/components/ShortcutSettingsModal.tsx +318 -0
  48. package/src/components/Sidebar.tsx +532 -0
  49. package/src/components/TabBar.tsx +188 -0
  50. package/src/components/TableView.tsx +2147 -0
  51. package/src/components/ThemeToggle.tsx +44 -0
  52. package/src/components/index.ts +17 -0
  53. package/src/constants.ts +12 -0
  54. package/src/electron.d.ts +12 -0
  55. package/src/index.css +44 -0
  56. package/src/main.tsx +13 -0
  57. package/src/stores/hooks.ts +1146 -0
  58. package/src/stores/index.ts +12 -0
  59. package/src/stores/store.ts +1514 -0
  60. package/src/stores/useCloudSync.ts +274 -0
  61. package/src/stores/useSyncDatabase.ts +422 -0
  62. package/src/types.ts +277 -0
  63. package/src/utils/csv.ts +27 -0
  64. package/src/vite-env.d.ts +2 -0
  65. package/tsconfig.app.json +28 -0
  66. package/tsconfig.json +7 -0
  67. package/tsconfig.node.json +26 -0
  68. package/tsconfig.server.json +14 -0
  69. package/vite.config.ts +14 -0
package/src/types.ts ADDED
@@ -0,0 +1,277 @@
1
+ export interface DatabaseConfigDisplay {
2
+ name: string;
3
+ color: string;
4
+ }
5
+
6
+ export type DatabaseType = "postgres";
7
+
8
+ export interface DatabaseConfigConnection {
9
+ type: DatabaseType;
10
+ host: string;
11
+ port: number;
12
+ database: string;
13
+ username: string;
14
+ password: string;
15
+ params?: Record<string, string>;
16
+ }
17
+
18
+ // Schema metadata types
19
+
20
+ export interface ColumnConstraints {
21
+ isPrimaryKey: boolean;
22
+ isForeignKey: boolean;
23
+ isIndexed: boolean; // Part of any non-PK, non-unique index
24
+ isUnique: boolean; // Part of a unique index/constraint
25
+ foreignKeyRef?: { schema: string; table: string; column: string };
26
+ }
27
+
28
+ export interface ColumnInfo {
29
+ name: string;
30
+ dataType: string; // e.g., "integer", "varchar(255)"
31
+ isNullable: boolean;
32
+ defaultValue: string | null;
33
+ constraints: ColumnConstraints;
34
+ }
35
+
36
+ export interface IndexInfo {
37
+ name: string;
38
+ columns: string[];
39
+ isUnique: boolean;
40
+ isPrimary: boolean;
41
+ }
42
+
43
+ export interface TableMetadata {
44
+ schema: string;
45
+ name: string;
46
+ columns: ColumnInfo[];
47
+ primaryKey: string[];
48
+ indexes: IndexInfo[];
49
+ }
50
+
51
+ export interface SchemaMetadata {
52
+ name: string;
53
+ tables: TableMetadata[];
54
+ }
55
+
56
+ export interface DatabaseConfigCache {
57
+ tables?: string[]; // Keep for backward compat
58
+ schemas?: SchemaMetadata[]; // New comprehensive metadata
59
+ }
60
+
61
+ export type DatabaseConfigSource = "local" | "cloud";
62
+
63
+ export type AccessLevel = "write" | "read" | "none";
64
+ export type AccessMap = Record<string, AccessLevel>;
65
+
66
+ export type CloudConnectionRole = "owner" | "member";
67
+
68
+ export interface CloudConnectionInfo {
69
+ id: string; // cloud UUID
70
+ ownerId: string;
71
+ ownerEmail: string;
72
+ role: CloudConnectionRole;
73
+ access?: AccessMap;
74
+ updatedAt: string;
75
+ }
76
+
77
+ export interface DatabaseConfig {
78
+ id: string;
79
+ display: DatabaseConfigDisplay;
80
+ connection: DatabaseConfigConnection;
81
+ cache: DatabaseConfigCache;
82
+ source: DatabaseConfigSource;
83
+ cloud?: CloudConnectionInfo;
84
+ tableConfigs?: Record<string, TableConfig>;
85
+ }
86
+
87
+ export interface TableConfig {
88
+ pageSize?: number;
89
+ fkPreviewColumns?: Record<string, string>; // FK col name -> display col from referenced table
90
+ }
91
+
92
+ export interface InnerTab {
93
+ id: string;
94
+ type: "table" | "console" | "query";
95
+ name: string;
96
+ }
97
+
98
+ export interface ConnectionTab {
99
+ id: string;
100
+ name: string;
101
+ databaseConfigId: string | null;
102
+ innerTabs: InnerTab[];
103
+ activeInnerTabId: string | null;
104
+ }
105
+
106
+ // API types
107
+
108
+ export interface QueryRequest {
109
+ connection: DatabaseConfigConnection;
110
+ query: string;
111
+ }
112
+
113
+ export interface QueryFieldInfo {
114
+ name: string;
115
+ dataTypeID: number;
116
+ }
117
+
118
+ export interface QueryResponse {
119
+ rows: Record<string, unknown>[];
120
+ fields: QueryFieldInfo[];
121
+ rowCount: number | null;
122
+ }
123
+
124
+ export interface QueryErrorResponse {
125
+ error: string;
126
+ }
127
+
128
+ export interface ScanLocalhostResult {
129
+ host: string;
130
+ port: number;
131
+ username: string;
132
+ password: string;
133
+ database: string;
134
+ }
135
+
136
+ export interface ScanLocalhostResponse {
137
+ databases: ScanLocalhostResult[];
138
+ error?: string;
139
+ }
140
+
141
+ // Diff types
142
+
143
+ export interface DiffTableResult {
144
+ tableName: string;
145
+ columns: QueryFieldInfo[];
146
+ primaryKeyColumns: string[];
147
+ deleted: Record<string, unknown>[];
148
+ added: Record<string, unknown>[];
149
+ modified: {
150
+ before: Record<string, unknown>;
151
+ after: Record<string, unknown>;
152
+ changedColumns: string[];
153
+ }[];
154
+ unchangedCount: number;
155
+ }
156
+
157
+ export interface DiffResponse {
158
+ tables: DiffTableResult[];
159
+ }
160
+
161
+ // Console tab state types
162
+
163
+ export type ExecutionStatus = "idle" | "executing" | "completed" | "error";
164
+
165
+ export interface ConsoleTabState {
166
+ queryText: string;
167
+ status: ExecutionStatus;
168
+ executionId: string | null; // For race condition handling
169
+ startedAt: number | null;
170
+ completedAt: number | null;
171
+ result: QueryResponse | null;
172
+ error: string | null;
173
+ diffResult: DiffResponse | null;
174
+ lastAction: "run" | "diff" | null;
175
+ }
176
+
177
+ // Table tab state types
178
+
179
+ export type SortDirection = "ASC" | "DESC";
180
+
181
+ export interface SortColumn {
182
+ column: string;
183
+ direction: SortDirection;
184
+ }
185
+
186
+ export interface TableTabState {
187
+ tableName: string;
188
+ whereClause: string; // e.g., "user_id='abc123'"
189
+ sortColumns: SortColumn[]; // Ordered list of sort columns
190
+ currentPage: number; // Zero-indexed page number
191
+ totalRowCount: number | null; // From COUNT query, null = unknown
192
+ status: ExecutionStatus;
193
+ executionId: string | null;
194
+ startedAt: number | null;
195
+ completedAt: number | null;
196
+ result: QueryResponse | null;
197
+ error: string | null;
198
+ cellEditState: TableCellEditState;
199
+ }
200
+
201
+ // Config sync state (for syncing schema metadata per database config)
202
+
203
+ export interface ConfigSyncState {
204
+ status: ExecutionStatus;
205
+ executionId: string | null;
206
+ startedAt: number | null;
207
+ completedAt: number | null;
208
+ error: string | null;
209
+ }
210
+
211
+ // Cell editing types
212
+
213
+ export interface CellPosition {
214
+ rowIndex: number;
215
+ columnName: string;
216
+ }
217
+
218
+ export interface CellRange {
219
+ start: CellPosition;
220
+ end: CellPosition;
221
+ }
222
+
223
+ export interface CellChange {
224
+ rowIndex: number;
225
+ columnName: string;
226
+ originalValue: unknown;
227
+ newValue: string | null; // null means SQL NULL
228
+ }
229
+
230
+ // New row pending insertion
231
+ export interface PendingNewRow {
232
+ tempId: string; // Unique ID (e.g., timestamp)
233
+ explicitlySetColumns: Set<string>; // Columns user explicitly set
234
+ values: Record<string, string | null>; // Column values (null = SQL NULL)
235
+ }
236
+
237
+ export interface TableCellEditState {
238
+ selectedCell: CellPosition | null;
239
+ selectedRange: CellRange | null;
240
+ isDragging: boolean;
241
+ editingCell: CellPosition | null;
242
+ editValue: string | null;
243
+ pendingChanges: Record<string, CellChange>; // key: "rowIndex:columnName"
244
+ pendingNewRows: PendingNewRow[]; // Rows to insert
245
+ pendingDeletions: number[]; // Row indices marked for deletion (positive only)
246
+ }
247
+
248
+ // Keyboard shortcuts
249
+
250
+ export type ShortcutAction =
251
+ | "newConsole"
252
+ | "closeInnerTab"
253
+ | "nextInnerTab"
254
+ | "prevInnerTab"
255
+ | "newConnectionTab"
256
+ | "closeConnectionTab"
257
+ | "nextConnectionTab"
258
+ | "prevConnectionTab"
259
+ | "runQuery"
260
+ | "closeModal"
261
+ | "openTableSwitcher"
262
+ | "deleteRows"
263
+ | "selectAll"
264
+ | "refreshTable"
265
+ | "openDatabaseSwitcher";
266
+
267
+ export type ShortcutConfig = Record<ShortcutAction, string>;
268
+
269
+ export type ExportType = "schema" | "schema-and-data";
270
+
271
+ // Connection member (for sharing cloud connections)
272
+ export interface ConnectionMember {
273
+ id: string;
274
+ email: string;
275
+ access: AccessMap;
276
+ createdAt: string;
277
+ }
@@ -0,0 +1,27 @@
1
+ interface CsvOptions {
2
+ includeHeaders: boolean;
3
+ }
4
+
5
+ function escapeCsvValue(value: unknown): string {
6
+ if (value === null || value === undefined) return "";
7
+ const str = typeof value === "object" ? JSON.stringify(value) : String(value);
8
+ if (str.includes(",") || str.includes('"') || str.includes("\n")) {
9
+ return `"${str.replace(/"/g, '""')}"`;
10
+ }
11
+ return str;
12
+ }
13
+
14
+ export function generateCsv(
15
+ fields: { name: string }[],
16
+ rows: Record<string, unknown>[],
17
+ options: CsvOptions,
18
+ ): string {
19
+ const lines: string[] = [];
20
+ if (options.includeHeaders) {
21
+ lines.push(fields.map((f) => escapeCsvValue(f.name)).join(","));
22
+ }
23
+ for (const row of rows) {
24
+ lines.push(fields.map((f) => escapeCsvValue(row[f.name])).join(","));
25
+ }
26
+ return lines.join("\n");
27
+ }
@@ -0,0 +1,2 @@
1
+ /// <reference types="vite/client" />
2
+ declare const process: { env: { NODE_ENV?: string } };
@@ -0,0 +1,28 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
+ "target": "ES2022",
5
+ "useDefineForClassFields": true,
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "module": "ESNext",
8
+ "types": ["vite/client"],
9
+ "skipLibCheck": true,
10
+
11
+ /* Bundler mode */
12
+ "moduleResolution": "bundler",
13
+ "allowImportingTsExtensions": true,
14
+ "verbatimModuleSyntax": true,
15
+ "moduleDetection": "force",
16
+ "noEmit": true,
17
+ "jsx": "react-jsx",
18
+
19
+ /* Linting */
20
+ "strict": true,
21
+ "noUnusedLocals": true,
22
+ "noUnusedParameters": true,
23
+ "erasableSyntaxOnly": true,
24
+ "noFallthroughCasesInSwitch": true,
25
+ "noUncheckedSideEffectImports": true
26
+ },
27
+ "include": ["src"]
28
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ { "path": "./tsconfig.app.json" },
5
+ { "path": "./tsconfig.node.json" }
6
+ ]
7
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
+ "target": "ES2023",
5
+ "lib": ["ES2023"],
6
+ "module": "ESNext",
7
+ "types": ["node"],
8
+ "skipLibCheck": true,
9
+
10
+ /* Bundler mode */
11
+ "moduleResolution": "bundler",
12
+ "allowImportingTsExtensions": true,
13
+ "verbatimModuleSyntax": true,
14
+ "moduleDetection": "force",
15
+ "noEmit": true,
16
+
17
+ /* Linting */
18
+ "strict": true,
19
+ "noUnusedLocals": true,
20
+ "noUnusedParameters": true,
21
+ "erasableSyntaxOnly": true,
22
+ "noFallthroughCasesInSwitch": true,
23
+ "noUncheckedSideEffectImports": true
24
+ },
25
+ "include": ["vite.config.ts"]
26
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "outDir": "dist-server",
7
+ "rootDir": ".",
8
+ "strict": true,
9
+ "skipLibCheck": true,
10
+ "esModuleInterop": true,
11
+ "declaration": false
12
+ },
13
+ "include": ["server", "src/types.ts"]
14
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,14 @@
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+ import tailwindcss from '@tailwindcss/vite'
4
+
5
+ // https://vite.dev/config/
6
+ export default defineConfig({
7
+ plugins: [react(), tailwindcss()],
8
+ server: {
9
+ port: 4089,
10
+ proxy: {
11
+ '/api': 'http://localhost:4088'
12
+ }
13
+ }
14
+ })