@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.
Files changed (78) hide show
  1. package/README.md +8 -8
  2. package/dist/cellsSlice.d.ts.map +1 -1
  3. package/dist/cellsSlice.js +121 -198
  4. package/dist/cellsSlice.js.map +1 -1
  5. package/dist/components/CellSourceSelector.d.ts +1 -0
  6. package/dist/components/CellSourceSelector.d.ts.map +1 -1
  7. package/dist/components/CellSourceSelector.js +8 -9
  8. package/dist/components/CellSourceSelector.js.map +1 -1
  9. package/dist/components/SqlCellContent.js +3 -3
  10. package/dist/components/SqlCellContent.js.map +1 -1
  11. package/dist/components/SqlCellDependentsMenu.d.ts +3 -3
  12. package/dist/components/SqlCellDependentsMenu.d.ts.map +1 -1
  13. package/dist/components/SqlCellDependentsMenu.js +4 -4
  14. package/dist/components/SqlCellDependentsMenu.js.map +1 -1
  15. package/dist/components/VegaCellHeader.d.ts.map +1 -1
  16. package/dist/components/VegaCellHeader.js +2 -1
  17. package/dist/components/VegaCellHeader.js.map +1 -1
  18. package/dist/dagUtils.d.ts +9 -9
  19. package/dist/dagUtils.d.ts.map +1 -1
  20. package/dist/dagUtils.js +44 -42
  21. package/dist/dagUtils.js.map +1 -1
  22. package/dist/defaultCellRegistry.d.ts.map +1 -1
  23. package/dist/defaultCellRegistry.js +16 -12
  24. package/dist/defaultCellRegistry.js.map +1 -1
  25. package/dist/execution.d.ts.map +1 -1
  26. package/dist/execution.js +22 -16
  27. package/dist/execution.js.map +1 -1
  28. package/dist/helpers.d.ts +9 -17
  29. package/dist/helpers.d.ts.map +1 -1
  30. package/dist/helpers.js +24 -43
  31. package/dist/helpers.js.map +1 -1
  32. package/dist/index.d.ts +1 -2
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +1 -2
  35. package/dist/index.js.map +1 -1
  36. package/dist/sqlHelpers.d.ts +5 -5
  37. package/dist/sqlHelpers.d.ts.map +1 -1
  38. package/dist/sqlHelpers.js +7 -7
  39. package/dist/sqlHelpers.js.map +1 -1
  40. package/dist/types.d.ts +19 -47
  41. package/dist/types.d.ts.map +1 -1
  42. package/dist/types.js +6 -12
  43. package/dist/types.js.map +1 -1
  44. package/dist/useValidateResultName.js +5 -5
  45. package/dist/useValidateResultName.js.map +1 -1
  46. package/dist/utils.d.ts +2 -2
  47. package/dist/utils.d.ts.map +1 -1
  48. package/dist/utils.js +4 -4
  49. package/dist/utils.js.map +1 -1
  50. package/package.json +10 -10
  51. package/dist/components/DeleteSheetModal.d.ts +0 -9
  52. package/dist/components/DeleteSheetModal.d.ts.map +0 -1
  53. package/dist/components/DeleteSheetModal.js +0 -6
  54. package/dist/components/DeleteSheetModal.js.map +0 -1
  55. package/dist/components/SheetsTabBar.d.ts +0 -6
  56. package/dist/components/SheetsTabBar.d.ts.map +0 -1
  57. package/dist/components/SheetsTabBar.js +0 -58
  58. package/dist/components/SheetsTabBar.js.map +0 -1
  59. package/dist/components/SqlCellBody.d.ts +0 -6
  60. package/dist/components/SqlCellBody.d.ts.map +0 -1
  61. package/dist/components/SqlCellBody.js +0 -12
  62. package/dist/components/SqlCellBody.js.map +0 -1
  63. package/dist/crdt.d.ts +0 -61
  64. package/dist/crdt.d.ts.map +0 -1
  65. package/dist/crdt.js +0 -82
  66. package/dist/crdt.js.map +0 -1
  67. package/dist/dagSlice.d.ts +0 -3
  68. package/dist/dagSlice.d.ts.map +0 -1
  69. package/dist/dagSlice.js +0 -51
  70. package/dist/dagSlice.js.map +0 -1
  71. package/dist/typeGuards.d.ts +0 -2
  72. package/dist/typeGuards.d.ts.map +0 -1
  73. package/dist/typeGuards.js +0 -4
  74. package/dist/typeGuards.js.map +0 -1
  75. package/dist/validation.d.ts +0 -16
  76. package/dist/validation.d.ts.map +0 -1
  77. package/dist/validation.js +0 -40
  78. 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
