Framework updates

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2026-03-04 23:20:19 +00:00
parent a89daf3d43
commit 3ed8517b2a
891 changed files with 11126 additions and 9600 deletions

View File

@@ -43,7 +43,9 @@ class AppendOnlyStackedSet {
clear() {
this._sets = [];
if (this._current) this._current.clear();
if (this._current) {
this._current = undefined;
}
}
/**
@@ -52,6 +54,25 @@ class AppendOnlyStackedSet {
createChild() {
return new AppendOnlyStackedSet(this._sets.length ? [...this._sets] : []);
}
/**
* @returns {Iterator<T>} iterable iterator
*/
[Symbol.iterator]() {
const iterators = this._sets.map((map) => map[Symbol.iterator]());
let current = iterators.pop();
return {
next() {
if (!current) return { done: true, value: undefined };
let result = current.next();
while (result.done && iterators.length > 0) {
current = /** @type {SetIterator<T>} */ (iterators.pop());
result = current.next();
}
return result;
}
};
}
}
module.exports = AppendOnlyStackedSet;

View File

@@ -8,19 +8,13 @@
const NO_MARKER = 0;
const IN_PROGRESS_MARKER = 1;
const DONE_MARKER = 2;
const DONE_MAYBE_ROOT_CYCLE_MARKER = 3;
const DONE_AND_ROOT_MARKER = 4;
const CANDIDATE_MARKER = 3;
/**
* @template T
* @typedef {Set<Node<T>>} Nodes
*/
/**
* @template T
* @typedef {Set<Cycle<T>>} Cycles
*/
/**
* @template T
*/
@@ -32,20 +26,24 @@ class Node {
this.item = item;
/** @type {Nodes<T>} */
this.dependencies = new Set();
this.marker = NO_MARKER;
/** @type {Cycle<T> | undefined} */
this.cycle = undefined;
/** @type {SCC<T>} */
this.scc = new SCC();
// Each node starts as a single-node SCC
this.scc.nodes.add(this);
/** @type {number} */
this.incoming = 0;
}
}
/**
* SCC (strongly connected component)
* @template T
*/
class Cycle {
class SCC {
constructor() {
/** @type {Nodes<T>} */
this.nodes = new Set();
this.marker = NO_MARKER;
}
}
@@ -70,10 +68,10 @@ module.exports = (items, getDependencies) => {
itemToNode.set(item, node);
}
// early exit when there is only a single item
// Early exit when there is only one node
if (itemToNode.size <= 1) return items;
// grab all the dependencies
// Build graph edges
for (const node of itemToNode.values()) {
for (const dep of getDependencies(node.item)) {
const depNode = itemToNode.get(dep);
@@ -83,27 +81,16 @@ module.exports = (items, getDependencies) => {
}
}
// Set of current root modules
// items will be removed if a new reference to it has been found
/** @type {Nodes<T>} */
const roots = new Set();
// All candidate root SCCs, they will be removed once an incoming edge is found
/** @type {Set<SCC<T>>} */
const rootSCCs = new Set();
// Set of current cycles without references to it
// cycles will be removed if a new reference to it has been found
// that is not part of the cycle
/** @type {Cycles<T>} */
const rootCycles = new Set();
// For all non-marked nodes
for (const selectedNode of itemToNode.values()) {
if (selectedNode.marker === NO_MARKER) {
// deep-walk all referenced modules
// in a non-recursive way
// DFS walk only once per unseen SCC
if (selectedNode.scc.marker === NO_MARKER) {
selectedNode.scc.marker = IN_PROGRESS_MARKER;
// start by entering the selected node
selectedNode.marker = IN_PROGRESS_MARKER;
// keep a stack to avoid recursive walk
// Keep a stack to avoid recursive walk
/** @type {StackEntry<T>[]} */
const stack = [
{
@@ -112,130 +99,113 @@ module.exports = (items, getDependencies) => {
}
];
// process the top item until stack is empty
while (stack.length > 0) {
const topOfStack = stack[stack.length - 1];
// Are there still edges unprocessed in the current node?
// Process one unvisited outgoing edge if available
if (topOfStack.openEdges.length > 0) {
// Process one dependency
const dependency =
/** @type {Node<T>} */
(topOfStack.openEdges.pop());
switch (dependency.marker) {
const depSCC = dependency.scc;
switch (depSCC.marker) {
case NO_MARKER:
// dependency has not be visited yet
// mark it as in-progress and recurse
// First time we see this SCC: enter it
stack.push({
node: dependency,
openEdges: [...dependency.dependencies]
});
dependency.marker = IN_PROGRESS_MARKER;
depSCC.marker = IN_PROGRESS_MARKER;
break;
case IN_PROGRESS_MARKER: {
// It's a in-progress cycle
let cycle = dependency.cycle;
if (!cycle) {
cycle = new Cycle();
cycle.nodes.add(dependency);
dependency.cycle = cycle;
}
// set cycle property for each node in the cycle
// if nodes are already part of a cycle
// we merge the cycles to a shared cycle
// Back-edge to an SCC that is still on the stack
// Example:
// A -> B -> C -> D
// ^ |
// |_________|
// If we are at `D` and traverse `D` -> `B`, then `B/C/D` must be in one SCC
/** @type {Set<SCC<T>>} */
const sccsToMerge = new Set();
for (
let i = stack.length - 1;
stack[i].node !== dependency;
stack[i].node.scc !== depSCC;
i--
) {
const node = stack[i].node;
if (node.cycle) {
if (node.cycle !== cycle) {
// merge cycles
for (const cycleNode of node.cycle.nodes) {
cycleNode.cycle = cycle;
cycle.nodes.add(cycleNode);
}
}
} else {
node.cycle = cycle;
cycle.nodes.add(node);
sccsToMerge.add(stack[i].node.scc);
}
for (const sccToMerge of sccsToMerge) {
for (const nodeInMergedSCC of sccToMerge.nodes) {
nodeInMergedSCC.scc = depSCC;
depSCC.nodes.add(nodeInMergedSCC);
}
}
// don't recurse into dependencies
// these are already on the stack
break;
}
case DONE_AND_ROOT_MARKER:
// This node has be visited yet and is currently a root node
// But as this is a new reference to the node
// it's not really a root
// so we have to convert it to a normal node
dependency.marker = DONE_MARKER;
roots.delete(dependency);
case CANDIDATE_MARKER:
// This finished SCC was previously considered as root SCC
// We just found a new incoming edge, so it is no longer a candidate
rootSCCs.delete(/** @type {SCC<T>} */ (depSCC));
depSCC.marker = DONE_MARKER;
break;
case DONE_MAYBE_ROOT_CYCLE_MARKER:
// This node has be visited yet and
// is maybe currently part of a completed root cycle
// we found a new reference to the cycle
// so it's not really a root cycle
// remove the cycle from the root cycles
// and convert it to a normal node
rootCycles.delete(/** @type {Cycle<T>} */ (dependency.cycle));
dependency.marker = DONE_MARKER;
case DONE_MARKER:
// Already finalized and not a candidate
break;
// DONE_MARKER: nothing to do, don't recurse into dependencies
}
} else {
// All dependencies of the current node has been visited
// we leave the node
// All dependencies of the current node have been processed
// So we leave the node
stack.pop();
topOfStack.node.marker = DONE_MARKER;
// Mark an SCC as DONE only when the popped node is the last
// node from that SCC remaining on the current stack.
// A -> B -> C -> D
// ^ |
// |_________|
// If `B` is popped and the new stack top is `A`, they are in
// different SCCs, so B's SCC can be finalized.
if (
stack.length &&
topOfStack.node.scc !== stack[stack.length - 1].node.scc
) {
topOfStack.node.scc.marker = DONE_MARKER;
}
}
}
const cycle = selectedNode.cycle;
if (cycle) {
for (const node of cycle.nodes) {
node.marker = DONE_MAYBE_ROOT_CYCLE_MARKER;
}
rootCycles.add(cycle);
} else {
selectedNode.marker = DONE_AND_ROOT_MARKER;
roots.add(selectedNode);
}
const scc = selectedNode.scc;
// This SCC is complete and currently has no known incoming edge
scc.marker = CANDIDATE_MARKER;
rootSCCs.add(scc);
}
}
// Extract roots from root cycles
// We take the nodes with most incoming edges
// inside of the cycle
for (const cycle of rootCycles) {
/** @type {Set<T>} */
const rootNodes = new Set();
// For each root SCC, we select node with the most incoming edges
// from within the same SCC
for (const scc of rootSCCs) {
let max = 0;
/** @type {Nodes<T>} */
const cycleRoots = new Set();
const nodes = cycle.nodes;
for (const node of nodes) {
const nodes = new Set(scc.nodes);
for (const node of scc.nodes) {
for (const dep of node.dependencies) {
if (nodes.has(dep)) {
if (scc.nodes.has(dep)) {
dep.incoming++;
if (dep.incoming < max) continue;
if (dep.incoming > max) {
cycleRoots.clear();
nodes.clear();
max = dep.incoming;
}
cycleRoots.add(dep);
nodes.add(dep);
}
}
}
for (const cycleRoot of cycleRoots) {
roots.add(cycleRoot);
for (const node of nodes) {
rootNodes.add(node.item);
}
}
// When roots were found, return them
if (roots.size > 0) {
return Array.from(roots, (r) => r.item);
}
// When root nodes were found, return them
if (rootNodes.size > 0) return rootNodes;
throw new Error("Implementation of findGraphRoots is broken");
};