infinispan 0.13.0 → 0.14.0

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/lib/io.js CHANGED
@@ -13,24 +13,35 @@
13
13
  var f = require('./functional');
14
14
  var u = require('./utils');
15
15
  var codec = require('./codec');
16
+ var protocols = require('./protocols');
16
17
 
17
18
  module.exports = transport;
18
19
 
19
20
  var DEFAULT_CLUSTER_NAME = '___DEFAULT-CLUSTER___';
20
21
 
22
+ /**
23
+ * Formats an array of connections as a display string.
24
+ * @param {Array} conns - Array of connection objects.
25
+ * @returns {string} Bracketed comma-separated string of connections.
26
+ */
21
27
  function showArrayConnections(conns) {
22
- return ["[", _.map(conns, function(c) { return c.toString(); }).join(","), "]"].join('');
28
+ return ['[', _.map(conns, function(c) { return c.toString(); }).join(','), ']'].join('');
23
29
  }
24
30
 
25
31
  var Connection = function(addr, transport, listeners) {
26
32
  var id = _.uniqueId('conn_');
27
- var logger = u.logger(transport.getId() + '_' + id);
33
+ var logger = u.logger(`${transport.getId() }_${ id}`);
28
34
  var sock = new net.Socket();
29
35
  var replayable = u.replayableBuffer();
30
- var authDone = false;
31
-
36
+ /** @returns {string} Formatted address string. */
32
37
  function show() { return u.showAddress(addr); }
33
38
 
39
+ /**
40
+ * Creates a callback for the socket connect event.
41
+ * @param {Function} fulfill - Promise resolve function.
42
+ * @param {Object} conn - The connection object.
43
+ * @returns {Function} Connect event handler.
44
+ */
34
45
  function onConnect(fulfill, conn) {
35
46
  return function() {
36
47
  logger.debugf('Connected to %s', show());
@@ -38,6 +49,11 @@
38
49
  };
39
50
  }
40
51
 
52
+ /**
53
+ * Creates a callback for socket error events.
54
+ * @param {Function} reject - Promise reject function.
55
+ * @returns {Function} Error event handler.
56
+ */
41
57
  function onError(reject) {
42
58
  return function(err) {
43
59
  logger.error('Error from %s: %s', show(), err.message);
@@ -46,6 +62,10 @@
46
62
  };
47
63
  }
48
64
 
65
+ /**
66
+ * Handles the socket end (disconnect) event.
67
+ * @returns {void}
68
+ */
49
69
  function onEnd() {
50
70
  logger.debugf('Disconnected from %s', show());
51
71
  // TODO: Try retrying on disconnect when socket.end is invoked
@@ -56,29 +76,57 @@
56
76
  // transport.retryRpcs(addr); // retry RPCs in case of disconnect
57
77
  }
58
78
 
79
+ /**
80
+ * Rewinds the replayable buffer when a response is incomplete.
81
+ * @returns {void}
82
+ */
59
83
  function rewind() {
60
- logger.tracef("Incomplete response or event, rewind buffer and wait more data");
84
+ logger.tracef('Incomplete response or event, rewind buffer and wait more data');
61
85
  replayable.rewind();
62
86
  }
63
87
 
88
+ /**
89
+ * Trims the replayable buffer after a successful decode.
90
+ * @param {Object} header - Decoded message header.
91
+ * @param {Object} bytebuf - Buffer with current read offset.
92
+ * @returns {void}
93
+ */
64
94
  function trim(header, bytebuf) {
65
- logger.tracef("After decoding request(msgId=%d), buffer size is %d, and offset %d",
95
+ logger.tracef('After decoding request(msgId=%d), buffer size is %d, and offset %d',
66
96
  header.msgId, bytebuf.buf.length, bytebuf.offset);
67
97
  replayable.trim(bytebuf);
68
98
  }
69
99
 
100
+ /**
101
+ * Waits for topology installation before completing the RPC, if needed.
102
+ * @param {Object} rpc - The pending RPC with success/fail callbacks.
103
+ * @param {Object} header - Decoded response header.
104
+ * @param {boolean} isError - Whether the response is an error.
105
+ * @param {Promise|undefined} topology - Pending topology update promise.
106
+ * @param {Object} body - Decoded response body.
107
+ * @returns {void}
108
+ */
70
109
  function waitTopologyThenCompleteRpc(rpc, header, isError, topology, body) {
71
110
  if (header.hasNewTopology) {
72
111
  // If new topology is received, rpc needs to be
73
112
  // completed after new topology has been installed
74
113
  topology.then(function() {
75
114
  completeRpc(rpc, header, isError, topology, body);
76
- })
115
+ });
77
116
  } else {
78
117
  completeRpc(rpc, header, isError, topology, body);
79
118
  }
80
119
  }
81
120
 
