@sqlrooms/cells 0.29.0-rc.2 → 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 +121 -198
- package/dist/cellsSlice.js.map +1 -1
- package/dist/components/CellSourceSelector.d.ts +1 -0
- package/dist/components/CellSourceSelector.d.ts.map +1 -1
- package/dist/components/CellSourceSelector.js +8 -9
- package/dist/components/CellSourceSelector.js.map +1 -1
- package/dist/components/SqlCellContent.js +3 -3
- package/dist/components/SqlCellContent.js.map +1 -1
- package/dist/components/SqlCellDependentsMenu.d.ts +3 -3
- package/dist/components/SqlCellDependentsMenu.d.ts.map +1 -1
- package/dist/components/SqlCellDependentsMenu.js +4 -4
- package/dist/components/SqlCellDependentsMenu.js.map +1 -1
- package/dist/components/VegaCellHeader.d.ts.map +1 -1
- package/dist/components/VegaCellHeader.js +2 -1
- package/dist/components/VegaCellHeader.js.map +1 -1
- package/dist/dagUtils.d.ts +9 -9
- package/dist/dagUtils.d.ts.map +1 -1
- package/dist/dagUtils.js +44 -42
- package/dist/dagUtils.js.map +1 -1
- package/dist/defaultCellRegistry.d.ts.map +1 -1
- package/dist/defaultCellRegistry.js +16 -12
- package/dist/defaultCellRegistry.js.map +1 -1
- package/dist/execution.d.ts.map +1 -1
- package/dist/execution.js +22 -16
- package/dist/execution.js.map +1 -1
- package/dist/helpers.d.ts +9 -17
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +24 -43
- package/dist/helpers.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/sqlHelpers.d.ts +5 -5
- package/dist/sqlHelpers.d.ts.map +1 -1
- package/dist/sqlHelpers.js +7 -7
- package/dist/sqlHelpers.js.map +1 -1
- package/dist/types.d.ts +19 -47
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +6 -12
- package/dist/types.js.map +1 -1
- package/dist/useValidateResultName.js +5 -5
- package/dist/useValidateResultName.js.map +1 -1
- package/dist/utils.d.ts +2 -2
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +4 -4
- package/dist/utils.js.map +1 -1
- package/package.json +10 -10
- 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 -58
- package/dist/components/SheetsTabBar.js.map +0 -1
- package/dist/components/SqlCellBody.d.ts +0 -6
- package/dist/components/SqlCellBody.d.ts.map +0 -1
- package/dist/components/SqlCellBody.js +0 -12
- package/dist/components/SqlCellBody.js.map +0 -1
- package/dist/crdt.d.ts +0 -61
- package/dist/crdt.d.ts.map +0 -1
- package/dist/crdt.js +0 -82
- package/dist/crdt.js.map +0 -1
- package/dist/dagSlice.d.ts +0 -3
- package/dist/dagSlice.d.ts.map +0 -1
- package/dist/dagSlice.js +0 -51
- package/dist/dagSlice.js.map +0 -1
- package/dist/typeGuards.d.ts +0 -2
- package/dist/typeGuards.d.ts.map +0 -1
- package/dist/typeGuards.js +0 -4
- package/dist/typeGuards.js.map +0 -1
- package/dist/validation.d.ts +0 -16
- package/dist/validation.d.ts.map +0 -1
- package/dist/validation.js +0 -40
- package/dist/validation.js.map +0 -1
package/README.md
CHANGED
|
@@ -5,16 +5,16 @@ Shared cells model and UI primitives used by notebook and canvas views.
|
|
|
5
5
|
The package owns:
|
|
6
6
|
|
|
7
7
|
- canonical cell records (`cells.config.data`)
|
|
8
|
-
-
|
|
9
|
-
- in-
|
|
8
|
+
- artifact-scoped runtime containers (`cells.config.artifacts`)
|
|
9
|
+
- in-artifact dependency edges and cascade execution
|
|
10
10
|
- SQL/result execution helpers and status tracking
|
|
11
11
|
|
|
12
12
|
## Dependency and schema model
|
|
13
13
|
|
|
14
|
-
- Dependencies and cascades are **
|
|
15
|
-
- Cross-
|
|
16
|
-
- SQL execution resolves to a stable
|
|
17
|
-
- Unqualified result-name references are resolved to the current
|
|
14
|
+
- Dependencies and cascades are **artifact-local** by default.
|
|
15
|
+
- Cross-artifact references are only supported via explicit fully qualified SQL names.
|
|
16
|
+
- SQL execution resolves to a stable artifact schema namespace; it does not rely on global `USE schema`.
|
|
17
|
+
- Unqualified result-name references are resolved to the current artifact namespace during execution.
|
|
18
18
|
|
|
19
19
|
## Stable public API
|
|
20
20
|
|
|
@@ -22,9 +22,9 @@ Import from package root for stable APIs:
|
|
|
22
22
|
|
|
23
23
|
- slice: `createCellsSlice`
|
|
24
24
|
- hooks: `useCellsStore`
|
|
25
|
-
- helpers: `
|
|
25
|
+
- helpers: `findArtifactIdForCell`, `resolveArtifactSchemaName`
|
|
26
26
|
- SQL helpers: `renderSqlWithInputs`, `findSqlDependencies`, `findSqlDependenciesFromAst`
|
|
27
|
-
- UI: `SqlCellContent`, `SqlCellRunButton`, `TextCellContent`, `InputCellContent`, `VegaCellContent`, `
|
|
27
|
+
- UI: `SqlCellContent`, `SqlCellRunButton`, `TextCellContent`, `InputCellContent`, `VegaCellContent`, `CellSourceSelector`
|
|
28
28
|
- types/schemas: exports from `types.ts`
|
|
29
29
|
|
|
30
30
|
## Internal APIs
|
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":"AAyBA,OAAO,KAAK,EAKV,iBAAiB,EACjB,eAAe,EAKhB,MAAM,SAAS,CAAC;AAiCjB,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,iBAAiB,mDA6zBxD"}
|
package/dist/cellsSlice.js
CHANGED
|
@@ -1,54 +1,33 @@
|
|
|
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
4
|
import { convertToValidColumnOrTableName, generateUniqueName, } from '@sqlrooms/utils';
|
|
6
5
|
import { produce } from 'immer';
|
|
7
|
-
import { buildDependencyGraph, buildDependencyGraphAsync, buildGraphCacheFromEdges, collectReachable,
|
|
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';
|
|
12
11
|
import { buildCrossFilterPredicate } from './vegaSelectionUtils';
|
|
13
12
|
function createDefaultCellsConfig(config) {
|
|
14
|
-
const sheetId = createId();
|
|
15
13
|
const defaultConfig = {
|
|
16
14
|
data: {},
|
|
17
|
-
|
|
18
|
-
[sheetId]: {
|
|
19
|
-
id: sheetId,
|
|
20
|
-
type: 'notebook',
|
|
21
|
-
title: 'Sheet',
|
|
22
|
-
schemaName: getSheetSchemaName(sheetId),
|
|
23
|
-
cellIds: [],
|
|
24
|
-
edges: [],
|
|
25
|
-
graphCache: {
|
|
26
|
-
dependencies: {},
|
|
27
|
-
dependents: {},
|
|
28
|
-
contentHashByCell: {},
|
|
29
|
-
tableDependencies: {},
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
sheetOrder: [sheetId],
|
|
34
|
-
currentSheetId: sheetId,
|
|
15
|
+
artifacts: {},
|
|
35
16
|
tableDepSchemas: ['main'],
|
|
36
17
|
};
|
|
37
18
|
if (!config) {
|
|
38
19
|
return defaultConfig;
|
|
39
20
|
}
|
|
40
|
-
const {
|
|
21
|
+
const { artifacts } = normalizeCellsConfigStructure(config);
|
|
41
22
|
return {
|
|
42
23
|
...defaultConfig,
|
|
43
24
|
...config,
|
|
44
|
-
|
|
45
|
-
sheetOrder,
|
|
46
|
-
currentSheetId,
|
|
25
|
+
artifacts,
|
|
47
26
|
};
|
|
48
27
|
}
|
|
49
28
|
// --- Slice Implementation ---
|
|
50
29
|
export function createCellsSlice(props) {
|
|
51
|
-
const { cellRegistry
|
|
30
|
+
const { cellRegistry } = props;
|
|
52
31
|
const initialConfig = createDefaultCellsConfig(props?.config);
|
|
53
32
|
// Keep result data outside Immer drafts, but scoped per slice instance.
|
|
54
33
|
const cellResultCache = new Map();
|
|
@@ -89,8 +68,8 @@ export function createCellsSlice(props) {
|
|
|
89
68
|
}
|
|
90
69
|
if (!changed.size)
|
|
91
70
|
return;
|
|
92
|
-
for (const
|
|
93
|
-
const cache =
|
|
71
|
+
for (const artifact of Object.values(state.cells.config.artifacts)) {
|
|
72
|
+
const cache = artifact.graphCache;
|
|
94
73
|
if (!cache?.tableDependencies)
|
|
95
74
|
continue;
|
|
96
75
|
for (const [cellId, tables] of Object.entries(cache.tableDependencies)) {
|
|
@@ -103,7 +82,6 @@ export function createCellsSlice(props) {
|
|
|
103
82
|
return {
|
|
104
83
|
cells: {
|
|
105
84
|
cellRegistry,
|
|
106
|
-
supportedSheetTypes,
|
|
107
85
|
config: initialConfig,
|
|
108
86
|
status: {},
|
|
109
87
|
activeAbortControllers: {},
|
|
@@ -122,6 +100,11 @@ export function createCellsSlice(props) {
|
|
|
122
100
|
}
|
|
123
101
|
}
|
|
124
102
|
},
|
|
103
|
+
setConfig(config) {
|
|
104
|
+
set((state) => produce(state, (draft) => {
|
|
105
|
+
draft.cells.config = config;
|
|
106
|
+
}));
|
|
107
|
+
},
|
|
125
108
|
crossFilterSelections: {},
|
|
126
109
|
setCrossFilterSelection: (chartCellId, sqlId, selection) => {
|
|
127
110
|
set((state) => produce(state, (draft) => {
|
|
@@ -145,11 +128,34 @@ export function createCellsSlice(props) {
|
|
|
145
128
|
delete draft.cells.crossFilterSelections[sqlId];
|
|
146
129
|
}));
|
|
147
130
|
},
|
|
148
|
-
|
|
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) => {
|
|
149
155
|
// Pre-compute dependencies outside produce() to support async
|
|
150
156
|
const sqlSelectToJson = getRequiredSqlSelectToJson(get());
|
|
151
|
-
const
|
|
152
|
-
const scopedCells = Object.fromEntries((
|
|
157
|
+
const targetArtifact = get().cells.config.artifacts[artifactId];
|
|
158
|
+
const scopedCells = Object.fromEntries((targetArtifact?.cellIds ?? [])
|
|
153
159
|
.map((id) => get().cells.config.data[id])
|
|
154
160
|
.filter(isDefined)
|
|
155
161
|
.map((candidate) => [candidate.id, candidate]));
|
|
@@ -163,26 +169,24 @@ export function createCellsSlice(props) {
|
|
|
163
169
|
const uniqueName = generateUniqueName(baseName, existingNames);
|
|
164
170
|
cell.data.resultName = uniqueName;
|
|
165
171
|
}
|
|
166
|
-
const deps = await resolveDependencies(cell, scopedCells,
|
|
172
|
+
const deps = await resolveDependencies(cell, scopedCells, artifactId, cellRegistry, sqlSelectToJson);
|
|
167
173
|
set((state) => produce(state, (draft) => {
|
|
168
174
|
draft.cells.config.data[cell.id] = cell;
|
|
169
175
|
const registryItem = cellRegistry[cell.type];
|
|
170
176
|
draft.cells.status[cell.id] = registryItem?.createStatus?.(cell.id) ?? { type: 'other' };
|
|
171
|
-
// Single-owner invariant: a cell can belong to one
|
|
172
|
-
for (const [
|
|
173
|
-
if (
|
|
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)
|
|
174
180
|
continue;
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
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);
|
|
178
184
|
}
|
|
179
|
-
let
|
|
180
|
-
if (!
|
|
181
|
-
|
|
182
|
-
id:
|
|
183
|
-
|
|
184
|
-
title: 'Sheet',
|
|
185
|
-
schemaName: getSheetSchemaName(sheetId),
|
|
185
|
+
let artifact = draft.cells.config.artifacts[artifactId];
|
|
186
|
+
if (!artifact) {
|
|
187
|
+
artifact = {
|
|
188
|
+
id: artifactId,
|
|
189
|
+
schemaName: getArtifactSchemaName(artifactId),
|
|
186
190
|
cellIds: [],
|
|
187
191
|
edges: [],
|
|
188
192
|
graphCache: {
|
|
@@ -192,31 +196,25 @@ export function createCellsSlice(props) {
|
|
|
192
196
|
tableDependencies: {},
|
|
193
197
|
},
|
|
194
198
|
};
|
|
195
|
-
draft.cells.config.
|
|
196
|
-
if (!draft.cells.config.sheetOrder.includes(sheetId)) {
|
|
197
|
-
draft.cells.config.sheetOrder.push(sheetId);
|
|
198
|
-
}
|
|
199
|
-
if (!draft.cells.config.currentSheetId) {
|
|
200
|
-
draft.cells.config.currentSheetId = sheetId;
|
|
201
|
-
}
|
|
199
|
+
draft.cells.config.artifacts[artifactId] = artifact;
|
|
202
200
|
}
|
|
203
|
-
const newIndex = index !== undefined ? index :
|
|
204
|
-
|
|
205
|
-
|
|
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);
|
|
206
204
|
// Add edges from pre-computed dependencies
|
|
207
|
-
|
|
205
|
+
artifact.edges = artifact.edges.filter((edge) => edge.target !== cell.id &&
|
|
208
206
|
edge.id !== `${edge.source}-${cell.id}`);
|
|
209
|
-
const localCellIds = new Set(
|
|
207
|
+
const localCellIds = new Set(artifact.cellIds);
|
|
210
208
|
for (const depId of deps.cellIds) {
|
|
211
209
|
if (!localCellIds.has(depId) || depId === cell.id)
|
|
212
210
|
continue;
|
|
213
|
-
|
|
211
|
+
artifact.edges.push({
|
|
214
212
|
id: `${depId}-${cell.id}`,
|
|
215
213
|
source: depId,
|
|
216
214
|
target: cell.id,
|
|
217
215
|
});
|
|
218
216
|
}
|
|
219
|
-
replaceCellDependenciesInCache(
|
|
217
|
+
replaceCellDependenciesInCache(artifact, cell.id, deps.cellIds.filter((depId) => localCellIds.has(depId)), deps.tableNames);
|
|
220
218
|
}));
|
|
221
219
|
},
|
|
222
220
|
removeCell: (id) => {
|
|
@@ -233,11 +231,11 @@ export function createCellsSlice(props) {
|
|
|
233
231
|
controller.abort();
|
|
234
232
|
delete draft.cells.activeAbortControllers[id];
|
|
235
233
|
}
|
|
236
|
-
// Remove from all
|
|
237
|
-
for (const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
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);
|
|
241
239
|
}
|
|
242
240
|
}));
|
|
243
241
|
if (cell) {
|
|
@@ -259,9 +257,9 @@ export function createCellsSlice(props) {
|
|
|
259
257
|
const cell = get().cells.config.data[id];
|
|
260
258
|
if (!cell)
|
|
261
259
|
return;
|
|
262
|
-
const
|
|
263
|
-
const scopedCells = Object.fromEntries(((
|
|
264
|
-
get().cells.config.
|
|
260
|
+
const ownerArtifactId = findArtifactIdForCell(get(), id);
|
|
261
|
+
const scopedCells = Object.fromEntries(((ownerArtifactId &&
|
|
262
|
+
get().cells.config.artifacts[ownerArtifactId]?.cellIds) ||
|
|
265
263
|
[])
|
|
266
264
|
.map((cellId) => get().cells.config.data[cellId])
|
|
267
265
|
.filter(isDefined)
|
|
@@ -302,9 +300,9 @@ export function createCellsSlice(props) {
|
|
|
302
300
|
if (shouldRecomputeDependencies) {
|
|
303
301
|
// Pre-compute dependencies outside produce() to support async
|
|
304
302
|
const sqlSelectToJson = getRequiredSqlSelectToJson(get());
|
|
305
|
-
const newDeps = await resolveDependencies(updatedCell, scopedCells,
|
|
303
|
+
const newDeps = await resolveDependencies(updatedCell, scopedCells, ownerArtifactId || '', cellRegistry, sqlSelectToJson);
|
|
306
304
|
set((state) => produce(state, (draft) => {
|
|
307
|
-
const ownerSheet = Object.values(draft.cells.config.
|
|
305
|
+
const ownerSheet = Object.values(draft.cells.config.artifacts).find((artifact) => artifact.cellIds.includes(id));
|
|
308
306
|
if (ownerSheet) {
|
|
309
307
|
const localCellIds = new Set(ownerSheet.cellIds);
|
|
310
308
|
ownerSheet.edges = ownerSheet.edges.filter((e) => e.target !== id);
|
|
@@ -339,41 +337,16 @@ export function createCellsSlice(props) {
|
|
|
339
337
|
// or if semantic execution inputs changed.
|
|
340
338
|
const shouldCascade = opts?.cascade || resultRelationChanged || semanticInputChanged;
|
|
341
339
|
if (shouldCascade) {
|
|
342
|
-
if (
|
|
343
|
-
void get().cells.runDownstreamCascade(
|
|
340
|
+
if (ownerArtifactId) {
|
|
341
|
+
void get().cells.runDownstreamCascade(ownerArtifactId, id);
|
|
344
342
|
}
|
|
345
343
|
}
|
|
346
344
|
},
|
|
347
|
-
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
const typeLabel = type.charAt(0).toUpperCase() + type.slice(1);
|
|
351
|
-
const existingTitles = Object.values(draft.cells.config.sheets).map((s) => s.title);
|
|
352
|
-
const defaultTitle = generateUniqueName(`${typeLabel} 1`, existingTitles, ' ');
|
|
353
|
-
draft.cells.config.sheets[id] = {
|
|
354
|
-
id,
|
|
355
|
-
type,
|
|
356
|
-
title: title || defaultTitle,
|
|
357
|
-
schemaName: getSheetSchemaName(id),
|
|
358
|
-
cellIds: [],
|
|
359
|
-
edges: [],
|
|
360
|
-
graphCache: {
|
|
361
|
-
dependencies: {},
|
|
362
|
-
dependents: {},
|
|
363
|
-
contentHashByCell: {},
|
|
364
|
-
tableDependencies: {},
|
|
365
|
-
},
|
|
366
|
-
};
|
|
367
|
-
draft.cells.config.sheetOrder.push(id);
|
|
368
|
-
draft.cells.config.currentSheetId = id;
|
|
369
|
-
}));
|
|
370
|
-
return id;
|
|
371
|
-
},
|
|
372
|
-
removeSheet: (sheetId) => {
|
|
373
|
-
const sheet = get().cells.config.sheets[sheetId];
|
|
374
|
-
if (!sheet)
|
|
345
|
+
removeArtifact: (artifactId) => {
|
|
346
|
+
const artifact = get().cells.config.artifacts[artifactId];
|
|
347
|
+
if (!artifact)
|
|
375
348
|
return;
|
|
376
|
-
const ownedCellIds = [...
|
|
349
|
+
const ownedCellIds = [...artifact.cellIds];
|
|
377
350
|
const relationNamesToDrop = ownedCellIds.flatMap((cellId) => {
|
|
378
351
|
const cell = get().cells.config.data[cellId];
|
|
379
352
|
const status = get().cells.status[cellId];
|
|
@@ -395,7 +368,7 @@ export function createCellsSlice(props) {
|
|
|
395
368
|
}
|
|
396
369
|
delete draft.cells.activeAbortControllers[cellId];
|
|
397
370
|
}
|
|
398
|
-
for (const existingSheet of Object.values(draft.cells.config.
|
|
371
|
+
for (const existingSheet of Object.values(draft.cells.config.artifacts)) {
|
|
399
372
|
existingSheet.cellIds = existingSheet.cellIds.filter((cid) => !ownedCellIds.includes(cid));
|
|
400
373
|
existingSheet.edges = existingSheet.edges.filter((e) => !ownedCellIds.includes(e.source) &&
|
|
401
374
|
!ownedCellIds.includes(e.target));
|
|
@@ -403,65 +376,19 @@ export function createCellsSlice(props) {
|
|
|
403
376
|
removeCellFromCache(existingSheet, cellId);
|
|
404
377
|
}
|
|
405
378
|
}
|
|
406
|
-
delete draft.cells.config.
|
|
407
|
-
draft.cells.config.sheetOrder =
|
|
408
|
-
draft.cells.config.sheetOrder.filter((id) => id !== sheetId);
|
|
409
|
-
if (draft.cells.config.currentSheetId === sheetId) {
|
|
410
|
-
draft.cells.config.currentSheetId =
|
|
411
|
-
draft.cells.config.sheetOrder[0];
|
|
412
|
-
}
|
|
379
|
+
delete draft.cells.config.artifacts[artifactId];
|
|
413
380
|
}));
|
|
414
381
|
for (const relationName of relationNamesToDrop) {
|
|
415
382
|
void dropRelationBestEffort(relationName);
|
|
416
383
|
}
|
|
417
384
|
},
|
|
418
|
-
|
|
419
|
-
set((state) => produce(state, (draft) => {
|
|
420
|
-
draft.cells.config.sheetOrder =
|
|
421
|
-
draft.cells.config.sheetOrder.filter((id) => id !== sheetId);
|
|
422
|
-
if (draft.cells.config.currentSheetId === sheetId) {
|
|
423
|
-
draft.cells.config.currentSheetId =
|
|
424
|
-
draft.cells.config.sheetOrder[0];
|
|
425
|
-
}
|
|
426
|
-
}));
|
|
427
|
-
},
|
|
428
|
-
openSheet: (sheetId) => {
|
|
385
|
+
addEdge: (artifactId, edge) => {
|
|
429
386
|
set((state) => produce(state, (draft) => {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
},
|
|
436
|
-
setSheetOrder: (sheetOrder) => {
|
|
437
|
-
set((state) => produce(state, (draft) => {
|
|
438
|
-
draft.cells.config.sheetOrder = sheetOrder;
|
|
439
|
-
}));
|
|
440
|
-
},
|
|
441
|
-
renameSheet: (sheetId, title) => {
|
|
442
|
-
set((state) => produce(state, (draft) => {
|
|
443
|
-
const sheet = draft.cells.config.sheets[sheetId];
|
|
444
|
-
if (sheet) {
|
|
445
|
-
// We could add validation here, but usually UI handles it.
|
|
446
|
-
// For now, we'll just allow it as the primary logic is in view slices.
|
|
447
|
-
sheet.title = title;
|
|
448
|
-
}
|
|
449
|
-
}));
|
|
450
|
-
},
|
|
451
|
-
setCurrentSheet: (sheetId) => {
|
|
452
|
-
set((state) => produce(state, (draft) => {
|
|
453
|
-
draft.cells.config.currentSheetId = sheetId;
|
|
454
|
-
}));
|
|
455
|
-
},
|
|
456
|
-
addEdge: (sheetId, edge) => {
|
|
457
|
-
set((state) => produce(state, (draft) => {
|
|
458
|
-
let sheet = draft.cells.config.sheets[sheetId];
|
|
459
|
-
if (!sheet) {
|
|
460
|
-
sheet = {
|
|
461
|
-
id: sheetId,
|
|
462
|
-
type: 'notebook',
|
|
463
|
-
title: 'Sheet',
|
|
464
|
-
schemaName: getSheetSchemaName(sheetId),
|
|
387
|
+
let artifact = draft.cells.config.artifacts[artifactId];
|
|
388
|
+
if (!artifact) {
|
|
389
|
+
artifact = {
|
|
390
|
+
id: artifactId,
|
|
391
|
+
schemaName: getArtifactSchemaName(artifactId),
|
|
465
392
|
cellIds: [],
|
|
466
393
|
edges: [],
|
|
467
394
|
graphCache: {
|
|
@@ -471,53 +398,46 @@ export function createCellsSlice(props) {
|
|
|
471
398
|
tableDependencies: {},
|
|
472
399
|
},
|
|
473
400
|
};
|
|
474
|
-
draft.cells.config.
|
|
475
|
-
if (!draft.cells.config.sheetOrder.includes(sheetId)) {
|
|
476
|
-
draft.cells.config.sheetOrder.push(sheetId);
|
|
477
|
-
}
|
|
478
|
-
if (!draft.cells.config.currentSheetId) {
|
|
479
|
-
draft.cells.config.currentSheetId = sheetId;
|
|
480
|
-
}
|
|
401
|
+
draft.cells.config.artifacts[artifactId] = artifact;
|
|
481
402
|
}
|
|
482
|
-
if (!
|
|
483
|
-
!
|
|
403
|
+
if (!artifact.cellIds.includes(edge.source) ||
|
|
404
|
+
!artifact.cellIds.includes(edge.target)) {
|
|
484
405
|
return;
|
|
485
406
|
}
|
|
486
407
|
const id = `${edge.source}-${edge.target}`;
|
|
487
|
-
if (!
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
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, [
|
|
492
412
|
...deps,
|
|
493
413
|
edge.source,
|
|
494
414
|
]);
|
|
495
415
|
}
|
|
496
416
|
}));
|
|
497
417
|
},
|
|
498
|
-
removeEdge: (
|
|
418
|
+
removeEdge: (artifactId, edgeId) => {
|
|
499
419
|
set((state) => produce(state, (draft) => {
|
|
500
|
-
const
|
|
501
|
-
if (
|
|
502
|
-
|
|
503
|
-
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;
|
|
504
424
|
}
|
|
505
425
|
}));
|
|
506
426
|
},
|
|
507
|
-
updateEdgesFromSql: async (
|
|
427
|
+
updateEdgesFromSql: async (artifactId, cellId) => {
|
|
508
428
|
const cell = get().cells.config.data[cellId];
|
|
509
|
-
const
|
|
510
|
-
if (!cell || !
|
|
429
|
+
const artifact = get().cells.config.artifacts[artifactId];
|
|
430
|
+
if (!cell || !artifact)
|
|
511
431
|
return;
|
|
512
432
|
// Pre-compute dependencies outside produce() to support async
|
|
513
433
|
const sqlSelectToJson = getRequiredSqlSelectToJson(get());
|
|
514
|
-
const scopedCells = Object.fromEntries(
|
|
434
|
+
const scopedCells = Object.fromEntries(artifact.cellIds
|
|
515
435
|
.map((id) => get().cells.config.data[id])
|
|
516
436
|
.filter(isDefined)
|
|
517
437
|
.map((candidate) => [candidate.id, candidate]));
|
|
518
|
-
const deps = await resolveDependencies(cell, scopedCells,
|
|
438
|
+
const deps = await resolveDependencies(cell, scopedCells, artifactId, cellRegistry, sqlSelectToJson);
|
|
519
439
|
set((state) => produce(state, (draft) => {
|
|
520
|
-
const draftSheet = draft.cells.config.
|
|
440
|
+
const draftSheet = draft.cells.config.artifacts[artifactId];
|
|
521
441
|
if (draftSheet) {
|
|
522
442
|
draftSheet.edges = draftSheet.edges.filter((e) => e.target !== cellId);
|
|
523
443
|
const localCellIds = new Set(draftSheet.cellIds);
|
|
@@ -614,13 +534,16 @@ export function createCellsSlice(props) {
|
|
|
614
534
|
get().cells.setCellResultPage(id, { arrowTable, totalRows });
|
|
615
535
|
},
|
|
616
536
|
// DAG methods (sync versions for UI usage)
|
|
617
|
-
|
|
618
|
-
|
|
537
|
+
getArtifactIdForCell: (cellId) => {
|
|
538
|
+
return findArtifactIdForCell(get(), cellId);
|
|
539
|
+
},
|
|
540
|
+
getRootCells: (artifactId) => {
|
|
541
|
+
const { dependencies } = buildDependencyGraph(artifactId, get());
|
|
619
542
|
const ids = Object.keys(dependencies);
|
|
620
543
|
return ids.filter((id) => (dependencies[id]?.length ?? 0) === 0);
|
|
621
544
|
},
|
|
622
|
-
getDownstream: (
|
|
623
|
-
const { dependencies, dependents } = buildDependencyGraph(
|
|
545
|
+
getDownstream: (artifactId, sourceCellId) => {
|
|
546
|
+
const { dependencies, dependents } = buildDependencyGraph(artifactId, get());
|
|
624
547
|
const reachable = collectReachable(sourceCellId, dependents);
|
|
625
548
|
if (!reachable.size)
|
|
626
549
|
return [];
|
|
@@ -632,19 +555,19 @@ export function createCellsSlice(props) {
|
|
|
632
555
|
return topologicalOrder(rootsWithinScope, dependencies, dependents, reachable);
|
|
633
556
|
},
|
|
634
557
|
// Async cascade execution using AST-based dependency resolution
|
|
635
|
-
runAllCellsCascade: async (
|
|
636
|
-
const cached = buildDependencyGraph(
|
|
558
|
+
runAllCellsCascade: async (artifactId) => {
|
|
559
|
+
const cached = buildDependencyGraph(artifactId, get());
|
|
637
560
|
const hasCachedNodes = Object.keys(cached.dependencies).length > 0;
|
|
638
561
|
if (!hasCachedNodes) {
|
|
639
562
|
set((state) => produce(state, (draft) => {
|
|
640
|
-
const
|
|
641
|
-
if (
|
|
642
|
-
|
|
563
|
+
const artifact = draft.cells.config.artifacts[artifactId];
|
|
564
|
+
if (artifact) {
|
|
565
|
+
artifact.graphCache = buildGraphCacheFromEdges(artifact);
|
|
643
566
|
}
|
|
644
567
|
}));
|
|
645
568
|
}
|
|
646
569
|
const sqlSelectToJson = getRequiredSqlSelectToJson(get());
|
|
647
|
-
const { dependencies, dependents } = await buildDependencyGraphAsync(
|
|
570
|
+
const { dependencies, dependents } = await buildDependencyGraphAsync(artifactId, get(), sqlSelectToJson);
|
|
648
571
|
const roots = Object.keys(dependencies).filter((id) => (dependencies[id]?.length ?? 0) === 0);
|
|
649
572
|
const order = topologicalOrder(roots, dependencies, dependents);
|
|
650
573
|
for (const cellId of order) {
|
|
@@ -669,19 +592,19 @@ export function createCellsSlice(props) {
|
|
|
669
592
|
}
|
|
670
593
|
}
|
|
671
594
|
},
|
|
672
|
-
runDownstreamCascade: async (
|
|
673
|
-
const cached = buildDependencyGraph(
|
|
595
|
+
runDownstreamCascade: async (artifactId, sourceCellId) => {
|
|
596
|
+
const cached = buildDependencyGraph(artifactId, get());
|
|
674
597
|
const hasCachedNodes = Object.keys(cached.dependencies).length > 0;
|
|
675
598
|
if (!hasCachedNodes) {
|
|
676
599
|
set((state) => produce(state, (draft) => {
|
|
677
|
-
const
|
|
678
|
-
if (
|
|
679
|
-
|
|
600
|
+
const artifact = draft.cells.config.artifacts[artifactId];
|
|
601
|
+
if (artifact) {
|
|
602
|
+
artifact.graphCache = buildGraphCacheFromEdges(artifact);
|
|
680
603
|
}
|
|
681
604
|
}));
|
|
682
605
|
}
|
|
683
606
|
const sqlSelectToJson = getRequiredSqlSelectToJson(get());
|
|
684
|
-
const { dependencies, dependents } = await buildDependencyGraphAsync(
|
|
607
|
+
const { dependencies, dependents } = await buildDependencyGraphAsync(artifactId, get(), sqlSelectToJson);
|
|
685
608
|
const reachable = collectReachable(sourceCellId, dependents);
|
|
686
609
|
if (!reachable.size)
|
|
687
610
|
return;
|