sqlite-hub 0.17.2 → 1.0.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 (61) hide show
  1. package/README.md +101 -19
  2. package/bin/sqlite-hub.js +83 -401
  3. package/examples/api/queries.js +31 -0
  4. package/examples/api/rows.js +27 -0
  5. package/frontend/assets/mockups/charts_1_bars_1200.webp +0 -0
  6. package/frontend/assets/mockups/charts_2_pie_1200.webp +0 -0
  7. package/frontend/assets/mockups/charts_3_scatter_plot_1200.webp +0 -0
  8. package/frontend/assets/mockups/connections_1200.webp +0 -0
  9. package/frontend/assets/mockups/data_1_1200.webp +0 -0
  10. package/frontend/assets/mockups/data_2_row_editor_1200.webp +0 -0
  11. package/frontend/assets/mockups/documents_1200.webp +0 -0
  12. package/frontend/assets/mockups/media_tagging_1_setup_1200.webp +0 -0
  13. package/frontend/assets/mockups/media_tagging_2_tagging_queue_1200.webp +0 -0
  14. package/frontend/assets/mockups/media_tagging_3_media_viewer_1200.webp +0 -0
  15. package/frontend/assets/mockups/overview_1200.webp +0 -0
  16. package/frontend/assets/mockups/settings_1200.webp +0 -0
  17. package/frontend/assets/mockups/sql_editor_1_1200.webp +0 -0
  18. package/frontend/assets/mockups/sql_editor_2_query_details_1200.webp +0 -0
  19. package/frontend/assets/mockups/sql_editor_3_export_column_1200.webp +0 -0
  20. package/frontend/assets/mockups/sql_editor_4_export_column_as_markdown_1200.webp +0 -0
  21. package/frontend/assets/mockups/sql_editor_5_export_query_result_1200.webp +0 -0
  22. package/frontend/assets/mockups/structure_1_1200.webp +0 -0
  23. package/frontend/assets/mockups/structure_2_inspector_1200.webp +0 -0
  24. package/frontend/assets/mockups/table_designer_1_1200.webp +0 -0
  25. package/frontend/assets/mockups/table_designer_2_checks_1200.webp +0 -0
  26. package/frontend/js/api.js +19 -0
  27. package/frontend/js/app.js +64 -0
  28. package/frontend/js/components/formControls.js +38 -0
  29. package/frontend/js/components/modal.js +60 -20
  30. package/frontend/js/store.js +115 -0
  31. package/frontend/js/views/settings.js +141 -5
  32. package/frontend/styles/tailwind.generated.css +2 -2
  33. package/package.json +6 -6
  34. package/server/middleware/apiTokenAuth.js +30 -0
  35. package/server/routes/connections.js +17 -0
  36. package/server/routes/externalApi.js +222 -0
  37. package/server/routes/settings.js +64 -4
  38. package/server/server.js +22 -1
  39. package/server/services/apiTokenService.js +101 -0
  40. package/server/services/databaseCommandService.js +399 -0
  41. package/server/services/nativeFileDialogService.js +93 -1
  42. package/server/services/storage/appStateStore.js +113 -0
  43. package/server/utils/errors.js +7 -0
  44. package/tests/api-token-auth.test.js +127 -0
  45. package/tests/cli-service-delegation.test.js +43 -0
  46. package/tests/connections-file-dialog-route.test.js +43 -0
  47. package/tests/copy-column-modal.test.js +34 -0
  48. package/tests/database-command-service.test.js +102 -0
  49. package/tests/form-controls.test.js +34 -0
  50. package/tests/native-file-dialog.test.js +27 -0
  51. package/tests/settings-api-tokens-route.test.js +76 -0
  52. package/tests/settings-view.test.js +47 -0
  53. package/frontend/assets/mockups/connections.png +0 -0
  54. package/frontend/assets/mockups/data.png +0 -0
  55. package/frontend/assets/mockups/data_row_editor.png +0 -0
  56. package/frontend/assets/mockups/home.png +0 -0
  57. package/frontend/assets/mockups/sql_editor.png +0 -0
  58. package/frontend/assets/mockups/sql_editor_croped.png +0 -0
  59. package/frontend/assets/mockups/sql_editor_querydetail.png +0 -0
  60. package/frontend/assets/mockups/structure.png +0 -0
  61. package/frontend/assets/mockups/structure_inspector.png +0 -0
