sqlite-hub 2.0.1 → 2.1.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.
- package/README.md +14 -9
- package/docs/MCP.md +8 -0
- package/docs/changelog.md +10 -0
- package/docs/guidelines/DESIGN.md +64 -0
- package/frontend/assets/mockups/backups_1_1920.webp +0 -0
- package/frontend/assets/mockups/backups_2_compare_drawer_1920.webp +0 -0
- package/frontend/assets/mockups/backups_3_create_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_4_edit_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_5_restore_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_6_delete_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/charts_1_1920.webp +0 -0
- package/frontend/assets/mockups/charts_2_query_detail_1920.webp +0 -0
- package/frontend/assets/mockups/charts_3_create_query_chart_modal_1920.webp +0 -0
- package/frontend/assets/mockups/charts_4_edit_query_chart_modal_1920.webp +0 -0
- package/frontend/assets/mockups/charts_5_delete_query_chart_modal_1920.webp +0 -0
- package/frontend/assets/mockups/charts_6_copy_column_modal_1920.webp +0 -0
- package/frontend/assets/mockups/connections_1_1920.webp +0 -0
- package/frontend/assets/mockups/connections_2_create_connection_modal_1920.webp +0 -0
- package/frontend/assets/mockups/connections_3_open_connection_modal_1920.webp +0 -0
- package/frontend/assets/mockups/data_1_1920.webp +0 -0
- package/frontend/assets/mockups/data_2_roweditor_1920.webp +0 -0
- package/frontend/assets/mockups/data_3_generate_data_modal_1920.webp +0 -0
- package/frontend/assets/mockups/data_4_data_export_modal_1920.webp +0 -0
- package/frontend/assets/mockups/documents_1_1920.webp +0 -0
- package/frontend/assets/mockups/documents_2_document_insert_table_modal_1920.webp +0 -0
- package/frontend/assets/mockups/documents_3_document_insert_note_modal_1920.webp +0 -0
- package/frontend/assets/mockups/logs_1_1920.webp +0 -0
- package/frontend/assets/mockups/media_tagging_queue_1_1920.webp +0 -0
- package/frontend/assets/mockups/media_tagging_setup_1_1920.webp +0 -0
- package/frontend/assets/mockups/media_tagging_setup_2_create_media_tagging_tag_table_modal_1920.webp +0 -0
- package/frontend/assets/mockups/media_tagging_setup_3_create_media_tagging_mapping_table_modal_1920.webp +0 -0
- package/frontend/assets/mockups/overview_1_1920.webp +0 -0
- package/frontend/assets/mockups/settings_1_1920.webp +0 -0
- package/frontend/assets/mockups/sql_editor_1_1920.webp +0 -0
- package/frontend/assets/mockups/sql_editor_2_query_detail_1920.webp +0 -0
- package/frontend/assets/mockups/sql_editor_3_query_export_modal_1920.webp +0 -0
- package/frontend/assets/mockups/structure_1_1920.webp +0 -0
- package/frontend/assets/mockups/structure_2_generate_types_modal_1920.webp +0 -0
- package/frontend/assets/mockups/structure_3_generate_types_modal_1920.webp +0 -0
- package/frontend/assets/mockups/table_designer_1_1920.webp +0 -0
- package/frontend/assets/mockups/table_designer_2_table_designer_constraints_modal_1920.webp +0 -0
- package/frontend/js/api.js +11 -0
- package/frontend/js/app.js +238 -8
- package/frontend/js/components/badges.js +16 -0
- package/frontend/js/components/connectionCard.js +35 -0
- package/frontend/js/components/modal.js +116 -245
- package/frontend/js/components/queryHistoryDetail.js +25 -45
- package/frontend/js/components/rowEditorPanel.js +7 -60
- package/frontend/js/components/structureGraph.js +292 -13
- package/frontend/js/components/tableDesignerConstraintsDrawer.js +400 -0
- package/frontend/js/components/tableDesignerEditor.js +51 -16
- package/frontend/js/components/tableDesignerSidebar.js +9 -5
- package/frontend/js/store.js +551 -7
- package/frontend/js/utils/connectionRegistry.js +76 -0
- package/frontend/js/utils/tableDesigner.js +431 -62
- package/frontend/js/views/backups.js +2 -5
- package/frontend/js/views/charts.js +20 -10
- package/frontend/js/views/connections.js +103 -3
- package/frontend/js/views/data.js +19 -5
- package/frontend/js/views/structure.js +28 -5
- package/frontend/js/views/tableAdvisor.js +14 -6
- package/frontend/js/views/tableDesigner.js +44 -9
- package/frontend/styles/components.css +676 -79
- package/frontend/styles/structure-graph.css +49 -0
- package/frontend/styles/tailwind.generated.css +1 -1
- package/package.json +1 -1
- package/server/routes/connections.js +12 -0
- package/server/routes/data.js +1 -1
- package/server/routes/tableDesigner.js +15 -1
- package/server/services/mcpToolService.js +28 -0
- package/server/services/sqlite/connectionManager.js +23 -2
- package/server/services/sqlite/dataBrowserService.js +21 -0
- package/server/services/sqlite/introspection.js +39 -1
- package/server/services/sqlite/structureService.js +66 -11
- package/server/services/sqlite/tableAdvisor.js +6 -0
- package/server/services/sqlite/tableDesigner/schemaMapping.js +7 -1
- package/server/services/sqlite/tableDesigner/sql.js +31 -2
- package/server/services/sqlite/tableDesigner/validation.js +69 -9
- package/server/services/sqlite/tableDesignerService.js +110 -5
- package/server/services/storage/appStateStore.js +303 -4
- package/frontend/assets/mockups/backups_2_create_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_3_edit_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_4_restore_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_5_delete_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/data_3_data_export_modal_1920.webp +0 -0
package/frontend/js/store.js
CHANGED
|
@@ -2,9 +2,12 @@ import * as api from './api.js';
|
|
|
2
2
|
import { formatCellValue, inferStatusTone, truncateMiddle } from './utils/format.js';
|
|
3
3
|
import {
|
|
4
4
|
addTableDesignerColumn,
|
|
5
|
-
|
|
5
|
+
addTableDesignerCheckConstraint,
|
|
6
|
+
createTableDesignerDraftFromImport,
|
|
6
7
|
createNewTableDesignerDraft,
|
|
7
8
|
hydrateTableDesignerDraft,
|
|
9
|
+
recalculateTableDesignerDraft,
|
|
10
|
+
removeTableDesignerCheckConstraint,
|
|
8
11
|
removeTableDesignerColumn,
|
|
9
12
|
updateTableDesignerColumnField,
|
|
10
13
|
updateTableDesignerConstraintField,
|
|
@@ -28,6 +31,10 @@ import {
|
|
|
28
31
|
getDefaultSyntheticOptions,
|
|
29
32
|
normalizeSyntheticGeneratorType,
|
|
30
33
|
} from './utils/syntheticData.js';
|
|
34
|
+
import {
|
|
35
|
+
normalizeConnectionTagKey,
|
|
36
|
+
normalizeConnectionTagName,
|
|
37
|
+
} from './utils/connectionRegistry.js';
|
|
31
38
|
|
|
32
39
|
const listeners = new Set();
|
|
33
40
|
const DEFAULT_SETTINGS = {
|
|
@@ -90,6 +97,16 @@ const MISSING_DATABASE_ERROR = {
|
|
|
90
97
|
message: 'No active SQLite database selected.',
|
|
91
98
|
};
|
|
92
99
|
|
|
100
|
+
function createClosedTableDesignerConstraintsDrawer() {
|
|
101
|
+
return {
|
|
102
|
+
visible: false,
|
|
103
|
+
columnId: '',
|
|
104
|
+
columnName: '',
|
|
105
|
+
editingConstraintId: '',
|
|
106
|
+
editor: null,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
93
110
|
let routeLoadVersion = 0;
|
|
94
111
|
let queryHistoryLoadVersion = 0;
|
|
95
112
|
let queryHistoryDetailLoadVersion = 0;
|
|
@@ -296,6 +313,9 @@ const state = {
|
|
|
296
313
|
connections: {
|
|
297
314
|
recent: [],
|
|
298
315
|
active: null,
|
|
316
|
+
tags: [],
|
|
317
|
+
searchQuery: '',
|
|
318
|
+
selectedTagIds: [],
|
|
299
319
|
loading: false,
|
|
300
320
|
backupLoading: false,
|
|
301
321
|
error: null,
|
|
@@ -465,6 +485,7 @@ const state = {
|
|
|
465
485
|
tablesVisible: readStoredBoolean(UI_PREFERENCE_STORAGE_KEYS.tableDesignerTablesVisible, true),
|
|
466
486
|
sqlPreviewVisible: readStoredBoolean(UI_PREFERENCE_STORAGE_KEYS.tableDesignerSqlPreviewVisible, true),
|
|
467
487
|
pendingImportedDraft: null,
|
|
488
|
+
constraintsDrawer: createClosedTableDesignerConstraintsDrawer(),
|
|
468
489
|
loading: false,
|
|
469
490
|
detailLoading: false,
|
|
470
491
|
saving: false,
|
|
@@ -1531,13 +1552,17 @@ async function refreshConnectionsState() {
|
|
|
1531
1552
|
emitChange();
|
|
1532
1553
|
|
|
1533
1554
|
try {
|
|
1534
|
-
const [recentResponse, activeResponse] = await Promise.all([
|
|
1555
|
+
const [recentResponse, activeResponse, tagsResponse] = await Promise.all([
|
|
1535
1556
|
api.getRecentConnections(),
|
|
1536
1557
|
api.getActiveConnection(),
|
|
1558
|
+
api.getConnectionTags(),
|
|
1537
1559
|
]);
|
|
1538
1560
|
|
|
1539
1561
|
state.connections.recent = recentResponse.data ?? [];
|
|
1540
1562
|
state.connections.active = activeResponse.data ?? null;
|
|
1563
|
+
state.connections.tags = tagsResponse.data ?? [];
|
|
1564
|
+
const availableTagIds = new Set(state.connections.tags.map(tag => String(tag.id)));
|
|
1565
|
+
state.connections.selectedTagIds = state.connections.selectedTagIds.filter(tagId => availableTagIds.has(String(tagId)));
|
|
1541
1566
|
state.connections.error = null;
|
|
1542
1567
|
} catch (error) {
|
|
1543
1568
|
state.connections.error = normalizeError(error);
|
|
@@ -2474,7 +2499,7 @@ async function loadTableAdvisor(version, route) {
|
|
|
2474
2499
|
return;
|
|
2475
2500
|
}
|
|
2476
2501
|
|
|
2477
|
-
const tables = response.data?.tables ?? [];
|
|
2502
|
+
const tables = (response.data?.tables ?? []).filter(table => !table?.isShadow);
|
|
2478
2503
|
const requestedTableName = route.params?.tableName ?? null;
|
|
2479
2504
|
const selectedTableName =
|
|
2480
2505
|
requestedTableName && tables.some(table => table.name === requestedTableName)
|
|
@@ -2613,6 +2638,7 @@ async function loadTableDesignerDetail(version, tableName) {
|
|
|
2613
2638
|
}
|
|
2614
2639
|
|
|
2615
2640
|
state.tableDesigner.selectedTableName = tableName;
|
|
2641
|
+
state.tableDesigner.constraintsDrawer = createClosedTableDesignerConstraintsDrawer();
|
|
2616
2642
|
state.tableDesigner.draft = decorateTableDesignerDraft(response.data?.draft ?? null);
|
|
2617
2643
|
} catch (error) {
|
|
2618
2644
|
if (version !== routeLoadVersion) {
|
|
@@ -2649,6 +2675,7 @@ async function loadTableDesigner(version, route) {
|
|
|
2649
2675
|
state.tableDesigner.selectedTableName = null;
|
|
2650
2676
|
state.tableDesigner.detailLoading = false;
|
|
2651
2677
|
state.tableDesigner.pendingImportedDraft = null;
|
|
2678
|
+
state.tableDesigner.constraintsDrawer = createClosedTableDesignerConstraintsDrawer();
|
|
2652
2679
|
state.tableDesigner.draft = decorateTableDesignerDraft(importedDraft ?? createNewTableDesignerDraft());
|
|
2653
2680
|
return;
|
|
2654
2681
|
}
|
|
@@ -2665,6 +2692,7 @@ async function loadTableDesigner(version, route) {
|
|
|
2665
2692
|
if (!tableName) {
|
|
2666
2693
|
state.tableDesigner.selectedTableName = null;
|
|
2667
2694
|
state.tableDesigner.detailLoading = false;
|
|
2695
|
+
state.tableDesigner.constraintsDrawer = createClosedTableDesignerConstraintsDrawer();
|
|
2668
2696
|
state.tableDesigner.draft = null;
|
|
2669
2697
|
return;
|
|
2670
2698
|
}
|
|
@@ -3221,6 +3249,11 @@ export function openGenerateDataModal() {
|
|
|
3221
3249
|
return;
|
|
3222
3250
|
}
|
|
3223
3251
|
|
|
3252
|
+
if (table.isShadow) {
|
|
3253
|
+
pushToast('Shadow tables are read-only in Data.', 'alert');
|
|
3254
|
+
return;
|
|
3255
|
+
}
|
|
3256
|
+
|
|
3224
3257
|
const columns = (table.columnMeta ?? []).filter(column => column.visible && !column.generated);
|
|
3225
3258
|
|
|
3226
3259
|
state.modal = {
|
|
@@ -3961,6 +3994,136 @@ export async function submitDeleteDocumentConfirmation() {
|
|
|
3961
3994
|
return result;
|
|
3962
3995
|
}
|
|
3963
3996
|
|
|
3997
|
+
function normalizeConnectionTagDrafts(tags = []) {
|
|
3998
|
+
const draftsByKey = new Map();
|
|
3999
|
+
|
|
4000
|
+
for (const tag of tags) {
|
|
4001
|
+
const name = normalizeConnectionTagName(tag?.name ?? tag);
|
|
4002
|
+
|
|
4003
|
+
if (!name) {
|
|
4004
|
+
continue;
|
|
4005
|
+
}
|
|
4006
|
+
|
|
4007
|
+
const key = normalizeConnectionTagKey(name);
|
|
4008
|
+
|
|
4009
|
+
if (!draftsByKey.has(key)) {
|
|
4010
|
+
draftsByKey.set(key, {
|
|
4011
|
+
id: tag?.id ?? null,
|
|
4012
|
+
name,
|
|
4013
|
+
});
|
|
4014
|
+
}
|
|
4015
|
+
}
|
|
4016
|
+
|
|
4017
|
+
return [...draftsByKey.values()];
|
|
4018
|
+
}
|
|
4019
|
+
|
|
4020
|
+
function findKnownConnectionTagByName(name) {
|
|
4021
|
+
const key = normalizeConnectionTagKey(name);
|
|
4022
|
+
|
|
4023
|
+
return state.connections.tags.find(tag => normalizeConnectionTagKey(tag.name) === key) ?? null;
|
|
4024
|
+
}
|
|
4025
|
+
|
|
4026
|
+
export function setConnectionSearchQuery(query) {
|
|
4027
|
+
const nextQuery = String(query ?? '');
|
|
4028
|
+
|
|
4029
|
+
if (state.connections.searchQuery === nextQuery) {
|
|
4030
|
+
return;
|
|
4031
|
+
}
|
|
4032
|
+
|
|
4033
|
+
state.connections.searchQuery = nextQuery;
|
|
4034
|
+
emitChange();
|
|
4035
|
+
}
|
|
4036
|
+
|
|
4037
|
+
export function toggleConnectionTagFilter(tagId, selected) {
|
|
4038
|
+
const normalizedTagId = String(tagId ?? '').trim();
|
|
4039
|
+
|
|
4040
|
+
if (!normalizedTagId) {
|
|
4041
|
+
return;
|
|
4042
|
+
}
|
|
4043
|
+
|
|
4044
|
+
const currentIds = new Set(state.connections.selectedTagIds.map(id => String(id)));
|
|
4045
|
+
|
|
4046
|
+
if (selected) {
|
|
4047
|
+
currentIds.add(normalizedTagId);
|
|
4048
|
+
} else {
|
|
4049
|
+
currentIds.delete(normalizedTagId);
|
|
4050
|
+
}
|
|
4051
|
+
|
|
4052
|
+
const nextTagIds = [...currentIds];
|
|
4053
|
+
|
|
4054
|
+
if (JSON.stringify(nextTagIds) === JSON.stringify(state.connections.selectedTagIds)) {
|
|
4055
|
+
return;
|
|
4056
|
+
}
|
|
4057
|
+
|
|
4058
|
+
state.connections.selectedTagIds = nextTagIds;
|
|
4059
|
+
emitChange();
|
|
4060
|
+
}
|
|
4061
|
+
|
|
4062
|
+
export function clearConnectionTagFilters() {
|
|
4063
|
+
if (!state.connections.selectedTagIds.length) {
|
|
4064
|
+
return;
|
|
4065
|
+
}
|
|
4066
|
+
|
|
4067
|
+
state.connections.selectedTagIds = [];
|
|
4068
|
+
emitChange();
|
|
4069
|
+
}
|
|
4070
|
+
|
|
4071
|
+
export function updateEditConnectionTagQuery(query) {
|
|
4072
|
+
if (state.modal?.kind !== 'edit-connection') {
|
|
4073
|
+
return;
|
|
4074
|
+
}
|
|
4075
|
+
|
|
4076
|
+
state.modal.tagQuery = String(query ?? '');
|
|
4077
|
+
state.modal.tagError = null;
|
|
4078
|
+
emitChange();
|
|
4079
|
+
}
|
|
4080
|
+
|
|
4081
|
+
export function addEditConnectionTag(tagName) {
|
|
4082
|
+
if (state.modal?.kind !== 'edit-connection') {
|
|
4083
|
+
return;
|
|
4084
|
+
}
|
|
4085
|
+
|
|
4086
|
+
const name = normalizeConnectionTagName(tagName);
|
|
4087
|
+
|
|
4088
|
+
if (!name) {
|
|
4089
|
+
state.modal.tagError = 'Tag names must be 1-40 characters.';
|
|
4090
|
+
emitChange();
|
|
4091
|
+
return;
|
|
4092
|
+
}
|
|
4093
|
+
|
|
4094
|
+
const currentTags = normalizeConnectionTagDrafts(state.modal.assignedTags);
|
|
4095
|
+
const key = normalizeConnectionTagKey(name);
|
|
4096
|
+
|
|
4097
|
+
if (currentTags.some(tag => normalizeConnectionTagKey(tag.name) === key)) {
|
|
4098
|
+
state.modal.tagQuery = '';
|
|
4099
|
+
state.modal.tagError = null;
|
|
4100
|
+
emitChange();
|
|
4101
|
+
return;
|
|
4102
|
+
}
|
|
4103
|
+
|
|
4104
|
+
const knownTag = findKnownConnectionTagByName(name);
|
|
4105
|
+
state.modal.assignedTags = [
|
|
4106
|
+
...currentTags,
|
|
4107
|
+
knownTag ? { id: knownTag.id, name: knownTag.name } : { id: null, name },
|
|
4108
|
+
];
|
|
4109
|
+
state.modal.tagQuery = '';
|
|
4110
|
+
state.modal.tagError = null;
|
|
4111
|
+
emitChange();
|
|
4112
|
+
}
|
|
4113
|
+
|
|
4114
|
+
export function removeEditConnectionTag(tagName) {
|
|
4115
|
+
if (state.modal?.kind !== 'edit-connection') {
|
|
4116
|
+
return;
|
|
4117
|
+
}
|
|
4118
|
+
|
|
4119
|
+
const key = normalizeConnectionTagKey(tagName);
|
|
4120
|
+
state.modal.assignedTags = normalizeConnectionTagDrafts(state.modal.assignedTags).filter(
|
|
4121
|
+
tag => normalizeConnectionTagKey(tag.name) !== key,
|
|
4122
|
+
);
|
|
4123
|
+
state.modal.tagError = null;
|
|
4124
|
+
emitChange();
|
|
4125
|
+
}
|
|
4126
|
+
|
|
3964
4127
|
export function openEditConnectionModal(id) {
|
|
3965
4128
|
const connection = state.connections.recent.find(entry => entry.id === id);
|
|
3966
4129
|
|
|
@@ -3973,6 +4136,9 @@ export function openEditConnectionModal(id) {
|
|
|
3973
4136
|
kind: 'edit-connection',
|
|
3974
4137
|
connectionId: connection.id,
|
|
3975
4138
|
connection,
|
|
4139
|
+
assignedTags: normalizeConnectionTagDrafts(connection.tags),
|
|
4140
|
+
tagQuery: '',
|
|
4141
|
+
tagError: null,
|
|
3976
4142
|
error: null,
|
|
3977
4143
|
submitting: false,
|
|
3978
4144
|
};
|
|
@@ -4158,6 +4324,11 @@ export async function openDataRowUpdatePreview(rowIndex, values, identity = null
|
|
|
4158
4324
|
return null;
|
|
4159
4325
|
}
|
|
4160
4326
|
|
|
4327
|
+
if (state.dataBrowser.table?.isShadow) {
|
|
4328
|
+
pushToast('Shadow tables are read-only in Data.', 'alert');
|
|
4329
|
+
return null;
|
|
4330
|
+
}
|
|
4331
|
+
|
|
4161
4332
|
state.dataBrowser.saving = true;
|
|
4162
4333
|
state.dataBrowser.saveError = null;
|
|
4163
4334
|
emitChange();
|
|
@@ -5192,6 +5363,19 @@ export async function selectStructureEntry(name) {
|
|
|
5192
5363
|
await loadStructureDetail(++routeLoadVersion);
|
|
5193
5364
|
}
|
|
5194
5365
|
|
|
5366
|
+
export function clearStructureSelection() {
|
|
5367
|
+
const hadSelection = Boolean(state.structure.selectedName || state.structure.detail || state.structure.detailLoading);
|
|
5368
|
+
|
|
5369
|
+
routeLoadVersion += 1;
|
|
5370
|
+
state.structure.selectedName = null;
|
|
5371
|
+
state.structure.detail = null;
|
|
5372
|
+
state.structure.detailLoading = false;
|
|
5373
|
+
|
|
5374
|
+
if (hadSelection) {
|
|
5375
|
+
emitChange();
|
|
5376
|
+
}
|
|
5377
|
+
}
|
|
5378
|
+
|
|
5195
5379
|
function getDefaultTypeGenerationOptions(target = 'typescript') {
|
|
5196
5380
|
const propertyNaming = target === 'rust' ? 'snake' : 'camel';
|
|
5197
5381
|
|
|
@@ -5480,6 +5664,14 @@ export function updateCurrentTableDesignerColumnField(columnId, field, value, op
|
|
|
5480
5664
|
value,
|
|
5481
5665
|
getTableDesignerContext(),
|
|
5482
5666
|
);
|
|
5667
|
+
|
|
5668
|
+
if (field === 'name') {
|
|
5669
|
+
state.tableDesigner.draft = refreshTableDesignerPresetChecksForColumnRename(
|
|
5670
|
+
state.tableDesigner.draft,
|
|
5671
|
+
columnId,
|
|
5672
|
+
);
|
|
5673
|
+
}
|
|
5674
|
+
|
|
5483
5675
|
state.tableDesigner.saveError = null;
|
|
5484
5676
|
|
|
5485
5677
|
if (options.notify !== false) {
|
|
@@ -5507,6 +5699,342 @@ export function updateCurrentTableDesignerConstraintField(constraintKind, constr
|
|
|
5507
5699
|
}
|
|
5508
5700
|
}
|
|
5509
5701
|
|
|
5702
|
+
function quoteSqlIdentifier(identifier) {
|
|
5703
|
+
return `"${String(identifier ?? '').replaceAll('"', '""')}"`;
|
|
5704
|
+
}
|
|
5705
|
+
|
|
5706
|
+
function quoteSqlStringLiteral(value) {
|
|
5707
|
+
return `'${String(value ?? '').replaceAll("'", "''")}'`;
|
|
5708
|
+
}
|
|
5709
|
+
|
|
5710
|
+
function getTableDesignerDrawerColumn() {
|
|
5711
|
+
const drawer = state.tableDesigner.constraintsDrawer ?? createClosedTableDesignerConstraintsDrawer();
|
|
5712
|
+
return (state.tableDesigner.draft?.columns ?? []).find(column => column.id === drawer.columnId) ?? null;
|
|
5713
|
+
}
|
|
5714
|
+
|
|
5715
|
+
function getTableDesignerCheckPresetFields(seed = {}) {
|
|
5716
|
+
return {
|
|
5717
|
+
minLength: String(seed.minLength ?? '3'),
|
|
5718
|
+
maxLength: String(seed.maxLength ?? '255'),
|
|
5719
|
+
minValue: String(seed.minValue ?? '0'),
|
|
5720
|
+
maxValue: String(seed.maxValue ?? '100'),
|
|
5721
|
+
allowedValues: String(seed.allowedValues ?? 'draft\npublished\narchived'),
|
|
5722
|
+
};
|
|
5723
|
+
}
|
|
5724
|
+
|
|
5725
|
+
export function buildTableDesignerCheckPresetExpression(presetId, column, fields = {}) {
|
|
5726
|
+
const columnSql = quoteSqlIdentifier(column?.name || 'column_name');
|
|
5727
|
+
const presetFields = getTableDesignerCheckPresetFields(fields);
|
|
5728
|
+
const allowedValues = presetFields.allowedValues
|
|
5729
|
+
.split(/\r?\n|,/)
|
|
5730
|
+
.map(value => value.trim())
|
|
5731
|
+
.filter(Boolean);
|
|
5732
|
+
|
|
5733
|
+
switch (presetId) {
|
|
5734
|
+
case 'text-non-empty':
|
|
5735
|
+
return `length(trim(${columnSql})) > 0`;
|
|
5736
|
+
case 'text-min-length':
|
|
5737
|
+
return `length(${columnSql}) >= ${Number(presetFields.minLength) || 0}`;
|
|
5738
|
+
case 'text-max-length':
|
|
5739
|
+
return `length(${columnSql}) <= ${Number(presetFields.maxLength) || 0}`;
|
|
5740
|
+
case 'text-length-range':
|
|
5741
|
+
return `length(${columnSql}) BETWEEN ${Number(presetFields.minLength) || 0} AND ${Number(presetFields.maxLength) || 0}`;
|
|
5742
|
+
case 'text-allowed-values':
|
|
5743
|
+
return `${columnSql} IN (${allowedValues.map(quoteSqlStringLiteral).join(', ')})`;
|
|
5744
|
+
case 'numeric-positive':
|
|
5745
|
+
return `${columnSql} > 0`;
|
|
5746
|
+
case 'numeric-non-negative':
|
|
5747
|
+
return `${columnSql} >= 0`;
|
|
5748
|
+
case 'numeric-min-value':
|
|
5749
|
+
return `${columnSql} >= ${Number(presetFields.minValue) || 0}`;
|
|
5750
|
+
case 'numeric-max-value':
|
|
5751
|
+
return `${columnSql} <= ${Number(presetFields.maxValue) || 0}`;
|
|
5752
|
+
case 'numeric-range':
|
|
5753
|
+
return `${columnSql} BETWEEN ${Number(presetFields.minValue) || 0} AND ${Number(presetFields.maxValue) || 0}`;
|
|
5754
|
+
case 'numeric-non-zero':
|
|
5755
|
+
return `${columnSql} <> 0`;
|
|
5756
|
+
case 'boolean-integer':
|
|
5757
|
+
return `${columnSql} IN (0, 1)`;
|
|
5758
|
+
default:
|
|
5759
|
+
return '';
|
|
5760
|
+
}
|
|
5761
|
+
}
|
|
5762
|
+
|
|
5763
|
+
function refreshTableDesignerPresetChecksForColumnRename(draft, columnId) {
|
|
5764
|
+
const column = (draft.columns ?? []).find(candidate => candidate.id === columnId);
|
|
5765
|
+
|
|
5766
|
+
if (!column) {
|
|
5767
|
+
return draft;
|
|
5768
|
+
}
|
|
5769
|
+
|
|
5770
|
+
let changed = false;
|
|
5771
|
+
const checkConstraints = (draft.checkConstraints ?? []).map(constraint => {
|
|
5772
|
+
if (
|
|
5773
|
+
constraint.deleted ||
|
|
5774
|
+
constraint.columnId !== columnId ||
|
|
5775
|
+
!constraint.presetId ||
|
|
5776
|
+
constraint.source !== 'preset'
|
|
5777
|
+
) {
|
|
5778
|
+
return constraint;
|
|
5779
|
+
}
|
|
5780
|
+
|
|
5781
|
+
const expression = buildTableDesignerCheckPresetExpression(
|
|
5782
|
+
constraint.presetId,
|
|
5783
|
+
column,
|
|
5784
|
+
constraint.presetFields,
|
|
5785
|
+
);
|
|
5786
|
+
|
|
5787
|
+
if (!expression || expression === constraint.expression) {
|
|
5788
|
+
return constraint;
|
|
5789
|
+
}
|
|
5790
|
+
|
|
5791
|
+
changed = true;
|
|
5792
|
+
return {
|
|
5793
|
+
...constraint,
|
|
5794
|
+
columns: [{ name: column.name, allowedValues: constraint.columns?.[0]?.allowedValues ?? [] }],
|
|
5795
|
+
expression,
|
|
5796
|
+
};
|
|
5797
|
+
});
|
|
5798
|
+
|
|
5799
|
+
return changed
|
|
5800
|
+
? recalculateTableDesignerDraft(
|
|
5801
|
+
{
|
|
5802
|
+
...draft,
|
|
5803
|
+
checkConstraints,
|
|
5804
|
+
},
|
|
5805
|
+
getTableDesignerContext(),
|
|
5806
|
+
)
|
|
5807
|
+
: draft;
|
|
5808
|
+
}
|
|
5809
|
+
|
|
5810
|
+
function createTableDesignerCheckEditor(seed = {}) {
|
|
5811
|
+
return {
|
|
5812
|
+
expression: String(seed.expression ?? ''),
|
|
5813
|
+
presetId: String(seed.presetId ?? ''),
|
|
5814
|
+
presetFields: getTableDesignerCheckPresetFields(seed.presetFields),
|
|
5815
|
+
error: null,
|
|
5816
|
+
validating: false,
|
|
5817
|
+
};
|
|
5818
|
+
}
|
|
5819
|
+
|
|
5820
|
+
export function openTableDesignerConstraintsDrawer(columnId = '', columnName = '') {
|
|
5821
|
+
const column = (state.tableDesigner.draft?.columns ?? []).find(candidate => candidate.id === columnId);
|
|
5822
|
+
|
|
5823
|
+
state.tableDesigner.constraintsDrawer = {
|
|
5824
|
+
visible: true,
|
|
5825
|
+
columnId: String(columnId ?? ''),
|
|
5826
|
+
columnName: String(columnName || column?.name || ''),
|
|
5827
|
+
editingConstraintId: '',
|
|
5828
|
+
editor: null,
|
|
5829
|
+
};
|
|
5830
|
+
emitChange();
|
|
5831
|
+
}
|
|
5832
|
+
|
|
5833
|
+
export function closeTableDesignerConstraintsDrawer() {
|
|
5834
|
+
state.tableDesigner.constraintsDrawer = createClosedTableDesignerConstraintsDrawer();
|
|
5835
|
+
emitChange();
|
|
5836
|
+
}
|
|
5837
|
+
|
|
5838
|
+
export function startTableDesignerCheckEditor(constraintId = '') {
|
|
5839
|
+
const constraint = (state.tableDesigner.draft?.checkConstraints ?? []).find(item => item.id === constraintId);
|
|
5840
|
+
|
|
5841
|
+
state.tableDesigner.constraintsDrawer = {
|
|
5842
|
+
...(state.tableDesigner.constraintsDrawer ?? createClosedTableDesignerConstraintsDrawer()),
|
|
5843
|
+
visible: true,
|
|
5844
|
+
editingConstraintId: String(constraintId ?? ''),
|
|
5845
|
+
editor: createTableDesignerCheckEditor({
|
|
5846
|
+
expression: constraint?.expression ?? '',
|
|
5847
|
+
presetId: constraint?.presetId ?? '',
|
|
5848
|
+
presetFields: constraint?.presetFields ?? {},
|
|
5849
|
+
}),
|
|
5850
|
+
};
|
|
5851
|
+
emitChange();
|
|
5852
|
+
}
|
|
5853
|
+
|
|
5854
|
+
export function cancelTableDesignerCheckEditor() {
|
|
5855
|
+
state.tableDesigner.constraintsDrawer = {
|
|
5856
|
+
...(state.tableDesigner.constraintsDrawer ?? createClosedTableDesignerConstraintsDrawer()),
|
|
5857
|
+
editingConstraintId: '',
|
|
5858
|
+
editor: null,
|
|
5859
|
+
};
|
|
5860
|
+
emitChange();
|
|
5861
|
+
}
|
|
5862
|
+
|
|
5863
|
+
export function applyTableDesignerCheckPreset(presetId) {
|
|
5864
|
+
const column = getTableDesignerDrawerColumn();
|
|
5865
|
+
const drawer = state.tableDesigner.constraintsDrawer ?? createClosedTableDesignerConstraintsDrawer();
|
|
5866
|
+
const editor = drawer.editor ?? createTableDesignerCheckEditor();
|
|
5867
|
+
const presetFields = getTableDesignerCheckPresetFields(editor.presetFields);
|
|
5868
|
+
const expression = buildTableDesignerCheckPresetExpression(presetId, column, presetFields);
|
|
5869
|
+
|
|
5870
|
+
state.tableDesigner.constraintsDrawer = {
|
|
5871
|
+
...drawer,
|
|
5872
|
+
editor: {
|
|
5873
|
+
...editor,
|
|
5874
|
+
presetId,
|
|
5875
|
+
presetFields,
|
|
5876
|
+
expression,
|
|
5877
|
+
error: null,
|
|
5878
|
+
},
|
|
5879
|
+
};
|
|
5880
|
+
emitChange();
|
|
5881
|
+
}
|
|
5882
|
+
|
|
5883
|
+
export function updateTableDesignerCheckEditorField(field, value, options = {}) {
|
|
5884
|
+
const drawer = state.tableDesigner.constraintsDrawer ?? createClosedTableDesignerConstraintsDrawer();
|
|
5885
|
+
const editor = drawer.editor ?? createTableDesignerCheckEditor();
|
|
5886
|
+
const nextEditor = { ...editor, error: null };
|
|
5887
|
+
|
|
5888
|
+
if (field in getTableDesignerCheckPresetFields()) {
|
|
5889
|
+
nextEditor.presetFields = {
|
|
5890
|
+
...getTableDesignerCheckPresetFields(editor.presetFields),
|
|
5891
|
+
[field]: String(value ?? ''),
|
|
5892
|
+
};
|
|
5893
|
+
|
|
5894
|
+
if (editor.presetId) {
|
|
5895
|
+
nextEditor.expression = buildTableDesignerCheckPresetExpression(
|
|
5896
|
+
editor.presetId,
|
|
5897
|
+
getTableDesignerDrawerColumn(),
|
|
5898
|
+
nextEditor.presetFields,
|
|
5899
|
+
);
|
|
5900
|
+
}
|
|
5901
|
+
} else {
|
|
5902
|
+
nextEditor[field] = String(value ?? '');
|
|
5903
|
+
|
|
5904
|
+
if (field === 'expression') {
|
|
5905
|
+
nextEditor.presetId = '';
|
|
5906
|
+
}
|
|
5907
|
+
}
|
|
5908
|
+
|
|
5909
|
+
state.tableDesigner.constraintsDrawer = {
|
|
5910
|
+
...drawer,
|
|
5911
|
+
editor: nextEditor,
|
|
5912
|
+
};
|
|
5913
|
+
|
|
5914
|
+
if (options.notify !== false) {
|
|
5915
|
+
emitChange();
|
|
5916
|
+
}
|
|
5917
|
+
}
|
|
5918
|
+
|
|
5919
|
+
export async function saveTableDesignerCheckEditor() {
|
|
5920
|
+
const draft = state.tableDesigner.draft;
|
|
5921
|
+
const drawer = state.tableDesigner.constraintsDrawer ?? createClosedTableDesignerConstraintsDrawer();
|
|
5922
|
+
const editor = drawer.editor ?? createTableDesignerCheckEditor();
|
|
5923
|
+
const expression = String(editor.expression ?? '').trim();
|
|
5924
|
+
const column = getTableDesignerDrawerColumn();
|
|
5925
|
+
|
|
5926
|
+
if (!draft) {
|
|
5927
|
+
return false;
|
|
5928
|
+
}
|
|
5929
|
+
|
|
5930
|
+
if (!expression) {
|
|
5931
|
+
state.tableDesigner.constraintsDrawer = {
|
|
5932
|
+
...drawer,
|
|
5933
|
+
editor: {
|
|
5934
|
+
...editor,
|
|
5935
|
+
error: { code: 'CHECK_EXPRESSION_REQUIRED', message: 'CHECK expression is required.' },
|
|
5936
|
+
},
|
|
5937
|
+
};
|
|
5938
|
+
emitChange();
|
|
5939
|
+
return false;
|
|
5940
|
+
}
|
|
5941
|
+
|
|
5942
|
+
state.tableDesigner.constraintsDrawer = {
|
|
5943
|
+
...drawer,
|
|
5944
|
+
editor: {
|
|
5945
|
+
...editor,
|
|
5946
|
+
validating: true,
|
|
5947
|
+
error: null,
|
|
5948
|
+
},
|
|
5949
|
+
};
|
|
5950
|
+
emitChange();
|
|
5951
|
+
|
|
5952
|
+
try {
|
|
5953
|
+
await api.validateTableDesignerCheck({ draft, expression });
|
|
5954
|
+
|
|
5955
|
+
const seed = {
|
|
5956
|
+
expression,
|
|
5957
|
+
columnId: drawer.columnId,
|
|
5958
|
+
columns: column ? [{ name: column.name, allowedValues: [] }] : [],
|
|
5959
|
+
source: editor.presetId ? 'preset' : 'user',
|
|
5960
|
+
presetId: editor.presetId,
|
|
5961
|
+
presetFields: editor.presetFields,
|
|
5962
|
+
};
|
|
5963
|
+
|
|
5964
|
+
state.tableDesigner.draft = drawer.editingConstraintId
|
|
5965
|
+
? updateTableDesignerConstraintField(
|
|
5966
|
+
draft,
|
|
5967
|
+
'check',
|
|
5968
|
+
drawer.editingConstraintId,
|
|
5969
|
+
'expression',
|
|
5970
|
+
expression,
|
|
5971
|
+
getTableDesignerContext(),
|
|
5972
|
+
)
|
|
5973
|
+
: addTableDesignerCheckConstraint(draft, seed, getTableDesignerContext());
|
|
5974
|
+
|
|
5975
|
+
if (drawer.editingConstraintId) {
|
|
5976
|
+
state.tableDesigner.draft = updateTableDesignerConstraintField(
|
|
5977
|
+
state.tableDesigner.draft,
|
|
5978
|
+
'check',
|
|
5979
|
+
drawer.editingConstraintId,
|
|
5980
|
+
'presetId',
|
|
5981
|
+
editor.presetId,
|
|
5982
|
+
getTableDesignerContext(),
|
|
5983
|
+
);
|
|
5984
|
+
state.tableDesigner.draft = updateTableDesignerConstraintField(
|
|
5985
|
+
state.tableDesigner.draft,
|
|
5986
|
+
'check',
|
|
5987
|
+
drawer.editingConstraintId,
|
|
5988
|
+
'presetFields',
|
|
5989
|
+
editor.presetFields,
|
|
5990
|
+
getTableDesignerContext(),
|
|
5991
|
+
);
|
|
5992
|
+
state.tableDesigner.draft = updateTableDesignerConstraintField(
|
|
5993
|
+
state.tableDesigner.draft,
|
|
5994
|
+
'check',
|
|
5995
|
+
drawer.editingConstraintId,
|
|
5996
|
+
'source',
|
|
5997
|
+
editor.presetId ? 'preset' : 'user',
|
|
5998
|
+
getTableDesignerContext(),
|
|
5999
|
+
);
|
|
6000
|
+
}
|
|
6001
|
+
|
|
6002
|
+
state.tableDesigner.saveError = null;
|
|
6003
|
+
state.tableDesigner.constraintsDrawer = {
|
|
6004
|
+
...drawer,
|
|
6005
|
+
editingConstraintId: '',
|
|
6006
|
+
editor: null,
|
|
6007
|
+
};
|
|
6008
|
+
emitChange();
|
|
6009
|
+
return true;
|
|
6010
|
+
} catch (error) {
|
|
6011
|
+
state.tableDesigner.constraintsDrawer = {
|
|
6012
|
+
...drawer,
|
|
6013
|
+
editor: {
|
|
6014
|
+
...editor,
|
|
6015
|
+
validating: false,
|
|
6016
|
+
error: normalizeError(error),
|
|
6017
|
+
},
|
|
6018
|
+
};
|
|
6019
|
+
emitChange();
|
|
6020
|
+
return false;
|
|
6021
|
+
}
|
|
6022
|
+
}
|
|
6023
|
+
|
|
6024
|
+
export function removeCurrentTableDesignerCheckConstraint(constraintId) {
|
|
6025
|
+
if (!state.tableDesigner.draft) {
|
|
6026
|
+
return;
|
|
6027
|
+
}
|
|
6028
|
+
|
|
6029
|
+
state.tableDesigner.draft = removeTableDesignerCheckConstraint(
|
|
6030
|
+
state.tableDesigner.draft,
|
|
6031
|
+
constraintId,
|
|
6032
|
+
getTableDesignerContext(),
|
|
6033
|
+
);
|
|
6034
|
+
state.tableDesigner.saveError = null;
|
|
6035
|
+
emitChange();
|
|
6036
|
+
}
|
|
6037
|
+
|
|
5510
6038
|
export function addCurrentTableDesignerColumn() {
|
|
5511
6039
|
if (!state.tableDesigner.draft) {
|
|
5512
6040
|
return null;
|
|
@@ -5522,10 +6050,12 @@ export function addCurrentTableDesignerColumn() {
|
|
|
5522
6050
|
return nextColumn?.id ?? null;
|
|
5523
6051
|
}
|
|
5524
6052
|
|
|
5525
|
-
export function
|
|
6053
|
+
export function queueTableDesignerImport(fileName, text, options = {}) {
|
|
6054
|
+
const format = String(options.format ?? '').trim().toLowerCase();
|
|
6055
|
+
|
|
5526
6056
|
try {
|
|
5527
|
-
const imported =
|
|
5528
|
-
{ fileName,
|
|
6057
|
+
const imported = createTableDesignerDraftFromImport(
|
|
6058
|
+
{ fileName, text, format },
|
|
5529
6059
|
{
|
|
5530
6060
|
...getTableDesignerContext(),
|
|
5531
6061
|
...(options.context ?? {}),
|
|
@@ -5543,11 +6073,15 @@ export function queueTableDesignerCsvImport(fileName, csvText, options = {}) {
|
|
|
5543
6073
|
throw error;
|
|
5544
6074
|
}
|
|
5545
6075
|
|
|
5546
|
-
pushToast(error?.message || '
|
|
6076
|
+
pushToast(error?.message || 'Data import failed.', 'alert');
|
|
5547
6077
|
return null;
|
|
5548
6078
|
}
|
|
5549
6079
|
}
|
|
5550
6080
|
|
|
6081
|
+
export function queueTableDesignerCsvImport(fileName, csvText, options = {}) {
|
|
6082
|
+
return queueTableDesignerImport(fileName, csvText, { ...options, format: 'csv' });
|
|
6083
|
+
}
|
|
6084
|
+
|
|
5551
6085
|
export function removeCurrentTableDesignerColumn(columnId) {
|
|
5552
6086
|
if (!state.tableDesigner.draft) {
|
|
5553
6087
|
return;
|
|
@@ -6190,6 +6724,11 @@ export async function submitDataRowUpdate(rowIndex, values, identity = null, opt
|
|
|
6190
6724
|
return null;
|
|
6191
6725
|
}
|
|
6192
6726
|
|
|
6727
|
+
if (state.dataBrowser.table?.isShadow) {
|
|
6728
|
+
pushToast('Shadow tables are read-only in Data.', 'alert');
|
|
6729
|
+
return null;
|
|
6730
|
+
}
|
|
6731
|
+
|
|
6193
6732
|
state.dataBrowser.saving = true;
|
|
6194
6733
|
state.dataBrowser.saveError = null;
|
|
6195
6734
|
emitChange();
|
|
@@ -6228,6 +6767,11 @@ export async function submitDataRowDelete(rowIndex, options = {}) {
|
|
|
6228
6767
|
return null;
|
|
6229
6768
|
}
|
|
6230
6769
|
|
|
6770
|
+
if (state.dataBrowser.table?.isShadow) {
|
|
6771
|
+
pushToast('Shadow tables are read-only in Data.', 'alert');
|
|
6772
|
+
return null;
|
|
6773
|
+
}
|
|
6774
|
+
|
|
6231
6775
|
const shouldStepBackPage =
|
|
6232
6776
|
selected.rowIndex !== null && (state.dataBrowser.table?.rows?.length ?? 0) <= 1 && state.dataBrowser.page > 1;
|
|
6233
6777
|
|