sqlite-hub 1.1.2 → 1.4.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 +106 -73
- package/bin/sqlite-hub.js +176 -2
- package/docs/API.md +122 -0
- package/docs/CLI.md +227 -0
- package/docs/DESIGN_GUIDELINES.md +45 -0
- package/docs/changelog.md +141 -0
- package/docs/shortkeys.md +27 -0
- package/docs/todo.md +16 -0
- package/examples/api/generate-types.js +52 -0
- package/frontend/assets/mockups/backups_1_1920.webp +0 -0
- package/frontend/assets/mockups/backups_2_create_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_3_edit_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_4_restore_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_5_delete_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/charts_1_1920.webp +0 -0
- package/frontend/assets/mockups/charts_2_query_detail_1920.webp +0 -0
- package/frontend/assets/mockups/charts_3_create_query_chart_modal_1920.webp +0 -0
- package/frontend/assets/mockups/charts_4_edit_query_chart_modal_1920.webp +0 -0
- package/frontend/assets/mockups/charts_5_delete_query_chart_modal_1920.webp +0 -0
- package/frontend/assets/mockups/charts_6_copy_column_modal_1920.webp +0 -0
- package/frontend/assets/mockups/connections_1_1920.webp +0 -0
- package/frontend/assets/mockups/connections_2_create_connection_modal_1920.webp +0 -0
- package/frontend/assets/mockups/connections_3_open_connection_modal_1920.webp +0 -0
- package/frontend/assets/mockups/data_1_1920.webp +0 -0
- package/frontend/assets/mockups/data_2_roweditor_1920.webp +0 -0
- package/frontend/assets/mockups/data_3_data_export_modal_1920.webp +0 -0
- package/frontend/assets/mockups/documents_1_1920.webp +0 -0
- package/frontend/assets/mockups/documents_2_document_insert_table_modal_1920.webp +0 -0
- package/frontend/assets/mockups/documents_3_document_insert_note_modal_1920.webp +0 -0
- package/frontend/assets/mockups/media_tagging_queue_1_1920.webp +0 -0
- package/frontend/assets/mockups/media_tagging_setup_1_1920.webp +0 -0
- package/frontend/assets/mockups/media_tagging_setup_2_create_media_tagging_tag_table_modal_1920.webp +0 -0
- package/frontend/assets/mockups/media_tagging_setup_3_create_media_tagging_mapping_table_modal_1920.webp +0 -0
- package/frontend/assets/mockups/overview_1_1920.webp +0 -0
- package/frontend/assets/mockups/settings_1_1920.webp +0 -0
- package/frontend/assets/mockups/sql_editor_1_1920.webp +0 -0
- package/frontend/assets/mockups/sql_editor_2_query_detail_1920.webp +0 -0
- package/frontend/assets/mockups/sql_editor_3_query_export_modal_1920.webp +0 -0
- package/frontend/assets/mockups/structure_1_1920.webp +0 -0
- package/frontend/assets/mockups/structure_2_generate_types_modal_1920.webp +0 -0
- package/frontend/assets/mockups/structure_3_generate_types_modal_1920.webp +0 -0
- package/frontend/assets/mockups/table_designer_1_1920.webp +0 -0
- package/frontend/assets/mockups/table_designer_2_table_designer_constraints_modal_1920.webp +0 -0
- package/frontend/js/api.js +18 -0
- package/frontend/js/app.js +209 -13
- package/frontend/js/components/connectionCard.js +1 -1
- package/frontend/js/components/emptyState.js +4 -4
- package/frontend/js/components/generateTypesDropdown.js +33 -0
- package/frontend/js/components/metricCard.js +1 -1
- package/frontend/js/components/modal.js +211 -18
- package/frontend/js/components/pageHeader.js +1 -1
- package/frontend/js/components/queryHistoryDetail.js +3 -3
- package/frontend/js/components/queryHistoryHeader.js +1 -1
- package/frontend/js/components/queryHistoryPanel.js +1 -1
- package/frontend/js/components/queryResults.js +1 -1
- package/frontend/js/components/rowEditorPanel.js +65 -16
- package/frontend/js/components/topNav.js +1 -4
- package/frontend/js/components/workspaceOpenDropdown.js +52 -0
- package/frontend/js/router.js +28 -6
- package/frontend/js/store.js +489 -9
- package/frontend/js/utils/emailPreview.js +28 -0
- package/frontend/js/utils/markdownDocuments.js +17 -1
- package/frontend/js/views/backups.js +556 -36
- package/frontend/js/views/charts.js +9 -9
- package/frontend/js/views/connections.js +2 -2
- package/frontend/js/views/data.js +43 -17
- package/frontend/js/views/documents.js +3 -3
- package/frontend/js/views/editor.js +6 -6
- package/frontend/js/views/mediaTagging.js +7 -5
- package/frontend/js/views/overview.js +3 -3
- package/frontend/js/views/settings.js +4 -3
- package/frontend/js/views/structure.js +27 -10
- package/frontend/js/views/tableDesigner.js +23 -0
- package/frontend/styles/base.css +1 -40
- package/frontend/styles/components.css +114 -232
- package/frontend/styles/structure-graph.css +32 -68
- package/frontend/styles/tailwind.css +8 -4
- package/frontend/styles/tailwind.generated.css +1 -1
- package/frontend/styles/tokens.css +29 -21
- package/frontend/styles/views.css +94 -261
- package/package.json +18 -2
- package/server/routes/backups.js +18 -2
- package/server/routes/externalApi.js +36 -0
- package/server/routes/structure.js +22 -0
- package/server/services/databaseCommandService.js +14 -0
- package/server/services/sqlite/backupDiff.js +914 -0
- package/server/services/sqlite/backupService.js +77 -1
- package/server/services/sqlite/structureService.js +7 -0
- package/server/services/typeGenerationService.js +663 -0
- package/tailwind.config.cjs +0 -1
- package/.github/funding.yml +0 -2
- package/.github/workflows/ci.yml +0 -36
- package/database.sqlite +0 -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/tests/api-token-auth.test.js +0 -279
- package/tests/backup-manager.test.js +0 -140
- package/tests/backups-view.test.js +0 -64
- package/tests/charts-height-preset-storage.test.js +0 -60
- package/tests/charts-route-state.test.js +0 -144
- package/tests/check-constraint-options.test.js +0 -90
- package/tests/cli-args.test.js +0 -113
- package/tests/cli-service-delegation.test.js +0 -140
- package/tests/connection-removal.test.js +0 -52
- package/tests/connections-file-dialog-route.test.js +0 -89
- package/tests/copy-column-modal.test.js +0 -131
- package/tests/database-command-service.test.js +0 -174
- package/tests/database-documents.test.js +0 -85
- package/tests/documents-view.test.js +0 -132
- package/tests/dropdown-button.test.js +0 -75
- package/tests/export-blob.test.js +0 -99
- package/tests/export-filenames.test.js +0 -38
- package/tests/file-path-preview.test.js +0 -165
- package/tests/form-controls.test.js +0 -34
- package/tests/json-preview.test.js +0 -49
- package/tests/local-request-security.test.js +0 -85
- package/tests/markdown-documents.test.js +0 -79
- package/tests/native-file-dialog.test.js +0 -105
- package/tests/query-editor.test.js +0 -28
- package/tests/query-history-detail.test.js +0 -37
- package/tests/query-history-header.test.js +0 -30
- package/tests/query-results-truncation.test.js +0 -20
- package/tests/risky-sql.test.js +0 -30
- package/tests/row-editor-json.test.js +0 -82
- package/tests/row-editor-null-values.test.js +0 -155
- package/tests/row-editor-timestamp-preview.test.js +0 -192
- package/tests/security-paths.test.js +0 -84
- package/tests/settings-api-tokens-route.test.js +0 -97
- package/tests/settings-metadata.test.js +0 -114
- package/tests/settings-view.test.js +0 -107
- package/tests/sql-formatter.test.js +0 -173
- package/tests/sql-highlight.test.js +0 -38
- package/tests/sql-identifier-safety.test.js +0 -171
- package/tests/sql-result-limit.test.js +0 -66
- package/tests/structure-view.test.js +0 -56
- package/tests/table-designer-v2-unique-constraints.test.js +0 -78
- package/tests/table-scroll-state.test.js +0 -70
- package/tests/text-cell-stats.test.js +0 -38
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
const assert = require("node:assert/strict");
|
|
2
|
-
const path = require("node:path");
|
|
3
|
-
const { pathToFileURL } = require("node:url");
|
|
4
|
-
const test = require("node:test");
|
|
5
|
-
|
|
6
|
-
let structureViewModulePromise = null;
|
|
7
|
-
|
|
8
|
-
function loadStructureViewModule() {
|
|
9
|
-
if (!structureViewModulePromise) {
|
|
10
|
-
structureViewModulePromise = import(
|
|
11
|
-
pathToFileURL(path.resolve(__dirname, "../frontend/js/views/structure.js")).href
|
|
12
|
-
);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
return structureViewModulePromise;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function buildStructureState() {
|
|
19
|
-
return {
|
|
20
|
-
structure: {
|
|
21
|
-
data: {
|
|
22
|
-
grouped: {
|
|
23
|
-
tables: [{ name: "companies", type: "table" }],
|
|
24
|
-
views: [],
|
|
25
|
-
indexes: [],
|
|
26
|
-
triggers: [],
|
|
27
|
-
},
|
|
28
|
-
graph: {
|
|
29
|
-
relationshipCount: 0,
|
|
30
|
-
tables: [{ name: "companies", type: "table", columns: [], foreignKeys: [] }],
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
detail: null,
|
|
34
|
-
detailLoading: false,
|
|
35
|
-
error: null,
|
|
36
|
-
loading: false,
|
|
37
|
-
selectedName: "companies",
|
|
38
|
-
tableSearchQuery: "",
|
|
39
|
-
tablesVisible: true,
|
|
40
|
-
},
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
test("structure toolbar groups graph format actions in a dropdown", async () => {
|
|
45
|
-
const { renderStructureView } = await loadStructureViewModule();
|
|
46
|
-
const { main } = renderStructureView(buildStructureState());
|
|
47
|
-
|
|
48
|
-
assert.match(main, /data-dropdown-button/);
|
|
49
|
-
assert.match(main, /Format graph/);
|
|
50
|
-
assert.match(main, /Fit Graph/);
|
|
51
|
-
assert.match(main, /data-structure-graph-action="fit"/);
|
|
52
|
-
assert.match(main, /Recalculate Layout/);
|
|
53
|
-
assert.match(main, /data-structure-graph-action="relayout"/);
|
|
54
|
-
assert.match(main, /Clear Selection/);
|
|
55
|
-
assert.match(main, /data-structure-graph-action="clear"/);
|
|
56
|
-
});
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
const Database = require("better-sqlite3");
|
|
2
|
-
const assert = require("node:assert/strict");
|
|
3
|
-
const test = require("node:test");
|
|
4
|
-
const { getTableDetail } = require("../server/services/sqlite/introspection");
|
|
5
|
-
const { analyzeTableDesignerChanges } = require("../server/services/sqlite/tableDesigner/changeAnalysis");
|
|
6
|
-
const { buildTableDesignerDraft } = require("../server/services/sqlite/tableDesigner/schemaMapping");
|
|
7
|
-
|
|
8
|
-
test("table designer draft exposes v2 complex unique constraints", () => {
|
|
9
|
-
const db = new Database(":memory:");
|
|
10
|
-
|
|
11
|
-
try {
|
|
12
|
-
db.exec(`
|
|
13
|
-
CREATE TABLE companies (
|
|
14
|
-
company_id INTEGER NOT NULL,
|
|
15
|
-
ticker TEXT NOT NULL,
|
|
16
|
-
deleted_at TEXT,
|
|
17
|
-
name TEXT,
|
|
18
|
-
UNIQUE (company_id, ticker)
|
|
19
|
-
);
|
|
20
|
-
CREATE UNIQUE INDEX uniq_active_company_ticker
|
|
21
|
-
ON companies (ticker)
|
|
22
|
-
WHERE deleted_at IS NULL;
|
|
23
|
-
`);
|
|
24
|
-
|
|
25
|
-
const tableDetail = getTableDetail(db, "companies", { includeRowCount: false });
|
|
26
|
-
const draft = buildTableDesignerDraft(tableDetail);
|
|
27
|
-
|
|
28
|
-
assert.equal(draft.designerVersion, 2);
|
|
29
|
-
assert.equal(draft.uniqueConstraints.length, 2);
|
|
30
|
-
|
|
31
|
-
const multiColumnConstraint = draft.uniqueConstraints.find(
|
|
32
|
-
(constraint) => constraint.columns.length === 2
|
|
33
|
-
);
|
|
34
|
-
assert.ok(multiColumnConstraint);
|
|
35
|
-
assert.equal(multiColumnConstraint.partial, false);
|
|
36
|
-
assert.deepEqual(
|
|
37
|
-
multiColumnConstraint.columns.map((column) => column.name),
|
|
38
|
-
["company_id", "ticker"]
|
|
39
|
-
);
|
|
40
|
-
assert.equal(multiColumnConstraint.expression, 'UNIQUE ("company_id", "ticker")');
|
|
41
|
-
assert.equal(multiColumnConstraint.originalExpression, multiColumnConstraint.expression);
|
|
42
|
-
assert.equal(multiColumnConstraint.editable, true);
|
|
43
|
-
assert.equal(multiColumnConstraint.preserved, true);
|
|
44
|
-
|
|
45
|
-
const partialConstraint = draft.uniqueConstraints.find((constraint) => constraint.partial);
|
|
46
|
-
assert.ok(partialConstraint);
|
|
47
|
-
assert.equal(partialConstraint.name, "uniq_active_company_ticker");
|
|
48
|
-
assert.match(partialConstraint.sql, /CREATE UNIQUE INDEX uniq_active_company_ticker/i);
|
|
49
|
-
assert.match(partialConstraint.sql, /WHERE deleted_at IS NULL/i);
|
|
50
|
-
assert.equal(partialConstraint.expression, partialConstraint.sql);
|
|
51
|
-
|
|
52
|
-
const changedDraft = {
|
|
53
|
-
...draft,
|
|
54
|
-
uniqueConstraints: draft.uniqueConstraints.map((constraint) =>
|
|
55
|
-
constraint.id === multiColumnConstraint.id
|
|
56
|
-
? {
|
|
57
|
-
...constraint,
|
|
58
|
-
expression: 'UNIQUE ("company_id", "ticker", "name")',
|
|
59
|
-
}
|
|
60
|
-
: constraint
|
|
61
|
-
),
|
|
62
|
-
};
|
|
63
|
-
const analysis = analyzeTableDesignerChanges({ draft: changedDraft, originalDraft: draft });
|
|
64
|
-
assert.equal(analysis.dirty, true);
|
|
65
|
-
assert.equal(analysis.executable, false);
|
|
66
|
-
assert.match(analysis.warnings[0].title, /Constraint Change Requires Rebuild/);
|
|
67
|
-
|
|
68
|
-
const uniqueWarning = draft.schemaWarnings.find(
|
|
69
|
-
(warning) => warning.code === "COMPLEX_UNIQUE_CONSTRAINTS_PRESENT"
|
|
70
|
-
);
|
|
71
|
-
assert.ok(uniqueWarning);
|
|
72
|
-
assert.equal(uniqueWarning.title, "Table Designer v2 Unique Constraints");
|
|
73
|
-
assert.equal(uniqueWarning.tone, "muted");
|
|
74
|
-
assert.doesNotMatch(uniqueWarning.message, /outside Table Designer v1/i);
|
|
75
|
-
} finally {
|
|
76
|
-
db.close();
|
|
77
|
-
}
|
|
78
|
-
});
|
|
@@ -1,70 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,38 +0,0 @@
|
|
|
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 textCellStatsModulePromise = null;
|
|
7
|
-
|
|
8
|
-
function loadTextCellStatsModule() {
|
|
9
|
-
if (!textCellStatsModulePromise) {
|
|
10
|
-
const source = readFileSync(
|
|
11
|
-
path.resolve(__dirname, "../frontend/js/utils/textCellStats.js"),
|
|
12
|
-
"utf8"
|
|
13
|
-
);
|
|
14
|
-
const url = `data:text/javascript;base64,${Buffer.from(source).toString("base64")}`;
|
|
15
|
-
|
|
16
|
-
textCellStatsModulePromise = import(url);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return textCellStatsModulePromise;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
test("text cell character count only returns counts for non-empty strings", async () => {
|
|
23
|
-
const { getTextCellCharacterCount } = await loadTextCellStatsModule();
|
|
24
|
-
|
|
25
|
-
assert.equal(getTextCellCharacterCount(""), null);
|
|
26
|
-
assert.equal(getTextCellCharacterCount(null), null);
|
|
27
|
-
assert.equal(getTextCellCharacterCount(123), null);
|
|
28
|
-
assert.equal(getTextCellCharacterCount("hello"), 5);
|
|
29
|
-
assert.equal(getTextCellCharacterCount("a\nb"), 3);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test("text cell character count label is readable", async () => {
|
|
33
|
-
const { formatTextCellCharacterCount } = await loadTextCellStatsModule();
|
|
34
|
-
|
|
35
|
-
assert.equal(formatTextCellCharacterCount(1), "1 char");
|
|
36
|
-
assert.equal(formatTextCellCharacterCount(25), "25 chars");
|
|
37
|
-
assert.equal(formatTextCellCharacterCount(1200), "1,200 chars");
|
|
38
|
-
});
|