@sqlrooms/cells 0.29.0-rc.1 → 0.29.0-rc.3
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 +8 -8
- package/dist/cellsSlice.d.ts.map +1 -1
- package/dist/cellsSlice.js +305 -264
- 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 +12 -0
- package/dist/components/CellSourceSelector.d.ts.map +1 -0
- package/dist/components/CellSourceSelector.js +34 -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/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 +31 -156
- 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 +32 -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/dagUtils.d.ts +10 -9
- package/dist/dagUtils.d.ts.map +1 -1
- package/dist/dagUtils.js +76 -50
- package/dist/dagUtils.js.map +1 -1
- package/dist/defaultCellRegistry.d.ts.map +1 -1
- package/dist/defaultCellRegistry.js +79 -36
- package/dist/defaultCellRegistry.js.map +1 -1
- package/dist/execution.d.ts.map +1 -1
- package/dist/execution.js +24 -17
- package/dist/execution.js.map +1 -1
- package/dist/helpers.d.ts +20 -16
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +79 -44
- 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 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/sqlHelpers.d.ts +8 -8
- package/dist/sqlHelpers.d.ts.map +1 -1
- package/dist/sqlHelpers.js +16 -12
- package/dist/sqlHelpers.js.map +1 -1
- package/dist/types.d.ts +113 -70
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +22 -13
- 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 +10 -2
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +20 -4
- package/dist/utils.js.map +1 -1
- 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/components/DeleteSheetModal.d.ts +0 -9
- package/dist/components/DeleteSheetModal.d.ts.map +0 -1
- package/dist/components/DeleteSheetModal.js +0 -6
- package/dist/components/DeleteSheetModal.js.map +0 -1
- package/dist/components/SheetsTabBar.d.ts +0 -6
- package/dist/components/SheetsTabBar.d.ts.map +0 -1
- package/dist/components/SheetsTabBar.js +0 -57
- package/dist/components/SheetsTabBar.js.map +0 -1
package/dist/cellsSlice.js
CHANGED
|
@@ -1,147 +1,224 @@
|
|
|
1
|
-
import { createId } from '@paralleldrive/cuid2';
|
|
2
1
|
import { makePagedQuery } from '@sqlrooms/data-table';
|
|
3
2
|
import { sanitizeQuery } from '@sqlrooms/duckdb';
|
|
4
3
|
import { createSlice } from '@sqlrooms/room-store';
|
|
5
|
-
import { generateUniqueName } from '@sqlrooms/utils';
|
|
4
|
+
import { convertToValidColumnOrTableName, generateUniqueName, } from '@sqlrooms/utils';
|
|
6
5
|
import { produce } from 'immer';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import { buildDependencyGraph, buildDependencyGraphAsync, buildGraphCacheFromEdges, collectReachable, removeCellFromCache, replaceCellDependenciesInCache, topologicalOrder, } from './dagUtils';
|
|
7
|
+
import { findArtifactIdForCell, getRequiredSqlSelectToJson, normalizeCellsConfigStructure, resolveDependencies, } from './helpers';
|
|
9
8
|
import { dropResultRelation } from './resultRelationPolicy';
|
|
10
9
|
import { isInputCell, isSqlCell } from './types';
|
|
11
|
-
import {
|
|
10
|
+
import { getArtifactSchemaName, getEffectiveResultName, isDefined, } from './utils';
|
|
11
|
+
import { buildCrossFilterPredicate } from './vegaSelectionUtils';
|
|
12
12
|
function createDefaultCellsConfig(config) {
|
|
13
|
-
const sheetId = createId();
|
|
14
13
|
const defaultConfig = {
|
|
15
14
|
data: {},
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
id: sheetId,
|
|
19
|
-
type: 'notebook',
|
|
20
|
-
title: 'Sheet',
|
|
21
|
-
schemaName: getSheetSchemaName(sheetId),
|
|
22
|
-
cellIds: [],
|
|
23
|
-
edges: [],
|
|
24
|
-
graphCache: {
|
|
25
|
-
dependencies: {},
|
|
26
|
-
dependents: {},
|
|
27
|
-
contentHashByCell: {},
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
sheetOrder: [sheetId],
|
|
32
|
-
currentSheetId: sheetId,
|
|
15
|
+
artifacts: {},
|
|
16
|
+
tableDepSchemas: ['main'],
|
|
33
17
|
};
|
|
34
18
|
if (!config) {
|
|
35
19
|
return defaultConfig;
|
|
36
20
|
}
|
|
37
|
-
const {
|
|
21
|
+
const { artifacts } = normalizeCellsConfigStructure(config);
|
|
38
22
|
return {
|
|
39
23
|
...defaultConfig,
|
|
40
24
|
...config,
|
|
41
|
-
|
|
42
|
-
sheetOrder,
|
|
43
|
-
currentSheetId,
|
|
25
|
+
artifacts,
|
|
44
26
|
};
|
|
45
27
|
}
|
|
46
28
|
// --- Slice Implementation ---
|
|
47
29
|
export function createCellsSlice(props) {
|
|
48
|
-
const { cellRegistry
|
|
30
|
+
const { cellRegistry } = props;
|
|
49
31
|
const initialConfig = createDefaultCellsConfig(props?.config);
|
|
50
32
|
// Keep result data outside Immer drafts, but scoped per slice instance.
|
|
51
33
|
const cellResultCache = new Map();
|
|
52
34
|
return createSlice((set, get, store) => {
|
|
53
|
-
const
|
|
35
|
+
const dropRelationBestEffort = async (relationName) => {
|
|
54
36
|
try {
|
|
55
37
|
const connector = await get().db.getConnector();
|
|
56
|
-
await dropResultRelation({ connector, relationName
|
|
38
|
+
await dropResultRelation({ connector, relationName });
|
|
57
39
|
}
|
|
58
40
|
catch {
|
|
59
|
-
// Best-effort cleanup
|
|
60
|
-
|
|
41
|
+
// Best-effort cleanup
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
const dropRelationsForStatus = async (cellType, status) => {
|
|
45
|
+
if (!status)
|
|
46
|
+
return;
|
|
47
|
+
const registryItem = cellRegistry[cellType];
|
|
48
|
+
const names = registryItem?.getRelationsToDrop?.(status) ?? [];
|
|
49
|
+
for (const name of names) {
|
|
50
|
+
await dropRelationBestEffort(name);
|
|
61
51
|
}
|
|
62
52
|
};
|
|
53
|
+
// Mark cells stale when their dependent database tables change (schema-level).
|
|
54
|
+
store.subscribe((state, prevState) => {
|
|
55
|
+
if (state.db.tables === prevState.db.tables)
|
|
56
|
+
return;
|
|
57
|
+
const toName = (t) => `${t.table.schema ?? 'main'}.${t.table.table}`;
|
|
58
|
+
const oldNames = new Set(prevState.db.tables.map(toName));
|
|
59
|
+
const newNames = new Set(state.db.tables.map(toName));
|
|
60
|
+
const changed = new Set();
|
|
61
|
+
for (const n of oldNames) {
|
|
62
|
+
if (!newNames.has(n))
|
|
63
|
+
changed.add(n);
|
|
64
|
+
}
|
|
65
|
+
for (const n of newNames) {
|
|
66
|
+
if (!oldNames.has(n))
|
|
67
|
+
changed.add(n);
|
|
68
|
+
}
|
|
69
|
+
if (!changed.size)
|
|
70
|
+
return;
|
|
71
|
+
for (const artifact of Object.values(state.cells.config.artifacts)) {
|
|
72
|
+
const cache = artifact.graphCache;
|
|
73
|
+
if (!cache?.tableDependencies)
|
|
74
|
+
continue;
|
|
75
|
+
for (const [cellId, tables] of Object.entries(cache.tableDependencies)) {
|
|
76
|
+
if (tables.some((t) => changed.has(t))) {
|
|
77
|
+
state.cells.invalidateCellStatus(cellId);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
});
|
|
63
82
|
return {
|
|
64
83
|
cells: {
|
|
65
84
|
cellRegistry,
|
|
66
|
-
supportedSheetTypes,
|
|
67
85
|
config: initialConfig,
|
|
68
86
|
status: {},
|
|
69
87
|
activeAbortControllers: {},
|
|
70
88
|
resultVersion: {},
|
|
71
89
|
pageVersion: {},
|
|
72
|
-
|
|
90
|
+
async initialize() {
|
|
91
|
+
for (const [cellId, cell] of Object.entries(get().cells.config.data)) {
|
|
92
|
+
const registryItem = cellRegistry[cell.type];
|
|
93
|
+
if (registryItem?.onInitialize) {
|
|
94
|
+
registryItem.onInitialize({
|
|
95
|
+
id: cellId,
|
|
96
|
+
status: get().cells.status[cellId],
|
|
97
|
+
get,
|
|
98
|
+
set,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
setConfig(config) {
|
|
104
|
+
set((state) => produce(state, (draft) => {
|
|
105
|
+
draft.cells.config = config;
|
|
106
|
+
}));
|
|
107
|
+
},
|
|
108
|
+
crossFilterSelections: {},
|
|
109
|
+
setCrossFilterSelection: (chartCellId, sqlId, selection) => {
|
|
110
|
+
set((state) => produce(state, (draft) => {
|
|
111
|
+
if (!draft.cells.crossFilterSelections[sqlId]) {
|
|
112
|
+
draft.cells.crossFilterSelections[sqlId] = {};
|
|
113
|
+
}
|
|
114
|
+
draft.cells.crossFilterSelections[sqlId][chartCellId] = selection;
|
|
115
|
+
}));
|
|
116
|
+
},
|
|
117
|
+
getCrossFilterPredicate: (chartCellId, sqlId) => {
|
|
118
|
+
const group = get().cells.crossFilterSelections[sqlId];
|
|
119
|
+
if (!group)
|
|
120
|
+
return null;
|
|
121
|
+
const siblingSelections = Object.entries(group)
|
|
122
|
+
.filter(([id]) => id !== chartCellId)
|
|
123
|
+
.map(([, sel]) => sel);
|
|
124
|
+
return buildCrossFilterPredicate(siblingSelections);
|
|
125
|
+
},
|
|
126
|
+
clearCrossFilterGroup: (sqlId) => {
|
|
127
|
+
set((state) => produce(state, (draft) => {
|
|
128
|
+
delete draft.cells.crossFilterSelections[sqlId];
|
|
129
|
+
}));
|
|
130
|
+
},
|
|
131
|
+
ensureArtifact: (artifactId, options) => {
|
|
132
|
+
set((state) => produce(state, (draft) => {
|
|
133
|
+
if (!draft.cells.config.artifacts[artifactId]) {
|
|
134
|
+
draft.cells.config.artifacts[artifactId] = {
|
|
135
|
+
id: artifactId,
|
|
136
|
+
schemaName: options?.schemaName ?? getArtifactSchemaName(artifactId),
|
|
137
|
+
cellIds: [],
|
|
138
|
+
edges: [],
|
|
139
|
+
graphCache: {
|
|
140
|
+
dependencies: {},
|
|
141
|
+
dependents: {},
|
|
142
|
+
contentHashByCell: {},
|
|
143
|
+
tableDependencies: {},
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
else if (options?.schemaName &&
|
|
148
|
+
!draft.cells.config.artifacts[artifactId]?.schemaName) {
|
|
149
|
+
draft.cells.config.artifacts[artifactId].schemaName =
|
|
150
|
+
options.schemaName;
|
|
151
|
+
}
|
|
152
|
+
}));
|
|
153
|
+
},
|
|
154
|
+
addCell: async (artifactId, cell, index) => {
|
|
73
155
|
// Pre-compute dependencies outside produce() to support async
|
|
74
156
|
const sqlSelectToJson = getRequiredSqlSelectToJson(get());
|
|
75
|
-
const
|
|
76
|
-
const scopedCells = Object.fromEntries((
|
|
157
|
+
const targetArtifact = get().cells.config.artifacts[artifactId];
|
|
158
|
+
const scopedCells = Object.fromEntries((targetArtifact?.cellIds ?? [])
|
|
77
159
|
.map((id) => get().cells.config.data[id])
|
|
78
160
|
.filter(isDefined)
|
|
79
161
|
.map((candidate) => [candidate.id, candidate]));
|
|
80
162
|
scopedCells[cell.id] = cell;
|
|
81
|
-
|
|
163
|
+
// Generate a unique result name for SQL cells if not set
|
|
164
|
+
if (cell.type === 'sql' && !cell.data.resultName) {
|
|
165
|
+
const existingNames = Object.values(scopedCells)
|
|
166
|
+
.filter((c) => c.type === 'sql' && c.id !== cell.id)
|
|
167
|
+
.map((c) => getEffectiveResultName(c.data, convertToValidColumnOrTableName));
|
|
168
|
+
const baseName = getEffectiveResultName(cell.data, convertToValidColumnOrTableName);
|
|
169
|
+
const uniqueName = generateUniqueName(baseName, existingNames);
|
|
170
|
+
cell.data.resultName = uniqueName;
|
|
171
|
+
}
|
|
172
|
+
const deps = await resolveDependencies(cell, scopedCells, artifactId, cellRegistry, sqlSelectToJson);
|
|
82
173
|
set((state) => produce(state, (draft) => {
|
|
83
174
|
draft.cells.config.data[cell.id] = cell;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
draft.cells.status[cell.id] = { type: 'other' };
|
|
93
|
-
}
|
|
94
|
-
// Single-owner invariant: a cell can belong to one sheet only.
|
|
95
|
-
for (const [existingSheetId, existingSheet] of Object.entries(draft.cells.config.sheets)) {
|
|
96
|
-
if (existingSheetId === sheetId)
|
|
175
|
+
const registryItem = cellRegistry[cell.type];
|
|
176
|
+
draft.cells.status[cell.id] = registryItem?.createStatus?.(cell.id) ?? { type: 'other' };
|
|
177
|
+
// Single-owner invariant: a cell can belong to one artifact only.
|
|
178
|
+
for (const [existingArtifactId, existingArtifact,] of Object.entries(draft.cells.config.artifacts)) {
|
|
179
|
+
if (existingArtifactId === artifactId)
|
|
97
180
|
continue;
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
removeCellFromCache(
|
|
181
|
+
existingArtifact.cellIds = existingArtifact.cellIds.filter((cid) => cid !== cell.id);
|
|
182
|
+
existingArtifact.edges = existingArtifact.edges.filter((e) => e.source !== cell.id && e.target !== cell.id);
|
|
183
|
+
removeCellFromCache(existingArtifact, cell.id);
|
|
101
184
|
}
|
|
102
|
-
let
|
|
103
|
-
if (!
|
|
104
|
-
|
|
105
|
-
id:
|
|
106
|
-
|
|
107
|
-
title: 'Sheet',
|
|
108
|
-
schemaName: getSheetSchemaName(sheetId),
|
|
185
|
+
let artifact = draft.cells.config.artifacts[artifactId];
|
|
186
|
+
if (!artifact) {
|
|
187
|
+
artifact = {
|
|
188
|
+
id: artifactId,
|
|
189
|
+
schemaName: getArtifactSchemaName(artifactId),
|
|
109
190
|
cellIds: [],
|
|
110
191
|
edges: [],
|
|
111
192
|
graphCache: {
|
|
112
193
|
dependencies: {},
|
|
113
194
|
dependents: {},
|
|
114
195
|
contentHashByCell: {},
|
|
196
|
+
tableDependencies: {},
|
|
115
197
|
},
|
|
116
198
|
};
|
|
117
|
-
draft.cells.config.
|
|
118
|
-
if (!draft.cells.config.sheetOrder.includes(sheetId)) {
|
|
119
|
-
draft.cells.config.sheetOrder.push(sheetId);
|
|
120
|
-
}
|
|
121
|
-
if (!draft.cells.config.currentSheetId) {
|
|
122
|
-
draft.cells.config.currentSheetId = sheetId;
|
|
123
|
-
}
|
|
199
|
+
draft.cells.config.artifacts[artifactId] = artifact;
|
|
124
200
|
}
|
|
125
|
-
const newIndex = index !== undefined ? index :
|
|
126
|
-
|
|
127
|
-
|
|
201
|
+
const newIndex = index !== undefined ? index : artifact.cellIds.length;
|
|
202
|
+
artifact.cellIds = artifact.cellIds.filter((cid) => cid !== cell.id);
|
|
203
|
+
artifact.cellIds.splice(newIndex, 0, cell.id);
|
|
128
204
|
// Add edges from pre-computed dependencies
|
|
129
|
-
|
|
205
|
+
artifact.edges = artifact.edges.filter((edge) => edge.target !== cell.id &&
|
|
130
206
|
edge.id !== `${edge.source}-${cell.id}`);
|
|
131
|
-
const localCellIds = new Set(
|
|
132
|
-
for (const depId of deps) {
|
|
207
|
+
const localCellIds = new Set(artifact.cellIds);
|
|
208
|
+
for (const depId of deps.cellIds) {
|
|
133
209
|
if (!localCellIds.has(depId) || depId === cell.id)
|
|
134
210
|
continue;
|
|
135
|
-
|
|
211
|
+
artifact.edges.push({
|
|
136
212
|
id: `${depId}-${cell.id}`,
|
|
137
213
|
source: depId,
|
|
138
214
|
target: cell.id,
|
|
139
215
|
});
|
|
140
216
|
}
|
|
141
|
-
replaceCellDependenciesInCache(
|
|
217
|
+
replaceCellDependenciesInCache(artifact, cell.id, deps.cellIds.filter((depId) => localCellIds.has(depId)), deps.tableNames);
|
|
142
218
|
}));
|
|
143
219
|
},
|
|
144
220
|
removeCell: (id) => {
|
|
221
|
+
const cell = get().cells.config.data[id];
|
|
145
222
|
const previousStatus = get().cells.status[id];
|
|
146
223
|
cellResultCache.delete(id);
|
|
147
224
|
set((state) => produce(state, (draft) => {
|
|
@@ -154,39 +231,46 @@ export function createCellsSlice(props) {
|
|
|
154
231
|
controller.abort();
|
|
155
232
|
delete draft.cells.activeAbortControllers[id];
|
|
156
233
|
}
|
|
157
|
-
// Remove from all
|
|
158
|
-
for (const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
removeCellFromCache(
|
|
234
|
+
// Remove from all artifacts
|
|
235
|
+
for (const artifact of Object.values(draft.cells.config.artifacts)) {
|
|
236
|
+
artifact.cellIds = artifact.cellIds.filter((cid) => cid !== id);
|
|
237
|
+
artifact.edges = artifact.edges.filter((e) => e.source !== id && e.target !== id);
|
|
238
|
+
removeCellFromCache(artifact, id);
|
|
162
239
|
}
|
|
163
240
|
}));
|
|
164
|
-
if (
|
|
165
|
-
|
|
241
|
+
if (cell) {
|
|
242
|
+
const registryItem = cellRegistry[cell.type];
|
|
243
|
+
if (registryItem?.onRemove) {
|
|
244
|
+
void registryItem.onRemove({
|
|
245
|
+
id,
|
|
246
|
+
status: previousStatus,
|
|
247
|
+
get,
|
|
248
|
+
set,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
void dropRelationsForStatus(cell.type, previousStatus);
|
|
253
|
+
}
|
|
166
254
|
}
|
|
167
255
|
},
|
|
168
256
|
updateCell: async (id, updater, opts) => {
|
|
169
257
|
const cell = get().cells.config.data[id];
|
|
170
258
|
if (!cell)
|
|
171
259
|
return;
|
|
172
|
-
const
|
|
173
|
-
const scopedCells = Object.fromEntries(((
|
|
174
|
-
get().cells.config.
|
|
260
|
+
const ownerArtifactId = findArtifactIdForCell(get(), id);
|
|
261
|
+
const scopedCells = Object.fromEntries(((ownerArtifactId &&
|
|
262
|
+
get().cells.config.artifacts[ownerArtifactId]?.cellIds) ||
|
|
175
263
|
[])
|
|
176
264
|
.map((cellId) => get().cells.config.data[cellId])
|
|
177
265
|
.filter(isDefined)
|
|
178
266
|
.map((candidate) => [candidate.id, candidate]));
|
|
179
267
|
const updatedCell = updater(cell);
|
|
180
268
|
scopedCells[id] = updatedCell;
|
|
181
|
-
|
|
182
|
-
const resultNameChanged = isSqlCell(cell) &&
|
|
183
|
-
isSqlCell(updatedCell) &&
|
|
184
|
-
cell.data.resultName !== updatedCell.data.resultName;
|
|
269
|
+
const registryItem = cellRegistry[cell.type];
|
|
185
270
|
const existingStatus = get().cells.status[id];
|
|
186
|
-
const
|
|
187
|
-
existingStatus
|
|
188
|
-
|
|
189
|
-
existingStatus.resultView;
|
|
271
|
+
const oldResultRelation = existingStatus && registryItem?.getResultRelation
|
|
272
|
+
? registryItem.getResultRelation(existingStatus)
|
|
273
|
+
: undefined;
|
|
190
274
|
const semanticInputChanged = isInputCell(cell) &&
|
|
191
275
|
isInputCell(updatedCell) &&
|
|
192
276
|
(cell.data.input.varName !== updatedCell.data.input.varName ||
|
|
@@ -194,12 +278,20 @@ export function createCellsSlice(props) {
|
|
|
194
278
|
const semanticSqlChanged = isSqlCell(cell) &&
|
|
195
279
|
isSqlCell(updatedCell) &&
|
|
196
280
|
cell.data.sql !== updatedCell.data.sql;
|
|
281
|
+
const semanticChanged = registryItem?.hasSemanticChange?.(cell, updatedCell) ?? false;
|
|
197
282
|
// Apply cell data to the store immediately so that other
|
|
198
283
|
// actions (e.g. runCell triggered via Cmd+Enter) always see
|
|
199
284
|
// the latest value. Edge/dependency updates follow
|
|
200
285
|
// asynchronously below.
|
|
201
286
|
set((state) => produce(state, (draft) => {
|
|
202
287
|
draft.cells.config.data[id] = updatedCell;
|
|
288
|
+
if (semanticChanged) {
|
|
289
|
+
const status = draft.cells.status[id];
|
|
290
|
+
if (status && registryItem?.invalidateStatus) {
|
|
291
|
+
draft.cells.status[id] =
|
|
292
|
+
registryItem.invalidateStatus(status);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
203
295
|
}));
|
|
204
296
|
// Avoid expensive dependency graph recomputation on each SQL keystroke.
|
|
205
297
|
// We recompute immediately for non-SQL edits, or when callers explicitly
|
|
@@ -208,13 +300,13 @@ export function createCellsSlice(props) {
|
|
|
208
300
|
if (shouldRecomputeDependencies) {
|
|
209
301
|
// Pre-compute dependencies outside produce() to support async
|
|
210
302
|
const sqlSelectToJson = getRequiredSqlSelectToJson(get());
|
|
211
|
-
const newDeps = await resolveDependencies(updatedCell, scopedCells,
|
|
303
|
+
const newDeps = await resolveDependencies(updatedCell, scopedCells, ownerArtifactId || '', cellRegistry, sqlSelectToJson);
|
|
212
304
|
set((state) => produce(state, (draft) => {
|
|
213
|
-
const ownerSheet = Object.values(draft.cells.config.
|
|
305
|
+
const ownerSheet = Object.values(draft.cells.config.artifacts).find((artifact) => artifact.cellIds.includes(id));
|
|
214
306
|
if (ownerSheet) {
|
|
215
307
|
const localCellIds = new Set(ownerSheet.cellIds);
|
|
216
308
|
ownerSheet.edges = ownerSheet.edges.filter((e) => e.target !== id);
|
|
217
|
-
for (const depId of newDeps) {
|
|
309
|
+
for (const depId of newDeps.cellIds) {
|
|
218
310
|
if (!localCellIds.has(depId) || depId === id)
|
|
219
311
|
continue;
|
|
220
312
|
ownerSheet.edges.push({
|
|
@@ -223,65 +315,45 @@ export function createCellsSlice(props) {
|
|
|
223
315
|
target: id,
|
|
224
316
|
});
|
|
225
317
|
}
|
|
226
|
-
replaceCellDependenciesInCache(ownerSheet, id, newDeps.filter((depId) => localCellIds.has(depId)));
|
|
318
|
+
replaceCellDependenciesInCache(ownerSheet, id, newDeps.cellIds.filter((depId) => localCellIds.has(depId)), newDeps.tableNames);
|
|
227
319
|
}
|
|
228
320
|
}));
|
|
229
321
|
}
|
|
230
|
-
// If
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
322
|
+
// If the result relation changed name (e.g. SQL resultName changed), rename it
|
|
323
|
+
const newStatus = get().cells.status[id];
|
|
324
|
+
const newResultRelation = newStatus && registryItem?.getResultRelation
|
|
325
|
+
? registryItem.getResultRelation(newStatus)
|
|
326
|
+
: undefined;
|
|
327
|
+
const resultRelationChanged = oldResultRelation && newResultRelation !== oldResultRelation;
|
|
328
|
+
if (resultRelationChanged && registryItem?.renameResult) {
|
|
329
|
+
void registryItem.renameResult({
|
|
330
|
+
id,
|
|
331
|
+
oldResultView: oldResultRelation,
|
|
332
|
+
get,
|
|
333
|
+
set,
|
|
334
|
+
});
|
|
241
335
|
}
|
|
242
336
|
// After update, trigger cascade only if explicitly requested
|
|
243
337
|
// or if semantic execution inputs changed.
|
|
244
|
-
const shouldCascade = opts?.cascade ||
|
|
338
|
+
const shouldCascade = opts?.cascade || resultRelationChanged || semanticInputChanged;
|
|
245
339
|
if (shouldCascade) {
|
|
246
|
-
if (
|
|
247
|
-
void get().cells.runDownstreamCascade(
|
|
340
|
+
if (ownerArtifactId) {
|
|
341
|
+
void get().cells.runDownstreamCascade(ownerArtifactId, id);
|
|
248
342
|
}
|
|
249
343
|
}
|
|
250
344
|
},
|
|
251
|
-
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
const typeLabel = type.charAt(0).toUpperCase() + type.slice(1);
|
|
255
|
-
const existingTitles = Object.values(draft.cells.config.sheets).map((s) => s.title);
|
|
256
|
-
const defaultTitle = generateUniqueName(`${typeLabel} 1`, existingTitles, ' ');
|
|
257
|
-
draft.cells.config.sheets[id] = {
|
|
258
|
-
id,
|
|
259
|
-
type,
|
|
260
|
-
title: title || defaultTitle,
|
|
261
|
-
schemaName: getSheetSchemaName(id),
|
|
262
|
-
cellIds: [],
|
|
263
|
-
edges: [],
|
|
264
|
-
graphCache: {
|
|
265
|
-
dependencies: {},
|
|
266
|
-
dependents: {},
|
|
267
|
-
contentHashByCell: {},
|
|
268
|
-
},
|
|
269
|
-
};
|
|
270
|
-
draft.cells.config.sheetOrder.push(id);
|
|
271
|
-
draft.cells.config.currentSheetId = id;
|
|
272
|
-
}));
|
|
273
|
-
return id;
|
|
274
|
-
},
|
|
275
|
-
removeSheet: (sheetId) => {
|
|
276
|
-
const sheet = get().cells.config.sheets[sheetId];
|
|
277
|
-
if (!sheet)
|
|
345
|
+
removeArtifact: (artifactId) => {
|
|
346
|
+
const artifact = get().cells.config.artifacts[artifactId];
|
|
347
|
+
if (!artifact)
|
|
278
348
|
return;
|
|
279
|
-
const ownedCellIds = [...
|
|
349
|
+
const ownedCellIds = [...artifact.cellIds];
|
|
280
350
|
const relationNamesToDrop = ownedCellIds.flatMap((cellId) => {
|
|
351
|
+
const cell = get().cells.config.data[cellId];
|
|
281
352
|
const status = get().cells.status[cellId];
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
353
|
+
if (!cell || !status)
|
|
354
|
+
return [];
|
|
355
|
+
const registryItem = cellRegistry[cell.type];
|
|
356
|
+
return registryItem?.getRelationsToDrop?.(status) ?? [];
|
|
285
357
|
});
|
|
286
358
|
set((state) => produce(state, (draft) => {
|
|
287
359
|
for (const cellId of ownedCellIds) {
|
|
@@ -296,7 +368,7 @@ export function createCellsSlice(props) {
|
|
|
296
368
|
}
|
|
297
369
|
delete draft.cells.activeAbortControllers[cellId];
|
|
298
370
|
}
|
|
299
|
-
for (const existingSheet of Object.values(draft.cells.config.
|
|
371
|
+
for (const existingSheet of Object.values(draft.cells.config.artifacts)) {
|
|
300
372
|
existingSheet.cellIds = existingSheet.cellIds.filter((cid) => !ownedCellIds.includes(cid));
|
|
301
373
|
existingSheet.edges = existingSheet.edges.filter((e) => !ownedCellIds.includes(e.source) &&
|
|
302
374
|
!ownedCellIds.includes(e.target));
|
|
@@ -304,124 +376,72 @@ export function createCellsSlice(props) {
|
|
|
304
376
|
removeCellFromCache(existingSheet, cellId);
|
|
305
377
|
}
|
|
306
378
|
}
|
|
307
|
-
delete draft.cells.config.
|
|
308
|
-
draft.cells.config.sheetOrder =
|
|
309
|
-
draft.cells.config.sheetOrder.filter((id) => id !== sheetId);
|
|
310
|
-
if (draft.cells.config.currentSheetId === sheetId) {
|
|
311
|
-
draft.cells.config.currentSheetId =
|
|
312
|
-
draft.cells.config.sheetOrder[0];
|
|
313
|
-
}
|
|
379
|
+
delete draft.cells.config.artifacts[artifactId];
|
|
314
380
|
}));
|
|
315
381
|
for (const relationName of relationNamesToDrop) {
|
|
316
|
-
void
|
|
382
|
+
void dropRelationBestEffort(relationName);
|
|
317
383
|
}
|
|
318
384
|
},
|
|
319
|
-
|
|
320
|
-
set((state) => produce(state, (draft) => {
|
|
321
|
-
draft.cells.config.sheetOrder =
|
|
322
|
-
draft.cells.config.sheetOrder.filter((id) => id !== sheetId);
|
|
323
|
-
if (draft.cells.config.currentSheetId === sheetId) {
|
|
324
|
-
draft.cells.config.currentSheetId =
|
|
325
|
-
draft.cells.config.sheetOrder[0];
|
|
326
|
-
}
|
|
327
|
-
}));
|
|
328
|
-
},
|
|
329
|
-
openSheet: (sheetId) => {
|
|
330
|
-
set((state) => produce(state, (draft) => {
|
|
331
|
-
if (!draft.cells.config.sheetOrder.includes(sheetId)) {
|
|
332
|
-
draft.cells.config.sheetOrder.push(sheetId);
|
|
333
|
-
}
|
|
334
|
-
draft.cells.config.currentSheetId = sheetId;
|
|
335
|
-
}));
|
|
336
|
-
},
|
|
337
|
-
setSheetOrder: (sheetOrder) => {
|
|
385
|
+
addEdge: (artifactId, edge) => {
|
|
338
386
|
set((state) => produce(state, (draft) => {
|
|
339
|
-
draft.cells.config.
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
const sheet = draft.cells.config.sheets[sheetId];
|
|
345
|
-
if (sheet) {
|
|
346
|
-
// We could add validation here, but usually UI handles it.
|
|
347
|
-
// For now, we'll just allow it as the primary logic is in view slices.
|
|
348
|
-
sheet.title = title;
|
|
349
|
-
}
|
|
350
|
-
}));
|
|
351
|
-
},
|
|
352
|
-
setCurrentSheet: (sheetId) => {
|
|
353
|
-
set((state) => produce(state, (draft) => {
|
|
354
|
-
draft.cells.config.currentSheetId = sheetId;
|
|
355
|
-
}));
|
|
356
|
-
},
|
|
357
|
-
addEdge: (sheetId, edge) => {
|
|
358
|
-
set((state) => produce(state, (draft) => {
|
|
359
|
-
let sheet = draft.cells.config.sheets[sheetId];
|
|
360
|
-
if (!sheet) {
|
|
361
|
-
sheet = {
|
|
362
|
-
id: sheetId,
|
|
363
|
-
type: 'notebook',
|
|
364
|
-
title: 'Sheet',
|
|
365
|
-
schemaName: getSheetSchemaName(sheetId),
|
|
387
|
+
let artifact = draft.cells.config.artifacts[artifactId];
|
|
388
|
+
if (!artifact) {
|
|
389
|
+
artifact = {
|
|
390
|
+
id: artifactId,
|
|
391
|
+
schemaName: getArtifactSchemaName(artifactId),
|
|
366
392
|
cellIds: [],
|
|
367
393
|
edges: [],
|
|
368
394
|
graphCache: {
|
|
369
395
|
dependencies: {},
|
|
370
396
|
dependents: {},
|
|
371
397
|
contentHashByCell: {},
|
|
398
|
+
tableDependencies: {},
|
|
372
399
|
},
|
|
373
400
|
};
|
|
374
|
-
draft.cells.config.
|
|
375
|
-
if (!draft.cells.config.sheetOrder.includes(sheetId)) {
|
|
376
|
-
draft.cells.config.sheetOrder.push(sheetId);
|
|
377
|
-
}
|
|
378
|
-
if (!draft.cells.config.currentSheetId) {
|
|
379
|
-
draft.cells.config.currentSheetId = sheetId;
|
|
380
|
-
}
|
|
401
|
+
draft.cells.config.artifacts[artifactId] = artifact;
|
|
381
402
|
}
|
|
382
|
-
if (!
|
|
383
|
-
!
|
|
403
|
+
if (!artifact.cellIds.includes(edge.source) ||
|
|
404
|
+
!artifact.cellIds.includes(edge.target)) {
|
|
384
405
|
return;
|
|
385
406
|
}
|
|
386
407
|
const id = `${edge.source}-${edge.target}`;
|
|
387
|
-
if (!
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
replaceCellDependenciesInCache(sheet, edge.target, [
|
|
408
|
+
if (!artifact.edges.find((e) => e.id === id)) {
|
|
409
|
+
artifact.edges.push({ ...edge, id });
|
|
410
|
+
const deps = artifact.graphCache?.dependencies[edge.target] || [];
|
|
411
|
+
replaceCellDependenciesInCache(artifact, edge.target, [
|
|
392
412
|
...deps,
|
|
393
413
|
edge.source,
|
|
394
414
|
]);
|
|
395
415
|
}
|
|
396
416
|
}));
|
|
397
417
|
},
|
|
398
|
-
removeEdge: (
|
|
418
|
+
removeEdge: (artifactId, edgeId) => {
|
|
399
419
|
set((state) => produce(state, (draft) => {
|
|
400
|
-
const
|
|
401
|
-
if (
|
|
402
|
-
|
|
403
|
-
delete
|
|
420
|
+
const artifact = draft.cells.config.artifacts[artifactId];
|
|
421
|
+
if (artifact) {
|
|
422
|
+
artifact.edges = artifact.edges.filter((e) => e.id !== edgeId);
|
|
423
|
+
delete artifact.graphCache;
|
|
404
424
|
}
|
|
405
425
|
}));
|
|
406
426
|
},
|
|
407
|
-
updateEdgesFromSql: async (
|
|
427
|
+
updateEdgesFromSql: async (artifactId, cellId) => {
|
|
408
428
|
const cell = get().cells.config.data[cellId];
|
|
409
|
-
const
|
|
410
|
-
if (!cell || !
|
|
429
|
+
const artifact = get().cells.config.artifacts[artifactId];
|
|
430
|
+
if (!cell || !artifact)
|
|
411
431
|
return;
|
|
412
432
|
// Pre-compute dependencies outside produce() to support async
|
|
413
433
|
const sqlSelectToJson = getRequiredSqlSelectToJson(get());
|
|
414
|
-
const scopedCells = Object.fromEntries(
|
|
434
|
+
const scopedCells = Object.fromEntries(artifact.cellIds
|
|
415
435
|
.map((id) => get().cells.config.data[id])
|
|
416
436
|
.filter(isDefined)
|
|
417
437
|
.map((candidate) => [candidate.id, candidate]));
|
|
418
|
-
const deps = await resolveDependencies(cell, scopedCells,
|
|
438
|
+
const deps = await resolveDependencies(cell, scopedCells, artifactId, cellRegistry, sqlSelectToJson);
|
|
419
439
|
set((state) => produce(state, (draft) => {
|
|
420
|
-
const draftSheet = draft.cells.config.
|
|
440
|
+
const draftSheet = draft.cells.config.artifacts[artifactId];
|
|
421
441
|
if (draftSheet) {
|
|
422
442
|
draftSheet.edges = draftSheet.edges.filter((e) => e.target !== cellId);
|
|
423
443
|
const localCellIds = new Set(draftSheet.cellIds);
|
|
424
|
-
for (const depId of deps) {
|
|
444
|
+
for (const depId of deps.cellIds) {
|
|
425
445
|
if (!localCellIds.has(depId) || depId === cellId)
|
|
426
446
|
continue;
|
|
427
447
|
draftSheet.edges.push({
|
|
@@ -430,7 +450,7 @@ export function createCellsSlice(props) {
|
|
|
430
450
|
target: cellId,
|
|
431
451
|
});
|
|
432
452
|
}
|
|
433
|
-
replaceCellDependenciesInCache(draftSheet, cellId, deps.filter((depId) => localCellIds.has(depId)));
|
|
453
|
+
replaceCellDependenciesInCache(draftSheet, cellId, deps.cellIds.filter((depId) => localCellIds.has(depId)), deps.tableNames);
|
|
434
454
|
}
|
|
435
455
|
}));
|
|
436
456
|
},
|
|
@@ -457,15 +477,14 @@ export function createCellsSlice(props) {
|
|
|
457
477
|
}
|
|
458
478
|
},
|
|
459
479
|
invalidateCellStatus: (id) => {
|
|
480
|
+
const cell = get().cells.config.data[id];
|
|
481
|
+
if (!cell)
|
|
482
|
+
return;
|
|
483
|
+
const registryItem = cellRegistry[cell.type];
|
|
460
484
|
set((state) => produce(state, (draft) => {
|
|
461
485
|
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
|
-
};
|
|
486
|
+
if (status && registryItem?.invalidateStatus) {
|
|
487
|
+
draft.cells.status[id] = registryItem.invalidateStatus(status);
|
|
469
488
|
}
|
|
470
489
|
}));
|
|
471
490
|
},
|
|
@@ -496,25 +515,35 @@ export function createCellsSlice(props) {
|
|
|
496
515
|
},
|
|
497
516
|
fetchCellResultPage: async (id, pagination, sorting) => {
|
|
498
517
|
const state = get();
|
|
518
|
+
const cell = state.cells.config.data[id];
|
|
519
|
+
if (!cell)
|
|
520
|
+
return;
|
|
521
|
+
const registryItem = cellRegistry[cell.type];
|
|
499
522
|
const status = state.cells.status[id];
|
|
500
|
-
|
|
523
|
+
const resultRelation = status && registryItem?.getResultRelation
|
|
524
|
+
? registryItem.getResultRelation(status)
|
|
525
|
+
: undefined;
|
|
526
|
+
if (!resultRelation)
|
|
501
527
|
return;
|
|
502
528
|
const connector = await state.db.getConnector();
|
|
503
|
-
const baseQuery = sanitizeQuery(`SELECT * FROM ${
|
|
529
|
+
const baseQuery = sanitizeQuery(`SELECT * FROM ${resultRelation}`);
|
|
504
530
|
const pagedQuery = makePagedQuery(baseQuery, sorting ?? [], pagination);
|
|
505
531
|
const arrowTable = await connector.query(pagedQuery);
|
|
506
|
-
const countResult = await connector.query(`SELECT COUNT(*)::int AS count FROM ${
|
|
532
|
+
const countResult = await connector.query(`SELECT COUNT(*)::int AS count FROM ${resultRelation}`);
|
|
507
533
|
const totalRows = countResult.toArray()[0]?.count ?? 0;
|
|
508
534
|
get().cells.setCellResultPage(id, { arrowTable, totalRows });
|
|
509
535
|
},
|
|
510
536
|
// DAG methods (sync versions for UI usage)
|
|
511
|
-
|
|
512
|
-
|
|
537
|
+
getArtifactIdForCell: (cellId) => {
|
|
538
|
+
return findArtifactIdForCell(get(), cellId);
|
|
539
|
+
},
|
|
540
|
+
getRootCells: (artifactId) => {
|
|
541
|
+
const { dependencies } = buildDependencyGraph(artifactId, get());
|
|
513
542
|
const ids = Object.keys(dependencies);
|
|
514
543
|
return ids.filter((id) => (dependencies[id]?.length ?? 0) === 0);
|
|
515
544
|
},
|
|
516
|
-
getDownstream: (
|
|
517
|
-
const { dependencies, dependents } = buildDependencyGraph(
|
|
545
|
+
getDownstream: (artifactId, sourceCellId) => {
|
|
546
|
+
const { dependencies, dependents } = buildDependencyGraph(artifactId, get());
|
|
518
547
|
const reachable = collectReachable(sourceCellId, dependents);
|
|
519
548
|
if (!reachable.size)
|
|
520
549
|
return [];
|
|
@@ -526,19 +555,19 @@ export function createCellsSlice(props) {
|
|
|
526
555
|
return topologicalOrder(rootsWithinScope, dependencies, dependents, reachable);
|
|
527
556
|
},
|
|
528
557
|
// Async cascade execution using AST-based dependency resolution
|
|
529
|
-
runAllCellsCascade: async (
|
|
530
|
-
const cached = buildDependencyGraph(
|
|
558
|
+
runAllCellsCascade: async (artifactId) => {
|
|
559
|
+
const cached = buildDependencyGraph(artifactId, get());
|
|
531
560
|
const hasCachedNodes = Object.keys(cached.dependencies).length > 0;
|
|
532
561
|
if (!hasCachedNodes) {
|
|
533
562
|
set((state) => produce(state, (draft) => {
|
|
534
|
-
const
|
|
535
|
-
if (
|
|
536
|
-
|
|
563
|
+
const artifact = draft.cells.config.artifacts[artifactId];
|
|
564
|
+
if (artifact) {
|
|
565
|
+
artifact.graphCache = buildGraphCacheFromEdges(artifact);
|
|
537
566
|
}
|
|
538
567
|
}));
|
|
539
568
|
}
|
|
540
569
|
const sqlSelectToJson = getRequiredSqlSelectToJson(get());
|
|
541
|
-
const { dependencies, dependents } = await buildDependencyGraphAsync(
|
|
570
|
+
const { dependencies, dependents } = await buildDependencyGraphAsync(artifactId, get(), sqlSelectToJson);
|
|
542
571
|
const roots = Object.keys(dependencies).filter((id) => (dependencies[id]?.length ?? 0) === 0);
|
|
543
572
|
const order = topologicalOrder(roots, dependencies, dependents);
|
|
544
573
|
for (const cellId of order) {
|
|
@@ -547,29 +576,35 @@ export function createCellsSlice(props) {
|
|
|
547
576
|
}
|
|
548
577
|
catch (error) {
|
|
549
578
|
const message = error instanceof Error ? error.message : String(error);
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
579
|
+
const failedCell = get().cells.config.data[cellId];
|
|
580
|
+
const failedRegistryItem = failedCell
|
|
581
|
+
? cellRegistry[failedCell.type]
|
|
582
|
+
: undefined;
|
|
583
|
+
if (failedRegistryItem?.recordError) {
|
|
584
|
+
set((state) => produce(state, (draft) => {
|
|
585
|
+
const status = draft.cells.status[cellId];
|
|
586
|
+
if (status) {
|
|
587
|
+
draft.cells.status[cellId] =
|
|
588
|
+
failedRegistryItem.recordError(status, message);
|
|
589
|
+
}
|
|
590
|
+
}));
|
|
591
|
+
}
|
|
557
592
|
}
|
|
558
593
|
}
|
|
559
594
|
},
|
|
560
|
-
runDownstreamCascade: async (
|
|
561
|
-
const cached = buildDependencyGraph(
|
|
595
|
+
runDownstreamCascade: async (artifactId, sourceCellId) => {
|
|
596
|
+
const cached = buildDependencyGraph(artifactId, get());
|
|
562
597
|
const hasCachedNodes = Object.keys(cached.dependencies).length > 0;
|
|
563
598
|
if (!hasCachedNodes) {
|
|
564
599
|
set((state) => produce(state, (draft) => {
|
|
565
|
-
const
|
|
566
|
-
if (
|
|
567
|
-
|
|
600
|
+
const artifact = draft.cells.config.artifacts[artifactId];
|
|
601
|
+
if (artifact) {
|
|
602
|
+
artifact.graphCache = buildGraphCacheFromEdges(artifact);
|
|
568
603
|
}
|
|
569
604
|
}));
|
|
570
605
|
}
|
|
571
606
|
const sqlSelectToJson = getRequiredSqlSelectToJson(get());
|
|
572
|
-
const { dependencies, dependents } = await buildDependencyGraphAsync(
|
|
607
|
+
const { dependencies, dependents } = await buildDependencyGraphAsync(artifactId, get(), sqlSelectToJson);
|
|
573
608
|
const reachable = collectReachable(sourceCellId, dependents);
|
|
574
609
|
if (!reachable.size)
|
|
575
610
|
return;
|
|
@@ -585,13 +620,19 @@ export function createCellsSlice(props) {
|
|
|
585
620
|
}
|
|
586
621
|
catch (error) {
|
|
587
622
|
const message = error instanceof Error ? error.message : String(error);
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
623
|
+
const failedCell = get().cells.config.data[cellId];
|
|
624
|
+
const failedRegistryItem = failedCell
|
|
625
|
+
? cellRegistry[failedCell.type]
|
|
626
|
+
: undefined;
|
|
627
|
+
if (failedRegistryItem?.recordError) {
|
|
628
|
+
set((state) => produce(state, (draft) => {
|
|
629
|
+
const status = draft.cells.status[cellId];
|
|
630
|
+
if (status) {
|
|
631
|
+
draft.cells.status[cellId] =
|
|
632
|
+
failedRegistryItem.recordError(status, message);
|
|
633
|
+
}
|
|
634
|
+
}));
|
|
635
|
+
}
|
|
595
636
|
}
|
|
596
637
|
}
|
|
597
638
|
},
|