UNPKG

@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

777 lines (667 loc) 22.7 kB
const { MongoClient } = require('mongodb'); const EventEmitter = require('events'); class AdvancedMongoConnector extends EventEmitter { constructor() { super(); this.client = null; this.db = null; this.connected = false; this.config = null; this.activeWatchers = new Map(); // collection -> { callbacks: Set, options } this.changeStreams = new Map(); // collection -> changeStream this.lastKnownState = new Map(); // collection -> lastData this.metrics = { connections: 0, writesHandled: 0, readsHandled: 0, changesProcessed: 0, errorsHandled: 0, activeWatchers: 0, activeStreams: 0 }; this.healthCheckInterval = null; this.reconnectAttempts = 0; this.maxReconnectAttempts = 10; this.collectionsWithIndexes = new Set(); // Track collections with indexes } async connect(config) { if (this.connected) { console.log('MongoDB already connected'); return; } this.config = config; const connectionString = this.buildConnectionString(config); try { console.log('🔄 Connecting to MongoDB...'); this.client = new MongoClient(connectionString, { maxPoolSize: config.maxConnections || 50, serverSelectionTimeoutMS: 5000, socketTimeoutMS: 45000, useUnifiedTopology: true }); await this.client.connect(); this.db = this.client.db(config.database); this.connected = true; this.reconnectAttempts = 0; console.log('✅ MongoDB connected successfully'); // Set up connection monitoring this.setupConnectionMonitoring(); // Start health checks this.startHealthChecks(); this.emit('connected'); } catch (error) { console.error('❌ MongoDB connection failed:', error.message); this.emit('error', error); throw error; } } buildConnectionString(config) { const { host, port, user, password, database } = config; if (user && password) { return `mongodb://${user}:${password}@${host}:${port}/${database}?authSource=admin`; } else { return `mongodb://${host}:${port}/${database}`; } } setupConnectionMonitoring() { this.client.on('serverHeartbeatSucceeded', () => { this.metrics.connections++; }); this.client.on('serverHeartbeatFailed', async (error) => { console.error('MongoDB heartbeat failed:', error); await this.handleConnectionError(error); }); this.client.on('close', async () => { console.log('MongoDB connection closed'); this.connected = false; await this.handleDisconnection(); }); } async handleConnectionError(error) { this.metrics.errorsHandled++; this.emit('error', error); if (this.reconnectAttempts < this.maxReconnectAttempts) { this.reconnectAttempts++; console.log(`🔄 Attempting to reconnect (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`); setTimeout(async () => { try { await this.connect(this.config); await this.restoreWatchers(); } catch (reconnectError) { console.error('Reconnection failed:', reconnectError.message); } }, 1000 * this.reconnectAttempts); } else { console.error('Max reconnection attempts reached'); this.emit('maxReconnectAttemptsReached'); } } async handleDisconnection() { this.connected = false; this.emit('disconnected'); // Clear change streams for (const [collection, changeStream] of this.changeStreams) { try { await changeStream.close(); } catch (error) { console.error(`Error closing change stream for ${collection}:`, error); } } this.changeStreams.clear(); } async restoreWatchers() { console.log('🔄 Restoring active watchers...'); for (const [collection, watcherInfo] of this.activeWatchers) { try { await this.startRealTimeWatch(collection, watcherInfo.callback, watcherInfo.options); console.log(`✅ Restored watcher for collection: ${collection}`); } catch (error) { console.error(`❌ Failed to restore watcher for collection ${collection}:`, error); } } } startHealthChecks() { if (this.healthCheckInterval) { clearInterval(this.healthCheckInterval); } this.healthCheckInterval = setInterval(async () => { try { await this.db.admin().ping(); this.emit('healthCheck', { status: 'healthy', metrics: this.getMetrics() }); } catch (error) { console.error('Health check failed:', error); this.emit('healthCheck', { status: 'unhealthy', error: error.message }); } }, 30000); // Every 30 seconds } // 🆕 NEW: Write data to specific collection async writeData(collection, data, options = {}) { if (!this.connected) { throw new Error('MongoDB not connected'); } try { const coll = this.db.collection(collection); const document = { ...data, _createdAt: new Date(), _updatedAt: new Date(), _ttl: options.ttl ? new Date(Date.now() + options.ttl * 1000) : null, _tags: options.tags || [], _metadata: options.metadata || {} }; // Create indexes automatically for new collections if (!this.collectionsWithIndexes.has(collection)) { try { await this.setupOptimizedIndexes(collection); this.collectionsWithIndexes.add(collection); } catch (error) { console.log(`Index creation for ${collection} will be retried later`); } } // Use transactions for consistency (only if supported) const useTransactions = this.config.useChangeStreams !== false; let result; if (useTransactions) { try { const session = this.client.startSession(); try { await session.withTransaction(async () => { result = await coll.insertOne(document, { session }); }); } finally { await session.endSession(); } } catch (error) { // Fallback to non-transactional operation if transactions not supported if (error.code === 20) { console.log('Transactions not supported, using non-transactional operation'); result = await coll.insertOne(document); } else { throw error; } } } else { // Non-transactional operation for local MongoDB result = await coll.insertOne(document); } this.metrics.writesHandled++; return { success: true, collection: collection, insertedId: result.insertedId, timestamp: document._createdAt }; } catch (error) { console.error('MongoDB write error:', error.message); this.metrics.errorsHandled++; throw error; } } // 🆕 NEW: Update data in specific collection with query async updateData(collection, query, updateData, options = {}) { if (!this.connected) { throw new Error('MongoDB not connected'); } try { const coll = this.db.collection(collection); // Prepare update document const updateDocument = { $set: { ...updateData, _updatedAt: new Date(), _ttl: options.ttl ? new Date(Date.now() + options.ttl * 1000) : null, _tags: options.tags || [], _metadata: options.metadata || {} } }; // Use transactions for consistency (only if supported) const useTransactions = this.config.useChangeStreams !== false; let result; if (useTransactions) { try { const session = this.client.startSession(); try { await session.withTransaction(async () => { result = await coll.updateOne( query, updateDocument, { session } ); }); } finally { await session.endSession(); } } catch (error) { // Fallback to non-transactional update console.log('Transaction failed, falling back to regular update'); result = await coll.updateOne( query, updateDocument ); } } else { result = await coll.updateOne( query, updateDocument ); } this.metrics.writesHandled++; return { success: true, collection: collection, found: result.matchedCount > 0, updated: result.modifiedCount > 0, timestamp: new Date(), query: query }; } catch (error) { console.error('MongoDB update error:', error.message); this.metrics.errorsHandled++; throw error; } } // 🆕 NEW: Read data from specific collection async readData(collection, query = {}, options = {}) { if (!this.connected) { throw new Error('MongoDB not connected'); } try { const coll = this.db.collection(collection); const result = await coll.findOne( query, { readPreference: 'primaryPreferred', ...options } ); if (!result) return null; // Check TTL if (result._ttl && new Date() > result._ttl) { await this.deleteData(collection, query); return null; } return { collection: collection, data: result, timestamp: result._createdAt, lastModified: result._updatedAt, tags: result._tags || [], metadata: result._metadata || {} }; } catch (error) { console.error('MongoDB read error:', error.message); throw error; } } // 🆕 NEW: Start real-time watch for specific collection async startRealTimeWatch(collection, callback, options = {}) { if (!this.connected) { throw new Error('MongoDB not connected'); } try { // Store watcher info for reconnection this.activeWatchers.set(collection, { callback, options }); // Check if change streams are enabled in config const useChangeStreams = this.config.useChangeStreams !== false; if (useChangeStreams) { return await this.startChangeStreamWatch(collection, callback, options); } else { return await this.startPollingWatch(collection, callback, options); } } catch (error) { console.error(`Failed to start real-time watch for collection ${collection}:`, error); this.activeWatchers.delete(collection); // If change streams fail, fallback to polling if (this.config.useChangeStreams !== false) { console.log(`Falling back to polling for collection: ${collection}`); return await this.startPollingWatch(collection, callback, options); } throw error; } } async startChangeStreamWatch(collection, callback, options = {}) { const coll = this.db.collection(collection); // Create change stream with advanced options const pipeline = [ { $match: { operationType: { $in: ['insert', 'update', 'replace', 'delete'] } } } ]; const changeStreamOptions = { fullDocument: 'updateLookup', resumeAfter: options.resumeToken, startAtOperationTime: options.startAtOperationTime, maxAwaitTimeMS: 1000, batchSize: 1 }; const changeStream = coll.watch(pipeline, changeStreamOptions); this.changeStreams.set(collection, changeStream); this.metrics.activeStreams++; // Handle change events changeStream.on('change', (change) => { try { this.metrics.changesProcessed++; const changeType = this.mapOperationType(change.operationType); const data = change.fullDocument || null; // Create metadata object const meta = { collection: collection, changeType: changeType, timestamp: new Date(), operationType: change.operationType, resumeToken: change._id, clusterTime: change.clusterTime, txnNumber: change.txnNumber, lsid: change.lsid, source: 'changeStream' }; callback(data, meta); this.emit('change', { collection, data, meta }); } catch (error) { console.error(`Error processing change for collection ${collection}:`, error); this.emit('error', error); } }); // Handle change stream errors changeStream.on('error', (error) => { console.error(`Change stream error for collection ${collection}:`, error); this.metrics.errorsHandled++; // If change streams aren't supported, fallback to polling if (error.code === 40573) { console.log(`Change streams not supported, falling back to polling for collection: ${collection}`); this.changeStreams.delete(collection); this.startPollingWatch(collection, callback, options); return; } // Attempt to restart the change stream setTimeout(() => { this.restartChangeStream(collection); }, 1000); }); changeStream.on('close', () => { console.log(`Change stream closed for collection: ${collection}`); this.changeStreams.delete(collection); this.metrics.activeStreams--; }); // Get initial data const initialData = await this.readData(collection); if (initialData) { const meta = { collection: collection, changeType: 'initial', timestamp: new Date(), source: 'changeStream' }; callback(initialData.data, meta); } console.log(`Real-time change stream started for collection: ${collection}`); return changeStream; } async startPollingWatch(collection, callback, options = {}) { const pollingInterval = this.config.pollingInterval || 2000; let lastModified = null; // Store polling timer const pollTimer = setInterval(async () => { try { const currentData = await this.readData(collection); if (!currentData) { // Data was deleted if (lastModified !== null) { const meta = { collection: collection, changeType: 'deleted', timestamp: new Date(), source: 'polling' }; callback(null, meta); this.emit('change', { collection, data: null, meta }); lastModified = null; } return; } const currentModified = currentData.lastModified?.getTime(); if (lastModified === null) { // Initial data const meta = { collection: collection, changeType: 'initial', timestamp: new Date(), source: 'polling' }; callback(currentData.data, meta); this.emit('change', { collection, data: currentData.data, meta }); lastModified = currentModified; } else if (currentModified > lastModified) { // Data changed const meta = { collection: collection, changeType: 'updated', timestamp: new Date(), source: 'polling' }; callback(currentData.data, meta); this.emit('change', { collection, data: currentData.data, meta }); lastModified = currentModified; } } catch (error) { console.error(`Error in polling for collection ${collection}:`, error); } }, pollingInterval); // Store timer for cleanup this.activeWatchers.set(collection, { callback, options, timer: pollTimer }); console.log(`Polling started for collection: ${collection} (${pollingInterval}ms)`); return { collection, timer: pollTimer }; } async restartChangeStream(collection) { const watcherInfo = this.activeWatchers.get(collection); if (watcherInfo) { try { await this.startChangeStreamWatch(collection, watcherInfo.callback, watcherInfo.options); console.log(`✅ Restarted change stream for collection: ${collection}`); } catch (error) { console.error(`❌ Failed to restart change stream for collection ${collection}:`, error); } } } mapOperationType(operationType) { const mapping = { 'insert': 'created', 'update': 'updated', 'replace': 'updated', 'delete': 'deleted', 'drop': 'deleted', 'rename': 'updated', 'dropDatabase': 'deleted' }; return mapping[operationType] || 'updated'; } async stopWatch(collection) { // Stop change stream const changeStream = this.changeStreams.get(collection); if (changeStream) { try { await changeStream.close(); this.changeStreams.delete(collection); this.metrics.activeStreams--; } catch (error) { console.error(`Error closing change stream for collection ${collection}:`, error); } } // Stop polling timer const watcherInfo = this.activeWatchers.get(collection); if (watcherInfo && watcherInfo.timer) { clearInterval(watcherInfo.timer); } this.activeWatchers.delete(collection); this.lastKnownState.delete(collection); this.metrics.activeWatchers--; console.log(`Stopped watching collection: ${collection}`); } // 🆕 NEW: Query data from specific collection async queryData(collection, query = {}, options = {}) { if (!this.connected) { throw new Error('MongoDB not connected'); } try { const coll = this.db.collection(collection); // Build MongoDB query const mongoQuery = this.buildMongoQuery(query); const projection = options.fields ? this.buildProjection(options.fields) : {}; const cursor = coll.find(mongoQuery, { projection, sort: options.sort || { _createdAt: -1 }, limit: options.limit || 100, skip: options.skip || 0 }); const results = await cursor.toArray(); return results.map(doc => ({ collection: collection, data: doc, timestamp: doc._createdAt, lastModified: doc._updatedAt, tags: doc._tags || [], metadata: doc._metadata || {} })); } catch (error) { console.error('MongoDB query error:', error.message); throw error; } } buildMongoQuery(query) { const mongoQuery = {}; // Handle different query types if (query.where) { Object.assign(mongoQuery, query.where); } if (query.text) { mongoQuery.$text = { $search: query.text }; } if (query.geoNear) { // Handle geospatial queries return { $geoNear: { near: query.geoNear.near, distanceField: query.geoNear.distanceField, maxDistance: query.geoNear.maxDistance, spherical: true } }; } return mongoQuery; } buildProjection(fields) { const projection = {}; if (Array.isArray(fields)) { fields.forEach(field => { projection[field] = 1; }); } else if (typeof fields === 'object') { Object.assign(projection, fields); } return projection; } // 🆕 NEW: Get all collections async getAllCollections() { if (!this.connected) { throw new Error('MongoDB not connected'); } try { const collections = await this.db.listCollections().toArray(); return collections.map(col => col.name); } catch (error) { console.error('MongoDB get collections error:', error.message); throw error; } } // 🆕 NEW: Delete data from specific collection async deleteData(collection, query = {}) { if (!this.connected) { throw new Error('MongoDB not connected'); } try { const coll = this.db.collection(collection); const result = await coll.deleteOne(query); return { success: true, collection: collection, deleted: result.deletedCount > 0, timestamp: new Date() }; } catch (error) { console.error('MongoDB delete error:', error.message); this.metrics.errorsHandled++; throw error; } } // 🆕 NEW: Create indexes for specific collection async createIndex(collection, indexSpec, options = {}) { if (!this.connected) { throw new Error('MongoDB not connected'); } try { const coll = this.db.collection(collection); const result = await coll.createIndex(indexSpec, options); console.log(`✅ Created index for collection ${collection}:`, result); return result; } catch (error) { console.error(`❌ Failed to create index for collection ${collection}:`, error.message); throw error; } } // 🆕 NEW: Setup optimized indexes for specific collection async setupOptimizedIndexes(collection) { try { // Create common indexes await this.createIndex(collection, { _createdAt: -1 }); await this.createIndex(collection, { _updatedAt: -1 }); await this.createIndex(collection, { _ttl: 1 }, { expireAfterSeconds: 0 }); console.log(`✅ Optimized indexes created for collection: ${collection}`); } catch (error) { console.error(`❌ Failed to setup indexes for collection ${collection}:`, error.message); } } getMetrics() { return { ...this.metrics, connected: this.connected, activeWatchers: this.activeWatchers.size, activeStreams: this.changeStreams.size, reconnectAttempts: this.reconnectAttempts }; } async disconnect() { if (this.healthCheckInterval) { clearInterval(this.healthCheckInterval); } // Stop all watchers for (const [collection] of this.activeWatchers) { await this.stopWatch(collection); } // Close all change streams for (const [collection, changeStream] of this.changeStreams) { try { await changeStream.close(); } catch (error) { console.error(`Error closing change stream for ${collection}:`, error); } } if (this.client) { await this.client.close(); } this.connected = false; this.activeWatchers.clear(); this.changeStreams.clear(); this.lastKnownState.clear(); console.log('MongoDB disconnected'); } isConnected() { return this.connected; } } module.exports = AdvancedMongoConnector;