import { Meteor } from 'meteor/meteor' ;
import { mongodbDriverManager } from './mongodbDriverManager' ;
/ * *
* MongoDB Connection Manager
*
* This module handles MongoDB connections with automatic driver selection
* based on detected MongoDB server version and wire protocol compatibility .
*
* Features :
* - Automatic driver selection based on MongoDB version
* - Connection retry with different drivers on wire protocol errors
* - Fallback mechanism for unsupported versions
* - Connection pooling and management
* /
class MongoDBConnectionManager {
constructor ( ) {
this . connections = new Map ( ) ;
this . connectionConfigs = new Map ( ) ;
this . retryAttempts = 3 ;
this . retryDelay = 1000 ; // 1 second
}
/ * *
* Create a MongoDB connection with automatic driver selection
* @ param { string } connectionString - MongoDB connection string
* @ param { Object } options - Connection options
* @ returns { Promise < Object > } - MongoDB connection object
* /
async createConnection ( connectionString , options = { } ) {
const connectionId = this . generateConnectionId ( connectionString ) ;
// Check if we already have a working connection
if ( this . connections . has ( connectionId ) ) {
const existingConnection = this . connections . get ( connectionId ) ;
if ( existingConnection . status === 'connected' ) {
return existingConnection ;
}
}
// Try to connect with automatic driver selection
return await this . connectWithDriverSelection ( connectionString , options , connectionId ) ;
}
/ * *
* Connect with automatic driver selection and retry logic
* @ param { string } connectionString - MongoDB connection string
* @ param { Object } options - Connection options
* @ param { string } connectionId - Connection identifier
* @ returns { Promise < Object > } - MongoDB connection object
* /
async connectWithDriverSelection ( connectionString , options , connectionId ) {
let lastError = null ;
let currentDriver = null ;
// First, try with the default driver (if we have a detected version)
if ( mongodbDriverManager . detectedVersion ) {
currentDriver = mongodbDriverManager . getDriverForVersion ( mongodbDriverManager . detectedVersion ) ;
} else {
// Start with the most recent driver
currentDriver = 'mongodb8legacy' ;
}
// Try connection with different drivers
for ( let attempt = 0 ; attempt < this . retryAttempts ; attempt ++ ) {
try {
console . log ( ` Attempting MongoDB connection with driver: ${ currentDriver } (attempt ${ attempt + 1 } ) ` ) ;
const connection = await this . connectWithDriver ( currentDriver , connectionString , options ) ;
// Record successful connection
mongodbDriverManager . recordConnectionAttempt (
currentDriver ,
mongodbDriverManager . detectedVersion || 'unknown' ,
true
) ;
// Store connection
this . connections . set ( connectionId , {
connection ,
driver : currentDriver ,
version : mongodbDriverManager . detectedVersion || 'unknown' ,
status : 'connected' ,
connectionString ,
options ,
createdAt : new Date ( )
} ) ;
return connection ;
} catch ( error ) {
lastError = error ;
console . error ( ` Connection attempt ${ attempt + 1 } failed with driver ${ currentDriver } : ` , error . message ) ;
// Try to detect MongoDB version from error
const detectedVersion = mongodbDriverManager . detectVersionFromError ( error ) ;
if ( detectedVersion && detectedVersion !== 'unknown' ) {
mongodbDriverManager . detectedVersion = detectedVersion ;
currentDriver = mongodbDriverManager . getDriverForVersion ( detectedVersion ) ;
console . log ( ` Detected MongoDB version ${ detectedVersion } , switching to driver ${ currentDriver } ` ) ;
} else {
// Try next fallback driver
const nextDriver = mongodbDriverManager . getNextFallbackDriver ( ) ;
if ( nextDriver ) {
currentDriver = nextDriver ;
console . log ( ` Trying fallback driver: ${ currentDriver } ` ) ;
} else {
console . error ( 'No more fallback drivers available' ) ;
break ;
}
}
// Record failed attempt
mongodbDriverManager . recordConnectionAttempt (
currentDriver ,
detectedVersion || 'unknown' ,
false ,
error
) ;
// Wait before retry
if ( attempt < this . retryAttempts - 1 ) {
await this . delay ( this . retryDelay * ( attempt + 1 ) ) ;
}
}
}
// All attempts failed
throw new Error ( ` Failed to connect to MongoDB after ${ this . retryAttempts } attempts. Last error: ${ lastError ? . message } ` ) ;
}
/ * *
* Connect using a specific driver
* @ param { string } driverName - Driver package name
* @ param { string } connectionString - MongoDB connection string
* @ param { Object } options - Connection options
* @ returns { Promise < Object > } - MongoDB connection object
* /
async connectWithDriver ( driverName , connectionString , options ) {
try {
// Dynamically import the driver
const driver = await import ( driverName ) ;
const MongoClient = driver . MongoClient ;
// Set default options
const defaultOptions = {
useNewUrlParser : true ,
useUnifiedTopology : true ,
maxPoolSize : 10 ,
serverSelectionTimeoutMS : 5000 ,
socketTimeoutMS : 45000 ,
... options
} ;
// Create connection
const client = new MongoClient ( connectionString , defaultOptions ) ;
await client . connect ( ) ;
// Test the connection
await client . db ( 'admin' ) . admin ( ) . ping ( ) ;
return client ;
} catch ( error ) {
throw new Error ( ` Failed to connect with driver ${ driverName } : ${ error . message } ` ) ;
}
}
/ * *
* Get connection by ID
* @ param { string } connectionId - Connection identifier
* @ returns { Object | null } - Connection object or null
* /
getConnection ( connectionId ) {
return this . connections . get ( connectionId ) || null ;
}
/ * *
* Close a connection
* @ param { string } connectionId - Connection identifier
* @ returns { Promise < boolean > } - Whether connection was closed successfully
* /
async closeConnection ( connectionId ) {
const connection = this . connections . get ( connectionId ) ;
if ( connection && connection . connection ) {
try {
await connection . connection . close ( ) ;
this . connections . delete ( connectionId ) ;
console . log ( ` Closed MongoDB connection: ${ connectionId } ` ) ;
return true ;
} catch ( error ) {
console . error ( ` Error closing MongoDB connection ${ connectionId } : ` , error . message ) ;
return false ;
}
}
return false ;
}
/ * *
* Close all connections
* @ returns { Promise < number > } - Number of connections closed
* /
async closeAllConnections ( ) {
let closedCount = 0 ;
const connectionIds = Array . from ( this . connections . keys ( ) ) ;
for ( const connectionId of connectionIds ) {
if ( await this . closeConnection ( connectionId ) ) {
closedCount ++ ;
}
}
console . log ( ` Closed ${ closedCount } MongoDB connections ` ) ;
return closedCount ;
}
/ * *
* Get connection statistics
* @ returns { Object } - Connection statistics
* /
getConnectionStats ( ) {
const connections = Array . from ( this . connections . values ( ) ) ;
const connected = connections . filter ( conn => conn . status === 'connected' ) . length ;
const disconnected = connections . length - connected ;
return {
total : connections . length ,
connected ,
disconnected ,
connections : connections . map ( conn => ( {
id : this . getConnectionIdFromConnection ( conn ) ,
driver : conn . driver ,
version : conn . version ,
status : conn . status ,
createdAt : conn . createdAt
} ) )
} ;
}
/ * *
* Generate a unique connection ID
* @ param { string } connectionString - MongoDB connection string
* @ returns { string } - Unique connection ID
* /
generateConnectionId ( connectionString ) {
// Create a hash of the connection string for unique ID
let hash = 0 ;
for ( let i = 0 ; i < connectionString . length ; i ++ ) {
const char = connectionString . charCodeAt ( i ) ;
hash = ( ( hash << 5 ) - hash ) + char ;
hash = hash & hash ; // Convert to 32-bit integer
}
return ` mongodb_ ${ Math . abs ( hash ) } ` ;
}
/ * *
* Get connection ID from connection object
* @ param { Object } connection - Connection object
* @ returns { string } - Connection ID
* /
getConnectionIdFromConnection ( connection ) {
return this . generateConnectionId ( connection . connectionString ) ;
}
/ * *
* Utility function to delay execution
* @ param { number } ms - Milliseconds to delay
* @ returns { Promise } - Promise that resolves after delay
* /
delay ( ms ) {
return new Promise ( resolve => setTimeout ( resolve , ms ) ) ;
}
/ * *
* Reset all connections and driver manager state
* /
reset ( ) {
this . connections . clear ( ) ;
this . connectionConfigs . clear ( ) ;
mongodbDriverManager . reset ( ) ;
}
}
// Create singleton instance
const mongodbConnectionManager = new MongoDBConnectionManager ( ) ;
// Export for use in other modules
export { mongodbConnectionManager , MongoDBConnectionManager } ;
// MongoDB Connection Manager initialized (status available in Admin Panel)