@bian-womp/spark-workbench 0.3.90 → 0.3.91

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 (181) hide show
  1. package/lib/cjs/index.cjs +19 -6
  2. package/lib/cjs/index.cjs.map +1 -1
  3. package/lib/cjs/src/misc/layout.d.ts.map +1 -1
  4. package/lib/cjs/src/misc/mapping.d.ts.map +1 -1
  5. package/lib/cjs/src/misc/value.d.ts +2 -1
  6. package/lib/cjs/src/misc/value.d.ts.map +1 -1
  7. package/lib/esm/index.js +19 -6
  8. package/lib/esm/index.js.map +1 -1
  9. package/lib/esm/src/misc/layout.d.ts.map +1 -1
  10. package/lib/esm/src/misc/mapping.d.ts.map +1 -1
  11. package/lib/esm/src/misc/value.d.ts +2 -1
  12. package/lib/esm/src/misc/value.d.ts.map +1 -1
  13. package/package.json +4 -4
  14. package/lib/src/adapters/cli/index.d.ts +0 -22
  15. package/lib/src/adapters/cli/index.d.ts.map +0 -1
  16. package/lib/src/adapters/cli/index.js +0 -50
  17. package/lib/src/adapters/cli/index.js.map +0 -1
  18. package/lib/src/core/AbstractWorkbench.d.ts +0 -40
  19. package/lib/src/core/AbstractWorkbench.d.ts.map +0 -1
  20. package/lib/src/core/AbstractWorkbench.js +0 -15
  21. package/lib/src/core/AbstractWorkbench.js.map +0 -1
  22. package/lib/src/core/InMemoryWorkbench.d.ts +0 -304
  23. package/lib/src/core/InMemoryWorkbench.d.ts.map +0 -1
  24. package/lib/src/core/InMemoryWorkbench.js +0 -1016
  25. package/lib/src/core/InMemoryWorkbench.js.map +0 -1
  26. package/lib/src/core/contracts.d.ts +0 -172
  27. package/lib/src/core/contracts.d.ts.map +0 -1
  28. package/lib/src/core/contracts.js +0 -2
  29. package/lib/src/core/contracts.js.map +0 -1
  30. package/lib/src/core/ui-extensions.d.ts +0 -85
  31. package/lib/src/core/ui-extensions.d.ts.map +0 -1
  32. package/lib/src/core/ui-extensions.js +0 -111
  33. package/lib/src/core/ui-extensions.js.map +0 -1
  34. package/lib/src/examples/cli.d.ts +0 -2
  35. package/lib/src/examples/cli.d.ts.map +0 -1
  36. package/lib/src/examples/cli.js +0 -244
  37. package/lib/src/examples/cli.js.map +0 -1
  38. package/lib/src/examples/reactflow/App.d.ts +0 -2
  39. package/lib/src/examples/reactflow/App.d.ts.map +0 -1
  40. package/lib/src/examples/reactflow/App.js +0 -20
  41. package/lib/src/examples/reactflow/App.js.map +0 -1
  42. package/lib/src/index.d.ts +0 -30
  43. package/lib/src/index.d.ts.map +0 -1
  44. package/lib/src/index.js +0 -30
  45. package/lib/src/index.js.map +0 -1
  46. package/lib/src/misc/DebugEvents.d.ts +0 -7
  47. package/lib/src/misc/DebugEvents.d.ts.map +0 -1
  48. package/lib/src/misc/DebugEvents.js +0 -51
  49. package/lib/src/misc/DebugEvents.js.map +0 -1
  50. package/lib/src/misc/DefaultEdge.d.ts +0 -5
  51. package/lib/src/misc/DefaultEdge.d.ts.map +0 -1
  52. package/lib/src/misc/DefaultEdge.js +0 -15
  53. package/lib/src/misc/DefaultEdge.js.map +0 -1
  54. package/lib/src/misc/DefaultNode.d.ts +0 -5
  55. package/lib/src/misc/DefaultNode.d.ts.map +0 -1
  56. package/lib/src/misc/DefaultNode.js +0 -25
  57. package/lib/src/misc/DefaultNode.js.map +0 -1
  58. package/lib/src/misc/DefaultNodeContent.d.ts +0 -4
  59. package/lib/src/misc/DefaultNodeContent.d.ts.map +0 -1
  60. package/lib/src/misc/DefaultNodeContent.js +0 -58
  61. package/lib/src/misc/DefaultNodeContent.js.map +0 -1
  62. package/lib/src/misc/DefaultNodeHeader.d.ts +0 -13
  63. package/lib/src/misc/DefaultNodeHeader.d.ts.map +0 -1
  64. package/lib/src/misc/DefaultNodeHeader.js +0 -78
  65. package/lib/src/misc/DefaultNodeHeader.js.map +0 -1
  66. package/lib/src/misc/Inspector.d.ts +0 -12
  67. package/lib/src/misc/Inspector.d.ts.map +0 -1
  68. package/lib/src/misc/Inspector.js +0 -253
  69. package/lib/src/misc/Inspector.js.map +0 -1
  70. package/lib/src/misc/IssueBadge.d.ts +0 -7
  71. package/lib/src/misc/IssueBadge.d.ts.map +0 -1
  72. package/lib/src/misc/IssueBadge.js +0 -7
  73. package/lib/src/misc/IssueBadge.js.map +0 -1
  74. package/lib/src/misc/KeyboardShortcutToast.d.ts +0 -16
  75. package/lib/src/misc/KeyboardShortcutToast.d.ts.map +0 -1
  76. package/lib/src/misc/KeyboardShortcutToast.js +0 -40
  77. package/lib/src/misc/KeyboardShortcutToast.js.map +0 -1
  78. package/lib/src/misc/NodeHandles.d.ts +0 -18
  79. package/lib/src/misc/NodeHandles.d.ts.map +0 -1
  80. package/lib/src/misc/NodeHandles.js +0 -67
  81. package/lib/src/misc/NodeHandles.js.map +0 -1
  82. package/lib/src/misc/SelectionActiveSync.d.ts +0 -10
  83. package/lib/src/misc/SelectionActiveSync.d.ts.map +0 -1
  84. package/lib/src/misc/SelectionActiveSync.js +0 -21
  85. package/lib/src/misc/SelectionActiveSync.js.map +0 -1
  86. package/lib/src/misc/WorkbenchCanvas.d.ts +0 -23
  87. package/lib/src/misc/WorkbenchCanvas.d.ts.map +0 -1
  88. package/lib/src/misc/WorkbenchCanvas.js +0 -669
  89. package/lib/src/misc/WorkbenchCanvas.js.map +0 -1
  90. package/lib/src/misc/WorkbenchStudio.d.ts +0 -43
  91. package/lib/src/misc/WorkbenchStudio.d.ts.map +0 -1
  92. package/lib/src/misc/WorkbenchStudio.js +0 -463
  93. package/lib/src/misc/WorkbenchStudio.js.map +0 -1
  94. package/lib/src/misc/constants.d.ts +0 -4
  95. package/lib/src/misc/constants.d.ts.map +0 -1
  96. package/lib/src/misc/constants.js +0 -5
  97. package/lib/src/misc/constants.js.map +0 -1
  98. package/lib/src/misc/context/WorkbenchContext.d.ts +0 -133
  99. package/lib/src/misc/context/WorkbenchContext.d.ts.map +0 -1
  100. package/lib/src/misc/context/WorkbenchContext.js +0 -9
  101. package/lib/src/misc/context/WorkbenchContext.js.map +0 -1
  102. package/lib/src/misc/context/WorkbenchContext.provider.d.ts +0 -12
  103. package/lib/src/misc/context/WorkbenchContext.provider.d.ts.map +0 -1
  104. package/lib/src/misc/context/WorkbenchContext.provider.js +0 -995
  105. package/lib/src/misc/context/WorkbenchContext.provider.js.map +0 -1
  106. package/lib/src/misc/context-menu/ContextMenuButton.d.ts +0 -8
  107. package/lib/src/misc/context-menu/ContextMenuButton.d.ts.map +0 -1
  108. package/lib/src/misc/context-menu/ContextMenuButton.js +0 -10
  109. package/lib/src/misc/context-menu/ContextMenuButton.js.map +0 -1
  110. package/lib/src/misc/context-menu/ContextMenuHandlers.d.ts +0 -85
  111. package/lib/src/misc/context-menu/ContextMenuHandlers.d.ts.map +0 -1
  112. package/lib/src/misc/context-menu/ContextMenuHandlers.js +0 -2
  113. package/lib/src/misc/context-menu/ContextMenuHandlers.js.map +0 -1
  114. package/lib/src/misc/context-menu/ContextMenuHelpers.d.ts +0 -45
  115. package/lib/src/misc/context-menu/ContextMenuHelpers.d.ts.map +0 -1
  116. package/lib/src/misc/context-menu/ContextMenuHelpers.js +0 -182
  117. package/lib/src/misc/context-menu/ContextMenuHelpers.js.map +0 -1
  118. package/lib/src/misc/context-menu/DefaultContextMenu.d.ts +0 -3
  119. package/lib/src/misc/context-menu/DefaultContextMenu.d.ts.map +0 -1
  120. package/lib/src/misc/context-menu/DefaultContextMenu.js +0 -111
  121. package/lib/src/misc/context-menu/DefaultContextMenu.js.map +0 -1
  122. package/lib/src/misc/context-menu/NodeContextMenu.d.ts +0 -3
  123. package/lib/src/misc/context-menu/NodeContextMenu.d.ts.map +0 -1
  124. package/lib/src/misc/context-menu/NodeContextMenu.js +0 -51
  125. package/lib/src/misc/context-menu/NodeContextMenu.js.map +0 -1
  126. package/lib/src/misc/context-menu/SelectionContextMenu.d.ts +0 -3
  127. package/lib/src/misc/context-menu/SelectionContextMenu.d.ts.map +0 -1
  128. package/lib/src/misc/context-menu/SelectionContextMenu.js +0 -47
  129. package/lib/src/misc/context-menu/SelectionContextMenu.js.map +0 -1
  130. package/lib/src/misc/hooks.d.ts +0 -18
  131. package/lib/src/misc/hooks.d.ts.map +0 -1
  132. package/lib/src/misc/hooks.js +0 -275
  133. package/lib/src/misc/hooks.js.map +0 -1
  134. package/lib/src/misc/layout.d.ts +0 -117
  135. package/lib/src/misc/layout.d.ts.map +0 -1
  136. package/lib/src/misc/layout.js +0 -205
  137. package/lib/src/misc/layout.js.map +0 -1
  138. package/lib/src/misc/load.d.ts +0 -5
  139. package/lib/src/misc/load.d.ts.map +0 -1
  140. package/lib/src/misc/load.js +0 -106
  141. package/lib/src/misc/load.js.map +0 -1
  142. package/lib/src/misc/mapping.d.ts +0 -128
  143. package/lib/src/misc/mapping.d.ts.map +0 -1
  144. package/lib/src/misc/mapping.js +0 -270
  145. package/lib/src/misc/mapping.js.map +0 -1
  146. package/lib/src/misc/merge-utils.d.ts +0 -12
  147. package/lib/src/misc/merge-utils.d.ts.map +0 -1
  148. package/lib/src/misc/merge-utils.js +0 -51
  149. package/lib/src/misc/merge-utils.js.map +0 -1
  150. package/lib/src/misc/thumbnail-utils.d.ts +0 -53
  151. package/lib/src/misc/thumbnail-utils.d.ts.map +0 -1
  152. package/lib/src/misc/thumbnail-utils.js +0 -629
  153. package/lib/src/misc/thumbnail-utils.js.map +0 -1
  154. package/lib/src/misc/types.d.ts +0 -18
  155. package/lib/src/misc/types.d.ts.map +0 -1
  156. package/lib/src/misc/types.js +0 -2
  157. package/lib/src/misc/types.js.map +0 -1
  158. package/lib/src/misc/value.d.ts +0 -16
  159. package/lib/src/misc/value.d.ts.map +0 -1
  160. package/lib/src/misc/value.js +0 -114
  161. package/lib/src/misc/value.js.map +0 -1
  162. package/lib/src/misc/viewport-utils.d.ts +0 -6
  163. package/lib/src/misc/viewport-utils.d.ts.map +0 -1
  164. package/lib/src/misc/viewport-utils.js +0 -18
  165. package/lib/src/misc/viewport-utils.js.map +0 -1
  166. package/lib/src/runtime/AbstractGraphRunner.d.ts +0 -61
  167. package/lib/src/runtime/AbstractGraphRunner.d.ts.map +0 -1
  168. package/lib/src/runtime/AbstractGraphRunner.js +0 -63
  169. package/lib/src/runtime/AbstractGraphRunner.js.map +0 -1
  170. package/lib/src/runtime/IGraphRunner.d.ts +0 -100
  171. package/lib/src/runtime/IGraphRunner.d.ts.map +0 -1
  172. package/lib/src/runtime/IGraphRunner.js +0 -2
  173. package/lib/src/runtime/IGraphRunner.js.map +0 -1
  174. package/lib/src/runtime/LocalGraphRunner.d.ts +0 -60
  175. package/lib/src/runtime/LocalGraphRunner.d.ts.map +0 -1
  176. package/lib/src/runtime/LocalGraphRunner.js +0 -294
  177. package/lib/src/runtime/LocalGraphRunner.js.map +0 -1
  178. package/lib/src/runtime/RemoteGraphRunner.d.ts +0 -109
  179. package/lib/src/runtime/RemoteGraphRunner.d.ts.map +0 -1
  180. package/lib/src/runtime/RemoteGraphRunner.js +0 -696
  181. package/lib/src/runtime/RemoteGraphRunner.js.map +0 -1