@@ -3,6 +3,8 @@ const test = require("node:test");
3
3
  const {
4
4
  NativeFileDialogService,
5
5
  buildDialogAttempts,
6
+ buildOpenDialogAttempts,
7
+ normalizeOpenedDatabasePath,
6
8
  normalizeSelectedDatabasePath,
7
9
  } = require("../server/services/nativeFileDialogService");
8
10
 
@@ -24,6 +26,21 @@ test("native database dialog builds platform-specific save commands", () => {
24
26
  assert.deepEqual(linuxAttempts.map((attempt) => attempt.command), ["zenity", "kdialog"]);
25
27
  });
26
28
 
29
+ test("native database dialog builds platform-specific open commands", () => {
30
+ const macAttempt = buildOpenDialogAttempts({ platform: "darwin", homeDirectory: "/Users/test" })[0];
31
+ const windowsAttempt = buildOpenDialogAttempts({ platform: "win32", homeDirectory: "C:\\Users\\test" })[0];
32
+ const linuxAttempts = buildOpenDialogAttempts({ platform: "linux", homeDirectory: "/home/test" });
33
+
34
+ assert.match(macAttempt.args.join(" "), /choose file with prompt/);
35
+ assert.match(windowsAttempt.args.at(-1), /OpenFileDialog/);
36
+ assert.deepEqual(linuxAttempts.map((attempt) => attempt.command), ["zenity", "kdialog"]);
37
+ });
38
+
39
+ test("open database dialog preserves the selected filename", () => {
40
+ assert.equal(normalizeOpenedDatabasePath("/tmp/catalog\n"), "/tmp/catalog");
41
+ assert.equal(normalizeOpenedDatabasePath(""), null);
42
+ });
43
+
27
44
  test("native database dialog returns null when the user cancels", async () => {
28
45
  const service = new NativeFileDialogService({
29
46
  platform: "darwin",
@@ -76,3 +93,13 @@ test("native database dialog falls back from zenity to kdialog", async () => {
76
93
  assert.equal(await service.chooseCreateDatabasePath(), "/home/test/catalog.sqlite3");
77
94
  assert.deepEqual(commands, ["zenity", "kdialog"]);
78
95
  });
96
+
97
+ test("native open database dialog returns the selected existing path", async () => {
98
+ const service = new NativeFileDialogService({
99
+ platform: "linux",
100
+ homeDirectory: "/home/test",
101
+ executeFile: async () => ({ stdout: "/home/test/catalog.db\n" }),
102
+ });
103
+
104
+ assert.equal(await service.chooseOpenDatabasePath(), "/home/test/catalog.db");
105
+ });
@@ -0,0 +1,76 @@
1
+ const assert = require("node:assert/strict");
2
+ const express = require("express");
3
+ const fs = require("node:fs");
4
+ const os = require("node:os");
5
+ const path = require("node:path");
6
+ const test = require("node:test");
7
+
8
+ const { createSettingsRouter } = require("../server/routes/settings");
9
+ const { ApiTokenService } = require("../server/services/apiTokenService");
10
+ const { AppStateStore } = require("../server/services/storage/appStateStore");
11
+ const { errorMiddleware } = require("../server/utils/errors");
12
+
13
+ test("settings routes create and delete tokens for the active database", async (t) => {
14
+ const directory = fs.mkdtempSync(path.join(os.tmpdir(), "sqlite-hub-settings-token-"));
15
+ const store = new AppStateStore(path.join(directory, "state.db"));
16
+ const connection = {
17
+ id: "db-active",
18
+ label: "Active Database",
19
+ path: path.join(directory, "active.db"),
20
+ lastOpenedAt: new Date().toISOString(),
21
+ lastModifiedAt: new Date().toISOString(),
22
+ sizeBytes: 0,
23
+ readOnly: false,
24
+ logoPath: null,
25
+ };
26
+
27
+ store.upsertRecentConnection(connection);
28
+
29
+ const tokenService = new ApiTokenService({ appStateStore: store });
30
+ const connectionManager = {
31
+ getActiveConnection: () => connection,
32
+ };
33
+ const app = express();
34
+
35
+ app.use(express.json());
36
+ app.use(
37
+ "/api/settings",
38
+ createSettingsRouter({ appStateStore: store, connectionManager, tokenService })
39
+ );
40
+ app.use(errorMiddleware);
41
+
42
+ const server = await new Promise((resolve) => {
43
+ const listener = app.listen(0, "127.0.0.1", () => resolve(listener));
44
+ });
45
+
46
+ t.after(async () => {
47
+ await new Promise((resolve, reject) => {
48
+ server.close((error) => (error ? reject(error) : resolve()));
49
+ });
50
+ store.db.close();
51
+ fs.rmSync(directory, { recursive: true, force: true });
52
+ });
53
+
54
+ const baseUrl = `http://127.0.0.1:${server.address().port}/api/settings`;
55
+ const createResponse = await fetch(`${baseUrl}/api-tokens`, {
56
+ method: "POST",
57
+ headers: { "Content-Type": "application/json" },
58
+ body: JSON.stringify({ name: "Settings token" }),
59
+ });
60
+ const created = await createResponse.json();
61
+
62
+ assert.equal(createResponse.status, 201);
63
+ assert.equal(created.data.databaseKey, connection.id);
64
+ assert.match(created.data.token, /^shub_/);
65
+ assert.equal(created.metadata.apiTokens.length, 1);
66
+ assert.equal(created.metadata.apiTokens[0].token, undefined);
67
+
68
+ const deleteResponse = await fetch(`${baseUrl}/api-tokens/${created.data.id}`, {
69
+ method: "DELETE",
70
+ });
71
+ const deleted = await deleteResponse.json();
72
+
73
+ assert.equal(deleteResponse.status, 200);
74
+ assert.deepEqual(deleted.data, { id: created.data.id, deleted: true });
75
+ assert.equal(deleted.metadata.apiTokens.length, 0);
76
+ });
@@ -29,4 +29,51 @@ test("settings view shows SQLite runtime version and custom port command", async
29
29
  assert.match(rendered.main, /SQLite_Runtime/);
30
30
  assert.match(rendered.main, /3\.53\.1/);
31
31
  assert.match(rendered.main, /sqlite-hub --port:PORT/);
32
+ assert.match(rendered.main, /data-section="information"/);
33
+ assert.match(rendered.main, /data-section="api-tokens"/);
34
+ assert.doesNotMatch(rendered.main, /data-form="create-api-token"/);
35
+ });
36
+
37
+ test("settings view scopes API token controls to the active database", async () => {
38
+ const { renderSettingsView } = await loadSettingsViewModule();
39
+ const rendered = renderSettingsView({
40
+ settings: {
41
+ loading: false,
42
+ error: null,
43
+ appVersion: "1.2.3",
44
+ sqliteVersion: "3.53.1",
45
+ section: "api-tokens",
46
+ tokenDatabase: { id: "db-one", label: "Database One" },
47
+ apiTokens: [
48
+ {
49
+ id: "token-one",
50
+ name: "Automation",
51
+ tokenPrefix: "shub_example",
52
+ createdAt: "2026-06-15T10:00:00.000Z",
53
+ },
54
+ ],
55
+ createdApiToken: null,
56
+ tokenSaving: false,
57
+ },
58
+ });
59
+
60
+ assert.match(rendered.main, /API Tokens/);
61
+ assert.match(rendered.main, /Security \/\/ Database API access/);
62
+ assert.match(rendered.main, /Database One/);
63
+ assert.match(rendered.main, /Database ID/);
64
+ assert.match(rendered.main, /data-database-id/);
65
+ assert.match(rendered.main, /data-action="copy-database-id"/);
66
+ assert.match(rendered.main, /Copy ID/);
67
+ assert.match(rendered.main, /sm:grid-cols-\[minmax\(0,1fr\)_9rem\]/);
68
+ assert.equal((rendered.main.match(/w-full self-center justify-center/g) ?? []).length, 2);
69
+ assert.match(rendered.main, /shub_example\.\.\./);
70
+ assert.match(rendered.main, /data-api-token-name/);
71
+ assert.match(rendered.main, /data-form="create-api-token"/);
72
+ assert.match(rendered.main, /name="name"/);
73
+ assert.match(rendered.main, /type="submit"/);
74
+ assert.match(rendered.main, /data-action="open-delete-api-token-modal"/);
75
+ assert.match(rendered.main, /bg-surface-container-lowest/);
76
+ assert.doesNotMatch(rendered.main, /sqlite-hub --port:PORT/);
77
+ assert.doesNotMatch(rendered.main, /Open Github/);
78
+ assert.doesNotMatch(rendered.main, /token_hash/);
32
79
  });
Binary file
Binary file