Skip to content

Commit e65db05

Browse files
committed
Add Node Manager
1 parent 9511aeb commit e65db05

File tree

2 files changed

+125
-9
lines changed

2 files changed

+125
-9
lines changed

src/nodeManager.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { nanoid } from "nanoid";
2+
3+
import {
4+
type ComponentSpec,
5+
isGraphImplementation,
6+
} from "./utils/componentSpec";
7+
8+
export type NodeType = "task" | "input" | "output";
9+
10+
interface NodeMapping {
11+
nodeId: string;
12+
taskId: string;
13+
nodeType: NodeType;
14+
createdAt: number;
15+
}
16+
17+
export class NodeManager {
18+
private mappings = new Map<string, NodeMapping>();
19+
private taskToNodeMap = new Map<string, string>();
20+
21+
// Get stable node ID for a task/input/output
22+
getNodeId(taskId: string, nodeType: NodeType): string {
23+
const existing = this.taskToNodeMap.get(taskId);
24+
if (existing) {
25+
return existing;
26+
}
27+
28+
// Generate new stable ID
29+
const nodeId = `${nodeType}_${nanoid()}`;
30+
const mapping: NodeMapping = {
31+
nodeId,
32+
taskId,
33+
nodeType,
34+
createdAt: Date.now(),
35+
};
36+
37+
this.mappings.set(nodeId, mapping);
38+
this.taskToNodeMap.set(taskId, nodeId);
39+
40+
return nodeId;
41+
}
42+
43+
// Update task ID when name changes (keeps same node ID)
44+
updateTaskId(oldTaskId: string, newTaskId: string): void {
45+
const nodeId = this.taskToNodeMap.get(oldTaskId);
46+
if (!nodeId) return;
47+
48+
const mapping = this.mappings.get(nodeId);
49+
if (!mapping) return;
50+
51+
// Update mappings
52+
this.taskToNodeMap.delete(oldTaskId);
53+
this.taskToNodeMap.set(newTaskId, nodeId);
54+
55+
mapping.taskId = newTaskId;
56+
this.mappings.set(nodeId, mapping);
57+
}
58+
59+
// Remove node when task is deleted
60+
removeNode(taskId: string): void {
61+
const nodeId = this.taskToNodeMap.get(taskId);
62+
if (!nodeId) return;
63+
64+
this.mappings.delete(nodeId);
65+
this.taskToNodeMap.delete(taskId);
66+
}
67+
68+
// Get task ID from node ID
69+
getTaskId(nodeId: string): string | undefined {
70+
return this.mappings.get(nodeId)?.taskId;
71+
}
72+
73+
// Sync with component spec to handle external changes
74+
syncWithComponentSpec(componentSpec: ComponentSpec): void {
75+
const currentTasks = new Set<string>();
76+
77+
// Collect all current task IDs from spec
78+
if (isGraphImplementation(componentSpec.implementation)) {
79+
Object.keys(componentSpec.implementation.graph.tasks).forEach(
80+
(taskId) => {
81+
currentTasks.add(taskId);
82+
},
83+
);
84+
}
85+
86+
componentSpec.inputs?.forEach((input) => currentTasks.add(input.name));
87+
componentSpec.outputs?.forEach((output) => currentTasks.add(output.name));
88+
89+
// Remove mappings for deleted tasks
90+
for (const [taskId] of this.taskToNodeMap) {
91+
if (!currentTasks.has(taskId)) {
92+
this.removeNode(taskId);
93+
}
94+
}
95+
}
96+
97+
// Get all mappings (for debugging/persistence)
98+
getAllMappings(): NodeMapping[] {
99+
return Array.from(this.mappings.values());
100+
}
101+
}

src/providers/ComponentSpecProvider.tsx

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { type ReactNode, useCallback, useMemo, useRef, useState } from "react";
22

33
import { type UndoRedo, useUndoRedo } from "@/hooks/useUndoRedo";
4+
import { NodeManager } from "@/nodeManager";
45
import { loadPipelineByName } from "@/services/pipelineService";
56
import { USER_PIPELINES_LIST_NAME } from "@/utils/constants";
67
import { prepareComponentRefForEditor } from "@/utils/prepareComponentRefForEditor";
@@ -44,6 +45,7 @@ interface ComponentSpecContextType {
4445
taskStatusMap: Map<string, string>;
4546
setTaskStatusMap: (taskStatusMap: Map<string, string>) => void;
4647
undoRedo: UndoRedo;
48+
nodeManager: NodeManager;
4749
}
4850

4951
const ComponentSpecContext = createRequiredContext<ComponentSpecContextType>(
@@ -59,6 +61,7 @@ export const ComponentSpecProvider = ({
5961
readOnly?: boolean;
6062
children: ReactNode;
6163
}) => {
64+
const [nodeManager] = useState(() => new NodeManager());
6265
const [componentSpec, setComponentSpec] = useState<ComponentSpec>(
6366
spec ?? EMPTY_GRAPH_COMPONENT_SPEC,
6467
);
@@ -142,15 +145,25 @@ export const ComponentSpecProvider = ({
142145
[componentSpec, readOnly],
143146
);
144147

145-
const updateGraphSpec = useCallback((newGraphSpec: GraphSpec) => {
146-
setComponentSpec((prevSpec) => ({
147-
...prevSpec,
148-
implementation: {
149-
...prevSpec.implementation,
150-
graph: newGraphSpec,
151-
},
152-
}));
153-
}, []);
148+
const updateGraphSpec = useCallback(
149+
(newGraphSpec: GraphSpec) => {
150+
setComponentSpec((prevSpec) => {
151+
const newSpec = {
152+
...prevSpec,
153+
implementation: {
154+
...prevSpec.implementation,
155+
graph: newGraphSpec,
156+
},
157+
};
158+
159+
// Sync node manager with new spec
160+
nodeManager.syncWithComponentSpec(newSpec);
161+
162+
return newSpec;
163+
});
164+
},
165+
[nodeManager],
166+
);
154167

155168
const value = useMemo(
156169
() => ({
@@ -167,6 +180,7 @@ export const ComponentSpecProvider = ({
167180
updateGraphSpec,
168181
setTaskStatusMap,
169182
undoRedo,
183+
nodeManager,
170184
}),
171185
[
172186
componentSpec,
@@ -182,6 +196,7 @@ export const ComponentSpecProvider = ({
182196
updateGraphSpec,
183197
setTaskStatusMap,
184198
undoRedo,
199+
nodeManager,
185200
],
186201
);
187202

0 commit comments

Comments
 (0)