sqlite-hub 1.4.0 → 1.5.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 (40) hide show
  1. package/README.md +9 -0
  2. package/bin/sqlite-hub.js +555 -122
  3. package/docs/API.md +30 -0
  4. package/docs/CLI.md +22 -0
  5. package/docs/CLI_API_PARITY.md +61 -0
  6. package/docs/changelog.md +9 -1
  7. package/docs/guidelines/AGENTS.md +32 -0
  8. package/docs/{DESIGN_GUIDELINES.md → guidelines/DESIGN.md} +10 -0
  9. package/docs/todo.md +1 -13
  10. package/frontend/js/api.js +45 -0
  11. package/frontend/js/app.js +192 -2
  12. package/frontend/js/components/connectionCard.js +3 -1
  13. package/frontend/js/components/modal.js +356 -15
  14. package/frontend/js/components/sidebar.js +41 -7
  15. package/frontend/js/components/topNav.js +2 -14
  16. package/frontend/js/router.js +10 -0
  17. package/frontend/js/store.js +448 -0
  18. package/frontend/js/utils/inputClear.js +36 -0
  19. package/frontend/js/utils/syntheticData.js +240 -0
  20. package/frontend/js/views/backups.js +52 -0
  21. package/frontend/js/views/data.js +16 -0
  22. package/frontend/js/views/logs.js +339 -0
  23. package/frontend/js/views/settings.js +77 -27
  24. package/frontend/js/views/structure.js +6 -0
  25. package/frontend/js/views/tableAdvisor.js +385 -0
  26. package/frontend/js/views/tableDesigner.js +6 -0
  27. package/frontend/styles/components.css +149 -0
  28. package/frontend/styles/tailwind.generated.css +1 -1
  29. package/frontend/styles/views.css +8 -0
  30. package/package.json +1 -1
  31. package/server/routes/data.js +45 -0
  32. package/server/routes/externalApi.js +285 -2
  33. package/server/routes/logs.js +127 -0
  34. package/server/server.js +3 -0
  35. package/server/services/databaseCommandService.js +45 -0
  36. package/server/services/sqlite/dataBrowserService.js +36 -0
  37. package/server/services/sqlite/introspection.js +226 -22
  38. package/server/services/sqlite/syntheticDataGenerator.js +728 -0
  39. package/server/services/sqlite/tableAdvisor.js +790 -0
  40. package/server/services/storage/appStateStore.js +773 -169
@@ -23,6 +23,11 @@ import { MEDIA_TAGGING_DEFAULT_MAPPING_TABLE, MEDIA_TAGGING_DEFAULT_TAG_TABLE }
23
23
  import { buildTextExportFilename } from './utils/exportFilenames.js';
24
24
  import { toggleMarkdownTodoLine } from './utils/markdownDocuments.js';
25
25
  import { buildRiskySqlBackupContext, detectRiskySqlOperations } from './utils/riskySql.js';
26
+ import {
27
+ buildSyntheticDataMappings,
28
+ getDefaultSyntheticOptions,
29
+ normalizeSyntheticGeneratorType,
30
+ } from './utils/syntheticData.js';
26
31
 
27
32
  const listeners = new Set();
