afv-library/skills/generating-lwc-components/assets/state-store/store.js
sandipkumar-yadav 37aa84df42
feat: @W-22444026@ Introducing Core Skills, Datacloud Skills, Industries and Utility Skills. (#268)
* Migrating Core Salesforce Skills

* Updating pr comments

* updat reference

* Updating a skill

* Migrating Datacloud skills

* Migrating Industries cloud skills

* Validating - skills fixing

---------

Co-authored-by: Sandip Kumar Yadav <sandipkumar.yadav+sfemu@salesforce.com>
2026-05-14 19:32:15 +05:30

283 lines
9.1 KiB
JavaScript

/**
* 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<string, any>} State container */
const state = new Map();
/** @type {Map<string, Array<Function>>} Subscribers by key */
const subscribers = new Map();
/** @type {Map<string, any>} 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<string>} 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
};