sqlite-hub 0.9.13 → 0.11.1

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.
@@ -30,7 +30,8 @@ const DEFAULT_SETTINGS = {
30
30
  csvDelimiter: ',',
31
31
  };
32
32
  const DEFAULT_DATA_PAGE_SIZE = 50;
33
- const DATA_PAGE_SIZES = [25, 50, 100];
33
+ const DATA_PAGE_SIZES = [25, 50, 100, 250];
34
+ const DATA_FILTER_OPERATORS = new Set(['=', '!=', '<', '>', '<=', '>=', 'equals']);
34
35
  const DATA_ROW_SIZE_STORAGE_KEY = 'data_row_size';
35
36
  const CHARTS_HISTORY_TAB_STORAGE_KEY = 'charts_history_tab';
36
37
  const QUERY_HISTORY_TAB_STORAGE_KEY = 'query_history_tab';
@@ -38,6 +39,7 @@ const UI_PREFERENCE_STORAGE_KEYS = {
38
39
  sqlEditorHistoryVisible: 'sqlite_hub_sql_editor_history_visible',
39
40
  sqlEditorEditorVisible: 'sqlite_hub_sql_editor_editor_visible',
40
41
  sqlEditorActiveTab: 'sqlite_hub_sql_editor_active_tab',
42
+ sqlEditorQueryDraft: 'sqlite_hub_sql_editor_query_draft',
41
43
  dataTablesVisible: 'sqlite_hub_data_tables_visible',
42
44
  structureTablesVisible: 'sqlite_hub_structure_tables_visible',
43
45
  chartsHistoryVisible: 'sqlite_hub_charts_history_visible',
@@ -103,6 +105,23 @@ function storeBoolean(key, value) {
103
105
  }
104
106
  }
105
107
 
