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.
296 lines (222 loc) • 8.58 kB
Markdown
# jw-gate
A reactive coordination library that manages multiple conditions through an event-driven "Gate" with independent "locks". Perfect for two key scenarios:
1. **UI State Management** - Track multiple async operations and show progress as each completes
2. **Dynamic Condition Monitoring** - React to conditions that change over time (IoT, sensors, resources)
Unlike promise-based coordination, jw-gate lets you respond immediately to individual condition changes rather than waiting for everything to complete at once.
## Why jw-gate?
**For UI Progress Tracking**: `Promise.all()` only tells you when everything is done - you can't show individual completions. jw-gate lets you update your UI as each operation finishes.
**For Dynamic Conditions**: Traditional promises are one-shot, but real-world conditions change over time. jw-gate provides reactive coordination that responds immediately when conditions shift.
## Features
- 🎯 Track multiple async operations with individual progress updates
- 🚪 Reactive coordination of changing conditions over time
- 🔄 Event-driven architecture - respond immediately to state changes
- ⏱️ Automatic timeout support for temporary conditions
- 🔍 Comprehensive state monitoring and introspection
- 🛡️ Error handling through events (no crashes)
- 🌐 Works in both Node.js and browser environments
- 📦 Zero dependencies
## Installation
```bash
npm install jw-gate
```
## Quick Start
### UI Progress Tracking
```javascript
const Gate = require('jw-gate');
// Track multiple file uploads
const uploadGate = new Gate(['file1', 'file2', 'file3'], true);
// Update UI when all files are complete
uploadGate.on('unlocked', () => {
updateUI('All uploads complete! 🎉');
});
// Start uploads and update progress individually
uploadFile1().then(() => {
updateUI('File 1 complete ✓');
uploadGate.setLock('file1', false);
});
uploadFile2().then(() => {
updateUI('File 2 complete ✓');
uploadGate.setLock('file2', false);
});
uploadFile3().then(() => {
updateUI('File 3 complete ✓');
uploadGate.setLock('file3', false);
});
```
### Dynamic Condition Monitoring
```javascript
// Monitor changing conditions
const systemGate = new Gate(['network', 'power', 'sensors'], true);
// React immediately when all conditions are met
systemGate.on('unlocked', () => {
console.log('System ready - starting operation');
startOperation();
});
systemGate.on('locked', () => {
console.log('Conditions changed - pausing operation');
pauseOperation();
});
// Conditions change independently over time
networkMonitor.on('connected', () => systemGate.setLock('network', false));
networkMonitor.on('disconnected', () => systemGate.setLock('network', true));
powerMonitor.on('stable', () => systemGate.setLock('power', false));
powerMonitor.on('unstable', () => systemGate.setLock('power', true));
```
## Common Examples
### UI Progress Tracking
#### File Upload Dashboard
```javascript
const uploadGate = new Gate(['validation', 'upload', 'processing'], true);
// Show progress as each step completes
uploadGate.on('unlocked', () => {
showMessage('Upload complete! File is ready to use.');
enableDownloadButton();
});
// Validate file
validateFile(file).then(valid => {
if (valid) {
showProgress('Validation complete ✓');
uploadGate.setLock('validation', false);
}
});
// Upload to server
uploadToServer(file).then(() => {
showProgress('Upload complete ✓');
uploadGate.setLock('upload', false);
});
// Server processing
processOnServer(file).then(() => {
showProgress('Processing complete ✓');
uploadGate.setLock('processing', false);
});
```
#### Multi-Step Form Validation
```javascript
const formGate = new Gate(['email', 'password', 'terms'], true);
formGate.on('unlocked', () => {
enableSubmitButton();
showMessage('Form ready for submission');
});
formGate.on('locked', () => {
disableSubmitButton();
});
emailField.on('validated', () => {
showCheckmark('email');
formGate.setLock('email', false);
});
passwordField.on('validated', () => {
showCheckmark('password');
formGate.setLock('password', false);
});
termsCheckbox.on('checked', () => {
showCheckmark('terms');
formGate.setLock('terms', false);
});
```
### Dynamic Condition Monitoring
#### Smart Device Control
```javascript
const deviceGate = new Gate(['safety', 'network', 'power'], true);
deviceGate.on('unlocked', () => {
console.log('Device activated - all systems go');
device.start();
statusLight.setGreen();
});
deviceGate.on('locked', () => {
console.log('Safety conditions changed - device stopped');
device.emergencyStop();
statusLight.setRed();
});
// Sensors update conditions independently
safetySystem.on('safe', () => deviceGate.setLock('safety', false));
safetySystem.on('unsafe', () => deviceGate.setLock('safety', true));
networkMonitor.on('connected', () => deviceGate.setLock('network', false));
networkMonitor.on('disconnected', () => deviceGate.setLock('network', true));
powerMonitor.on('stable', () => deviceGate.setLock('power', false));
powerMonitor.on('fluctuation', () => deviceGate.setLock('power', true));
```
#### Resource-Aware Processing
```javascript
const processingGate = new Gate(['cpu', 'memory', 'disk'], true);
processingGate.on('unlocked', () => {
console.log('Resources available - starting batch job');
startBatchProcessing();
});
processingGate.on('locked', () => {
console.log('Resource constraints - pausing batch job');
pauseBatchProcessing();
});
// Resource monitors update conditions
cpuMonitor.on('available', () => processingGate.setLock('cpu', false));
cpuMonitor.on('busy', () => processingGate.setLock('cpu', true));
memoryMonitor.on('sufficient', () => processingGate.setLock('memory', false));
memoryMonitor.on('low', () => processingGate.setLock('memory', true));
diskMonitor.on('space', () => processingGate.setLock('disk', false));
diskMonitor.on('full', () => processingGate.setLock('disk', true));
```
## API Reference
### Constructor
```javascript
new Gate(lockNames, initialState = false)
```
- `lockNames`: Array of strings representing lock names
- `initialState`: Boolean indicating initial state of locks (default: false = unlocked)
### Event Management
```javascript
gate.on(event, callback) // Register event listener
gate.off(event, callback) // Remove event listener
```
**Events:**
- `'locked'`: Emitted when gate becomes locked (one or more locks engaged)
- `'unlocked'`: Emitted when gate becomes unlocked (all locks disengaged)
- `'error'`: Emitted when an error occurs
### Lock Management
```javascript
gate.setLock(lockName, state) // Set lock state (true = locked)
gate.setLockWithTimeout(lock, state, ms) // Set lock with automatic timeout
gate.resetAll(state) // Set all locks to same state
```
### State Inspection
```javascript
gate.getState() // Get complete state object
gate.isUnlocked() // Check if gate is currently unlocked
gate.getLockedCount() // Count of currently locked locks
gate.getTotalLocks() // Total number of locks
```
## State Object
The `getState()` method returns:
```javascript
{
state: 'locked' | 'unlocked',
locks: {
[lockName: string]: boolean // true = locked, false = unlocked
},
isLocked: boolean
}
```
## Error Handling
All errors are emitted as events rather than thrown, preventing crashes:
```javascript
gate.on('error', (errorMessage) => {
console.error('Gate error:', errorMessage);
// Handle error appropriately
});
```
## Browser Support
Works in both Node.js and browser environments:
```html
<script src="jw-gate.js"></script>
<script>
const gate = new Gate(['condition1', 'condition2']);
// Use normally
</script>
```
## When NOT to Use jw-gate
- **Simple one-time coordination**: Use `Promise.all()` if you don't need individual progress updates
- **Single async operation**: Use `async/await` instead
- **Static boolean logic**: Use regular conditionals instead
jw-gate excels when you need **immediate feedback on individual condition changes** or **reactive coordination of changing conditions**.
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
## License
MIT