Package Exports
- node-event-tracker
- node-event-tracker/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (node-event-tracker) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
Node Event Tracker
A robust, scalable event aggregation and throttling engine for Node.js.
This library provides a powerful way to manage high-frequency events, such as errors, security alerts, or user actions. Instead of handling every single event immediately, you can use flexible strategies to count, aggregate, and defer events, preventing alert fatigue and reducing system load.
✨ Core Features
- Pluggable Throttling Strategies: Comes with
SimpleCounter
(limit-based) andTokenBucket
(rate-based) strategies. Easily create your own for custom logic. - Pluggable Storage Backends: Includes
InMemoryAdapter
for single-process use and a high-performanceRedisAdapter
for distributed, multi-server applications. - Automatic & Manual Processing: Handle deferred events automatically with a background processor and callback, or manually poll and process them as needed.
- Dynamic Configuration: Update throttling rules for specific event streams on-the-fly without restarting your application.
- High Performance: Leverages atomic Redis operations via Lua scripts to ensure data consistency and prevent race conditions in distributed environments.
- Rich Observability: Emits detailed events (
immediate
,deferred
,ignored
,processed
) for robust monitoring and integration.
🚀 Installation
npm install node-event-tracker redis
🏁 Quick Start
Here's a basic example of tracking events. The tracker will allow 2 events, defer the 3rd, and ignore any subsequent events until the deferral period is over.
const EventTracker = require('node-event-tracker');
async function main() {
// Configure a tracker that defers after 2 events.
const tracker = new EventTracker({
limit: 2,
deferInterval: 5 * 1000, // Defer for 5 seconds
});
const CATEGORY = 'authentication';
const USER_ID = 'user-123';
console.log('--- Simulating an event flood ---');
// Attempts 1 & 2 are immediate
await tracker.trackEvent(CATEGORY, USER_ID);
console.log('Tracked event 1 (immediate)');
await tracker.trackEvent(CATEGORY, USER_ID);
console.log('Tracked event 2 (immediate)');
// Attempt 3 is deferred
const res3 = await tracker.trackEvent(CATEGORY, USER_ID);
console.log(`Tracked event 3 (${res3.type})`);
// Attempt 4 is ignored (already deferred)
const res4 = await tracker.trackEvent(CATEGORY, USER_ID);
console.log(`Tracked event 4 (${res4.type})`);
console.log('\nWaiting 6 seconds for deferral to expire...');
await new Promise(r => setTimeout(r, 6000));
// Manually process due events
const dueEvents = await tracker.processDeferredEvents();
if (dueEvents.length > 0) {
console.log(`\nProcessed ${dueEvents.length} due event(s).`);
console.log('Example due event:', dueEvents[0]);
// Manually delete the record after processing
await tracker.storage.delete(dueEvents[0].key);
}
// Tracking again will start a new count.
const res5 = await tracker.trackEvent(CATEGORY, USER_ID);
console.log(`\nTracked event 5 (${res5.type})`);
tracker.destroy();
}
main();
🛠️ Advanced Usage
Using Redis for Distributed Tracking
For use in a cluster of servers, the RedisAdapter
provides a shared, distributed state.
Important: The RedisAdapter
uses Lua scripts for atomic operations. You must configure your node-redis
client with the scripts provided by the adapter.
const { createClient } = require('redis');
const EventTracker = require('node-event-tracker');
const RedisAdapter = require('node-event-tracker/storage/RedisAdapter');
// 1. Import the script definitions from the adapter
const { scripts } = RedisAdapter;
async function runWithRedis() {
// 2. Create a redis client, loading the scripts
const redisClient = createClient({
database: 1, // Use a dedicated DB for safety
scripts, // Load the required scripts
});
await redisClient.connect();
await redisClient.flushDb(); // Clear for a clean run
// 3. Setup a processor to handle events automatically
const processor = async (events) => {
console.log(`\n[PROCESSOR] Auto-processing ${events.length} event(s).`);
};
// 4. Create the tracker with the RedisAdapter and processor
const tracker = new EventTracker({
storage: new RedisAdapter({ redisClient }),
limit: 2,
processor, // Set the callback for automatic processing
});
console.log('--- Simulating traffic with Redis backend ---');
await tracker.trackEvent('api_error', 'db_fail');
await tracker.trackEvent('api_error', 'db_fail');
await tracker.trackEvent('api_error', 'db_fail'); // This one will be deferred
console.log('\nWaiting for automatic processing...');
await new Promise(r => setTimeout(r, 20000)); // Wait for the default 10s interval
// Cleanup
await redisClient.quit();
tracker.destroy();
}
runWithRedis();
Using the Token Bucket Strategy
The TokenBucketStrategy
is ideal for rate-limiting that allows for occasional bursts of traffic.
const EventTracker = require('node-event-tracker');
const { TokenBucketStrategy } = EventTracker;
const tracker = new EventTracker({
strategy: new TokenBucketStrategy({
bucketSize: 10, // Allow 10 events in a burst
refillRate: 1, // Refill 1 token per second
}),
});
// Now, tracker.trackEvent(...) will use the token bucket rules.
Dynamic Configuration
You can change the throttling configuration for a specific event stream at runtime.
const tracker = new EventTracker({ limit: 5 });
// Initially, this event stream can have 5 events
await tracker.trackEvent('notifications', 'user-A');
// Now, update the limit for just this stream to 10
await tracker.updateConfig('notifications', 'user-A', { limit: 10 });
console.log('Configuration updated for notifications/user-A.');
📖 API Reference
new EventTracker(options)
Creates a new tracker instance.
Option | Type | Default | Description |
---|---|---|---|
limit |
number |
5 |
Default event count before deferring (for SimpleCounterStrategy ). |
deferInterval |
number |
3600000 |
Default time (ms) to defer events. (1 hour) |
expireTime |
number |
86400000 |
Default time (ms) an inactive event record is kept. (24 hours) |
maxKeys |
number |
0 |
Max unique keys to track. 0 for unlimited. |
storage |
BaseAdapter |
InMemoryAdapter |
The storage backend instance. |
strategy |
BaseStrategy |
SimpleCounterStrategy |
The throttling strategy instance. |
processor |
function |
null |
An async callback to automatically process due events. |
processingInterval |
number |
10000 |
How often (ms) the background processor runs. |
Key Methods
async trackEvent(category, id, details = {})
: Tracks an event. Returns{ type, data }
wheretype
is'immediate'
,'deferred'
, or'ignored'
.async processDeferredEvents()
: In manual mode (noprocessor
set), returns an array of due events. In automatic mode, this is called by the background loop.async getDeferredEvents()
: Returns an array of all currently deferred events, regardless of whether they are due.async updateConfig(category, id, newConfig)
: Atomically updates the configuration for a single event stream.destroy()
: Stops all background timers and cleans up resources.
Events
The EventTracker
is an EventEmitter
.
tracker.on('immediate', (record) => ...)
tracker.on('deferred', (record) => ...)
tracker.on('ignored', ({ reason, category, id }) => ...)
tracker.on('processed', (record) => ...)
tracker.on('error', (error) => ...)
🏗️ Architecture
The library is composed of three main pluggable components:
EventTracker
: The main engine that orchestrates everything.- Strategies (
BaseStrategy
): These classes contain the logic to decide if an event should beimmediate
,deferred
, orignored
. - Adapters (
BaseAdapter
): These classes handle the persistence of event records, either in memory or in a distributed store like Redis.
You can create your own custom strategies or storage adapters by extending the base classes.
🤝 Contributing
Contributions are welcome! Please feel free to submit a pull request.
- Fork the repository.
- Create your feature branch (
git checkout -b feature/my-new-feature
). - Commit your changes (
git commit -am 'Add some feature'
). - Push to the branch (
git push origin feature/my-new-feature
). - Open a new Pull Request.
Please ensure all tests pass (npm test
) before submitting.
📜 License
This project is licensed under the ISC License.