pms_md 1.0.0 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Database Connection Monitor
3
- * Monitors database connections for MongoDB, PostgreSQL, MySQL, and Redis
3
+ * Monitors database connections for MongoDB, PostgreSQL, MySQL, MSSQL, SQLite,
4
+ * MariaDB, Redis, CouchDB, Cassandra, and more
4
5
  */
5
6
 
6
7
  const cron = require('node-cron');
@@ -104,18 +105,51 @@ class DbConnectionMonitor {
104
105
  case 'mongodb':
105
106
  case 'mongoose':
106
107
  return this.checkMongoConnection(connection);
107
-
108
+
108
109
  case 'postgresql':
109
110
  case 'postgres':
110
111
  case 'pg':
111
112
  return this.checkPostgresConnection(connection, testQuery);
112
-
113
+
113
114
  case 'mysql':
115
+ case 'mysql2':
114
116
  return this.checkMySqlConnection(connection, testQuery);
115
-
117
+
118
+ case 'mariadb':
119
+ return this.checkMariaDBConnection(connection, testQuery);
120
+
121
+ case 'mssql':
122
+ case 'sqlserver':
123
+ case 'tedious':
124
+ return this.checkMSSQLConnection(connection, testQuery);
125
+
126
+ case 'sqlite':
127
+ case 'sqlite3':
128
+ return this.checkSQLiteConnection(connection, testQuery);
129
+
130
+ case 'sequelize':
131
+ return this.checkSequelizeConnection(connection);
132
+
116
133
  case 'redis':
117
134
  return this.checkRedisConnection(connection);
118
-
135
+
136
+ case 'couchdb':
137
+ case 'nano':
138
+ return this.checkCouchDBConnection(connection);
139
+
140
+ case 'cassandra':
141
+ return this.checkCassandraConnection(connection);
142
+
143
+ case 'elasticsearch':
144
+ case 'elastic':
145
+ return this.checkElasticsearchConnection(connection);
146
+
147
+ case 'dynamodb':
148
+ return this.checkDynamoDBConnection(connection);
149
+
150
+ case 'neo4j':
151
+ return this.checkNeo4jConnection(connection);
152
+
119
153
  default:
120
154
  throw new Error(`Unsupported database type: ${type}`);
121
155
  }
@@ -160,16 +194,48 @@ class DbConnectionMonitor {
160
194
  }
161
195
 
162
196
  /**
163
- * Check MySQL connection
197
+ * Check MySQL/MySQL2 connection
164
198
  */
