@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
782 lines (663 loc) • 23.6 kB
JavaScript
const mysql = require('mysql2/promise');
const EventEmitter = require('events');
class AdvancedMysqlConnector extends EventEmitter {
constructor() {
super();
this.pool = null;
this.connected = false;
this.config = null;
this.activeWatchers = new Map(); // table -> { callbacks: Set, options }
this.lastKnownState = new Map(); // table -> lastData
this.zongJi = null; // Binlog reader
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;
}
async connect(config) {
if (this.connected) {
console.log('MySQL already connected');
return;
}
this.config = config;
try {
console.log('🔄 Connecting to MySQL...');
// Create connection pool
this.pool = mysql.createPool({
host: config.host,
port: config.port || 3306,
user: config.user,
password: config.password,
database: config.database,
connectionLimit: config.maxConnections || 50,
charset: 'utf8mb4',
supportBigNumbers: true,
bigNumberStrings: true,
dateStrings: true
});
// Test connection
const connection = await this.pool.getConnection();
await connection.ping();
connection.release();
this.connected = true;
this.reconnectAttempts = 0;
console.log('✅ MySQL connected successfully');
// Set up connection monitoring
this.setupConnectionMonitoring();
// Start health checks
this.startHealthChecks();
// Setup binlog monitoring if enabled
if (config.useBinlog !== false) {
await this.setupBinlogMonitoring();
}
this.emit('connected');
} catch (error) {
console.error('❌ MySQL connection failed:', error.message);
this.emit('error', error);
throw error;
}
}
setupConnectionMonitoring() {
this.pool.on('connection', (connection) => {
this.metrics.connections++;
});
this.pool.on('error', async (error) => {
console.error('MySQL pool error:', error);
await this.handleConnectionError(error);
});
}
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');
// Close binlog reader
if (this.zongJi) {
try {
this.zongJi.stop();
this.zongJi = null;
} catch (error) {
console.error('Error closing binlog reader:', error);
}
}
}
async restoreWatchers() {
console.log('🔄 Restoring active watchers...');
for (const [table, watcherInfo] of this.activeWatchers) {
try {
await this.startRealTimeWatch(table, watcherInfo.callback, watcherInfo.options);
console.log(`✅ Restored watcher for table: ${table}`);
} catch (error) {
console.error(`❌ Failed to restore watcher for table ${table}:`, error);
}
}
}
startHealthChecks() {
if (this.healthCheckInterval) {
clearInterval(this.healthCheckInterval);
}
this.healthCheckInterval = setInterval(async () => {
try {
const connection = await this.pool.getConnection();
await connection.ping();
connection.release();
this.emit('healthCheck', {
status: 'healthy',
metrics: this.getMetrics(),
binlogActive: !!this.zongJi
});
} catch (error) {
console.error('Health check failed:', error);
this.emit('healthCheck', {
status: 'unhealthy',
error: error.message,
binlogActive: !!this.zongJi
});
}
}, 30000);
}
// 🆕 NEW: Write data to specific table
async writeData(table, data, options = {}) {
if (!this.connected) {
throw new Error('MySQL not connected');
}
try {
const connection = await this.pool.getConnection();
try {
await connection.beginTransaction();
// Build INSERT query dynamically based on data fields
const fields = Object.keys(data);
const placeholders = fields.map(() => '?').join(', ');
const values = fields.map(field => data[field]);
const query = `
INSERT INTO \`${table}\` (${fields.map(f => `\`${f}\``).join(', ')})
VALUES (${placeholders})
`;
let result;
try {
result = await connection.execute(query, values);
} catch (error) {
// If table doesn't exist, create it and retry
if (error.message.includes("doesn't exist")) {
console.log(`🔄 Table ${table} doesn't exist, creating it...`);
await this.ensureTableExists(connection, table, data);
result = await connection.execute(query, values);
} else {
throw error;
}
}
await connection.commit();
return {
success: true,
table: table,
insertedId: result.insertId,
timestamp: new Date()
};
} catch (error) {
await connection.rollback();
throw error;
} finally {
connection.release();
}
} catch (error) {
console.error('MySQL write error:', error.message);
this.metrics.errorsHandled++;
throw error;
}
}
// Helper method to create table if it doesn't exist
async ensureTableExists(connection, table, sampleData) {
try {
console.log(`🔄 Ensuring table exists: ${table}`);
// Build the CREATE TABLE query
const columnDefinitions = [
'`id` INT AUTO_INCREMENT PRIMARY KEY',
'`createdAt` DATETIME DEFAULT CURRENT_TIMESTAMP',
'`updatedAt` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'
];
// Add data-specific columns
Object.keys(sampleData).forEach(field => {
// Skip if it's a standard column
if (['id', 'createdAt', 'updatedAt'].includes(field)) {
return;
}
const value = sampleData[field];
let columnDef = `\`${field}\``;
// Handle nested objects as JSON
if (typeof value === 'object' && value !== null && !(value instanceof Date) && !Array.isArray(value)) {
columnDef += ' JSON';
} else if (typeof value === 'string') {
if (field.includes('Id') || field.includes('ID')) {
columnDef += ' VARCHAR(255)';
} else if (field.includes('email')) {
columnDef += ' VARCHAR(255)';
} else if (field.includes('name')) {
columnDef += ' VARCHAR(255)';
} else if (field.includes('status')) {
columnDef += ' VARCHAR(50)';
} else if (field.includes('preferences') || field.includes('items')) {
columnDef += ' JSON';
} else {
columnDef += ' TEXT';
}
} else if (typeof value === 'number') {
if (Number.isInteger(value)) {
columnDef += ' INT';
} else {
columnDef += ' DECIMAL(10,2)';
}
} else if (value instanceof Date) {
columnDef += ' DATETIME';
} else if (typeof value === 'boolean') {
columnDef += ' BOOLEAN';
} else if (Array.isArray(value)) {
columnDef += ' JSON';
} else {
columnDef += ' JSON';
}
columnDefinitions.push(columnDef);
});
// Add indexes for common fields
const indexDefinitions = [];
Object.keys(sampleData).forEach(field => {
if (field.includes('Id') || field.includes('ID') || field.includes('email')) {
indexDefinitions.push(`INDEX idx_${field} (\`${field}\`)`);
}
});
// Build the complete CREATE TABLE query
const allDefinitions = [...columnDefinitions, ...indexDefinitions];
console.log(`🔄 Column definitions:`, columnDefinitions);
console.log(`🔄 Index definitions:`, indexDefinitions);
console.log(`🔄 All definitions:`, allDefinitions);
const createTableQuery = `
CREATE TABLE IF NOT EXISTS \`${table}\` (
${allDefinitions.join(',\n ')}
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
`;
console.log(`🔄 Creating table with query:`, createTableQuery);
await connection.execute(createTableQuery);
console.log(`✅ Table created: ${table}`);
} catch (error) {
console.error(`❌ Failed to create table ${table}:`, error.message);
console.error('Error details:', error);
throw error;
}
}
// 🆕 NEW: Update data in specific table with query
async updateData(table, query, updateData, options = {}) {
if (!this.connected) {
throw new Error('MySQL not connected');
}
try {
const connection = await this.pool.getConnection();
try {
await connection.beginTransaction();
// Ensure all update fields exist as columns
await this.ensureColumnsExist(connection, table, updateData);
// Build WHERE clause
const whereConditions = [];
const whereValues = [];
Object.keys(query).forEach(field => {
whereConditions.push(`\`${field}\` = ?`);
whereValues.push(query[field]);
});
const whereClause = whereConditions.length > 0 ? `WHERE ${whereConditions.join(' AND ')}` : '';
// Build SET clause
const setFields = [];
const setValues = [];
Object.keys(updateData).forEach(field => {
setFields.push(`\`${field}\` = ?`);
setValues.push(updateData[field]);
});
const setClause = setFields.join(', ');
const updateQuery = `
UPDATE \`${table}\`
SET ${setClause}
${whereClause}
`;
const [result] = await connection.execute(updateQuery, [...setValues, ...whereValues]);
await connection.commit();
return {
success: true,
table: table,
found: result.affectedRows > 0,
updated: result.affectedRows > 0,
timestamp: new Date(),
query: query
};
} catch (error) {
await connection.rollback();
throw error;
} finally {
connection.release();
}
} catch (error) {
console.error('MySQL update error:', error.message);
this.metrics.errorsHandled++;
throw error;
}
}
// Helper to ensure columns exist for updateData
async ensureColumnsExist(connection, table, updateData) {
try {
// Get current columns
const [columns] = await connection.execute(
'SELECT COLUMN_NAME, DATA_TYPE FROM information_schema.columns WHERE table_schema = ? AND table_name = ?',
[this.config.database, table]
);
const existing = new Set(columns.map(col => col.COLUMN_NAME));
const alters = [];
for (const field of Object.keys(updateData)) {
if (!existing.has(field)) {
// Infer type
const value = updateData[field];
let type = 'TEXT';
if (typeof value === 'string') {
if (field.includes('Id') || field.includes('ID')) type = 'VARCHAR(255)';
else if (field.includes('email')) type = 'VARCHAR(255)';
else if (field.includes('name')) type = 'VARCHAR(255)';
else if (field.includes('status')) type = 'VARCHAR(50)';
else if (field.includes('preferences') || field.includes('items')) type = 'JSON';
else type = 'TEXT';
} else if (typeof value === 'number') {
if (Number.isInteger(value)) type = 'INT';
else type = 'DECIMAL(10,2)';
} else if (value instanceof Date) {
type = 'DATETIME';
} else if (typeof value === 'boolean') {
type = 'BOOLEAN';
} else {
type = 'JSON';
}
alters.push(`ADD COLUMN \`${field}\` ${type}`);
}
}
if (alters.length > 0) {
const alterQuery = `ALTER TABLE \`${table}\` ${alters.join(', ')}`;
console.log(`🔄 Altering table ${table} to add columns:`, alters);
await connection.execute(alterQuery);
console.log(`✅ Table ${table} altered successfully.`);
}
} catch (error) {
console.error(`❌ Failed to alter table ${table}:`, error.message);
throw error;
}
}
// 🆕 NEW: Read data from specific table
async readData(table, query = {}, options = {}) {
if (!this.connected) {
throw new Error('MySQL not connected');
}
try {
const connection = await this.pool.getConnection();
try {
// Build WHERE clause
const whereConditions = [];
const whereValues = [];
Object.keys(query).forEach(field => {
whereConditions.push(`\`${field}\` = ?`);
whereValues.push(query[field]);
});
const whereClause = whereConditions.length > 0 ? `WHERE ${whereConditions.join(' AND ')}` : '';
const selectQuery = `
SELECT * FROM \`${table}\`
${whereClause}
${options.orderBy ? `ORDER BY ${options.orderBy}` : ''}
${options.limit ? `LIMIT ${options.limit}` : ''}
`;
const [rows] = await connection.execute(selectQuery, whereValues);
if (rows.length === 0) {
return null;
}
return {
table: table,
data: rows[0],
timestamp: new Date(),
lastModified: rows[0].updated_at || new Date()
};
} finally {
connection.release();
}
} catch (error) {
console.error('MySQL read error:', error.message);
throw error;
}
}
// 🆕 NEW: Start real-time watch for specific table
async startRealTimeWatch(table, callback, options = {}) {
if (!this.connected) {
throw new Error('MySQL not connected');
}
try {
// Store watcher info
this.activeWatchers.set(table, { callback, options });
this.metrics.activeWatchers++;
// Get initial data and store in cache
const initialData = await this.readData(table);
if (initialData) {
this.lastKnownState.set(table, initialData);
const meta = {
table: table,
changeType: 'initial',
timestamp: new Date(),
source: 'initial'
};
callback(initialData.data, meta);
}
console.log(`Real-time MySQL watch started for table: ${table}`);
// If binlog is not available, the polling fallback will handle this
return { table, active: true };
} catch (error) {
console.error(`Failed to start MySQL watch for table ${table}:`, error);
this.activeWatchers.delete(table);
throw error;
}
}
async stopWatch(table) {
if (this.activeWatchers.has(table)) {
this.activeWatchers.delete(table);
this.lastKnownState.delete(table);
this.metrics.activeWatchers--;
console.log(`Stopped watching table: ${table}`);
}
}
// 🆕 NEW: Query data from specific table
async queryData(table, query = {}, options = {}) {
if (!this.connected) {
throw new Error('MySQL not connected');
}
try {
const connection = await this.pool.getConnection();
try {
// Build WHERE clause
const whereConditions = [];
const whereValues = [];
if (query.where) {
Object.keys(query.where).forEach(field => {
whereConditions.push(`\`${field}\` = ?`);
whereValues.push(query.where[field]);
});
}
const whereClause = whereConditions.length > 0 ? `WHERE ${whereConditions.join(' AND ')}` : '';
// Build SELECT clause
const selectFields = options.fields ? options.fields.map(f => `\`${f}\``).join(', ') : '*';
const selectQuery = `
SELECT ${selectFields} FROM \`${table}\`
${whereClause}
${options.orderBy ? `ORDER BY ${options.orderBy}` : ''}
${options.limit ? `LIMIT ${options.limit}` : ''}
${options.offset ? `OFFSET ${options.offset}` : ''}
`;
const [rows] = await connection.execute(selectQuery, whereValues);
return rows.map(row => ({
table: table,
data: row,
timestamp: new Date(),
lastModified: row.updated_at || new Date()
}));
} finally {
connection.release();
}
} catch (error) {
console.error('MySQL query error:', error.message);
throw error;
}
}
// 🆕 NEW: Get all tables
async getAllTables() {
if (!this.connected) {
throw new Error('MySQL not connected');
}
try {
const connection = await this.pool.getConnection();
try {
const [rows] = await connection.execute(`
SELECT table_name
FROM information_schema.tables
WHERE table_schema = ?
`, [this.config.database]);
return rows.map(row => row.table_name);
} finally {
connection.release();
}
} catch (error) {
console.error('MySQL get tables error:', error.message);
throw error;
}
}
// 🆕 NEW: Delete data from specific table
async deleteData(table, query = {}) {
if (!this.connected) {
throw new Error('MySQL not connected');
}
try {
const connection = await this.pool.getConnection();
try {
await connection.beginTransaction();
// Build WHERE clause
const whereConditions = [];
const whereValues = [];
Object.keys(query).forEach(field => {
whereConditions.push(`\`${field}\` = ?`);
whereValues.push(query[field]);
});
const whereClause = whereConditions.length > 0 ? `WHERE ${whereConditions.join(' AND ')}` : '';
const deleteQuery = `
DELETE FROM \`${table}\`
${whereClause}
`;
const [result] = await connection.execute(deleteQuery, whereValues);
await connection.commit();
return {
success: true,
table: table,
deleted: result.affectedRows > 0,
timestamp: new Date()
};
} catch (error) {
await connection.rollback();
throw error;
} finally {
connection.release();
}
} catch (error) {
console.error('MySQL delete error:', error.message);
this.metrics.errorsHandled++;
throw error;
}
}
// 🆕 NEW: Create indexes for specific table
async createIndex(table, indexName, fields, options = {}) {
if (!this.connected) {
throw new Error('MySQL not connected');
}
try {
const connection = await this.pool.getConnection();
try {
const fieldList = Array.isArray(fields) ? fields.map(f => `\`${f}\``).join(', ') : `\`${fields}\``;
const indexType = options.unique ? 'UNIQUE' : '';
const createIndexQuery = `
CREATE ${indexType} INDEX \`${indexName}\`
ON \`${table}\` (${fieldList})
`;
await connection.execute(createIndexQuery);
console.log(`✅ Created index ${indexName} for table ${table}`);
return { success: true, indexName, table };
} finally {
connection.release();
}
} catch (error) {
console.error(`❌ Failed to create index for table ${table}:`, error.message);
throw error;
}
}
// 🆕 NEW: Setup optimized indexes for specific table
async setupOptimizedIndexes(table) {
try {
// Create common indexes
await this.createIndex(table, `idx_${table}_created_at`, 'created_at');
await this.createIndex(table, `idx_${table}_updated_at`, 'updated_at');
console.log(`✅ Optimized indexes created for table: ${table}`);
} catch (error) {
console.error(`❌ Failed to setup indexes for table ${table}:`, error.message);
}
}
async setupBinlogMonitoring() {
try {
// This would require additional binlog library
// For now, we'll use polling fallback
console.log('Binlog monitoring not implemented, using polling fallback');
this.setupPollingFallback();
} catch (error) {
console.error('Failed to setup binlog monitoring:', error);
this.setupPollingFallback();
}
}
setupPollingFallback() {
// Setup polling for all active watchers
for (const [table, watcherInfo] of this.activeWatchers) {
this.checkTableForChanges(table, watcherInfo);
}
}
async checkTableForChanges(table, watcherInfo) {
try {
const currentData = await this.readData(table);
const lastKnown = this.lastKnownState.get(table);
if (currentData && (!lastKnown || currentData.lastModified > lastKnown.lastModified)) {
// Data changed
this.lastKnownState.set(table, currentData);
const meta = {
table: table,
changeType: 'updated',
timestamp: new Date(),
source: 'polling'
};
watcherInfo.callback(currentData.data, meta);
this.emit('change', { table, data: currentData.data, meta });
}
} catch (error) {
console.error(`Error checking table ${table} for changes:`, error);
}
}
getMetrics() {
return {
...this.metrics,
connected: this.connected,
activeWatchers: this.activeWatchers.size,
reconnectAttempts: this.reconnectAttempts
};
}
async disconnect() {
if (this.healthCheckInterval) {
clearInterval(this.healthCheckInterval);
}
// Stop all watchers
for (const [table] of this.activeWatchers) {
await this.stopWatch(table);
}
// Close binlog reader
if (this.zongJi) {
try {
this.zongJi.stop();
} catch (error) {
console.error('Error closing binlog reader:', error);
}
}
if (this.pool) {
await this.pool.end();
}
this.connected = false;
this.activeWatchers.clear();
this.lastKnownState.clear();
console.log('MySQL disconnected');
}
isConnected() {
return this.connected;
}
}
module.exports = AdvancedMysqlConnector;