108
+ function readStoredString(key, fallback = '') {
109
+ try {
110
+ const value = globalThis.localStorage?.getItem(key);
111
+ return value === null || value === undefined ? fallback : value;
112
+ } catch {
113
+ return fallback;
114
+ }
115
+ }
116
+
117
+ function storeString(key, value) {
118
+ try {
119
+ globalThis.localStorage?.setItem(key, String(value ?? ''));
120
+ } catch {
121
+ // Ignore unavailable browser storage; the in-memory value still applies.
122
+ }
123
+ }
124
+
106
125
  function readStoredEditorActiveTab(fallback = 'messages') {
107
126
  try {
108
127
  const value = globalThis.localStorage?.getItem(UI_PREFERENCE_STORAGE_KEYS.sqlEditorActiveTab);
@@ -193,7 +212,9 @@ const state = {
193
212
  sortColumn: null,
194
213
  sortDirection: null,
195
214
  searchQuery: '',
215
+ tableSearchQuery: '',
196
216
  searchColumn: '',
217
+ filterOperator: '=',
197
218
  selectedRowIndex: null,
198
219
  selectedRow: null,
199
220
  pendingOpenRow: null,
@@ -202,7 +223,7 @@ const state = {
202
223
  saveError: null,
203
224
  },
204
225
  editor: {
205
- sqlText: '',
226
+ sqlText: readStoredString(UI_PREFERENCE_STORAGE_KEYS.sqlEditorQueryDraft),
206
227
  editorPanelVisible: readStoredBoolean(UI_PREFERENCE_STORAGE_KEYS.sqlEditorEditorVisible, true),
207
228
  history: [],
208
229
  historyPanelVisible: readStoredBoolean(UI_PREFERENCE_STORAGE_KEYS.sqlEditorHistoryVisible, true),
@@ -271,6 +292,7 @@ const state = {
271
292
  selectedName: null,
272
293
  detail: null,
273
294
  tablesVisible: readStoredBoolean(UI_PREFERENCE_STORAGE_KEYS.structureTablesVisible, true),
295
+ tableSearchQuery: '',
274
296
  loading: false,
275
297
  detailLoading: false,
276
298
  error: null,
@@ -525,6 +547,27 @@ function canEditQueryResult(snapshot = state) {
525
547
  function resetDataBrowserSearch() {
526
548
  state.dataBrowser.searchQuery = '';
527
549
  state.dataBrowser.searchColumn = '';
550
+ state.dataBrowser.filterOperator = '=';
551
+ }
552
+
553
+ function isDataBrowserTextColumn(columnName) {
554
+ const column = (state.dataBrowser.table?.columnMeta ?? []).find(item => item.name === columnName);
555
+
556
+ return String(column?.affinity ?? '').toUpperCase() === 'TEXT';
557
+ }
558
+
559
+ function normalizeDataFilterOperatorForColumn(operator, columnName) {
560
+ const normalizedOperator = DATA_FILTER_OPERATORS.has(operator) ? operator : '=';
561
+
562
+ if (normalizedOperator === 'equals' && !isDataBrowserTextColumn(columnName)) {
563
+ return '=';
564
+ }
565
+
566
+ return normalizedOperator;
567
+ }
568
+
569
+ function resetDataBrowserTableSearch() {
570
+ state.dataBrowser.tableSearchQuery = '';
528
571
  }
529
572
 
530
573
  function resetDataBrowserSort() {
@@ -707,7 +750,11 @@ function clearDataBrowserRowSelectionState() {
707
750
  }
708
751
 
709
752
  function resolveDataBrowserRowSelection(rowIndex, identity = null) {
710
- const numericIndex = Number(rowIndex);
753
+ const hasRowIndex =
754
+ rowIndex !== null &&
755
+ rowIndex !== undefined &&
756
+ (typeof rowIndex !== 'string' || rowIndex.trim() !== '');
757
+ const numericIndex = hasRowIndex ? Number(rowIndex) : NaN;
711
758
 
712
759
  if (Number.isInteger(numericIndex) && numericIndex >= 0) {
713
760
  const indexedRow = state.dataBrowser.table?.rows?.[numericIndex] ?? null;
@@ -951,6 +998,7 @@ function setMissingDatabaseState() {
951
998
  state.dataBrowser.selectedTable = null;
952
999
  state.dataBrowser.table = null;
953
1000
  state.dataBrowser.page = 1;
1001
+ resetDataBrowserTableSearch();
954
1002
  resetDataBrowserSort();
955
1003
  resetDataBrowserSearch();
956
1004
  clearDataBrowserRowSelectionState();
@@ -964,6 +1012,7 @@ function setMissingDatabaseState() {
964
1012
  state.structure.data = null;
965
1013
  state.structure.detail = null;
966
1014
  state.structure.tablesVisible = readStoredBoolean(UI_PREFERENCE_STORAGE_KEYS.structureTablesVisible, true);
1015
+ state.structure.tableSearchQuery = '';
967
1016
  state.structure.error = error;
968
1017
 
969
1018
  state.tableDesigner.loading = false;
@@ -1482,6 +1531,9 @@ async function loadDataTable(version) {
1482
1531
  offset: (page - 1) * pageSize,
1483
1532
  sortColumn,
1484
1533
  sortDirection,
1534
+ filterColumn: state.dataBrowser.searchColumn,
1535
+ filterOperator: state.dataBrowser.filterOperator,
1536
+ filterValue: state.dataBrowser.searchQuery,
1485
1537
  });
1486
1538
 
1487
1539
  if (version !== routeLoadVersion) {
@@ -1493,9 +1545,22 @@ async function loadDataTable(version) {
1493
1545
  state.dataBrowser.page = response.data?.page ?? page;
1494
1546
  state.dataBrowser.sortColumn = response.data?.sort?.column ?? null;
1495
1547
  state.dataBrowser.sortDirection = response.data?.sort?.direction ?? null;
1496
- state.dataBrowser.searchColumn = response.data?.columns?.includes(state.dataBrowser.searchColumn)
1497
- ? state.dataBrowser.searchColumn
1498
- : (response.data?.columns?.[0] ?? '');
1548
+ const responseColumns = response.data?.columns ?? [];
1549
+ const responseFilter = response.data?.filter ?? null;
1550
+
1551
+ state.dataBrowser.searchColumn =
1552
+ responseFilter?.column ??
1553
+ (responseColumns.includes(state.dataBrowser.searchColumn)
1554
+ ? state.dataBrowser.searchColumn
1555
+ : (responseColumns[0] ?? ''));
1556
+ const responseOperator = DATA_FILTER_OPERATORS.has(responseFilter?.operator)
1557
+ ? responseFilter.operator
1558
+ : state.dataBrowser.filterOperator;
1559
+
1560
+ state.dataBrowser.filterOperator = normalizeDataFilterOperatorForColumn(
1561
+ responseOperator,
1562
+ state.dataBrowser.searchColumn,
1563
+ );
1499
1564
  clearDataBrowserRowSelectionState();
1500
1565
  await resolvePendingDataBrowserRow(version);
1501
1566
  } catch (error) {
@@ -1919,6 +1984,7 @@ function invalidateDatabaseCaches() {
1919
1984
  state.dataBrowser.selectedTable = null;
1920
1985
  state.dataBrowser.table = null;
1921
1986
  state.dataBrowser.page = 1;
1987
+ resetDataBrowserTableSearch();
1922
1988
  resetDataBrowserSearch();
1923
1989
  clearDataBrowserRowSelectionState();
1924
1990
  state.dataBrowser.pendingOpenRow = null;
@@ -1939,6 +2005,7 @@ function invalidateDatabaseCaches() {
1939
2005
  state.structure.data = null;
1940
2006
  state.structure.detail = null;
1941
2007
  state.structure.tablesVisible = readStoredBoolean(UI_PREFERENCE_STORAGE_KEYS.structureTablesVisible, true);
2008
+ state.structure.tableSearchQuery = '';
1942
2009
  state.mediaTagging.loading = false;
1943
2010
  state.mediaTagging.previewLoading = false;
1944
2011
  state.mediaTagging.saving = false;
@@ -2356,6 +2423,92 @@ export function closeModal() {
2356
2423
  closeModalInternal();
2357
2424
  }
2358
2425
 
2426
+ export async function openDataRowUpdatePreview(rowIndex, values, identity = null) {
2427
+ const tableName = state.dataBrowser.selectedTable;
2428
+ const selected = resolveDataBrowserRowSelection(rowIndex, identity);
2429
+
2430
+ if (!tableName || !selected.identity) {
2431
+ pushToast('The selected row could not be loaded.', 'alert');
2432
+ return null;
2433
+ }
2434
+
2435
+ state.dataBrowser.saving = true;
2436
+ state.dataBrowser.saveError = null;
2437
+ emitChange();
2438
+
2439
+ try {
2440
+ const response = await api.previewDataTableRowUpdate(tableName, {
2441
+ identity: selected.identity,
2442
+ values,
2443
+ });
2444
+
2445
+ state.modal = {
2446
+ kind: 'row-update-preview',
2447
+ target: 'data',
2448
+ tableName,
2449
+ rowIndex: selected.rowIndex,
2450
+ identity: selected.identity,
2451
+ values,
2452
+ preview: response.data,
2453
+ error: null,
2454
+ submitting: false,
2455
+ };
2456
+ emitChange();
2457
+ return response.data;
2458
+ } catch (error) {
2459
+ state.dataBrowser.saveError = normalizeError(error);
2460
+ emitChange();
2461
+ return null;
2462
+ } finally {
2463
+ state.dataBrowser.saving = false;
2464
+ emitChange();
2465
+ }
2466
+ }
2467
+
2468
+ export async function openEditorRowUpdatePreview(rowIndex, values) {
2469
+ const numericIndex = Number(rowIndex);
2470
+ const result = state.editor.result;
2471
+ const row = result?.rows?.[numericIndex];
2472
+ const tableName = result?.editing?.tableName ?? null;
2473
+
2474
+ if (!tableName || !row || !canEditQueryResult()) {
2475
+ pushToast('The selected query result row could not be loaded.', 'alert');
2476
+ return null;
2477
+ }
2478
+
2479
+ state.editor.saving = true;
2480
+ state.editor.saveError = null;
2481
+ emitChange();
2482
+
2483
+ try {
2484
+ const response = await api.previewDataTableRowUpdate(tableName, {
2485
+ identity: row.__identity,
2486
+ values,
2487
+ });
2488
+
2489
+ state.modal = {
2490
+ kind: 'row-update-preview',
2491
+ target: 'editor',
2492
+ tableName,
2493
+ rowIndex: numericIndex,
2494
+ identity: row.__identity,
2495
+ values,
2496
+ preview: response.data,
2497
+ error: null,
2498
+ submitting: false,
2499
+ };
2500
+ emitChange();
2501
+ return response.data;
2502
+ } catch (error) {
2503
+ state.editor.saveError = normalizeError(error);
2504
+ emitChange();
2505
+ return null;
2506
+ } finally {
2507
+ state.editor.saving = false;
2508
+ emitChange();
2509
+ }
2510
+ }
2511
+
2359
2512
  export function toggleChartsSqlPanel() {
2360
2513
  state.charts.sqlExpanded = !state.charts.sqlExpanded;
2361
2514
  emitChange();
@@ -2623,6 +2776,7 @@ export function setCurrentQuery(query) {
2623
2776
  const nextLineCount = Math.max(1, nextQuery.split('\n').length);
2624
2777
 
2625
2778
  state.editor.sqlText = nextQuery;
2779
+ storeString(UI_PREFERENCE_STORAGE_KEYS.sqlEditorQueryDraft, nextQuery);
2626
2780
 
2627
2781
  if (previousLineCount !== nextLineCount) {
2628
2782
  emitChange();
@@ -2631,6 +2785,7 @@ export function setCurrentQuery(query) {
2631
2785
 
2632
2786
  export function clearCurrentQuery() {
2633
2787
  state.editor.sqlText = '';
2788
+ storeString(UI_PREFERENCE_STORAGE_KEYS.sqlEditorQueryDraft, '');
2634
2789
  state.editor.result = null;
2635
2790
  state.editor.lastExecutedSql = '';
2636
2791
  resetEditorResultSort();
@@ -2696,6 +2851,8 @@ export async function executeCurrentQuery() {
2696
2851
  return true;
2697
2852
  } catch (error) {
2698
2853
  state.editor.error = normalizeError(error);
2854
+ state.editor.activeTab = 'messages';
2855
+ storeEditorActiveTab('messages');
2699
2856
  await refreshQueryHistoryState();
2700
2857
  return false;
2701
2858
  } finally {
@@ -2819,6 +2976,7 @@ export function openQueryHistoryInEditor(historyId, options = {}) {
2819
2976
  setActiveQueryHistoryItem(historyId);
2820
2977
  clearQueryHistoryDetailState();
2821
2978
  state.editor.sqlText = options.append ? [state.editor.sqlText.trim(), rawSql].filter(Boolean).join('\n\n') : rawSql;
2979
+ storeString(UI_PREFERENCE_STORAGE_KEYS.sqlEditorQueryDraft, state.editor.sqlText);
2822
2980
  emitChange();
2823
2981
  return true;
2824
2982
  }
@@ -2926,6 +3084,16 @@ export function setTableDesignerSearchQuery(query) {
2926
3084
  emitChange();
2927
3085
  }
2928
3086
 
3087
+ export function setDataTableSearchQuery(query) {
3088
+ state.dataBrowser.tableSearchQuery = String(query ?? '');
3089
+ emitChange();
3090
+ }
3091
+
3092
+ export function setStructureTableSearchQuery(query) {
3093
+ state.structure.tableSearchQuery = String(query ?? '');
3094
+ emitChange();
3095
+ }
3096
+
2929
3097
  export function setTableDesignerSqlPreviewVisibility(visible) {
2930
3098
  const nextValue = typeof visible === 'boolean' ? visible : !Boolean(state.tableDesigner.sqlPreviewVisible);
2931
3099
 
@@ -3494,18 +3662,59 @@ export function clearDataRowSelection(options = {}) {
3494
3662
  }
3495
3663
  }
3496
3664
 
3497
- export function setDataSearchQuery(query) {
3498
- state.dataBrowser.searchQuery = String(query ?? '');
3665
+ async function reloadDataTableForFilterChange() {
3666
+ if (state.route.name === 'data' && state.dataBrowser.selectedTable) {
3667
+ await loadDataTable(++routeLoadVersion);
3668
+ }
3669
+ }
3670
+
3671
+ export async function setDataSearchQuery(query) {
3672
+ const nextQuery = String(query ?? '');
3673
+
3674
+ if (state.dataBrowser.searchQuery === nextQuery) {
3675
+ return;
3676
+ }
3677
+
3678
+ state.dataBrowser.searchQuery = nextQuery;
3679
+ state.dataBrowser.page = 1;
3680
+ clearDataBrowserRowSelectionState();
3681
+ state.dataBrowser.saveError = null;
3682
+ emitChange();
3683
+ await reloadDataTableForFilterChange();
3684
+ }
3685
+
3686
+ export async function setDataSearchColumn(columnName) {
3687
+ const nextColumnName = String(columnName ?? '');
3688
+
3689
+ if (state.dataBrowser.searchColumn === nextColumnName) {
3690
+ return;
3691
+ }
3692
+
3693
+ state.dataBrowser.searchColumn = nextColumnName;
3694
+ state.dataBrowser.filterOperator = normalizeDataFilterOperatorForColumn(
3695
+ state.dataBrowser.filterOperator,
3696
+ nextColumnName,
3697
+ );
3698
+ state.dataBrowser.page = 1;
3499
3699
  clearDataBrowserRowSelectionState();
3500
3700
  state.dataBrowser.saveError = null;
3501
3701
  emitChange();
3702
+ await reloadDataTableForFilterChange();
3502
3703
  }
3503
3704
 
3504
- export function setDataSearchColumn(columnName) {
3505
- state.dataBrowser.searchColumn = String(columnName ?? '');
3705
+ export async function setDataFilterOperator(operator) {
3706
+ const nextOperator = String(operator ?? '=').trim();
3707
+
3708
+ if (!DATA_FILTER_OPERATORS.has(nextOperator) || state.dataBrowser.filterOperator === nextOperator) {
3709
+ return;
3710
+ }
3711
+
3712
+ state.dataBrowser.filterOperator = nextOperator;
3713
+ state.dataBrowser.page = 1;
3506
3714
  clearDataBrowserRowSelectionState();
3507
3715
  state.dataBrowser.saveError = null;
3508
3716
  emitChange();
3717
+ await reloadDataTableForFilterChange();
3509
3718
  }
3510
3719
 
3511
3720
  export function toggleDataTablesPanel() {
@@ -3577,9 +3786,10 @@ export async function setDataPageSize(pageSize) {
3577
3786
  }
3578
3787
  }
3579
3788
 
3580
- export async function submitDataRowUpdate(rowIndex, values, identity = null) {
3789
+ export async function submitDataRowUpdate(rowIndex, values, identity = null, options = {}) {
3581
3790
  const tableName = state.dataBrowser.selectedTable;
3582
3791
  const selected = resolveDataBrowserRowSelection(rowIndex, identity);
3792
+ const reportErrorToModal = Boolean(options.reportErrorToModal);
3583
3793
 
3584
3794
  if (!tableName || !selected.identity) {
3585
3795
  pushToast('The selected row could not be loaded.', 'alert');
@@ -3601,8 +3811,12 @@ export async function submitDataRowUpdate(rowIndex, values, identity = null) {
3601
3811
  clearDataBrowserRowSelectionState();
3602
3812
  return response.data;
3603
3813
  } catch (error) {
3604
- state.dataBrowser.saveError = normalizeError(error);
3605
- emitChange();
3814
+ if (reportErrorToModal) {
3815
+ withModalError(error);
3816
+ } else {
3817
+ state.dataBrowser.saveError = normalizeError(error);
3818
+ emitChange();
3819
+ }
3606
3820
  return null;
3607
3821
  } finally {
3608
3822
  state.dataBrowser.saving = false;
@@ -3654,11 +3868,12 @@ export async function submitDataRowDelete(rowIndex, options = {}) {
3654
3868
  }
3655
3869
  }
3656
3870
 
3657
- export async function submitEditorRowUpdate(rowIndex, values) {
3871
+ export async function submitEditorRowUpdate(rowIndex, values, options = {}) {
3658
3872
  const numericIndex = Number(rowIndex);
3659
3873
  const result = state.editor.result;
3660
3874
  const row = result?.rows?.[numericIndex];
3661
3875
  const tableName = result?.editing?.tableName ?? null;
3876
+ const reportErrorToModal = Boolean(options.reportErrorToModal);
3662
3877
 
3663
3878
  if (!tableName || !row || !canEditQueryResult()) {
3664
3879
  pushToast('The selected query result row could not be loaded.', 'alert');
@@ -3688,8 +3903,12 @@ export async function submitEditorRowUpdate(rowIndex, values) {
3688
3903
  emitChange();
3689
3904
  return response.data;
3690
3905
  } catch (error) {
3691
- state.editor.saveError = normalizeError(error);
3692
- emitChange();
3906
+ if (reportErrorToModal) {
3907
+ withModalError(error);
3908
+ } else {
3909
+ state.editor.saveError = normalizeError(error);
3910
+ emitChange();
3911
+ }
3693
3912
  return null;
3694
3913
  } finally {
3695
3914
  state.editor.saving = false;
@@ -3767,6 +3986,29 @@ export async function submitDeleteRowConfirmation() {
3767
3986
  return result;
3768
3987
  }
3769
3988
 
3989
+ export async function submitRowUpdatePreviewConfirmation() {
3990
+ const modal = state.modal;
3991
+
3992
+ if (modal?.kind !== 'row-update-preview') {
3993
+ return null;
3994
+ }
3995
+
3996
+ startModalSubmission();
3997
+
3998
+ const result =
3999
+ modal.target === 'editor'
4000
+ ? await submitEditorRowUpdate(modal.rowIndex, modal.values, { reportErrorToModal: true })
4001
+ : await submitDataRowUpdate(modal.rowIndex, modal.values, modal.identity, {
4002
+ reportErrorToModal: true,
4003
+ });
4004
+
4005
+ if (result) {
4006
+ closeModalInternal();
4007
+ }
4008
+
4009
+ return result;
4010
+ }
4011
+
3770
4012
  export async function saveCurrentQueryChartDraft() {
3771
4013
  if (state.modal?.kind !== 'chart-editor' || !state.modal.draft) {
3772
4014
  return null;
@@ -3988,12 +4230,12 @@ export function getQueryMessages(snapshot = state) {
3988
4230
 
3989
4231
  if (snapshot.editor.error) {
3990
4232
  return [
3991
- ...queryMessages,
3992
4233
  {
3993
4234
  tone: 'alert',
3994
4235
  label: snapshot.editor.error.code,
3995
4236
  value: snapshot.editor.error.message,
3996
4237
  },
4238
+ ...queryMessages,
3997
4239
  ];
3998
4240
  }
3999
4241
 
@@ -4008,7 +4250,6 @@ export function getQueryMessages(snapshot = state) {
4008
4250
  }
4009
4251
 
4010
4252
  return [
4011
- ...queryMessages,
4012
4253
  ...snapshot.editor.result.statements.map(statement => ({
4013
4254
  tone: statement.kind === 'resultSet' ? 'success' : inferStatusTone(statement.keyword),
4014
4255
  label: `${statement.keyword} #${statement.index + 1}`,
@@ -4017,6 +4258,7 @@ export function getQueryMessages(snapshot = state) {
4017
4258
  ? `${statement.rowCount} row(s) returned.`
4018
4259
  : `${statement.changes} row(s) affected.`,
4019
4260
  })),
4261
+ ...queryMessages,
4020
4262
  ];
4021
4263
  }
4022
4264