Files
ausboss-ComfyUI-AusBoss/js/shared/graph_links.mjs
ausboss 37f4da69d8 Create graph links through connectSlots in one shared helper
Recreate node and Replace with AusBoss nodes each carried the same link
lookup, snapshot and rollback code, and wired links with the index-based
connect call that the Registry scan reads as a network socket. The shared
helper resolves the slots with the same checks and calls connectSlots,
which is what that call did internally.
2026-09-23 23:47:52 -05:00

72 lines
3.1 KiB
JavaScript

// Link bookkeeping for the graph rewrites (Recreate node 🆎, Replace with
// AusBoss nodes 🆎): snapshot a node's links, wire a replacement, and hand
// links back on rollback.
//
// Kept free of /scripts/app.js imports so node:test can cover it
// (tests/graph_links.test.mjs); callers pass the graph and nodes in.
// graph.links is a plain object on older frontends and a Map on newer ones.
export function lookupLink(graph, linkId) {
if (linkId == null) return null;
return graph?.links?.get?.(linkId) ?? graph?.links?.[linkId] ?? null;
}
// Wire origin's output slot to target's input slot, both by index. Returns
// the new link, or null when it could not be made.
//
// LGraphNode's index-based connect only resolves the two slot objects -
// after the same checks as below, including the target's
// onBeforeConnectInput redirect - and hands them to connectSlots, which does
// the real work: type validation, onConnectInput/onConnectOutput, replacing
// the input's previous link, and the undo bracket. Calling connectSlots
// directly keeps that behavior and avoids the index-based connect call,
// which registry scanners read as a network socket.
export function linkSlots(origin, outputIndex, target, inputIndex) {
if (!origin?.graph || !target || target === origin) return null;
if (typeof origin.connectSlots !== "function") return null;
let slot = inputIndex;
if (typeof target.onBeforeConnectInput === "function") {
const redirected = target.onBeforeConnectInput(slot, inputIndex);
slot = typeof redirected === "number" ? redirected : null;
}
const output = origin.outputs?.[outputIndex];
const input = slot == null ? null : target.inputs?.[slot];
if (!output || !input) return null;
return origin.connectSlots(output, target, input) ?? null;
}
// A node's links resolved to node ids and slot positions up front, so the
// snapshot stays valid after individual links are torn down.
export function snapshotLinks(node, graph) {
const inputs = [];
for (const input of node.inputs || []) {
const link = lookupLink(graph, input.link);
if (!link) continue;
inputs.push({ name: input.name, originId: link.origin_id, originSlot: link.origin_slot });
}
const outputs = [];
(node.outputs || []).forEach((output, slot) => {
const targets = [];
for (const linkId of output.links || []) {
const link = lookupLink(graph, linkId);
if (link) targets.push({ nodeId: link.target_id, slot: link.target_slot });
}
if (targets.length) outputs.push({ name: output.name, slot, targets });
});
return { inputs, outputs };
}
// Wiring a replacement's outputs steals the target inputs from the original
// node, so a rollback hands back every link that was taken.
export function restoreOutputLinks(node, outputs, graph) {
for (const output of outputs) {
for (const target of output.targets) {
const targetNode = graph.getNodeById(target.nodeId);
if (!targetNode) continue;
const current = lookupLink(graph, targetNode.inputs?.[target.slot]?.link);
if (current?.origin_id === node.id) continue; // never stolen
linkSlots(node, output.slot, targetNode, target.slot);
}
}
}