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.
Files changed (157) hide show
  1. package/README.md +106 -73
  2. package/bin/sqlite-hub.js +176 -2
  3. package/docs/API.md +122 -0
  4. package/docs/CLI.md +227 -0
  5. package/docs/DESIGN_GUIDELINES.md +45 -0
  6. package/docs/changelog.md +141 -0
  7. package/docs/shortkeys.md +27 -0
  8. package/docs/todo.md +16 -0
  9. package/examples/api/generate-types.js +52 -0
  10. package/frontend/assets/mockups/backups_1_1920.webp +0 -0
  11. package/frontend/assets/mockups/backups_2_create_backup_modal_1920.webp +0 -0
  12. package/frontend/assets/mockups/backups_3_edit_backup_modal_1920.webp +0 -0
  13. package/frontend/assets/mockups/backups_4_restore_backup_modal_1920.webp +0 -0
  14. package/frontend/assets/mockups/backups_5_delete_backup_modal_1920.webp +0 -0
  15. package/frontend/assets/mockups/charts_1_1920.webp +0 -0
  16. package/frontend/assets/mockups/charts_2_query_detail_1920.webp +0 -0
  17. package/frontend/assets/mockups/charts_3_create_query_chart_modal_1920.webp +0 -0
  18. package/frontend/assets/mockups/charts_4_edit_query_chart_modal_1920.webp +0 -0
  19. package/frontend/assets/mockups/charts_5_delete_query_chart_modal_1920.webp +0 -0
  20. package/frontend/assets/mockups/charts_6_copy_column_modal_1920.webp +0 -0
  21. package/frontend/assets/mockups/connections_1_1920.webp +0 -0
  22. package/frontend/assets/mockups/connections_2_create_connection_modal_1920.webp +0 -0
  23. package/frontend/assets/mockups/connections_3_open_connection_modal_1920.webp +0 -0
  24. package/frontend/assets/mockups/data_1_1920.webp +0 -0
  25. package/frontend/assets/mockups/data_2_roweditor_1920.webp +0 -0
  26. package/frontend/assets/mockups/data_3_data_export_modal_1920.webp +0 -0
  27. package/frontend/assets/mockups/documents_1_1920.webp +0 -0
  28. package/frontend/assets/mockups/documents_2_document_insert_table_modal_1920.webp +0 -0
  29. package/frontend/assets/mockups/documents_3_document_insert_note_modal_1920.webp +0 -0
  30. package/frontend/assets/mockups/media_tagging_queue_1_1920.webp +0 -0
  31. package/frontend/assets/mockups/media_tagging_setup_1_1920.webp +0 -0
  32. package/frontend/assets/mockups/media_tagging_setup_2_create_media_tagging_tag_table_modal_1920.webp +0 -0
  33. package/frontend/assets/mockups/media_tagging_setup_3_create_media_tagging_mapping_table_modal_1920.webp +0 -0
  34. package/frontend/assets/mockups/overview_1_1920.webp +0 -0
  35. package/frontend/assets/mockups/settings_1_1920.webp +0 -0
  36. package/frontend/assets/mockups/sql_editor_1_1920.webp +0 -0
  37. package/frontend/assets/mockups/sql_editor_2_query_detail_1920.webp +0 -0
  38. package/frontend/assets/mockups/sql_editor_3_query_export_modal_1920.webp +0 -0
  39. package/frontend/assets/mockups/structure_1_1920.webp +0 -0
  40. package/frontend/assets/mockups/structure_2_generate_types_modal_1920.webp +0 -0
  41. package/frontend/assets/mockups/structure_3_generate_types_modal_1920.webp +0 -0
  42. package/frontend/assets/mockups/table_designer_1_1920.webp +0 -0
  43. package/frontend/assets/mockups/table_designer_2_table_designer_constraints_modal_1920.webp +0 -0
  44. package/frontend/js/api.js +18 -0
  45. package/frontend/js/app.js +209 -13
  46. package/frontend/js/components/connectionCard.js +1 -1
  47. package/frontend/js/components/emptyState.js +4 -4
  48. package/frontend/js/components/generateTypesDropdown.js +33 -0
  49. package/frontend/js/components/metricCard.js +1 -1
  50. package/frontend/js/components/modal.js +211 -18
  51. package/frontend/js/components/pageHeader.js +1 -1
  52. package/frontend/js/components/queryHistoryDetail.js +3 -3
  53. package/frontend/js/components/queryHistoryHeader.js +1 -1
  54. package/frontend/js/components/queryHistoryPanel.js +1 -1
  55. package/frontend/js/components/queryResults.js +1 -1
  56. package/frontend/js/components/rowEditorPanel.js +65 -16
  57. package/frontend/js/components/topNav.js +1 -4
  58. package/frontend/js/components/workspaceOpenDropdown.js +52 -0
  59. package/frontend/js/router.js +28 -6
  60. package/frontend/js/store.js +489 -9
  61. package/frontend/js/utils/emailPreview.js +28 -0
  62. package/frontend/js/utils/markdownDocuments.js +17 -1
  63. package/frontend/js/views/backups.js +556 -36
  64. package/frontend/js/views/charts.js +9 -9
  65. package/frontend/js/views/connections.js +2 -2
  66. package/frontend/js/views/data.js +43 -17
  67. package/frontend/js/views/documents.js +3 -3
  68. package/frontend/js/views/editor.js +6 -6
  69. package/frontend/js/views/mediaTagging.js +7 -5
  70. package/frontend/js/views/overview.js +3 -3
  71. package/frontend/js/views/settings.js +4 -3
  72. package/frontend/js/views/structure.js +27 -10
  73. package/frontend/js/views/tableDesigner.js +23 -0
  74. package/frontend/styles/base.css +1 -40
  75. package/frontend/styles/components.css +114 -232
  76. package/frontend/styles/structure-graph.css +32 -68
  77. package/frontend/styles/tailwind.css +8 -4
  78. package/frontend/styles/tailwind.generated.css +1 -1
  79. package/frontend/styles/tokens.css +29 -21
  80. package/frontend/styles/views.css +94 -261
  81. package/package.json +18 -2
  82. package/server/routes/backups.js +18 -2
  83. package/server/routes/externalApi.js +36 -0
  84. package/server/routes/structure.js +22 -0
  85. package/server/services/databaseCommandService.js +14 -0
  86. package/server/services/sqlite/backupDiff.js +914 -0
  87. package/server/services/sqlite/backupService.js +77 -1
  88. package/server/services/sqlite/structureService.js +7 -0
  89. package/server/services/typeGenerationService.js +663 -0
  90. package/tailwind.config.cjs +0 -1
  91. package/.github/funding.yml +0 -2
  92. package/.github/workflows/ci.yml +0 -36
  93. package/database.sqlite +0 -0
  94. package/frontend/assets/mockups/charts_1_bars_1200.webp +0 -0
  95. package/frontend/assets/mockups/charts_2_pie_1200.webp +0 -0
  96. package/frontend/assets/mockups/charts_3_scatter_plot_1200.webp +0 -0
  97. package/frontend/assets/mockups/connections_1200.webp +0 -0
  98. package/frontend/assets/mockups/data_1_1200.webp +0 -0
  99. package/frontend/assets/mockups/data_2_row_editor_1200.webp +0 -0
  100. package/frontend/assets/mockups/documents_1200.webp +0 -0
  101. package/frontend/assets/mockups/media_tagging_1_setup_1200.webp +0 -0
  102. package/frontend/assets/mockups/media_tagging_2_tagging_queue_1200.webp +0 -0
  103. package/frontend/assets/mockups/media_tagging_3_media_viewer_1200.webp +0 -0
  104. package/frontend/assets/mockups/overview_1200.webp +0 -0
  105. package/frontend/assets/mockups/settings_1200.webp +0 -0
  106. package/frontend/assets/mockups/sql_editor_1_1200.webp +0 -0
  107. package/frontend/assets/mockups/sql_editor_2_query_details_1200.webp +0 -0
  108. package/frontend/assets/mockups/sql_editor_3_export_column_1200.webp +0 -0
  109. package/frontend/assets/mockups/sql_editor_4_export_column_as_markdown_1200.webp +0 -0
  110. package/frontend/assets/mockups/sql_editor_5_export_query_result_1200.webp +0 -0
  111. package/frontend/assets/mockups/structure_1_1200.webp +0 -0
  112. package/frontend/assets/mockups/structure_2_inspector_1200.webp +0 -0
  113. package/frontend/assets/mockups/table_designer_1_1200.webp +0 -0
  114. package/frontend/assets/mockups/table_designer_2_checks_1200.webp +0 -0
  115. package/tests/api-token-auth.test.js +0 -279
  116. package/tests/backup-manager.test.js +0 -140
  117. package/tests/backups-view.test.js +0 -64
  118. package/tests/charts-height-preset-storage.test.js +0 -60
  119. package/tests/charts-route-state.test.js +0 -144
  120. package/tests/check-constraint-options.test.js +0 -90
  121. package/tests/cli-args.test.js +0 -113
  122. package/tests/cli-service-delegation.test.js +0 -140
  123. package/tests/connection-removal.test.js +0 -52
  124. package/tests/connections-file-dialog-route.test.js +0 -89
  125. package/tests/copy-column-modal.test.js +0 -131
  126. package/tests/database-command-service.test.js +0 -174
  127. package/tests/database-documents.test.js +0 -85
  128. package/tests/documents-view.test.js +0 -132
  129. package/tests/dropdown-button.test.js +0 -75
  130. package/tests/export-blob.test.js +0 -99
  131. package/tests/export-filenames.test.js +0 -38
  132. package/tests/file-path-preview.test.js +0 -165
  133. package/tests/form-controls.test.js +0 -34
  134. package/tests/json-preview.test.js +0 -49
  135. package/tests/local-request-security.test.js +0 -85
  136. package/tests/markdown-documents.test.js +0 -79
  137. package/tests/native-file-dialog.test.js +0 -105
  138. package/tests/query-editor.test.js +0 -28
  139. package/tests/query-history-detail.test.js +0 -37
  140. package/tests/query-history-header.test.js +0 -30
  141. package/tests/query-results-truncation.test.js +0 -20
  142. package/tests/risky-sql.test.js +0 -30
  143. package/tests/row-editor-json.test.js +0 -82
  144. package/tests/row-editor-null-values.test.js +0 -155
  145. package/tests/row-editor-timestamp-preview.test.js +0 -192
  146. package/tests/security-paths.test.js +0 -84
  147. package/tests/settings-api-tokens-route.test.js +0 -97
  148. package/tests/settings-metadata.test.js +0 -114
  149. package/tests/settings-view.test.js +0 -107
  150. package/tests/sql-formatter.test.js +0 -173
  151. package/tests/sql-highlight.test.js +0 -38
  152. package/tests/sql-identifier-safety.test.js +0 -171
  153. package/tests/sql-result-limit.test.js +0 -66
  154. package/tests/structure-view.test.js +0 -56
  155. package/tests/table-designer-v2-unique-constraints.test.js +0 -78
  156. package/tests/table-scroll-state.test.js +0 -70
  157. 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
- });