sqlite-hub 0.1.3

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 (76) hide show
  1. package/.npmingnore +4 -0
  2. package/README.md +46 -0
  3. package/assets/images/logo.webp +0 -0
  4. package/assets/images/logo_extrasmall.webp +0 -0
  5. package/assets/images/logo_raw.png +0 -0
  6. package/assets/images/logo_small.webp +0 -0
  7. package/assets/mockups/connections.png +0 -0
  8. package/assets/mockups/data.png +0 -0
  9. package/assets/mockups/data_edit.png +0 -0
  10. package/assets/mockups/home.png +0 -0
  11. package/assets/mockups/overview.png +0 -0
  12. package/assets/mockups/sql_editor.png +0 -0
  13. package/assets/mockups/structure.png +0 -0
  14. package/bin/sqlite-hub.js +116 -0
  15. package/changelog.md +3 -0
  16. package/data/.gitkeep +0 -0
  17. package/index.html +100 -0
  18. package/js/api.js +193 -0
  19. package/js/app.js +520 -0
  20. package/js/components/actionBar.js +8 -0
  21. package/js/components/appShell.js +17 -0
  22. package/js/components/badges.js +5 -0
  23. package/js/components/bottomTabs.js +37 -0
  24. package/js/components/connectionCard.js +106 -0
  25. package/js/components/dataGrid.js +47 -0
  26. package/js/components/emptyState.js +159 -0
  27. package/js/components/metricCard.js +32 -0
  28. package/js/components/modal.js +317 -0
  29. package/js/components/pageHeader.js +33 -0
  30. package/js/components/queryEditor.js +121 -0
  31. package/js/components/queryResults.js +107 -0
  32. package/js/components/rowEditorPanel.js +164 -0
  33. package/js/components/sidebar.js +57 -0
  34. package/js/components/statusBar.js +39 -0
  35. package/js/components/toast.js +39 -0
  36. package/js/components/topNav.js +27 -0
  37. package/js/router.js +66 -0
  38. package/js/store.js +1092 -0
  39. package/js/utils/format.js +179 -0
  40. package/js/views/connections.js +133 -0
  41. package/js/views/data.js +400 -0
  42. package/js/views/editor.js +259 -0
  43. package/js/views/landing.js +11 -0
  44. package/js/views/overview.js +220 -0
  45. package/js/views/settings.js +109 -0
  46. package/js/views/structure.js +242 -0
  47. package/package.json +18 -0
  48. package/publish_brew.sh +444 -0
  49. package/publish_npm.sh +241 -0
  50. package/server/routes/connections.js +146 -0
  51. package/server/routes/data.js +59 -0
  52. package/server/routes/export.js +25 -0
  53. package/server/routes/overview.js +39 -0
  54. package/server/routes/settings.js +50 -0
  55. package/server/routes/sql.js +50 -0
  56. package/server/routes/structure.js +38 -0
  57. package/server/server.js +136 -0
  58. package/server/services/sqlite/connectionManager.js +306 -0
  59. package/server/services/sqlite/dataBrowserService.js +255 -0
  60. package/server/services/sqlite/exportService.js +34 -0
  61. package/server/services/sqlite/importService.js +111 -0
  62. package/server/services/sqlite/introspection.js +302 -0
  63. package/server/services/sqlite/overviewService.js +109 -0
  64. package/server/services/sqlite/sqlExecutor.js +434 -0
  65. package/server/services/sqlite/structureService.js +60 -0
  66. package/server/services/storage/appStateStore.js +530 -0
  67. package/server/utils/csv.js +34 -0
  68. package/server/utils/errors.js +175 -0
  69. package/server/utils/fileValidation.js +135 -0
  70. package/server/utils/identifier.js +38 -0
  71. package/server/utils/sqliteTypes.js +112 -0
  72. package/styles/base.css +176 -0
  73. package/styles/components.css +323 -0
  74. package/styles/layout.css +101 -0
  75. package/styles/tokens.css +49 -0
  76. package/styles/views.css +84 -0
