pms_md 1.0.2 → 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');
@@ -114,12 +115,41 @@ class DbConnectionMonitor {
114
115
  case 'mysql2':
115
116
  return this.checkMySqlConnection(connection, testQuery);
116
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
+
117
130
  case 'sequelize':
118
131
  return this.checkSequelizeConnection(connection);
119
132
 
120
133
  case 'redis':
121
134
  return this.checkRedisConnection(connection);
122
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
+
123
153
  default:
124
154
  throw new Error(`Unsupported database type: ${type}`);
125
155
  }
@@ -225,6 +255,259 @@ class DbConnectionMonitor {
225
255
  }
226
256
  }
227
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
+
228
511
  /**
229
512
  * Handle connection status changes
230
513
  */
@@ -321,12 +604,41 @@ class DbConnectionMonitor {
321
604
  case 'mysql2':
322
605
  return this.getMySqlStats(connection);
323
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
+
324
619
  case 'sequelize':
325
620
  return this.getSequelizeStats(connection);
326
621
 
327
622
  case 'redis':
328
623
  return this.getRedisStats(connection);
329
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
+
330
642
  default:
331
643
  return { type, status: 'unknown' };
332
644
  }
@@ -438,14 +750,14 @@ class DbConnectionMonitor {
438
750
  const info = await connection.info();
439
751
  const lines = info.split('\r\n');
440
752
  const stats = {};
441
-
753
+
442
754
  lines.forEach(line => {
443
755
  const [key, value] = line.split(':');
444
756
  if (key && value) {
445
757
  stats[key] = value;
446
758
  }
447
759
  });
448
-
760
+
449
761
  return {
450
762
  type: 'redis',
451
763
  connectedClients: stats.connected_clients,
@@ -457,11 +769,231 @@ class DbConnectionMonitor {
457
769
  }
458
770
  }
459
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
+
460
992
  /**
461
993
  * Timeout helper
462
994
  */
463
995
  timeout(ms) {
464
- return new Promise((_, reject) =>
996
+ return new Promise((_, reject) =>
465
997
  setTimeout(() => reject(new Error('Query timeout')), ms)
466
998
  );
467
999
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pms_md",
3
- "version": "1.0.2",
4
- "description": "Comprehensive monitoring solution for Node.js applications with error tracking, health checks, and multi-channel notifications",
3
+ "version": "1.0.3",
4
+ "description": "Comprehensive monitoring solution for Node.js applications with error tracking, health checks, multi-database support (MongoDB, PostgreSQL, MySQL, MSSQL, SQLite, Redis, Cassandra, Elasticsearch, DynamoDB, Neo4j, CouchDB), and multi-channel notifications",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "test": "jest",
@@ -47,8 +47,19 @@
47
47
  "mongoose": "^7.0.0 || ^8.0.0",
48
48
  "pg": "^8.0.0",
49
49
  "mysql2": "^2.0.0 || ^3.0.0",
50
+ "mariadb": "^3.0.0",
51
+ "mssql": "^9.0.0 || ^10.0.0",
52
+ "tedious": "^15.0.0 || ^16.0.0",
53
+ "sqlite3": "^5.0.0",
54
+ "better-sqlite3": "^8.0.0 || ^9.0.0",
50
55
  "sequelize": "^6.0.0",
51
- "ioredis": "^5.0.0"
56
+ "ioredis": "^5.0.0",
57
+ "nano": "^10.0.0",
58
+ "cassandra-driver": "^4.0.0",
59
+ "@elastic/elasticsearch": "^8.0.0",
60
+ "@aws-sdk/client-dynamodb": "^3.0.0",
61
+ "aws-sdk": "^2.0.0",
62
+ "neo4j-driver": "^5.0.0"
52
63
  },
53
64
  "peerDependenciesMeta": {
54
65
  "express": {
@@ -63,11 +74,44 @@
63
74
  "mysql2": {
64
75
  "optional": true
65
76
  },
77
+ "mariadb": {
78
+ "optional": true
79
+ },
80
+ "mssql": {
81
+ "optional": true
82
+ },
83
+ "tedious": {
84
+ "optional": true
85
+ },
86
+ "sqlite3": {
87
+ "optional": true
88
+ },
89
+ "better-sqlite3": {
90
+ "optional": true
91
+ },
66
92
  "sequelize": {
67
93
  "optional": true
68
94
  },
69
95
  "ioredis": {
70
96
  "optional": true
97
+ },
98
+ "nano": {
99
+ "optional": true
100
+ },
101
+ "cassandra-driver": {
102
+ "optional": true
103
+ },
104
+ "@elastic/elasticsearch": {
105
+ "optional": true
106
+ },
107
+ "@aws-sdk/client-dynamodb": {
108
+ "optional": true
109
+ },
110
+ "aws-sdk": {
111
+ "optional": true
112
+ },
113
+ "neo4j-driver": {
114
+ "optional": true
71
115
  }
72
116
  },
73
117
  "devDependencies": {