* Determines whether this output is a valid endpoint for a link (non-virtual, non-bypass). * @param slot The slot index of the output. * @param type The type of the input * @param visited A set of unique IDs to guard against infinite recursion. See resolveInput. * @returns The no
(slot: number, type: ISlotType, visited: Set<string>)
| 201 | * @returns The node and the origin ID / slot index of the output. |
| 202 | */ |
| 203 | resolveOutput(slot: number, type: ISlotType, visited: Set<string>): ResolvedInput | undefined { |
| 204 | const uniqueId = `${this.subgraphNode?.subgraph.id}:${this.node.id}[O]${slot}` |
| 205 | if (visited.has(uniqueId)) { |
| 206 | const nodeInfo = `${this.node.id}${this.node.title ? ` (${this.node.title})` : ""}` |
| 207 | const pathInfo = this.subgraphNodePath.length > 0 ? ` at path ${this.subgraphNodePath.join(":")}` : "" |
| 208 | throw new RecursionError( |
| 209 | `Circular reference detected while resolving output ${slot} of node ${nodeInfo}${pathInfo}. ` + |
| 210 | `This creates an infinite loop in link resolution. UniqueID: [${uniqueId}]`, |
| 211 | ) |
| 212 | } |
| 213 | visited.add(uniqueId) |
| 214 | |
| 215 | // Upstreamed: Bypass nodes are bypassed using the first input with matching type |
| 216 | if (this.mode === LGraphEventMode.BYPASS) { |
| 217 | const { inputs } = this |
| 218 | |
| 219 | // Bypass nodes by finding first input with matching type |
| 220 | const parentInputIndexes = Object.keys(inputs).map(Number) |
| 221 | // Prioritise exact slot index |
| 222 | const indexes = [slot, ...parentInputIndexes] |
| 223 | const matchingIndex = indexes.find(i => inputs[i]?.type === type) |
| 224 | |
| 225 | // No input types match |
| 226 | if (matchingIndex === undefined) { |
| 227 | console.debug(`[ExecutableNodeDTO.resolveOutput] No input types match type [${type}] for id [${this.id}] slot [${slot}]`, this) |
| 228 | return |
| 229 | } |
| 230 | |
| 231 | return this.resolveInput(matchingIndex, visited) |
| 232 | } |
| 233 | |
| 234 | const { node } = this |
| 235 | if (node.isSubgraphNode()) return this.#resolveSubgraphOutput(slot, type, visited) |
| 236 | |
| 237 | // Upstreamed: Other virtual nodes are bypassed using the same input/output index (slots must match) |
| 238 | if (node.isVirtualNode) { |
| 239 | if (this.inputs.at(slot)) return this.resolveInput(slot, visited) |
| 240 | |
| 241 | // Fallback check for nodes performing link redirection |
| 242 | const virtualLink = this.node.getInputLink(slot) |
| 243 | if (virtualLink) { |
| 244 | const outputNode = this.graph.getNodeById(virtualLink.origin_id) |
| 245 | if (!outputNode) throw new InvalidLinkError(`Virtual node failed to resolve parent [${this.id}] slot [${slot}]`) |
| 246 | |
| 247 | const outputNodeExecutionId = [...this.subgraphNodePath, outputNode.id].join(":") |
| 248 | const outputNodeDto = this.nodesByExecutionId.get(outputNodeExecutionId) |
| 249 | if (!outputNodeDto) throw new Error(`No output node DTO found for id [${outputNode.id}]`) |
| 250 | |
| 251 | return outputNodeDto.resolveOutput(virtualLink.origin_slot, type, visited) |
| 252 | } |
| 253 | |
| 254 | // Virtual nodes without a matching input should be discarded. |
| 255 | return |
| 256 | } |
| 257 | |
| 258 | return { |
| 259 | node: this, |
| 260 | origin_id: this.id, |
no test coverage detected