sqlite-hub 0.3.2 → 0.5.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 +5 -5
- package/changelog.md +14 -0
- package/{js → frontend/js}/api.js +99 -5
- package/{js → frontend/js}/app.js +162 -11
- package/{js → frontend/js}/components/connectionCard.js +8 -9
- package/frontend/js/components/connectionLogo.js +33 -0
- package/{js → frontend/js}/components/dataGrid.js +3 -3
- package/{js → frontend/js}/components/emptyState.js +25 -11
- package/{js → frontend/js}/components/modal.js +57 -0
- package/{js → frontend/js}/components/queryEditor.js +33 -55
- package/frontend/js/components/queryHistoryDetail.js +263 -0
- package/frontend/js/components/queryHistoryPanel.js +228 -0
- package/{js → frontend/js}/components/queryResults.js +32 -46
- package/{js → frontend/js}/components/rowEditorPanel.js +73 -14
- package/{js → frontend/js}/components/sidebar.js +8 -3
- package/{js → frontend/js}/store.js +577 -21
- package/{js → frontend/js}/utils/format.js +23 -0
- package/{js → frontend/js}/views/data.js +136 -10
- package/{js → frontend/js}/views/editor.js +34 -10
- package/{js → frontend/js}/views/overview.js +15 -0
- package/{js → frontend/js}/views/structure.js +10 -12
- package/{styles → frontend/styles}/components.css +106 -0
- package/{styles → frontend/styles}/structure-graph.css +5 -10
- package/package.json +2 -2
- package/{publish_brew.sh → scripts/publish_brew.sh} +2 -2
- package/{publish_npm.sh → scripts/publish_npm.sh} +2 -2
- package/server/data/db_logos/.gitkeep +0 -0
- package/server/routes/connections.js +2 -0
- package/server/routes/data.js +2 -0
- package/server/routes/export.js +4 -1
- package/server/routes/overview.js +12 -0
- package/server/routes/sql.js +163 -5
- package/server/server.js +8 -6
- package/server/services/sqlite/connectionManager.js +68 -33
- package/server/services/sqlite/dataBrowserService.js +4 -16
- package/server/services/sqlite/exportService.js +4 -16
- package/server/services/sqlite/overviewService.js +34 -0
- package/server/services/sqlite/sqlExecutor.js +83 -63
- package/server/services/sqlite/tableSort.js +63 -0
- package/server/services/storage/appStateStore.js +832 -20
- package/server/services/storage/queryHistoryUtils.js +169 -0
- package/server/utils/appPaths.js +42 -18
- package/{assets → frontend/assets}/images/logo.webp +0 -0
- package/{assets → frontend/assets}/images/logo_extrasmall.webp +0 -0
- package/{assets → frontend/assets}/images/logo_raw.png +0 -0
- package/{assets → frontend/assets}/images/logo_small.webp +0 -0
- package/{assets → frontend/assets}/mockups/connections.png +0 -0
- package/{assets → frontend/assets}/mockups/data.png +0 -0
- package/{assets → frontend/assets}/mockups/data_edit.png +0 -0
- package/{assets → frontend/assets}/mockups/graph_visualize.png +0 -0
- package/{assets → frontend/assets}/mockups/home.png +0 -0
- package/{assets → frontend/assets}/mockups/overview.png +0 -0
- package/{assets → frontend/assets}/mockups/sql_editor.png +0 -0
- package/{assets → frontend/assets}/mockups/structure.png +0 -0
- package/{index.html → frontend/index.html} +0 -0
- package/{js → frontend/js}/components/actionBar.js +0 -0
- package/{js → frontend/js}/components/appShell.js +0 -0
- package/{js → frontend/js}/components/badges.js +0 -0
- package/{js → frontend/js}/components/bottomTabs.js +1 -1
- /package/{js → frontend/js}/components/metricCard.js +0 -0
- /package/{js → frontend/js}/components/pageHeader.js +0 -0
- /package/{js → frontend/js}/components/statusBar.js +0 -0
- /package/{js → frontend/js}/components/structureGraph.js +0 -0
- /package/{js → frontend/js}/components/toast.js +0 -0
- /package/{js → frontend/js}/components/topNav.js +0 -0
- /package/{js → frontend/js}/lib/cytoscapeRuntime.js +0 -0
- /package/{js → frontend/js}/router.js +0 -0
- /package/{js → frontend/js}/views/connections.js +0 -0
- /package/{js → frontend/js}/views/landing.js +0 -0
- /package/{js → frontend/js}/views/settings.js +0 -0
- /package/{styles → frontend/styles}/base.css +0 -0
- /package/{styles → frontend/styles}/layout.css +0 -0
- /package/{styles → frontend/styles}/tokens.css +0 -0
- /package/{styles → frontend/styles}/views.css +0 -0
- /package/{data → server/data}/.gitkeep +0 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
const
|
|
2
|
-
const { ValidationError } = require("../../utils/errors");
|
|
1
|
+
const { ValidationError, mapSqliteError } = require("../../utils/errors");
|
|
3
2
|
const { serializeRows } = require("../../utils/sqliteTypes");
|
|
4
3
|
const { getTableDetail } = require("./introspection");
|
|
5
4
|
|
|
@@ -151,7 +150,7 @@ function splitSqlStatements(sql) {
|
|
|
151
150
|
statements.push(current.trim());
|
|
152
151
|
}
|
|
153
152
|
|
|
154
|
-
return statements.filter((statement) => statement.trim());
|
|
153
|
+
return statements.filter((statement) => statement.trim() && getFirstKeyword(statement));
|
|
155
154
|
}
|
|
156
155
|
|
|
157
156
|
function buildRowIdentity(tableDetail, row) {
|
|
@@ -337,62 +336,85 @@ class SqlExecutor {
|
|
|
337
336
|
const results = [];
|
|
338
337
|
let lastResultSet = null;
|
|
339
338
|
let totalChanges = 0;
|
|
339
|
+
try {
|
|
340
|
+
statements.forEach((statement, index) => {
|
|
341
|
+
const prepared = db.prepare(statement);
|
|
342
|
+
const keyword = getFirstKeyword(statement);
|
|
343
|
+
|
|
344
|
+
if (options.requireReader && !prepared.reader) {
|
|
345
|
+
throw new ValidationError(
|
|
346
|
+
`Statement ${index + 1} is not a result-set statement and cannot be exported.`
|
|
347
|
+
);
|
|
348
|
+
}
|
|
340
349
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
+
if (prepared.reader) {
|
|
351
|
+
const rows = prepared.all();
|
|
352
|
+
const columnDefinitions = prepared.columns();
|
|
353
|
+
const serializedRows = serializeRows(rows);
|
|
354
|
+
const editableResult = resolveEditableResult(db, columnDefinitions, serializedRows);
|
|
355
|
+
const columns = columnDefinitions.map((column) => column.name);
|
|
356
|
+
const result = {
|
|
357
|
+
index,
|
|
358
|
+
sql: statement,
|
|
359
|
+
keyword,
|
|
360
|
+
kind: "resultSet",
|
|
361
|
+
rowCount: serializedRows.length,
|
|
362
|
+
columns,
|
|
363
|
+
rows: editableResult?.rows ?? serializedRows,
|
|
364
|
+
editing: editableResult
|
|
365
|
+
? {
|
|
366
|
+
enabled: editableResult.enabled,
|
|
367
|
+
tableName: editableResult.tableName ?? null,
|
|
368
|
+
reason: editableResult.reason ?? "",
|
|
369
|
+
columns: editableResult.columns ?? [],
|
|
370
|
+
identityStrategy: editableResult.identityStrategy ?? null,
|
|
371
|
+
}
|
|
372
|
+
: null,
|
|
373
|
+
};
|
|
374
|
+
results.push(result);
|
|
375
|
+
lastResultSet = result;
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
350
378
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
const serializedRows = serializeRows(rows);
|
|
355
|
-
const editableResult = resolveEditableResult(db, columnDefinitions, serializedRows);
|
|
356
|
-
const columns = columnDefinitions.map((column) => column.name);
|
|
357
|
-
const result = {
|
|
379
|
+
const info = prepared.run();
|
|
380
|
+
totalChanges += info.changes;
|
|
381
|
+
results.push({
|
|
358
382
|
index,
|
|
359
383
|
sql: statement,
|
|
360
384
|
keyword,
|
|
361
|
-
kind: "
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
385
|
+
kind: "mutation",
|
|
386
|
+
changes: info.changes,
|
|
387
|
+
lastInsertRowid:
|
|
388
|
+
keyword === "INSERT" || keyword === "REPLACE"
|
|
389
|
+
? typeof info.lastInsertRowid === "bigint"
|
|
390
|
+
? Number(info.lastInsertRowid)
|
|
391
|
+
: info.lastInsertRowid
|
|
392
|
+
: null,
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
} catch (error) {
|
|
396
|
+
const normalizedError = mapSqliteError(error);
|
|
397
|
+
|
|
398
|
+
if (options.persistHistory !== false) {
|
|
399
|
+
try {
|
|
400
|
+
this.appStateStore.recordQueryExecution({
|
|
401
|
+
databaseKey: connection.id,
|
|
402
|
+
rawSql: sql,
|
|
403
|
+
status: "error",
|
|
404
|
+
durationMs: Date.now() - startedAt,
|
|
405
|
+
rowCount: lastResultSet?.rowCount ?? null,
|
|
406
|
+
affectedRows: totalChanges,
|
|
407
|
+
errorMessage: normalizedError.message,
|
|
408
|
+
});
|
|
409
|
+
} catch (recordingError) {
|
|
410
|
+
console.warn(
|
|
411
|
+
`Failed to record SQL query history error for ${connection.id}: ${recordingError.message}`
|
|
412
|
+
);
|
|
413
|
+
}
|
|
378
414
|
}
|
|
379
415
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
results.push({
|
|
383
|
-
index,
|
|
384
|
-
sql: statement,
|
|
385
|
-
keyword,
|
|
386
|
-
kind: "mutation",
|
|
387
|
-
changes: info.changes,
|
|
388
|
-
lastInsertRowid:
|
|
389
|
-
keyword === "INSERT" || keyword === "REPLACE"
|
|
390
|
-
? typeof info.lastInsertRowid === "bigint"
|
|
391
|
-
? Number(info.lastInsertRowid)
|
|
392
|
-
: info.lastInsertRowid
|
|
393
|
-
: null,
|
|
394
|
-
});
|
|
395
|
-
});
|
|
416
|
+
throw normalizedError;
|
|
417
|
+
}
|
|
396
418
|
|
|
397
419
|
const timingMs = Date.now() - startedAt;
|
|
398
420
|
const payload = {
|
|
@@ -405,25 +427,23 @@ class SqlExecutor {
|
|
|
405
427
|
affectedRowCount: totalChanges,
|
|
406
428
|
resultKind: lastResultSet ? "resultSet" : results.at(-1)?.kind ?? "unknown",
|
|
407
429
|
};
|
|
430
|
+
let historyId = null;
|
|
408
431
|
|
|
409
432
|
if (options.persistHistory !== false) {
|
|
410
|
-
this.appStateStore.
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
affectedRowCount: totalChanges,
|
|
418
|
-
rowCount: lastResultSet?.rowCount ?? 0,
|
|
419
|
-
timingMs,
|
|
420
|
-
executedAt: new Date().toISOString(),
|
|
433
|
+
historyId = this.appStateStore.recordQueryExecution({
|
|
434
|
+
databaseKey: connection.id,
|
|
435
|
+
rawSql: sql,
|
|
436
|
+
status: "success",
|
|
437
|
+
durationMs: timingMs,
|
|
438
|
+
rowCount: lastResultSet?.rowCount ?? null,
|
|
439
|
+
affectedRows: totalChanges,
|
|
421
440
|
});
|
|
422
441
|
}
|
|
423
442
|
|
|
424
443
|
return {
|
|
425
444
|
...payload,
|
|
426
445
|
timingMs,
|
|
446
|
+
historyId,
|
|
427
447
|
};
|
|
428
448
|
}
|
|
429
449
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const { ValidationError } = require("../../utils/errors");
|
|
2
|
+
const { quoteIdentifier } = require("../../utils/identifier");
|
|
3
|
+
|
|
4
|
+
function normalizeSortDirection(value) {
|
|
5
|
+
const normalized = String(value ?? "").trim().toLowerCase();
|
|
6
|
+
return normalized === "desc" ? "desc" : "asc";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function buildDefaultOrderClause(tableDetail) {
|
|
10
|
+
if (tableDetail.identityStrategy?.type === "rowid") {
|
|
11
|
+
return "rowid ASC";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (tableDetail.identityStrategy?.type === "primaryKey") {
|
|
15
|
+
return tableDetail.identityStrategy.columns
|
|
16
|
+
.map((columnName) => `${quoteIdentifier(columnName)} ASC`)
|
|
17
|
+
.join(", ");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return "";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function normalizeTableSort(tableDetail, options = {}) {
|
|
24
|
+
const sortColumn = typeof options.sortColumn === "string" ? options.sortColumn.trim() : "";
|
|
25
|
+
|
|
26
|
+
if (!sortColumn) {
|
|
27
|
+
return {
|
|
28
|
+
column: null,
|
|
29
|
+
direction: null,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const availableColumns = (tableDetail.columns ?? [])
|
|
34
|
+
.filter((column) => column.visible !== false)
|
|
35
|
+
.map((column) => column.name);
|
|
36
|
+
|
|
37
|
+
if (!availableColumns.includes(sortColumn)) {
|
|
38
|
+
throw new ValidationError(`sortColumn must reference a visible column on ${tableDetail.name}.`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
column: sortColumn,
|
|
43
|
+
direction: normalizeSortDirection(options.sortDirection),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function buildTableOrderClause(tableDetail, sort = {}) {
|
|
48
|
+
const defaultOrderClause = buildDefaultOrderClause(tableDetail);
|
|
49
|
+
|
|
50
|
+
if (!sort.column) {
|
|
51
|
+
return defaultOrderClause;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const primarySort = `${quoteIdentifier(sort.column)} ${sort.direction.toUpperCase()}`;
|
|
55
|
+
|
|
56
|
+
return [primarySort, defaultOrderClause].filter(Boolean).join(", ");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = {
|
|
60
|
+
buildTableOrderClause,
|
|
61
|
+
normalizeSortDirection,
|
|
62
|
+
normalizeTableSort,
|
|
63
|
+
};
|