sqlite-hub 0.4.0 → 0.6.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 (56) hide show
  1. package/README.md +2 -2
  2. package/changelog.md +15 -0
  3. package/frontend/assets/mockups/connections.png +0 -0
  4. package/frontend/assets/mockups/data.png +0 -0
  5. package/frontend/assets/mockups/data_row_editor.png +0 -0
  6. package/frontend/assets/mockups/home.png +0 -0
  7. package/frontend/assets/mockups/sql_editor.png +0 -0
  8. package/frontend/assets/mockups/sql_editor_querydetail.png +0 -0
  9. package/frontend/assets/mockups/structure.png +0 -0
  10. package/frontend/assets/mockups/structure_inspector.png +0 -0
  11. package/frontend/js/api.js +114 -5
  12. package/frontend/js/app.js +368 -18
  13. package/frontend/js/components/bottomTabs.js +1 -1
  14. package/frontend/js/components/dataGrid.js +3 -3
  15. package/frontend/js/components/queryEditor.js +33 -55
  16. package/frontend/js/components/queryHistoryDetail.js +263 -0
  17. package/frontend/js/components/queryHistoryPanel.js +228 -0
  18. package/frontend/js/components/queryResults.js +32 -46
  19. package/frontend/js/components/rowEditorPanel.js +73 -14
  20. package/frontend/js/components/sidebar.js +1 -0
  21. package/frontend/js/components/tableDesignerEditor.js +356 -0
  22. package/frontend/js/components/tableDesignerSidebar.js +126 -0
  23. package/frontend/js/components/tableDesignerSqlPreview.js +40 -0
  24. package/frontend/js/router.js +10 -0
  25. package/frontend/js/store.js +841 -22
  26. package/frontend/js/utils/format.js +23 -0
  27. package/frontend/js/utils/tableDesigner.js +1192 -0
  28. package/frontend/js/views/data.js +273 -250
  29. package/frontend/js/views/editor.js +34 -10
  30. package/frontend/js/views/overview.js +15 -0
  31. package/frontend/js/views/tableDesigner.js +37 -0
  32. package/frontend/styles/base.css +87 -73
  33. package/frontend/styles/components.css +841 -188
  34. package/frontend/styles/views.css +40 -0
  35. package/package.json +1 -1
  36. package/server/routes/data.js +2 -0
  37. package/server/routes/export.js +4 -1
  38. package/server/routes/overview.js +12 -0
  39. package/server/routes/sql.js +163 -5
  40. package/server/routes/tableDesigner.js +60 -0
  41. package/server/server.js +5 -1
  42. package/server/services/sqlite/dataBrowserService.js +4 -16
  43. package/server/services/sqlite/exportService.js +4 -16
  44. package/server/services/sqlite/overviewService.js +34 -0
  45. package/server/services/sqlite/sqlExecutor.js +83 -63
  46. package/server/services/sqlite/tableDesigner/changeAnalysis.js +295 -0
  47. package/server/services/sqlite/tableDesigner/schemaMapping.js +233 -0
  48. package/server/services/sqlite/tableDesigner/sql.js +63 -0
  49. package/server/services/sqlite/tableDesigner/validation.js +245 -0
  50. package/server/services/sqlite/tableDesignerService.js +181 -0
  51. package/server/services/sqlite/tableSort.js +63 -0
  52. package/server/services/storage/appStateStore.js +674 -1
  53. package/server/services/storage/queryHistoryUtils.js +169 -0
  54. package/frontend/assets/mockups/data_edit.png +0 -0
  55. package/frontend/assets/mockups/graph_visualize.png +0 -0
  56. package/frontend/assets/mockups/overview.png +0 -0
@@ -0,0 +1,181 @@
1
+ const { mapSqliteError, ValidationError } = require("../../utils/errors");
2
+ const { getTableDetail } = require("./introspection");
3
+ const { analyzeTableDesignerChanges } = require("./tableDesigner/changeAnalysis");
4
+ const {
5
+ SUPPORTED_TABLE_DESIGNER_TYPES,
6
+ buildTableDesignerDraft,
7
+ listDesignerTables,
8
+ normalizeDesignerType,
9
+ } = require("./tableDesigner/schemaMapping");
10
+ const {
11
+ normalizeDraftPayload,
12
+ validateTableDesignerDraft,
13
+ } = require("./tableDesigner/validation");
14
+ const { buildCreateTableSql, buildInsertRowsSql } = require("./tableDesigner/sql");
15
+
16
+ function getImportedFillColumns(draft) {
17
+ return draft.columns.filter(
18
+ (column) => !column.deleted && Number.isInteger(column.importedValueIndex)
19
+ );
20
+ }
21
+
22
+ function coerceImportedCellValue(column, value) {
23
+ const textValue = String(value ?? "");
24
+ const trimmedValue = textValue.trim();
25
+ const type = normalizeDesignerType(column.type);
26
+
27
+ if (!trimmedValue) {
28
+ return ["TEXT", "DATE", "DATETIME"].includes(type) ? "" : null;
29
+ }
30
+
31
+ if (type === "BOOLEAN") {
32
+ const normalized = trimmedValue.toLowerCase();
33
+
34
+ if (["true", "yes", "1"].includes(normalized)) {
35
+ return 1;
36
+ }
37
+
38
+ if (["false", "no", "0"].includes(normalized)) {
39
+ return 0;
40
+ }
41
+ }
42
+
43
+ if (type === "INTEGER" && /^-?\d+$/.test(trimmedValue)) {
44
+ return Number(trimmedValue);
45
+ }
46
+
47
+ if (
48
+ ["REAL", "NUMERIC"].includes(type) &&
49
+ /^-?(?:\d+|\d*\.\d+)(?:e[+-]?\d+)?$/i.test(trimmedValue)
50
+ ) {
51
+ return Number(trimmedValue);
52
+ }
53
+
54
+ return textValue;
55
+ }
56
+
57
+ class TableDesignerService {
58
+ constructor({ connectionManager }) {
59
+ this.connectionManager = connectionManager;
60
+ }
61
+
62
+ getOverview() {
63
+ const db = this.connectionManager.getActiveDatabase();
64
+
65
+ return {
66
+ tables: listDesignerTables(db),
67
+ supportedTypes: SUPPORTED_TABLE_DESIGNER_TYPES,
68
+ };
69
+ }
70
+
71
+ getTableDraft(tableName) {
72
+ const db = this.connectionManager.getActiveDatabase();
73
+ const tableDetail = getTableDetail(db, tableName, { includeRowCount: false });
74
+
75
+ return {
76
+ draft: buildTableDesignerDraft(tableDetail),
77
+ supportedTypes: SUPPORTED_TABLE_DESIGNER_TYPES,
78
+ };
79
+ }
80
+
81
+ saveDraft(payload = {}) {
82
+ this.connectionManager.assertWritable();
83
+
84
+ const db = this.connectionManager.getActiveDatabase();
85
+ const draft = normalizeDraftPayload(payload.draft ?? payload);
86
+ const catalogTables = listDesignerTables(db);
87
+ const originalDraft =
88
+ draft.mode === "edit"
89
+ ? buildTableDesignerDraft(
90
+ getTableDetail(db, draft.originalTableName, { includeRowCount: false })
91
+ )
92
+ : null;
93
+
94
+ validateTableDesignerDraft(draft, {
95
+ catalogTables,
96
+ originalDraft,
97
+ });
98
+
99
+ const analysis = analyzeTableDesignerChanges({ draft, originalDraft });
100
+
101
+ if (!analysis.dirty) {
102
+ return {
103
+ savedTableName: draft.mode === "edit" ? draft.originalTableName : draft.tableName,
104
+ executedSql: [],
105
+ draft: originalDraft ?? draft,
106
+ tables: catalogTables,
107
+ };
108
+ }
109
+
110
+ if (!analysis.executable) {
111
+ throw new ValidationError(
112
+ "This schema change set would require a SQLite table rebuild. Table Designer v1 keeps the SQL preview available but will not execute those changes automatically.",
113
+ {
114
+ code: "TABLE_DESIGNER_REBUILD_REQUIRED",
115
+ warnings: analysis.warnings,
116
+ }
117
+ );
118
+ }
119
+
120
+ const executeStatements = db.transaction((statements) => {
121
+ statements.forEach((statement) => {
122
+ db.exec(statement);
123
+ });
124
+ });
125
+
126
+ const executeCreateWithImport = db.transaction((nextDraft) => {
127
+ db.exec(buildCreateTableSql(nextDraft));
128
+
129
+ if (!nextDraft.fillImportedRows || !nextDraft.importedCsvRows.length) {
130
+ return;
131
+ }
132
+
133
+ const importedColumns = getImportedFillColumns(nextDraft);
134
+
135
+ if (!importedColumns.length) {
136
+ return;
137
+ }
138
+
139
+ const insertStatement = db.prepare(
140
+ buildInsertRowsSql(
141
+ nextDraft.tableName,
142
+ importedColumns.map((column) => column.name)
143
+ )
144
+ );
145
+
146
+ nextDraft.importedCsvRows.forEach((row) => {
147
+ insertStatement.run(
148
+ importedColumns.map((column) =>
149
+ coerceImportedCellValue(column, row[column.importedValueIndex] ?? "")
150
+ )
151
+ );
152
+ });
153
+ });
154
+
155
+ try {
156
+ if (draft.mode === "create") {
157
+ executeCreateWithImport(draft);
158
+ } else {
159
+ executeStatements(analysis.statements);
160
+ }
161
+ } catch (error) {
162
+ throw mapSqliteError(error);
163
+ }
164
+
165
+ const savedTableName = draft.tableName;
166
+ const nextDraft = buildTableDesignerDraft(
167
+ getTableDetail(db, savedTableName, { includeRowCount: false })
168
+ );
169
+
170
+ return {
171
+ savedTableName,
172
+ executedSql: analysis.statements,
173
+ draft: nextDraft,
174
+ tables: listDesignerTables(db),
175
+ };
176
+ }
177
+ }
178
+
179
+ module.exports = {
180
+ TableDesignerService,
181
+ };
@@ -0,0 +1,63 @@
1
+ const { ValidationError } = require("../../utils/errors");
2
+ const { quoteIdentifier } = require("../../utils/identifier");
3
+
4
+ function normalizeSortDirection(value) {
5
+ const normalized = String(value ?? "").trim().toLowerCase();
6
+ return normalized === "desc" ? "desc" : "asc";
7
+ }
8
+
9
+ function buildDefaultOrderClause(tableDetail) {
10
+ if (tableDetail.identityStrategy?.type === "rowid") {
11
+ return "rowid ASC";
12
+ }
13
+
14
+ if (tableDetail.identityStrategy?.type === "primaryKey") {
15
+ return tableDetail.identityStrategy.columns
16
+ .map((columnName) => `${quoteIdentifier(columnName)} ASC`)
17
+ .join(", ");
18
+ }
19
+
20
+ return "";
21
+ }
22
+
23
+ function normalizeTableSort(tableDetail, options = {}) {
24
+ const sortColumn = typeof options.sortColumn === "string" ? options.sortColumn.trim() : "";
25
+
26
+ if (!sortColumn) {
27
+ return {
28
+ column: null,
29
+ direction: null,
30
+ };
31
+ }
32
+
33
+ const availableColumns = (tableDetail.columns ?? [])
34
+ .filter((column) => column.visible !== false)
35
+ .map((column) => column.name);
36
+
37
+ if (!availableColumns.includes(sortColumn)) {
38
+ throw new ValidationError(`sortColumn must reference a visible column on ${tableDetail.name}.`);
39
+ }
40
+
41
+ return {
42
+ column: sortColumn,
43
+ direction: normalizeSortDirection(options.sortDirection),
44
+ };
45
+ }
46
+
47
+ function buildTableOrderClause(tableDetail, sort = {}) {
48
+ const defaultOrderClause = buildDefaultOrderClause(tableDetail);
49
+
50
+ if (!sort.column) {
51
+ return defaultOrderClause;
52
+ }
53
+
54
+ const primarySort = `${quoteIdentifier(sort.column)} ${sort.direction.toUpperCase()}`;
55
+
56
+ return [primarySort, defaultOrderClause].filter(Boolean).join(", ");
57
+ }
58
+
59
+ module.exports = {
60
+ buildTableOrderClause,
61
+ normalizeSortDirection,
62
+ normalizeTableSort,
63
+ };