sqlite-hub 0.10.0 → 0.12.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.
@@ -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';
@@ -49,6 +50,11 @@ const QUERY_HISTORY_PAGE_SIZE = 30;
49
50
  const QUERY_HISTORY_RUN_LIMIT = 8;
50
51
  const CHART_HEIGHT_PRESETS = new Set(['small', 'medium', 'large']);
51
52
  const EDITOR_RESULT_TABS = new Set(['results', 'performance', 'messages']);
53
+ const TEXT_EXPORT_FORMAT_LABELS = {
54
+ csv: 'CSV',
55
+ tsv: 'TSV',
56
+ md: 'Markdown',
57
+ };
52
58
  const MISSING_DATABASE_ERROR = {
53
59
  code: 'ACTIVE_DATABASE_REQUIRED',
54
60
  message: 'No active SQLite database selected.',
@@ -213,6 +219,7 @@ const state = {
213
219
  searchQuery: '',
214
220
  tableSearchQuery: '',
215
221
  searchColumn: '',
222
+ filterOperator: '=',
216
223
  selectedRowIndex: null,
217
224
  selectedRow: null,
218
225
  pendingOpenRow: null,
@@ -545,6 +552,23 @@ function canEditQueryResult(snapshot = state) {
545
552
  function resetDataBrowserSearch() {
546
553
  state.dataBrowser.searchQuery = '';
547
554
  state.dataBrowser.searchColumn = '';
555
+ state.dataBrowser.filterOperator = '=';
556
+ }
557
+
558
+ function isDataBrowserTextColumn(columnName) {
559
+ const column = (state.dataBrowser.table?.columnMeta ?? []).find(item => item.name === columnName);
560
+
561
+ return String(column?.affinity ?? '').toUpperCase() === 'TEXT';
562
+ }
563
+
564
+ function normalizeDataFilterOperatorForColumn(operator, columnName) {
565
+ const normalizedOperator = DATA_FILTER_OPERATORS.has(operator) ? operator : '=';
566
+
567
+ if (normalizedOperator === 'equals' && !isDataBrowserTextColumn(columnName)) {
568
+ return '=';
569
+ }
570
+
571
+ return normalizedOperator;
548
572
  }
549
573
 
550
574
  function resetDataBrowserTableSearch() {
@@ -1512,6 +1536,9 @@ async function loadDataTable(version) {
1512
1536
  offset: (page - 1) * pageSize,
1513
1537
  sortColumn,
1514
1538
  sortDirection,
1539
+ filterColumn: state.dataBrowser.searchColumn,
1540
+ filterOperator: state.dataBrowser.filterOperator,
1541
+ filterValue: state.dataBrowser.searchQuery,
1515
1542
  });
1516
1543
 
1517
1544
  if (version !== routeLoadVersion) {
@@ -1523,9 +1550,22 @@ async function loadDataTable(version) {
1523
1550
  state.dataBrowser.page = response.data?.page ?? page;
1524
1551
  state.dataBrowser.sortColumn = response.data?.sort?.column ?? null;
1525
1552
  state.dataBrowser.sortDirection = response.data?.sort?.direction ?? null;
1526
- state.dataBrowser.searchColumn = response.data?.columns?.includes(state.dataBrowser.searchColumn)
1527
- ? state.dataBrowser.searchColumn
1528
- : (response.data?.columns?.[0] ?? '');
1553
+ const responseColumns = response.data?.columns ?? [];
1554
+ const responseFilter = response.data?.filter ?? null;
1555
+
1556
+ state.dataBrowser.searchColumn =
1557
+ responseFilter?.column ??
1558
+ (responseColumns.includes(state.dataBrowser.searchColumn)
1559
+ ? state.dataBrowser.searchColumn
1560
+ : (responseColumns[0] ?? ''));
1561
+ const responseOperator = DATA_FILTER_OPERATORS.has(responseFilter?.operator)
1562
+ ? responseFilter.operator
1563
+ : state.dataBrowser.filterOperator;
1564
+
1565
+ state.dataBrowser.filterOperator = normalizeDataFilterOperatorForColumn(
1566
+ responseOperator,
1567
+ state.dataBrowser.searchColumn,
1568
+ );
1529
1569
  clearDataBrowserRowSelectionState();
1530
1570
  await resolvePendingDataBrowserRow(version);
1531
1571
  } catch (error) {
@@ -1943,14 +1983,20 @@ async function previewMediaTaggingDraft(options = {}) {
1943
1983
  }
1944
1984
  }
1945
1985
 
1946
- function invalidateDatabaseCaches() {
1986
+ function invalidateDatabaseCaches(options = {}) {
1987
+ const preserveDataBrowserState = options.preserveDataBrowserState === true;
1988
+
1947
1989
  state.overview.data = null;
1948
1990
  state.dataBrowser.tables = [];
1949
- state.dataBrowser.selectedTable = null;
1991
+ if (!preserveDataBrowserState) {
1992
+ state.dataBrowser.selectedTable = null;
1993
+ }
1950
1994
  state.dataBrowser.table = null;
1951
- state.dataBrowser.page = 1;
1952
- resetDataBrowserTableSearch();
1953
- resetDataBrowserSearch();
1995
+ if (!preserveDataBrowserState) {
1996
+ state.dataBrowser.page = 1;
1997
+ resetDataBrowserTableSearch();
1998
+ resetDataBrowserSearch();
1999
+ }
1954
2000
  clearDataBrowserRowSelectionState();
1955
2001
  state.dataBrowser.pendingOpenRow = null;
1956
2002
  state.dataBrowser.exportLoading = false;
@@ -2218,6 +2264,34 @@ export function openModal(kind) {
2218
2264
  emitChange();
2219
2265
  }
2220
2266
 
2267
+ export function openQueryExportModal() {
2268
+ if (!String(state.editor.sqlText ?? '').trim()) {
2269
+ pushToast('Enter a query before exporting.', 'alert');
2270
+ return;
2271
+ }
2272
+
2273
+ state.modal = {
2274
+ kind: 'query-export',
2275
+ error: null,
2276
+ submitting: false,
2277
+ };
2278
+ emitChange();
2279
+ }
2280
+
2281
+ export function openDataExportModal() {
2282
+ if (!state.dataBrowser.selectedTable) {
2283
+ pushToast('No table selected for export.', 'alert');
2284
+ return;
2285
+ }
2286
+
2287
+ state.modal = {
2288
+ kind: 'data-export',
2289
+ error: null,
2290
+ submitting: false,
2291
+ };
2292
+ emitChange();
2293
+ }
2294
+
2221
2295
  export function openEditConnectionModal(id) {
2222
2296
  const connection = state.connections.recent.find(entry => entry.id === id);
2223
2297
 
@@ -2810,7 +2884,7 @@ export async function executeCurrentQuery() {
2810
2884
  state.editor.result = response.data;
2811
2885
  resetEditorResultSort();
2812
2886
  state.editor.error = null;
2813
- invalidateDatabaseCaches();
2887
+ invalidateDatabaseCaches({ preserveDataBrowserState: true });
2814
2888
  await refreshQueryHistoryState();
2815
2889
  pushToast(response.message || `Executed ${response.data.statementCount} SQL statement(s).`, 'success');
2816
2890
  return true;
@@ -3123,9 +3197,15 @@ export function addCurrentTableDesignerColumn() {
3123
3197
  return nextColumn?.id ?? null;
3124
3198
  }
3125
3199
 
3126
- export function queueTableDesignerCsvImport(fileName, csvText) {
3200
+ export function queueTableDesignerCsvImport(fileName, csvText, options = {}) {
3127
3201
  try {
3128
- const imported = createTableDesignerDraftFromCsvImport({ fileName, csvText }, getTableDesignerContext());
3202
+ const imported = createTableDesignerDraftFromCsvImport(
3203
+ { fileName, csvText },
3204
+ {
3205
+ ...getTableDesignerContext(),
3206
+ ...(options.context ?? {}),
3207
+ },
3208
+ );
3129
3209
 
3130
3210
  state.tableDesigner.pendingImportedDraft = imported.draft;
3131
3211
  state.tableDesigner.selectedTableName = null;
@@ -3134,6 +3214,10 @@ export function queueTableDesignerCsvImport(fileName, csvText) {
3134
3214
  emitChange();
3135
3215
  return imported;
3136
3216
  } catch (error) {
3217
+ if (options.throwOnError) {
3218
+ throw error;
3219
+ }
3220
+
3137
3221
  pushToast(error?.message || 'CSV import failed.', 'alert');
3138
3222
  return null;
3139
3223
  }
@@ -3627,18 +3711,59 @@ export function clearDataRowSelection(options = {}) {
3627
3711
  }
3628
3712
  }
3629
3713
 
3630
- export function setDataSearchQuery(query) {
3631
- state.dataBrowser.searchQuery = String(query ?? '');
3714
+ async function reloadDataTableForFilterChange() {
3715
+ if (state.route.name === 'data' && state.dataBrowser.selectedTable) {
3716
+ await loadDataTable(++routeLoadVersion);
3717
+ }
3718
+ }
3719
+
3720
+ export async function setDataSearchQuery(query) {
3721
+ const nextQuery = String(query ?? '');
3722
+
3723
+ if (state.dataBrowser.searchQuery === nextQuery) {
3724
+ return;
3725
+ }
3726
+
3727
+ state.dataBrowser.searchQuery = nextQuery;
3728
+ state.dataBrowser.page = 1;
3729
+ clearDataBrowserRowSelectionState();
3730
+ state.dataBrowser.saveError = null;
3731
+ emitChange();
3732
+ await reloadDataTableForFilterChange();
3733
+ }
3734
+
3735
+ export async function setDataSearchColumn(columnName) {
3736
+ const nextColumnName = String(columnName ?? '');
3737
+
3738
+ if (state.dataBrowser.searchColumn === nextColumnName) {
3739
+ return;
3740
+ }
3741
+
3742
+ state.dataBrowser.searchColumn = nextColumnName;
3743
+ state.dataBrowser.filterOperator = normalizeDataFilterOperatorForColumn(
3744
+ state.dataBrowser.filterOperator,
3745
+ nextColumnName,
3746
+ );
3747
+ state.dataBrowser.page = 1;
3632
3748
  clearDataBrowserRowSelectionState();
3633
3749
  state.dataBrowser.saveError = null;
3634
3750
  emitChange();
3751
+ await reloadDataTableForFilterChange();
3635
3752
  }
3636
3753
 
3637
- export function setDataSearchColumn(columnName) {
3638
- state.dataBrowser.searchColumn = String(columnName ?? '');
3754
+ export async function setDataFilterOperator(operator) {
3755
+ const nextOperator = String(operator ?? '=').trim();
3756
+
3757
+ if (!DATA_FILTER_OPERATORS.has(nextOperator) || state.dataBrowser.filterOperator === nextOperator) {
3758
+ return;
3759
+ }
3760
+
3761
+ state.dataBrowser.filterOperator = nextOperator;
3762
+ state.dataBrowser.page = 1;
3639
3763
  clearDataBrowserRowSelectionState();
3640
3764
  state.dataBrowser.saveError = null;
3641
3765
  emitChange();
3766
+ await reloadDataTableForFilterChange();
3642
3767
  }
3643
3768
 
3644
3769
  export function toggleDataTablesPanel() {
@@ -4056,25 +4181,127 @@ export async function submitDeleteQueryHistoryConfirmation() {
4056
4181
  return deleted;
4057
4182
  }
4058
4183
 
4059
- export async function exportCurrentQueryCsv() {
4184
+ function beginCurrentQueryExport() {
4060
4185
  state.editor.exportLoading = true;
4186
+ if (state.modal?.kind === 'query-export') {
4187
+ state.modal.submitting = true;
4188
+ state.modal.error = null;
4189
+ }
4061
4190
  emitChange();
4191
+ }
4192
+
4193
+ function reportCurrentQueryExportError(error) {
4194
+ if (state.modal?.kind === 'query-export') {
4195
+ withModalError(error);
4196
+ return;
4197
+ }
4198
+
4199
+ state.editor.error = normalizeError(error);
4200
+ emitChange();
4201
+ }
4202
+
4203
+ function finishCurrentQueryExport() {
4204
+ state.editor.exportLoading = false;
4205
+ if (state.modal?.kind === 'query-export') {
4206
+ state.modal.submitting = false;
4207
+ }
4208
+ emitChange();
4209
+ }
4210
+
4211
+ export async function exportCurrentQueryFormat(format = 'csv') {
4212
+ const normalizedFormat = String(format ?? 'csv').toLowerCase();
4213
+ const label = TEXT_EXPORT_FORMAT_LABELS[normalizedFormat] ?? TEXT_EXPORT_FORMAT_LABELS.csv;
4214
+
4215
+ beginCurrentQueryExport();
4062
4216
 
4063
4217
  try {
4064
- await api.downloadQueryCsv(state.editor.sqlText);
4065
- pushToast('Query export started.', 'success');
4218
+ await api.downloadQueryExport(state.editor.sqlText, normalizedFormat);
4219
+ closeModalInternal();
4220
+ pushToast(`${label} export started.`, 'success');
4066
4221
  return true;
4067
4222
  } catch (error) {
4068
- state.editor.error = normalizeError(error);
4069
- emitChange();
4223
+ reportCurrentQueryExportError(error);
4070
4224
  return false;
4071
4225
  } finally {
4072
- state.editor.exportLoading = false;
4226
+ finishCurrentQueryExport();
4227
+ }
4228
+ }
4229
+
4230
+ export async function duplicateCurrentQueryAsTable() {
4231
+ beginCurrentQueryExport();
4232
+
4233
+ try {
4234
+ const response = await api.getQueryExport(state.editor.sqlText, 'csv');
4235
+ const exportData = response?.data ?? {};
4236
+ const imported = queueTableDesignerCsvImport(
4237
+ exportData.filename || 'query-results.csv',
4238
+ exportData.content || '',
4239
+ { throwOnError: true },
4240
+ );
4241
+
4242
+ closeModalInternal();
4243
+ pushToast(
4244
+ `Table draft created from ${imported.importedRowCount} row${imported.importedRowCount === 1 ? '' : 's'}.`,
4245
+ 'success',
4246
+ );
4247
+ return imported;
4248
+ } catch (error) {
4249
+ reportCurrentQueryExportError(error);
4250
+ return null;
4251
+ } finally {
4252
+ finishCurrentQueryExport();
4253
+ }
4254
+ }
4255
+
4256
+ export async function exportCurrentQueryCsv() {
4257
+ try {
4258
+ return await exportCurrentQueryFormat('csv');
4259
+ } catch (error) {
4260
+ state.editor.error = normalizeError(error);
4073
4261
  emitChange();
4262
+ return false;
4074
4263
  }
4075
4264
  }
4076
4265
 
4077
- export async function exportCurrentDataTableCsv() {
4266
+ function getCurrentDataTableExportOptions(format = 'csv') {
4267
+ return {
4268
+ sortColumn: state.dataBrowser.sortColumn,
4269
+ sortDirection: state.dataBrowser.sortDirection,
4270
+ filterColumn: state.dataBrowser.searchColumn,
4271
+ filterOperator: state.dataBrowser.filterOperator,
4272
+ filterValue: state.dataBrowser.searchQuery,
4273
+ format,
4274
+ };
4275
+ }
4276
+
4277
+ function beginCurrentDataTableExport() {
4278
+ state.dataBrowser.exportLoading = true;
4279
+ if (state.modal?.kind === 'data-export') {
4280
+ state.modal.submitting = true;
4281
+ state.modal.error = null;
4282
+ }
4283
+ emitChange();
4284
+ }
4285
+
4286
+ function reportCurrentDataTableExportError(error) {
4287
+ if (state.modal?.kind === 'data-export') {
4288
+ withModalError(error);
4289
+ return;
4290
+ }
4291
+
4292
+ state.dataBrowser.error = normalizeError(error);
4293
+ emitChange();
4294
+ }
4295
+
4296
+ function finishCurrentDataTableExport() {
4297
+ state.dataBrowser.exportLoading = false;
4298
+ if (state.modal?.kind === 'data-export') {
4299
+ state.modal.submitting = false;
4300
+ }
4301
+ emitChange();
4302
+ }
4303
+
4304
+ export async function exportCurrentDataTableFormat(format = 'csv') {
4078
4305
  const tableName = state.dataBrowser.selectedTable;
4079
4306
 
4080
4307
  if (!tableName) {
@@ -4082,26 +4309,68 @@ export async function exportCurrentDataTableCsv() {
4082
4309
  return false;
4083
4310
  }
4084
4311
 
4085
- state.dataBrowser.exportLoading = true;
4086
- emitChange();
4312
+ const normalizedFormat = String(format ?? 'csv').toLowerCase();
4313
+ const label = TEXT_EXPORT_FORMAT_LABELS[normalizedFormat] ?? TEXT_EXPORT_FORMAT_LABELS.csv;
4314
+
4315
+ beginCurrentDataTableExport();
4087
4316
 
4088
4317
  try {
4089
- await api.downloadTableCsv(tableName, {
4090
- sortColumn: state.dataBrowser.sortColumn,
4091
- sortDirection: state.dataBrowser.sortDirection,
4092
- });
4093
- pushToast(`CSV export started for ${tableName}.`, 'success');
4318
+ await api.downloadTableExport(tableName, getCurrentDataTableExportOptions(normalizedFormat));
4319
+ closeModalInternal();
4320
+ pushToast(`${label} export started for ${tableName}.`, 'success');
4094
4321
  return true;
4095
4322
  } catch (error) {
4096
- state.dataBrowser.error = normalizeError(error);
4097
- emitChange();
4323
+ reportCurrentDataTableExportError(error);
4098
4324
  return false;
4099
4325
  } finally {
4100
- state.dataBrowser.exportLoading = false;
4101
- emitChange();
4326
+ finishCurrentDataTableExport();
4102
4327
  }
4103
4328
  }
4104
4329
 
4330
+ export async function duplicateCurrentDataTableAsTable() {
4331
+ const tableName = state.dataBrowser.selectedTable;
4332
+
4333
+ if (!tableName) {
4334
+ pushToast('No table selected for export.', 'alert');
4335
+ return null;
4336
+ }
4337
+
4338
+ beginCurrentDataTableExport();
4339
+
4340
+ try {
4341
+ const response = await api.getTableExport(tableName, getCurrentDataTableExportOptions('csv'));
4342
+ const exportData = response?.data ?? {};
4343
+ const imported = queueTableDesignerCsvImport(
4344
+ exportData.filename || `${tableName}.csv`,
4345
+ exportData.content || '',
4346
+ {
4347
+ throwOnError: true,
4348
+ context: {
4349
+ catalogTables: state.tableDesigner.tables?.length
4350
+ ? state.tableDesigner.tables
4351
+ : state.dataBrowser.tables,
4352
+ },
4353
+ },
4354
+ );
4355
+
4356
+ closeModalInternal();
4357
+ pushToast(
4358
+ `Table draft created from ${imported.importedRowCount} row${imported.importedRowCount === 1 ? '' : 's'}.`,
4359
+ 'success',
4360
+ );
4361
+ return imported;
4362
+ } catch (error) {
4363
+ reportCurrentDataTableExportError(error);
4364
+ return null;
4365
+ } finally {
4366
+ finishCurrentDataTableExport();
4367
+ }
4368
+ }
4369
+
4370
+ export async function exportCurrentDataTableCsv() {
4371
+ return exportCurrentDataTableFormat('csv');
4372
+ }
4373
+
4105
4374
  export function sortEditorResultsByColumn(columnName) {
4106
4375
  const normalizedColumn = String(columnName ?? '').trim();
4107
4376
  const result = state.editor.result;