28
33
  const DEFAULT_SETTINGS = {
@@ -59,6 +64,13 @@ const UI_PREFERENCE_STORAGE_KEYS = {
59
64
  };
60
65
  const QUERY_HISTORY_PAGE_SIZE = 30;
61
66
  const QUERY_HISTORY_RUN_LIMIT = 8;
67
+ const LOG_PAGE_SIZE = 80;
68
+ const LOG_KINDS = new Set(['all', 'query', 'access']);
69
+ const LOG_RANGES = new Set(['1h', '24h', '7d', '30d', 'all']);
70
+ const LOG_ACTORS = new Set(['all', 'user', 'cli', 'api', 'mcp']);
71
+ const LOG_STATUSES = new Set(['all', 'success', 'error']);
72
+ const LOG_QUERY_TYPES = new Set(['all', 'select', 'insert', 'update', 'delete', 'pragma', 'create', 'alter', 'drop', 'other']);
73
+ const LOG_DESTRUCTIVE_FILTERS = new Set(['all', 'yes', 'no']);
62
74
  const CHART_HEIGHT_PRESETS = new Set(['small', 'medium', 'large']);
63
75
  const TYPE_GENERATION_TARGETS = new Set(['typescript', 'rust', 'kotlin', 'swift']);
64
76
  const TYPE_GENERATION_NAMING = new Set(['preserve', 'camel', 'pascal', 'snake']);
@@ -258,6 +270,24 @@ function storeQueryHistoryTab(tab) {
258
270
  }
259
271
  }
260
272
 
