/** * STATE STORE TEMPLATE * * Singleton store for cross-component state management in LWC. * Provides reactive state with subscription-based updates. * * Features: * - Cross-component state sharing * - Subscription-based reactivity * - Automatic cleanup utilities * - Debug mode for development * * Usage: * import store from 'c/store'; * * // In connectedCallback: * this.cart = store.initState('cart', { items: [], total: 0 }); * this._unsubscribe = store.subscribe('cart', (cart) => { this.cart = cart; }); * * // In disconnectedCallback: * if (this._unsubscribe) this._unsubscribe(); * * // Update state anywhere: * store.setState('cart', { ...store.getState('cart'), items: [...items, newItem] }); */ // ═══════════════════════════════════════════════════════════════════════════ // CONFIGURATION // ═══════════════════════════════════════════════════════════════════════════ /** * Enable debug mode for state change logging. * Set to false in production. */ const DEBUG_MODE = false; // ═══════════════════════════════════════════════════════════════════════════ // PRIVATE STATE // ═══════════════════════════════════════════════════════════════════════════ /** @type {Map} State container */ const state = new Map(); /** @type {Map>} Subscribers by key */ const subscribers = new Map(); /** @type {Map} Initial values for reset */ const initialValues = new Map(); // ═══════════════════════════════════════════════════════════════════════════ // CORE API // ═══════════════════════════════════════════════════════════════════════════ /** * Get current state for a key. * * @param {string} key - State key * @returns {any} Current state value, or undefined if not set * * @example * const cart = store.getState('cart'); */ function getState(key) { return state.get(key); } /** * Set state and notify all subscribers. * * @param {string} key - State key * @param {any} value - New state value * * @example * store.setState('cart', { items: [...cart.items, newItem], total: newTotal }); */ function setState(key, value) { const oldValue = state.get(key); state.set(key, value); if (DEBUG_MODE) { console.log(`[Store] ${key} updated:`, { old: oldValue, new: value }); } // Notify all subscribers for this key const keySubscribers = subscribers.get(key) || []; keySubscribers.forEach(callback => { try { callback(value, oldValue); } catch (e) { console.error(`[Store] Subscriber error for "${key}":`, e); } }); } /** * Subscribe to state changes for a key. * * @param {string} key - State key to watch * @param {Function} callback - Called with (newValue, oldValue) on change * @returns {Function} Unsubscribe function - MUST be called in disconnectedCallback * * @example * // In connectedCallback: * this._unsubscribe = store.subscribe('cart', (cart) => { * this.cart = cart; * }); * * // In disconnectedCallback: * if (this._unsubscribe) this._unsubscribe(); */ function subscribe(key, callback) { if (!subscribers.has(key)) { subscribers.set(key, []); } subscribers.get(key).push(callback); if (DEBUG_MODE) { console.log(`[Store] Subscribed to "${key}", total: ${subscribers.get(key).length}`); } // Return unsubscribe function return () => { const keySubscribers = subscribers.get(key) || []; const index = keySubscribers.indexOf(callback); if (index > -1) { keySubscribers.splice(index, 1); if (DEBUG_MODE) { console.log(`[Store] Unsubscribed from "${key}", remaining: ${keySubscribers.length}`); } } }; } /** * Initialize state with a default value if not already set. * * @param {string} key - State key * @param {any} defaultValue - Default value if key doesn't exist * @returns {any} Current state value (existing or newly set default) * * @example * // Safe initialization - won't overwrite existing state * const cart = store.initState('cart', { items: [], total: 0 }); */ function initState(key, defaultValue) { if (!state.has(key)) { state.set(key, defaultValue); initialValues.set(key, JSON.parse(JSON.stringify(defaultValue))); } return state.get(key); } // ═══════════════════════════════════════════════════════════════════════════ // UTILITY METHODS // ═══════════════════════════════════════════════════════════════════════════ /** * Reset a state key to its initial value. * * @param {string} key - State key to reset * * @example * store.resetState('cart'); // Resets to initial { items: [], total: 0 } */ function resetState(key) { if (initialValues.has(key)) { const initial = JSON.parse(JSON.stringify(initialValues.get(key))); setState(key, initial); } } /** * Clear all state (useful for testing or logout). */ function clearAll() { state.clear(); subscribers.clear(); initialValues.clear(); if (DEBUG_MODE) { console.log('[Store] All state cleared'); } } /** * Get all current state keys. * * @returns {Array} Array of state keys */ function getKeys() { return Array.from(state.keys()); } /** * Check if a state key exists. * * @param {string} key - State key * @returns {boolean} True if key exists */ function hasState(key) { return state.has(key); } /** * Get subscriber count for a key (useful for debugging). * * @param {string} key - State key * @returns {number} Number of active subscribers */ function getSubscriberCount(key) { return (subscribers.get(key) || []).length; } // ═══════════════════════════════════════════════════════════════════════════ // BATCH UPDATES // ═══════════════════════════════════════════════════════════════════════════ let batchUpdates = []; let isBatching = false; /** * Batch multiple state updates to reduce re-renders. * * @param {Function} updateFn - Function containing multiple setState calls * * @example * store.batch(() => { * store.setState('user', newUser); * store.setState('preferences', newPrefs); * store.setState('cart', newCart); * }); // Subscribers notified once after all updates */ function batch(updateFn) { isBatching = true; batchUpdates = []; try { updateFn(); } finally { isBatching = false; // Notify subscribers for all changed keys const changedKeys = new Set(batchUpdates.map(u => u.key)); changedKeys.forEach(key => { const lastUpdate = batchUpdates.filter(u => u.key === key).pop(); if (lastUpdate) { const keySubscribers = subscribers.get(key) || []; keySubscribers.forEach(callback => { try { callback(lastUpdate.value, lastUpdate.oldValue); } catch (e) { console.error(`[Store] Batch subscriber error for "${key}":`, e); } }); } }); batchUpdates = []; } } // ═══════════════════════════════════════════════════════════════════════════ // EXPORTS // ═══════════════════════════════════════════════════════════════════════════ export default { // Core API getState, setState, subscribe, initState, // Utilities resetState, clearAll, getKeys, hasState, getSubscriberCount, // Batch operations batch };