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.
@@ -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 grid-cols-1 gap-4 bg-surface-container-lowest p-6 md:grid-cols-4">
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
- String(metrics.timingMs ?? 0),
58
- )}ms</span>
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.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) {