121
+ /**
122
+ * Completes an RPC by invoking its success or failure callback.
123
+ * @param {Object} rpc - The pending RPC with success/fail callbacks.
124
+ * @param {Object} header - Decoded response header.
125
+ * @param {boolean} isError - Whether the response is an error.
126
+ * @param {Promise|undefined} topology - Pending topology update promise.
127
+ * @param {Object} body - Decoded response body.
128
+ * @returns {void}
129
+ */
82
130
  function completeRpc(rpc, header, isError, topology, body) {
83
131
  logger.tracel(function() { return [
84
132
  'Complete %s for request(msgId=%d) with %s',
@@ -92,6 +140,11 @@
92
140
  transport.removeRpc(header.msgId);
93
141
  }
94
142
 
143
+ /**
144
+ * Handles incoming socket data by decoding headers, topologies, events, and RPC bodies.
145
+ * @param {Buffer} data - Raw data received from the socket.
146
+ * @returns {void}
147
+ */
95
148
  function onData(data) {
96
149
  if (!f.existy(replayable))
97
150
  replayable = u.replayableBuffer();
@@ -141,6 +194,13 @@
141
194
  replayable = null;
142
195
  }
143
196
 
197
+ /**
198
+ * Decodes an error response and completes the associated RPC.
199
+ * @param {Object} header - Decoded response header.
200
+ * @param {Object} bytebuf - Buffer to decode from.
201
+ * @param {Promise|undefined} topology - Pending topology update promise.
202
+ * @returns {boolean} Whether decoding can continue.
203
+ */
144
204
  function decodeError(header, bytebuf, topology) {
145
205
  var protocol = transport.getProtocol();
146
206
  var body = protocol.decodeError(header, bytebuf);
@@ -156,6 +216,13 @@
156
216
  return canDecodeMore;
157
217
  }
158
218
 
219
+ /**
220
+ * Decodes a successful RPC response body and completes the associated RPC.
221
+ * @param {Object} header - Decoded response header.
222
+ * @param {Object} bytebuf - Buffer to decode from.
223
+ * @param {Promise|undefined} topology - Pending topology update promise.
224
+ * @returns {boolean} Whether decoding can continue.
225
+ */
159
226
  function decodeRpcBody(header, bytebuf, topology) {
160
227
  var protocol = transport.getProtocol();
161
228
  var rpc = transport.findRpc(header.msgId);
@@ -172,6 +239,12 @@
172
239
  }
173
240
  }
174
241
 
242
+ /**
243
+ * Builds TLS socket options from SSL configuration.
244
+ * @param {Object} sslOpts - SSL options including certificates and protocols.
245
+ * @param {Function} reject - Promise reject function for validation errors.
246
+ * @returns {Object} TLS connection options.
247
+ */
175
248
  function sslSocketOpts(sslOpts, reject) {
176
249
  var options = {};
177
250
 
@@ -188,6 +261,12 @@
188
261
  return options;
189
262
  }
190
263
 
264
+ /**
265
+ * Adds client authentication options to the TLS options.
266
+ * @param {Object} options - Existing TLS options.
267
+ * @param {Object} authOpts - Client auth config with key, cert, and passphrase.
268
+ * @returns {Object} Updated TLS options.
269
+ */
191
270
  function sslClientAuthOpts(options, authOpts) {
192
271
  if (f.existy(authOpts)) {
193
272
  options.key = readFileIfExists(authOpts, 'key');
@@ -197,13 +276,20 @@
197
276
  return options;
198
277
  }
199
278
 
279
+ /**
280
+ * Adds PKCS#12 crypto store options to the TLS options.
281
+ * @param {Object} options - Existing TLS options.
282
+ * @param {Object} storeOpts - Crypto store config with path and passphrase.
283
+ * @param {Function} reject - Promise reject function for validation errors.
284
+ * @returns {Object} Updated TLS options.
285
+ */
200
286
  function sslCryptoStoreOpts(options, storeOpts, reject) {
201
287
  if (f.existy(storeOpts)) {
202
288
  if (!f.existy(storeOpts.path))
203
- reject(new Error('No path defined for crypto store'));
289
+ return reject(new Error('No path defined for crypto store'));
204
290
 
205
291
  if (!f.existy(storeOpts.passphrase))
206
- reject(new Error('No passphrase defined for crypto store'));
292
+ return reject(new Error('No passphrase defined for crypto store'));
207
293
 
208
294
  options.pfx = fs.readFileSync(storeOpts.path);
209
295
  options.passphrase = storeOpts.passphrase;
@@ -211,16 +297,35 @@
211
297
  return options;
212
298
  }
213
299
 
300
+ /**
301
+ * Reads a file specified by a property on the parent object, if defined.
302
+ * @param {Object} parent - Object containing the file path property.
303
+ * @param {string} opt - Property name whose value is the file path.
304
+ * @returns {Buffer|undefined} File contents or undefined.
305
+ */
214
306
  function readFileIfExists(parent, opt) {
215
307
  return f.existy(parent[opt])
216
308
  ? fs.readFileSync(parent[opt])
217
309
  : undefined;
218
310
  }
219
311
 
312
+ /**
313
+ * Connects via a plain (non-TLS) TCP socket.
314
+ * @param {Function} fulfill - Promise resolve function.
315
+ * @param {Object} conn - The connection object.
316
+ * @returns {Object} The socket instance.
317
+ */
220
318
  function connectPlainSocket(fulfill, conn) {
221
319
  return sock.connect(addr.port, addr.host, onConnect(fulfill, conn));
222
320
  }
223
321
 
322
+ /**
323
+ * Connects via a TLS-encrypted socket.
324
+ * @param {Function} fulfill - Promise resolve function.
325
+ * @param {Function} reject - Promise reject function.
326
+ * @param {Object} conn - The connection object.
327
+ * @returns {Object} The TLS socket instance.
328
+ */
224
329
  function connectSslSocket(fulfill, reject, conn) {
225
330
  var sslOpts = transport.sslOpts();
226
331
  var options = sslSocketOpts(sslOpts, reject);
@@ -257,7 +362,7 @@
257
362
  },
258
363
  write: function(buffer) {
259
364
  return new Promise(function (fulfill, reject) {
260
- var flushed = sock.write(buffer, (err) => {
365
+ var flushed = sock.write(buffer, err => {
261
366
  if (err) {
262
367
  logger.error('Error writing to socket: %s', err);
263
368
  transport.retryRpcs(addr); // retry RPCs in case of error
@@ -274,7 +379,7 @@
274
379
  return addr;
275
380
  },
276
381
  toString: function() {
277
- return show() + '@' + id;
382
+ return `${show() }@${ id}`;
278
383
  }
279
384
  };
280
385
  return conn;
@@ -284,6 +389,11 @@
284
389
  var i = 0;
285
390
  var mh3 = u.murmurHash3();
286
391
 
392
+ /**
393
+ * Finds connections not present in the given address list.
394
+ * @param {Array} addrs - Array of server addresses to check against.
395
+ * @returns {Array} Connections missing from the address list.
396
+ */
287
397
  function filterNot(addrs) {
288
398
  var missing = _.filter(conns, function(c) {
289
399
  return !_.where(addrs, c.getAddress()).length > 0;
@@ -293,34 +403,64 @@
293
403
  return missing;
294
404
  }
295
405
 
406
+ /**
407
+ * Returns the next connection using round-robin selection.
408
+ * @returns {Object} The next connection in rotation.
409
+ */
296
410
  function getConnectionRoundRobin() {
297
411
  var conn = conns[i++];
298
412
  if (i >= conns.length) i = 0;
299
413
  return conn;
300
414
  }
301
415
 
416
+ /**
417
+ * Finds a connection whose address has not been tried yet.
418
+ * @param {Array} triedAddrs - Array of already-tried addresses.
419
+ * @returns {Object|undefined} An untried connection, or undefined.
420
+ */
302
421
  function findConnectionUntried(triedAddrs) {
303
422
  return _.find(conns, function(c) {
304
423
  return !_.contains(triedAddrs, c.getAddress());
305
424
  });
306
425
  }
307
426
 
427
+ /**
428
+ * Computes the size of each hash segment.
429
+ * @param {number} numSegments - Total number of segments.
430
+ * @returns {number} The size of each segment.
431
+ */
308
432
  function getSegmentSize(numSegments) {
309
433
  return Math.abs(Math.ceil((1 << 31) / numSegments));
310
434
  }
311
435
 
436
+ /**
437
+ * Computes a non-negative MurmurHash3 hash for the given buffer.
438
+ * @param {Buffer} buf - The buffer to hash.
439
+ * @returns {number} Non-negative 32-bit hash value.
440
+ */
312
441
  function getNormalizedHash(buf) {
313
442
  // make sure no negative numbers are involved.
314
443
  var hash = mh3.hash(buf);
315
444
  return hash & 0x7FFFFFFF;
316
445
  }
317
446
 
447
+ /**
448
+ * Encodes a key to its byte representation using the protocol codec.
449
+ * @param {string|Object} k - The cache key to encode.
450
+ * @param {Object} protocol - The Hot Rod protocol instance.
451
+ * @returns {Buffer} Encoded key bytes.
452
+ */
318
453
  function keyToBytes(k, protocol) {
319
454
  var ctx = u.context(32);
320
455
  f.actions([protocol.encodeMediaKey(k)], codec.bytesEncoded)(ctx);
321
456
  return f.actions([codec.decodeVariableBytes()], codec.lastDecoded)({buf: ctx.buf, offset: 0});
322
457
  }
323
458
 
459
+ /**
460
+ * Finds the connection matching the given address.
461
+ * @param {Object} addr - Server address with host and port.
462
+ * @returns {Object|undefined} The matching connection, or undefined.
463
+ */
324
464
  function addrToConn(addr) {
325
465
  return _.find(conns, function(con) {
326
466
  return _.isEqual(con.getAddress(), addr);
@@ -365,10 +505,15 @@
365
505
  toString: function() {
366
506
  return util.format('ConsistentHashRouter(conns=%s)', showArrayConnections(conns));
367
507
  }
368
- }
508
+ };
369
509
  };
370
510
 
371
511
  var FixedRouter = function(logger, conn, clusterName) {
512
+ /**
513
+ * Checks whether this connection's address is absent from the given list.
514
+ * @param {Array} addrs - Array of server addresses.
515
+ * @returns {boolean} True if this connection's address is not in the list.
516
+ */
372
517
  function isMemberMissing(addrs) {
373
518
  return _.isEmpty(_.find(addrs, function(addr) {
374
519
  return _.isMatch(addr, conn.getAddress());
@@ -400,7 +545,7 @@
400
545
  var isMissing = !_.isEmpty(missing);
401
546
  if (isMissing) {
402
547
  logger.debugf('Removed server is: %s', conn.toString());
403
- return conn.disconnect()
548
+ return conn.disconnect();
404
549
  } else {
405
550
  logger.debugf('Removed server: none');
406
551
  return Promise.resolve();
@@ -415,9 +560,17 @@
415
560
  toString: function() {
416
561
  return util.format('FixedRouter(conn=%s)', conn.toString());
417
562
  }
418
- }
563
+ };
419
564
  };
420
565
 
566
+ /**
567
+ * Creates a transport layer managing connections, routing, and failover.
568
+ * @param {Array} addrs - Initial server addresses to connect to.
569
+ * @param {Object} protocol - The Hot Rod protocol instance.
570
+ * @param {Object} clientOpts - Client configuration options.
571
+ * @param {Object} listeners - Event listener manager.
572
+ * @returns {Object} Transport object with connect, write, and topology methods.
573
+ */
421
574
  function transport(addrs, protocol, clientOpts, listeners) {
422
575
  var id = _.uniqueId('io_');
423
576
  var logger = u.logger(id);
@@ -428,6 +581,11 @@
428
581
  [{ name: DEFAULT_CLUSTER_NAME, servers: addrs}]);
429
582
  var clusterSwitchInProgress = false;
430
583
 
584
+ /**
585
+ * Installs a new router and retries all RPCs if a cluster switch occurred.
586
+ * @param {Object} r - The new router to install.
587
+ * @returns {void}
588
+ */
431
589
  function onRouter(r) {
432
590
  // Check if a different router has been installed as result of cluster switch
433
591
  var isRetryAll = f.existy(router)
@@ -435,10 +593,10 @@
435
593
 
436
594
  // var oldRouter = router;
437
595
  router = r; // Assign new router
438
- logger.debugf("New router installed: %s", r.toString());
596
+ logger.debugf('New router installed: %s', r.toString());
439
597
 
440
598
  if (isRetryAll) {
441
- logger.debugf("Retry all after new router installed %s", r.toString());
599
+ logger.debugf('Retry all after new router installed %s', r.toString());
442
600
  retryAll(); // If cluster switched happened, retry all rpcs
443
601
  clusterSwitchInProgress = false;
444
602
  }
@@ -450,6 +608,10 @@
450
608
  // }
451
609
  }
452
610
 
611
+ /**
612
+ * Retries all pending RPCs after a cluster switch.
613
+ * @returns {void}
614
+ */
453
615
  function retryAll() {
454
616
  _.each(rpcMap.values(), function(rpc) {
455
617
  rpc.ctx.triedAddrs = [];
@@ -461,6 +623,11 @@
461
623
  });
462
624
  }
463
625
 
626
+ /**
627
+ * Filters topology servers to find newly added addresses.
628
+ * @param {Object} topology - The new topology with server addresses.
629
+ * @returns {Array} Server addresses not present in the current router.
630
+ */
464
631
  function filterAdded(topology) {
465
632
  var currentAddrs = router.getAddresses();
466
633
  var added = _.filter(topology.servers, function(a) {
@@ -471,6 +638,11 @@
471
638
  return added;
472
639
  }
473
640
 
641
+ /**
642
+ * Filters current router addresses to find those still present in the new topology.
643
+ * @param {Object} topology - The new topology with server addresses.
644
+ * @returns {Array} Addresses that remain connected in the new topology.
645
+ */
474
646
  function filterConnected(topology) {
475
647
  var connected = _.filter(router.getAddresses(), function(addr) {
476
648
  return _.where(topology.servers, addr).length > 0;
@@ -480,6 +652,14 @@
480
652
  return connected;
481
653
  }
482
654
 
655
+ /**
656
+ * Writes an encoded command to a connection and registers the RPC for response.
657
+ * @param {Object} ctx - Encoding context with buffer and message ID.
658
+ * @param {Function} decoder - Response decoder function.
659
+ * @param {Function} connFunc - Function returning the target connection.
660
+ * @param {Function} [preWrite] - Optional callback invoked before writing.
661
+ * @returns {Promise} Resolves with the decoded response.
662
+ */
483
663
  function write(ctx, decoder, connFunc, preWrite) {
484
664
  return new Promise(function (fulfill, reject) {
485
665
  var conn = connFunc();
@@ -496,6 +676,15 @@
496
676
  });
497
677
  }
498
678
 
679
+ /**
680
+ * Writes a retried RPC command to a specific connection.
681
+ * @param {Object} ctx - Encoding context with buffer and message ID.
682
+ * @param {Function} decoder - Response decoder function.
683
+ * @param {Object} conn - The target connection.
684
+ * @param {Function} fulfill - Promise resolve function.
685
+ * @param {Function} reject - Promise reject function.
686
+ * @returns {void}
687
+ */
499
688
  function writeRetry(ctx, decoder, conn, fulfill, reject) {
500
689
  logger.tracel(function() {
501
690
  return ['Write retried buffer(msgId=%d) to %s: %s'
@@ -506,23 +695,35 @@
506
695
  conn.write(ctx.buf);
507
696
  }
508
697
 
698
+ /**
699
+ * Disconnects the existing router and installs a new one.
700
+ * @param {Object} newRouter - The new router to install.
701
+ * @returns {Promise} Resolves when the new router is installed.
702
+ */
509
703
  function disconnectAndEmitRouter(newRouter) {
510
704
  if (f.existy(router)) {
511
705
  // Disconnect existing router connections
512
706
  // and regardless of outcome, install new router
513
- router.disconnect().finally(function() {
707
+ return router.disconnect().finally(function() {
514
708
  onRouter(newRouter);
515
- })
709
+ });
516
710
  } else {
517
711
  onRouter(newRouter);
712
+ return Promise.resolve();
518
713
  }
519
714
  }
520
715
 
716
+ /**
717
+ * Installs a new topology by updating connections and router.
718
+ * @param {Object} topology - New topology with server addresses and segments.
719
+ * @param {Object} transport - The transport instance.
720
+ * @returns {Promise} Resolves when the new topology is fully installed.
721
+ */
521
722
  function installNewTopology(topology, transport) {
522
723
  var newAddrs = topology.servers;
523
724
  logger.debugl(function() {return [
524
725
  'New topology(id=%s) discovered: %s',
525
- topology.id, u.showArrayAddress(newAddrs)] });
726
+ topology.id, u.showArrayAddress(newAddrs)]; });
526
727
 
527
728
  // Disconnect connections for members not present in topology
528
729
  var missing = router.getMissingConnections(topology);
@@ -551,7 +752,7 @@
551
752
  return conn.connect().then(function() {
552
753
  if(transport.authOpts().enabled) {
553
754
  return authMech(transport, conn, topology.id)
554
- .then(function (authMechs) {
755
+ .then(function (_) {
555
756
  return auth(transport, conn, topology.id).then(function () {
556
757
  return conn;
557
758
  });
@@ -561,30 +762,43 @@
561
762
  return conn;
562
763
  });
563
764
  }
564
- })
765
+ });
565
766
  }));
566
767
  })
567
768
  .then(failoverListeners(transport, missing));
568
769
  }
569
770
 
771
+ /**
772
+ * Creates a function that fails over listeners from removed connections.
773
+ * @param {Object} transport - The transport instance.
774
+ * @param {Array} missing - Connections that were removed from the topology.
775
+ * @returns {Function} Failover function that re-registers listeners.
776
+ */
570
777
  function failoverListeners(transport, missing) {
571
778
  return function() {
572
- logger.debugf("Failover listeners registered in: %s", showArrayConnections(missing));
779
+ logger.debugf('Failover listeners registered in: %s', showArrayConnections(missing));
573
780
 
574
781
  var failover = _.map(missing, function(c) {
575
782
  var listenersAt = listeners.getListenersAt(c.getAddress());
576
783
 
577
784
  return _.map(listenersAt, function(listener) {
578
- logger.debugf("Failover listener with id: %s", listener.id);
785
+ logger.debugf('Failover listener with id: %s', listener.id);
579
786
  listeners.removeListeners(listener.id);
580
787
  return listeners.addRemoteListener(transport, transport.context(32), listener.event, listener.callback);
581
788
  });
582
789
  });
583
790
 
584
791
  return Promise.all(_.flatten(failover));
585
- }
792
+ };
586
793
  }
587
794
 
795
+ /**
796
+ * Creates lazy connection factories that install a FixedRouter on first call.
797
+ * @param {Array} servers - Server addresses to create connections for.
798
+ * @param {Function} connF - Connection callback to invoke after connecting.
799
+ * @param {Object} transport - The transport instance.
800
+ * @returns {Array} Array of lazy connection factory functions.
801
+ */
588
802
  function toLazyConnectionsFirstCall(servers, connF, transport) {
589
803
  return _.map(servers, function(server) {
590
804
  return function() {
@@ -595,6 +809,13 @@
595
809
  });
596
810
  }
597
811
 
812
+ /**
813
+ * Creates lazy connection factories for the given servers.
814
+ * @param {Array} servers - Server addresses to create connections for.
815
+ * @param {Function} connF - Connection callback to invoke after connecting.
816
+ * @param {Object} transport - The transport instance.
817
+ * @returns {Array} Array of lazy connection factory functions.
818
+ */
598
819
  function toLazyConnections(servers, connF, transport) {
599
820
  return _.map(servers, function(server) {
600
821
  return function() {
@@ -604,21 +825,36 @@
604
825
  });
605
826
  }
606
827
 
828
+ /**
829
+ * Returns clusters excluding the one that failed.
830
+ * @param {string} failedClusterName - Name of the failed cluster.
831
+ * @returns {Array} Alternative cluster configurations.
832
+ */
607
833
  function getCandidateClusters(failedClusterName) {
608
834
  return _.filter(clusters, function(cluster) {
609
835
  return !_.isEqual(cluster.name, failedClusterName);
610
836
  });
611
837
  }
612
838
 
839
+ /**
840
+ * Creates a callback that installs a new router after failover.
841
+ * @param {string} failedClusterName - Name of the cluster that failed.
842
+ * @returns {Function} Callback accepting connection info to install new router.
843
+ */
613
844
  function getFailoverMainRouter(failedClusterName) {
614
845
  return function(connInfo) {
615
846
  logger.debugf('Switched from failed cluster=%s to cluster=%s',
616
847
  failedClusterName, connInfo[0]);
617
- disconnectAndEmitRouter(new FixedRouter(logger, connInfo[1], connInfo[0]));
618
- return true;
619
- }
848
+ return disconnectAndEmitRouter(new FixedRouter(logger, connInfo[1], connInfo[0]))
849
+ .then(function() { return true; });
850
+ };
620
851
  }
621
852
 
853
+ /**
854
+ * Creates a connection factory for an alternative cluster.
855
+ * @param {string} clusterName - Name of the alternative cluster.
856
+ * @returns {Function} Factory that connects and optionally authenticates.
857
+ */
622
858
  function altConnection(clusterName) {
623
859
  return function(conn) {
624
860
  logger.debugf('Alt connection %s', clusterName);
@@ -632,14 +868,26 @@
632
868
  }
633
869
  return conn;
634
870
  })]);
635
- }
871
+ };
636
872
  }
637
873
 
874
+ /**
875
+ * Creates lazy connection factories for an alternative cluster.
876
+ * @param {Object} cluster - Cluster config with name and servers.
877
+ * @param {Object} transport - The transport instance.
878
+ * @returns {Array} Array of lazy connection factory functions.
879
+ */
638
880
  function toLazyAltConnections(cluster, transport) {
639
881
  return toLazyConnections(
640
882
  cluster.servers, altConnection(cluster.name), transport);
641
883
  }
642
884
 
885
+ /**
886
+ * Wraps a cluster switch operation with progress tracking.
887
+ * @param {Function} clusterSwitchF - Function performing the cluster switch.
888
+ * @param {Function} [clusterFailF] - Optional error handler if switch fails.
889
+ * @returns {Promise} Resolves when the cluster switch completes.
890
+ */
643
891
  function aroundClusterSwitch(clusterSwitchF, clusterFailF) {
644
892
  logger.debugf('aroundClusterSwitch set clusterSwitchInProgress=true');
645
893
  clusterSwitchInProgress = true;
@@ -653,6 +901,13 @@
653
901
  });
654
902
  }
655
903
 
904
+ /**
905
+ * Attempts automatic failover to an alternative cluster.
906
+ * @param {Function} noClustersF - Callback if no alternative clusters exist.
907
+ * @param {Function} clusterFailF - Callback if cluster switch fails.
908
+ * @param {Object} transport - The transport instance.
909
+ * @returns {Promise} Resolves when failover completes or is rejected.
910
+ */
656
911
  function trySwitchCluster(noClustersF, clusterFailF, transport) {
657
912
  var failedClusterName = router.getClusterName();
658
913
  logger.debugf('Try to failover away from cluster=%s', failedClusterName);
@@ -672,15 +927,25 @@
672
927
  }
673
928
  }
674
929
 
930
+ /**
931
+ * Installs a FixedRouter for a manually selected cluster connection.
932
+ * @param {Array} connInfo - Tuple of [clusterName, connection].
933
+ * @returns {boolean} True indicating the switch succeeded.
934
+ */
675
935
  function getManualMainRouter(connInfo) {
676
936
  var clusterName = connInfo[0];
677
937
  var conn = connInfo[1];
678
938
  logger.debugf('Manually switched to cluster=%s, establishing connection to %s',
679
939
  clusterName, conn.toString());
680
- disconnectAndEmitRouter(new FixedRouter(logger, conn, clusterName));
681
- return true; // cluster switched
940
+ return disconnectAndEmitRouter(new FixedRouter(logger, conn, clusterName))
941
+ .then(function() { return true; });
682
942
  }
683
943
 
944
+ /**
945
+ * Finds a cluster configuration by name for manual switching.
946
+ * @param {string} clusterName - Target cluster name, or undefined for default.
947
+ * @returns {Object|undefined} The matching cluster configuration, or undefined.
948
+ */
684
949
  function findManualSwitchCluster(clusterName) {
685
950
  var name = f.existy(clusterName) ? clusterName : DEFAULT_CLUSTER_NAME;
686
951
  return _.find(clusters, function(cluster) {
@@ -688,6 +953,12 @@
688
953
  });
689
954
  }
690
955
 
956
+ /**
957
+ * Attempts a manual switch to a named cluster.
958
+ * @param {string} clusterName - Name of the target cluster.
959
+ * @param {Object} transport - The transport instance.
960
+ * @returns {Promise<boolean>} Resolves true if switch succeeded, false otherwise.
961
+ */
691
962
  function tryManualSwitchCluster(clusterName, transport) {
692
963
  logger.debugf('Try to manually switch to cluster=%s', clusterName);
693
964
  var targetCluster = findManualSwitchCluster(clusterName);
@@ -702,35 +973,59 @@
702
973
  }, function () {
703
974
  logger.error('Unable to switch to cluster %s', targetCluster.name);
704
975
  return Promise.resolve(false);
705
- })
976
+ });
706
977
  }
707
978
 
979
+ /**
980
+ * Establishes and authenticates the main connection to a server.
981
+ * @param {Object} conn - The connection to establish.
982
+ * @returns {Promise<Object>} Resolves with the connected connection.
983
+ */
708
984
  function mainConnection(conn) {
985
+ var connPromise = conn.connect();
986
+
987
+ if (clientOpts.version === 'auto') {
988
+ connPromise = connPromise.then(function() {
989
+ return negotiateProtocol(o, conn, o.getTopologyId(), clientOpts);
990
+ });
991
+ }
992
+
709
993
  if (o.authOpts().enabled) {
710
- return conn.connect().then(function () {
994
+ return connPromise.then(function () {
711
995
  return authMech(o, conn, o.getTopologyId());
712
996
  }).then(function (authMechs) {
713
997
  logger.tracef(authMechs);
714
998
  if (!_.contains(authMechs, o.authOpts().saslMechanism)) {
715
- throw new Error('The selected authentication mechanism ' + o.authOpts().saslMechanism
716
- + ' is not among the supported server mechanisms: ' + authMechs);
999
+ throw new Error(`The selected authentication mechanism ${ o.authOpts().saslMechanism
1000
+ } is not among the supported server mechanisms: ${ authMechs}`);
717
1001
  }
718
1002
  return auth(o, conn, o.getTopologyId());
719
1003
  }).then(conn);
720
1004
  }
721
1005
 
722
- return conn.connect().then(function () {
1006
+ return connPromise.then(function () {
723
1007
  return ping(o, conn, o.getTopologyId());
724
1008
  }).then(conn);
725
1009
  }
726
1010
 
1011
+ /**
1012
+ * Tries lazy connections in order, returning the first successful one.
1013
+ * @param {Array<Function>} lazyConns - Array of lazy connection factory functions.
1014
+ * @returns {Promise<Object>} Resolves with the first successful connection.
1015
+ */
727
1016
  function getFirstConnection(lazyConns) {
728
1017
  return _.foldl(lazyConns, function(state, f) {
729
1018
  return state
730
- .catch(function() { return f(); } ) // if fails, try next
1019
+ .catch(function() { return f(); } ); // if fails, try next
731
1020
  }, Promise.reject(new Error('Unable to find the first connection')));
732
1021
  }
733
1022
 
1023
+ /**
1024
+ * Tries lazy connections in order and applies a callback to the first success.
1025
+ * @param {Array<Function>} lazyConns - Array of lazy connection factory functions.
1026
+ * @param {Function} connF - Callback applied to the successful connection.
1027
+ * @returns {Promise} Resolves with the callback result.
1028
+ */
734
1029
  function findFirstConnection(lazyConns, connF) {
735
1030
  logger.debugf('Call findFirstConnection');
736
1031
 
@@ -741,10 +1036,13 @@
741
1036
  }, Promise.reject(new Error('Unable to find a connection')));
742
1037
  }
743
1038
 
744
- function getMainRouter(conn) {
745
- disconnectAndEmitRouter(new FixedRouter(logger, conn, DEFAULT_CLUSTER_NAME));
746
- }
747
-
1039
+ /**
1040
+ * Sends a ping request to a server connection.
1041
+ * @param {Object} transport - The transport instance.
1042
+ * @param {Object} conn - The connection to ping.
1043
+ * @param {number} topologyId - Current topology ID.
1044
+ * @returns {Promise} Resolves with the ping response.
1045
+ */
748
1046
  function ping(transport, conn, topologyId) {
749
1047
  var ctx = u.context(16);
750
1048
  ctx.topologyId = topologyId;
@@ -754,6 +1052,56 @@
754
1052
  return transport.writeCommandPinned(ctx, p.decodePingResponse, conn);
755
1053
  }
756
1054
 
1055
+ /**
1056
+ * Negotiates the highest protocol version supported by the server.
1057
+ * Tries versions in descending order, starting from the highest.
1058
+ * @param {Object} transport - The transport instance.
1059
+ * @param {Object} conn - The connection to negotiate on.
1060
+ * @param {number} topologyId - Current topology ID.
1061
+ * @param {Object} cOpts - Client configuration options.
1062
+ * @returns {Promise<string>} Resolves with the negotiated version string.
1063
+ */
1064
+ function negotiateProtocol(transport, conn, topologyId, cOpts) {
1065
+ var versions = protocols.VERSION_ORDER;
1066
+
1067
+ /**
1068
+ * @param {number} index Version index to try.
1069
+ * @returns {Promise<string>} Resolves with negotiated version.
1070
+ */
1071
+ function tryVersion(index) {
1072
+ if (index >= versions.length)
1073
+ return Promise.reject(new Error('No compatible protocol version found'));
1074
+
1075
+ var version = versions[index];
1076
+ var candidateProtocol = protocols.resolve(version, cOpts);
1077
+
1078
+ transport.setProtocol(candidateProtocol);
1079
+
1080
+ return ping(transport, conn, topologyId)
1081
+ .then(function() {
1082
+ logger.debugf('Protocol negotiation succeeded with version %s', version);
1083
+ return version;
1084
+ })
1085
+ .catch(function(err) {
1086
+ var msg = typeof err === 'string' ? err : (err && err.message) || '';
1087
+ if (msg.indexOf('CacheNotFoundException') >= 0) {
1088
+ return Promise.reject(err);
1089
+ }
1090
+ logger.debugf('Protocol version %s not supported, trying next', version);
1091
+ return tryVersion(index + 1);
1092
+ });
1093
+ }
1094
+
1095
+ return tryVersion(0);
1096
+ }
1097
+
1098
+ /**
1099
+ * Performs SASL authentication on a connection.
1100
+ * @param {Object} transport - The transport instance.
1101
+ * @param {Object} conn - The connection to authenticate.
1102
+ * @param {number} topologyId - Current topology ID.
1103
+ * @returns {Promise<Object>} Resolves with the authenticated connection.
1104
+ */
757
1105
  function auth(transport, conn, topologyId) {
758
1106
  if (!transport.authOpts().enabled || conn.authDone) {
759
1107
  logger.debugf('Auth not enabled');
@@ -779,6 +1127,13 @@
779
1127
  });
780
1128
  }
781
1129
 
1130
+ /**
1131
+ * Queries the server for supported authentication mechanisms.
1132
+ * @param {Object} transport - The transport instance.
1133
+ * @param {Object} conn - The connection to query.
1134
+ * @param {number} topologyId - Current topology ID.
1135
+ * @returns {Promise<Array>} Resolves with the list of supported SASL mechanisms.
1136
+ */
782
1137
  function authMech(transport, conn, topologyId) {
783
1138
  if (!transport.authOpts().enabled) {
784
1139
  logger.debugf('Auth not enabled');
@@ -830,7 +1185,7 @@
830
1185
  writeRetry(ctx, decoder, conn, fulfill, reject);
831
1186
  else {
832
1187
  trySwitchCluster(
833
- function() { reject('No clusters and no connections left to try on writeRetry.') },
1188
+ function() { reject('No clusters and no connections left to try on writeRetry.'); },
834
1189
  function(err) {
835
1190
  logger.tracef('Clusters configured but none available on writeRetry: %s', err.message);
836
1191
  reject('Clusters configured but none available on writeRetry. No connections left to try');
@@ -838,6 +1193,7 @@
838
1193
  }
839
1194
  },
840
1195
  getProtocol: function() { return protocol; },
1196
+ setProtocol: function(newProtocol) { protocol = newProtocol; },
841
1197
  findRpc: function(id) { return rpcMap.get(id); },
842
1198
  removeRpc: function(id) { return rpcMap.remove(id); },
843
1199
  updateTopology: function(bytebuf) {
@@ -862,7 +1218,7 @@
862
1218
  function(rpc) { return _.isEqual(rpc.address, addr); });
863
1219
 
864
1220
  logger.tracel(function() {
865
- return ['Pending request ids are: [%s]', _.keys(pendingRpcs)] });
1221
+ return ['Pending request ids are: [%s]', _.keys(pendingRpcs)]; });
866
1222
 
867
1223
  _.each(pendingRpcs, function(rpc, id) {
868
1224
  if (rpc.ctx.triedAddrs.length >= clientOpts.maxRetries) {
@@ -884,7 +1240,7 @@
884
1240
  logger.tracef('Retrying request(msgId=%d), retry %d', id, rpc.ctx.triedAddrs.length);
885
1241
  o.writeRetry(rpc.ctx, rpc.decoder, rpc.success, rpc.fail, rpc.ctx.triedAddrs);
886
1242
  }
887
- })
1243
+ });
888
1244
  } else {
889
1245
  logger.debugf('Do not retry RPCs since cluster switch is in progress');
890
1246
  }