@krunal_tarale-5/ultimate-streaming-package
Version:
🚀 Ultimate Real-Time Streaming Package v2.1.9 - Multi-Platform, Multi-Collection Architecture with Native MongoDB & MySQL Support, 99.96% Performance Improvement. Enterprise-grade real-time data streaming with Socket.IO integration, dynamic schema evolut
276 lines (227 loc) • 7.22 kB
JavaScript
const EventEmitter = require('events');
class HeartbeatSystem extends EventEmitter {
constructor(dbConnector, config = {}) {
super();
this.dbConnector = dbConnector;
this.pollingInterval = config.pollingInterval || 2000; // Default 2 seconds
this.debug = config.debug || false;
// Internal state
this.isPolling = false;
this.intervalId = null;
this.cache = new Map(); // Store last known states
this.listeners = new Map(); // Store key -> callback mappings
this.lastPollTime = null;
this.log('HeartbeatSystem initialized');
}
log(message) {
if (this.debug) {
console.log(`[HeartbeatSystem] ${new Date().toISOString()}: ${message}`);
}
}
// Register a listener for a specific key
addListener(key, callback) {
if (typeof callback !== 'function') {
throw new Error('Callback must be a function');
}
if (!this.listeners.has(key)) {
this.listeners.set(key, new Set());
}
this.listeners.get(key).add(callback);
this.log(`Listener added for key: ${key}`);
// Start polling if not already started
if (!this.isPolling) {
this.startPolling();
}
// Return unsubscribe function
return () => this.removeListener(key, callback);
}
// Remove a specific listener for a key
removeListener(key, callback) {
if (this.listeners.has(key)) {
this.listeners.get(key).delete(callback);
// Remove the key if no more listeners
if (this.listeners.get(key).size === 0) {
this.listeners.delete(key);
this.cache.delete(key); // Also remove from cache
this.log(`All listeners removed for key: ${key}`);
}
}
// Stop polling if no more listeners
if (this.listeners.size === 0) {
this.stopPolling();
}
}
// Remove all listeners for a key
removeAllListeners(key) {
if (key) {
this.listeners.delete(key);
this.cache.delete(key);
this.log(`All listeners removed for key: ${key}`);
} else {
this.listeners.clear();
this.cache.clear();
this.log('All listeners removed');
}
if (this.listeners.size === 0) {
this.stopPolling();
}
}
// Start the polling mechanism
startPolling() {
if (this.isPolling) {
return;
}
if (!this.dbConnector.isConnected()) {
throw new Error('Database not connected');
}
this.isPolling = true;
this.log(`Starting polling with interval: ${this.pollingInterval}ms`);
this.intervalId = setInterval(async () => {
await this.poll();
}, this.pollingInterval);
// Initial poll
this.poll();
}
// Stop the polling mechanism
stopPolling() {
if (!this.isPolling) {
return;
}
this.isPolling = false;
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
this.log('Polling stopped');
}
// Main polling function
async poll() {
if (!this.dbConnector.isConnected()) {
this.log('Database disconnected, stopping polling');
this.stopPolling();
return;
}
const startTime = Date.now();
this.lastPollTime = new Date();
try {
// Get all keys that we're listening to
const keysToCheck = Array.from(this.listeners.keys());
if (keysToCheck.length === 0) {
this.log('No keys to check, stopping polling');
this.stopPolling();
return;
}
// Check each key for changes
const promises = keysToCheck.map(key => this.checkKeyForChanges(key));
await Promise.all(promises);
const duration = Date.now() - startTime;
this.log(`Poll completed in ${duration}ms, checked ${keysToCheck.length} keys`);
} catch (error) {
console.error('Poll error:', error.message);
this.emit('error', error);
}
}
// Check a specific key for changes
async checkKeyForChanges(key) {
try {
const currentData = await this.dbConnector.readData(key);
const cachedData = this.cache.get(key);
// If no current data and no cached data, nothing to do
if (!currentData && !cachedData) {
return;
}
// If data was deleted
if (!currentData && cachedData) {
this.cache.delete(key);
this.notifyListeners(key, null, 'deleted');
return;
}
// If new data (first time or previously deleted)
if (currentData && !cachedData) {
this.cache.set(key, {
data: currentData.data,
lastModified: currentData.lastModified
});
this.notifyListeners(key, currentData.data, 'created');
return;
}
// Check if data has changed
if (this.hasDataChanged(cachedData, currentData)) {
this.cache.set(key, {
data: currentData.data,
lastModified: currentData.lastModified
});
this.notifyListeners(key, currentData.data, 'updated');
}
} catch (error) {
console.error(`Error checking key ${key}:`, error.message);
}
}
// Check if data has changed
hasDataChanged(cachedData, currentData) {
if (!cachedData || !currentData) {
return false;
}
// Compare by lastModified timestamp first
if (cachedData.lastModified && currentData.lastModified) {
const cachedTime = new Date(cachedData.lastModified).getTime();
const currentTime = new Date(currentData.lastModified).getTime();
if (currentTime > cachedTime) {
return true;
}
}
// Fallback to deep comparison of data
return JSON.stringify(cachedData.data) !== JSON.stringify(currentData.data);
}
// Notify all listeners for a key
notifyListeners(key, data, changeType) {
const callbacks = this.listeners.get(key);
if (!callbacks || callbacks.size === 0) {
return;
}
this.log(`Notifying ${callbacks.size} listeners for key: ${key} (${changeType})`);
callbacks.forEach(callback => {
try {
callback(data, {
key: key,
changeType: changeType,
timestamp: new Date()
});
} catch (error) {
console.error(`Error in listener callback for key ${key}:`, error.message);
}
});
}
// Set polling interval
setPollingInterval(interval) {
if (typeof interval !== 'number' || interval < 100) {
throw new Error('Polling interval must be a number >= 100ms');
}
this.pollingInterval = interval;
this.log(`Polling interval set to: ${interval}ms`);
// Restart polling with new interval if currently polling
if (this.isPolling) {
this.stopPolling();
this.startPolling();
}
}
// Get current status
getStatus() {
return {
isPolling: this.isPolling,
pollingInterval: this.pollingInterval,
listenerCount: this.listeners.size,
cacheSize: this.cache.size,
lastPollTime: this.lastPollTime,
watchedKeys: Array.from(this.listeners.keys())
};
}
// Cleanup
destroy() {
this.stopPolling();
this.removeAllListeners();
this.removeAllListeners(); // EventEmitter listeners
this.log('HeartbeatSystem destroyed');
}
}
module.exports = HeartbeatSystem;