273
+ function normalizeSetValue(value, allowedValues, fallback) {
274
+ const normalized = String(value ?? fallback).trim().toLowerCase();
275
+ return allowedValues.has(normalized) ? normalized : fallback;
276
+ }
277
+
278
+ function normalizeLogFilters(filters = {}) {
279
+ return {
280
+ kind: normalizeSetValue(filters.kind, LOG_KINDS, 'all'),
281
+ range: normalizeSetValue(filters.range, LOG_RANGES, 'all'),
282
+ actor: normalizeSetValue(filters.actor, LOG_ACTORS, 'all'),
283
+ status: normalizeSetValue(filters.status, LOG_STATUSES, 'all'),
284
+ queryType: normalizeSetValue(filters.queryType, LOG_QUERY_TYPES, 'all'),
285
+ destructive: normalizeSetValue(filters.destructive, LOG_DESTRUCTIVE_FILTERS, 'all'),
286
+ searchInput: String(filters.searchInput ?? filters.search ?? ''),
287
+ search: String(filters.search ?? filters.searchInput ?? '').trim(),
288
+ };
289
+ }
290
+
261
291
  const state = {
262
292
  ready: false,
263
293
  route: { name: 'landing', path: '/', params: {} },
@@ -308,6 +338,18 @@ const state = {
308
338
  loading: false,
309
339
  error: null,
310
340
  },
341
+ logs: {
342
+ items: [],
343
+ total: 0,
344
+ limit: LOG_PAGE_SIZE,
345
+ offset: 0,
346
+ hasMore: false,
347
+ loading: false,
348
+ loadingMore: false,
349
+ error: null,
350
+ filters: normalizeLogFilters(),
351
+ metadata: null,
352
+ },
311
353
  dataBrowser: {
312
354
  tables: [],
313
355
  selectedTable: null,
@@ -332,6 +374,15 @@ const state = {
332
374
  error: null,
333
375
  saveError: null,
334
376
  },
377
+ tableAdvisor: {
378
+ tables: [],
379
+ selectedTableName: null,
380
+ result: null,
381
+ loading: false,
382
+ analysisLoading: false,
383
+ error: null,
384
+ analysisError: null,
385
+ },
335
386
  editor: {
336
387
  sqlText: readStoredString(UI_PREFERENCE_STORAGE_KEYS.sqlEditorQueryDraft),
337
388
  editorPanelVisible: readStoredBoolean(UI_PREFERENCE_STORAGE_KEYS.sqlEditorEditorVisible, true),
@@ -622,6 +673,7 @@ function requiresActiveDatabase(routeName) {
622
673
  return [
623
674
  'overview',
624
675
  'backups',
676
+ 'logs',
625
677
  'data',
626
678
  'editor',
627
679
  'editorResults',
@@ -629,6 +681,7 @@ function requiresActiveDatabase(routeName) {
629
681
  'documents',
630
682
  'structure',
631
683
  'tableDesigner',
684
+ 'tableAdvisor',
632
685
  'mediaTaggingSetup',
633
686
  'mediaTaggingQueue',
634
687
  ].includes(routeName);
@@ -1290,6 +1343,7 @@ function resolveQueryHistorySql(historyId) {
1290
1343
 
1291
1344
  function clearRouteSlices() {
1292
1345
  state.overview.error = null;
1346
+ state.logs.error = null;
1293
1347
  state.dataBrowser.error = null;
1294
1348
  state.dataBrowser.saveError = null;
1295
1349
  state.charts.error = null;
@@ -1297,6 +1351,8 @@ function clearRouteSlices() {
1297
1351
  state.charts.resultError = null;
1298
1352
  state.tableDesigner.error = null;
1299
1353
  state.tableDesigner.saveError = null;
1354
+ state.tableAdvisor.error = null;
1355
+ state.tableAdvisor.analysisError = null;
1300
1356
  state.structure.error = null;
1301
1357
  state.mediaTagging.error = null;
1302
1358
  state.documents.error = null;
@@ -1310,6 +1366,15 @@ function setMissingDatabaseState() {
1310
1366
  state.overview.data = null;
1311
1367
  state.overview.error = error;
1312
1368
 
1369
+ state.logs.loading = false;
1370
+ state.logs.loadingMore = false;
1371
+ state.logs.items = [];
1372
+ state.logs.total = 0;
1373
+ state.logs.offset = 0;
1374
+ state.logs.hasMore = false;
1375
+ state.logs.error = error;
1376
+ state.logs.metadata = null;
1377
+
1313
1378
  state.dataBrowser.loading = false;
1314
1379
  state.dataBrowser.tableLoading = false;
1315
1380
  state.dataBrowser.tables = [];
@@ -1350,6 +1415,14 @@ function setMissingDatabaseState() {
1350
1415
  state.tableDesigner.error = error;
1351
1416
  state.tableDesigner.saveError = null;
1352
1417
 
1418
+ state.tableAdvisor.loading = false;
1419
+ state.tableAdvisor.analysisLoading = false;
1420
+ state.tableAdvisor.tables = [];
1421
+ state.tableAdvisor.selectedTableName = null;
1422
+ state.tableAdvisor.result = null;
1423
+ state.tableAdvisor.error = error;
1424
+ state.tableAdvisor.analysisError = null;
1425
+
1353
1426
  state.mediaTagging.loading = false;
1354
1427
  state.mediaTagging.previewLoading = false;
1355
1428
  state.mediaTagging.saving = false;
@@ -1495,6 +1568,65 @@ async function refreshSettingsState() {
1495
1568
  }
1496
1569
  }
1497
1570
 
1571
+ function buildLogRequestOptions({ append = false } = {}) {
1572
+ const filters = normalizeLogFilters(state.logs.filters);
1573
+
1574
+ return {
1575
+ ...filters,
1576
+ search: filters.search,
1577
+ limit: state.logs.limit,
1578
+ offset: append ? state.logs.items.length : 0,
1579
+ };
1580
+ }
1581
+
1582
+ async function loadLogs(options = {}) {
1583
+ const append = Boolean(options.append);
1584
+
1585
+ if (append && (state.logs.loading || state.logs.loadingMore || !state.logs.hasMore)) {
1586
+ return;
1587
+ }
1588
+
1589
+ if (append) {
1590
+ state.logs.loadingMore = true;
1591
+ } else {
1592
+ state.logs.loading = true;
1593
+ state.logs.offset = 0;
1594
+ state.logs.error = null;
1595
+ }
1596
+
1597
+ emitChange();
1598
+
1599
+ try {
1600
+ const response = await api.getLogs(buildLogRequestOptions({ append }));
1601
+ const data = response.data ?? {};
1602
+ const items = Array.isArray(data.items) ? data.items : [];
1603
+
1604
+ state.logs.items = append ? [...state.logs.items, ...items] : items;
1605
+ state.logs.total = Number(data.total ?? state.logs.items.length);
1606
+ state.logs.limit = Number(data.limit ?? state.logs.limit);
1607
+ state.logs.offset = Number(data.offset ?? 0);
1608
+ state.logs.hasMore = Boolean(data.hasMore);
1609
+ state.logs.metadata = response.metadata ?? data.filters ?? null;
1610
+ state.logs.filters = normalizeLogFilters({
1611
+ ...state.logs.filters,
1612
+ ...(data.filters ?? {}),
1613
+ ...(response.metadata ?? {}),
1614
+ });
1615
+ state.logs.error = null;
1616
+ } catch (error) {
1617
+ state.logs.error = normalizeError(error);
1618
+ if (!append) {
1619
+ state.logs.items = [];
1620
+ state.logs.total = 0;
1621
+ state.logs.hasMore = false;
1622
+ }
1623
+ } finally {
1624
+ state.logs.loading = false;
1625
+ state.logs.loadingMore = false;
1626
+ emitChange();
1627
+ }
1628
+ }
1629
+
1498
1630
  async function refreshBackupsState() {
1499
1631
  if (!state.connections.active) {
1500
1632
  state.backups.items = [];
@@ -1552,6 +1684,48 @@ export function setSettingsSection(section) {
1552
1684
  emitChange();
1553
1685
  }
1554
1686
 
1687
+ export async function setLogFilter(field, value) {
1688
+ if (!Object.prototype.hasOwnProperty.call(state.logs.filters, field)) {
1689
+ return;
1690
+ }
1691
+
1692
+ state.logs.filters = normalizeLogFilters({
1693
+ ...state.logs.filters,
1694
+ [field]: value,
1695
+ });
1696
+ state.logs.items = [];
1697
+ state.logs.total = 0;
1698
+ state.logs.hasMore = false;
1699
+ await loadLogs();
1700
+ }
1701
+
1702
+ export function setLogSearchInput(value) {
1703
+ state.logs.filters = normalizeLogFilters({
1704
+ ...state.logs.filters,
1705
+ searchInput: value,
1706
+ });
1707
+ }
1708
+
1709
+ export async function applyLogSearch(value = state.logs.filters.searchInput) {
1710
+ state.logs.filters = normalizeLogFilters({
1711
+ ...state.logs.filters,
1712
+ searchInput: value,
1713
+ search: value,
1714
+ });
1715
+ state.logs.items = [];
1716
+ state.logs.total = 0;
1717
+ state.logs.hasMore = false;
1718
+ await loadLogs();
1719
+ }
1720
+
1721
+ export async function refreshLogs() {
1722
+ await loadLogs();
1723
+ }
1724
+
1725
+ export async function loadMoreLogs() {
1726
+ await loadLogs({ append: true });
1727
+ }
1728
+
1555
1729
  export async function checkSettingsAppVersion() {
1556
1730
  state.settings.versionCheckLoading = true;
1557
1731
  state.settings.versionCheckError = null;
@@ -2224,6 +2398,94 @@ async function loadData(version, route) {
2224
2398
  }
2225
2399
  }
2226
2400
 
2401
+ async function loadTableAdvisorResult(version, tableName) {
2402
+ if (!tableName) {
2403
+ state.tableAdvisor.result = null;
2404
+ state.tableAdvisor.analysisError = null;
2405
+ state.tableAdvisor.analysisLoading = false;
2406
+ return;
2407
+ }
2408
+
2409
+ state.tableAdvisor.analysisLoading = true;
2410
+ state.tableAdvisor.analysisError = null;
2411
+ state.tableAdvisor.result = null;
2412
+ emitChange();
2413
+
2414
+ try {
2415
+ const response = await api.analyzeTableAdvisor(tableName);
2416
+
2417
+ if (version !== routeLoadVersion) {
2418
+ return;
2419
+ }
2420
+
2421
+ state.tableAdvisor.result = response.data ?? null;
2422
+ state.tableAdvisor.analysisError = null;
2423
+ } catch (error) {
2424
+ if (version !== routeLoadVersion) {
2425
+ return;
2426
+ }
2427
+
2428
+ state.tableAdvisor.result = null;
2429
+ state.tableAdvisor.analysisError = normalizeError(error);
2430
+ } finally {
2431
+ if (version === routeLoadVersion) {
2432
+ state.tableAdvisor.analysisLoading = false;
2433
+ emitChange();
2434
+ }
2435
+ }
2436
+ }
2437
+
2438
+ async function loadTableAdvisor(version, route) {
2439
+ state.tableAdvisor.loading = true;
2440
+ state.tableAdvisor.error = null;
2441
+ state.tableAdvisor.analysisError = null;
2442
+ emitChange();
2443
+
2444
+ try {
2445
+ const response = await api.getDataTables();
2446
+
2447
+ if (version !== routeLoadVersion) {
2448
+ return;
2449
+ }
2450
+
2451
+ const tables = response.data?.tables ?? [];
2452
+ const requestedTableName = route.params?.tableName ?? null;
2453
+ const selectedTableName =
2454
+ requestedTableName && tables.some(table => table.name === requestedTableName)
2455
+ ? requestedTableName
2456
+ : (state.tableAdvisor.selectedTableName && tables.some(table => table.name === state.tableAdvisor.selectedTableName)
2457
+ ? state.tableAdvisor.selectedTableName
2458
+ : (tables[0]?.name ?? null));
2459
+
2460
+ state.tableAdvisor.tables = tables;
2461
+ state.tableAdvisor.selectedTableName = selectedTableName;
2462
+ state.tableAdvisor.error = null;
2463
+
2464
+ if (!selectedTableName) {
2465
+ state.tableAdvisor.result = null;
2466
+ return;
2467
+ }
2468
+
2469
+ state.tableAdvisor.loading = false;
2470
+ emitChange();
2471
+ await loadTableAdvisorResult(version, selectedTableName);
2472
+ } catch (error) {
2473
+ if (version !== routeLoadVersion) {
2474
+ return;
2475
+ }
2476
+
2477
+ state.tableAdvisor.tables = [];
2478
+ state.tableAdvisor.selectedTableName = null;
2479
+ state.tableAdvisor.result = null;
2480
+ state.tableAdvisor.error = normalizeError(error);
2481
+ } finally {
2482
+ if (version === routeLoadVersion) {
2483
+ state.tableAdvisor.loading = false;
2484
+ emitChange();
2485
+ }
2486
+ }
2487
+ }
2488
+
2227
2489
  async function loadStructureDetail(version) {
2228
2490
  const entry = getCurrentStructureEntry(state);
2229
2491
 
@@ -2671,6 +2933,9 @@ async function loadRouteData(route, options = {}) {
2671
2933
  case 'backups':
2672
2934
  await refreshBackupsState();
2673
2935
  return;
2936
+ case 'logs':
2937
+ await loadLogs();
2938
+ return;
2674
2939
  case 'data':
2675
2940
  await loadData(version, route);
2676
2941
  return;
@@ -2690,6 +2955,9 @@ async function loadRouteData(route, options = {}) {
2690
2955
  case 'tableDesigner':
2691
2956
  await loadTableDesigner(version, route);
2692
2957
  return;
2958
+ case 'tableAdvisor':
2959
+ await loadTableAdvisor(version, route);
2960
+ return;
2693
2961
  case 'mediaTaggingSetup':
2694
2962
  case 'mediaTaggingQueue':
2695
2963
  await loadMediaTagging(version);
@@ -2892,6 +3160,186 @@ export function openDataExportModal() {
2892
3160
  emitChange();
2893
3161
  }
2894
3162
 
3163
+ function getGenerateDataModalColumn(modal, columnName) {
3164
+ return (modal?.columns ?? []).find(column => column.name === columnName) ?? null;
3165
+ }
3166
+
3167
+ function resetGenerateDataPreview(modal) {
3168
+ if (!modal) {
3169
+ return;
3170
+ }
3171
+
3172
+ modal.previewRows = [];
3173
+ modal.previewColumns = (modal.columns ?? []).map(column => column.name);
3174
+ }
3175
+
3176
+ function buildGenerateDataPayload(modal) {
3177
+ return {
3178
+ rowCount: Number(modal.rowCount ?? 100),
3179
+ mappings: (modal.mappings ?? []).map(mapping => ({
3180
+ columnName: mapping.columnName,
3181
+ generator: mapping.generator,
3182
+ options: { ...(mapping.options ?? {}) },
3183
+ })),
3184
+ };
3185
+ }
3186
+
3187
+ export function openGenerateDataModal() {
3188
+ const table = state.dataBrowser.table;
3189
+ const tableName = table?.name ?? state.dataBrowser.selectedTable;
3190
+
3191
+ if (!tableName || !table) {
3192
+ pushToast('Select a table before generating rows.', 'alert');
3193
+ return;
3194
+ }
3195
+
3196
+ const columns = (table.columnMeta ?? []).filter(column => column.visible && !column.generated);
3197
+
3198
+ state.modal = {
3199
+ kind: 'generate-data',
3200
+ tableName,
3201
+ columns,
3202
+ rowCount: 100,
3203
+ mappings: buildSyntheticDataMappings(columns, table.foreignKeys ?? []),
3204
+ previewColumns: columns.map(column => column.name),
3205
+ previewRows: [],
3206
+ previewLoading: false,
3207
+ previewRequestId: 0,
3208
+ error: null,
3209
+ submitting: false,
3210
+ };
3211
+ emitChange();
3212
+ }
3213
+
3214
+ export function updateGenerateDataModal(field, value, options = {}) {
3215
+ if (state.modal?.kind !== 'generate-data') {
3216
+ return;
3217
+ }
3218
+
3219
+ if (field === 'rowCount') {
3220
+ const nextValue = String(value ?? '');
3221
+
3222
+ if (state.modal.rowCount === nextValue) {
3223
+ return;
3224
+ }
3225
+
3226
+ state.modal.rowCount = nextValue;
3227
+ state.modal.error = null;
3228
+ resetGenerateDataPreview(state.modal);
3229
+ if (options.notify !== false) {
3230
+ emitChange();
3231
+ }
3232
+ }
3233
+ }
3234
+
3235
+ export function updateGenerateDataMapping(columnName, field, value, options = {}) {
3236
+ const modal = state.modal;
3237
+
3238
+ if (modal?.kind !== 'generate-data') {
3239
+ return;
3240
+ }
3241
+
3242
+ const mapping = (modal.mappings ?? []).find(item => item.columnName === columnName);
3243
+
3244
+ if (!mapping) {
3245
+ return;
3246
+ }
3247
+
3248
+ const column = getGenerateDataModalColumn(modal, columnName);
3249
+
3250
+ if (field === 'generator') {
3251
+ const generator = normalizeSyntheticGeneratorType(value, mapping.generator);
3252
+
3253
+ if (mapping.generator === generator) {
3254
+ return;
3255
+ }
3256
+
3257
+ mapping.generator = generator;
3258
+ mapping.options = getDefaultSyntheticOptions(generator, column ?? {});
3259
+ } else {
3260
+ const nextValue = value;
3261
+
3262
+ if ((mapping.options ?? {})[field] === nextValue) {
3263
+ return;
3264
+ }
3265
+
3266
+ mapping.options = {
3267
+ ...(mapping.options ?? {}),
3268
+ [field]: nextValue,
3269
+ };
3270
+ }
3271
+
3272
+ modal.error = null;
3273
+ resetGenerateDataPreview(modal);
3274
+ if (options.notify !== false) {
3275
+ emitChange();
3276
+ }
3277
+ }
3278
+
3279
+ export async function previewGenerateDataRows() {
3280
+ const modal = state.modal;
3281
+
3282
+ if (modal?.kind !== 'generate-data') {
3283
+ return null;
3284
+ }
3285
+
3286
+ const requestId = (modal.previewRequestId ?? 0) + 1;
3287
+ modal.previewRequestId = requestId;
3288
+ modal.previewLoading = true;
3289
+ modal.error = null;
3290
+ emitChange();
3291
+
3292
+ try {
3293
+ const response = await api.previewSyntheticDataRows(modal.tableName, buildGenerateDataPayload(modal));
3294
+
3295
+ if (state.modal?.kind !== 'generate-data' || state.modal.previewRequestId !== requestId) {
3296
+ return null;
3297
+ }
3298
+
3299
+ state.modal.previewColumns = response.data?.columns ?? [];
3300
+ state.modal.previewRows = response.data?.rows ?? [];
3301
+ state.modal.error = null;
3302
+ return response.data;
3303
+ } catch (error) {
3304
+ if (state.modal?.kind === 'generate-data' && state.modal.previewRequestId === requestId) {
3305
+ state.modal.error = normalizeError(error);
3306
+ }
3307
+
3308
+ return null;
3309
+ } finally {
3310
+ if (state.modal?.kind === 'generate-data' && state.modal.previewRequestId === requestId) {
3311
+ state.modal.previewLoading = false;
3312
+ emitChange();
3313
+ }
3314
+ }
3315
+ }
3316
+
3317
+ export async function submitGenerateDataRows() {
3318
+ const modal = state.modal;
3319
+
3320
+ if (modal?.kind !== 'generate-data') {
3321
+ return null;
3322
+ }
3323
+
3324
+ startModalSubmission();
3325
+
3326
+ try {
3327
+ const response = await api.insertSyntheticDataRows(modal.tableName, buildGenerateDataPayload(modal));
3328
+
3329
+ if (state.modal?.kind === 'generate-data') {
3330
+ closeModalInternal();
3331
+ }
3332
+
3333
+ pushToast(response.message || `Generated ${response.data?.insertedRowCount ?? modal.rowCount} rows for ${modal.tableName}.`, 'success');
3334
+ await loadDataTable(++routeLoadVersion);
3335
+ clearDataBrowserRowSelectionState();
3336
+ return response.data;
3337
+ } catch (error) {
3338
+ withModalError(error);
3339
+ return null;
3340
+ }
3341
+ }
3342
+
2895
3343
  export function openCopyColumnModal({ scope = 'editor', columnName = '', mode = 'column' } = {}) {
2896
3344
  const resultScope = normalizeCopyColumnScope(scope);
2897
3345
  const normalizedColumnName = String(columnName ?? '');
@@ -0,0 +1,36 @@
1
+ const ESCAPE_CLEARABLE_INPUT_TYPES = new Set([
2
+ '',
3
+ 'email',
4
+ 'number',
5
+ 'password',
6
+ 'search',
7
+ 'tel',
8
+ 'text',
9
+ 'url',
10
+ ]);
11
+
12
+ export function isEscapeClearableInput(input) {
13
+ const type = String(input?.type ?? 'text').trim().toLowerCase();
14
+
15
+ return (
16
+ Boolean(input) &&
17
+ typeof input.value === 'string' &&
18
+ !input.disabled &&
19
+ !input.readOnly &&
20
+ ESCAPE_CLEARABLE_INPUT_TYPES.has(type)
21
+ );
22
+ }
23
+
24
+ export function clearInputForEscape(input, createInputEvent = () => new Event('input', { bubbles: true })) {
25
+ if (!isEscapeClearableInput(input) || input.value === '') {
26
+ return false;
27
+ }
28
+
29
+ input.value = '';
30
+
31
+ if (typeof input.dispatchEvent === 'function') {
32
+ input.dispatchEvent(createInputEvent());
33
+ }
34
+
35
+ return true;
36
+ }