@@ -1,1016 +0,0 @@
1
- import { GraphBuilder, getInputDeclaredTypes, isTyped, unwrapValue, createSimpleGraphRegistry, } from "@bian-womp/spark-graph";
2
- import lod from "lodash";
3
- import { AbstractWorkbench } from "./AbstractWorkbench";
4
- export class InMemoryWorkbench extends AbstractWorkbench {
5
- constructor(args) {
6
- super(args);
7
- this._def = { nodes: [], edges: [] };
8
- this.listeners = new Map();
9
- this._batchChanges = null;
10
- this._batchDepth = 0;
11
- this.positions = {};
12
- this.sizes = {};
13
- this.selection = {
14
- nodes: [],
15
- edges: [],
16
- };
17
- this.nodeNames = {};
18
- this.customData = {};
19
- this.runtimeState = null;
20
- this.viewport = null;
21
- this.historyState = undefined;
22
- this.copiedData = null;
23
- this._registry = createSimpleGraphRegistry();
24
- }
25
- get def() {
26
- return this._def;
27
- }
28
- get registry() {
29
- return this._registry;
30
- }
31
- setRegistry(registry) {
32
- this._registry = registry;
33
- this.refreshValidation();
34
- this.emit("registryChanged", { registry });
35
- }
36
- /**
37
- * Check if a node should auto run based on registry node description policy or node definition params policy.
38
- * @param nodeId - The node ID to check
39
- * @returns true if the node has autoRun enabled in either registry or params policy
40
- */
41
- shouldNodeAutoRun(nodeId) {
42
- const node = this._def.nodes.find((n) => n.nodeId === nodeId);
43
- if (!node)
44
- return false;
45
- // Check registry node description policy
46
- const desc = this._registry.nodes.get(node.typeId);
47
- const registryAutoRun = desc?.policy?.autoRun === true;
48
- // Check node definition params policy (takes precedence over registry)
49
- const paramsAutoRun = node?.params?.policy?.autoRun === true;
50
- // Node has autoRun if either registry or params sets it
51
- return registryAutoRun || paramsAutoRun;
52
- }
53
- async load(def) {
54
- this._def = { nodes: [...def.nodes], edges: [...def.edges] };
55
- if (this.layout) {
56
- const { positions } = await this.layout.layout(this._def);
57
- this.positions = positions;
58
- }
59
- const defNodeIds = new Set(this._def.nodes.map((n) => n.nodeId));
60
- const defEdgeIds = new Set(this._def.edges.map((e) => e.id));
61
- const filteredPositions = Object.fromEntries(Object.entries(this.positions).filter(([id]) => defNodeIds.has(id)));
62
- const filteredSizes = Object.fromEntries(Object.entries(this.sizes).filter(([id]) => defNodeIds.has(id)));
63
- const filteredNodes = this.selection.nodes.filter((id) => defNodeIds.has(id));
64
- const filteredEdges = this.selection.edges.filter((id) => defEdgeIds.has(id));
65
- // Clean up extData for removed nodes/edges
66
- if (this.customData.nodes) {
67
- const filteredExtNodes = Object.fromEntries(Object.entries(this.customData.nodes).filter(([id]) => defNodeIds.has(id)));
68
- this.customData.nodes = Object.keys(filteredExtNodes).length > 0 ? filteredExtNodes : undefined;
69
- }
70
- if (this.customData.edges) {
71
- const filteredExtEdges = Object.fromEntries(Object.entries(this.customData.edges).filter(([id]) => defEdgeIds.has(id)));
72
- this.customData.edges = Object.keys(filteredExtEdges).length > 0 ? filteredExtEdges : undefined;
73
- }
74
- this.positions = filteredPositions;
75
- this.sizes = filteredSizes;
76
- this.selection = { nodes: filteredNodes, edges: filteredEdges };
77
- this.emit("graphChanged", { def: this._def, change: { type: "load" } });
78
- this.refreshValidation();
79
- }
80
- refreshValidation() {
81
- this.emit("validationChanged", this.validate());
82
- }
83
- validate() {
84
- if (this.registry) {
85
- const builder = new GraphBuilder(this.registry);
86
- const report = builder.validate(this._def);
87
- return report;
88
- }
89
- const issues = [];
90
- const nodeIds = new Set();
91
- for (const n of this._def.nodes) {
92
- if (nodeIds.has(n.nodeId)) {
93
- issues.push({
94
- level: "error",
95
- code: "NODE_ID_DUP",
96
- message: `Duplicate nodeId ${n.nodeId}`,
97
- });
98
- }
99
- else
100
- nodeIds.add(n.nodeId);
101
- }
102
- const edgeIds = new Set();
103
- for (const e of this._def.edges) {
104
- if (edgeIds.has(e.id)) {
105
- issues.push({
106
- level: "error",
107
- code: "EDGE_ID_DUP",
108
- message: `Duplicate edge id ${e.id}`,
109
- });
110
- }
111
- else
112
- edgeIds.add(e.id);
113
- }
114
- return { ok: issues.every((i) => i.level !== "error"), issues };
115
- }
116
- setInputs(nodeId, inputs, options) {
117
- this.emit("graphChanged", {
118
- def: this._def,
119
- change: { type: "setInputs", nodeId, inputs },
120
- ...options,
121
- });
122
- this.refreshValidation();
123
- }
124
- addNode(node, options) {
125
- const id = node.nodeId ?? this.genId("n", new Set(this._def.nodes.map((n) => n.nodeId)));
126
- this._def.nodes.push({
127
- nodeId: id,
128
- typeId: node.typeId,
129
- params: node.params,
130
- resolvedHandles: node.resolvedHandles,
131
- });
132
- if (options?.position)
133
- this.positions[id] = options.position;
134
- if (options?.size)
135
- this.sizes[id] = options.size;
136
- this.emit("graphChanged", {
137
- def: this._def,
138
- change: {
139
- type: "addNode",
140
- nodeId: id,
141
- inputs: options?.inputs,
142
- copyOutputsFrom: options?.copyOutputsFrom,
143
- },
144
- ...lod.pick(options, ["dry", "commit", "reason"]),
145
- });
146
- this.refreshValidation();
147
- return id;
148
- }
149
- removeNode(nodeId, options) {
150
- const removedEdges = new Set();
151
- this._def.nodes = this._def.nodes.filter((n) => n.nodeId !== nodeId);
152
- this._def.edges = this._def.edges.filter((e) => {
153
- if (e.source.nodeId === nodeId || e.target.nodeId === nodeId) {
154
- removedEdges.add(e.id);
155
- return false;
156
- }
157
- return true;
158
- });
159
- delete this.positions[nodeId];
160
- delete this.sizes[nodeId];
161
- delete this.nodeNames[nodeId];
162
- this.setSelectionInternal({
163
- nodes: this.selection.nodes.filter((id) => id !== nodeId),
164
- edges: this.selection.edges.filter((id) => !removedEdges.has(id)),
165
- }, options);
166
- this.emit("graphChanged", {
167
- def: this._def,
168
- change: { type: "removeNode", nodeId },
169
- ...options,
170
- });
171
- this.refreshValidation();
172
- }
173
- connect(edge, options) {
174
- const id = edge.id ?? this.genId("e", new Set(this._def.edges.map((e) => e.id)));
175
- this._def.edges.push({
176
- id,
177
- source: { ...edge.source },
178
- target: { ...edge.target },
179
- typeId: edge.typeId,
180
- });
181
- this.emit("graphChanged", {
182
- def: this._def,
183
- change: { type: "connect", edgeId: id },
184
- ...options,
185
- });
186
- this.refreshValidation();
187
- return id;
188
- }
189
- disconnect(edgeId, options) {
190
- this._def.edges = this._def.edges.filter((e) => e.id !== edgeId);
191
- this.emit("graphChanged", {
192
- def: this._def,
193
- change: { type: "disconnect", edgeId },
194
- ...options,
195
- });
196
- this.emit("validationChanged", this.validate());
197
- }
198
- updateEdgeType(edgeId, typeId) {
199
- const e = this._def.edges.find((x) => x.id === edgeId);
200
- if (!e)
201
- return;
202
- if (!typeId)
203
- delete e.typeId;
204
- else
205
- e.typeId = typeId;
206
- this.emit("graphChanged", {
207
- def: this._def,
208
- change: { type: "updateEdgeType", edgeId, typeId },
209
- });
210
- this.refreshValidation();
211
- }
212
- updateParams(nodeId, params) {
213
- const n = this._def.nodes.find((n) => n.nodeId === nodeId);
214
- if (!n)
215
- return;
216
- n.params = { ...(n.params ?? {}), ...params };
217
- this.emit("graphChanged", {
218
- def: this._def,
219
- change: { type: "updateParams", nodeId },
220
- });
221
- }
222
- updateResolvedHandles(nodeId, resolvedHandles, options) {
223
- const n = this._def.nodes.find((n) => n.nodeId === nodeId);
224
- if (!n)
225
- return;
226
- n.resolvedHandles = { ...(n.resolvedHandles ?? {}), ...resolvedHandles };
227
- this.emit("graphChanged", {
228
- def: this._def,
229
- change: { type: "updateResolvedHandles", nodeId },
230
- ...options,
231
- });
232
- this.refreshValidation();
233
- }
234
- // Position and selection APIs for React Flow bridge
235
- setPositions(positions, options) {
236
- this.positions = { ...this.positions, ...positions };
237
- this.emit("graphUiChanged", { change: { type: "moveNodes" }, ...options });
238
- }
239
- getPositions() {
240
- return { ...this.positions };
241
- }
242
- setSizes(sizes, options) {
243
- const updatedSizes = { ...this.sizes };
244
- for (const [nodeId, size] of Object.entries(sizes)) {
245
- if (size) {
246
- updatedSizes[nodeId] = size;
247
- }
248
- else {
249
- delete updatedSizes[nodeId];
250
- }
251
- }
252
- this.sizes = updatedSizes;
253
- this.emit("graphUiChanged", {
254
- change: { type: "resizeNodes" },
255
- ...options,
256
- });
257
- }
258
- getSizes() {
259
- return { ...this.sizes };
260
- }
261
- setSelectionInternal(sel, options) {
262
- if (lod.isEqual(this.selection, sel))
263
- return;
264
- this.selection = { nodes: [...sel.nodes], edges: [...sel.edges] };
265
- this.emit("selectionChanged", this.selection);
266
- this.emit("graphUiChanged", { change: { type: "selection" }, ...options });
267
- }
268
- setSelection(sel, options) {
269
- this.setSelectionInternal(sel, options);
270
- }
271
- getSelection() {
272
- return {
273
- nodes: [...this.selection.nodes],
274
- edges: [...this.selection.edges],
275
- };
276
- }
277
- /**
278
- * Delete all selected nodes and edges.
279
- */
280
- deleteSelection(options) {
281
- const selection = this.getSelection();
282
- this.beginBatch();
283
- let completed = false;
284
- try {
285
- // Delete all selected nodes (this will also remove connected edges)
286
- for (const nodeId of selection.nodes) {
287
- this.removeNode(nodeId);
288
- }
289
- // Delete remaining selected edges (edges not connected to deleted nodes)
290
- for (const edgeId of selection.edges) {
291
- this.disconnect(edgeId);
292
- }
293
- // Clear selection
294
- this.setSelectionInternal({ nodes: [], edges: [] }, options);
295
- completed = true;
296
- }
297
- finally {
298
- this.endBatch(completed ? options : { ...options, commit: false });
299
- }
300
- }
301
- setViewport(viewport) {
302
- if (lod.isEqual(this.viewport, viewport))
303
- return;
304
- const init = this.viewport === null;
305
- this.viewport = { ...viewport };
306
- this.emit("graphUiChanged", { change: { type: "viewport" }, init });
307
- }
308
- getViewport() {
309
- return this.viewport ? { ...this.viewport } : null;
310
- }
311
- getUIState() {
312
- const defNodeIds = new Set(this._def.nodes.map((n) => n.nodeId));
313
- const defEdgeIds = new Set(this._def.edges.map((e) => e.id));
314
- const filteredPositions = Object.fromEntries(Object.entries(this.positions).filter(([id]) => defNodeIds.has(id)));
315
- const filteredNodes = this.selection.nodes.filter((id) => defNodeIds.has(id));
316
- const filteredEdges = this.selection.edges.filter((id) => defEdgeIds.has(id));
317
- const filteredNodeNames = Object.fromEntries(Object.entries(this.nodeNames).filter(([id]) => defNodeIds.has(id)));
318
- const filteredSizes = Object.fromEntries(Object.entries(this.sizes).filter(([id]) => defNodeIds.has(id)));
319
- return {
320
- positions: Object.keys(filteredPositions).length > 0 ? filteredPositions : undefined,
321
- selection: filteredNodes.length > 0 || filteredEdges.length > 0
322
- ? {
323
- nodes: filteredNodes,
324
- edges: filteredEdges,
325
- }
326
- : undefined,
327
- viewport: this.viewport ? { ...this.viewport } : undefined,
328
- nodeNames: Object.keys(filteredNodeNames).length > 0 ? filteredNodeNames : undefined,
329
- sizes: Object.keys(filteredSizes).length > 0 ? filteredSizes : undefined,
330
- };
331
- }
332
- setUIState(ui) {
333
- if (!ui)
334
- return;
335
- if (ui.positions) {
336
- this.positions = { ...ui.positions };
337
- }
338
- if (ui.selection) {
339
- this.selection = {
340
- nodes: [...ui.selection.nodes],
341
- edges: [...ui.selection.edges],
342
- };
343
- this.emit("selectionChanged", this.selection);
344
- this.emit("graphUiChanged", {
345
- change: { type: "selection" },
346
- init: true,
347
- });
348
- }
349
- if (ui.viewport) {
350
- this.viewport = { ...ui.viewport };
351
- this.emit("graphUiChanged", { change: { type: "viewport" }, init: true });
352
- }
353
- if (ui.nodeNames !== undefined) {
354
- this.nodeNames = { ...ui.nodeNames };
355
- for (const [nodeId, name] of Object.entries(ui.nodeNames)) {
356
- this.emit("graphUiChanged", {
357
- change: { type: "nodeName", nodeId, name },
358
- init: true,
359
- });
360
- }
361
- }
362
- if (ui.sizes !== undefined) {
363
- const defNodeIds = new Set(this._def.nodes.map((n) => n.nodeId));
364
- this.sizes = Object.fromEntries(Object.entries(ui.sizes).filter(([id]) => defNodeIds.has(id)));
365
- }
366
- }
367
- getRuntimeState() {
368
- return this.runtimeState ? { ...this.runtimeState } : null;
369
- }
370
- setRuntimeState(runtime) {
371
- this.runtimeState = runtime ? { ...runtime } : null;
372
- this.emit("runtimeMetadataChanged", {});
373
- }
374
- getHistory() {
375
- return this.historyState;
376
- }
377
- setHistory(history) {
378
- this.historyState = history;
379
- this.emit("historyChanged", { history });
380
- }
381
- getNodeRuntimeMetadata(nodeId) {
382
- return this.runtimeState?.nodes[nodeId];
383
- }
384
- updateNodeRuntimeMetadata(nodeId, updater) {
385
- const current = this.runtimeState ?? { nodes: {} };
386
- const nodeMeta = current.nodes[nodeId] ?? {};
387
- const updated = updater({ ...nodeMeta });
388
- this.runtimeState = { nodes: { ...current.nodes, [nodeId]: updated } };
389
- this.emit("runtimeMetadataChanged", { nodeId });
390
- }
391
- on(event, handler) {
392
- if (!this.listeners.has(event))
393
- this.listeners.set(event, new Set());
394
- const set = this.listeners.get(event);
395
- set.add(handler);
396
- return () => set.delete(handler);
397
- }
398
- emit(event, payload) {
399
- if (this._batchDepth > 0 && this._batchChanges !== null) {
400
- if (event === "graphChanged") {
401
- const d = payload;
402
- if (d.change && d.change.type !== "load" && d.change.type !== "batch") {
403
- this._batchChanges.push(d.change);
404
- }
405
- return;
406
- }
407
- if (event === "graphUiChanged") {
408
- const d = payload;
409
- if (d.commit) {
410
- const stripped = { ...d, commit: undefined };
411
- const set = this.listeners.get(event);
412
- if (set)
413
- for (const h of Array.from(set))
414
- h(stripped);
415
- return;
416
- }
417
- }
418
- if (event === "validationChanged") {
419
- return;
420
- }
421
- }
422
- const set = this.listeners.get(event);
423
- if (set)
424
- for (const h of Array.from(set))
425
- h(payload);
426
- }
427
- beginBatch() {
428
- if (this._batchDepth === 0) {
429
- this._batchChanges = [];
430
- }
431
- this._batchDepth++;
432
- }
433
- endBatch(options) {
434
- if (this._batchDepth <= 0)
435
- return;
436
- this._batchDepth--;
437
- if (this._batchDepth > 0)
438
- return;
439
- const changes = this._batchChanges;
440
- this._batchChanges = null;
441
- if (!changes || changes.length === 0)
442
- return;
443
- const set = this.listeners.get("graphChanged");
444
- if (set) {
445
- const payload = {
446
- def: this._def,
447
- change: { type: "batch", changes },
448
- ...options,
449
- };
450
- for (const h of Array.from(set))
451
- h(payload);
452
- }
453
- this.refreshValidation();
454
- }
455
- getInboundHandleIds(nodeId, shouldExcludeEdge) {
456
- const inboundEdges = this.def.edges.filter((e) => e.target.nodeId === nodeId);
457
- return new Set(inboundEdges.filter((e) => shouldExcludeEdge(e)).map((e) => e.target.handle));
458
- }
459
- excludeHandlesFromInputs(allNodeInputs, excludedHandles) {
460
- return Object.fromEntries(Object.entries(allNodeInputs).filter(([handle]) => !excludedHandles.has(handle)));
461
- }
462
- /**
463
- * Copy selected nodes and their internal edges.
464
- * Returns data in a format suitable for pasting.
465
- * Positions are normalized relative to the selection bounds center.
466
- * Uses the same logic as duplicate: copies inputs without bindings and supports copyOutputsFrom.
467
- */
468
- copySelection(runner, getNodeSize) {
469
- const selection = this.getSelection();
470
- if (selection.nodes.length === 0)
471
- return null;
472
- const positions = this.getPositions();
473
- const sizes = this.getSizes();
474
- const selectedNodeSet = new Set(selection.nodes);
475
- // Collect nodes to copy
476
- const nodesToCopy = this.def.nodes.filter((n) => selectedNodeSet.has(n.nodeId));
477
- if (nodesToCopy.length === 0)
478
- return null;
479
- // Calculate bounds
480
- let minX = Infinity;
481
- let minY = Infinity;
482
- let maxX = -Infinity;
483
- let maxY = -Infinity;
484
- nodesToCopy.forEach((node) => {
485
- const pos = positions[node.nodeId];
486
- const size = sizes[node.nodeId] || getNodeSize?.(node.nodeId, node.typeId);
487
- if (pos) {
488
- minX = Math.min(minX, pos.x);
489
- minY = Math.min(minY, pos.y);
490
- if (size) {
491
- maxX = Math.max(maxX, pos.x + size.width);
492
- maxY = Math.max(maxY, pos.y + size.height);
493
- }
494
- }
495
- });
496
- const bounds = { minX, minY, maxX, maxY };
497
- const centerX = (bounds.minX + bounds.maxX) / 2;
498
- const centerY = (bounds.minY + bounds.maxY) / 2;
499
- // Get inputs for each node
500
- // Include values from inbound edges if those edges are selected
501
- const allInputs = runner.getInputs(this.def);
502
- const selectedEdgeSet = new Set(selection.edges);
503
- const copiedNodes = nodesToCopy.map((node) => {
504
- const pos = positions[node.nodeId];
505
- const size = sizes[node.nodeId];
506
- const customNodeData = this.getCustomNodeData(node.nodeId);
507
- const inboundHandlesToExclude = this.getInboundHandleIds(node.nodeId, (e) => !selectedEdgeSet.has(e.id));
508
- const allNodeInputs = allInputs[node.nodeId] || {};
509
- // Include inputs that either:
510
- // 1. Don't have inbound edges (literal values)
511
- // 2. Have inbound edges that ARE selected (preserve the value from the edge)
512
- const inputsToCopy = this.excludeHandlesFromInputs(allNodeInputs, inboundHandlesToExclude);
513
- return {
514
- typeId: node.typeId,
515
- params: node.params,
516
- resolvedHandles: node.resolvedHandles,
517
- position: pos
518
- ? {
519
- x: pos.x - centerX,
520
- y: pos.y - centerY,
521
- }
522
- : undefined,
523
- size,
524
- inputs: inputsToCopy,
525
- customData: customNodeData !== undefined ? lod.cloneDeep(customNodeData) : undefined,
526
- originalNodeId: node.nodeId,
527
- };
528
- });
529
- // Collect edges between copied nodes
530
- const copiedEdges = this.def.edges
531
- .filter((edge) => {
532
- return selectedNodeSet.has(edge.source.nodeId) && selectedNodeSet.has(edge.target.nodeId);
533
- })
534
- .map((edge) => ({
535
- sourceNodeId: edge.source.nodeId,
536
- sourceHandle: edge.source.handle,
537
- targetNodeId: edge.target.nodeId,
538
- targetHandle: edge.target.handle,
539
- typeId: edge.typeId,
540
- }));
541
- return {
542
- nodes: copiedNodes,
543
- edges: copiedEdges,
544
- bounds,
545
- };
546
- }
547
- /**
548
- * Bake an output value from a node into a new node.
549
- * Creates a new node based on the output type's bakeTarget configuration.
550
- * Returns the ID of the last created node (or undefined if none created).
551
- */
552
- async bake(registry, runner, outputValue, outputTypeId, nodePosition, options) {
553
- if (!outputTypeId || outputValue === undefined)
554
- return undefined;
555
- const bakeOpts = options || { commit: true, reason: "bake" };
556
- this.beginBatch();
557
- let completed = false;
558
- try {
559
- const unwrap = (v) => (isTyped(v) ? unwrapValue(v) : v);
560
- const coerceIfNeeded = async (fromType, toTypes, value) => {
561
- if (!toTypes)
562
- return value;
563
- const typesArray = Array.isArray(toTypes) ? toTypes : [toTypes];
564
- if (typesArray.includes(fromType))
565
- return value;
566
- if (!runner?.coerce)
567
- return value;
568
- for (const toType of typesArray) {
569
- try {
570
- return await runner.coerce(fromType, toType, value);
571
- }
572
- catch {
573
- // Continue to next type
574
- }
575
- }
576
- return value;
577
- };
578
- const pos = nodePosition;
579
- const isArray = outputTypeId.endsWith("[]");
580
- const baseTypeId = isArray ? outputTypeId.slice(0, -2) : outputTypeId;
581
- const tArr = isArray ? registry.types.get(outputTypeId) : undefined;
582
- const tElem = registry.types.get(baseTypeId);
583
- const singleTarget = !isArray ? tElem?.bakeTarget : undefined;
584
- const arrTarget = isArray ? tArr?.bakeTarget : undefined;
585
- const elemTarget = isArray ? tElem?.bakeTarget : undefined;
586
- let newNodeId;
587
- if (singleTarget) {
588
- const nodeDesc = registry.nodes.get(singleTarget.nodeTypeId);
589
- const inTypes = getInputDeclaredTypes(nodeDesc?.inputs, singleTarget.inputHandle);
590
- const coerced = await coerceIfNeeded(outputTypeId, inTypes, unwrap(outputValue));
591
- newNodeId = this.addNode({
592
- typeId: singleTarget.nodeTypeId,
593
- }, {
594
- inputs: { [singleTarget.inputHandle]: coerced },
595
- position: { x: pos.x + 180, y: pos.y },
596
- });
597
- }
598
- else if (isArray && arrTarget) {
599
- const nodeDesc = registry.nodes.get(arrTarget.nodeTypeId);
600
- const inTypes = getInputDeclaredTypes(nodeDesc?.inputs, arrTarget.inputHandle);
601
- const coerced = await coerceIfNeeded(outputTypeId, inTypes, unwrap(outputValue));
602
- newNodeId = this.addNode({
603
- typeId: arrTarget.nodeTypeId,
604
- }, {
605
- position: { x: pos.x + 180, y: pos.y },
606
- inputs: { [arrTarget.inputHandle]: coerced },
607
- });
608
- }
609
- else if (isArray && elemTarget) {
610
- const nodeDesc = registry.nodes.get(elemTarget.nodeTypeId);
611
- const inTypes = getInputDeclaredTypes(nodeDesc?.inputs, elemTarget.inputHandle);
612
- const src = unwrap(outputValue);
613
- const items = Array.isArray(src) ? src : [src];
614
- const coercedItems = await Promise.all(items.map((v) => coerceIfNeeded(baseTypeId, inTypes, v)));
615
- const COLS = 4;
616
- const DX = 180;
617
- const DY = 160;
618
- for (let idx = 0; idx < coercedItems.length; idx++) {
619
- const col = idx % COLS;
620
- const row = Math.floor(idx / COLS);
621
- newNodeId = this.addNode({
622
- typeId: elemTarget.nodeTypeId,
623
- }, {
624
- position: { x: pos.x + (col + 1) * DX, y: pos.y + row * DY },
625
- inputs: { [elemTarget.inputHandle]: coercedItems[idx] },
626
- });
627
- }
628
- }
629
- if (newNodeId) {
630
- this.setSelectionInternal({ nodes: [newNodeId], edges: [] }, bakeOpts);
631
- }
632
- completed = true;
633
- return newNodeId;
634
- }
635
- catch {
636
- return undefined;
637
- }
638
- finally {
639
- this.endBatch(completed ? bakeOpts : { ...bakeOpts, commit: false });
640
- }
641
- }
642
- /**
643
- * Duplicate all selected nodes.
644
- * Returns the list of newly created node IDs.
645
- * Each duplicated node is offset by 24px in both x and y directions.
646
- * Copies inputs without bindings and uses copyOutputsFrom to copy outputs.
647
- */
648
- duplicateSelection(runner, options) {
649
- const selection = this.getSelection();
650
- if (selection.nodes.length === 0)
651
- return [];
652
- const reason = options?.reason || "duplicate-selection";
653
- const batchOptions = { commit: options?.commit ?? true, reason, dry: true };
654
- this.beginBatch();
655
- let completed = false;
656
- try {
657
- const positions = this.getPositions();
658
- const sizes = this.getSizes();
659
- const newNodes = [];
660
- const nodeIdMap = new Map();
661
- const processedNodes = new Set();
662
- const selectedNodeSet = new Set(selection.nodes);
663
- const allInputs = runner.getInputs(this.def) || {};
664
- const incomingEdgesByNode = new Map();
665
- for (const nodeId of selection.nodes) {
666
- const incomingEdges = this.def.edges.filter((e) => e.target.nodeId === nodeId);
667
- incomingEdgesByNode.set(nodeId, incomingEdges);
668
- }
669
- const isNodeReady = (nodeId) => {
670
- const incomingEdges = incomingEdgesByNode.get(nodeId) || [];
671
- for (const edge of incomingEdges) {
672
- if (selectedNodeSet.has(edge.source.nodeId)) {
673
- if (!processedNodes.has(edge.source.nodeId)) {
674
- return false;
675
- }
676
- }
677
- }
678
- return true;
679
- };
680
- let remainingNodes = new Set(selection.nodes);
681
- while (remainingNodes.size > 0) {
682
- const readyNodes = Array.from(remainingNodes).filter((nodeId) => isNodeReady(nodeId));
683
- if (readyNodes.length === 0) {
684
- readyNodes.push(...remainingNodes);
685
- }
686
- for (const nodeId of readyNodes) {
687
- const n = this.def.nodes.find((n) => n.nodeId === nodeId);
688
- if (!n) {
689
- remainingNodes.delete(nodeId);
690
- continue;
691
- }
692
- const pos = positions[nodeId];
693
- const size = sizes[nodeId];
694
- const inputs = allInputs[nodeId] || {};
695
- const newNodeId = this.addNode({
696
- typeId: n.typeId,
697
- params: n.params,
698
- resolvedHandles: n.resolvedHandles,
699
- }, {
700
- inputs,
701
- position: pos ? { x: pos.x + 24, y: pos.y + 24 } : undefined,
702
- size,
703
- copyOutputsFrom: nodeId,
704
- dry: true,
705
- reason,
706
- });
707
- newNodes.push(newNodeId);
708
- nodeIdMap.set(nodeId, newNodeId);
709
- processedNodes.add(nodeId);
710
- const incomingEdges = incomingEdgesByNode.get(nodeId) || [];
711
- for (const edge of incomingEdges) {
712
- const sourceNodeId = nodeIdMap.get(edge.source.nodeId) || edge.source.nodeId;
713
- this.connect({
714
- source: { nodeId: sourceNodeId, handle: edge.source.handle },
715
- target: { nodeId: newNodeId, handle: edge.target.handle },
716
- typeId: edge.typeId,
717
- }, { dry: true, reason });
718
- }
719
- remainingNodes.delete(nodeId);
720
- }
721
- }
722
- if (newNodes.length > 0) {
723
- this.setSelectionInternal({ nodes: newNodes, edges: [] }, options || { commit: true, reason });
724
- }
725
- completed = true;
726
- return newNodes;
727
- }
728
- finally {
729
- this.endBatch(completed ? batchOptions : { ...batchOptions, commit: false });
730
- }
731
- }
732
- /**
733
- * Duplicate a node and all its incoming edges.
734
- * Returns the ID of the newly created node.
735
- * The duplicated node is offset by 24px in both x and y directions.
736
- * All incoming edges are duplicated to point to the new node.
737
- */
738
- duplicateNode(nodeId, runner, options) {
739
- const n = this.def.nodes.find((n) => n.nodeId === nodeId);
740
- if (!n)
741
- return undefined;
742
- const reason = options?.reason || "duplicate-node";
743
- const batchOptions = { commit: options?.commit ?? true, reason, dry: true };
744
- this.beginBatch();
745
- let completed = false;
746
- try {
747
- const pos = this.getPositions()[nodeId];
748
- const size = this.getSizes()[nodeId];
749
- const inputs = runner.getInputs(this.def)[nodeId] || {};
750
- const newNodeId = this.addNode({
751
- typeId: n.typeId,
752
- params: n.params,
753
- resolvedHandles: n.resolvedHandles,
754
- }, {
755
- inputs,
756
- position: pos ? { x: pos.x + 24, y: pos.y + 24 } : undefined,
757
- size,
758
- copyOutputsFrom: nodeId,
759
- dry: true,
760
- reason,
761
- });
762
- const incomingEdges = this.def.edges.filter((e) => e.target.nodeId === nodeId);
763
- for (const edge of incomingEdges) {
764
- this.connect({
765
- source: edge.source,
766
- target: { nodeId: newNodeId, handle: edge.target.handle },
767
- typeId: edge.typeId,
768
- }, { dry: true, reason });
769
- }
770
- if (newNodeId) {
771
- this.setSelectionInternal({ nodes: [newNodeId], edges: [] }, options || { commit: true, reason });
772
- }
773
- completed = true;
774
- return newNodeId;
775
- }
776
- finally {
777
- this.endBatch(completed ? batchOptions : { ...batchOptions, commit: false });
778
- }
779
- }
780
- /**
781
- * Paste copied graph data at the specified center position.
782
- * Returns the mapping from original node IDs to new node IDs.
783
- * Uses copyOutputsFrom to copy outputs from original nodes (like duplicate does).
784
- */
785
- pasteCopiedData(data, center, options) {
786
- const nodeIdMap = new Map();
787
- const edgeIds = [];
788
- const reason = options?.reason || "paste";
789
- const batchOptions = { commit: options?.commit ?? true, reason, dry: true };
790
- this.beginBatch();
791
- let completed = false;
792
- try {
793
- for (const nodeData of data.nodes) {
794
- const newNodeId = this.addNode({
795
- typeId: nodeData.typeId,
796
- params: nodeData.params,
797
- resolvedHandles: nodeData.resolvedHandles,
798
- }, {
799
- inputs: nodeData.inputs,
800
- position: nodeData.position
801
- ? {
802
- x: nodeData.position.x + center.x,
803
- y: nodeData.position.y + center.y,
804
- }
805
- : undefined,
806
- size: nodeData.size,
807
- copyOutputsFrom: nodeData.originalNodeId,
808
- dry: true,
809
- reason,
810
- });
811
- nodeIdMap.set(nodeData.originalNodeId, newNodeId);
812
- if (nodeData.customData !== undefined) {
813
- this.setCustomNodeData(newNodeId, lod.cloneDeep(nodeData.customData), {
814
- commit: false,
815
- reason: options?.reason,
816
- });
817
- }
818
- }
819
- for (const edgeData of data.edges) {
820
- const newSourceNodeId = nodeIdMap.get(edgeData.sourceNodeId);
821
- const newTargetNodeId = nodeIdMap.get(edgeData.targetNodeId);
822
- if (newSourceNodeId && newTargetNodeId) {
823
- const edgeId = this.connect({
824
- source: {
825
- nodeId: newSourceNodeId,
826
- handle: edgeData.sourceHandle,
827
- },
828
- target: {
829
- nodeId: newTargetNodeId,
830
- handle: edgeData.targetHandle,
831
- },
832
- typeId: edgeData.typeId,
833
- }, { dry: true, reason });
834
- edgeIds.push(edgeId);
835
- }
836
- }
837
- this.setSelectionInternal({ nodes: Array.from(nodeIdMap.values()), edges: edgeIds }, options);
838
- completed = true;
839
- return { nodeIdMap, edgeIds };
840
- }
841
- finally {
842
- this.endBatch(completed ? batchOptions : { ...batchOptions, commit: false });
843
- }
844
- }
845
- /**
846
- * Get the currently copied graph data.
847
- */
848
- getCopiedData() {
849
- return this.copiedData;
850
- }
851
- /**
852
- * Set the copied graph data (used for paste functionality).
853
- */
854
- setCopiedData(data) {
855
- this.copiedData = data;
856
- }
857
- /**
858
- * Get the custom name for a node, if set.
859
- */
860
- getNodeName(nodeId) {
861
- return this.nodeNames[nodeId];
862
- }
863
- /**
864
- * Set a custom name for a node. Empty string or undefined removes the custom name.
865
- * This is included in undo/redo history via extData.ui.
866
- */
867
- setNodeName(nodeId, name, options) {
868
- if (name === undefined || name.trim() === "") {
869
- delete this.nodeNames[nodeId];
870
- }
871
- else {
872
- this.nodeNames[nodeId] = name.trim();
873
- }
874
- this.emit("graphUiChanged", {
875
- change: { type: "nodeName", nodeId, name },
876
- ...options,
877
- });
878
- }
879
- /**
880
- * Get custom data for a specific node.
881
- */
882
- getCustomNodeData(nodeId) {
883
- return this.customData.nodes?.[nodeId];
884
- }
885
- /**
886
- * Set custom data for a specific node (supports partial updates).
887
- */
888
- setCustomNodeData(nodeId, data, options) {
889
- if (!this.customData.nodes) {
890
- this.customData.nodes = {};
891
- }
892
- if (data === undefined) {
893
- delete this.customData.nodes[nodeId];
894
- if (Object.keys(this.customData.nodes).length === 0) {
895
- delete this.customData.nodes;
896
- }
897
- }
898
- else {
899
- // If merge is true, merge with existing data; otherwise replace
900
- if (options?.merge && this.customData.nodes[nodeId]) {
901
- this.customData.nodes[nodeId] = {
902
- ...this.customData.nodes[nodeId],
903
- ...data,
904
- };
905
- }
906
- else {
907
- this.customData.nodes[nodeId] = data;
908
- }
909
- }
910
- this.emit("graphUiChanged", {
911
- change: {
912
- type: "customNodeData",
913
- nodeId,
914
- data: this.customData.nodes?.[nodeId],
915
- },
916
- ...options,
917
- });
918
- }
919
- /**
920
- * Get custom data for a specific edge.
921
- */
922
- getCustomEdgeData(edgeId) {
923
- return this.customData.edges?.[edgeId];
924
- }
925
- /**
926
- * Set custom data for a specific edge (supports partial updates).
927
- */
928
- setCustomEdgeData(edgeId, data, options) {
929
- if (!this.customData.edges) {
930
- this.customData.edges = {};
931
- }
932
- if (data === undefined) {
933
- delete this.customData.edges[edgeId];
934
- if (Object.keys(this.customData.edges).length === 0) {
935
- delete this.customData.edges;
936
- }
937
- }
938
- else {
939
- // If merge is true, merge with existing data; otherwise replace
940
- if (options?.merge && this.customData.edges[edgeId]) {
941
- this.customData.edges[edgeId] = {
942
- ...this.customData.edges[edgeId],
943
- ...data,
944
- };
945
- }
946
- else {
947
- this.customData.edges[edgeId] = data;
948
- }
949
- }
950
- this.emit("graphUiChanged", {
951
- change: {
952
- type: "customEdgeData",
953
- edgeId,
954
- data: this.customData.edges?.[edgeId],
955
- },
956
- ...options,
957
- });
958
- }
959
- /**
960
- * Get custom metadata.
961
- */
962
- getCustomMetaData() {
963
- return this.customData.meta;
964
- }
965
- /**
966
- * Set custom metadata (supports partial updates).
967
- */
968
- setCustomMetaData(meta, options) {
969
- if (meta === undefined) {
970
- delete this.customData.meta;
971
- }
972
- else {
973
- // If merge is true, merge with existing metadata; otherwise replace
974
- if (options?.merge && this.customData.meta) {
975
- this.customData.meta = {
976
- ...this.customData.meta,
977
- ...meta,
978
- };
979
- }
980
- else {
981
- this.customData.meta = meta;
982
- }
983
- }
984
- this.emit("graphUiChanged", {
985
- change: { type: "customMetaData", meta: this.customData.meta },
986
- ...options,
987
- });
988
- }
989
- /**
990
- * Get all custom data.
991
- */
992
- getCustomData() {
993
- return { ...this.customData };
994
- }
995
- /**
996
- * Set all custom data.
997
- */
998
- setCustomData(custom, options) {
999
- if (custom === undefined) {
1000
- this.customData = {};
1001
- }
1002
- else {
1003
- this.customData = lod.pick(custom, ["nodes", "edges", "meta"]);
1004
- }
1005
- this.emit("graphUiChanged", {
1006
- change: {
1007
- type: "customData",
1008
- nodes: custom?.nodes,
1009
- edges: custom?.edges,
1010
- meta: custom?.meta,
1011
- },
1012
- ...options,
1013
- });
1014
- }
1015
- }
1016
- //# sourceMappingURL=InMemoryWorkbench.js.map