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.
Files changed (75) hide show
  1. package/README.md +5 -5
  2. package/changelog.md +14 -0
  3. package/{js → frontend/js}/api.js +99 -5
  4. package/{js → frontend/js}/app.js +162 -11
  5. package/{js → frontend/js}/components/connectionCard.js +8 -9
  6. package/frontend/js/components/connectionLogo.js +33 -0
  7. package/{js → frontend/js}/components/dataGrid.js +3 -3
  8. package/{js → frontend/js}/components/emptyState.js +25 -11
  9. package/{js → frontend/js}/components/modal.js +57 -0
  10. package/{js → frontend/js}/components/queryEditor.js +33 -55
  11. package/frontend/js/components/queryHistoryDetail.js +263 -0
  12. package/frontend/js/components/queryHistoryPanel.js +228 -0
  13. package/{js → frontend/js}/components/queryResults.js +32 -46
  14. package/{js → frontend/js}/components/rowEditorPanel.js +73 -14
  15. package/{js → frontend/js}/components/sidebar.js +8 -3
  16. package/{js → frontend/js}/store.js +577 -21
  17. package/{js → frontend/js}/utils/format.js +23 -0
  18. package/{js → frontend/js}/views/data.js +136 -10
  19. package/{js → frontend/js}/views/editor.js +34 -10
  20. package/{js → frontend/js}/views/overview.js +15 -0
  21. package/{js → frontend/js}/views/structure.js +10 -12
  22. package/{styles → frontend/styles}/components.css +106 -0
  23. package/{styles → frontend/styles}/structure-graph.css +5 -10
  24. package/package.json +2 -2
  25. package/{publish_brew.sh → scripts/publish_brew.sh} +2 -2
  26. package/{publish_npm.sh → scripts/publish_npm.sh} +2 -2
  27. package/server/data/db_logos/.gitkeep +0 -0
  28. package/server/routes/connections.js +2 -0
  29. package/server/routes/data.js +2 -0
  30. package/server/routes/export.js +4 -1
  31. package/server/routes/overview.js +12 -0
  32. package/server/routes/sql.js +163 -5
  33. package/server/server.js +8 -6
  34. package/server/services/sqlite/connectionManager.js +68 -33
  35. package/server/services/sqlite/dataBrowserService.js +4 -16
  36. package/server/services/sqlite/exportService.js +4 -16
  37. package/server/services/sqlite/overviewService.js +34 -0
  38. package/server/services/sqlite/sqlExecutor.js +83 -63
  39. package/server/services/sqlite/tableSort.js +63 -0
  40. package/server/services/storage/appStateStore.js +832 -20
  41. package/server/services/storage/queryHistoryUtils.js +169 -0
  42. package/server/utils/appPaths.js +42 -18
  43. package/{assets → frontend/assets}/images/logo.webp +0 -0
  44. package/{assets → frontend/assets}/images/logo_extrasmall.webp +0 -0
  45. package/{assets → frontend/assets}/images/logo_raw.png +0 -0
  46. package/{assets → frontend/assets}/images/logo_small.webp +0 -0
  47. package/{assets → frontend/assets}/mockups/connections.png +0 -0
  48. package/{assets → frontend/assets}/mockups/data.png +0 -0
  49. package/{assets → frontend/assets}/mockups/data_edit.png +0 -0
  50. package/{assets → frontend/assets}/mockups/graph_visualize.png +0 -0
  51. package/{assets → frontend/assets}/mockups/home.png +0 -0
  52. package/{assets → frontend/assets}/mockups/overview.png +0 -0
  53. package/{assets → frontend/assets}/mockups/sql_editor.png +0 -0
  54. package/{assets → frontend/assets}/mockups/structure.png +0 -0
  55. package/{index.html → frontend/index.html} +0 -0
  56. package/{js → frontend/js}/components/actionBar.js +0 -0
  57. package/{js → frontend/js}/components/appShell.js +0 -0
  58. package/{js → frontend/js}/components/badges.js +0 -0
  59. package/{js → frontend/js}/components/bottomTabs.js +1 -1
  60. /package/{js → frontend/js}/components/metricCard.js +0 -0
  61. /package/{js → frontend/js}/components/pageHeader.js +0 -0
  62. /package/{js → frontend/js}/components/statusBar.js +0 -0
  63. /package/{js → frontend/js}/components/structureGraph.js +0 -0
  64. /package/{js → frontend/js}/components/toast.js +0 -0
  65. /package/{js → frontend/js}/components/topNav.js +0 -0
  66. /package/{js → frontend/js}/lib/cytoscapeRuntime.js +0 -0
  67. /package/{js → frontend/js}/router.js +0 -0
  68. /package/{js → frontend/js}/views/connections.js +0 -0
  69. /package/{js → frontend/js}/views/landing.js +0 -0
  70. /package/{js → frontend/js}/views/settings.js +0 -0
  71. /package/{styles → frontend/styles}/base.css +0 -0
  72. /package/{styles → frontend/styles}/layout.css +0 -0
  73. /package/{styles → frontend/styles}/tokens.css +0 -0
  74. /package/{styles → frontend/styles}/views.css +0 -0
  75. /package/{data → server/data}/.gitkeep +0 -0
@@ -1,5 +1,4 @@
1
- const crypto = require("node:crypto");
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
- statements.forEach((statement, index) => {
342
- const prepared = db.prepare(statement);
343
- const keyword = getFirstKeyword(statement);
344
-
345
- if (options.requireReader && !prepared.reader) {
346
- throw new ValidationError(
347
- `Statement ${index + 1} is not a result-set statement and cannot be exported.`
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
- if (prepared.reader) {
352
- const rows = prepared.all();
353
- const columnDefinitions = prepared.columns();
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: "resultSet",
362
- rowCount: serializedRows.length,
363
- columns,
364
- rows: editableResult?.rows ?? serializedRows,
365
- editing: editableResult
366
- ? {
367
- enabled: editableResult.enabled,
368
- tableName: editableResult.tableName ?? null,
369
- reason: editableResult.reason ?? "",
370
- columns: editableResult.columns ?? [],
371
- identityStrategy: editableResult.identityStrategy ?? null,
372
- }
373
- : null,
374
- };
375
- results.push(result);
376
- lastResultSet = result;
377
- return;
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
- const info = prepared.run();
381
- totalChanges += info.changes;
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.addSqlHistory({
411
- id: crypto.randomUUID(),
412
- connectionId: connection.id,
413
- connectionLabel: connection.label,
414
- sql,
415
- statementCount: statements.length,
416
- resultKind: payload.resultKind,
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
+ };