- - sheet grouping and ordering (`cells.config.sheets`, `sheetOrder`, `currentSheetId`)
9
- - in-sheet dependency edges and cascade execution
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 **sheet-local** by default.
15
- - Cross-sheet references are only supported via explicit fully qualified SQL names.
16
- - SQL execution resolves to a stable sheet schema namespace; it does not rely on global `USE schema`.
17
- - Unqualified result-name references are resolved to the current sheet namespace during execution.
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: `findSheetIdForCell`, `getSheetsByType`
25
+ - helpers: `findArtifactIdForCell`, `resolveArtifactSchemaName`
26
26
  - SQL helpers: `renderSqlWithInputs`, `findSqlDependencies`, `findSqlDependenciesFromAst`
27
- - UI: `SqlCellContent`, `SqlCellRunButton`, `TextCellContent`, `InputCellContent`, `VegaCellContent`, `SheetsTabBar`
27
+ - UI: `SqlCellContent`, `SqlCellRunButton`, `TextCellContent`, `InputCellContent`, `VegaCellContent`, `CellSourceSelector`
28
28
  - types/schemas: exports from `types.ts`
29
29
 
30
30
  ## Internal APIs
@@ -1 +1 @@
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"}
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"}
@@ -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, ensureGraphCache, removeCellFromCache, replaceCellDependenciesInCache, topologicalOrder, } from './dagUtils';
8
- import { findSheetIdForCell, getRequiredSqlSelectToJson, normalizeCellsConfigStructure, resolveDependencies, } from './helpers';
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 { getEffectiveResultName, getSheetSchemaName, isDefined } from './utils';
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
- sheets: {
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 { sheets, sheetOrder, currentSheetId } = normalizeCellsConfigStructure(config, defaultConfig);
21
+ const { artifacts } = normalizeCellsConfigStructure(config);
41
22
  return {
42
23
  ...defaultConfig,
43
24
  ...config,
44
- sheets,
45
- sheetOrder,
46
- currentSheetId,
25
+ artifacts,
47
26
  };
48
27
  }
49
28
  // --- Slice Implementation ---
