sqlite-hub 0.5.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 (36) hide show
  1. package/README.md +2 -2
  2. package/changelog.md +8 -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 +15 -0
  12. package/frontend/js/app.js +279 -8
  13. package/frontend/js/components/queryEditor.js +2 -2
  14. package/frontend/js/components/sidebar.js +1 -0
  15. package/frontend/js/components/tableDesignerEditor.js +356 -0
  16. package/frontend/js/components/tableDesignerSidebar.js +126 -0
  17. package/frontend/js/components/tableDesignerSqlPreview.js +40 -0
  18. package/frontend/js/router.js +10 -0
  19. package/frontend/js/store.js +296 -1
  20. package/frontend/js/utils/tableDesigner.js +1192 -0
  21. package/frontend/js/views/data.js +253 -263
  22. package/frontend/js/views/tableDesigner.js +37 -0
  23. package/frontend/styles/base.css +87 -73
  24. package/frontend/styles/components.css +798 -251
  25. package/frontend/styles/views.css +40 -0
  26. package/package.json +1 -1
  27. package/server/routes/tableDesigner.js +60 -0
  28. package/server/server.js +4 -0
  29. package/server/services/sqlite/tableDesigner/changeAnalysis.js +295 -0
  30. package/server/services/sqlite/tableDesigner/schemaMapping.js +233 -0
  31. package/server/services/sqlite/tableDesigner/sql.js +63 -0
  32. package/server/services/sqlite/tableDesigner/validation.js +245 -0
  33. package/server/services/sqlite/tableDesignerService.js +181 -0
  34. package/frontend/assets/mockups/data_edit.png +0 -0
  35. package/frontend/assets/mockups/graph_visualize.png +0 -0
  36. package/frontend/assets/mockups/overview.png +0 -0
@@ -1,5 +1,14 @@
1
1
  import * as api from "./api.js";
2
2
  import { formatCellValue, inferStatusTone, truncateMiddle } from "./utils/format.js";
3
+ import {
4
+ addTableDesignerColumn,
5
+ createTableDesignerDraftFromCsvImport,
6
+ createNewTableDesignerDraft,
7
+ hydrateTableDesignerDraft,
8
+ removeTableDesignerColumn,
9
+ updateTableDesignerColumnField,
10
+ updateTableDesignerDraftField,
11
+ } from "./utils/tableDesigner.js";
3
12
 
4
13
  const listeners = new Set();
