sqlite-hub 0.17.0 → 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.
- package/README.md +101 -19
- package/bin/sqlite-hub.js +83 -401
- package/examples/api/queries.js +31 -0
- package/examples/api/rows.js +27 -0
- package/frontend/assets/mockups/charts_1_bars_1200.webp +0 -0
- package/frontend/assets/mockups/charts_2_pie_1200.webp +0 -0
- package/frontend/assets/mockups/charts_3_scatter_plot_1200.webp +0 -0
- package/frontend/assets/mockups/connections_1200.webp +0 -0
- package/frontend/assets/mockups/data_1_1200.webp +0 -0
- package/frontend/assets/mockups/data_2_row_editor_1200.webp +0 -0
- package/frontend/assets/mockups/documents_1200.webp +0 -0
- package/frontend/assets/mockups/media_tagging_1_setup_1200.webp +0 -0
- package/frontend/assets/mockups/media_tagging_2_tagging_queue_1200.webp +0 -0
- package/frontend/assets/mockups/media_tagging_3_media_viewer_1200.webp +0 -0
- package/frontend/assets/mockups/overview_1200.webp +0 -0
- package/frontend/assets/mockups/settings_1200.webp +0 -0
- package/frontend/assets/mockups/sql_editor_1_1200.webp +0 -0
- package/frontend/assets/mockups/sql_editor_2_query_details_1200.webp +0 -0
- package/frontend/assets/mockups/sql_editor_3_export_column_1200.webp +0 -0
- package/frontend/assets/mockups/sql_editor_4_export_column_as_markdown_1200.webp +0 -0
- package/frontend/assets/mockups/sql_editor_5_export_query_result_1200.webp +0 -0
- package/frontend/assets/mockups/structure_1_1200.webp +0 -0
- package/frontend/assets/mockups/structure_2_inspector_1200.webp +0 -0
- package/frontend/assets/mockups/table_designer_1_1200.webp +0 -0
- package/frontend/assets/mockups/table_designer_2_checks_1200.webp +0 -0
- package/frontend/js/api.js +25 -0
- package/frontend/js/app.js +183 -26
- package/frontend/js/components/formControls.js +38 -0
- package/frontend/js/components/modal.js +79 -19
- package/frontend/js/components/queryResults.js +18 -1
- package/frontend/js/components/rowEditorPanel.js +42 -34
- package/frontend/js/store.js +129 -2
- package/frontend/js/utils/jsonPreview.js +31 -0
- package/frontend/js/utils/rowEditorValues.js +41 -0
- package/frontend/js/utils/tableScrollState.js +39 -0
- package/frontend/js/views/charts.js +8 -0
- package/frontend/js/views/data.js +4 -1
- package/frontend/js/views/editor.js +2 -0
- package/frontend/js/views/settings.js +141 -5
- package/frontend/styles/components.css +6 -0
- package/frontend/styles/tailwind.generated.css +2 -2
- package/package.json +6 -6
- package/server/middleware/apiTokenAuth.js +30 -0
- package/server/middleware/localRequestSecurity.js +84 -0
- package/server/routes/connections.js +35 -1
- package/server/routes/externalApi.js +222 -0
- package/server/routes/settings.js +64 -4
- package/server/server.js +34 -2
- package/server/services/apiTokenService.js +101 -0
- package/server/services/databaseCommandService.js +399 -0
- package/server/services/nativeFileDialogService.js +260 -0
- package/server/services/sqlite/dataBrowserService.js +0 -4
- package/server/services/sqlite/exportService.js +5 -1
- package/server/services/sqlite/sqlExecutor.js +51 -2
- package/server/services/storage/appStateStore.js +113 -0
- package/server/utils/errors.js +7 -0
- package/server/utils/sqliteTypes.js +17 -8
- package/tests/api-token-auth.test.js +127 -0
- package/tests/cli-service-delegation.test.js +43 -0
- package/tests/connections-file-dialog-route.test.js +89 -0
- package/tests/copy-column-modal.test.js +48 -0
- package/tests/database-command-service.test.js +102 -0
- package/tests/export-blob.test.js +46 -0
- package/tests/form-controls.test.js +34 -0
- package/tests/json-preview.test.js +49 -0
- package/tests/local-request-security.test.js +85 -0
- package/tests/native-file-dialog.test.js +105 -0
- package/tests/query-results-truncation.test.js +20 -0
- package/tests/row-editor-null-values.test.js +131 -0
- package/tests/settings-api-tokens-route.test.js +76 -0
- package/tests/settings-view.test.js +47 -0
- package/tests/sql-identifier-safety.test.js +9 -3
- package/tests/sql-result-limit.test.js +66 -0
- package/tests/table-scroll-state.test.js +70 -0
- package/frontend/assets/mockups/connections.png +0 -0
- package/frontend/assets/mockups/data.png +0 -0
- package/frontend/assets/mockups/data_row_editor.png +0 -0
- package/frontend/assets/mockups/home.png +0 -0
- package/frontend/assets/mockups/sql_editor.png +0 -0
- package/frontend/assets/mockups/sql_editor_croped.png +0 -0
- package/frontend/assets/mockups/sql_editor_querydetail.png +0 -0
- package/frontend/assets/mockups/structure.png +0 -0
- package/frontend/assets/mockups/structure_inspector.png +0 -0
|
@@ -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
|
});
|
|
@@ -114,7 +114,8 @@ test("data browser mutations preserve quoted dynamic identifiers", () => {
|
|
|
114
114
|
"UPDATE",
|
|
115
115
|
quoteIdentifier(tableName),
|
|
116
116
|
"SET",
|
|
117
|
-
quoteIdentifier(valueColumn) + " =
|
|
117
|
+
quoteIdentifier(valueColumn) + " = ?,",
|
|
118
|
+
quoteIdentifier(noteColumn) + " = ?",
|
|
118
119
|
"WHERE",
|
|
119
120
|
"rowid IS ?",
|
|
120
121
|
].join(" ")
|
|
@@ -125,6 +126,11 @@ test("data browser mutations preserve quoted dynamic identifiers", () => {
|
|
|
125
126
|
oldValue: "before",
|
|
126
127
|
newValue: "after",
|
|
127
128
|
},
|
|
129
|
+
{
|
|
130
|
+
column: noteColumn,
|
|
131
|
+
oldValue: "NULL",
|
|
132
|
+
newValue: "",
|
|
133
|
+
},
|
|
128
134
|
]);
|
|
129
135
|
|
|
130
136
|
const updated = service.updateTableRow(tableName, {
|
|
@@ -136,7 +142,7 @@ test("data browser mutations preserve quoted dynamic identifiers", () => {
|
|
|
136
142
|
});
|
|
137
143
|
|
|
138
144
|
assert.equal(updated.row[valueColumn], "after");
|
|
139
|
-
assert.equal(updated.row[noteColumn],
|
|
145
|
+
assert.equal(updated.row[noteColumn], "");
|
|
140
146
|
const persistedRow = db.prepare(
|
|
141
147
|
[
|
|
142
148
|
"SELECT",
|
|
@@ -148,7 +154,7 @@ test("data browser mutations preserve quoted dynamic identifiers", () => {
|
|
|
148
154
|
).get();
|
|
149
155
|
|
|
150
156
|
assert.equal(persistedRow[valueColumn], "after");
|
|
151
|
-
assert.equal(persistedRow[noteColumn],
|
|
157
|
+
assert.equal(persistedRow[noteColumn], "");
|
|
152
158
|
|
|
153
159
|
const deleted = service.deleteTableRow(tableName, {
|
|
154
160
|
identity: updated.row.__identity,
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const Database = require("better-sqlite3");
|
|
2
|
+
const assert = require("node:assert/strict");
|
|
3
|
+
const test = require("node:test");
|
|
4
|
+
const {
|
|
5
|
+
DEFAULT_RESULT_ROW_LIMIT,
|
|
6
|
+
SqlExecutor,
|
|
7
|
+
} = require("../server/services/sqlite/sqlExecutor");
|
|
8
|
+
|
|
9
|
+
function createExecutor(db) {
|
|
10
|
+
return new SqlExecutor({
|
|
11
|
+
connectionManager: {
|
|
12
|
+
getActiveDatabase: () => db,
|
|
13
|
+
getActiveConnection: () => ({ id: "result-limit-test" }),
|
|
14
|
+
},
|
|
15
|
+
appStateStore: {
|
|
16
|
+
recordQueryExecution: () => 1,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
test("interactive SQL results stop at the configured row limit", () => {
|
|
22
|
+
const db = new Database(":memory:");
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
db.exec(`
|
|
26
|
+
CREATE TABLE items (id INTEGER PRIMARY KEY, label TEXT);
|
|
27
|
+
INSERT INTO items (label) VALUES ('one'), ('two'), ('three'), ('four'), ('five');
|
|
28
|
+
`);
|
|
29
|
+
|
|
30
|
+
const result = createExecutor(db).execute("SELECT id, label FROM items ORDER BY id", {
|
|
31
|
+
maxRows: 3,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
assert.equal(DEFAULT_RESULT_ROW_LIMIT, 5000);
|
|
35
|
+
assert.equal(result.rows.length, 3);
|
|
36
|
+
assert.deepEqual(result.rows.map((row) => row.id), [1, 2, 3]);
|
|
37
|
+
assert.equal(result.truncated, true);
|
|
38
|
+
assert.equal(result.rowLimit, 3);
|
|
39
|
+
assert.equal(result.statements[0].truncated, true);
|
|
40
|
+
assert.equal(result.statements[0].rowCount, 3);
|
|
41
|
+
} finally {
|
|
42
|
+
db.close();
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("unlimited SQL execution remains available for export services", () => {
|
|
47
|
+
const db = new Database(":memory:");
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
db.exec(`
|
|
51
|
+
CREATE TABLE items (id INTEGER PRIMARY KEY);
|
|
52
|
+
INSERT INTO items VALUES (1), (2), (3), (4), (5);
|
|
53
|
+
`);
|
|
54
|
+
|
|
55
|
+
const result = createExecutor(db).execute("SELECT id FROM items ORDER BY id", {
|
|
56
|
+
maxRows: null,
|
|
57
|
+
persistHistory: false,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
assert.equal(result.rows.length, 5);
|
|
61
|
+
assert.equal(result.truncated, false);
|
|
62
|
+
assert.equal(result.rowLimit, null);
|
|
63
|
+
} finally {
|
|
64
|
+
db.close();
|
|
65
|
+
}
|
|
66
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
const assert = require("node:assert/strict");
|
|
2
|
+
const { readFileSync } = require("node:fs");
|
|
3
|
+
const path = require("node:path");
|
|
4
|
+
const test = require("node:test");
|
|
5
|
+
|
|
6
|
+
let tableScrollStateModulePromise = null;
|
|
7
|
+
|
|
8
|
+
function loadTableScrollStateModule() {
|
|
9
|
+
if (!tableScrollStateModulePromise) {
|
|
10
|
+
const source = readFileSync(
|
|
11
|
+
path.resolve(__dirname, "../frontend/js/utils/tableScrollState.js"),
|
|
12
|
+
"utf8"
|
|
13
|
+
);
|
|
14
|
+
const url = `data:text/javascript;base64,${Buffer.from(source).toString("base64")}`;
|
|
15
|
+
|
|
16
|
+
tableScrollStateModulePromise = import(url);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return tableScrollStateModulePromise;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
test("table scroll state restores horizontal position for the matching grid", async () => {
|
|
23
|
+
const { captureTableHorizontalScrollState, restoreTableHorizontalScrollState } =
|
|
24
|
+
await loadTableScrollStateModule();
|
|
25
|
+
const key = 'data:events "archive"';
|
|
26
|
+
const previousGrid = { dataset: { tableScrollKey: key }, scrollLeft: 640 };
|
|
27
|
+
const nextGrid = { dataset: { tableScrollKey: key }, scrollLeft: 0 };
|
|
28
|
+
const snapshot = captureTableHorizontalScrollState({
|
|
29
|
+
routeName: "data",
|
|
30
|
+
scrollNodes: [previousGrid],
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
assert.equal(
|
|
34
|
+
restoreTableHorizontalScrollState({
|
|
35
|
+
snapshot,
|
|
36
|
+
routeName: "data",
|
|
37
|
+
scrollNodes: [nextGrid],
|
|
38
|
+
}),
|
|
39
|
+
true
|
|
40
|
+
);
|
|
41
|
+
assert.equal(nextGrid.scrollLeft, 640);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("table scroll state is not restored across routes or different grids", async () => {
|
|
45
|
+
const { captureTableHorizontalScrollState, restoreTableHorizontalScrollState } =
|
|
46
|
+
await loadTableScrollStateModule();
|
|
47
|
+
const snapshot = captureTableHorizontalScrollState({
|
|
48
|
+
routeName: "editorResults",
|
|
49
|
+
scrollNodes: [{ dataset: { tableScrollKey: "editor:12" }, scrollLeft: 320 }],
|
|
50
|
+
});
|
|
51
|
+
const nextGrid = { dataset: { tableScrollKey: "editor:13" }, scrollLeft: 0 };
|
|
52
|
+
|
|
53
|
+
assert.equal(
|
|
54
|
+
restoreTableHorizontalScrollState({
|
|
55
|
+
snapshot,
|
|
56
|
+
routeName: "data",
|
|
57
|
+
scrollNodes: [nextGrid],
|
|
58
|
+
}),
|
|
59
|
+
false
|
|
60
|
+
);
|
|
61
|
+
assert.equal(
|
|
62
|
+
restoreTableHorizontalScrollState({
|
|
63
|
+
snapshot,
|
|
64
|
+
routeName: "editorResults",
|
|
65
|
+
scrollNodes: [nextGrid],
|
|
66
|
+
}),
|
|
67
|
+
false
|
|
68
|
+
);
|
|
69
|
+
assert.equal(nextGrid.scrollLeft, 0);
|
|
70
|
+
});
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|