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
@@ -0,0 +1,169 @@
1
+ function stripLineComments(sql = "") {
2
+ return String(sql)
3
+ .split(/\r?\n/)
4
+ .map((line) => line.replace(/--.*$/g, ""))
5
+ .join("\n");
6
+ }
7
+
8
+ function stripBlockComments(sql = "") {
9
+ return String(sql).replace(/\/\*[\s\S]*?\*\//g, " ");
10
+ }
11
+
12
+ function compactWhitespace(value = "") {
13
+ return String(value).replace(/\s+/g, " ").trim();
14
+ }
15
+
16
+ function truncateText(value = "", maxLength = 80) {
17
+ const text = String(value ?? "").trim();
18
+
19
+ if (text.length <= maxLength) {
20
+ return text;
21
+ }
22
+
23
+ return `${text.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
24
+ }
25
+
26
+ function normalizeSql(sql = "") {
27
+ const withoutComments = stripBlockComments(stripLineComments(sql));
28
+ const compact = compactWhitespace(withoutComments).replace(/;+\s*$/g, "").trim();
29
+
30
+ return compact.toLowerCase();
31
+ }
32
+
33
+ function getLeadingKeywords(sql = "", limit = 3) {
34
+ const stripped = stripBlockComments(stripLineComments(sql));
35
+ const matches = stripped.match(/[A-Za-z]+/g) ?? [];
36
+
37
+ return matches.slice(0, limit).map((token) => token.toLowerCase());
38
+ }
39
+
40
+ function resolveEffectiveKeyword(sql = "") {
41
+ const [firstKeyword, secondKeyword, thirdKeyword] = getLeadingKeywords(sql, 3);
42
+
43
+ if (firstKeyword === "with") {
44
+ return secondKeyword === "recursive" ? thirdKeyword ?? "with" : secondKeyword ?? "with";
45
+ }
46
+
47
+ if (firstKeyword === "explain") {
48
+ return secondKeyword ?? "explain";
49
+ }
50
+
51
+ return firstKeyword ?? "";
52
+ }
53
+
54
+ function detectQueryType(sql = "") {
55
+ const keyword = resolveEffectiveKeyword(sql);
56
+
57
+ if (keyword === "select") {
58
+ return "select";
59
+ }
60
+
61
+ if (keyword === "insert") {
62
+ return "insert";
63
+ }
64
+
65
+ if (keyword === "update") {
66
+ return "update";
67
+ }
68
+
69
+ if (keyword === "delete") {
70
+ return "delete";
71
+ }
72
+
73
+ if (keyword === "pragma") {
74
+ return "pragma";
75
+ }
76
+
77
+ if (["create", "alter", "drop", "rename", "truncate"].includes(keyword)) {
78
+ return "ddl";
79
+ }
80
+
81
+ return "other";
82
+ }
83
+
84
+ function isDestructiveQuery(sql = "") {
85
+ const keyword = resolveEffectiveKeyword(sql);
86
+ return ["insert", "update", "delete", "alter", "drop", "replace"].includes(keyword);
87
+ }
88
+
89
+ function normalizeTableName(candidate = "") {
90
+ const trimmed = String(candidate ?? "")
91
+ .trim()
92
+ .replace(/[;,)]*$/g, "");
93
+
94
+ if (!trimmed || trimmed.startsWith("(")) {
95
+ return null;
96
+ }
97
+
98
+ const segments = trimmed.split(".").filter(Boolean);
99
+ const finalSegment = segments.at(-1) ?? trimmed;
100
+ const normalized = finalSegment.replace(/^["`[]|["`\]]$/g, "");
101
+
102
+ if (!normalized || ["select", "with", "pragma"].includes(normalized.toLowerCase())) {
103
+ return null;
104
+ }
105
+
106
+ return normalized;
107
+ }
108
+
109
+ function collectTableMatches(sql = "", patterns = []) {
110
+ const text = stripBlockComments(stripLineComments(sql));
111
+ const tableNames = [];
112
+
113
+ patterns.forEach((pattern) => {
114
+ let match;
115
+
116
+ while ((match = pattern.exec(text))) {
117
+ const tableName = normalizeTableName(match[1]);
118
+
119
+ if (tableName) {
120
+ tableNames.push(tableName);
121
+ }
122
+ }
123
+ });
124
+
125
+ return Array.from(new Set(tableNames));
126
+ }
127
+
128
+ function detectTables(sql = "") {
129
+ return collectTableMatches(sql, [
130
+ /\bfrom\s+([^\s,;()]+)/gi,
131
+ /\bjoin\s+([^\s,;()]+)/gi,
132
+ /\bupdate(?:\s+or\s+\w+)?\s+([^\s,;()]+)/gi,
133
+ /\binto\s+([^\s,;()]+)/gi,
134
+ /\btable\s+(?:if\s+(?:not\s+)?exists\s+)?([^\s,;()]+)/gi,
135
+ ]);
136
+ }
137
+
138
+ function buildAutoTitle(rawSql = "", { queryType = "other", tablesDetected = [] } = {}) {
139
+ const firstMeaningfulLine = String(rawSql)
140
+ .split(/\r?\n/)
141
+ .map((line) => line.replace(/--.*$/g, "").trim())
142
+ .find(Boolean);
143
+
144
+ if (firstMeaningfulLine) {
145
+ return truncateText(firstMeaningfulLine.replace(/;+\s*$/g, ""), 80);
146
+ }
147
+
148
+ const primaryTable = tablesDetected[0] ? ` on ${tablesDetected[0]}` : "";
149
+
150
+ if (queryType && queryType !== "other") {
151
+ return `${queryType.toUpperCase()}${primaryTable}`;
152
+ }
153
+
154
+ return primaryTable ? `Query${primaryTable}` : "SQL query";
155
+ }
156
+
157
+ function buildSqlPreview(rawSql = "", maxLength = 140) {
158
+ const compact = compactWhitespace(stripLineComments(rawSql));
159
+ return truncateText(compact, maxLength) || "SQL query";
160
+ }
161
+
162
+ module.exports = {
163
+ buildAutoTitle,
164
+ buildSqlPreview,
165
+ detectQueryType,
166
+ detectTables,
167
+ isDestructiveQuery,
168
+ normalizeSql,
169
+ };
@@ -6,6 +6,13 @@ const APP_NAME = "sqlite-hub";
6
6
  const APP_STATE_DB_FILENAME = "sqlite-hub-state.db";
7
7
  const LEGACY_STATE_FILENAME = "app-state.json";
8
8
 
9
+ function resolvePackagedDataDirectories(packageRoot) {
10
+ return [
11
+ path.resolve(packageRoot, "server", "data"),
12
+ path.resolve(packageRoot, "data"),
13
+ ].filter((candidatePath, index, candidates) => candidates.indexOf(candidatePath) === index);
14
+ }
15
+
9
16
  function resolveAppStateDirectory() {
10
17
  const homeDirectory = os.homedir();
11
18
 
@@ -28,7 +35,7 @@ function resolveAppStateDirectory() {
28
35
  }
29
36
 
30
37
  function resolvePackagedDataDirectory(packageRoot) {
31
- return path.resolve(packageRoot, "data");
38
+ return resolvePackagedDataDirectories(packageRoot)[0];
32
39
  }
33
40
 
34
41
  function resolvePackagedAppStateDbPath(packageRoot) {
@@ -36,7 +43,11 @@ function resolvePackagedAppStateDbPath(packageRoot) {
36
43
  }
37
44
 
38
45
  function resolvePackagedLegacyStatePath(packageRoot) {
39
- return path.join(resolvePackagedDataDirectory(packageRoot), LEGACY_STATE_FILENAME);
46
+ const candidates = resolvePackagedDataDirectories(packageRoot).map((directoryPath) =>
47
+ path.join(directoryPath, LEGACY_STATE_FILENAME)
48
+ );
49
+
50
+ return candidates.find((candidatePath) => fs.existsSync(candidatePath)) ?? candidates[0];
40
51
  }
41
52
 
42
53
  function resolveHomebrewCellarInfo(packageRoot) {
@@ -83,23 +94,34 @@ function collectHomebrewLegacyStateDbPaths(packageRoot) {
83
94
  return fs
84
95
  .readdirSync(cellarInfo.cellarRoot, { withFileTypes: true })
85
96
  .filter((entry) => entry.isDirectory() && entry.name !== cellarInfo.currentVersion)
86
- .map((entry) => {
87
- const candidatePath = path.join(
88
- cellarInfo.cellarRoot,
89
- entry.name,
90
- "libexec",
91
- "lib",
92
- "node_modules",
93
- APP_NAME,
94
- "data",
95
- APP_STATE_DB_FILENAME
96
- );
97
-
98
- return {
97
+ .flatMap((entry) =>
98
+ [
99
+ path.join(
100
+ cellarInfo.cellarRoot,
101
+ entry.name,
102
+ "libexec",
103
+ "lib",
104
+ "node_modules",
105
+ APP_NAME,
106
+ "server",
107
+ "data",
108
+ APP_STATE_DB_FILENAME
109
+ ),
110
+ path.join(
111
+ cellarInfo.cellarRoot,
112
+ entry.name,
113
+ "libexec",
114
+ "lib",
115
+ "node_modules",
116
+ APP_NAME,
117
+ "data",
118
+ APP_STATE_DB_FILENAME
119
+ ),
120
+ ].map((candidatePath) => ({
99
121
  path: candidatePath,
100
122
  mtimeMs: safeStatMtimeMs(candidatePath),
101
- };
102
- })
123
+ }))
124
+ )
103
125
  .filter((candidate) => candidate.mtimeMs >= 0)
104
126
  .sort((left, right) => right.mtimeMs - left.mtimeMs)
105
127
  .map((candidate) => candidate.path);
@@ -107,7 +129,9 @@ function collectHomebrewLegacyStateDbPaths(packageRoot) {
107
129
 
108
130
  function collectLegacyDatabasePaths(packageRoot) {
109
131
  return [
110
- resolvePackagedAppStateDbPath(packageRoot),
132
+ ...resolvePackagedDataDirectories(packageRoot).map((directoryPath) =>
133
+ path.join(directoryPath, APP_STATE_DB_FILENAME)
134
+ ),
111
135
  ...collectHomebrewLegacyStateDbPaths(packageRoot),
112
136
  ].filter((candidatePath, index, candidates) => candidates.indexOf(candidatePath) === index);
113
137
  }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -3,8 +3,8 @@ import { escapeHtml } from "../utils/format.js";
3
3
  export function renderBottomTabs(activeTab, counts = {}) {
4
4
  const tabs = [
5
5
  { key: "results", label: "results", meta: counts.resultRows ?? 0 },
6
- { key: "messages", label: "messages", meta: counts.messages ?? 0 },
7
6
  { key: "performance", label: "performance", meta: counts.statementCount ?? 0 },
7
+ { key: "messages", label: "messages", meta: counts.messages ?? 0 },
8
8
  ];
9
9
 
10
10
  return `
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes