infinispan 0.13.0 → 0.15.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/infinispan.js CHANGED
@@ -13,27 +13,32 @@
13
13
  var f = require('./functional');
14
14
  var codec = require('./codec');
15
15
  var u = require('./utils');
16
+ var uri = require('./uri');
16
17
  var protocols = require('./protocols');
17
18
  var io = require('./io');
18
19
  var listeners = require('./listeners');
20
+ var nearCacheFactory = require('./near-cache');
19
21
 
20
22
  var Client = function(addrs, clientOpts) {
21
23
  var logger = u.logger('client');
22
24
 
25
+ /**
26
+ * Resolves the protocol implementation for a given version string.
27
+ * @param {string} version Protocol version (e.g. '2.9', '3.0').
28
+ * @returns {Object} Protocol instance for the specified version.
29
+ */
23
30
  function protocolResolver(version) {
24
- logger.debugf('Using protocol version: %s', version);
25
-
26
- switch (version) {
27
- case '3.0': return protocols.version30(clientOpts);
28
- case '2.9': return protocols.version29(clientOpts);
29
- case '2.5': return protocols.version25(clientOpts);
30
- case '2.2': return protocols.version22(clientOpts);
31
- default : throw new Error('Unknown protocol version: ' + version);
31
+ if (version === 'auto') {
32
+ logger.debugf('Using protocol auto-negotiation (starting with %s)', protocols.VERSION_ORDER[0]);
33
+ return protocols.resolve(protocols.VERSION_ORDER[0], clientOpts);
32
34
  }
35
+ logger.debugf('Using protocol version: %s', version);
36
+ return protocols.resolve(version, clientOpts);
33
37
  }
34
38
 
35
39
  var p = protocolResolver(clientOpts['version']);
36
40
  var listen = listeners(p);
41
+ var nc = clientOpts.nearCache ? nearCacheFactory(clientOpts.nearCache.maxEntries) : null;
37
42
 
38
43
  var TINY = 16, SMALL = 32, MEDIUM = 64, BIG = 128;
39
44
 
@@ -41,31 +46,82 @@
41
46
  var events = ['create', 'modify', 'remove', 'expiry'];
42
47
  Object.freeze(events);
43
48
 
49
+ /**
50
+ * Sends a request with header and body, returning a promise for the decoded response.
51
+ * @param {Object} ctx Request context with buffer and id.
52
+ * @param {number} op Operation code.
53
+ * @param {Function} body Body encoder function.
54
+ * @param {Function} decoder Response decoder function.
55
+ * @param {Object} [opts] Optional operation options.
56
+ * @returns {Promise} Promise resolved with the decoded response.
57
+ */
44
58
  function future(ctx, op, body, decoder, opts) {
45
59
  f.actions(p.stepsHeaderBody(ctx, op, body, opts), codec.bytesEncoded)(ctx);
46
60
  return transport.writeCommand(ctx, decoder);
47
61
  }
48
62
 
63
+ /**
64
+ * Sends a header-only request and decodes the response.
65
+ * @param {Object} ctx Request context with buffer and id.
66
+ * @param {number} op Operation code.
67
+ * @param {Function} decoder Response decoder function.
68
+ * @param {Object} [opts] Optional operation options.
69
+ * @returns {Promise} Promise resolved with the decoded response.
70
+ */
49
71
  function futureDecodeOnly(ctx, op, decoder, opts) {
50
72
  f.actions(p.stepsHeader(ctx, op, opts), codec.bytesEncoded)(ctx);
51
73
  return transport.writeCommand(ctx, decoder);
52
74
  }
53
75
 
76
+ /**
77
+ * Sends a header-only request with no decoder.
78
+ * @param {Object} ctx Request context with buffer and id.
79
+ * @param {number} op Operation code.
80
+ * @returns {Promise} Promise resolved when the operation completes.
81
+ */
54
82
  function futureEmpty(ctx, op) {
55
83
  f.actions(p.stepsHeader(ctx, op), codec.bytesEncoded)(ctx);
56
84
  return transport.writeCommand(ctx);
57
85
  }
58
86
 
87
+ /**
88
+ * Sends a key-routed request with body, using consistent hashing for server selection.
89
+ * @param {Object} ctx Request context with buffer and id.
90
+ * @param {number} op Operation code.
91
+ * @param {string|Object} key Key used for routing.
92
+ * @param {Function} body Body encoder function.
93
+ * @param {Function} decoder Response decoder function.
94
+ * @param {Object} [opts] Optional operation options.
95
+ * @returns {Promise} Promise resolved with the decoded response.
96
+ */
59
97
  function futureKey(ctx, op, key, body, decoder, opts) {
60
98
  f.actions(p.stepsHeaderBody(ctx, op, body, opts), codec.bytesEncoded)(ctx);
61
99
  return transport.writeKeyCommand(ctx, key, decoder);
62
100
  }
63
101
 
102
+ /**
103
+ * Sends a request pinned to a specific connection.
104
+ * @param {Object} ctx Request context with buffer and id.
105
+ * @param {number} op Operation code.
106
+ * @param {Function} body Body encoder function.
107
+ * @param {Function} decoder Response decoder function.
108
+ * @param {Object} conn Connection to pin the request to.
109
+ * @returns {Promise} Promise resolved with the decoded response.
110
+ */
64
111
  function futurePinned(ctx, op, body, decoder, conn) {
65
112
  f.actions(p.stepsHeaderBody(ctx, op, body), codec.bytesEncoded)(ctx);
66
113
  return transport.writeCommandPinned(ctx, decoder, conn);
67
114
  }
68
115
 
116
+ /**
117
+ * Sends a script execution request and post-processes nulls in the response.
118
+ * @param {Object} ctx Request context with buffer and id.
119
+ * @param {number} op Operation code.
120
+ * @param {Function} body Body encoder function.
121
+ * @param {Function} decoder Response decoder function.
122
+ * @param {Object} [opts] Optional operation options.
123
+ * @returns {Promise<string>} Promise resolved with the execution result.
124
+ */
69
125
  function futureExec(ctx, op, body, decoder, opts) {
70
126
  f.actions(p.stepsHeaderBody(ctx, op, body, opts), codec.bytesEncoded)(ctx);
71
127
  var resultPromise = transport.writeCommand(ctx, decoder);
@@ -79,6 +135,11 @@
79
135
  });
80
136
  }
81
137
 
138
+ /**
139
+ * Checks whether the given event name is a supported listener event.
140
+ * @param {string} event Event name to check.
141
+ * @returns {boolean} True if the event is supported.
142
+ */
82
143
  function isEventDefined(event) {
83
144
  var exists = false;
84
145
  var eventLowerCase = event.toLowerCase();
@@ -96,6 +157,9 @@
96
157
  /**
97
158
  * Iterator instance returned by completed promise from Client.iterator() calls.
98
159
  *
160
+ * @param {String} iterId Iterator identifier.
161
+ * @param {Object} conn Connection instance.
162
+ * @returns {Object} Iterator instance with close and next methods.
99
163
  * @constructs Iterator
100
164
  * @since 0.3
101
165
  */
@@ -103,6 +167,10 @@
103
167
  var nextElems = [];
104
168
  var done = false;
105
169
 
170
+ /**
171
+ * Returns a promise fulfilled with the next cached entry.
172
+ * @returns {Promise<Object>} Promise with the next entry and done status.
173
+ */
106
174
  function nextPromise() {
107
175
  var kv = nextElems.pop();
108
176
  return new Promise(function (fulfill, reject) {
@@ -110,6 +178,10 @@
110
178
  });
111
179
  }
112
180
 
181
+ /**
182
+ * Returns a promise indicating iteration is complete.
183
+ * @returns {Promise<Object>} Promise with done set to true.
184
+ */
113
185
  function donePromise() {
114
186
  return new Promise(function (fulfill, reject) {
115
187
  fulfill({done: done});
@@ -180,7 +252,7 @@
180
252
  return futurePinned(
181
253
  ctx, 0x35, p.encodeIterId(iterId), p.complete(p.hasSuccess), conn);
182
254
  }
183
- }
255
+ };
184
256
  }
185
257
 
186
258
  return {
@@ -188,6 +260,29 @@
188
260
  // TODO: Avoid user calling connect by checking if connected
189
261
  var client = this;
190
262
  return transport.connect()
263
+ .then(function() {
264
+ if (clientOpts.version === 'auto') {
265
+ var negotiatedProtocol = transport.getProtocol();
266
+ p = negotiatedProtocol;
267
+ listen.setProtocol(negotiatedProtocol);
268
+ logger.debugf('Negotiated protocol version: %s', p.getVersionString());
269
+ }
270
+ })
271
+ .then(function() {
272
+ if (nc) {
273
+ var invalidate = function(key) { nc.remove(key); };
274
+ return client.addListener('create', invalidate)
275
+ .then(function(listenerId) {
276
+ return client.addListener('modify', invalidate, {listenerId: listenerId});
277
+ })
278
+ .then(function(listenerId) {
279
+ return client.addListener('remove', invalidate, {listenerId: listenerId});
280
+ })
281
+ .then(function(listenerId) {
282
+ return client.addListener('expiry', invalidate, {listenerId: listenerId});
283
+ });
284
+ }
285
+ })
191
286
  .then(function() {
192
287
  logger.debugf('Started Infinispan %s', client);
193
288
  return client; // return client
@@ -208,12 +303,23 @@
208
303
  * @since 0.3
209
304
  */
210
305
  disconnect: function() {
306
+ if (nc) nc.clear();
211
307
  return transport.disconnect();
212
308
  },
309
+ /**
310
+ * Returns the active protocol version string (e.g. '3.1', '4.1').
311
+ * When using auto-negotiation, this reflects the negotiated version.
312
+ *
313
+ * @returns {string} Protocol version string.
314
+ * @memberof Client#
315
+ */
316
+ getProtocolVersion: function() {
317
+ return p.getVersionString();
318
+ },
213
319
  /**
214
320
  * Get the value associated with the given key parameter.
215
321
  *
216
- * @param k {(String|Object)} Key to retrieve.
322
+ * @param {(String|Object)} k Key to retrieve.
217
323
  * @returns {Promise.<?String>}
218
324
  * A promise that will be completed with the value associated with
219
325
  * the key, or undefined if the value is not present.
@@ -221,18 +327,33 @@
221
327
  * @since 0.3
222
328
  */
223
329
  get: function(k) {
330
+ if (nc) {
331
+ var cached = nc.get(k);
332
+ if (cached !== undefined) {
333
+ return Promise.resolve(cached);
334
+ }
335
+ }
224
336
  var ctx = transport.context(SMALL);
225
337
  logger.debugf('Invoke get(msgId=%d,key=%s)', ctx.id, u.str(k));
226
338
  var decoder = p.decodeValue();
227
- return futureKey(ctx, 0x03, k, p.encodeKey(k), decoder);
339
+ var result = futureKey(ctx, 0x03, k, p.encodeKey(k), decoder);
340
+ if (nc) {
341
+ return result.then(function(value) {
342
+ if (value !== undefined) {
343
+ nc.put(k, value);
344
+ }
345
+ return value;
346
+ });
347
+ }
348
+ return result;
228
349
  },
229
350
  /**
230
351
  * Query the server with the given queryString.
231
352
  *
232
- * @param q {(Object)} query to retrieve.
233
- * @returns {Promise.<?Object[]>}
353
+ * @param {Object} q Query to retrieve.
354
+ * @returns {Promise.<Object[]>}
234
355
  * A promise that will be completed with the array of values associated with
235
- * the query, or empty array if the no values matches the query.
356
+ * the query, or empty array if no values match the query.
236
357
  * @memberof Client#
237
358
  * @since 1.3
238
359
  */
@@ -246,7 +367,7 @@
246
367
  /**
247
368
  * Check whether the given key is present.
248
369
  *
249
- * @param k {(String|Object)} Key to check for presence.
370
+ * @param {(String|Object)} k Key to check for presence.
250
371
  * @returns {Promise.<boolean>}
251
372
  * A promise that will be completed with true if there is a value
252
373
  * associated with the key, or false otherwise.
@@ -273,7 +394,7 @@
273
394
  /**
274
395
  * Get the value and metadata associated with the given key parameter.
275
396
  *
276
- * @param k {(String|Object)} Key to retrieve.
397
+ * @param {(String|Object)} k Key to retrieve.
277
398
  * @returns {Promise.<?MetadataValue>}
278
399
  * A promise that will be completed with the value and metadata
279
400
  * associated with the key, or undefined if the value is not present.
@@ -284,7 +405,16 @@
284
405
  var ctx = transport.context(SMALL);
285
406
  logger.debugf('Invoke getWithMetadata(msgId=%d,key=%s)', ctx.id, u.str(k));
286
407
  var decoder = p.decodeWithMeta();
287
- return futureKey(ctx, 0x1B, k, p.encodeKey(k), decoder);
408
+ var result = futureKey(ctx, 0x1B, k, p.encodeKey(k), decoder);
409
+ if (nc) {
410
+ return result.then(function(meta) {
411
+ if (meta !== undefined) {
412
+ nc.put(k, meta.value);
413
+ }
414
+ return meta;
415
+ });
416
+ }
417
+ return result;
288
418
  },
289
419
  /**
290
420
  * A String formatted to specify duration unit information.
@@ -314,9 +444,9 @@
314
444
  /**
315
445
  * Associates the specified value with the given key.
316
446
  *
317
- * @param k {(String|Object)} Key with which the specified value is to be associated.
318
- * @param v {(String|Object)} Value to be associated with the specified key.
319
- * @param opts {StoreOptions=} Optional store options.
447
+ * @param {(String|Object)} k Key with which the specified value is to be associated.
448
+ * @param {(String|Object)} v Value to be associated with the specified key.
449
+ * @param {StoreOptions=} opts Optional store options.
320
450
  * @returns {Promise.<?(String|Object)>}
321
451
  * A promise that will be completed with undefined unless 'previous'
322
452
  * option has been enabled and a previous value exists, in which case it
@@ -325,6 +455,7 @@
325
455
  * @since 0.3
326
456
  */
327
457
  put: function(k, v, opts) {
458
+ if (nc) nc.remove(k);
328
459
  var ctx = transport.context(MEDIUM);
329
460
  logger.debugl(function() { return ['Invoke put(msgId=%d,key=%s,value=%s,opts=%s)',
330
461
  ctx.id, u.str(k), u.str(v), u.str(opts)]; });
@@ -344,8 +475,8 @@
344
475
  /**
345
476
  * Removes the mapping for a key if it is present.
346
477
  *
347
- * @param k {(String|Object)} Key whose mapping is to be removed.
348
- * @param opts {RemoveOptions=} Optional remove options.
478
+ * @param {(String|Object)} k Key whose mapping is to be removed.
479
+ * @param {RemoveOptions=} opts Optional remove options.
349
480
  * @returns {Promise.<(Boolean|String|Object)>}
350
481
  * A promise that will be completed with true if the mapping was removed,
351
482
  * or false if the key did not exist.
@@ -355,6 +486,7 @@
355
486
  * @since 0.3
356
487
  */
357
488
  remove: function(k, opts) {
489
+ if (nc) nc.remove(k);
358
490
  var ctx = transport.context(SMALL);
359
491
  logger.debugl(function() {return ['Invoke remove(msgId=%d,key=%s,opts=%s)',
360
492
  ctx.id, u.str(k), JSON.stringify(opts)]; });
@@ -365,9 +497,9 @@
365
497
  * Conditional store operation that associates the key with the given
366
498
  * value if the specified key is not already associated with a value.
367
499
  *
368
- * @param k {(String|Object)} Key with which the specified value is to be associated.
369
- * @param v {(String|Object)} Value to be associated with the specified key.
370
- * @param opts {StoreOptions=} Optional store options.
500
+ * @param {(String|Object)} k Key with which the specified value is to be associated.
501
+ * @param {(String|Object)} v Value to be associated with the specified key.
502
+ * @param {StoreOptions=} opts Optional store options.
371
503
  * @returns {Promise.<(Boolean|String|Object)>}
372
504
  * A promise that will be completed with true if the mapping was stored,
373
505
  * or false if the key is already present.
@@ -377,6 +509,7 @@
377
509
  * @since 0.3
378
510
  */
379
511
  putIfAbsent: function(k, v, opts) {
512
+ if (nc) nc.remove(k);
380
513
  var ctx = transport.context(MEDIUM);
381
514
  logger.debugl(function() {return ['Invoke putIfAbsent(msgId=%d,key=%s,value=%s,opts=%s)',
382
515
  ctx.id, u.str(k), u.str(v), JSON.stringify(opts)]; });
@@ -387,9 +520,9 @@
387
520
  * Conditional store operation that replaces the entry for a key only
388
521
  * if currently mapped to a given value.
389
522
  *
390
- * @param k {(String|Object)} Key with which the specified value is associated.
391
- * @param v {(String|Object)} Value expected to be associated with the specified key.
392
- * @param opts {StoreOptions=} Optional store options.
523
+ * @param {(String|Object)} k Key with which the specified value is associated.
524
+ * @param {(String|Object)} v Value expected to be associated with the specified key.
525
+ * @param {StoreOptions=} opts Optional store options.
393
526
  * @returns {Promise.<(Boolean|String|Object)>}
394
527
  * A promise that will be completed with true if the mapping was replaced,
395
528
  * or false if the key does not exist.
@@ -399,6 +532,7 @@
399
532
  * @since 0.3
400
533
  */
401
534
  replace: function(k, v, opts) {
535
+ if (nc) nc.remove(k);
402
536
  var ctx = transport.context(MEDIUM);
403
537
  logger.debugl(function() { return ['Invoke replace(msgId=%d,key=%s,value=%s,opts=%s)',
404
538
  ctx.id, u.str(k), u.str(v), JSON.stringify(opts)]; });
@@ -409,12 +543,12 @@
409
543
  * Replaces the given value only if its version matches the supplied
410
544
  * version.
411
545
  *
412
- * @param k {(String|Object)} Key with which the specified value is associated.
413
- * @param v {(String|Object)} Value expected to be associated with the specified key.
414
- * @param version {Buffer} binary buffer version that should match the
546
+ * @param {(String|Object)} k Key with which the specified value is associated.
547
+ * @param {(String|Object)} v Value expected to be associated with the specified key.
548
+ * @param {Buffer} version binary buffer version that should match the
415
549
  * one in the server for the operation to succeed. Version information
416
550
  * can be retrieved with getWithMetadata method.
417
- * @param opts {StoreOptions=} Optional store options.
551
+ * @param {StoreOptions=} opts Optional store options.
418
552
  * @returns {Promise.<(Boolean|String|Object)>}
419
553
  * A promise that will be completed with true if the version matches
420
554
  * and the mapping was replaced, otherwise it returns false if not
@@ -428,6 +562,7 @@
428
562
  * @since 0.3
429
563
  */
430
564
  replaceWithVersion: function(k, v, version, opts) {
565
+ if (nc) nc.remove(k);
431
566
  var ctx = transport.context(MEDIUM);
432
567
  logger.debugl(function() { return ['Invoke replaceWithVersion(msgId=%d,key=%s,value=%s,version=0x%s,opts=%s)',
433
568
  ctx.id, u.str(k), u.str(v), version.toString('hex'), JSON.stringify(opts)]; });
@@ -438,11 +573,11 @@
438
573
  * Removes the given entry only if its version matches the
439
574
  * supplied version.
440
575
  *
441
- * @param k {(String|Object)} Key whose mapping is to be removed.
442
- * @param version {Buffer} binary buffer version that should match the
576
+ * @param {(String|Object)} k Key whose mapping is to be removed.
577
+ * @param {Buffer} version binary buffer version that should match the
443
578
  * one in the server for the operation to succeed. Version information
444
579
  * can be retrieved with getWithMetadata method.
445
- * @param opts {RemoveOptions=} Optional remove options.
580
+ * @param {RemoveOptions=} opts Optional remove options.
446
581
  * @returns {Promise.<(Boolean|String|Object)>}
447
582
  * A promise that will be completed with true if the version matches
448
583
  * and the mapping was removed, otherwise it returns false if not
@@ -456,6 +591,7 @@
456
591
  * @since 0.3
457
592
  */
458
593
  removeWithVersion: function(k, version, opts) {
594
+ if (nc) nc.remove(k);
459
595
  var ctx = transport.context(SMALL);
460
596
  logger.debugl(function() { return ['Invoke removeWithVersion(msgId=%d,key=%s,version=0x%s,opts=%s)',
461
597
  ctx.id, u.str(k), version.toString('hex'), JSON.stringify(opts)]; });
@@ -473,7 +609,7 @@
473
609
  /**
474
610
  * Retrieves all of the entries for the provided keys.
475
611
  *
476
- * @param keys {(String[]|Object[])} Keys to find values for.
612
+ * @param {(String[]|Object[])} keys Keys to find values for.
477
613
  * @returns {Promise.<Entry[]>}
478
614
  * A promise that will be completed with an array of entries for all
479
615
  * keys found. If a key does not exist, there won't be an entry for that
@@ -501,8 +637,8 @@
501
637
  /**
502
638
  * Stores all of the mappings from the specified entry array.
503
639
  *
504
- * @param pairs {Entry[]} key/value pair mappings to be stored
505
- * @param opts {MultiStoreOptions=}
640
+ * @param {Entry[]} pairs key/value pair mappings to be stored
641
+ * @param {MultiStoreOptions=} opts
506
642
  * Optional storage options to apply to all entries.
507
643
  * @returns {Promise}
508
644
  * A promise that will be completed when all entries have been stored.
@@ -510,6 +646,9 @@
510
646
  * @since 0.3
511
647
  */
512
648
  putAll: function(pairs, opts) {
649
+ if (nc) {
650
+ _.each(pairs, function(pair) { nc.remove(pair.key); });
651
+ }
513
652
  var ctx = transport.context(BIG);
514
653
  logger.debugl(function() { return ['Invoke putAll(msgId=%d,pairs=%s,opts=%s)',
515
654
  ctx.id, JSON.stringify(pairs), JSON.stringify(opts)]; });
@@ -528,9 +667,9 @@
528
667
  /**
529
668
  * Iterate over the entries stored in server(s).
530
669
  *
531
- * @param batchSize {Number}
670
+ * @param {Number} batchSize
532
671
  * The number of entries transferred from the server at a time.
533
- * @param opts {IteratorOptions=} Optional iteration settings.
672
+ * @param {IteratorOptions=} opts Optional iteration settings.
534
673
  * @return {Promise.<Iterator>}
535
674
  * A promise that will be completed with an iterator that can be used
536
675
  * to retrieve stored elements.
@@ -567,6 +706,7 @@
567
706
  * @since 0.3
568
707
  */
569
708
  clear: function() {
709
+ if (nc) nc.clear();
570
710
  var ctx = transport.context(TINY);
571
711
  logger.debugf('Invoke clear(msgId=%d)', ctx.id);
572
712
  return futureEmpty(ctx, 0x13);
@@ -632,7 +772,7 @@
632
772
  * entry version and listener id.
633
773
  * 'remove' and 'expiry' events callback the function with key
634
774
  * and listener id.
635
- * @param opts {ListenOptions=} Options for adding listener.
775
+ * @param {ListenOptions=} opts Options for adding listener.
636
776
  * @returns {Promise<String>}
637
777
  * A promise that will be completed with the identifier of the listener.
638
778
  * This identifier can be used to register multiple callbacks with the
@@ -647,7 +787,7 @@
647
787
  var conn = listen.findConnectionListener(opts.listenerId);
648
788
  if (!f.existy(conn))
649
789
  return Promise.reject(
650
- new Error('No server connection for listener (listenerId=' + opts.listenerId + ')'));
790
+ new Error(`No server connection for listener (listenerId=${ opts.listenerId })`));
651
791
 
652
792
  return listen.addLocalListener(ctx, event, listener, opts);
653
793
  } else {
@@ -655,7 +795,7 @@
655
795
  }
656
796
  } else {
657
797
  return Promise.reject(
658
- new Error('The event \'' + event + '\' is not supported'));
798
+ new Error(`The event '${ event }' is not supported`));
659
799
  }
660
800
  },
661
801
  /**
@@ -674,7 +814,59 @@
674
814
  var conn = listen.findConnectionListener(listenerId);
675
815
  if (!f.existy(conn))
676
816
  return Promise.reject(
677
- new Error('No server connection for listener (listenerId=' + listenerId + ')'));
817
+ new Error(`No server connection for listener (listenerId=${ listenerId })`));
818
+
819
+ var remote = futurePinned(ctx, 0x27, p.encodeListenerId(listenerId), p.complete(p.hasSuccess), conn);
820
+ return remote.then(function (success) {
821
+ if (success) {
822
+ listen.removeListeners(listenerId);
823
+ return true;
824
+ }
825
+ return false;
826
+ });
827
+ },
828
+ /**
829
+ * Continuous query options.
830
+ *
831
+ * @typedef {Object} ContinuousQueryOptions
832
+ * @property {Object} [params] - Named parameter bindings for the Ickle query.
833
+ * @since 0.16
834
+ */
835
+ /**
836
+ * Register a continuous query that watches for cache changes
837
+ * matching the given Ickle query.
838
+ *
839
+ * @param {String} queryString Ickle query string.
840
+ * @param {ContinuousQueryOptions=} opts Optional CQ options.
841
+ * @returns {Promise<Object>}
842
+ * A promise completed with a ContinuousQuery handle.
843
+ * Use cq.on('joining', fn), cq.on('leaving', fn), cq.on('updated', fn)
844
+ * to receive events.
845
+ * @memberof Client#
846
+ * @since 0.16
847
+ */
848
+ addContinuousQuery: function(queryString, opts) {
849
+ var ctx = transport.context(SMALL);
850
+ logger.debugf('Invoke addContinuousQuery(msgId=%d,query=%s)', ctx.id, queryString);
851
+ return listen.addContinuousQueryListener(transport, ctx, queryString, opts);
852
+ },
853
+ /**
854
+ * Remove a continuous query.
855
+ *
856
+ * @param {Object} cq ContinuousQuery handle returned by addContinuousQuery.
857
+ * @returns {Promise}
858
+ * A promise completed when the continuous query has been removed.
859
+ * @memberof Client#
860
+ * @since 0.16
861
+ */
862
+ removeContinuousQuery: function(cq) {
863
+ var listenerId = cq.getListenerId();
864
+ var ctx = transport.context(SMALL);
865
+ logger.debugf('Invoke removeContinuousQuery(msgId=%d,listenerId=%s)', ctx.id, listenerId);
866
+ var conn = listen.findConnectionListener(listenerId);
867
+ if (!f.existy(conn))
868
+ return Promise.reject(
869
+ new Error(`No server connection for CQ listener (listenerId=${ listenerId })`));
678
870
 
679
871
  var remote = futurePinned(ctx, 0x27, p.encodeListenerId(listenerId), p.complete(p.hasSuccess), conn);
680
872
  return remote.then(function (success) {
@@ -734,6 +926,153 @@
734
926
  // TODO update jsdoc, value does not need to be String, can be JSON too
735
927
  return futureExec(ctx, 0x2B, p.encodeNameParams(scriptName, params), p.decodeValue());
736
928
  },
929
+ /**
930
+ * Counter configuration options.
931
+ *
932
+ * @typedef {Object} CounterConfig
933
+ * @property {('strong'|'weak')} type - Counter type.
934
+ * @property {('persistent'|'volatile')} [storage='volatile'] - Storage mode.
935
+ * @property {Number} [initialValue=0] - Initial counter value.
936
+ * @property {Number} [lowerBound] - Lower bound (strong bounded counters only).
937
+ * @property {Number} [upperBound] - Upper bound (strong bounded counters only).
938
+ * @property {Number} [concurrencyLevel=16] - Concurrency level (weak counters only).
939
+ * @since 0.14
940
+ */
941
+ /**
942
+ * Create a distributed counter.
943
+ *
944
+ * @param {String} name Counter name.
945
+ * @param {CounterConfig} config Counter configuration.
946
+ * @returns {Promise.<Boolean>}
947
+ * A promise that will be completed with true if the counter was created,
948
+ * or false if it already exists.
949
+ * @memberof Client#
950
+ * @since 0.14
951
+ */
952
+ counterCreate: function(name, config) {
953
+ var ctx = transport.context(MEDIUM);
954
+ logger.debugf('Invoke counterCreate(msgId=%d,name=%s)', ctx.id, name);
955
+ return future(ctx, 0x4B, p.encodeCounterCreate(name, config), p.decodeCounterStatus);
956
+ },
957
+ /**
958
+ * Get the current value of a counter.
959
+ *
960
+ * @param {String} name Counter name.
961
+ * @returns {Promise.<?Number>}
962
+ * A promise that will be completed with the counter value,
963
+ * or undefined if the counter is not defined.
964
+ * @memberof Client#
965
+ * @since 0.14
966
+ */
967
+ counterGet: function(name) {
968
+ var ctx = transport.context(SMALL);
969
+ logger.debugf('Invoke counterGet(msgId=%d,name=%s)', ctx.id, name);
970
+ return future(ctx, 0x56, p.encodeCounterName(name), p.decodeCounterValue);
971
+ },
972
+ /**
973
+ * Add a value to the counter and return the new value.
974
+ *
975
+ * @param {String} name Counter name.
976
+ * @param {Number} value Value to add (can be negative).
977
+ * @returns {Promise.<?Number>}
978
+ * A promise that will be completed with the new counter value.
979
+ * @memberof Client#
980
+ * @since 0.14
981
+ */
982
+ counterAddAndGet: function(name, value) {
983
+ var ctx = transport.context(SMALL);
984
+ logger.debugf('Invoke counterAddAndGet(msgId=%d,name=%s,value=%d)', ctx.id, name, value);
985
+ return future(ctx, 0x52, p.encodeCounterNameValue(name, value), p.decodeCounterValue);
986
+ },
987
+ /**
988
+ * Reset the counter to its initial value.
989
+ *
990
+ * @param {String} name Counter name.
991
+ * @returns {Promise.<Boolean>}
992
+ * A promise that will be completed with true if the counter was reset.
993
+ * @memberof Client#
994
+ * @since 0.14
995
+ */
996
+ counterReset: function(name) {
997
+ var ctx = transport.context(SMALL);
998
+ logger.debugf('Invoke counterReset(msgId=%d,name=%s)', ctx.id, name);
999
+ return future(ctx, 0x54, p.encodeCounterName(name), p.decodeCounterStatus);
1000
+ },
1001
+ /**
1002
+ * Compare and swap the counter value.
1003
+ *
1004
+ * @param {String} name Counter name.
1005
+ * @param {Number} expect Expected current value.
1006
+ * @param {Number} update New value to set if current matches expected.
1007
+ * @returns {Promise.<?Number>}
1008
+ * A promise that will be completed with the counter value
1009
+ * (the old value if successful, or current value if not).
1010
+ * @memberof Client#
1011
+ * @since 0.14
1012
+ */
1013
+ counterCompareAndSwap: function(name, expect, update) {
1014
+ var ctx = transport.context(SMALL);
1015
+ logger.debugf('Invoke counterCompareAndSwap(msgId=%d,name=%s,expect=%d,update=%d)', ctx.id, name, expect, update);
1016
+ return future(ctx, 0x58, p.encodeCounterNameTwoLongs(name, expect, update), p.decodeCounterValue);
1017
+ },
1018
+ /**
1019
+ * Check if a counter is defined.
1020
+ *
1021
+ * @param {String} name Counter name.
1022
+ * @returns {Promise.<Boolean>}
1023
+ * A promise that will be completed with true if the counter is defined.
1024
+ * @memberof Client#
1025
+ * @since 0.14
1026
+ */
1027
+ counterIsDefined: function(name) {
1028
+ var ctx = transport.context(SMALL);
1029
+ logger.debugf('Invoke counterIsDefined(msgId=%d,name=%s)', ctx.id, name);
1030
+ return future(ctx, 0x4F, p.encodeCounterName(name), p.decodeCounterStatus);
1031
+ },
1032
+ /**
1033
+ * Get the configuration of a counter.
1034
+ *
1035
+ * @param {String} name Counter name.
1036
+ * @returns {Promise.<?CounterConfig>}
1037
+ * A promise that will be completed with the counter configuration,
1038
+ * or undefined if the counter is not defined.
1039
+ * @memberof Client#
1040
+ * @since 0.14
1041
+ */
1042
+ counterGetConfiguration: function(name) {
1043
+ var ctx = transport.context(SMALL);
1044
+ logger.debugf('Invoke counterGetConfiguration(msgId=%d,name=%s)', ctx.id, name);
1045
+ return future(ctx, 0x4D, p.encodeCounterName(name), p.decodeCounterConfig);
1046
+ },
1047
+ /**
1048
+ * Remove a counter from the cluster.
1049
+ *
1050
+ * @param {String} name Counter name.
1051
+ * @returns {Promise.<Boolean>}
1052
+ * A promise that will be completed with true if the counter was removed.
1053
+ * @memberof Client#
1054
+ * @since 0.14
1055
+ */
1056
+ counterRemove: function(name) {
1057
+ var ctx = transport.context(SMALL);
1058
+ logger.debugf('Invoke counterRemove(msgId=%d,name=%s)', ctx.id, name);
1059
+ return future(ctx, 0x5E, p.encodeCounterName(name), p.decodeCounterStatus);
1060
+ },
1061
+ /**
1062
+ * Set a value to the counter and return the previous value.
1063
+ *
1064
+ * @param {String} name Counter name.
1065
+ * @param {Number} value Value to set.
1066
+ * @returns {Promise.<?Number>}
1067
+ * A promise that will be completed with the previous counter value.
1068
+ * @memberof Client#
1069
+ * @since 0.14
1070
+ */
1071
+ counterGetAndSet: function(name, value) {
1072
+ var ctx = transport.context(SMALL);
1073
+ logger.debugf('Invoke counterGetAndSet(msgId=%d,name=%s,value=%d)', ctx.id, name, value);
1074
+ return future(ctx, 0x7F, p.encodeCounterNameValue(name, value), p.decodeCounterValue);
1075
+ },
737
1076
  /**
738
1077
  * Get server topology related information.
739
1078
  *
@@ -748,6 +1087,7 @@
748
1087
  },
749
1088
  /**
750
1089
  * Get client information represented as a string.
1090
+ * @returns {String} String representation of the client.
751
1091
  * @memberof Client#
752
1092
  * @since 0.4
753
1093
  */
@@ -761,13 +1101,175 @@
761
1101
 
762
1102
  registerProtostreamRoot: function(root){
763
1103
  return p.registerProtostreamRoot(root);
1104
+ },
1105
+ /**
1106
+ * Get the number of entries in the near cache.
1107
+ *
1108
+ * @returns {Number} Number of entries in the near cache, or 0 if near caching is not enabled.
1109
+ * @memberof Client#
1110
+ * @since 0.14
1111
+ */
1112
+ nearCacheSize: function() {
1113
+ return nc ? nc.size() : 0;
1114
+ },
1115
+
1116
+ /**
1117
+ * Admin operations for cache lifecycle, schema management, and indexing.
1118
+ * These operations use Hot Rod server tasks (exec opcode 0x2B) with
1119
+ * predefined @@-prefixed task names.
1120
+ * @memberof Client#
1121
+ * @since 0.15
1122
+ */
1123
+ admin: {
1124
+ /**
1125
+ * Create a cache with the given configuration.
1126
+ *
1127
+ * @param {String} name Cache name.
1128
+ * @param {String} config Cache configuration (XML or JSON).
1129
+ * @param {Object} [opts] Optional settings.
1130
+ * @param {String} [opts.template] Template name to use instead of config.
1131
+ * @param {String} [opts.flags] Admin flags (e.g. 'VOLATILE' for non-persistent caches).
1132
+ * @returns {Promise} A promise that completes when the cache is created.
1133
+ * @memberof Client.admin#
1134
+ * @since 0.15
1135
+ */
1136
+ createCache: function(name, config, opts) {
1137
+ var params = {name: name};
1138
+ if (f.existy(config)) params.configuration = config;
1139
+ if (f.existy(opts) && f.existy(opts.template)) params.template = opts.template;
1140
+ if (f.existy(opts) && f.existy(opts.flags)) params.flags = opts.flags;
1141
+ var ctx = transport.context(MEDIUM);
1142
+ logger.debugf('Invoke admin.createCache(msgId=%d,name=%s)', ctx.id, name);
1143
+ return future(ctx, 0x2B, p.encodeNameParams('@@cache@create', params), p.decodeValue());
1144
+ },
1145
+ /**
1146
+ * Get an existing cache or create it with the given configuration.
1147
+ *
1148
+ * @param {String} name Cache name.
1149
+ * @param {String} config Cache configuration (XML or JSON).
1150
+ * @param {Object} [opts] Optional settings.
1151
+ * @param {String} [opts.template] Template name to use instead of config.
1152
+ * @param {String} [opts.flags] Admin flags (e.g. 'VOLATILE').
1153
+ * @returns {Promise} A promise that completes when the cache is available.
1154
+ * @memberof Client.admin#
1155
+ * @since 0.15
1156
+ */
1157
+ getOrCreateCache: function(name, config, opts) {
1158
+ var params = {name: name};
1159
+ if (f.existy(config)) params.configuration = config;
1160
+ if (f.existy(opts) && f.existy(opts.template)) params.template = opts.template;
1161
+ if (f.existy(opts) && f.existy(opts.flags)) params.flags = opts.flags;
1162
+ var ctx = transport.context(MEDIUM);
1163
+ logger.debugf('Invoke admin.getOrCreateCache(msgId=%d,name=%s)', ctx.id, name);
1164
+ return future(ctx, 0x2B, p.encodeNameParams('@@cache@getorcreate', params), p.decodeValue());
1165
+ },
1166
+ /**
1167
+ * Remove a cache.
1168
+ *
1169
+ * @param {String} name Cache name.
1170
+ * @returns {Promise} A promise that completes when the cache is removed.
1171
+ * @memberof Client.admin#
1172
+ * @since 0.15
1173
+ */
1174
+ removeCache: function(name) {
1175
+ var ctx = transport.context(SMALL);
1176
+ logger.debugf('Invoke admin.removeCache(msgId=%d,name=%s)', ctx.id, name);
1177
+ return future(ctx, 0x2B, p.encodeNameParams('@@cache@remove', {name: name}), p.decodeValue());
1178
+ },
1179
+ /**
1180
+ * List all cache names.
1181
+ *
1182
+ * @returns {Promise<String[]>} A promise that completes with the list of cache names.
1183
+ * @memberof Client.admin#
1184
+ * @since 0.15
1185
+ */
1186
+ cacheNames: function() {
1187
+ var ctx = transport.context(SMALL);
1188
+ logger.debugf('Invoke admin.cacheNames(msgId=%d)', ctx.id);
1189
+ return future(ctx, 0x2B, p.encodeNameParams('@@cache@names', {}), p.decodeValue())
1190
+ .then(function(result) { return JSON.parse(result); });
1191
+ },
1192
+ /**
1193
+ * Update a mutable configuration attribute on a cache.
1194
+ *
1195
+ * @param {String} name Cache name.
1196
+ * @param {String} attribute Attribute path (e.g. 'memory.max-count').
1197
+ * @param {String} value New attribute value.
1198
+ * @returns {Promise} A promise that completes when the attribute is updated.
1199
+ * @memberof Client.admin#
1200
+ * @since 0.15
1201
+ */
1202
+ updateConfigurationAttribute: function(name, attribute, value) {
1203
+ var ctx = transport.context(SMALL);
1204
+ logger.debugf('Invoke admin.updateConfigurationAttribute(msgId=%d,name=%s,attr=%s)', ctx.id, name, attribute);
1205
+ return future(ctx, 0x2B, p.encodeNameParams('@@cache@updateConfigurationAttribute', {
1206
+ name: name, attribute: attribute, value: value
1207
+ }), p.decodeValue());
1208
+ },
1209
+ /**
1210
+ * Rebuild indexes for a cache.
1211
+ *
1212
+ * @param {String} name Cache name.
1213
+ * @returns {Promise} A promise that completes when reindexing starts.
1214
+ * @memberof Client.admin#
1215
+ * @since 0.15
1216
+ */
1217
+ reindex: function(name) {
1218
+ var ctx = transport.context(SMALL);
1219
+ logger.debugf('Invoke admin.reindex(msgId=%d,name=%s)', ctx.id, name);
1220
+ return future(ctx, 0x2B, p.encodeNameParams('@@cache@reindex', {name: name}), p.decodeValue());
1221
+ },
1222
+ /**
1223
+ * Update the index schema for a cache.
1224
+ *
1225
+ * @param {String} name Cache name.
1226
+ * @returns {Promise} A promise that completes when the index schema is updated.
1227
+ * @memberof Client.admin#
1228
+ * @since 0.15
1229
+ */
1230
+ updateIndexSchema: function(name) {
1231
+ var ctx = transport.context(SMALL);
1232
+ logger.debugf('Invoke admin.updateIndexSchema(msgId=%d,name=%s)', ctx.id, name);
1233
+ return future(ctx, 0x2B, p.encodeNameParams('@@cache@updateindexschema', {name: name}), p.decodeValue());
1234
+ },
1235
+ /**
1236
+ * Register (create or update) a Protobuf schema on the server.
1237
+ *
1238
+ * @param {String} name Schema name (e.g. 'person.proto').
1239
+ * @param {String} schema Protobuf schema content.
1240
+ * @returns {Promise} A promise that completes when the schema is registered.
1241
+ * @memberof Client.admin#
1242
+ * @since 0.15
1243
+ */
1244
+ registerSchema: function(name, schema) {
1245
+ var ctx = transport.context(MEDIUM);
1246
+ logger.debugf('Invoke admin.registerSchema(msgId=%d,name=%s)', ctx.id, name);
1247
+ return future(ctx, 0x2B, p.encodeNameParams('@@schemas@createOrUpdate', {
1248
+ name: name, content: schema, op: 's'
1249
+ }), p.decodeValue());
1250
+ },
1251
+ /**
1252
+ * Remove a registered Protobuf schema.
1253
+ *
1254
+ * @param {String} name Schema name.
1255
+ * @returns {Promise} A promise that completes when the schema is removed.
1256
+ * @memberof Client.admin#
1257
+ * @since 0.15
1258
+ */
1259
+ removeSchema: function(name) {
1260
+ var ctx = transport.context(SMALL);
1261
+ logger.debugf('Invoke admin.removeSchema(msgId=%d,name=%s)', ctx.id, name);
1262
+ return future(ctx, 0x2B, p.encodeNameParams('@@schemas@delete', {name: name}), p.decodeValue());
1263
+ }
764
1264
  }
765
- }
1265
+ };
766
1266
  };
767
1267
 
768
1268
  /**
769
1269
  * Server topology information.
770
1270
  *
1271
+ * @param {Object} transport Transport instance.
1272
+ * @returns {Object} Topology info object.
771
1273
  * @constructs Topology
772
1274
  * @since 0.3
773
1275
  */
@@ -809,7 +1311,7 @@
809
1311
  * Switch remote cache manager to a different cluster,
810
1312
  * previously declared via configuration.
811
1313
  *
812
- * @param clusterName name of the cluster to which to switch to
1314
+ * @param {String} clusterName Name of the cluster to which to switch to.
813
1315
  * @return {Promise<Boolean>}
814
1316
  * A promise encapsulating a Boolean that indicates {@code true} if the
815
1317
  * switch happened, or {@code false} otherwise.
@@ -832,7 +1334,7 @@
832
1334
  switchToDefaultCluster: function() {
833
1335
  return transport.switchToDefaultCluster();
834
1336
  }
835
- }
1337
+ };
836
1338
  };
837
1339
 
838
1340
  /**
@@ -845,8 +1347,24 @@
845
1347
  */
846
1348
  /**
847
1349
  * Infinispan client constructor taking an optional initial address,
848
- * or multiple addresses, to which the client will try to connect to,
849
- * as well as optional configuration settings.
1350
+ * multiple addresses, or a Hot Rod URI string to which the client
1351
+ * will try to connect, as well as optional configuration settings.
1352
+ *
1353
+ * @example
1354
+ * // Connect with a Hot Rod URI
1355
+ * client('hotrod://admin:password@localhost:11222')
1356
+ *
1357
+ * @example
1358
+ * // URI with TLS and query parameters
1359
+ * client('hotrods://admin:password@localhost:11222?trust_ca=/ca.pem')
1360
+ *
1361
+ * @example
1362
+ * // URI with programmatic overrides
1363
+ * client('hotrod://admin:password@localhost:11222', {authentication: {saslMechanism: 'SCRAM-SHA-256'}})
1364
+ *
1365
+ * @example
1366
+ * // URI with multiple servers
1367
+ * client('hotrod://admin:password@node1:11222,node2:11322')
850
1368
  *
851
1369
  * @example
852
1370
  * client({port: 11222, host: 'localhost'})
@@ -861,18 +1379,29 @@
861
1379
  * client([{port: 11522, host: 'myhost'}, {port: 11622, host: 'myhost'}],
862
1380
  * {version: '2.2', cacheName: 'myCache'})
863
1381
  *
864
- * @param args {(ServerAddress|ServerAddress[])}
865
- * Optional single or multiple addresses to which to connect. If none
866
- * provided, the client will connect to localhost:11222 address by default.
867
- * @param [options] {ClientOptions}
868
- * Optional configuration settings.
1382
+ * @param {(String|ServerAddress|ServerAddress[])} args
1383
+ * A Hot Rod URI string (hotrod:// or hotrods://), a single server address,
1384
+ * or an array of server addresses. If none provided, the client connects
1385
+ * to localhost:11222 by default.
1386
+ * @param {ClientOptions=} options
1387
+ * Optional configuration settings. When a URI is provided, these settings
1388
+ * override URI values.
869
1389
  * @constructs Client
870
- * @returns {Promise<ReturnType<Client>>}
1390
+ * @returns {Promise<ReturnType<Client>>} A promise that resolves to a connected client.
871
1391
  * @since 0.3
872
1392
  */
873
1393
  exports.client = function client(args, options) {
874
- var merged = f.merge(Client.config, options);
875
- var c = new Client(u.normalizeAddresses(args), merged);
1394
+ var addrs, uriOpts;
1395
+ if (uri.isHotrodURI(args)) {
1396
+ var parsed = uri.parseHotrodURI(args);
1397
+ addrs = parsed.servers;
1398
+ uriOpts = parsed.options;
1399
+ } else {
1400
+ addrs = u.normalizeAddresses(args);
1401
+ uriOpts = {};
1402
+ }
1403
+ var merged = f.deepMerge(Client.config, uriOpts, options || {});
1404
+ var c = new Client(addrs, merged);
876
1405
 
877
1406
  return c.connect();
878
1407
  };
@@ -891,7 +1420,7 @@
891
1420
  *
892
1421
  * @static
893
1422
  * @typedef {Object} ClientOptions
894
- * @property {?(2.9|2.5|2.2)} [version=2.9] - Version of client/server protocol.
1423
+ * @property {?('auto'|'4.1'|'4.0'|'3.1'|'3.0'|'2.9'|'2.5'|'2.2')} [version='auto'] - Version of client/server protocol. Use 'auto' to negotiate the highest supported version.
895
1424
  * @property {?String} [cacheName] - Optional cache name.
896
1425
  * @property {?Number} [maxRetries=3] - Optional number of retries for operation.
897
1426
  * @property {?Object} [ssl] - TLS/SSL properties.
@@ -918,10 +1447,12 @@
918
1447
  * @property {?boolean} [topologyUpdates=true] - Optional flag to controls whether the client deals with topology updates or not.
919
1448
  * @property {?("text/plain"|"application/json")} [mediaType="text/plain"] - Media type of the cache contents.
920
1449
  * @property {?Cluster[]} [clusters] - Optional additional clusters for cross-site failovers.
1450
+ * @property {?Object} [nearCache] - Near cache configuration. When set, enables client-side caching with server-side invalidation.
1451
+ * @property {?Number} [nearCache.maxEntries] - Maximum number of entries to store in the near cache.
921
1452
  * @since 0.3
922
1453
  */
923
1454
  Client.config = {
924
- version: '2.9', // Hot Rod protocol version
1455
+ version: 'auto', // Hot Rod protocol version (auto-negotiate)
925
1456
  cacheName: undefined, // Cache name
926
1457
  maxRetries: 3, // Maximum number of retries
927
1458
  authentication: {
@@ -938,16 +1469,7 @@
938
1469
  enabled: false,
939
1470
  secureProtocol: 'TLS_client_method',
940
1471
  trustCerts: [],
941
- clientAuth: {
942
- key: undefined,
943
- passphrase: undefined,
944
- cert: undefined
945
- },
946
- sniHostName: undefined,
947
- cryptoStore: {
948
- path: undefined,
949
- passphrase: undefined
950
- }
1472
+ sniHostName: undefined
951
1473
  },
952
1474
  dataFormat : {
953
1475
  keyType: 'text/plain',