a reactive (signals based) hypermedia web framework (wip) stormlightlabs.github.io/volt/
hypermedia frontend signals
at main 5.3 kB view raw
1/** 2 * Console logging utilities for debugging signals. 3 * 4 * Provides pretty-printed output and update tracing. 5 */ 6 7import type { AnySignal } from "$types/volt"; 8import { getDependencies, getDependents } from "./graph"; 9import { getAllReactives, getAllSignals, getReactiveInfo, getSignalInfo } from "./registry"; 10 11const trackedSignals = new WeakSet<AnySignal>(); 12const traceListeners = new WeakMap<AnySignal, (value: unknown) => void>(); 13 14/** 15 * Pretty-print a signal's information to the console. 16 */ 17export function logSignal(signal: AnySignal): void { 18 const info = getSignalInfo(signal); 19 if (!info) { 20 console.log("[Volt Debug] Unregistered signal"); 21 return; 22 } 23 24 const deps = getDependencies(signal); 25 const depnts = getDependents(signal); 26 27 console.group(`[Volt Signal] ${info.name || info.id}`); 28 console.log("Type:", info.type); 29 console.log("Value:", info.value); 30 console.log("Age:", `${(info.age / 1000).toFixed(2)}s`); 31 console.log("Dependencies:", deps.length); 32 console.log("Dependents:", depnts.length); 33 34 if (deps.length > 0) { 35 console.group("Depends on:"); 36 for (const dep of deps) { 37 const depInfo = getSignalInfo(dep as AnySignal); 38 if (depInfo) { 39 console.log(` - ${depInfo.name || depInfo.id} = ${depInfo.value}`); 40 } 41 } 42 console.groupEnd(); 43 } 44 45 if (depnts.length > 0) { 46 console.group("Dependents:"); 47 for (const depnt of depnts) { 48 const depntInfo = getSignalInfo(depnt); 49 if (depntInfo) { 50 console.log(` - ${depntInfo.name || depntInfo.id}`); 51 } 52 } 53 console.groupEnd(); 54 } 55 56 console.groupEnd(); 57} 58 59export function logAllSignals(): void { 60 const signals = getAllSignals(); 61 console.group(`[Volt Debug] All Signals (${signals.length})`); 62 63 for (const signal of signals) { 64 const info = getSignalInfo(signal); 65 if (info) { 66 console.log(`${info.id.padEnd(15)} ${(info.name || "unnamed").padEnd(20)} ${String(info.value)}`); 67 } 68 } 69 70 console.groupEnd(); 71} 72 73/** 74 * Pretty-print a reactive object's information to the console. 75 */ 76export function logReactive(obj: object): void { 77 const info = getReactiveInfo(obj); 78 if (!info) { 79 console.log("[Volt Debug] Unregistered reactive object"); 80 return; 81 } 82 83 console.group(`[Volt Reactive] ${info.name || info.id}`); 84 console.log("Type:", info.type); 85 console.log("Value:", info.value); 86 console.log("Age:", `${(info.age / 1000).toFixed(2)}s`); 87 console.groupEnd(); 88} 89 90export function logAllReactives(): void { 91 const reactives = getAllReactives(); 92 console.group(`[Volt Debug] All Reactive Objects (${reactives.length})`); 93 94 for (const obj of reactives) { 95 const info = getReactiveInfo(obj); 96 if (info) { 97 console.log(`${info.id.padEnd(15)} ${(info.name || "unnamed").padEnd(20)} ${JSON.stringify(info.value)}`); 98 } 99 } 100 101 console.groupEnd(); 102} 103 104export function trace(signal: AnySignal, enabled = true): void { 105 if (!enabled) { 106 const listener = traceListeners.get(signal); 107 if (listener) { 108 // Can't unsubscribe without keeping the unsubscribe function 109 // TODO: we need to store unsubscribe functions 110 trackedSignals.delete(signal); 111 traceListeners.delete(signal); 112 } 113 return; 114 } 115 116 if (trackedSignals.has(signal)) { 117 return; 118 } 119 120 const info = getSignalInfo(signal); 121 const name = info?.name || info?.id || "unknown"; 122 123 const listener = (value: unknown) => { 124 const stack = new Error("Listener").stack; 125 const caller = stack?.split("\n")[3]?.trim(); 126 127 console.log(`[Volt Trace] ${name} changed:`, value, caller ? `(from ${caller})` : ""); 128 }; 129 130 signal.subscribe(listener); 131 traceListeners.set(signal, listener); 132 trackedSignals.add(signal); 133 134 console.log(`[Volt Debug] Tracing enabled for ${name}`); 135} 136 137export function enableGlobalTracing(): void { 138 const signals = getAllSignals(); 139 console.log(`[Volt Debug] Enabling global tracing for ${signals.length} signals`); 140 141 for (const signal of signals) { 142 trace(signal, true); 143 } 144} 145 146export function disableGlobalTracing(): void { 147 const signals = getAllSignals(); 148 for (const signal of signals) { 149 trace(signal, false); 150 } 151 152 console.log("[Volt Debug] Global tracing disabled"); 153} 154 155export function logSignalTable(): void { 156 const signals = getAllSignals(); 157 const data = signals.map((signal) => { 158 const info = getSignalInfo(signal); 159 if (!info) return null; 160 161 return { 162 ID: info.id, 163 Name: info.name || "(unnamed)", 164 Type: info.type, 165 Value: String(info.value).slice(0, 50), 166 "Age (s)": (info.age / 1000).toFixed(2), 167 Dependencies: getDependencies(signal).length, 168 Dependents: getDependents(signal).length, 169 }; 170 }).filter((row): row is NonNullable<typeof row> => row !== null); 171 172 console.table(data); 173} 174 175export function watch(signal: AnySignal): () => void { 176 const info = getSignalInfo(signal); 177 const name = info?.name || info?.id || "unknown"; 178 179 console.log(`[Volt Debug] Watching ${name}`); 180 181 const unsubscribe = signal.subscribe((value) => { 182 const timestamp = new Date().toISOString(); 183 console.group(`[Volt Watch] ${name} updated at ${timestamp}`); 184 console.log("New value:", value); 185 logSignal(signal); 186 console.groupEnd(); 187 }); 188 189 return () => { 190 console.log(`[Volt Debug] Stopped watching ${name}`); 191 unsubscribe(); 192 }; 193}