sqlite-hub 0.9.9 → 0.9.11
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/frontend/js/store.js
CHANGED
|
@@ -558,6 +558,16 @@ function getCurrentStructureEntry(snapshot = state) {
|
|
|
558
558
|
return entries.find(entry => entry.name === snapshot.structure.selectedName) ?? null;
|
|
559
559
|
}
|
|
560
560
|
|
|
561
|
+
function resolveStructureSelectedName(structure, preferredName) {
|
|
562
|
+
const entries = structure?.entries ?? [];
|
|
563
|
+
|
|
564
|
+
if (preferredName && entries.some(entry => entry.name === preferredName)) {
|
|
565
|
+
return preferredName;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return structure?.grouped?.tables?.[0]?.name ?? entries[0]?.name ?? null;
|
|
569
|
+
}
|
|
570
|
+
|
|
561
571
|
function getTableDesignerContext(snapshot = state) {
|
|
562
572
|
return {
|
|
563
573
|
catalogTables: snapshot.tableDesigner.tables ?? [],
|
|
@@ -986,7 +996,6 @@ function syncRouteContext() {
|
|
|
986
996
|
state.structure.detail = null;
|
|
987
997
|
} else if (route.name !== 'structure') {
|
|
988
998
|
state.structure.detail = null;
|
|
989
|
-
state.structure.selectedName = null;
|
|
990
999
|
}
|
|
991
1000
|
|
|
992
1001
|
if (route.name !== 'tableDesigner') {
|
|
@@ -1572,11 +1581,7 @@ async function loadStructure(version) {
|
|
|
1572
1581
|
}
|
|
1573
1582
|
|
|
1574
1583
|
state.structure.data = response.data;
|
|
1575
|
-
state.structure.selectedName =
|
|
1576
|
-
state.structure.selectedName ??
|
|
1577
|
-
response.data.grouped.tables[0]?.name ??
|
|
1578
|
-
response.data.entries[0]?.name ??
|
|
1579
|
-
null;
|
|
1584
|
+
state.structure.selectedName = resolveStructureSelectedName(response.data, state.structure.selectedName);
|
|
1580
1585
|
|
|
1581
1586
|
await loadStructureDetail(version);
|
|
1582
1587
|
} catch (error) {
|
|
@@ -3927,6 +3932,7 @@ export function getQueryPerformance(snapshot = state) {
|
|
|
3927
3932
|
if (!result) {
|
|
3928
3933
|
return {
|
|
3929
3934
|
timingMs: null,
|
|
3935
|
+
memoryBytes: 0,
|
|
3930
3936
|
statementCount: 0,
|
|
3931
3937
|
rowCount: 0,
|
|
3932
3938
|
affectedRowCount: 0,
|
|
@@ -3935,6 +3941,7 @@ export function getQueryPerformance(snapshot = state) {
|
|
|
3935
3941
|
|
|
3936
3942
|
return {
|
|
3937
3943
|
timingMs: result.timingMs ?? 0,
|
|
3944
|
+
memoryBytes: result.memoryBytes ?? 0,
|
|
3938
3945
|
statementCount: result.statementCount ?? result.statements?.length ?? 0,
|
|
3939
3946
|
rowCount: result.rows?.length ?? 0,
|
|
3940
3947
|
affectedRowCount: result.affectedRowCount ?? 0,
|
|
@@ -9,6 +9,11 @@ const COMPACT_DATE_TIME_FORMATTER = new Intl.DateTimeFormat("en-GB", {
|
|
|
9
9
|
dateStyle: "short",
|
|
10
10
|
timeStyle: "short",
|
|
11
11
|
});
|
|
12
|
+
const EXECUTION_TIME_SECONDS_THRESHOLD_MS = 10000;
|
|
13
|
+
const EXECUTION_TIME_SECONDS_FORMATTER = new Intl.NumberFormat("en-US", {
|
|
14
|
+
minimumFractionDigits: 1,
|
|
15
|
+
maximumFractionDigits: 1,
|
|
16
|
+
});
|
|
12
17
|
|
|
13
18
|
export function escapeHtml(value = "") {
|
|
14
19
|
return String(value)
|
|
@@ -75,6 +80,20 @@ export function formatDurationMs(value) {
|
|
|
75
80
|
return `${numericValue.toLocaleString("en-US")} ms`;
|
|
76
81
|
}
|
|
77
82
|
|
|
83
|
+
export function formatExecutionTimeMs(value) {
|
|
84
|
+
const numericValue = Number(value);
|
|
85
|
+
|
|
86
|
+
if (!Number.isFinite(numericValue) || numericValue < 0) {
|
|
87
|
+
return "N/A";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (numericValue > EXECUTION_TIME_SECONDS_THRESHOLD_MS) {
|
|
91
|
+
return `${EXECUTION_TIME_SECONDS_FORMATTER.format(numericValue / 1000)}s`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return `${numericValue.toLocaleString("en-US")}ms`;
|
|
95
|
+
}
|
|
96
|
+
|
|
78
97
|
export function formatRelativeBoolean(value) {
|
|
79
98
|
return value ? "ENABLED" : "DISABLED";
|
|
80
99
|
}
|
|
@@ -5,7 +5,7 @@ import { renderQueryHistoryPanel } from '../components/queryHistoryPanel.js';
|
|
|
5
5
|
import { renderRowEditorPanel } from '../components/rowEditorPanel.js';
|
|
6
6
|
import { renderQueryResultsPane } from '../components/queryResults.js';
|
|
7
7
|
import { getCurrentConnection, getQueryMessages, getQueryPerformance } from '../store.js';
|
|
8
|
-
import { escapeHtml, formatCellValue, formatNumber, isBlobPreview } from '../utils/format.js';
|
|
8
|
+
import { escapeHtml, formatBytes, formatCellValue, formatExecutionTimeMs, formatNumber, isBlobPreview } from '../utils/format.js';
|
|
9
9
|
|
|
10
10
|
function renderMissingDatabase() {
|
|
11
11
|
return `
|
|
@@ -50,12 +50,12 @@ function renderPerformancePane(state) {
|
|
|
50
50
|
const metrics = getQueryPerformance(state);
|
|
51
51
|
|
|
52
52
|
return `
|
|
53
|
-
<div class="grid flex-1
|
|
53
|
+
<div class="performance-metrics-grid grid flex-1 gap-4 bg-surface-container-lowest p-6">
|
|
54
54
|
<div class="metric-card">
|
|
55
55
|
<span class="text-[10px] font-mono uppercase text-on-surface/40">Exec_Time</span>
|
|
56
56
|
<span class="font-headline text-3xl font-bold text-on-surface">${escapeHtml(
|
|
57
|
-
|
|
58
|
-
)}
|
|
57
|
+
formatExecutionTimeMs(metrics.timingMs ?? 0),
|
|
58
|
+
)}</span>
|
|
59
59
|
<span class="text-[10px] text-primary-container">Measured backend execution time</span>
|
|
60
60
|
</div>
|
|
61
61
|
<div class="metric-card">
|
|
@@ -72,6 +72,13 @@ function renderPerformancePane(state) {
|
|
|
72
72
|
)}</span>
|
|
73
73
|
<span class="text-[10px] text-on-surface/40">Visible result set size</span>
|
|
74
74
|
</div>
|
|
75
|
+
<div class="metric-card">
|
|
76
|
+
<span class="text-[10px] font-mono uppercase text-on-surface/40">Memory_Size</span>
|
|
77
|
+
<span class="font-headline text-3xl font-bold text-on-surface">${escapeHtml(
|
|
78
|
+
formatBytes(metrics.memoryBytes),
|
|
79
|
+
)}</span>
|
|
80
|
+
<span class="text-[10px] text-on-surface/40">Serialized result payload</span>
|
|
81
|
+
</div>
|
|
75
82
|
<div class="metric-card metric-card--accent">
|
|
76
83
|
<span class="text-[10px] font-mono uppercase text-on-surface/40">Rows_Affected</span>
|
|
77
84
|
<span class="font-headline text-3xl font-bold text-on-surface">${escapeHtml(
|
|
@@ -311,6 +311,10 @@
|
|
|
311
311
|
padding: var(--spacing-6);
|
|
312
312
|
}
|
|
313
313
|
|
|
314
|
+
.performance-metrics-grid {
|
|
315
|
+
grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr));
|
|
316
|
+
}
|
|
317
|
+
|
|
314
318
|
.metric-card--accent {
|
|
315
319
|
box-shadow: inset 2px 0 0 var(--color-primary-container);
|
|
316
320
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sqlite-hub",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.11",
|
|
4
4
|
"description": "SQLite-only local management app backend and SPA shell",
|
|
5
5
|
"main": "server/server.js",
|
|
6
6
|
"bin": {
|
|
@@ -20,12 +20,14 @@
|
|
|
20
20
|
"better-sqlite3": "^12.10.0",
|
|
21
21
|
"cytoscape": "^3.33.4",
|
|
22
22
|
"cytoscape-elk": "^2.3.0",
|
|
23
|
+
"dotenv": "^17.4.2",
|
|
23
24
|
"echarts": "^6.1.0",
|
|
24
25
|
"elkjs": "^0.11.1",
|
|
25
26
|
"express": "^5.2.1",
|
|
26
27
|
"express-rate-limit": "^8.5.2",
|
|
27
28
|
"helmet": "^8.2.0",
|
|
28
|
-
"material-symbols": "^0.44.9"
|
|
29
|
+
"material-symbols": "^0.44.9",
|
|
30
|
+
"openai": "^6.39.1"
|
|
29
31
|
},
|
|
30
32
|
"devDependencies": {
|
|
31
33
|
"@tailwindcss/cli": "^4.3.0",
|
|
@@ -2,6 +2,14 @@ const { ValidationError, mapSqliteError } = require("../../utils/errors");
|
|
|
2
2
|
const { serializeRows } = require("../../utils/sqliteTypes");
|
|
3
3
|
const { getTableDetail } = require("./introspection");
|
|
4
4
|
|
|
5
|
+
function getSerializedMemoryBytes(value) {
|
|
6
|
+
try {
|
|
7
|
+
return Buffer.byteLength(JSON.stringify(value ?? null), "utf8");
|
|
8
|
+
} catch {
|
|
9
|
+
return 0;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
5
13
|
function getFirstKeyword(statement) {
|
|
6
14
|
const trimmed = statement.trim().replace(/^--.*$/gm, "").trim();
|
|
7
15
|
const match = trimmed.match(/^[A-Za-z]+/);
|
|
@@ -427,6 +435,8 @@ class SqlExecutor {
|
|
|
427
435
|
affectedRowCount: totalChanges,
|
|
428
436
|
resultKind: lastResultSet ? "resultSet" : results.at(-1)?.kind ?? "unknown",
|
|
429
437
|
};
|
|
438
|
+
payload.memoryBytes = getSerializedMemoryBytes(payload);
|
|
439
|
+
|
|
430
440
|
let historyId = null;
|
|
431
441
|
|
|
432
442
|
if (options.persistHistory !== false) {
|