package/.npmingnore ADDED
@@ -0,0 +1,4 @@
1
+ publish_brew.sh
2
+ pbulish_npm.sh
3
+ changelog.md
4
+ gitignore.md
package/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # sqlite-hub
2
+
3
+ A cyberpunk inspired management app for sqlite
4
+
5
+ ![](/assets/mockups/home.png)
6
+
7
+ ## Install
8
+
9
+ ### Homebrew
10
+
11
+ ```bash
12
+ brew install oliverjessner/tap/sqlite-hub
13
+ ```
14
+
15
+ Or tap first and then install:
16
+
17
+ ```bash
18
+ brew tap oliverjessner/tap
19
+ brew install sqlite-hub
20
+ ```
21
+
22
+ ### NPM
23
+
24
+ ```bash
25
+ npm install sqlite-hub
26
+ ```
27
+
28
+ ## Run
29
+
30
+ Start the app and open it automatically in the default browser:
31
+
32
+ ```bash
33
+ npm start
34
+ ```
35
+
36
+ Use a custom port via CLI. If omitted, `4173` is used:
37
+
38
+ ```bash
39
+ npm start -- --port:1203
40
+ ```
41
+
42
+ After linking or installing globally, the binary can also be called directly:
43
+
44
+ ```bash
45
+ sqlite-hub --port:1203
46
+ ```
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { spawn } = require("node:child_process");
4
+
5
+ const DEFAULT_PORT = 4173;
6
+
7
+ function printHelp() {
8
+ console.log(`SQLite Hub CLI
9
+
10
+ Usage:
11
+ sqlite-hub [--port:4173]
12
+
13
+ Options:
14
+ --port:PORT Start the server on a custom port.
15
+ --help Show this help text.
16
+ `);
17
+ }
18
+
19
+ function parsePort(rawValue) {
20
+ const port = Number(rawValue);
21
+
22
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
23
+ throw new Error(`Invalid port: ${rawValue}`);
24
+ }
25
+
26
+ return port;
27
+ }
28
+
29
+ function parseCliArguments(argv) {
30
+ let port;
31
+
32
+ for (let index = 0; index < argv.length; index += 1) {
33
+ const argument = argv[index];
34
+
35
+ if (argument === "--help" || argument === "-h") {
36
+ return { help: true };
37
+ }
38
+
39
+ if (argument.startsWith("--port:")) {
40
+ port = parsePort(argument.slice("--port:".length));
41
+ continue;
42
+ }
43
+
44
+ if (argument.startsWith("--port=")) {
45
+ port = parsePort(argument.slice("--port=".length));
46
+ continue;
47
+ }
48
+
49
+ if (argument === "--port") {
50
+ port = parsePort(argv[index + 1]);
51
+ index += 1;
52
+ continue;
53
+ }
54
+
55
+ throw new Error(`Unknown argument: ${argument}`);
56
+ }
57
+
58
+ return { help: false, port };
59
+ }
60
+
61
+ function openInDefaultBrowser(url) {
62
+ const openers = {
63
+ darwin: {
64
+ command: "open",
65
+ args: [url],
66
+ },
67
+ win32: {
68
+ command: "cmd",
69
+ args: ["/c", "start", "", url],
70
+ options: { windowsHide: true },
71
+ },
72
+ default: {
73
+ command: "xdg-open",
74
+ args: [url],
75
+ },
76
+ };
77
+
78
+ const opener = openers[process.platform] || openers.default;
79
+ const child = spawn(opener.command, opener.args, {
80
+ detached: true,
81
+ stdio: "ignore",
82
+ ...opener.options,
83
+ });
84
+
85
+ child.on("error", (error) => {
86
+ console.warn(`Could not open the browser automatically: ${error.message}`);
87
+ });
88
+
89
+ child.unref();
90
+ }
91
+
92
+ async function main() {
93
+ const { help, port = DEFAULT_PORT } = parseCliArguments(process.argv.slice(2));
94
+
95
+ if (help) {
96
+ printHelp();
97
+ return;
98
+ }
99
+
100
+ const { startServer } = require("../server/server");
101
+ const { url } = await startServer({ port });
102
+ openInDefaultBrowser(url);
103
+ }
104
+
105
+ if (require.main === module) {
106
+ main().catch((error) => {
107
+ console.error(error.message);
108
+ process.exit(1);
109
+ });
110
+ }
111
+
112
+ module.exports = {
113
+ main,
114
+ openInDefaultBrowser,
115
+ parseCliArguments,
116
+ };
package/changelog.md ADDED
@@ -0,0 +1,3 @@
1
+ # v0.1.3
2
+
3
+ - edit in sql editro
package/data/.gitkeep ADDED
File without changes
package/index.html ADDED
@@ -0,0 +1,100 @@
1
+ <!DOCTYPE html>
2
+ <html class="dark" lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta content="width=device-width, initial-scale=1.0" name="viewport" />
6
+ <title>SQLite Hub</title>
7
+ <link href="/assets/images/logo_raw.png" rel="icon" type="image/png" />
8
+ <link href="/assets/images/logo_raw.png" rel="shortcut icon" type="image/png" />
9
+ <script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
10
+ <link
11
+ href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700;800;900&family=Inter:wght@300;400;500;600;700;800&family=Roboto+Mono:wght@400;500;700&display=swap"
12
+ rel="stylesheet"
13
+ />
14
+ <link
15
+ href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap"
16
+ rel="stylesheet"
17
+ />
18
+ <script id="tailwind-config">
19
+ tailwind.config = {
20
+ darkMode: "class",
21
+ theme: {
22
+ extend: {
23
+ colors: {
24
+ "on-secondary-fixed": "#1c1b1b",
25
+ background: "#131313",
26
+ "inverse-primary": "#6a5f00",
27
+ "primary-fixed": "#fde403",
28
+ tertiary: "#fbfffe",
29
+ outline: "#979177",
30
+ "on-secondary": "#313030",
31
+ "error-container": "#93000a",
32
+ "on-primary-fixed-variant": "#504700",
33
+ "on-tertiary-container": "#006f72",
34
+ "on-tertiary-fixed-variant": "#004f51",
35
+ "secondary-fixed-dim": "#c8c6c5",
36
+ "tertiary-fixed-dim": "#00dce1",
37
+ "on-background": "#e5e2e1",
38
+ "surface-tint": "#dec800",
39
+ "primary-fixed-dim": "#dec800",
40
+ "on-surface": "#e5e2e1",
41
+ "surface-container-low": "#1c1b1b",
42
+ "on-tertiary": "#003738",
43
+ "surface-container": "#201f1f",
44
+ "on-surface-variant": "#cdc7ab",
45
+ "on-primary-container": "#706400",
46
+ "on-error": "#690005",
47
+ "surface-container-high": "#2a2a2a",
48
+ "surface-container-highest": "#353534",
49
+ error: "#ffb4ab",
50
+ primary: "#fffeff",
51
+ "outline-variant": "#4b4732",
52
+ "on-error-container": "#ffdad6",
53
+ "on-secondary-fixed-variant": "#474746",
54
+ "surface-container-lowest": "#0e0e0e",
55
+ "inverse-surface": "#e5e2e1",
56
+ surface: "#131313",
57
+ "tertiary-container": "#04faff",
58
+ "on-tertiary-fixed": "#002021",
59
+ "tertiary-fixed": "#2dfaff",
60
+ "surface-variant": "#353534",
61
+ "on-primary": "#373100",
62
+ "primary-container": "#fce300",
63
+ "inverse-on-surface": "#313030",
64
+ secondary: "#c8c6c5",
65
+ "on-secondary-container": "#b7b5b4",
66
+ "on-primary-fixed": "#201c00",
67
+ "secondary-container": "#474746",
68
+ "surface-dim": "#131313",
69
+ "surface-bright": "#3a3939",
70
+ "secondary-fixed": "#e5e2e1",
71
+ },
72
+ fontFamily: {
73
+ headline: ["Space Grotesk"],
74
+ body: ["Inter"],
75
+ label: ["Inter"],
76
+ mono: ["Roboto Mono"],
77
+ },
78
+ borderRadius: {
79
+ DEFAULT: "0px",
80
+ lg: "0px",
81
+ xl: "0px",
82
+ full: "9999px",
83
+ },
84
+ },
85
+ },
86
+ };
87
+ </script>
88
+ <link href="styles/tokens.css" rel="stylesheet" />
89
+ <link href="styles/base.css" rel="stylesheet" />
90
+ <link href="styles/layout.css" rel="stylesheet" />
91
+ <link href="styles/components.css" rel="stylesheet" />
92
+ <link href="styles/views.css" rel="stylesheet" />
93
+ </head>
94
+ <body
95
+ class="bg-background text-on-surface font-body selection:bg-primary-container selection:text-on-primary overflow-hidden"
96
+ >
97
+ <div id="app"></div>
98
+ <script src="js/app.js" type="module"></script>
99
+ </body>
100
+ </html>
package/js/api.js ADDED
@@ -0,0 +1,193 @@
1
+ async function parseResponse(response) {
2
+ const contentType = response.headers.get("content-type") ?? "";
3
+ const isJson = contentType.includes("application/json");
4
+ const payload = isJson ? await response.json() : await response.text();
5
+
6
+ if (!response.ok || !payload?.success) {
7
+ const error = new Error(
8
+ payload?.message || payload?.error?.message || response.statusText || "Request failed."
9
+ );
10
+ error.status = response.status;
11
+ error.code = payload?.error?.code ?? "REQUEST_FAILED";
12
+ error.details = payload?.error?.details ?? null;
13
+ error.sqliteCode = payload?.error?.sqliteCode ?? null;
14
+ error.warnings = payload?.warnings ?? [];
15
+ throw error;
16
+ }
17
+
18
+ return payload;
19
+ }
20
+
21
+ async function request(path, options = {}) {
22
+ const response = await fetch(path, {
23
+ method: options.method ?? "GET",
24
+ headers: {
25
+ ...(options.body ? { "Content-Type": "application/json" } : {}),
26
+ ...(options.headers ?? {}),
27
+ },
28
+ body: options.body ? JSON.stringify(options.body) : undefined,
29
+ });
30
+
31
+ return parseResponse(response);
32
+ }
33
+
34
+ async function download(path, options = {}) {
35
+ const response = await fetch(path, {
36
+ method: options.method ?? "GET",
37
+ headers: options.body ? { "Content-Type": "application/json" } : undefined,
38
+ body: options.body ? JSON.stringify(options.body) : undefined,
39
+ });
40
+
41
+ if (!response.ok) {
42
+ const payload = await parseResponse(response);
43
+ return payload;
44
+ }
45
+
46
+ const blob = await response.blob();
47
+ const disposition = response.headers.get("content-disposition") ?? "";
48
+ const match = disposition.match(/filename="([^"]+)"/i);
49
+ const filename = match?.[1] ?? options.fallbackFilename ?? "export.csv";
50
+ const url = URL.createObjectURL(blob);
51
+ const link = document.createElement("a");
52
+
53
+ link.href = url;
54
+ link.download = filename;
55
+ document.body.appendChild(link);
56
+ link.click();
57
+ link.remove();
58
+ URL.revokeObjectURL(url);
59
+
60
+ return { filename };
61
+ }
62
+
63
+ export function getHealth() {
64
+ return request("/api/health");
65
+ }
66
+
67
+ export function getRecentConnections() {
68
+ return request("/api/connections/recent");
69
+ }
70
+
71
+ export function getActiveConnection() {
72
+ return request("/api/connections/active");
73
+ }
74
+
75
+ export function openConnection(payload) {
76
+ return request("/api/connections/open", {
77
+ method: "POST",
78
+ body: payload,
79
+ });
80
+ }
81
+
82
+ export function createConnection(payload) {
83
+ return request("/api/connections/create", {
84
+ method: "POST",
85
+ body: payload,
86
+ });
87
+ }
88
+
89
+ export function importSql(payload) {
90
+ return request("/api/connections/import-sql", {
91
+ method: "POST",
92
+ body: payload,
93
+ });
94
+ }
95
+
96
+ export function selectActiveConnection(id) {
97
+ return request("/api/connections/select-active", {
98
+ method: "POST",
99
+ body: { id },
100
+ });
101
+ }
102
+
103
+ export function removeRecentConnection(id) {
104
+ return request(`/api/connections/recent/${encodeURIComponent(id)}`, {
105
+ method: "DELETE",
106
+ });
107
+ }
108
+
109
+ export function updateRecentConnection(id, payload) {
110
+ return request(`/api/connections/recent/${encodeURIComponent(id)}`, {
111
+ method: "PATCH",
112
+ body: payload,
113
+ });
114
+ }
115
+
116
+ export function getOverview() {
117
+ return request("/api/db/overview");
118
+ }
119
+
120
+ export function getDbStatus() {
121
+ return request("/api/db/status");
122
+ }
123
+
124
+ export function executeSql(sql) {
125
+ return request("/api/sql/execute", {
126
+ method: "POST",
127
+ body: { sql },
128
+ });
129
+ }
130
+
131
+ export function getSqlHistory() {
132
+ return request("/api/sql/history");
133
+ }
134
+
135
+ export function clearSqlHistory() {
136
+ return request("/api/sql/history", {
137
+ method: "DELETE",
138
+ });
139
+ }
140
+
141
+ export function getStructureOverview() {
142
+ return request("/api/structure");
143
+ }
144
+
145
+ export function getStructureDetail(tableName) {
146
+ return request(`/api/structure/${encodeURIComponent(tableName)}`);
147
+ }
148
+
149
+ export function getDataTables() {
150
+ return request("/api/data");
151
+ }
152
+
153
+ export function getDataTable(tableName, options = {}) {
154
+ const params = new URLSearchParams();
155
+
156
+ if (options.limit !== undefined) {
157
+ params.set("limit", String(options.limit));
158
+ }
159
+
160
+ if (options.offset !== undefined) {
161
+ params.set("offset", String(options.offset));
162
+ }
163
+
164
+ const query = params.toString();
165
+
166
+ return request(`/api/data/${encodeURIComponent(tableName)}${query ? `?${query}` : ""}`);
167
+ }
168
+
169
+ export function updateDataTableRow(tableName, payload) {
170
+ return request(`/api/data/${encodeURIComponent(tableName)}/rows`, {
171
+ method: "PATCH",
172
+ body: payload,
173
+ });
174
+ }
175
+
176
+ export function getSettings() {
177
+ return request("/api/settings");
178
+ }
179
+
180
+ export function patchSettings(settings) {
181
+ return request("/api/settings", {
182
+ method: "PATCH",
183
+ body: settings,
184
+ });
185
+ }
186
+
187
+ export function downloadQueryCsv(sql) {
188
+ return download("/api/export/query.csv", {
189
+ method: "POST",
190
+ body: { sql },
191
+ fallbackFilename: "query-results.csv",
192
+ });
193
+ }