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
JavaScript
/**
* 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;
}