MCPcopy Create free account
hub / github.com/ForestHubAI/edge-agents / addNodeToStore

Function addNodeToStore

ts/workflow-builder/src/utils/graphOperations.ts:103–178  ·  view source on GitHub ↗
(store: CanvasStore, nodeDef: NodeDefinition, position?: { x: number; y: number })

Source from the content-addressed store, hash-verified

101
102/** Add a node to the store given a node definition and optional position. Returns the new node ID, or null if the node cannot be added. */
103export function addNodeToStore(store: CanvasStore, nodeDef: NodeDefinition, position?: { x: number; y: number }): string | null {
104 if (!canAddNode(store, nodeDef)) return null;
105
106 const nodeId = generateId();
107
108 // Initialize parameters with default values. Clone so object/array defaults
109 // (Expression, weekdays `[]`, memory-refs `[]`) aren't shared by reference
110 // across instances or with the definition itself — a shared mutable default
111 // would alias on edit.
112 const args: Record<string, unknown> = {};
113 nodeDef.parameters.forEach((param) => {
114 if (param.default !== undefined) {
115 args[param.id] = structuredClone(param.default);
116 }
117 });
118
119 // Seed outputs directly into args:
120 // - Static outputs: `args[out.id] = { active: true, mode: "emit", name: out.id }` (OutputBinding)
121 // - List outputs: `args[out.id] = []` (empty OutputDeclaration[] — user adds entries)
122 for (const out of nodeDef.outputs ?? []) {
123 if (out.type === "static") {
124 args[out.id] = { active: true, mode: "emit", name: out.id };
125 } else {
126 args[out.id] = [];
127 }
128 }
129
130 const nodeData: NodeBase = {
131 id: nodeId,
132 type: nodeDef.type,
133 arguments: args,
134 };
135 if (nodeDef.type === "FunctionCall") {
136 const functionDef = nodeDef as FunctionNodeDefinition;
137 const functionNode = nodeData as FunctionCallNode;
138 functionNode.functionInfo = functionDef.functionInfo;
139 // Seed input bindings keyed by arg uid — empty expressions with the declared dataType.
140 // Output bindings were already seeded above by the generic outputs[] loop since
141 // buildFunctionNodeDef now emits real StaticOutput entries.
142 for (const arg of functionDef.functionInfo.arguments) {
143 args[paramKey(arg)] = { expression: "", references: [], dataType: arg.dataType };
144 }
145 // Generic seeding uses the output id as the default emit name, but for function
146 // returns we want the return's variable name.
147 for (const ret of functionDef.functionInfo.returns) {
148 args[paramKey(ret)] = { active: true, mode: "emit" as const, name: ret.name };
149 }
150 }
151
152 // Deduplicate emit binding names against existing variables on the canvas
153 const existingNames = collectVariableNames(store);
154 deduplicateEmitNames(nodeData as NodeData, existingNames);
155
156 // Nudge position if another node is already nearby (avoids stacking on click-to-add)
157 const currentNodes = store.getState().nodes;
158 let pos = position || { x: 250, y: 100 };
159 const SNAP_DISTANCE = 50;
160 while (currentNodes.some((n) => Math.abs(n.position.x - pos.x) < SNAP_DISTANCE && Math.abs(n.position.y - pos.y) < SNAP_DISTANCE)) {

Callers 1

useGraphFunction · 0.90

Calls 7

generateIdFunction · 0.90
paramKeyFunction · 0.90
canAddNodeFunction · 0.85
collectVariableNamesFunction · 0.85
deduplicateEmitNamesFunction · 0.85
getReactFlowTypeFunction · 0.85

Tested by

no test coverage detected