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
@@ -0,0 +1,146 @@
1
+ const express = require("express");
2
+ const { route, successResponse } = require("../utils/errors");
3
+
4
+ function createConnectionsRouter({ connectionManager, importService }) {
5
+ const router = express.Router();
6
+
7
+ router.post(
8
+ "/open",
9
+ route((req, res) => {
10
+ const connection = connectionManager.openConnection({
11
+ filePath: req.body.path,
12
+ label: req.body.label,
13
+ readOnly: Boolean(req.body.readOnly),
14
+ makeActive: true,
15
+ });
16
+
17
+ res.json(
18
+ successResponse({
19
+ message: "SQLite database opened successfully.",
20
+ data: connection,
21
+ readOnly: connection.readOnly,
22
+ })
23
+ );
24
+ })
25
+ );
26
+
27
+ router.post(
28
+ "/create",
29
+ route((req, res) => {
30
+ const connection = connectionManager.createConnection({
31
+ filePath: req.body.path,
32
+ label: req.body.label,
33
+ });
34
+
35
+ res.json(
36
+ successResponse({
37
+ message: "SQLite database created successfully.",
38
+ data: connection,
39
+ readOnly: connection.readOnly,
40
+ })
41
+ );
42
+ })
43
+ );
44
+
45
+ router.post(
46
+ "/import-sql",
47
+ route((req, res) => {
48
+ const result = importService.importSql({
49
+ sqlFilePath: req.body.sqlFilePath,
50
+ targetPath: req.body.targetPath,
51
+ targetConnectionId: req.body.targetConnectionId,
52
+ createNew: Boolean(req.body.createNew),
53
+ label: req.body.label,
54
+ });
55
+
56
+ res.json(
57
+ successResponse({
58
+ message: "SQL dump imported successfully.",
59
+ data: result,
60
+ metadata: {
61
+ importedInto: result.importedInto.id,
62
+ },
63
+ warnings: result.warnings,
64
+ timingMs: result.timingMs,
65
+ readOnly: result.importedInto.readOnly,
66
+ })
67
+ );
68
+ })
69
+ );
70
+
71
+ router.get(
72
+ "/recent",
73
+ route((req, res) => {
74
+ res.json(
75
+ successResponse({
76
+ data: connectionManager.listRecentConnections(),
77
+ })
78
+ );
79
+ })
80
+ );
81
+
82
+ router.delete(
83
+ "/recent/:id",
84
+ route((req, res) => {
85
+ const recentConnections = connectionManager.removeRecentConnection(req.params.id);
86
+ res.json(
87
+ successResponse({
88
+ message: "Recent connection removed.",
89
+ data: recentConnections,
90
+ })
91
+ );
92
+ })
93
+ );
94
+
95
+ router.patch(
96
+ "/recent/:id",
97
+ route((req, res) => {
98
+ const connection = connectionManager.updateRecentConnection(req.params.id, {
99
+ filePath: req.body.path,
100
+ label: req.body.label,
101
+ readOnly: Boolean(req.body.readOnly),
102
+ });
103
+
104
+ res.json(
105
+ successResponse({
106
+ message: "Recent connection updated.",
107
+ data: connection,
108
+ readOnly: connection.readOnly,
109
+ })
110
+ );
111
+ })
112
+ );
113
+
114
+ router.post(
115
+ "/select-active",
116
+ route((req, res) => {
117
+ const connection = connectionManager.selectActiveConnection(req.body.id);
118
+ res.json(
119
+ successResponse({
120
+ message: "Active SQLite database changed.",
121
+ data: connection,
122
+ readOnly: connection.readOnly,
123
+ })
124
+ );
125
+ })
126
+ );
127
+
128
+ router.get(
129
+ "/active",
130
+ route((req, res) => {
131
+ const active = connectionManager.getActiveConnection();
132
+ res.json(
133
+ successResponse({
134
+ data: active,
135
+ readOnly: active?.readOnly,
136
+ })
137
+ );
138
+ })
139
+ );
140
+
141
+ return router;
142
+ }
143
+
144
+ module.exports = {
145
+ createConnectionsRouter,
146
+ };
@@ -0,0 +1,59 @@
1
+ const express = require("express");
2
+ const { route, successResponse } = require("../utils/errors");
3
+
4
+ function createDataRouter({ dataBrowserService }) {
5
+ const router = express.Router();
6
+
7
+ router.get(
8
+ "/",
9
+ route((req, res) => {
10
+ const tables = dataBrowserService.listTables();
11
+
12
+ res.json(
13
+ successResponse({
14
+ data: {
15
+ tables,
16
+ },
17
+ readOnly: false,
18
+ })
19
+ );
20
+ })
21
+ );
22
+
23
+ router.get(
24
+ "/:tableName",
25
+ route((req, res) => {
26
+ const data = dataBrowserService.getTableData(req.params.tableName, {
27
+ limit: req.query.limit,
28
+ offset: req.query.offset,
29
+ });
30
+
31
+ res.json(
32
+ successResponse({
33
+ data,
34
+ readOnly: data.notSafelyUpdatable,
35
+ })
36
+ );
37
+ })
38
+ );
39
+
40
+ router.patch(
41
+ "/:tableName/rows",
42
+ route((req, res) => {
43
+ const data = dataBrowserService.updateTableRow(req.params.tableName, req.body ?? {});
44
+
45
+ res.json(
46
+ successResponse({
47
+ message: "Table row updated.",
48
+ data,
49
+ })
50
+ );
51
+ })
52
+ );
53
+
54
+ return router;
55
+ }
56
+
57
+ module.exports = {
58
+ createDataRouter,
59
+ };
@@ -0,0 +1,25 @@
1
+ const express = require("express");
2
+ const { route } = require("../utils/errors");
3
+
4
+ function createExportRouter({ exportService }) {
5
+ const router = express.Router();
6
+
7
+ router.post(
8
+ "/query.csv",
9
+ route((req, res) => {
10
+ const result = exportService.exportQuery(req.body.sql);
11
+ res.setHeader("Content-Type", "text/csv; charset=utf-8");
12
+ res.setHeader(
13
+ "Content-Disposition",
14
+ `attachment; filename="${result.filename}"`
15
+ );
16
+ res.send(result.csv);
17
+ })
18
+ );
19
+
20
+ return router;
21
+ }
22
+
23
+ module.exports = {
24
+ createExportRouter,
25
+ };
@@ -0,0 +1,39 @@
1
+ const express = require("express");
2
+ const { route, successResponse } = require("../utils/errors");
3
+
4
+ function createOverviewRouter({ overviewService }) {
5
+ const router = express.Router();
6
+
7
+ router.get(
8
+ "/overview",
9
+ route((req, res) => {
10
+ const data = overviewService.getOverview();
11
+ res.json(
12
+ successResponse({
13
+ data,
14
+ readOnly: data.connection.readOnly,
15
+ warnings: data.warnings,
16
+ })
17
+ );
18
+ })
19
+ );
20
+
21
+ router.get(
22
+ "/status",
23
+ route((req, res) => {
24
+ const data = overviewService.getStatus();
25
+ res.json(
26
+ successResponse({
27
+ data,
28
+ readOnly: data.readOnly,
29
+ })
30
+ );
31
+ })
32
+ );
33
+
34
+ return router;
35
+ }
36
+
37
+ module.exports = {
38
+ createOverviewRouter,
39
+ };
@@ -0,0 +1,50 @@
1
+ const express = require("express");
2
+ const fs = require("node:fs");
3
+ const path = require("node:path");
4
+ const { route, successResponse } = require("../utils/errors");
5
+
6
+ function readAppVersion() {
7
+ const packageJsonPath = path.resolve(__dirname, "..", "..", "package.json");
8
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
9
+ return packageJson.version ?? "0.0.0";
10
+ }
11
+
12
+ function createSettingsRouter({ appStateStore }) {
13
+ const router = express.Router();
14
+
15
+ router.get(
16
+ "/",
17
+ route((req, res) => {
18
+ res.json(
19
+ successResponse({
20
+ data: appStateStore.getSettings(),
21
+ metadata: {
22
+ appVersion: readAppVersion(),
23
+ },
24
+ })
25
+ );
26
+ })
27
+ );
28
+
29
+ router.patch(
30
+ "/",
31
+ route((req, res) => {
32
+ const settings = appStateStore.patchSettings(req.body ?? {});
33
+ res.json(
34
+ successResponse({
35
+ message: "Settings updated.",
36
+ data: settings,
37
+ metadata: {
38
+ appVersion: readAppVersion(),
39
+ },
40
+ })
41
+ );
42
+ })
43
+ );
44
+
45
+ return router;
46
+ }
47
+
48
+ module.exports = {
49
+ createSettingsRouter,
50
+ };
@@ -0,0 +1,50 @@
1
+ const express = require("express");
2
+ const { route, successResponse } = require("../utils/errors");
3
+
4
+ function createSqlRouter({ appStateStore, sqlExecutor }) {
5
+ const router = express.Router();
6
+
7
+ router.post(
8
+ "/execute",
9
+ route((req, res) => {
10
+ const result = sqlExecutor.execute(req.body.sql);
11
+ res.json(
12
+ successResponse({
13
+ message: "SQL executed successfully.",
14
+ data: result,
15
+ timingMs: result.timingMs,
16
+ })
17
+ );
18
+ })
19
+ );
20
+
21
+ router.get(
22
+ "/history",
23
+ route((req, res) => {
24
+ res.json(
25
+ successResponse({
26
+ data: appStateStore.getSqlHistory(),
27
+ })
28
+ );
29
+ })
30
+ );
31
+
32
+ router.delete(
33
+ "/history",
34
+ route((req, res) => {
35
+ appStateStore.clearSqlHistory();
36
+ res.json(
37
+ successResponse({
38
+ message: "SQL history cleared.",
39
+ data: [],
40
+ })
41
+ );
42
+ })
43
+ );
44
+
45
+ return router;
46
+ }
47
+
48
+ module.exports = {
49
+ createSqlRouter,
50
+ };
@@ -0,0 +1,38 @@
1
+ const express = require("express");
2
+ const { route, successResponse } = require("../utils/errors");
3
+
4
+ function createStructureRouter({ structureService }) {
5
+ const router = express.Router();
6
+
7
+ router.get(
8
+ "/",
9
+ route((req, res) => {
10
+ const data = structureService.getStructureOverview();
11
+ res.json(
12
+ successResponse({
13
+ data,
14
+ readOnly: true,
15
+ })
16
+ );
17
+ })
18
+ );
19
+
20
+ router.get(
21
+ "/:tableName",
22
+ route((req, res) => {
23
+ const data = structureService.getTableStructure(req.params.tableName);
24
+ res.json(
25
+ successResponse({
26
+ data,
27
+ readOnly: data.notSafelyUpdatable,
28
+ })
29
+ );
30
+ })
31
+ );
32
+
33
+ return router;
34
+ }
35
+
36
+ module.exports = {
37
+ createStructureRouter,
38
+ };
@@ -0,0 +1,136 @@
1
+ const express = require("express");
2
+ const path = require("node:path");
3
+ const { errorMiddleware } = require("./utils/errors");
4
+ const { AppStateStore } = require("./services/storage/appStateStore");
5
+ const { ConnectionManager } = require("./services/sqlite/connectionManager");
6
+ const { OverviewService } = require("./services/sqlite/overviewService");
7
+ const { SqlExecutor } = require("./services/sqlite/sqlExecutor");
8
+ const { ImportService } = require("./services/sqlite/importService");
9
+ const { ExportService } = require("./services/sqlite/exportService");
10
+ const { StructureService } = require("./services/sqlite/structureService");
11
+ const { DataBrowserService } = require("./services/sqlite/dataBrowserService");
12
+ const { createConnectionsRouter } = require("./routes/connections");
13
+ const { createOverviewRouter } = require("./routes/overview");
14
+ const { createSqlRouter } = require("./routes/sql");
15
+ const { createStructureRouter } = require("./routes/structure");
16
+ const { createDataRouter } = require("./routes/data");
17
+ const { createSettingsRouter } = require("./routes/settings");
18
+ const { createExportRouter } = require("./routes/export");
19
+
20
+ const APP_STATE_DB_PATH = path.resolve(__dirname, "..", "data", "sqlite-hub-state.db");
21
+ const LEGACY_STATE_PATH = path.resolve(__dirname, "..", "data", "app-state.json");
22
+ const DEFAULT_PORT = 4173;
23
+
24
+ const appStateStore = new AppStateStore(APP_STATE_DB_PATH, {
25
+ legacyFilePath: LEGACY_STATE_PATH,
26
+ });
27
+ const connectionManager = new ConnectionManager({ appStateStore });
28
+ const overviewService = new OverviewService({ connectionManager });
29
+ const sqlExecutor = new SqlExecutor({ connectionManager, appStateStore });
30
+ const importService = new ImportService({ connectionManager });
31
+ const exportService = new ExportService({
32
+ appStateStore,
33
+ sqlExecutor,
34
+ });
35
+ const structureService = new StructureService({ connectionManager, appStateStore });
36
+ const dataBrowserService = new DataBrowserService({ connectionManager });
37
+
38
+ connectionManager.initialize();
39
+
40
+ const app = express();
41
+
42
+ app.use(express.json({ limit: "10mb" }));
43
+ app.use(express.urlencoded({ extended: false }));
44
+
45
+ app.get("/api/health", (req, res) => {
46
+ res.json({
47
+ success: true,
48
+ message: "SQLite Hub backend is running.",
49
+ data: {
50
+ connected: Boolean(connectionManager.getActiveConnection()),
51
+ },
52
+ metadata: {},
53
+ warnings: [],
54
+ });
55
+ });
56
+
57
+ app.use(
58
+ "/api/connections",
59
+ createConnectionsRouter({
60
+ connectionManager,
61
+ importService,
62
+ })
63
+ );
64
+ app.use("/api/db", createOverviewRouter({ overviewService }));
65
+ app.use("/api/sql", createSqlRouter({ appStateStore, sqlExecutor }));
66
+ app.use("/api/structure", createStructureRouter({ structureService }));
67
+ app.use("/api/data", createDataRouter({ dataBrowserService }));
68
+ app.use("/api/settings", createSettingsRouter({ appStateStore }));
69
+ app.use("/api/export", createExportRouter({ exportService }));
70
+
71
+ app.get("/favicon.ico", (req, res) => {
72
+ res.status(204).end();
73
+ });
74
+
75
+ app.get("/", (req, res) => {
76
+ res.sendFile(path.resolve(__dirname, "..", "index.html"));
77
+ });
78
+
79
+ app.get("/index.html", (req, res) => {
80
+ res.sendFile(path.resolve(__dirname, "..", "index.html"));
81
+ });
82
+
83
+ app.use("/js", express.static(path.resolve(__dirname, "..", "js")));
84
+ app.use("/styles", express.static(path.resolve(__dirname, "..", "styles")));
85
+ app.use("/assets", express.static(path.resolve(__dirname, "..", "assets")));
86
+ app.use(errorMiddleware);
87
+
88
+ function resolvePort(value = process.env.PORT) {
89
+ if (value === undefined || value === null || value === "") {
90
+ return DEFAULT_PORT;
91
+ }
92
+
93
+ const port = Number(value);
94
+
95
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
96
+ throw new Error(`Invalid port: ${value}`);
97
+ }
98
+
99
+ return port;
100
+ }
101
+
102
+ function startServer({ port } = {}) {
103
+ const resolvedPort = resolvePort(port);
104
+
105
+ return new Promise((resolve, reject) => {
106
+ const server = app.listen(resolvedPort);
107
+
108
+ server.once("error", reject);
109
+ server.once("listening", () => {
110
+ const url = `http://127.0.0.1:${resolvedPort}`;
111
+
112
+ console.log(`SQLite Hub server listening on ${url}`);
113
+ resolve({
114
+ port: resolvedPort,
115
+ server,
116
+ url,
117
+ });
118
+ });
119
+ });
120
+ }
121
+
122
+ if (require.main === module) {
123
+ startServer().catch((error) => {
124
+ console.error(error.message);
125
+ process.exit(1);
126
+ });
127
+ }
128
+
129
+ module.exports = {
130
+ app,
131
+ appStateStore,
132
+ connectionManager,
133
+ DEFAULT_PORT,
134
+ resolvePort,
135
+ startServer,
136
+ };