( blocks: Record<string, BlockState>, edges: Edge[], subflowDepths?: Map<string, number> )
| 79 | * @param subflowDepths - Optional map of container block IDs to their internal depth (max layers inside) |
| 80 | */ |
| 81 | export function assignLayers( |
| 82 | blocks: Record<string, BlockState>, |
| 83 | edges: Edge[], |
| 84 | subflowDepths?: Map<string, number> |
| 85 | ): Map<string, GraphNode> { |
| 86 | const nodes = new Map<string, GraphNode>() |
| 87 | |
| 88 | for (const [id, block] of Object.entries(blocks)) { |
| 89 | nodes.set(id, { |
| 90 | id, |
| 91 | block, |
| 92 | metrics: getBlockMetrics(block), |
| 93 | incoming: new Set(), |
| 94 | outgoing: new Set(), |
| 95 | layer: 0, |
| 96 | position: { ...block.position }, |
| 97 | }) |
| 98 | } |
| 99 | |
| 100 | const incomingEdgesMap = new Map<string, Edge[]>() |
| 101 | for (const edge of edges) { |
| 102 | if (!incomingEdgesMap.has(edge.target)) { |
| 103 | incomingEdgesMap.set(edge.target, []) |
| 104 | } |
| 105 | incomingEdgesMap.get(edge.target)!.push(edge) |
| 106 | } |
| 107 | |
| 108 | for (const edge of edges) { |
| 109 | const sourceNode = nodes.get(edge.source) |
| 110 | const targetNode = nodes.get(edge.target) |
| 111 | |
| 112 | if (sourceNode && targetNode) { |
| 113 | sourceNode.outgoing.add(edge.target) |
| 114 | targetNode.incoming.add(edge.source) |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | const starterNodes = Array.from(nodes.values()).filter((node) => node.incoming.size === 0) |
| 119 | |
| 120 | if (starterNodes.length === 0 && nodes.size > 0) { |
| 121 | const firstNode = Array.from(nodes.values())[0] |
| 122 | starterNodes.push(firstNode) |
| 123 | logger.warn('No starter blocks found, using first block as starter', { blockId: firstNode.id }) |
| 124 | } |
| 125 | |
| 126 | const inDegreeCount = new Map<string, number>() |
| 127 | |
| 128 | for (const node of nodes.values()) { |
| 129 | inDegreeCount.set(node.id, node.incoming.size) |
| 130 | if (starterNodes.includes(node)) { |
| 131 | node.layer = 0 |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | const queue: string[] = starterNodes.map((n) => n.id) |
| 136 | const processed = new Set<string>() |
| 137 | |
| 138 | while (queue.length > 0) { |
no test coverage detected