a reactive (signals based) hypermedia web framework (wip) stormlightlabs.github.io/volt/
hypermedia frontend signals
at main 2.4 kB view raw
1/** 2 * Dependency tracking system for automatic signal dependency detection. 3 * 4 * Uses a stack-based tracking context to record signal accesses during computations. 5 * When a computed signal or effect runs, it pushes a tracking context onto the stack. 6 * Any signal.get() calls during execution are recorded as dependencies. 7 */ 8 9import type { Dep } from "$types/volt"; 10 11/** 12 * Holds the set of dependencies discovered during this tracking session and the source being tracked (to prevent cycles) 13 */ 14type TrackingContext = { deps: Set<Dep>; source?: Dep }; 15 16/** 17 * Global stack of active tracking contexts. 18 * When nested computeds run, multiple contexts can be active simultaneously. 19 */ 20const trackingStack: TrackingContext[] = []; 21 22/** 23 * Get the currently active tracking context, if any. 24 */ 25function getActiveContext(): TrackingContext | undefined { 26 return trackingStack.at(-1); 27} 28 29/** 30 * Start tracking signal dependencies. 31 * Should be called before executing a computation function. 32 * 33 * @param source - Optional source signal for cycle detection 34 * @returns The tracking context 35 */ 36export function startTracking(source?: Dep): TrackingContext { 37 const context: TrackingContext = { deps: new Set(), source }; 38 39 trackingStack.push(context); 40 return context; 41} 42 43/** 44 * Stop tracking and return the collected dependencies. 45 * Should be called after executing a computation function. 46 * 47 * @returns Array of signals that were accessed during tracking 48 */ 49export function stopTracking(): Dep[] { 50 const context = trackingStack.pop(); 51 if (!context) { 52 console.warn("stopTracking called without matching startTracking"); 53 return []; 54 } 55 56 return [...context.deps]; 57} 58 59/** 60 * Record a signal access as a dependency. 61 * Called by signal.get() when inside a tracking context. 62 * 63 * @param dep - The signal being accessed 64 */ 65export function recordDep(dep: Dep): void { 66 const context = getActiveContext(); 67 if (!context) { 68 return; 69 } 70 71 if (context.source === dep) { 72 throw new Error("Circular dependency detected: a signal cannot depend on itself"); 73 } 74 75 context.deps.add(dep); 76} 77 78/** 79 * Check if currently inside a tracking context. 80 * Useful for conditional behavior in signal.get() 81 */ 82export function isTracking(): boolean { 83 return trackingStack.length > 0; 84} 85 86/** 87 * Get current tracking depth (for debugging). 88 */ 89export function getTrackingDepth(): number { 90 return trackingStack.length; 91}