165
199
  async checkMySqlConnection(connection, testQuery = 'SELECT 1') {
166
200
  try {
167
- const [rows] = await Promise.race([
168
- connection.query(testQuery),
201
+ // Support for mysql2 pool or connection
202
+ if (connection.query) {
203
+ const result = await Promise.race([
204
+ connection.query(testQuery),
205
+ this.timeout(this.config.database.queryTimeout)
206
+ ]);
207
+ // mysql2 returns [rows, fields]
208
+ return Array.isArray(result) ? !!result[0] : !!result;
209
+ }
210
+
211
+ // Support for mysql2 promise pool
212
+ if (connection.execute) {
213
+ const [rows] = await Promise.race([
214
+ connection.execute(testQuery),
215
+ this.timeout(this.config.database.queryTimeout)
216
+ ]);
217
+ return !!rows;
218
+ }
219
+
220
+ return false;
221
+ } catch (error) {
222
+ this.logger.logWarning('mysql_check_failed', `MySQL connection check failed: ${error.message}`);
223
+ return false;
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Check Sequelize connection
229
+ */
230
+ async checkSequelizeConnection(connection) {
231
+ try {
232
+ await Promise.race([
233
+ connection.authenticate(),
169
234
  this.timeout(this.config.database.queryTimeout)
170
235
  ]);
171
- return !!rows;
172
- } catch {
236
+ return true;
237
+ } catch (error) {
238
+ this.logger.logWarning('sequelize_check_failed', `Sequelize connection check failed: ${error.message}`);
173
239
  return false;
174
240
  }
175
241
  }
@@ -189,6 +255,259 @@ class DbConnectionMonitor {
189
255
  }
190
256
  }
191
257
 
258
+ /**
259
+ * Check MariaDB connection
260
+ */
261
+ async checkMariaDBConnection(connection, testQuery = 'SELECT 1') {
262
+ try {
263
+ // MariaDB uses same interface as MySQL
264
+ if (connection.query) {
265
+ const result = await Promise.race([
266
+ connection.query(testQuery),
267
+ this.timeout(this.config.database.queryTimeout)
268
+ ]);
269
+ return Array.isArray(result) ? !!result[0] : !!result;
270
+ }
271
+
272
+ if (connection.execute) {
273
+ const [rows] = await Promise.race([
274
+ connection.execute(testQuery),
275
+ this.timeout(this.config.database.queryTimeout)
276
+ ]);
277
+ return !!rows;
278
+ }
279
+
280
+ return false;
281
+ } catch (error) {
282
+ this.logger.logWarning('mariadb_check_failed', `MariaDB connection check failed: ${error.message}`);
283
+ return false;
284
+ }
285
+ }
286
+
287
+ /**
288
+ * Check MSSQL connection
289
+ */
290
+ async checkMSSQLConnection(connection, testQuery = 'SELECT 1') {
291
+ try {
292
+ // For mssql package (ConnectionPool)
293
+ if (connection.connected !== undefined) {
294
+ if (!connection.connected) {
295
+ return false;
296
+ }
297
+ }
298
+
299
+ // Try to execute a query
300
+ if (connection.query) {
301
+ const result = await Promise.race([
302
+ connection.query(testQuery),
303
+ this.timeout(this.config.database.queryTimeout)
304
+ ]);
305
+ return !!result;
306
+ }
307
+
308
+ // For tedious connection
309
+ if (connection.request) {
310
+ const request = connection.request();
311
+ const result = await Promise.race([
312
+ request.query(testQuery),
313
+ this.timeout(this.config.database.queryTimeout)
314
+ ]);
315
+ return !!result;
316
+ }
317
+
318
+ return false;
319
+ } catch (error) {
320
+ this.logger.logWarning('mssql_check_failed', `MSSQL connection check failed: ${error.message}`);
321
+ return false;
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Check SQLite connection
327
+ */
328
+ async checkSQLiteConnection(connection, testQuery = 'SELECT 1') {
329
+ try {
330
+ // For sqlite3 package
331
+ if (connection.get) {
332
+ return new Promise((resolve) => {
333
+ const timeout = setTimeout(() => resolve(false), this.config.database.queryTimeout);
334
+
335
+ connection.get(testQuery, (err) => {
336
+ clearTimeout(timeout);
337
+ resolve(!err);
338
+ });
339
+ });
340
+ }
341
+
342
+ // For better-sqlite3 package
343
+ if (connection.prepare) {
344
+ const stmt = connection.prepare(testQuery);
345
+ const result = stmt.get();
346
+ return !!result || result === undefined;
347
+ }
348
+
349
+ return false;
350
+ } catch (error) {
351
+ this.logger.logWarning('sqlite_check_failed', `SQLite connection check failed: ${error.message}`);
352
+ return false;
353
+ }
354
+ }
355
+
356
+ /**
357
+ * Check CouchDB connection
358
+ */
359
+ async checkCouchDBConnection(connection) {
360
+ try {
361
+ // For nano package
362
+ if (connection.db && connection.db.list) {
363
+ const result = await Promise.race([
364
+ connection.db.list(),
365
+ this.timeout(this.config.database.queryTimeout)
366
+ ]);
367
+ return !!result;
368
+ }
369
+
370
+ // For direct HTTP client
371
+ if (connection.info) {
372
+ const result = await Promise.race([
373
+ connection.info(),
374
+ this.timeout(this.config.database.queryTimeout)
375
+ ]);
376
+ return !!result;
377
+ }
378
+
379
+ return false;
380
+ } catch (error) {
381
+ this.logger.logWarning('couchdb_check_failed', `CouchDB connection check failed: ${error.message}`);
382
+ return false;
383
+ }
384
+ }
385
+
386
+ /**
387
+ * Check Cassandra connection
388
+ */
389
+ async checkCassandraConnection(connection) {
390
+ try {
391
+ // For cassandra-driver package
392
+ if (connection.execute) {
393
+ const result = await Promise.race([
394
+ connection.execute('SELECT now() FROM system.local'),
395
+ this.timeout(this.config.database.queryTimeout)
396
+ ]);
397
+ return !!result;
398
+ }
399
+
400
+ // Check connection state
401
+ if (connection.getState) {
402
+ const state = connection.getState();
403
+ return state && state.getConnectedHosts && state.getConnectedHosts().length > 0;
404
+ }
405
+
406
+ return false;
407
+ } catch (error) {
408
+ this.logger.logWarning('cassandra_check_failed', `Cassandra connection check failed: ${error.message}`);
409
+ return false;
410
+ }
411
+ }
412
+
413
+ /**
414
+ * Check Elasticsearch connection
415
+ */
416
+ async checkElasticsearchConnection(connection) {
417
+ try {
418
+ // For @elastic/elasticsearch package
419
+ if (connection.ping) {
420
+ const result = await Promise.race([
421
+ connection.ping(),
422
+ this.timeout(this.config.database.queryTimeout)
423
+ ]);
424
+ return !!result;
425
+ }
426
+
427
+ // Alternative: check cluster health
428
+ if (connection.cluster && connection.cluster.health) {
429
+ const result = await Promise.race([
430
+ connection.cluster.health(),
431
+ this.timeout(this.config.database.queryTimeout)
432
+ ]);
433
+ return !!result;
434
+ }
435
+
436
+ return false;
437
+ } catch (error) {
438
+ this.logger.logWarning('elasticsearch_check_failed', `Elasticsearch connection check failed: ${error.message}`);
439
+ return false;
440
+ }
441
+ }
442
+
443
+ /**
444
+ * Check DynamoDB connection
445
+ */
446
+ async checkDynamoDBConnection(connection) {
447
+ try {
448
+ // For AWS SDK DynamoDB client
449
+ if (connection.listTables) {
450
+ const result = await Promise.race([
451
+ connection.listTables({ Limit: 1 }).promise(),
452
+ this.timeout(this.config.database.queryTimeout)
453
+ ]);
454
+ return !!result;
455
+ }
456
+
457
+ // For AWS SDK v3
458
+ if (connection.send) {
459
+ const { ListTablesCommand } = require('@aws-sdk/client-dynamodb');
460
+ const result = await Promise.race([
461
+ connection.send(new ListTablesCommand({ Limit: 1 })),
462
+ this.timeout(this.config.database.queryTimeout)
463
+ ]);
464
+ return !!result;
465
+ }
466
+
467
+ return false;
468
+ } catch (error) {
469
+ this.logger.logWarning('dynamodb_check_failed', `DynamoDB connection check failed: ${error.message}`);
470
+ return false;
471
+ }
472
+ }
473
+
474
+ /**
475
+ * Check Neo4j connection
476
+ */
477
+ async checkNeo4jConnection(connection) {
478
+ try {
479
+ // For neo4j-driver package
480
+ if (connection.verifyConnectivity) {
481
+ await Promise.race([
482
+ connection.verifyConnectivity(),
483
+ this.timeout(this.config.database.queryTimeout)
484
+ ]);
485
+ return true;
486
+ }
487
+
488
+ // Alternative: run a simple query
489
+ if (connection.session) {
490
+ const session = connection.session();
491
+ try {
492
+ const result = await Promise.race([
493
+ session.run('RETURN 1'),
494
+ this.timeout(this.config.database.queryTimeout)
495
+ ]);
496
+ await session.close();
497
+ return !!result;
498
+ } catch (error) {
499
+ await session.close();
500
+ throw error;
501
+ }
502
+ }
503
+
504
+ return false;
505
+ } catch (error) {
506
+ this.logger.logWarning('neo4j_check_failed', `Neo4j connection check failed: ${error.message}`);
507
+ return false;
508
+ }
509
+ }
510
+
192
511
  /**
193
512
  * Handle connection status changes
194
513
  */
@@ -275,18 +594,51 @@ class DbConnectionMonitor {
275
594
  case 'mongodb':
276
595
  case 'mongoose':
277
596
  return this.getMongoStats(connection);
278
-
597
+
279
598
  case 'postgresql':
280
599
  case 'postgres':
281
600
  case 'pg':
282
601
  return this.getPostgresStats(connection);
283
-
602
+
284
603
  case 'mysql':
604
+ case 'mysql2':
285
605
  return this.getMySqlStats(connection);
286
-
606
+
607
+ case 'mariadb':
608
+ return this.getMariaDBStats(connection);
609
+
610
+ case 'mssql':
611
+ case 'sqlserver':
612
+ case 'tedious':
613
+ return this.getMSSQLStats(connection);
614
+
615
+ case 'sqlite':
616
+ case 'sqlite3':
617
+ return this.getSQLiteStats(connection);
618
+
619
+ case 'sequelize':
620
+ return this.getSequelizeStats(connection);
621
+
287
622
  case 'redis':
288
623
  return this.getRedisStats(connection);
289
-
624
+
625
+ case 'couchdb':
626
+ case 'nano':
627
+ return this.getCouchDBStats(connection);
628
+
629
+ case 'cassandra':
630
+ return this.getCassandraStats(connection);
631
+
632
+ case 'elasticsearch':
633
+ case 'elastic':
634
+ return this.getElasticsearchStats(connection);
635
+
636
+ case 'dynamodb':
637
+ return this.getDynamoDBStats(connection);
638
+
639
+ case 'neo4j':
640
+ return this.getNeo4jStats(connection);
641
+
290
642
  default:
291
643
  return { type, status: 'unknown' };
292
644
  }
@@ -333,21 +685,63 @@ class DbConnectionMonitor {
333
685
  }
334
686
 
335
687
  /**
336
- * Get MySQL statistics
688
+ * Get MySQL/MySQL2 statistics
337
689
  */
338
690
  async getMySqlStats(connection) {
339
691
  try {
340
- const [rows] = await connection.query('SHOW STATUS LIKE "Threads_connected"');
341
-
692
+ let rows;
693
+
694
+ // Try mysql2 format first
695
+ if (connection.query) {
696
+ const result = await connection.query('SHOW STATUS LIKE "Threads_connected"');
697
+ rows = Array.isArray(result) ? result[0] : result;
698
+ } else if (connection.execute) {
699
+ [rows] = await connection.execute('SHOW STATUS LIKE "Threads_connected"');
700
+ }
701
+
342
702
  return {
343
703
  type: 'mysql',
344
- threadsConnected: rows[0]?.Value || 0
704
+ threadsConnected: rows?.[0]?.Value || 0
345
705
  };
346
706
  } catch (error) {
347
707
  return { type: 'mysql', error: error.message };
348
708
  }
349
709
  }
350
710
 
711
+ /**
712
+ * Get Sequelize statistics
713
+ */
714
+ async getSequelizeStats(connection) {
715
+ try {
716
+ // Get database dialect
717
+ const dialect = connection.getDialect();
718
+
719
+ // Get pool information if available
720
+ const poolInfo = connection.connectionManager?.pool
721
+ ? {
722
+ size: connection.connectionManager.pool.size || 0,
723
+ available: connection.connectionManager.pool.available || 0,
724
+ using: connection.connectionManager.pool.using || 0,
725
+ waiting: connection.connectionManager.pool.waiting || 0
726
+ }
727
+ : null;
728
+
729
+ // Get basic connection info
730
+ const dbName = connection.config?.database || 'unknown';
731
+ const host = connection.config?.host || 'unknown';
732
+
733
+ return {
734
+ type: 'sequelize',
735
+ dialect,
736
+ database: dbName,
737
+ host,
738
+ pool: poolInfo
739
+ };
740
+ } catch (error) {
741
+ return { type: 'sequelize', error: error.message };
742
+ }
743
+ }
744
+
351
745
  /**
352
746
  * Get Redis statistics
353
747
  */
@@ -356,14 +750,14 @@ class DbConnectionMonitor {
356
750
  const info = await connection.info();
357
751
  const lines = info.split('\r\n');
358
752
  const stats = {};
359
-
753
+
360
754
  lines.forEach(line => {
361
755
  const [key, value] = line.split(':');
362
756
  if (key && value) {
363
757
  stats[key] = value;
364
758
  }
365
759
  });
366
-
760
+
367
761
  return {
368
762
  type: 'redis',
369
763
  connectedClients: stats.connected_clients,
@@ -375,11 +769,231 @@ class DbConnectionMonitor {
375
769
  }
376
770
  }
377
771
 
772
+ /**
773
+ * Get MariaDB statistics
774
+ */
775
+ async getMariaDBStats(connection) {
776
+ try {
777
+ let rows;
778
+
779
+ if (connection.query) {
780
+ const result = await connection.query('SHOW STATUS LIKE "Threads_connected"');
781
+ rows = Array.isArray(result) ? result[0] : result;
782
+ } else if (connection.execute) {
783
+ [rows] = await connection.execute('SHOW STATUS LIKE "Threads_connected"');
784
+ }
785
+
786
+ return {
787
+ type: 'mariadb',
788
+ threadsConnected: rows?.[0]?.Value || 0
789
+ };
790
+ } catch (error) {
791
+ return { type: 'mariadb', error: error.message };
792
+ }
793
+ }
794
+
795
+ /**
796
+ * Get MSSQL statistics
797
+ */
798
+ async getMSSQLStats(connection) {
799
+ try {
800
+ let result;
801
+
802
+ if (connection.query) {
803
+ result = await connection.query('SELECT @@VERSION as version, @@CONNECTIONS as connections');
804
+ } else if (connection.request) {
805
+ const request = connection.request();
806
+ result = await request.query('SELECT @@VERSION as version, @@CONNECTIONS as connections');
807
+ }
808
+
809
+ return {
810
+ type: 'mssql',
811
+ version: result?.recordset?.[0]?.version || 'unknown',
812
+ connections: result?.recordset?.[0]?.connections || 0,
813
+ connected: connection.connected || false
814
+ };
815
+ } catch (error) {
816
+ return { type: 'mssql', error: error.message };
817
+ }
818
+ }
819
+
820
+ /**
821
+ * Get SQLite statistics
822
+ */
823
+ async getSQLiteStats(connection) {
824
+ try {
825
+ let stats = { type: 'sqlite' };
826
+
827
+ // For sqlite3
828
+ if (connection.get) {
829
+ return new Promise((resolve) => {
830
+ connection.get('PRAGMA database_list', (err, row) => {
831
+ if (!err && row) {
832
+ stats.database = row.file || row.name;
833
+ }
834
+ resolve(stats);
835
+ });
836
+ });
837
+ }
838
+
839
+ // For better-sqlite3
840
+ if (connection.name) {
841
+ stats.database = connection.name;
842
+ stats.inTransaction = connection.inTransaction || false;
843
+ }
844
+
845
+ return stats;
846
+ } catch (error) {
847
+ return { type: 'sqlite', error: error.message };
848
+ }
849
+ }
850
+
851
+ /**
852
+ * Get CouchDB statistics
853
+ */
854
+ async getCouchDBStats(connection) {
855
+ try {
856
+ let info;
857
+
858
+ if (connection.db && connection.db.list) {
859
+ const databases = await connection.db.list();
860
+ info = { type: 'couchdb', databases: databases.length };
861
+ } else if (connection.info) {
862
+ const serverInfo = await connection.info();
863
+ info = {
864
+ type: 'couchdb',
865
+ version: serverInfo.version,
866
+ vendor: serverInfo.vendor?.name
867
+ };
868
+ }
869
+
870
+ return info || { type: 'couchdb' };
871
+ } catch (error) {
872
+ return { type: 'couchdb', error: error.message };
873
+ }
874
+ }
875
+
876
+ /**
877
+ * Get Cassandra statistics
878
+ */
879
+ async getCassandraStats(connection) {
880
+ try {
881
+ const stats = { type: 'cassandra' };
882
+
883
+ if (connection.getState) {
884
+ const state = connection.getState();
885
+ const hosts = state.getConnectedHosts();
886
+ stats.connectedHosts = hosts.length;
887
+ stats.hosts = hosts.map(h => h.address);
888
+ }
889
+
890
+ if (connection.metadata) {
891
+ stats.keyspaces = Object.keys(connection.metadata.keyspaces || {}).length;
892
+ }
893
+
894
+ return stats;
895
+ } catch (error) {
896
+ return { type: 'cassandra', error: error.message };
897
+ }
898
+ }
899
+
900
+ /**
901
+ * Get Elasticsearch statistics
902
+ */
903
+ async getElasticsearchStats(connection) {
904
+ try {
905
+ const stats = { type: 'elasticsearch' };
906
+
907
+ // Get cluster health
908
+ if (connection.cluster && connection.cluster.health) {
909
+ const health = await connection.cluster.health();
910
+ stats.clusterName = health.cluster_name;
911
+ stats.status = health.status;
912
+ stats.numberOfNodes = health.number_of_nodes;
913
+ stats.activeShards = health.active_shards;
914
+ }
915
+
916
+ // Get cluster info
917
+ if (connection.info) {
918
+ const info = await connection.info();
919
+ stats.version = info.version?.number;
920
+ }
921
+
922
+ return stats;
923
+ } catch (error) {
924
+ return { type: 'elasticsearch', error: error.message };
925
+ }
926
+ }
927
+
928
+ /**
929
+ * Get DynamoDB statistics
930
+ */
931
+ async getDynamoDBStats(connection) {
932
+ try {
933
+ const stats = { type: 'dynamodb' };
934
+
935
+ // For AWS SDK v2
936
+ if (connection.listTables) {
937
+ const result = await connection.listTables().promise();
938
+ stats.tableCount = result.TableNames?.length || 0;
939
+ }
940
+
941
+ // For AWS SDK v3
942
+ if (connection.send) {
943
+ const { ListTablesCommand } = require('@aws-sdk/client-dynamodb');
944
+ const result = await connection.send(new ListTablesCommand({}));
945
+ stats.tableCount = result.TableNames?.length || 0;
946
+ }
947
+
948
+ return stats;
949
+ } catch (error) {
950
+ return { type: 'dynamodb', error: error.message };
951
+ }
952
+ }
953
+
954
+ /**
955
+ * Get Neo4j statistics
956
+ */
957
+ async getNeo4jStats(connection) {
958
+ try {
959
+ const stats = { type: 'neo4j' };
960
+
961
+ // Get server info
962
+ if (connection.getServerInfo) {
963
+ const serverInfo = await connection.getServerInfo();
964
+ stats.version = serverInfo.version;
965
+ stats.address = serverInfo.address;
966
+ }
967
+
968
+ // Run a query to get database stats
969
+ if (connection.session) {
970
+ const session = connection.session();
971
+ try {
972
+ const result = await session.run('CALL dbms.components() YIELD name, versions, edition');
973
+ const record = result.records[0];
974
+ if (record) {
975
+ stats.name = record.get('name');
976
+ stats.versions = record.get('versions');
977
+ stats.edition = record.get('edition');
978
+ }
979
+ await session.close();
980
+ } catch (error) {
981
+ await session.close();
982
+ stats.queryError = error.message;
983
+ }
984
+ }
985
+
986
+ return stats;
987
+ } catch (error) {
988
+ return { type: 'neo4j', error: error.message };
989
+ }
990
+ }
991
+
378
992
  /**
379
993
  * Timeout helper
380
994
  */
381
995
  timeout(ms) {
382
- return new Promise((_, reject) =>
996
+ return new Promise((_, reject) =>
383
997
  setTimeout(() => reject(new Error('Query timeout')), ms)
384
998
  );
385
999
  }