replica-failover-mongodb-ts 3.0.3 → 3.0.9

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.
package/Readme.md CHANGED
@@ -120,7 +120,11 @@ O Node Balancer utiliza as seguintes tecnologias:
120
120
 
121
121
  ## Uso como Biblioteca (Library)
122
122
 
123
- Você pode usar o gerenciador de conexões resiliente deste projeto em sua própria aplicação Node.js.
123
+ Você pode usar o gerenciador de conexões resiliente deste projeto em sua própria aplicação Node.js ou NestJS.
124
+
125
+ ### Modo Simples (Recomendado)
126
+
127
+ Basta passar a string de conexão padrão do MongoDB. A lib detecta automaticamente os nós e o banco de dados.
124
128
 
125
129
  1. **Instale a lib:**
126
130
  ```bash
@@ -131,18 +135,113 @@ Você pode usar o gerenciador de conexões resiliente deste projeto em sua próp
131
135
  ```typescript
132
136
  import { ConnectionManager } from 'replica-failover-mongodb-ts';
133
137
 
138
+ // ✨ Plug & Play: Apenas a string de conexão!
134
139
  const db = new ConnectionManager({
135
- nodes: [
136
- 'mongodb://mongo1:27017/mydb',
137
- 'mongodb://mongo2:27017/mydb'
138
- ],
139
- healthCheckIntervalMs: 5000
140
+ connectionString: 'mongodb://mongo1:27017,mongo2:27017/mydb'
140
141
  });
141
142
 
142
143
  await db.init();
143
- const myCollection = db.getDb().collection('users');
144
+
145
+ // ✅ Failover automático para QUALQUER collection
146
+ // Você NÃO precisa configurar as collections antes. Basta usar o nome.
147
+
148
+ // Leitura na collection 'users'
149
+ const users = await db.read('users', c => c.find().toArray());
150
+
151
+ // Escrita na collection 'logs'
152
+ await db.write('logs', c => c.insertOne({ event: 'login' }));
153
+
154
+ // Leitura na collection 'products' com preferência Secundária
155
+ const products = await db.read('products', c => c.find().toArray(), {}, 'secondaryPreferred');
144
156
  ```
145
157
 
158
+ const products = await db.read('products', c => c.find().toArray(), {}, 'secondaryPreferred');
159
+ ```
160
+
161
+
162
+ ### 🛰️ Monitoramento e Status (Plug & Play)
163
+
164
+ Você pode verificar a saúde das conexões a qualquer momento ou ouvir eventos em tempo real.
165
+
166
+ **Verificar Status:**
167
+ ```typescript
168
+ const status = db.getStatus();
169
+ console.log(status);
170
+ /* Retorno:
171
+ {
172
+ isConnected: true,
173
+ dbName: 'mydb',
174
+ primary: 'mongodb://mongo1:27017/mydb',
175
+ secondaries: ['mongodb://mongo2:27017/mydb'],
176
+ totalNodes: 2
177
+ }
178
+ */
179
+ ```
180
+
181
+ **Ouvir Eventos (Real-time):**
182
+ A classe `ConnectionManager` emite eventos que você pode escutar:
183
+
184
+ ```typescript
185
+ db.on('failover-start', (reason) => {
186
+ console.warn('⚠️ O banco principal caiu! Iniciando failover...', reason);
187
+ });
188
+
189
+ db.on('failover-complete', ({ newPrimary }) => {
190
+ console.info('✅ Novo banco principal eleito:', newPrimary);
191
+ });
192
+
193
+ db.on('node-lost', ({ count }) => {
194
+ console.error('❌ Um nó secundário caiu. Total restante:', count);
195
+ });
196
+ ```
197
+
198
+ ```typescript
199
+ const db = new ConnectionManager({
200
+ nodes: [
201
+ 'mongodb://mongo1:27017/mydb',
202
+ 'mongodb://mongo2:27017/mydb'
203
+ ],
204
+ healthCheckIntervalMs: 5000,
205
+ minPoolSize: 5
206
+ });
207
+ ```
208
+
209
+ ### Uso com NestJS
210
+
211
+ Se você usa NestJS, a integração é nativa:
212
+
213
+ ```typescript
214
+ // app.module.ts
215
+ import { Module } from '@nestjs/common';
216
+ import { NodeBalancerModule } from 'replica-failover-mongodb-ts/dist/nestjs';
217
+
218
+ @Module({
219
+ imports: [
220
+ NodeBalancerModule.forRoot({
221
+ connectionString: 'mongodb://localhost:27017,localhost:27018/mydb',
222
+ }),
223
+ ],
224
+ })
225
+ export class AppModule {}
226
+ ```
227
+
228
+ E para usar nos seus services:
229
+
230
+ ```typescript
231
+ import { Injectable } from '@nestjs/common';
232
+ import { InjectConnectionManager } from 'replica-failover-mongodb-ts/dist/nestjs';
233
+ import { ConnectionManager } from 'replica-failover-mongodb-ts';
234
+
235
+ @Injectable()
236
+ export class UserService {
237
+ constructor(@InjectConnectionManager() private readonly db: ConnectionManager) {}
238
+
239
+ async getUsers() {
240
+ return this.db.read('users', (col) => col.find().toArray());
241
+ }
242
+ }
243
+ ```
244
+
146
245
  ---
147
246
 
148
247
  ## Visual Dashboard (Painel de Controle)
@@ -13,37 +13,87 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.ConnectionManager = void 0;
16
+ const events_1 = require("events");
16
17
  const mongodb_1 = require("mongodb");
17
18
  const axios_1 = __importDefault(require("axios"));
18
19
  const logger_1 = require("../middlewares/logger");
19
20
  const websocket_1 = require("./websocket");
