a reactive (signals based) hypermedia web framework (wip) stormlightlabs.github.io/volt/
hypermedia frontend signals
at main 2.8 kB view raw
1/** 2 * Global reactive store for cross-scope state management 3 * 4 * The store holds signals that are accessible from any scope via $store. 5 * Supports both programmatic registration and declarative initialization. 6 */ 7 8import type { Optional } from "$types/helpers"; 9import type { GlobalStore, Signal } from "$types/volt"; 10import { signal } from "./signal"; 11 12/** 13 * Internal signal registry for the global store 14 */ 15const storeSignals = new Map<string, Signal<unknown>>(); 16 17/** 18 * Global store singleton with helper methods and direct signal access 19 */ 20const store: GlobalStore = { 21 _signals: storeSignals, 22 23 get<T = unknown>(key: string): Optional<T> { 24 const sig = storeSignals.get(key); 25 return sig ? (sig.get() as T) : undefined; 26 }, 27 28 set<T = unknown>(key: string, value: T): void { 29 const sig = storeSignals.get(key); 30 if (sig) { 31 sig.set(value); 32 } else { 33 storeSignals.set(key, signal(value)); 34 } 35 }, 36 37 has(key: string): boolean { 38 return storeSignals.has(key); 39 }, 40}; 41 42/** 43 * Register state in the global store. 44 * 45 * Accepts either: 46 * - An object of signals: { theme: signal('dark') } 47 * - An object of values: { count: 0 } (auto-wrapped in signals) 48 * 49 * @param state - Object containing signals or raw values to register globally 50 * 51 * @example 52 * ```ts 53 * // With signals 54 * registerStore({ 55 * theme: signal('dark'), 56 * user: signal({ name: 'Alice' }) 57 * }); 58 * 59 * // With raw values (auto-wrapped) 60 * registerStore({ 61 * count: 0, 62 * isLoggedIn: false 63 * }); 64 * ``` 65 */ 66export function registerStore(state: Record<string, unknown>): void { 67 for (const [key, value] of Object.entries(state)) { 68 if (value && typeof value === "object" && "get" in value && "set" in value && "subscribe" in value) { 69 storeSignals.set(key, value as Signal<unknown>); 70 Object.defineProperty(store, key, { get: () => value, enumerable: true, configurable: true }); 71 } else { 72 const sig = signal(value); 73 storeSignals.set(key, sig); 74 Object.defineProperty(store, key, { get: () => sig, enumerable: true, configurable: true }); 75 } 76 } 77} 78 79/** 80 * Get the global store instance. 81 * 82 * The store provides: 83 * - Direct signal access: `$store.theme` returns the signal 84 * - Helper methods: `$store.get('theme')` returns the unwrapped value 85 * - Signal creation: `$store.set('newKey', value)` creates a new signal 86 * 87 * @returns The global store singleton 88 * 89 * @example 90 * ```ts 91 * const store = getStore(); 92 * 93 * // Access signal directly 94 * const themeSignal = store.theme; 95 * 96 * // Get unwrapped value 97 * const currentTheme = store.get('theme'); 98 * 99 * // Set value (creates signal if doesn't exist) 100 * store.set('theme', 'light'); 101 * ``` 102 */ 103export function getStore(): GlobalStore { 104 return store; 105}