Something went wrong. Try again.
a reactive (signals based) hypermedia web framework (wip) stormlightlabs.github.io/volt
hypermedia frontend signals
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166/** * Dependency graph tracking and querying for debugging. * * Tracks signal dependency relationships to enable visualization * and debugging of reactive data flow. */
import type { AnySignal, Dep, DepGraph, GraphNode } from "$types/volt";import { getSignalMetadata } from "./registry";
const dependencies = new WeakMap<AnySignal, Set<Dep>>();const dependents = new WeakMap<Dep, Set<AnySignal>>();
/** * Record that a signal/computed depends on certain dependencies. * Should be called after tracking is complete. */export function recordDependencies(signal: AnySignal, deps: Dep[]): void { const existingDeps = dependencies.get(signal) || new Set();
for (const dep of deps) { existingDeps.add(dep);
const existingDependents = dependents.get(dep) || new Set(); existingDependents.add(signal); dependents.set(dep, existingDependents); }
dependencies.set(signal, existingDeps);}
/** * Get all signals that this signal/computed depends on. */export function getDependencies(signal: AnySignal): Dep[] { const deps = dependencies.get(signal); return deps ? [...deps] : [];}
/** * Get all signals/computeds that depend on this signal. */export function getDependents(signal: Dep): AnySignal[] { const deps = dependents.get(signal); return deps ? [...deps] : [];}
/** * Check if there's a dependency relationship between two signals. */export function hasDependency(dependent: AnySignal, dependency: Dep): boolean { const deps = dependencies.get(dependent); return deps ? deps.has(dependency) : false;}
export function buildDependencyGraph(signals: AnySignal[]): DepGraph { const nodes: GraphNode[] = []; const edges: Array<{ from: string; to: string }> = []; const signalToId = new Map<AnySignal, string>();
for (const signal of signals) { const metadata = getSignalMetadata(signal); if (!metadata) continue;
signalToId.set(signal, metadata.id);
const deps = getDependencies(signal); const depIds = deps.map((dep) => { const depMetadata = getSignalMetadata(dep as AnySignal); return depMetadata?.id; }).filter((id): id is string => id !== undefined);
const depnts = getDependents(signal); const depntIds = depnts.map((depnt) => { const depntMetadata = getSignalMetadata(depnt); return depntMetadata?.id; }).filter((id): id is string => id !== undefined);
nodes.push({ signal, id: metadata.id, name: metadata.name, type: metadata.type, value: signal.get(), dependencies: depIds, dependents: depntIds, }); }
for (const node of nodes) { for (const depId of node.dependencies) { edges.push({ from: depId, to: node.id }); } }
return { nodes, edges };}
export function detectCircularDependencies(signal: AnySignal): AnySignal[] | null { const visited = new Set<AnySignal>(); const path = new Set<AnySignal>();
function dfs(current: AnySignal): AnySignal[] | null { if (path.has(current)) { return [current]; }
if (visited.has(current)) { return null; }
visited.add(current); path.add(current);
const deps = getDependencies(current); for (const dep of deps) { const cycle = dfs(dep as AnySignal); if (cycle) { cycle.push(current); return cycle; } }
path.delete(current); return null; }
return dfs(signal);}
/** * Get the depth of a signal in the dependency tree. * Signals with no dependencies have depth 0. */export function getSignalDepth(signal: AnySignal): number { const visited = new Set<AnySignal>();
function calculateDepth(current: AnySignal): number { if (visited.has(current)) { return 0; }
visited.add(current);
const deps = getDependencies(current); if (deps.length === 0) { return 0; }
let maxDepth = 0; for (const dep of deps) { const depth = calculateDepth(dep as AnySignal); maxDepth = Math.max(maxDepth, depth); }
return maxDepth + 1; }
return calculateDepth(signal);}
export function clearDependencyGraph(): void { // WeakMaps don't have a clear method, but we can't do much here // The GC will clean up when signals are no longer referenced}