UNPKG

state-management-utilities

Version:
260 lines (259 loc) 7.82 kB
import { produce } from "immer"; /** * The `StateManagerCenter` class is a singleton that manages multiple state managers, * logs state changes, and provides methods for state hydration and dehydration. */ class StateManagerCenter { _cacheRefs = {}; /** * A record of state managers identified by unique IDs. */ _stateManagers = {}; /** * An array of state manager center records. */ _records = []; /** * A record of the current states identified by unique IDs. */ _currentStates = {}; /** * A flag indicating whether logging is enabled. */ _isEnabledLog = true; /** * A flag indicating whether cloning is disabled. */ _disableCloning = false; /** * An optional callback function for logging. */ _onLog; /** * A flag indicating whether hydration is in progress. */ _isHydration = false; /** * Gets the hydration status. */ get isHydration() { return this._isHydration; } /** * The singleton instance of `StateManagerCenter`. */ static _instance; /** * Private constructor to prevent direct instantiation. */ constructor() { } /** * Gets the cloning disable status. */ get disableCloning() { return this._disableCloning; } /** * Sets the cloning disable status. */ set disableCloning(value) { this._disableCloning = value; } /** * Gets the singleton instance of `StateManagerCenter`. */ static get instance() { if (!StateManagerCenter._instance) { StateManagerCenter._instance = new StateManagerCenter(); } return StateManagerCenter._instance; } /** * Gets the current states. */ get currentStates() { return this._currentStates; } /** * Gets the logging enable status. */ get enableLog() { return this._isEnabledLog; } /** * Sets the logging enable status. */ set enableLog(value) { if (value === this._isEnabledLog) return; this._isEnabledLog = value; if (!value) { this._records = []; } this._onLog?.({ uid: "TOGGLED-STATUS", state: { enable: value } }).catch(console.error); } /** * Returns the state manager center records. * These records can be sent to the server for monitoring and debugging purposes. * * @returns The state manager center records. */ get records() { return this._records; } /** * Sets the current records to the provided records. * This method can be used for monitoring and debugging purposes. * * @param records - The records to be set. */ set records(records) { this._records = records; this._onLog?.({ uid: "IMPORTED", state: undefined }).catch(console.error); } /** * Gets the state manager center records in reverse order. */ async getReverseRecords() { return this._records?.slice().reverse() ?? []; } /** * Logs a state change. */ _log({ uid, state }) { if (!this.enableLog) return this; this._currentStates[uid] = state; const timestamp = Date.now(); const number = ++counter; (async () => { this._records = produce(this._records, (draft) => { draft.push({ updatedUID: uid, states: { ...this._currentStates }, timestamp, number, }); }); this._onLog?.({ uid, state })?.catch(console.error); })().catch(console.error); return this; } /** * Registers a state manager with a unique ID. */ _register({ uid, stateManager, }) { if (this._stateManagers[uid]) throw new Error(`UID "${uid}" already is registered in the "State Manager Center".`); this._stateManagers[uid] = stateManager; return this; } /** * Applies the given states to the related state managers. * * @param {CenterRecordType} record - An object containing the states to be applied. * @returns {this} The current instance of the class. */ apply({ states }) { try { for (const uid in states) { if (this._stateManagers[uid]) this._stateManagers[uid].value = states[uid]; } } catch (error) { /* istanbul ignore next */ console.error(`Error occurred while applying the states in the "State Manager Center".\n`, error); } return this; } /** * Initializes the hydration process. */ _initializeHydration() { this._isHydration = true; this.enableLog = true; this._records = []; this._currentStates = {}; return this; } /** * Hydrates the state managers with the provided entities. * @param entities - The entities to be hydrated. * @returns The hydrated states. * @throws An error if the entity does not have a `hydrated` property. */ get hydrated() { return { generate: async (...entries) => await this._generateHydrated({ entries }), config: ({ initial }) => ({ generate: async (...entries) => await this._generateHydrated({ entries, initial }), }), }; } async _generateHydrated({ entries, initial, }) { this._initializeHydration(); const updaters = await Promise.all(entries); const timestamp = Date.now(); const hydrated = initial ?? { data: {}, timestamp, id: `${timestamp}`, }; const values = []; updaters.forEach(({ update, value }) => { update(hydrated.data); values.push(value); }); return Object.freeze({ hydrated, values }); } _registerCacheRef({ uid, ref }) { this._cacheRefs[uid] = ref; return this; } /** * Dehydrates the state managers with the provided states. * @param states - The states to be dehydrated. */ async dehydrate(hydrated) { if (!hydrated) return; for (const uid in hydrated.data) { const data = hydrated.data[uid]; data.timestamp = data.timestamp ? data.timestamp : hydrated.timestamp; this._updateCache(data); } } async _updateCache({ value, hash, cacheRef, timestamp, }) { if (!hash || !cacheRef || !timestamp) return; const ref = this._cacheRefs[cacheRef]; const { updatedAt } = (await ref?._getCache?.(hash)) ?? {}; if (updatedAt && updatedAt >= timestamp) return; ref?._setCache?.(hash, { data: value, updatedAt: timestamp }); } /** * Registers a callback function for logging. * If a callback is already registered, an error will be thrown. * @param callback - The callback function to be registered. * @returns The `StateManagerCenter` instance. * @throws An error if a callback is already registered. */ onLog(callback) { if (this._onLog && callback) throw new Error(`"onLog" callback is already registered in the "State Manager Center".`); this._onLog = callback; return this; } /** * Clears the state manager center records. */ clearRecords() { this._records = []; this._onLog?.({ uid: "CLEARED", state: undefined }).catch(console.error); return this; } } let counter = 0; export const center = StateManagerCenter.instance;