@sqlrooms/cells 0.29.0-rc.1 → 0.29.0-rc.2
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/dist/cellsSlice.d.ts.map +1 -1
- package/dist/cellsSlice.js +191 -73
- package/dist/cellsSlice.js.map +1 -1
- package/dist/components/AggregationSelector.d.ts +8 -0
- package/dist/components/AggregationSelector.d.ts.map +1 -0
- package/dist/components/AggregationSelector.js +13 -0
- package/dist/components/AggregationSelector.js.map +1 -0
- package/dist/components/CellSourceSelector.d.ts +11 -0
- package/dist/components/CellSourceSelector.d.ts.map +1 -0
- package/dist/components/CellSourceSelector.js +35 -0
- package/dist/components/CellSourceSelector.js.map +1 -0
- package/dist/components/ChartTypeSelector.d.ts +8 -0
- package/dist/components/ChartTypeSelector.d.ts.map +1 -0
- package/dist/components/ChartTypeSelector.js +13 -0
- package/dist/components/ChartTypeSelector.js.map +1 -0
- package/dist/components/CollapsibleSectionButton.d.ts +10 -0
- package/dist/components/CollapsibleSectionButton.d.ts.map +1 -0
- package/dist/components/CollapsibleSectionButton.js +10 -0
- package/dist/components/CollapsibleSectionButton.js.map +1 -0
- package/dist/components/ColorSelector.d.ts +24 -0
- package/dist/components/ColorSelector.d.ts.map +1 -0
- package/dist/components/ColorSelector.js +17 -0
- package/dist/components/ColorSelector.js.map +1 -0
- package/dist/components/FieldSelector.d.ts +11 -0
- package/dist/components/FieldSelector.d.ts.map +1 -0
- package/dist/components/FieldSelector.js +11 -0
- package/dist/components/FieldSelector.js.map +1 -0
- package/dist/components/FieldTypeIcon.d.ts +8 -0
- package/dist/components/FieldTypeIcon.d.ts.map +1 -0
- package/dist/components/FieldTypeIcon.js +27 -0
- package/dist/components/FieldTypeIcon.js.map +1 -0
- package/dist/components/InputCellContent.d.ts.map +1 -1
- package/dist/components/InputCellContent.js +3 -3
- package/dist/components/InputCellContent.js.map +1 -1
- package/dist/components/SelectionListener.d.ts +15 -0
- package/dist/components/SelectionListener.d.ts.map +1 -0
- package/dist/components/SelectionListener.js +74 -0
- package/dist/components/SelectionListener.js.map +1 -0
- package/dist/components/SheetsTabBar.d.ts.map +1 -1
- package/dist/components/SheetsTabBar.js +2 -1
- package/dist/components/SheetsTabBar.js.map +1 -1
- package/dist/components/SqlCellBody.d.ts +6 -0
- package/dist/components/SqlCellBody.d.ts.map +1 -0
- package/dist/components/SqlCellBody.js +12 -0
- package/dist/components/SqlCellBody.js.map +1 -0
- package/dist/components/SqlCellConnectionSelector.d.ts +9 -0
- package/dist/components/SqlCellConnectionSelector.d.ts.map +1 -0
- package/dist/components/SqlCellConnectionSelector.js +21 -0
- package/dist/components/SqlCellConnectionSelector.js.map +1 -0
- package/dist/components/SqlCellContent.d.ts.map +1 -1
- package/dist/components/SqlCellContent.js +29 -154
- package/dist/components/SqlCellContent.js.map +1 -1
- package/dist/components/SqlCellDependentsMenu.d.ts +11 -0
- package/dist/components/SqlCellDependentsMenu.d.ts.map +1 -0
- package/dist/components/SqlCellDependentsMenu.js +62 -0
- package/dist/components/SqlCellDependentsMenu.js.map +1 -0
- package/dist/components/SqlCellEditor.d.ts +14 -0
- package/dist/components/SqlCellEditor.d.ts.map +1 -0
- package/dist/components/SqlCellEditor.js +17 -0
- package/dist/components/SqlCellEditor.js.map +1 -0
- package/dist/components/SqlCellResultNameEditor.d.ts +8 -0
- package/dist/components/SqlCellResultNameEditor.d.ts.map +1 -0
- package/dist/components/SqlCellResultNameEditor.js +38 -0
- package/dist/components/SqlCellResultNameEditor.js.map +1 -0
- package/dist/components/SqlCellResults.d.ts +17 -0
- package/dist/components/SqlCellResults.d.ts.map +1 -0
- package/dist/components/SqlCellResults.js +45 -0
- package/dist/components/SqlCellResults.js.map +1 -0
- package/dist/components/SqlCellRunButton.d.ts +5 -6
- package/dist/components/SqlCellRunButton.d.ts.map +1 -1
- package/dist/components/SqlCellRunButton.js +11 -7
- package/dist/components/SqlCellRunButton.js.map +1 -1
- package/dist/components/SqlQueryPreview.d.ts +9 -0
- package/dist/components/SqlQueryPreview.d.ts.map +1 -0
- package/dist/components/SqlQueryPreview.js +14 -0
- package/dist/components/SqlQueryPreview.js.map +1 -0
- package/dist/components/TextCellContent.d.ts.map +1 -1
- package/dist/components/TextCellContent.js +24 -7
- package/dist/components/TextCellContent.js.map +1 -1
- package/dist/components/VegaCellContent.d.ts +0 -1
- package/dist/components/VegaCellContent.d.ts.map +1 -1
- package/dist/components/VegaCellContent.js +61 -47
- package/dist/components/VegaCellContent.js.map +1 -1
- package/dist/components/VegaCellHeader.d.ts +11 -0
- package/dist/components/VegaCellHeader.d.ts.map +1 -0
- package/dist/components/VegaCellHeader.js +31 -0
- package/dist/components/VegaCellHeader.js.map +1 -0
- package/dist/components/VegaConfigPanel.d.ts +5 -0
- package/dist/components/VegaConfigPanel.d.ts.map +1 -1
- package/dist/components/VegaConfigPanel.js +53 -63
- package/dist/components/VegaConfigPanel.js.map +1 -1
- package/dist/crdt.d.ts +61 -0
- package/dist/crdt.d.ts.map +1 -0
- package/dist/crdt.js +82 -0
- package/dist/crdt.js.map +1 -0
- package/dist/dagSlice.d.ts +3 -0
- package/dist/dagSlice.d.ts.map +1 -0
- package/dist/dagSlice.js +51 -0
- package/dist/dagSlice.js.map +1 -0
- package/dist/dagUtils.d.ts +2 -1
- package/dist/dagUtils.d.ts.map +1 -1
- package/dist/dagUtils.js +35 -11
- package/dist/dagUtils.js.map +1 -1
- package/dist/defaultCellRegistry.d.ts.map +1 -1
- package/dist/defaultCellRegistry.js +63 -24
- package/dist/defaultCellRegistry.js.map +1 -1
- package/dist/execution.d.ts.map +1 -1
- package/dist/execution.js +2 -1
- package/dist/execution.js.map +1 -1
- package/dist/helpers.d.ts +14 -2
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +56 -2
- package/dist/helpers.js.map +1 -1
- package/dist/hooks/useVegaCellDataSource.d.ts +18 -0
- package/dist/hooks/useVegaCellDataSource.d.ts.map +1 -0
- package/dist/hooks/useVegaCellDataSource.js +40 -0
- package/dist/hooks/useVegaCellDataSource.js.map +1 -0
- package/dist/hooks/useVegaCellQuery.d.ts +16 -0
- package/dist/hooks/useVegaCellQuery.d.ts.map +1 -0
- package/dist/hooks/useVegaCellQuery.js +25 -0
- package/dist/hooks/useVegaCellQuery.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/sqlHelpers.d.ts +3 -3
- package/dist/sqlHelpers.d.ts.map +1 -1
- package/dist/sqlHelpers.js +9 -5
- package/dist/sqlHelpers.js.map +1 -1
- package/dist/typeGuards.d.ts +2 -0
- package/dist/typeGuards.d.ts.map +1 -0
- package/dist/typeGuards.js +4 -0
- package/dist/typeGuards.js.map +1 -0
- package/dist/types.d.ts +97 -26
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +17 -2
- package/dist/types.js.map +1 -1
- package/dist/useValidateResultName.d.ts +34 -0
- package/dist/useValidateResultName.d.ts.map +1 -0
- package/dist/useValidateResultName.js +111 -0
- package/dist/useValidateResultName.js.map +1 -0
- package/dist/utils.d.ts +8 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +16 -0
- package/dist/utils.js.map +1 -1
- package/dist/validation.d.ts +16 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +40 -0
- package/dist/validation.js.map +1 -0
- package/dist/vegaSelectionUtils.d.ts +14 -0
- package/dist/vegaSelectionUtils.d.ts.map +1 -0
- package/dist/vegaSelectionUtils.js +97 -0
- package/dist/vegaSelectionUtils.js.map +1 -0
- package/dist/vegaSpecBuilder.d.ts +34 -0
- package/dist/vegaSpecBuilder.d.ts.map +1 -0
- package/dist/vegaSpecBuilder.js +90 -0
- package/dist/vegaSpecBuilder.js.map +1 -0
- package/package.json +12 -13
package/dist/cellsSlice.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cellsSlice.d.ts","sourceRoot":"","sources":["../src/cellsSlice.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cellsSlice.d.ts","sourceRoot":"","sources":["../src/cellsSlice.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAKV,iBAAiB,EACjB,eAAe,EAMhB,MAAM,SAAS,CAAC;AAoDjB,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,iBAAiB,mDA83BxD"}
|
package/dist/cellsSlice.js
CHANGED
|
@@ -2,13 +2,14 @@ import { createId } from '@paralleldrive/cuid2';
|
|
|
2
2
|
import { makePagedQuery } from '@sqlrooms/data-table';
|
|
3
3
|
import { sanitizeQuery } from '@sqlrooms/duckdb';
|
|
4
4
|
import { createSlice } from '@sqlrooms/room-store';
|
|
5
|
-
import { generateUniqueName } from '@sqlrooms/utils';
|
|
5
|
+
import { convertToValidColumnOrTableName, generateUniqueName, } from '@sqlrooms/utils';
|
|
6
6
|
import { produce } from 'immer';
|
|
7
|
-
import {
|
|
7
|
+
import { buildDependencyGraph, buildDependencyGraphAsync, buildGraphCacheFromEdges, collectReachable, ensureGraphCache, removeCellFromCache, replaceCellDependenciesInCache, topologicalOrder, } from './dagUtils';
|
|
8
8
|
import { findSheetIdForCell, getRequiredSqlSelectToJson, normalizeCellsConfigStructure, resolveDependencies, } from './helpers';
|
|
9
9
|
import { dropResultRelation } from './resultRelationPolicy';
|
|
10
10
|
import { isInputCell, isSqlCell } from './types';
|
|
11
|
-
import { getSheetSchemaName, isDefined } from './utils';
|
|
11
|
+
import { getEffectiveResultName, getSheetSchemaName, isDefined } from './utils';
|
|
12
|
+
import { buildCrossFilterPredicate } from './vegaSelectionUtils';
|
|
12
13
|
function createDefaultCellsConfig(config) {
|
|
13
14
|
const sheetId = createId();
|
|
14
15
|
const defaultConfig = {
|
|
@@ -25,11 +26,13 @@ function createDefaultCellsConfig(config) {
|
|
|
25
26
|
dependencies: {},
|
|
26
27
|
dependents: {},
|
|
27
28
|
contentHashByCell: {},
|
|
29
|
+
tableDependencies: {},
|
|
28
30
|
},
|
|
29
31
|
},
|
|
30
32
|
},
|
|
31
33
|
sheetOrder: [sheetId],
|
|
32
34
|
currentSheetId: sheetId,
|
|
35
|
+
tableDepSchemas: ['main'],
|
|
33
36
|
};
|
|
34
37
|
if (!config) {
|
|
35
38
|
return defaultConfig;
|
|
@@ -50,16 +53,53 @@ export function createCellsSlice(props) {
|
|
|
50
53
|
// Keep result data outside Immer drafts, but scoped per slice instance.
|
|
51
54
|
const cellResultCache = new Map();
|
|
52
55
|
return createSlice((set, get, store) => {
|
|
53
|
-
const
|
|
56
|
+
const dropRelationBestEffort = async (relationName) => {
|
|
54
57
|
try {
|
|
55
58
|
const connector = await get().db.getConnector();
|
|
56
|
-
await dropResultRelation({ connector, relationName
|
|
59
|
+
await dropResultRelation({ connector, relationName });
|
|
57
60
|
}
|
|
58
61
|
catch {
|
|
59
|
-
// Best-effort cleanup
|
|
60
|
-
// might be unavailable during teardown.
|
|
62
|
+
// Best-effort cleanup
|
|
61
63
|
}
|
|
62
64
|
};
|
|
65
|
+
const dropRelationsForStatus = async (cellType, status) => {
|
|
66
|
+
if (!status)
|
|
67
|
+
return;
|
|
68
|
+
const registryItem = cellRegistry[cellType];
|
|
69
|
+
const names = registryItem?.getRelationsToDrop?.(status) ?? [];
|
|
70
|
+
for (const name of names) {
|
|
71
|
+
await dropRelationBestEffort(name);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
// Mark cells stale when their dependent database tables change (schema-level).
|
|
75
|
+
store.subscribe((state, prevState) => {
|
|
76
|
+
if (state.db.tables === prevState.db.tables)
|
|
77
|
+
return;
|
|
78
|
+
const toName = (t) => `${t.table.schema ?? 'main'}.${t.table.table}`;
|
|
79
|
+
const oldNames = new Set(prevState.db.tables.map(toName));
|
|
80
|
+
const newNames = new Set(state.db.tables.map(toName));
|
|
81
|
+
const changed = new Set();
|
|
82
|
+
for (const n of oldNames) {
|
|
83
|
+
if (!newNames.has(n))
|
|
84
|
+
changed.add(n);
|
|
85
|
+
}
|
|
86
|
+
for (const n of newNames) {
|
|
87
|
+
if (!oldNames.has(n))
|
|
88
|
+
changed.add(n);
|
|
89
|
+
}
|
|
90
|
+
if (!changed.size)
|
|
91
|
+
return;
|
|
92
|
+
for (const sheet of Object.values(state.cells.config.sheets)) {
|
|
93
|
+
const cache = sheet.graphCache;
|
|
94
|
+
if (!cache?.tableDependencies)
|
|
95
|
+
continue;
|
|
96
|
+
for (const [cellId, tables] of Object.entries(cache.tableDependencies)) {
|
|
97
|
+
if (tables.some((t) => changed.has(t))) {
|
|
98
|
+
state.cells.invalidateCellStatus(cellId);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
63
103
|
return {
|
|
64
104
|
cells: {
|
|
65
105
|
cellRegistry,
|
|
@@ -69,6 +109,42 @@ export function createCellsSlice(props) {
|
|
|
69
109
|
activeAbortControllers: {},
|
|
70
110
|
resultVersion: {},
|
|
71
111
|
pageVersion: {},
|
|
112
|
+
async initialize() {
|
|
113
|
+
for (const [cellId, cell] of Object.entries(get().cells.config.data)) {
|
|
114
|
+
const registryItem = cellRegistry[cell.type];
|
|
115
|
+
if (registryItem?.onInitialize) {
|
|
116
|
+
registryItem.onInitialize({
|
|
117
|
+
id: cellId,
|
|
118
|
+
status: get().cells.status[cellId],
|
|
119
|
+
get,
|
|
120
|
+
set,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
crossFilterSelections: {},
|
|
126
|
+
setCrossFilterSelection: (chartCellId, sqlId, selection) => {
|
|
127
|
+
set((state) => produce(state, (draft) => {
|
|
128
|
+
if (!draft.cells.crossFilterSelections[sqlId]) {
|
|
129
|
+
draft.cells.crossFilterSelections[sqlId] = {};
|
|
130
|
+
}
|
|
131
|
+
draft.cells.crossFilterSelections[sqlId][chartCellId] = selection;
|
|
132
|
+
}));
|
|
133
|
+
},
|
|
134
|
+
getCrossFilterPredicate: (chartCellId, sqlId) => {
|
|
135
|
+
const group = get().cells.crossFilterSelections[sqlId];
|
|
136
|
+
if (!group)
|
|
137
|
+
return null;
|
|
138
|
+
const siblingSelections = Object.entries(group)
|
|
139
|
+
.filter(([id]) => id !== chartCellId)
|
|
140
|
+
.map(([, sel]) => sel);
|
|
141
|
+
return buildCrossFilterPredicate(siblingSelections);
|
|
142
|
+
},
|
|
143
|
+
clearCrossFilterGroup: (sqlId) => {
|
|
144
|
+
set((state) => produce(state, (draft) => {
|
|
145
|
+
delete draft.cells.crossFilterSelections[sqlId];
|
|
146
|
+
}));
|
|
147
|
+
},
|
|
72
148
|
addCell: async (sheetId, cell, index) => {
|
|
73
149
|
// Pre-compute dependencies outside produce() to support async
|
|
74
150
|
const sqlSelectToJson = getRequiredSqlSelectToJson(get());
|
|
@@ -78,19 +154,20 @@ export function createCellsSlice(props) {
|
|
|
78
154
|
.filter(isDefined)
|
|
79
155
|
.map((candidate) => [candidate.id, candidate]));
|
|
80
156
|
scopedCells[cell.id] = cell;
|
|
157
|
+
// Generate a unique result name for SQL cells if not set
|
|
158
|
+
if (cell.type === 'sql' && !cell.data.resultName) {
|
|
159
|
+
const existingNames = Object.values(scopedCells)
|
|
160
|
+
.filter((c) => c.type === 'sql' && c.id !== cell.id)
|
|
161
|
+
.map((c) => getEffectiveResultName(c.data, convertToValidColumnOrTableName));
|
|
162
|
+
const baseName = getEffectiveResultName(cell.data, convertToValidColumnOrTableName);
|
|
163
|
+
const uniqueName = generateUniqueName(baseName, existingNames);
|
|
164
|
+
cell.data.resultName = uniqueName;
|
|
165
|
+
}
|
|
81
166
|
const deps = await resolveDependencies(cell, scopedCells, sheetId, cellRegistry, sqlSelectToJson);
|
|
82
167
|
set((state) => produce(state, (draft) => {
|
|
83
168
|
draft.cells.config.data[cell.id] = cell;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
type: 'sql',
|
|
87
|
-
status: 'idle',
|
|
88
|
-
referencedTables: [],
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
draft.cells.status[cell.id] = { type: 'other' };
|
|
93
|
-
}
|
|
169
|
+
const registryItem = cellRegistry[cell.type];
|
|
170
|
+
draft.cells.status[cell.id] = registryItem?.createStatus?.(cell.id) ?? { type: 'other' };
|
|
94
171
|
// Single-owner invariant: a cell can belong to one sheet only.
|
|
95
172
|
for (const [existingSheetId, existingSheet] of Object.entries(draft.cells.config.sheets)) {
|
|
96
173
|
if (existingSheetId === sheetId)
|
|
@@ -112,6 +189,7 @@ export function createCellsSlice(props) {
|
|
|
112
189
|
dependencies: {},
|
|
113
190
|
dependents: {},
|
|
114
191
|
contentHashByCell: {},
|
|
192
|
+
tableDependencies: {},
|
|
115
193
|
},
|
|
116
194
|
};
|
|
117
195
|
draft.cells.config.sheets[sheetId] = sheet;
|
|
@@ -129,7 +207,7 @@ export function createCellsSlice(props) {
|
|
|
129
207
|
sheet.edges = sheet.edges.filter((edge) => edge.target !== cell.id &&
|
|
130
208
|
edge.id !== `${edge.source}-${cell.id}`);
|
|
131
209
|
const localCellIds = new Set(sheet.cellIds);
|
|
132
|
-
for (const depId of deps) {
|
|
210
|
+
for (const depId of deps.cellIds) {
|
|
133
211
|
if (!localCellIds.has(depId) || depId === cell.id)
|
|
134
212
|
continue;
|
|
135
213
|
sheet.edges.push({
|
|
@@ -138,10 +216,11 @@ export function createCellsSlice(props) {
|
|
|
138
216
|
target: cell.id,
|
|
139
217
|
});
|
|
140
218
|
}
|
|
141
|
-
replaceCellDependenciesInCache(sheet, cell.id, deps.filter((depId) => localCellIds.has(depId)));
|
|
219
|
+
replaceCellDependenciesInCache(sheet, cell.id, deps.cellIds.filter((depId) => localCellIds.has(depId)), deps.tableNames);
|
|
142
220
|
}));
|
|
143
221
|
},
|
|
144
222
|
removeCell: (id) => {
|
|
223
|
+
const cell = get().cells.config.data[id];
|
|
145
224
|
const previousStatus = get().cells.status[id];
|
|
146
225
|
cellResultCache.delete(id);
|
|
147
226
|
set((state) => produce(state, (draft) => {
|
|
@@ -161,8 +240,19 @@ export function createCellsSlice(props) {
|
|
|
161
240
|
removeCellFromCache(sheet, id);
|
|
162
241
|
}
|
|
163
242
|
}));
|
|
164
|
-
if (
|
|
165
|
-
|
|
243
|
+
if (cell) {
|
|
244
|
+
const registryItem = cellRegistry[cell.type];
|
|
245
|
+
if (registryItem?.onRemove) {
|
|
246
|
+
void registryItem.onRemove({
|
|
247
|
+
id,
|
|
248
|
+
status: previousStatus,
|
|
249
|
+
get,
|
|
250
|
+
set,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
void dropRelationsForStatus(cell.type, previousStatus);
|
|
255
|
+
}
|
|
166
256
|
}
|
|
167
257
|
},
|
|
168
258
|
updateCell: async (id, updater, opts) => {
|
|
@@ -178,15 +268,11 @@ export function createCellsSlice(props) {
|
|
|
178
268
|
.map((candidate) => [candidate.id, candidate]));
|
|
179
269
|
const updatedCell = updater(cell);
|
|
180
270
|
scopedCells[id] = updatedCell;
|
|
181
|
-
|
|
182
|
-
const resultNameChanged = isSqlCell(cell) &&
|
|
183
|
-
isSqlCell(updatedCell) &&
|
|
184
|
-
cell.data.resultName !== updatedCell.data.resultName;
|
|
271
|
+
const registryItem = cellRegistry[cell.type];
|
|
185
272
|
const existingStatus = get().cells.status[id];
|
|
186
|
-
const
|
|
187
|
-
existingStatus
|
|
188
|
-
|
|
189
|
-
existingStatus.resultView;
|
|
273
|
+
const oldResultRelation = existingStatus && registryItem?.getResultRelation
|
|
274
|
+
? registryItem.getResultRelation(existingStatus)
|
|
275
|
+
: undefined;
|
|
190
276
|
const semanticInputChanged = isInputCell(cell) &&
|
|
191
277
|
isInputCell(updatedCell) &&
|
|
192
278
|
(cell.data.input.varName !== updatedCell.data.input.varName ||
|
|
@@ -194,12 +280,20 @@ export function createCellsSlice(props) {
|
|
|
194
280
|
const semanticSqlChanged = isSqlCell(cell) &&
|
|
195
281
|
isSqlCell(updatedCell) &&
|
|
196
282
|
cell.data.sql !== updatedCell.data.sql;
|
|
283
|
+
const semanticChanged = registryItem?.hasSemanticChange?.(cell, updatedCell) ?? false;
|
|
197
284
|
// Apply cell data to the store immediately so that other
|
|
198
285
|
// actions (e.g. runCell triggered via Cmd+Enter) always see
|
|
199
286
|
// the latest value. Edge/dependency updates follow
|
|
200
287
|
// asynchronously below.
|
|
201
288
|
set((state) => produce(state, (draft) => {
|
|
202
289
|
draft.cells.config.data[id] = updatedCell;
|
|
290
|
+
if (semanticChanged) {
|
|
291
|
+
const status = draft.cells.status[id];
|
|
292
|
+
if (status && registryItem?.invalidateStatus) {
|
|
293
|
+
draft.cells.status[id] =
|
|
294
|
+
registryItem.invalidateStatus(status);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
203
297
|
}));
|
|
204
298
|
// Avoid expensive dependency graph recomputation on each SQL keystroke.
|
|
205
299
|
// We recompute immediately for non-SQL edits, or when callers explicitly
|
|
@@ -214,7 +308,7 @@ export function createCellsSlice(props) {
|
|
|
214
308
|
if (ownerSheet) {
|
|
215
309
|
const localCellIds = new Set(ownerSheet.cellIds);
|
|
216
310
|
ownerSheet.edges = ownerSheet.edges.filter((e) => e.target !== id);
|
|
217
|
-
for (const depId of newDeps) {
|
|
311
|
+
for (const depId of newDeps.cellIds) {
|
|
218
312
|
if (!localCellIds.has(depId) || depId === id)
|
|
219
313
|
continue;
|
|
220
314
|
ownerSheet.edges.push({
|
|
@@ -223,25 +317,27 @@ export function createCellsSlice(props) {
|
|
|
223
317
|
target: id,
|
|
224
318
|
});
|
|
225
319
|
}
|
|
226
|
-
replaceCellDependenciesInCache(ownerSheet, id, newDeps.filter((depId) => localCellIds.has(depId)));
|
|
320
|
+
replaceCellDependenciesInCache(ownerSheet, id, newDeps.cellIds.filter((depId) => localCellIds.has(depId)), newDeps.tableNames);
|
|
227
321
|
}
|
|
228
322
|
}));
|
|
229
323
|
}
|
|
230
|
-
// If
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
324
|
+
// If the result relation changed name (e.g. SQL resultName changed), rename it
|
|
325
|
+
const newStatus = get().cells.status[id];
|
|
326
|
+
const newResultRelation = newStatus && registryItem?.getResultRelation
|
|
327
|
+
? registryItem.getResultRelation(newStatus)
|
|
328
|
+
: undefined;
|
|
329
|
+
const resultRelationChanged = oldResultRelation && newResultRelation !== oldResultRelation;
|
|
330
|
+
if (resultRelationChanged && registryItem?.renameResult) {
|
|
331
|
+
void registryItem.renameResult({
|
|
332
|
+
id,
|
|
333
|
+
oldResultView: oldResultRelation,
|
|
334
|
+
get,
|
|
335
|
+
set,
|
|
336
|
+
});
|
|
241
337
|
}
|
|
242
338
|
// After update, trigger cascade only if explicitly requested
|
|
243
339
|
// or if semantic execution inputs changed.
|
|
244
|
-
const shouldCascade = opts?.cascade ||
|
|
340
|
+
const shouldCascade = opts?.cascade || resultRelationChanged || semanticInputChanged;
|
|
245
341
|
if (shouldCascade) {
|
|
246
342
|
if (ownerSheetId) {
|
|
247
343
|
void get().cells.runDownstreamCascade(ownerSheetId, id);
|
|
@@ -265,6 +361,7 @@ export function createCellsSlice(props) {
|
|
|
265
361
|
dependencies: {},
|
|
266
362
|
dependents: {},
|
|
267
363
|
contentHashByCell: {},
|
|
364
|
+
tableDependencies: {},
|
|
268
365
|
},
|
|
269
366
|
};
|
|
270
367
|
draft.cells.config.sheetOrder.push(id);
|
|
@@ -278,10 +375,12 @@ export function createCellsSlice(props) {
|
|
|
278
375
|
return;
|
|
279
376
|
const ownedCellIds = [...sheet.cellIds];
|
|
280
377
|
const relationNamesToDrop = ownedCellIds.flatMap((cellId) => {
|
|
378
|
+
const cell = get().cells.config.data[cellId];
|
|
281
379
|
const status = get().cells.status[cellId];
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
380
|
+
if (!cell || !status)
|
|
381
|
+
return [];
|
|
382
|
+
const registryItem = cellRegistry[cell.type];
|
|
383
|
+
return registryItem?.getRelationsToDrop?.(status) ?? [];
|
|
285
384
|
});
|
|
286
385
|
set((state) => produce(state, (draft) => {
|
|
287
386
|
for (const cellId of ownedCellIds) {
|
|
@@ -313,7 +412,7 @@ export function createCellsSlice(props) {
|
|
|
313
412
|
}
|
|
314
413
|
}));
|
|
315
414
|
for (const relationName of relationNamesToDrop) {
|
|
316
|
-
void
|
|
415
|
+
void dropRelationBestEffort(relationName);
|
|
317
416
|
}
|
|
318
417
|
},
|
|
319
418
|
closeSheet: (sheetId) => {
|
|
@@ -369,6 +468,7 @@ export function createCellsSlice(props) {
|
|
|
369
468
|
dependencies: {},
|
|
370
469
|
dependents: {},
|
|
371
470
|
contentHashByCell: {},
|
|
471
|
+
tableDependencies: {},
|
|
372
472
|
},
|
|
373
473
|
};
|
|
374
474
|
draft.cells.config.sheets[sheetId] = sheet;
|
|
@@ -421,7 +521,7 @@ export function createCellsSlice(props) {
|
|
|
421
521
|
if (draftSheet) {
|
|
422
522
|
draftSheet.edges = draftSheet.edges.filter((e) => e.target !== cellId);
|
|
423
523
|
const localCellIds = new Set(draftSheet.cellIds);
|
|
424
|
-
for (const depId of deps) {
|
|
524
|
+
for (const depId of deps.cellIds) {
|
|
425
525
|
if (!localCellIds.has(depId) || depId === cellId)
|
|
426
526
|
continue;
|
|
427
527
|
draftSheet.edges.push({
|
|
@@ -430,7 +530,7 @@ export function createCellsSlice(props) {
|
|
|
430
530
|
target: cellId,
|
|
431
531
|
});
|
|
432
532
|
}
|
|
433
|
-
replaceCellDependenciesInCache(draftSheet, cellId, deps.filter((depId) => localCellIds.has(depId)));
|
|
533
|
+
replaceCellDependenciesInCache(draftSheet, cellId, deps.cellIds.filter((depId) => localCellIds.has(depId)), deps.tableNames);
|
|
434
534
|
}
|
|
435
535
|
}));
|
|
436
536
|
},
|
|
@@ -457,15 +557,14 @@ export function createCellsSlice(props) {
|
|
|
457
557
|
}
|
|
458
558
|
},
|
|
459
559
|
invalidateCellStatus: (id) => {
|
|
560
|
+
const cell = get().cells.config.data[id];
|
|
561
|
+
if (!cell)
|
|
562
|
+
return;
|
|
563
|
+
const registryItem = cellRegistry[cell.type];
|
|
460
564
|
set((state) => produce(state, (draft) => {
|
|
461
565
|
const status = draft.cells.status[id];
|
|
462
|
-
if (status
|
|
463
|
-
|
|
464
|
-
draft.cells.status[id] = {
|
|
465
|
-
type: 'sql',
|
|
466
|
-
status: 'idle',
|
|
467
|
-
referencedTables: status.referencedTables || [],
|
|
468
|
-
};
|
|
566
|
+
if (status && registryItem?.invalidateStatus) {
|
|
567
|
+
draft.cells.status[id] = registryItem.invalidateStatus(status);
|
|
469
568
|
}
|
|
470
569
|
}));
|
|
471
570
|
},
|
|
@@ -496,14 +595,21 @@ export function createCellsSlice(props) {
|
|
|
496
595
|
},
|
|
497
596
|
fetchCellResultPage: async (id, pagination, sorting) => {
|
|
498
597
|
const state = get();
|
|
598
|
+
const cell = state.cells.config.data[id];
|
|
599
|
+
if (!cell)
|
|
600
|
+
return;
|
|
601
|
+
const registryItem = cellRegistry[cell.type];
|
|
499
602
|
const status = state.cells.status[id];
|
|
500
|
-
|
|
603
|
+
const resultRelation = status && registryItem?.getResultRelation
|
|
604
|
+
? registryItem.getResultRelation(status)
|
|
605
|
+
: undefined;
|
|
606
|
+
if (!resultRelation)
|
|
501
607
|
return;
|
|
502
608
|
const connector = await state.db.getConnector();
|
|
503
|
-
const baseQuery = sanitizeQuery(`SELECT * FROM ${
|
|
609
|
+
const baseQuery = sanitizeQuery(`SELECT * FROM ${resultRelation}`);
|
|
504
610
|
const pagedQuery = makePagedQuery(baseQuery, sorting ?? [], pagination);
|
|
505
611
|
const arrowTable = await connector.query(pagedQuery);
|
|
506
|
-
const countResult = await connector.query(`SELECT COUNT(*)::int AS count FROM ${
|
|
612
|
+
const countResult = await connector.query(`SELECT COUNT(*)::int AS count FROM ${resultRelation}`);
|
|
507
613
|
const totalRows = countResult.toArray()[0]?.count ?? 0;
|
|
508
614
|
get().cells.setCellResultPage(id, { arrowTable, totalRows });
|
|
509
615
|
},
|
|
@@ -547,13 +653,19 @@ export function createCellsSlice(props) {
|
|
|
547
653
|
}
|
|
548
654
|
catch (error) {
|
|
549
655
|
const message = error instanceof Error ? error.message : String(error);
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
656
|
+
const failedCell = get().cells.config.data[cellId];
|
|
657
|
+
const failedRegistryItem = failedCell
|
|
658
|
+
? cellRegistry[failedCell.type]
|
|
659
|
+
: undefined;
|
|
660
|
+
if (failedRegistryItem?.recordError) {
|
|
661
|
+
set((state) => produce(state, (draft) => {
|
|
662
|
+
const status = draft.cells.status[cellId];
|
|
663
|
+
if (status) {
|
|
664
|
+
draft.cells.status[cellId] =
|
|
665
|
+
failedRegistryItem.recordError(status, message);
|
|
666
|
+
}
|
|
667
|
+
}));
|
|
668
|
+
}
|
|
557
669
|
}
|
|
558
670
|
}
|
|
559
671
|
},
|
|
@@ -585,13 +697,19 @@ export function createCellsSlice(props) {
|
|
|
585
697
|
}
|
|
586
698
|
catch (error) {
|
|
587
699
|
const message = error instanceof Error ? error.message : String(error);
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
700
|
+
const failedCell = get().cells.config.data[cellId];
|
|
701
|
+
const failedRegistryItem = failedCell
|
|
702
|
+
? cellRegistry[failedCell.type]
|
|
703
|
+
: undefined;
|
|
704
|
+
if (failedRegistryItem?.recordError) {
|
|
705
|
+
set((state) => produce(state, (draft) => {
|
|
706
|
+
const status = draft.cells.status[cellId];
|
|
707
|
+
if (status) {
|
|
708
|
+
draft.cells.status[cellId] =
|
|
709
|
+
failedRegistryItem.recordError(status, message);
|
|
710
|
+
}
|
|
711
|
+
}));
|
|
712
|
+
}
|
|
595
713
|
}
|
|
596
714
|
}
|
|
597
715
|
},
|