20
21
  const metrics_1 = require("./metrics");
21
- class ConnectionManager {
22
+ class ConnectionManager extends events_1.EventEmitter {
22
23
  constructor(opts) {
23
- var _a;
24
+ var _a, _b, _c;
25
+ super();
24
26
  this.primaryClient = null;
25
27
  this.primaryDb = null;
28
+ this.secondaryClients = [];
29
+ this.rrIndex = 0;
26
30
  this.nodes = [];
27
31
  this.replicaUri = opts.replicaUri;
28
- this.nodes = (opts.nodes || []).map((u, i) => ({ uri: u, name: `node${i + 1}` }));
29
- this.dbName = opts.dbName || 'node-balancer';
30
32
  this.healthCheckIntervalMs = (_a = opts.healthCheckIntervalMs) !== null && _a !== void 0 ? _a : 5000;
31
33
  this.webhookUrl = opts.webhookUrl;
34
+ this.maxPoolSize = (_b = opts.maxPoolSize) !== null && _b !== void 0 ? _b : 20;
35
+ this.minPoolSize = (_c = opts.minPoolSize) !== null && _c !== void 0 ? _c : 1;
36
+ let parsedDbName;
37
+ if (opts.connectionString) {
38
+ parsedDbName = this.parseConnectionString(opts.connectionString);
39
+ }
40
+ else {
41
+ this.nodes = (opts.nodes || []).map((u, i) => ({ uri: u, name: `node${i + 1}` }));
42
+ }
43
+ // Use parsed dbName if allowed, or fallback to opts, or default
44
+ this.dbName = opts.dbName || parsedDbName || 'node-balancer';
45
+ }
46
+ parseConnectionString(uri) {
47
+ // Basic Regex to capture: protocol, auth(optional), hosts, db(optional), options(optional)
48
+ const regex = /^(mongodb(?:\+srv)?):\/\/((?:[^@\/]+@)?)?([^/?]+)(?:\/([^?]+))?(?:\?.*)?$/;
49
+ const match = uri.match(regex);
50
+ let foundDbName;
51
+ if (!match) {
52
+ logger_1.logger.warn('Invalid connection string format. Fallback to manual nodes if provided.');
53
+ return undefined;
54
+ }
55
+ const protocol = match[1];
56
+ const auth = match[2] || '';
57
+ const hostsPart = match[3];
58
+ const dbPart = match[4];
59
+ if (dbPart)
60
+ foundDbName = dbPart;
61
+ if (protocol === 'mongodb+srv') {
62
+ logger_1.logger.info('Detected SRV connection string. Using as replicaUri.');
63
+ this.replicaUri = uri;
64
+ }
65
+ else {
66
+ const hosts = hostsPart.split(',');
67
+ this.nodes = hosts.map((h, i) => ({
68
+ uri: `${protocol}://${auth}${h}`,
69
+ name: `node${i + 1}`
70
+ }));
71
+ logger_1.logger.info(`Parsed ${this.nodes.length} nodes from connection string.`);
72
+ }
73
+ return foundDbName;
32
74
  }
33
75
  init() {
34
76
  return __awaiter(this, void 0, void 0, function* () {
35
77
  logger_1.logger.info('ConnectionManager: init() starting');
36
- // First try replica uri if provided (let driver handle replica set)
78
+ // 1. Try Replica URI
37
79
  if (this.replicaUri) {
38
80
  logger_1.logger.info(`Trying replica URI: ${this.replicaUri}`);
39
81
  try {
40
- const c = new mongodb_1.MongoClient(this.replicaUri, { monitorCommands: true });
82
+ const c = new mongodb_1.MongoClient(this.replicaUri, {
83
+ monitorCommands: true,
84
+ minPoolSize: this.minPoolSize,
85
+ maxPoolSize: this.maxPoolSize
86
+ });
87
+ c._uri = this.replicaUri;
88
+ // Attach events BEFORE connecting to capture initial pool creation
89
+ this.attachPoolMonitor(c, 'primary', 'replica-uri');
41
90
  yield c.connect();
42
91
  const isWritable = yield this.checkWritable(c);
43
92
  if (isWritable) {
44
93
  logger_1.logger.info('Connected to replicaSet URI and found writable primary.');
45
94
  this.attachClient(c);
46
95
  this.startHealthChecks();
96
+ this.emit('ready', { mode: 'replica-set', uri: this.replicaUri });
47
97
  return;
48
98
  }
49
99
  else {
@@ -55,58 +105,102 @@ class ConnectionManager {
55
105
  logger_1.logger.warn(`Replica URI connect failed: ${err.message}`);
56
106
  }
57
107
  }
58
- // Fallback: iterate nodes and find a writable node
108
+ // 2. Fallback: Multi-node Manual Connection
109
+ logger_1.logger.info('Initializing multi-node connection...');
110
+ let connectedCount = 0;
59
111
  for (const n of this.nodes) {
60
- logger_1.logger.info(`Trying node ${n.uri}`);
112
+ logger_1.logger.info(`Connecting to node ${n.uri}`);
61
113
  try {
62
- const c = new mongodb_1.MongoClient(n.uri, { directConnection: true, monitorCommands: true });
114
+ const c = new mongodb_1.MongoClient(n.uri, {
115
+ directConnection: true,
116
+ monitorCommands: true,
117
+ minPoolSize: this.minPoolSize,
118
+ maxPoolSize: this.maxPoolSize
119
+ });
120
+ c._uri = n.uri;
121
+ this.attachPoolMonitor(c, 'unknown', n.uri);
63
122
  yield c.connect();
123
+ connectedCount++;
64
124
  const writable = yield this.checkWritable(c);
65
- if (writable) {
66
- logger_1.logger.info(`Found writable node at ${n.uri}`);
125
+ if (writable && !this.primaryClient) {
126
+ logger_1.logger.info(`Found Primary node at ${n.uri}`);
67
127
  this.attachClient(c);
68
- this.startHealthChecks();
69
- return;
70
128
  }
71
129
  else {
72
- yield c.close();
130
+ logger_1.logger.info(`Connected to Secondary node at ${n.uri}`);
131
+ this.secondaryClients.push(c);
73
132
  }
74
133
  }
75
134
  catch (err) {
76
135
  logger_1.logger.warn(`Node connect failed ${n.uri}: ${err.message}`);
77
136
  }
78
137
  }
79
- throw new Error('No writable MongoDB node found. Check your nodes/replica set.');
138
+ if (connectedCount === 0) {
139
+ throw new Error('No available MongoDB nodes found. Check your cluster.');
140
+ }
141
+ if (!this.primaryClient) {
142
+ logger_1.logger.warn('Initialized without a Primary node! System in Read-Only mode until a node becomes writable.');
143
+ this.emit('warn', 'Initialized in Read-Only mode (No Primary)');
144
+ }
145
+ else {
146
+ this.emit('ready', { mode: 'multi-node' });
147
+ }
148
+ this.startHealthChecks();
80
149
  });
81
150
  }
151
+ getStatus() {
152
+ return {
153
+ isConnected: !!this.primaryClient,
154
+ dbName: this.dbName,
155
+ primary: this.primaryClient ? this.primaryClient._uri : null,
156
+ secondaries: this.secondaryClients.map(c => c._uri),
157
+ totalNodes: (this.primaryClient ? 1 : 0) + this.secondaryClients.length
158
+ };
159
+ }
160
+ attachPoolMonitor(client, type, nodeUri) {
161
+ const label = { type, node: nodeUri };
162
+ client.on('connectionCreated', () => metrics_1.poolSize.inc(label));
163
+ client.on('connectionClosed', () => metrics_1.poolSize.dec(label));
164
+ client.on('connectionCheckedOut', () => {
165
+ metrics_1.poolCheckedOut.inc(label);
166
+ });
167
+ client.on('connectionCheckedIn', () => {
168
+ metrics_1.poolCheckedOut.dec(label);
169
+ });
170
+ client.on('connectionCheckOutStarted', () => metrics_1.poolWaitQueue.inc(label));
171
+ client.on('connectionCheckOutFailed', () => metrics_1.poolWaitQueue.dec(label));
172
+ client.on('connectionCheckedOut', () => metrics_1.poolWaitQueue.dec(label));
173
+ }
82
174
  attachClient(client) {
83
175
  this.primaryClient = client;
84
176
  this.primaryDb = client.db(this.dbName);
85
- // register monitoring events on the client
177
+ const uri = client._uri;
178
+ this.emit('primary-elected', { uri });
86
179
  client.on('topologyDescriptionChanged', (td) => {
87
180
  var _a;
88
- logger_1.logger.info(`topologyDescriptionChanged: ${JSON.stringify(this.summarizeTopology(td))}`);
89
181
  const summary = this.summarizeTopology(td);
90
182
  this.recordEvent('topologyDescriptionChanged', { td: summary }).catch(() => { });
91
183
  (_a = (0, websocket_1.getIO)()) === null || _a === void 0 ? void 0 : _a.emit('topology-change', summary);
184
+ this.emit('topology-change', summary);
92
185
  });
93
186
  client.on('serverHeartbeatFailed', (event) => {
94
187
  logger_1.logger.warn(`serverHeartbeatFailed: ${JSON.stringify(event)}`);
95
188
  this.recordEvent('serverHeartbeatFailed', { event }).catch(() => { });
96
189
  this.sendAlert('serverHeartbeatFailed', event).catch(() => { });
190
+ this.emit('server-heartbeat-failed', event);
97
191
  });
98
192
  client.on('serverHeartbeatSucceeded', (event) => {
99
- logger_1.logger.debug(`serverHeartbeatSucceeded: ${JSON.stringify(event)}`);
193
+ // verbose
100
194
  });
101
195
  client.on('close', () => {
102
196
  logger_1.logger.warn('MongoClient close event');
103
197
  this.recordEvent('clientClose', {}).catch(() => { });
198
+ this.emit('close');
104
199
  });
105
200
  logger_1.logger.info('Primary client attached.');
106
201
  metrics_1.connectionStatus.set(1);
107
202
  }
108
203
  summarizeTopology(td) {
109
- // gentle summary - driver topologyDescription shape may vary
110
204
  try {
111
205
  return {
112
206
  servers: Object.keys(td.servers || {}).map((k) => ({
@@ -123,15 +217,14 @@ class ConnectionManager {
123
217
  return __awaiter(this, void 0, void 0, function* () {
124
218
  try {
125
219
  const admin = client.db('admin');
126
- // isWritablePrimary command is supported
127
220
  const res = yield admin.command({ isWritablePrimary: 1 }).catch(() => null);
128
221
  if (res && res.isWritablePrimary)
129
222
  return true;
130
- // fallback: isMaster / hello
131
223
  const info = yield admin.command({ hello: 1 }).catch(() => null);
132
- if (info && (info.isWritablePrimary || info.isWritablePrimary === true))
224
+ if (info && (info.isWritablePrimary ||
225
+ info.isWritablePrimary === true ||
226
+ info.ismaster === true))
133
227
  return true;
134
- // if driver can't tell, assume writable if connected and write to a temp collection test (careful)
135
228
  return false;
136
229
  }
137
230
  catch (err) {
@@ -144,19 +237,14 @@ class ConnectionManager {
144
237
  return __awaiter(this, void 0, void 0, function* () {
145
238
  var _a;
146
239
  try {
147
- const event = {
148
- ts: new Date(),
149
- level: 'event',
150
- type,
151
- payload,
152
- };
240
+ const event = { ts: new Date(), level: 'event', type, payload };
153
241
  (_a = (0, websocket_1.getIO)()) === null || _a === void 0 ? void 0 : _a.emit('log', event);
154
242
  if (!this.primaryDb)
155
243
  return;
156
244
  yield this.primaryDb.collection('logs').insertOne(event);
157
245
  }
158
246
  catch (err) {
159
- logger_1.logger.warn('recordEvent failed: ' + err.message);
247
+ // logger.warn('recordEvent failed: ' + (err as Error).message);
160
248
  }
161
249
  });
162
250
  }
@@ -182,70 +270,111 @@ class ConnectionManager {
182
270
  }
183
271
  healthCheckLoop() {
184
272
  return __awaiter(this, void 0, void 0, function* () {
185
- // If primary still writable -> do nothing
273
+ // 1. Check Primary
186
274
  if (this.primaryClient) {
187
275
  const ok = yield this.checkWritable(this.primaryClient).catch(() => false);
188
- if (ok)
189
- return;
190
- logger_1.logger.warn('Primary client no longer writable. Will search for a writable node.');
191
- try {
192
- yield this.primaryClient.close();
276
+ if (!ok) {
277
+ logger_1.logger.warn('Primary client no longer writable. Demoting to potential secondary.');
278
+ this.secondaryClients.push(this.primaryClient);
279
+ this.primaryClient = null;
280
+ this.primaryDb = null;
281
+ metrics_1.connectionStatus.set(0);
282
+ this.emit('failover-start', { reason: 'Primary not writable' });
193
283
  }
194
- catch (_a) { }
195
- this.primaryClient = null;
196
- this.primaryDb = null;
197
- metrics_1.connectionStatus.set(0);
198
284
  }
199
- // attempt to find a writable node among nodes (directConnection)
200
- for (const n of this.nodes) {
285
+ // 2. Check Secondaries
286
+ for (let i = this.secondaryClients.length - 1; i >= 0; i--) {
287
+ const sec = this.secondaryClients[i];
201
288
  try {
202
- const c = new mongodb_1.MongoClient(n.uri, { directConnection: true, monitorCommands: true });
203
- yield c.connect();
204
- const writable = yield this.checkWritable(c);
205
- if (writable) {
206
- logger_1.logger.info(`Health-check: promoted ${n.uri} to primary connection`);
207
- this.attachClient(c);
208
- yield this.recordEvent('promote', { node: n.uri });
209
- yield this.sendAlert('promote', { node: n.uri, message: 'Promoted new primary connection' });
210
- metrics_1.failoverCount.inc();
211
- return;
212
- }
213
- else {
214
- yield c.close();
215
- }
289
+ yield sec.db('admin').command({ ping: 1 });
216
290
  }
217
291
  catch (err) {
218
- logger_1.logger.debug(`Health-check connect failed ${n.uri}: ${err.message}`);
292
+ logger_1.logger.warn('Secondary node lost connection. Removing.');
293
+ try {
294
+ yield sec.close();
295
+ }
296
+ catch (_a) { }
297
+ this.secondaryClients.splice(i, 1);
298
+ this.emit('node-lost', { count: this.secondaryClients.length });
299
+ }
300
+ }
301
+ // 3. Promote if needed
302
+ if (!this.primaryClient) {
303
+ // logger.warn('No Primary! Searching among secondaries...');
304
+ for (let i = 0; i < this.secondaryClients.length; i++) {
305
+ const client = this.secondaryClients[i];
306
+ const isWritable = yield this.checkWritable(client);
307
+ if (isWritable) {
308
+ logger_1.logger.info('Promoting secondary to Primary!');
309
+ this.attachClient(client);
310
+ this.secondaryClients.splice(i, 1);
311
+ yield this.recordEvent('promote', { message: 'Promoted secondary to primary' });
312
+ yield this.sendAlert('promote', { message: 'Promoted new primary connection' });
313
+ metrics_1.failoverCount.inc();
314
+ this.emit('failover-complete', { newPrimary: client._uri });
315
+ break;
316
+ }
219
317
  }
220
318
  }
221
- // no writable found
222
- logger_1.logger.error('Health-check: no writable nodes found.');
223
- yield this.recordEvent('no-writable', {});
224
- yield this.sendAlert('no-writable', { message: 'CRITICAL: No writable nodes found in cluster!' });
225
319
  });
226
320
  }
321
+ getSecondary() {
322
+ if (this.secondaryClients.length === 0)
323
+ return null;
324
+ const c = this.secondaryClients[this.rrIndex % this.secondaryClients.length];
325
+ this.rrIndex++;
326
+ return c;
327
+ }
227
328
  getDb() {
228
329
  return this.primaryDb;
229
330
  }
230
- // Generic wrappers that log operations to collection 'logs'
231
331
  read(collectionName_1, op_1) {
232
- return __awaiter(this, arguments, void 0, function* (collectionName, op, meta = {}) {
233
- const db = this.getDb();
332
+ return __awaiter(this, arguments, void 0, function* (collectionName, op, meta = {}, readPref = 'primary') {
333
+ let clientToUse = this.primaryClient;
334
+ let effectivePref = readPref;
335
+ if (readPref === 'secondary') {
336
+ const sec = this.getSecondary();
337
+ if (sec) {
338
+ clientToUse = sec;
339
+ }
340
+ else {
341
+ throw new Error('No secondary node available for read preference "secondary"');
342
+ }
343
+ }
344
+ else if (readPref === 'secondaryPreferred') {
345
+ const sec = this.getSecondary();
346
+ if (sec) {
347
+ clientToUse = sec;
348
+ effectivePref = 'secondary';
349
+ }
350
+ else {
351
+ clientToUse = this.primaryClient;
352
+ effectivePref = 'primary';
353
+ }
354
+ }
355
+ if (!clientToUse) {
356
+ throw new Error('No active connection available for requested read preference');
357
+ }
358
+ const db = clientToUse.db(this.dbName);
234
359
  const start = Date.now();
235
360
  try {
236
- if (!db)
237
- throw new Error('No DB connection');
238
- const res = yield op(db.collection(collectionName));
361
+ const collection = db.collection(collectionName);
362
+ const res = yield op(collection);
239
363
  const took = Date.now() - start;
240
364
  yield this.safeLog({
241
365
  ts: new Date(),
242
366
  op: 'read',
243
367
  collection: collectionName,
244
368
  success: true,
245
- meta,
369
+ meta: Object.assign(Object.assign({}, meta), { readPref: effectivePref }),
246
370
  durationMs: took,
247
371
  });
248
- metrics_1.operationDuration.observe({ operation: 'read', collection: collectionName, success: 'true' }, took / 1000);
372
+ metrics_1.operationDuration.observe({
373
+ operation: 'read',
374
+ collection: collectionName,
375
+ success: 'true',
376
+ read_preference: effectivePref
377
+ }, took / 1000);
249
378
  return res;
250
379
  }
251
380
  catch (err) {
@@ -256,10 +385,15 @@ class ConnectionManager {
256
385
  collection: collectionName,
257
386
  success: false,
258
387
  error: err.message,
259
- meta,
388
+ meta: Object.assign(Object.assign({}, meta), { readPref: effectivePref }),
260
389
  durationMs: took,
261
390
  });
262
- metrics_1.operationDuration.observe({ operation: 'read', collection: collectionName, success: 'false' }, took / 1000);
391
+ metrics_1.operationDuration.observe({
392
+ operation: 'read',
393
+ collection: collectionName,
394
+ success: 'false',
395
+ read_preference: effectivePref
396
+ }, took / 1000);
263
397
  throw err;
264
398
  }
265
399
  });
@@ -306,14 +440,13 @@ class ConnectionManager {
306
440
  try {
307
441
  (_a = (0, websocket_1.getIO)()) === null || _a === void 0 ? void 0 : _a.emit('log', doc);
308
442
  if (!this.primaryDb) {
309
- logger_1.logger.warn('safeLog: no primaryDb, skipping db log. Logging to console instead.');
310
- logger_1.logger.info(JSON.stringify(doc));
443
+ // logger.warn('safeLog: no primaryDb, skipping db log.');
311
444
  return;
312
445
  }
313
446
  yield this.primaryDb.collection('logs').insertOne(doc);
314
447
  }
315
448
  catch (err) {
316
- logger_1.logger.warn('safeLog failed: ' + err.message);
449
+ // silent fail for log
317
450
  }
318
451
  });
319
452
  }
@@ -321,11 +454,14 @@ class ConnectionManager {
321
454
  return __awaiter(this, void 0, void 0, function* () {
322
455
  if (this.healthInterval)
323
456
  clearInterval(this.healthInterval);
324
- if (this.primaryClient) {
457
+ if (this.primaryClient)
325
458
  yield this.primaryClient.close().catch(() => { });
326
- this.primaryClient = null;
327
- this.primaryDb = null;
328
- }
459
+ for (const c of this.secondaryClients)
460
+ yield c.close().catch(() => { });
461
+ this.primaryClient = null;
462
+ this.primaryDb = null;
463
+ this.secondaryClients = [];
464
+ this.removeAllListeners();
329
465
  });
330
466
  }
331
467
  }
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.operationDuration = exports.failoverCount = exports.connectionStatus = exports.register = void 0;
6
+ exports.poolWaitQueue = exports.poolCheckedOut = exports.poolSize = exports.operationDuration = exports.failoverCount = exports.connectionStatus = exports.register = void 0;
7
7
  const prom_client_1 = __importDefault(require("prom-client"));
8
8
  // Create a Registry
9
9
  exports.register = new prom_client_1.default.Registry();
@@ -23,7 +23,25 @@ exports.failoverCount = new prom_client_1.default.Counter({
23
23
  exports.operationDuration = new prom_client_1.default.Histogram({
24
24
  name: 'node_balancer_operation_duration_seconds',
25
25
  help: 'Duration of database operations in seconds',
26
- labelNames: ['operation', 'collection', 'success'],
26
+ labelNames: ['operation', 'collection', 'success', 'read_preference'],
27
27
  buckets: [0.01, 0.05, 0.1, 0.5, 1, 2, 5],
28
28
  registers: [exports.register]
29
29
  });
30
+ exports.poolSize = new prom_client_1.default.Gauge({
31
+ name: 'node_balancer_pool_size',
32
+ help: 'Current size of the connection pool',
33
+ labelNames: ['type', 'node'],
34
+ registers: [exports.register]
35
+ });
36
+ exports.poolCheckedOut = new prom_client_1.default.Gauge({
37
+ name: 'node_balancer_pool_checked_out',
38
+ help: 'Number of connections currently checked out',
39
+ labelNames: ['type', 'node'],
40
+ registers: [exports.register]
41
+ });
42
+ exports.poolWaitQueue = new prom_client_1.default.Gauge({
43
+ name: 'node_balancer_pool_wait_queue',
44
+ help: 'Number of requests waiting for a connection',
45
+ labelNames: ['type', 'node'],
46
+ registers: [exports.register]
47
+ });
package/dist/index.js CHANGED
@@ -1,5 +1,20 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
2
16
  Object.defineProperty(exports, "__esModule", { value: true });
3
17
  exports.ConnectionManager = void 0;
4
18
  var connectionManager_1 = require("./config/connectionManager");
5
19
  Object.defineProperty(exports, "ConnectionManager", { enumerable: true, get: function () { return connectionManager_1.ConnectionManager; } });
20
+ __exportStar(require("./nestjs"), exports);
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./node-balancer.module"), exports);
18
+ __exportStar(require("./node-balancer.decorators"), exports);
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NODE_BALANCER_CONNECTION = void 0;
4
+ exports.InjectConnectionManager = InjectConnectionManager;
5
+ const common_1 = require("@nestjs/common");
6
+ exports.NODE_BALANCER_CONNECTION = 'NODE_BALANCER_CONNECTION';
7
+ function InjectConnectionManager() {
8
+ return (0, common_1.Inject)(exports.NODE_BALANCER_CONNECTION);
9
+ }
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
9
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
10
+ return new (P || (P = Promise))(function (resolve, reject) {
11
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
12
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
13
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
14
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
15
+ });
16
+ };
17
+ var NodeBalancerModule_1;
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.NodeBalancerModule = void 0;
20
+ const common_1 = require("@nestjs/common");
21
+ const connectionManager_1 = require("../config/connectionManager");
22
+ const node_balancer_decorators_1 = require("./node-balancer.decorators");
23
+ let NodeBalancerModule = NodeBalancerModule_1 = class NodeBalancerModule {
24
+ static forRoot(options) {
25
+ const connectionProvider = {
26
+ provide: node_balancer_decorators_1.NODE_BALANCER_CONNECTION,
27
+ useFactory: () => __awaiter(this, void 0, void 0, function* () {
28
+ console.log('DEBUG: NodeBalancerModule useFactory called');
29
+ const manager = new connectionManager_1.ConnectionManager(options);
30
+ console.log('DEBUG: Manager created, initializing...');
31
+ yield manager.init();
32
+ console.log('DEBUG: Manager initialized successfully');
33
+ return manager;
34
+ }),
35
+ };
36
+ return {
37
+ module: NodeBalancerModule_1,
38
+ providers: [connectionProvider],
39
+ exports: [connectionProvider],
40
+ };
41
+ }
42
+ static forRootAsync(options) {
43
+ const connectionProvider = {
44
+ provide: node_balancer_decorators_1.NODE_BALANCER_CONNECTION,
45
+ useFactory: (...args) => __awaiter(this, void 0, void 0, function* () {
46
+ const config = yield options.useFactory(...args);
47
+ const manager = new connectionManager_1.ConnectionManager(config);
48
+ yield manager.init();
49
+ return manager;
50
+ }),
51
+ inject: options.inject || [],
52
+ };
53
+ return {
54
+ module: NodeBalancerModule_1,
55
+ imports: options.imports || [],
56
+ providers: [connectionProvider],
57
+ exports: [connectionProvider],
58
+ };
59
+ }
60
+ };
61
+ exports.NodeBalancerModule = NodeBalancerModule;
62
+ exports.NodeBalancerModule = NodeBalancerModule = NodeBalancerModule_1 = __decorate([
63
+ (0, common_1.Global)(),
64
+ (0, common_1.Module)({})
65
+ ], NodeBalancerModule);
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const connectionManager_1 = require("../config/connectionManager");
13
+ function testConnectionString() {
14
+ return __awaiter(this, void 0, void 0, function* () {
15
+ console.log('--- Testing Single Connection String Support ---');
16
+ // Test 1: Standard Multi-node string
17
+ const uri = 'mongodb://localhost:27017,localhost:27018/test_db_string';
18
+ console.log(`Testing URI: ${uri}`);
19
+ const cm = new connectionManager_1.ConnectionManager({
20
+ connectionString: uri,
21
+ minPoolSize: 1,
22
+ maxPoolSize: 2
23
+ });
24
+ // internal check (using any to bypass private check for test)
25
+ const nodes = cm.nodes;
26
+ console.log('Parsed Nodes:', nodes);
27
+ const dbName = cm.dbName;
28
+ console.log('Parsed DB Name:', dbName);
29
+ if (nodes.length !== 2)
30
+ console.error('❌ Incorrect node count parsed');
31
+ else
32
+ console.log('✅ Node count correct');
33
+ if (dbName !== 'test_db_string')
34
+ console.error('❌ Incorrect DB name parsed');
35
+ else
36
+ console.log('✅ DB name correct');
37
+ try {
38
+ console.log('Attempting connection (might fail if no DB)...');
39
+ yield cm.init();
40
+ console.log('✅ Init success with connection string');
41
+ const db = cm.getDb();
42
+ if (db) {
43
+ console.log('✅ DB Connection active');
44
+ }
45
+ else {
46
+ console.warn('⚠️ No active DB (might be no primary available, but parse worked)');
47
+ }
48
+ }
49
+ catch (err) {
50
+ console.warn('⚠️ Connection failed (expected if DB is down):', err.message);
51
+ console.log('✅ Parsing logic verified independently of connection.');
52
+ }
53
+ finally {
54
+ yield cm.close();
55
+ }
56
+ });
57
+ }
58
+ testConnectionString();
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const connectionManager_1 = require("../config/connectionManager");
13
+ function testMonitoring() {
14
+ return __awaiter(this, void 0, void 0, function* () {
15
+ console.log('--- Testing Monitoring & Events ---');
16
+ const uri = 'mongodb://localhost:27017,localhost:27018/test_monitor_db';
17
+ const cm = new connectionManager_1.ConnectionManager({
18
+ connectionString: uri,
19
+ minPoolSize: 1
20
+ });
21
+ // Check Initial Status
22
+ console.log('Initial Status:', cm.getStatus());
23
+ // Listen to Events
24
+ cm.on('ready', (info) => {
25
+ console.log('✅ EVENT: ready', info);
26
+ });
27
+ cm.on('primary-elected', (info) => {
28
+ console.log('✅ EVENT: primary-elected', info);
29
+ });
30
+ try {
31
+ console.log('Initializing...');
32
+ // We expect this to fail connecting if DB is down, but 'warn' event might emit?
33
+ // Or if it connects, 'ready' emits.
34
+ yield cm.init();
35
+ console.log('Post-Init Status:', cm.getStatus());
36
+ }
37
+ catch (err) {
38
+ console.warn('⚠️ Connection failed (expected if DB is down). Checking status anyway...');
39
+ console.log('Final Status:', cm.getStatus());
40
+ }
41
+ finally {
42
+ yield cm.close();
43
+ }
44
+ });
45
+ }
46
+ testMonitoring();
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
15
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
16
+ return new (P || (P = Promise))(function (resolve, reject) {
17
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
18
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
19
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
20
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
21
+ });
22
+ };
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ require("reflect-metadata");
25
+ const common_1 = require("@nestjs/common");
26
+ const core_1 = require("@nestjs/core");
27
+ const node_balancer_module_1 = require("../nestjs/node-balancer.module");
28
+ const node_balancer_decorators_1 = require("../nestjs/node-balancer.decorators");
29
+ const connectionManager_1 = require("../config/connectionManager");
30
+ process.on('unhandledRejection', (reason, p) => {
31
+ console.error('Unhandled Rejection at:', p, 'reason:', reason);
32
+ process.exit(1);
33
+ });
34
+ process.on('uncaughtException', (err) => {
35
+ console.error('Uncaught Exception:', err);
36
+ process.exit(1);
37
+ });
38
+ let AppService = class AppService {
39
+ constructor(connectionManager) {
40
+ this.connectionManager = connectionManager;
41
+ }
42
+ check() {
43
+ return __awaiter(this, void 0, void 0, function* () {
44
+ if (this.connectionManager) {
45
+ console.log('✅ ConnectionManager injected successfully!');
46
+ const db = this.connectionManager.getDb();
47
+ console.log(`✅ Database context available: ${db ? 'Yes' : 'No (init might be pending or failed)'}`);
48
+ return 'ok';
49
+ }
50
+ else {
51
+ console.error('❌ ConnectionManager failed to inject.');
52
+ return 'fail';
53
+ }
54
+ });
55
+ }
56
+ };
57
+ AppService = __decorate([
58
+ (0, common_1.Injectable)(),
59
+ __param(0, (0, node_balancer_decorators_1.InjectConnectionManager)()),
60
+ __metadata("design:paramtypes", [connectionManager_1.ConnectionManager])
61
+ ], AppService);
62
+ let AppController = class AppController {
63
+ constructor(appService) {
64
+ this.appService = appService;
65
+ }
66
+ getHello() {
67
+ return this.appService.check();
68
+ }
69
+ };
70
+ __decorate([
71
+ (0, common_1.Get)(),
72
+ __metadata("design:type", Function),
73
+ __metadata("design:paramtypes", []),
74
+ __metadata("design:returntype", void 0)
75
+ ], AppController.prototype, "getHello", null);
76
+ AppController = __decorate([
77
+ (0, common_1.Controller)(),
78
+ __metadata("design:paramtypes", [AppService])
79
+ ], AppController);
80
+ let AppModule = class AppModule {
81
+ };
82
+ AppModule = __decorate([
83
+ (0, common_1.Module)({
84
+ imports: [
85
+ node_balancer_module_1.NodeBalancerModule.forRoot({
86
+ nodes: ['mongodb://localhost:27017', 'mongodb://localhost:27018', 'mongodb://localhost:27019'],
87
+ minPoolSize: 1,
88
+ maxPoolSize: 5,
89
+ dbName: 'test_nest'
90
+ })
91
+ ],
92
+ controllers: [AppController],
93
+ providers: [AppService],
94
+ })
95
+ ], AppModule);
96
+ function manualTest() {
97
+ return __awaiter(this, void 0, void 0, function* () {
98
+ console.log('DEBUG: Starting manual test...');
99
+ try {
100
+ const m = new connectionManager_1.ConnectionManager({
101
+ nodes: ['mongodb://localhost:27017', 'mongodb://localhost:27018', 'mongodb://localhost:27019'],
102
+ minPoolSize: 1,
103
+ maxPoolSize: 5
104
+ });
105
+ yield m.init();
106
+ console.log('DEBUG: Manual test passed');
107
+ // await m.close(); // Keep it open or close? Close.
108
+ }
109
+ catch (e) {
110
+ console.error('DEBUG: Manual test failed', e);
111
+ }
112
+ });
113
+ }
114
+ function bootstrap() {
115
+ return __awaiter(this, void 0, void 0, function* () {
116
+ console.log('🚀 Starting NestJS Context...');
117
+ yield manualTest();
118
+ try {
119
+ const app = yield core_1.NestFactory.createApplicationContext(AppModule, { logger: ['error', 'warn', 'debug', 'verbose'] });
120
+ const service = app.get(AppService);
121
+ yield service.check();
122
+ yield app.close();
123
+ console.log('✅ NestJS Test Finished.');
124
+ }
125
+ catch (error) {
126
+ console.error('❌ NestJS Boot failed:', error);
127
+ process.exit(1);
128
+ }
129
+ });
130
+ }
131
+ bootstrap();
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ const connectionManager_1 = require("../config/connectionManager");
16
+ const dotenv_1 = __importDefault(require("dotenv"));
17
+ dotenv_1.default.config({ path: '.env.local' });
18
+ function run() {
19
+ return __awaiter(this, void 0, void 0, function* () {
20
+ const envNodes = process.env.MONGODB_NODES || '';
21
+ const nodes = envNodes ? envNodes.split(',') : [
22
+ 'mongodb://localhost:27017',
23
+ 'mongodb://localhost:27018',
24
+ 'mongodb://localhost:27019'
25
+ ];
26
+ console.log('--- Testing Read Preference and Multi-Node support ---');
27
+ console.log(`Nodes: ${nodes.join(', ')}`);
28
+ const db = new connectionManager_1.ConnectionManager({
29
+ nodes,
30
+ healthCheckIntervalMs: 2000,
31
+ maxPoolSize: 5,
32
+ minPoolSize: 1
33
+ });
34
+ try {
35
+ yield db.init();
36
+ console.log('✅ Initialization complete.');
37
+ // Test Write (Primary)
38
+ console.log('📝 Testing Write (Primary)...');
39
+ try {
40
+ yield db.write('test_reads', (col) => __awaiter(this, void 0, void 0, function* () {
41
+ yield col.insertOne({ test: 'read_pref', date: new Date() });
42
+ }));
43
+ console.log('✅ Write successful.');
44
+ }
45
+ catch (err) {
46
+ console.warn('⚠️ Write failed (No Primary?), skipping to read tests... Error:', err.message);
47
+ }
48
+ // Test Read (Primary default)
49
+ console.log('📖 Testing Read (Primary Default)...');
50
+ try {
51
+ const res1 = yield db.read('test_reads', (col) => __awaiter(this, void 0, void 0, function* () {
52
+ return col.findOne({ test: 'read_pref' });
53
+ }));
54
+ console.log('✅ Read Primary result:', res1 === null || res1 === void 0 ? void 0 : res1._id);
55
+ }
56
+ catch (err) {
57
+ console.warn('⚠️ Read Primary failed (No Primary?), skipping... Error:', err.message);
58
+ }
59
+ // Test Read (Secondary Strict)
60
+ console.log('📖 Testing Read (Secondary Strict)...');
61
+ try {
62
+ const res2 = yield db.read('test_reads', (col) => __awaiter(this, void 0, void 0, function* () {
63
+ return col.findOne({ test: 'read_pref' });
64
+ }), {}, 'secondary'); // <--- requesting secondary
65
+ console.log('✅ Read Secondary result:', res2 === null || res2 === void 0 ? void 0 : res2._id);
66
+ }
67
+ catch (err) {
68
+ console.error('❌ Read Secondary failed:', err.message);
69
+ }
70
+ // Test Read (Secondary Preferred)
71
+ console.log('📖 Testing Read (Secondary Preferred)...');
72
+ const res3 = yield db.read('test_reads', (col) => __awaiter(this, void 0, void 0, function* () {
73
+ return col.findOne({ test: 'read_pref' });
74
+ }), {}, 'secondaryPreferred');
75
+ console.log('✅ Read SecondaryPreferred result:', res3 === null || res3 === void 0 ? void 0 : res3._id);
76
+ }
77
+ catch (err) {
78
+ console.error('❌ Test failed:', err);
79
+ }
80
+ finally {
81
+ yield db.close();
82
+ console.log('👋 Connection closed.');
83
+ process.exit(0);
84
+ }
85
+ });
86
+ }
87
+ run();
package/dist/server.js CHANGED
@@ -23,7 +23,8 @@ app_1.default.get('/metrics', (req, res) => __awaiter(void 0, void 0, void 0, fu
23
23
  res.end(yield metrics_1.register.metrics());
24
24
  }
25
25
  catch (ex) {
26
- res.status(500).end(ex);
26
+ logger_1.logger.error('Error while serving /metrics endpoint', ex);
27
+ res.status(500).end('Internal server error');
27
28
  }
28
29
  }));
29
30
  const server = app_1.default.listen(PORT, () => {
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "replica-failover-mongodb-ts",
3
- "version": "3.0.3",
3
+ "version": "3.0.9",
4
4
  "description": "O sistema replica um dos conceitos básicos da arquitetura de sistemas distribuído, na qual ele tem uma API em express.js com TypeScript, e usando o mongoDB com replicaSET e failover, consegue automáticamente garantir disponibilidade e redundância de dados das requisições.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "scripts": {
8
+ "test": "node dist/scripts/test_read_pref.js",
8
9
  "dev": "nodemon --exec ts-node src/server.ts",
9
10
  "build": "tsc",
10
11
  "start": "node dist/server.js",
@@ -29,6 +30,11 @@
29
30
  "keywords": [],
30
31
  "author": "",
31
32
  "license": "ISC",
33
+ "peerDependencies": {
34
+ "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0",
35
+ "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0",
36
+ "rxjs": "^7.0.0"
37
+ },
32
38
  "dependencies": {
33
39
  "@types/blessed": "^0.1.26",
34
40
  "@types/inquirer": "^9.0.9",
@@ -46,11 +52,16 @@
46
52
  "winston": "^3.18.3"
47
53
  },
48
54
  "devDependencies": {
55
+ "@nestjs/common": "^11.1.13",
56
+ "@nestjs/core": "^11.1.13",
57
+ "@nestjs/platform-express": "^11.1.13",
49
58
  "@types/express": "^5.0.3",
50
59
  "@types/morgan": "^1.9.10",
51
60
  "@types/node": "^24.8.0",
52
61
  "nodemon": "^3.1.10",
62
+ "reflect-metadata": "^0.2.2",
63
+ "rxjs": "^7.8.2",
53
64
  "ts-node": "^10.9.2",
54
- "typescript": "^5.9.3"
65
+ "typescript": "^5.6.3"
55
66
  }
56
67
  }