UNPKG

jw-gate

Version:

Creates a "gate" with "locks." When all locks are open, the gate is open, useful for dealing with semi-random async events.

183 lines (166 loc) 4.55 kB
/** * A Gate class that manages multiple locks for controlling async events. * Emits 'locked', 'unlocked', or 'error' events when state changes. */ class Gate { /** * Creates a new Gate instance * @param {string[]} lockNames - Array of lock names to create * @param {boolean} [initialState=false] - Initial state of all locks */ constructor(lockNames, initialState = false) { this._events = {}; this._locks = new Map(); this._state = initialState ? 'locked' : 'unlocked'; this._lockedCount = 0; if (!Array.isArray(lockNames)) { this._emit('error', "lockNames must be an array of strings"); return; } lockNames.forEach(lock => { this._locks.set(lock, initialState); if (initialState) this._lockedCount++; }); } /** * Register an event listener * @param {string} event - Event name ('locked', 'unlocked', or 'error') * @param {Function} callback - Function to call when event fires * @returns {boolean} Success status */ on(event, callback) { if (typeof callback !== 'function') { this._emit('error', "callback must be a function"); return false; } if (!this._events[event]) { this._events[event] = new Set(); } this._events[event].add(callback); return true; } /** * Remove an event listener * @param {string} event - Event name * @param {Function} callback - Function to remove * @returns {boolean} Success status */ off(event, callback) { if (!this._events[event]) { this._emit('error', `No listeners for event '${event}'`); return false; } return this._events[event].delete(callback); } /** * Set the state of a specific lock * @param {string} lock - Lock name * @param {boolean} state - New state (true = locked) * @returns {boolean} Success status */ setLock(lock, state) { if (!this._locks.has(lock)) { this._emit('error', `Lock '${lock}' does not exist`); return false; } if (this._locks.get(lock) === state) return true; if (state) { this._lockedCount++; } else { this._lockedCount--; } this._locks.set(lock, state); this._checkGate(); return true; } /** * Get the current state of all locks * @returns {Object} Current state object */ getState() { const locks = {}; this._locks.forEach((value, key) => { locks[key] = value; }); return { state: this._state, locks, isLocked: this._state === 'locked' }; } /** * Check if the gate is currently unlocked * @returns {boolean} */ isUnlocked() { return this._state === 'unlocked'; } /** * Private method to emit events * @private */ _emit(event, ...args) { const listeners = this._events[event]; if (listeners) { listeners.forEach(callback => { try { callback(...args); } catch (error) { this._emit('error', `Error in ${event} listener: ${error.message}`); } }); } } /** * Private method to check and update gate state * @private */ _checkGate() { const newState = this._lockedCount === 0 ? 'unlocked' : 'locked'; if (newState !== this._state) { this._state = newState; this._emit(newState); } } /** * Reset all locks to a specific state * @param {boolean} state - State to set all locks to */ resetAll(state) { this._locks.forEach((_, lock) => this.setLock(lock, state)); } /** * Get the count of currently locked locks * @returns {number} */ getLockedCount() { return this._lockedCount; } /** * Get the total number of locks * @returns {number} */ getTotalLocks() { return this._locks.size; } /** * Set a lock with automatic timeout * @param {string} lock - Lock name * @param {boolean} state - State to set * @param {number} timeout - Timeout in milliseconds */ setLockWithTimeout(lock, state, timeout) { this.setLock(lock, state); if (timeout > 0) { setTimeout(() => { this.setLock(lock, !state); }, timeout); } } } // Export for both Node.js and browser environments if (typeof module !== 'undefined' && module.exports) { module.exports = Gate; } else if (typeof window !== 'undefined') { window.Gate = Gate; }