50
29
  export function createCellsSlice(props) {
51
- const { cellRegistry, supportedSheetTypes = ['notebook', 'canvas'] } = props;
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 sheet of Object.values(state.cells.config.sheets)) {
93
- const cache = sheet.graphCache;
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
- addCell: async (sheetId, cell, index) => {
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 targetSheet = get().cells.config.sheets[sheetId];
152
- const scopedCells = Object.fromEntries((targetSheet?.cellIds ?? [])
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, sheetId, cellRegistry, sqlSelectToJson);
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 sheet only.
172
- for (const [existingSheetId, existingSheet] of Object.entries(draft.cells.config.sheets)) {
173
- if (existingSheetId === sheetId)
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
- existingSheet.cellIds = existingSheet.cellIds.filter((cid) => cid !== cell.id);
176
- existingSheet.edges = existingSheet.edges.filter((e) => e.source !== cell.id && e.target !== cell.id);
177
- removeCellFromCache(existingSheet, cell.id);
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 sheet = draft.cells.config.sheets[sheetId];
180
- if (!sheet) {
181
- sheet = {
182
- id: sheetId,
183
- type: 'notebook',
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.sheets[sheetId] = sheet;
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 : sheet.cellIds.length;
204
- sheet.cellIds = sheet.cellIds.filter((cid) => cid !== cell.id);
205
- sheet.cellIds.splice(newIndex, 0, cell.id);
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
- sheet.edges = sheet.edges.filter((edge) => edge.target !== cell.id &&
205
+ artifact.edges = artifact.edges.filter((edge) => edge.target !== cell.id &&
208
206
  edge.id !== `${edge.source}-${cell.id}`);
209
- const localCellIds = new Set(sheet.cellIds);
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
- sheet.edges.push({
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(sheet, cell.id, deps.cellIds.filter((depId) => localCellIds.has(depId)), deps.tableNames);
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 sheets
237
- for (const sheet of Object.values(draft.cells.config.sheets)) {
238
- sheet.cellIds = sheet.cellIds.filter((cid) => cid !== id);
239
- sheet.edges = sheet.edges.filter((e) => e.source !== id && e.target !== id);
240
- removeCellFromCache(sheet, id);
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 ownerSheetId = findSheetIdForCell(get(), id);
263
- const scopedCells = Object.fromEntries(((ownerSheetId &&
264
- get().cells.config.sheets[ownerSheetId]?.cellIds) ||
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, ownerSheetId || '', cellRegistry, sqlSelectToJson);
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.sheets).find((sheet) => sheet.cellIds.includes(id));
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 (ownerSheetId) {
343
- void get().cells.runDownstreamCascade(ownerSheetId, id);
340
+ if (ownerArtifactId) {
341
+ void get().cells.runDownstreamCascade(ownerArtifactId, id);
344
342
  }
345
343
  }
346
344
  },
347
- addSheet: (title, type = 'notebook') => {
348
- const id = createId();
349
- set((state) => produce(state, (draft) => {
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 = [...sheet.cellIds];
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.sheets)) {
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.sheets[sheetId];
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
- closeSheet: (sheetId) => {
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
- if (!draft.cells.config.sheetOrder.includes(sheetId)) {
431
- draft.cells.config.sheetOrder.push(sheetId);
432
- }
433
- draft.cells.config.currentSheetId = sheetId;
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.sheets[sheetId] = sheet;
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 (!sheet.cellIds.includes(edge.source) ||
483
- !sheet.cellIds.includes(edge.target)) {
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 (!sheet.edges.find((e) => e.id === id)) {
488
- sheet.edges.push({ ...edge, id });
489
- ensureGraphCache(sheet);
490
- const deps = sheet.graphCache?.dependencies[edge.target] || [];
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: (sheetId, edgeId) => {
418
+ removeEdge: (artifactId, edgeId) => {
499
419
  set((state) => produce(state, (draft) => {
500
- const sheet = draft.cells.config.sheets[sheetId];
501
- if (sheet) {
502
- sheet.edges = sheet.edges.filter((e) => e.id !== edgeId);
503
- delete sheet.graphCache;
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 (sheetId, cellId) => {
427
+ updateEdgesFromSql: async (artifactId, cellId) => {
508
428
  const cell = get().cells.config.data[cellId];
509
- const sheet = get().cells.config.sheets[sheetId];
510
- if (!cell || !sheet)
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(sheet.cellIds
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, sheetId, cellRegistry, sqlSelectToJson);
438
+ const deps = await resolveDependencies(cell, scopedCells, artifactId, cellRegistry, sqlSelectToJson);
519
439
  set((state) => produce(state, (draft) => {
520
- const draftSheet = draft.cells.config.sheets[sheetId];
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
- getRootCells: (sheetId) => {
618
- const { dependencies } = buildDependencyGraph(sheetId, get());
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: (sheetId, sourceCellId) => {
623
- const { dependencies, dependents } = buildDependencyGraph(sheetId, get());
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 (sheetId) => {
636
- const cached = buildDependencyGraph(sheetId, get());
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 sheet = draft.cells.config.sheets[sheetId];
641
- if (sheet) {
642
- sheet.graphCache = buildGraphCacheFromEdges(sheet);
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(sheetId, get(), sqlSelectToJson);
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 (sheetId, sourceCellId) => {
673
- const cached = buildDependencyGraph(sheetId, get());
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 sheet = draft.cells.config.sheets[sheetId];
678
- if (sheet) {
679
- sheet.graphCache = buildGraphCacheFromEdges(sheet);
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(sheetId, get(), sqlSelectToJson);
607
+ const { dependencies, dependents } = await buildDependencyGraphAsync(artifactId, get(), sqlSelectToJson);
685
608
  const reachable = collectReachable(sourceCellId, dependents);
686
609
  if (!reachable.size)
687
610
  return;