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.
- package/README.md +73 -0
- package/bin/cli.js +83 -0
- package/bin/install-local.js +57 -0
- package/electron/generate-icon.mjs +54 -0
- package/electron/icon.icns +0 -0
- package/electron/icon.png +0 -0
- package/electron/icon.svg +21 -0
- package/electron/main.js +169 -0
- package/electron/patch-dev-plist.js +31 -0
- package/electron/preload.cjs +18 -0
- package/electron/wait-for-vite.js +43 -0
- package/index.html +13 -0
- package/package.json +91 -0
- package/public/favicon.svg +15 -0
- package/public/vite.svg +1 -0
- package/server/export.ts +57 -0
- package/server/index.ts +392 -0
- package/src/App.css +1 -0
- package/src/App.tsx +543 -0
- package/src/assets/react.svg +1 -0
- package/src/components/CommandPalette.tsx +243 -0
- package/src/components/ConnectedView.tsx +78 -0
- package/src/components/ConnectionPicker.tsx +381 -0
- package/src/components/ConsoleView.tsx +360 -0
- package/src/components/CsvExportModal.tsx +144 -0
- package/src/components/DataGrid/DataGrid.tsx +262 -0
- package/src/components/DataGrid/DataGridCell.tsx +73 -0
- package/src/components/DataGrid/DataGridHeader.tsx +89 -0
- package/src/components/DataGrid/index.ts +20 -0
- package/src/components/DataGrid/types.ts +63 -0
- package/src/components/DataGrid/useColumnResize.ts +153 -0
- package/src/components/DataGrid/useDataGridSelection.ts +340 -0
- package/src/components/DataGrid/utils.ts +184 -0
- package/src/components/DatabaseMenu.tsx +93 -0
- package/src/components/DatabaseSwitcher.tsx +208 -0
- package/src/components/DiffView.tsx +215 -0
- package/src/components/EditConnectionModal.tsx +417 -0
- package/src/components/ErrorBoundary.tsx +69 -0
- package/src/components/GlobalShortcuts.tsx +201 -0
- package/src/components/InnerTabBar.tsx +129 -0
- package/src/components/JsonTreeViewer.tsx +387 -0
- package/src/components/MemberAccessEditor.tsx +443 -0
- package/src/components/MembersModal.tsx +446 -0
- package/src/components/NewConnectionModal.tsx +274 -0
- package/src/components/Resizer.tsx +66 -0
- package/src/components/ScanSuccessModal.tsx +113 -0
- package/src/components/ShortcutSettingsModal.tsx +318 -0
- package/src/components/Sidebar.tsx +532 -0
- package/src/components/TabBar.tsx +188 -0
- package/src/components/TableView.tsx +2147 -0
- package/src/components/ThemeToggle.tsx +44 -0
- package/src/components/index.ts +17 -0
- package/src/constants.ts +12 -0
- package/src/electron.d.ts +12 -0
- package/src/index.css +44 -0
- package/src/main.tsx +13 -0
- package/src/stores/hooks.ts +1146 -0
- package/src/stores/index.ts +12 -0
- package/src/stores/store.ts +1514 -0
- package/src/stores/useCloudSync.ts +274 -0
- package/src/stores/useSyncDatabase.ts +422 -0
- package/src/types.ts +277 -0
- package/src/utils/csv.ts +27 -0
- package/src/vite-env.d.ts +2 -0
- package/tsconfig.app.json +28 -0
- package/tsconfig.json +7 -0
- package/tsconfig.node.json +26 -0
- package/tsconfig.server.json +14 -0
- 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
|
+
}
|
package/src/utils/csv.ts
ADDED
|
@@ -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,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,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
|
+
})
|