5
14
  const DEFAULT_SETTINGS = {
@@ -92,6 +101,19 @@ const state = {
92
101
  deleting: false,
93
102
  saveError: null,
94
103
  },
104
+ tableDesigner: {
105
+ tables: [],
106
+ selectedTableName: null,
107
+ draft: null,
108
+ pendingImportedDraft: null,
109
+ loading: false,
110
+ detailLoading: false,
111
+ saving: false,
112
+ searchQuery: "",
113
+ supportedTypes: [],
114
+ error: null,
115
+ saveError: null,
116
+ },
95
117
  structure: {
96
118
  data: null,
97
119
  selectedName: null,
@@ -145,7 +167,14 @@ function clearQueryHistoryDetailState() {
145
167
  }
146
168
 
147
169
  function requiresActiveDatabase(routeName) {
148
- return ["overview", "data", "editor", "editorResults", "structure"].includes(routeName);
170
+ return [
171
+ "overview",
172
+ "data",
173
+ "editor",
174
+ "editorResults",
175
+ "structure",
176
+ "tableDesigner",
177
+ ].includes(routeName);
149
178
  }
150
179
 
151
180
  function normalizeDataPageSize(value, fallback = 50) {
@@ -270,6 +299,22 @@ function getCurrentStructureEntry(snapshot = state) {
270
299
  return entries.find((entry) => entry.name === snapshot.structure.selectedName) ?? null;
271
300
  }
272
301
 
302
+ function getTableDesignerContext(snapshot = state) {
303
+ return {
304
+ catalogTables: snapshot.tableDesigner.tables ?? [],
305
+ supportedTypes: snapshot.tableDesigner.supportedTypes ?? [],
306
+ readOnly: Boolean(snapshot.connections.active?.readOnly),
307
+ };
308
+ }
309
+
310
+ function decorateTableDesignerDraft(draft, snapshot = state) {
311
+ if (!draft) {
312
+ return null;
313
+ }
314
+
315
+ return hydrateTableDesignerDraft(draft, getTableDesignerContext(snapshot));
316
+ }
317
+
273
318
  function buildDeleteRowPreview(fields = []) {
274
319
  return fields
275
320
  .filter((field) => field && field.label)
@@ -358,6 +403,8 @@ function clearRouteSlices() {
358
403
  state.overview.error = null;
359
404
  state.dataBrowser.error = null;
360
405
  state.dataBrowser.saveError = null;
406
+ state.tableDesigner.error = null;
407
+ state.tableDesigner.saveError = null;
361
408
  state.structure.error = null;
362
409
  }
363
410
 
@@ -387,6 +434,18 @@ function setMissingDatabaseState() {
387
434
  state.structure.detail = null;
388
435
  state.structure.error = error;
389
436
 
437
+ state.tableDesigner.loading = false;
438
+ state.tableDesigner.detailLoading = false;
439
+ state.tableDesigner.tables = [];
440
+ state.tableDesigner.selectedTableName = null;
441
+ state.tableDesigner.draft = null;
442
+ state.tableDesigner.pendingImportedDraft = null;
443
+ state.tableDesigner.saving = false;
444
+ state.tableDesigner.searchQuery = "";
445
+ state.tableDesigner.supportedTypes = [];
446
+ state.tableDesigner.error = error;
447
+ state.tableDesigner.saveError = null;
448
+
390
449
  resetQueryHistoryState({ preserveSearch: false });
391
450
  }
392
451
 
@@ -423,6 +482,10 @@ function syncRouteContext() {
423
482
  state.structure.detail = null;
424
483
  state.structure.selectedName = null;
425
484
  }
485
+
486
+ if (route.name !== "tableDesigner") {
487
+ state.tableDesigner.saveError = null;
488
+ }
426
489
  }
427
490
 
428
491
  async function refreshConnectionsState() {
@@ -824,6 +887,103 @@ async function loadStructure(version) {
824
887
  }
825
888
  }
826
889
 
890
+ async function loadTableDesignerDetail(version, tableName) {
891
+ if (!tableName) {
892
+ state.tableDesigner.draft = null;
893
+ return;
894
+ }
895
+
896
+ state.tableDesigner.detailLoading = true;
897
+ state.tableDesigner.saveError = null;
898
+ emitChange();
899
+
900
+ try {
901
+ const response = await api.getTableDesignerTable(tableName);
902
+
903
+ if (version !== routeLoadVersion) {
904
+ return;
905
+ }
906
+
907
+ state.tableDesigner.selectedTableName = tableName;
908
+ state.tableDesigner.draft = decorateTableDesignerDraft(response.data?.draft ?? null);
909
+ } catch (error) {
910
+ if (version !== routeLoadVersion) {
911
+ return;
912
+ }
913
+
914
+ state.tableDesigner.error = normalizeError(error);
915
+ state.tableDesigner.draft = null;
916
+ } finally {
917
+ if (version === routeLoadVersion) {
918
+ state.tableDesigner.detailLoading = false;
919
+ emitChange();
920
+ }
921
+ }
922
+ }
923
+
924
+ async function loadTableDesigner(version, route) {
925
+ state.tableDesigner.loading = true;
926
+ state.tableDesigner.error = null;
927
+ emitChange();
928
+
929
+ try {
930
+ const response = await api.getTableDesignerOverview();
931
+
932
+ if (version !== routeLoadVersion) {
933
+ return;
934
+ }
935
+
936
+ state.tableDesigner.tables = response.data?.tables ?? [];
937
+ state.tableDesigner.supportedTypes = response.data?.supportedTypes ?? [];
938
+
939
+ if (route.params?.isNew) {
940
+ const importedDraft = state.tableDesigner.pendingImportedDraft;
941
+ state.tableDesigner.selectedTableName = null;
942
+ state.tableDesigner.detailLoading = false;
943
+ state.tableDesigner.pendingImportedDraft = null;
944
+ state.tableDesigner.draft = decorateTableDesignerDraft(
945
+ importedDraft ?? createNewTableDesignerDraft()
946
+ );
947
+ return;
948
+ }
949
+
950
+ const requestedTableName = route.params?.tableName ?? null;
951
+ const tableName =
952
+ requestedTableName &&
953
+ state.tableDesigner.tables.some((table) => table.name === requestedTableName)
954
+ ? requestedTableName
955
+ : state.tableDesigner.selectedTableName &&
956
+ state.tableDesigner.tables.some(
957
+ (table) => table.name === state.tableDesigner.selectedTableName
958
+ )
959
+ ? state.tableDesigner.selectedTableName
960
+ : state.tableDesigner.tables[0]?.name ?? null;
961
+
962
+ if (!tableName) {
963
+ state.tableDesigner.selectedTableName = null;
964
+ state.tableDesigner.detailLoading = false;
965
+ state.tableDesigner.draft = null;
966
+ return;
967
+ }
968
+
969
+ await loadTableDesignerDetail(version, tableName);
970
+ } catch (error) {
971
+ if (version !== routeLoadVersion) {
972
+ return;
973
+ }
974
+
975
+ state.tableDesigner.tables = [];
976
+ state.tableDesigner.selectedTableName = null;
977
+ state.tableDesigner.draft = null;
978
+ state.tableDesigner.error = normalizeError(error);
979
+ } finally {
980
+ if (version === routeLoadVersion) {
981
+ state.tableDesigner.loading = false;
982
+ emitChange();
983
+ }
984
+ }
985
+ }
986
+
827
987
  function invalidateDatabaseCaches() {
828
988
  state.overview.data = null;
829
989
  state.dataBrowser.tables = [];
@@ -835,6 +995,15 @@ function invalidateDatabaseCaches() {
835
995
  state.dataBrowser.exportLoading = false;
836
996
  state.dataBrowser.error = null;
837
997
  state.dataBrowser.saveError = null;
998
+ state.tableDesigner.tables = [];
999
+ state.tableDesigner.selectedTableName = null;
1000
+ state.tableDesigner.draft = null;
1001
+ state.tableDesigner.pendingImportedDraft = null;
1002
+ state.tableDesigner.saving = false;
1003
+ state.tableDesigner.searchQuery = "";
1004
+ state.tableDesigner.supportedTypes = [];
1005
+ state.tableDesigner.error = null;
1006
+ state.tableDesigner.saveError = null;
838
1007
  state.structure.data = null;
839
1008
  state.structure.detail = null;
840
1009
  }
@@ -869,6 +1038,9 @@ async function loadRouteData(route) {
869
1038
  case "structure":
870
1039
  await loadStructure(version);
871
1040
  return;
1041
+ case "tableDesigner":
1042
+ await loadTableDesigner(version, route);
1043
+ return;
872
1044
  case "settings":
873
1045
  await refreshSettingsState();
874
1046
  return;
@@ -1450,6 +1622,125 @@ export async function selectStructureEntry(name) {
1450
1622
  await loadStructureDetail(++routeLoadVersion);
1451
1623
  }
1452
1624
 
1625
+ export function setTableDesignerSearchQuery(query) {
1626
+ state.tableDesigner.searchQuery = String(query ?? "");
1627
+ emitChange();
1628
+ }
1629
+
1630
+ export function updateCurrentTableDesignerField(field, value) {
1631
+ if (!state.tableDesigner.draft) {
1632
+ return;
1633
+ }
1634
+
1635
+ state.tableDesigner.draft = updateTableDesignerDraftField(
1636
+ state.tableDesigner.draft,
1637
+ field,
1638
+ value,
1639
+ getTableDesignerContext()
1640
+ );
1641
+ state.tableDesigner.saveError = null;
1642
+ emitChange();
1643
+ }
1644
+
1645
+ export function updateCurrentTableDesignerColumnField(columnId, field, value) {
1646
+ if (!state.tableDesigner.draft) {
1647
+ return;
1648
+ }
1649
+
1650
+ state.tableDesigner.draft = updateTableDesignerColumnField(
1651
+ state.tableDesigner.draft,
1652
+ columnId,
1653
+ field,
1654
+ value,
1655
+ getTableDesignerContext()
1656
+ );
1657
+ state.tableDesigner.saveError = null;
1658
+ emitChange();
1659
+ }
1660
+
1661
+ export function addCurrentTableDesignerColumn() {
1662
+ if (!state.tableDesigner.draft) {
1663
+ return null;
1664
+ }
1665
+
1666
+ const previousColumnIds = new Set(state.tableDesigner.draft.columns.map((column) => column.id));
1667
+ state.tableDesigner.draft = addTableDesignerColumn(
1668
+ state.tableDesigner.draft,
1669
+ getTableDesignerContext()
1670
+ );
1671
+ state.tableDesigner.saveError = null;
1672
+ emitChange();
1673
+
1674
+ const nextColumn = state.tableDesigner.draft.columns.find(
1675
+ (column) => !previousColumnIds.has(column.id)
1676
+ );
1677
+
1678
+ return nextColumn?.id ?? null;
1679
+ }
1680
+
1681
+ export function queueTableDesignerCsvImport(fileName, csvText) {
1682
+ try {
1683
+ const imported = createTableDesignerDraftFromCsvImport(
1684
+ { fileName, csvText },
1685
+ getTableDesignerContext()
1686
+ );
1687
+
1688
+ state.tableDesigner.pendingImportedDraft = imported.draft;
1689
+ state.tableDesigner.selectedTableName = null;
1690
+ state.tableDesigner.saveError = null;
1691
+ state.tableDesigner.error = null;
1692
+ emitChange();
1693
+ return imported;
1694
+ } catch (error) {
1695
+ pushToast(error?.message || "CSV import failed.", "alert");
1696
+ return null;
1697
+ }
1698
+ }
1699
+
1700
+ export function removeCurrentTableDesignerColumn(columnId) {
1701
+ if (!state.tableDesigner.draft) {
1702
+ return;
1703
+ }
1704
+
1705
+ state.tableDesigner.draft = removeTableDesignerColumn(
1706
+ state.tableDesigner.draft,
1707
+ columnId,
1708
+ getTableDesignerContext()
1709
+ );
1710
+ state.tableDesigner.saveError = null;
1711
+ emitChange();
1712
+ }
1713
+
1714
+ export async function saveCurrentTableDesignerDraft() {
1715
+ if (!state.tableDesigner.draft) {
1716
+ return null;
1717
+ }
1718
+
1719
+ state.tableDesigner.saving = true;
1720
+ state.tableDesigner.saveError = null;
1721
+ emitChange();
1722
+
1723
+ try {
1724
+ const response = await api.saveTableDesignerDraft({
1725
+ draft: state.tableDesigner.draft,
1726
+ });
1727
+
1728
+ state.tableDesigner.tables = response.data?.tables ?? state.tableDesigner.tables;
1729
+ state.tableDesigner.selectedTableName =
1730
+ response.data?.savedTableName ?? state.tableDesigner.selectedTableName;
1731
+ state.tableDesigner.draft = decorateTableDesignerDraft(response.data?.draft ?? null);
1732
+ pushToast(response.message || "Table schema saved.", "success");
1733
+ return response.data?.savedTableName ?? null;
1734
+ } catch (error) {
1735
+ state.tableDesigner.saveError = normalizeError(error);
1736
+ emitChange();
1737
+ return null;
1738
+ } finally {
1739
+ state.tableDesigner.saving = false;
1740
+ emitChange();
1741
+ }
1742
+ }
1743
+
1453
1744
  export function selectDataRow(index) {
1454
1745
  const numericIndex = Number(index);
1455
1746
 
@@ -1845,6 +2136,10 @@ export async function refreshCurrentRoute() {
1845
2136
  await loadRouteData(state.route);
1846
2137
  }
1847
2138
 
2139
+ export function showToast(message, tone = "muted") {
2140
+ pushToast(message, tone);
2141
+ }
2142
+
1848
2143
  export function getCurrentConnection(snapshot = state) {
1849
2144
  return snapshot.connections.active;
1850
2145
  }