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/protocols.js CHANGED
@@ -23,6 +23,12 @@
23
23
  var INFINITE_LIFESPAN = 0x01, INFINITE_MAXIDLE = 0x02; // Duration flag masks
24
24
  var MAGIC = 0xA0;
25
25
 
26
+ /**
27
+ * Creates decode actions for a key-value pair.
28
+ * @param {Object} decoderKey Key decoder descriptor.
29
+ * @param {Object} decoderValue Value decoder descriptor.
30
+ * @returns {Function} Action function that decodes a key-value pair from a byte buffer.
31
+ */
26
32
  function decodePairActions(decoderKey, decoderValue) {
27
33
  return f.actions(
28
34
  [
@@ -31,7 +37,7 @@
31
37
  ]
32
38
  , function(values) {
33
39
  if (values.length < 2) {
34
- logger.tracef("Not enough to read (not array): %s", values);
40
+ logger.tracef('Not enough to read (not array): %s', values);
35
41
  return undefined;
36
42
  }
37
43
 
@@ -43,7 +49,7 @@
43
49
  [codec.decodeString(), codec.decodeString()],
44
50
  function(values) {
45
51
  if (values.length < 2) {
46
- logger.tracef("Not enough to read (not array): %s", values);
52
+ logger.tracef('Not enough to read (not array): %s', values);
47
53
  return undefined;
48
54
  }
49
55
 
@@ -57,9 +63,28 @@
57
63
  var DECODE_VINT = f.actions([codec.decodeVInt()], codec.lastDecoded);
58
64
  var DECODE_SHORT = f.actions([codec.decodeShort()], codec.lastDecoded);
59
65
 
66
+ /**
67
+ * Checks whether an option is present and truthy.
68
+ * @param {Object} opts Options object.
69
+ * @param {string} name Option name to check.
70
+ * @returns {boolean} True if the option exists and is truthy.
71
+ */
60
72
  function hasOpt(opts, name) { return _.has(opts, name) && f.truthy(opts[name]); }
73
+ /**
74
+ * Checks whether the 'previous' option is present and truthy.
75
+ * @param {Object} opts Options object.
76
+ * @returns {boolean} True if the 'previous' option exists and is truthy.
77
+ */
61
78
  function hasOptPrev(opts) { return hasOpt(opts, 'previous'); }
62
79
 
80
+ /**
81
+ * Decodes a timestamp (created/lifespan or lastUsed/maxIdle) from a byte buffer.
82
+ * @param {number} flags Metadata flags byte.
83
+ * @param {number} mask Bitmask for infinite duration check.
84
+ * @param {string[]} headers Header names for the decoded values.
85
+ * @param {Object} bytebuf Byte buffer to decode from.
86
+ * @returns {Object|undefined} Object with header-keyed timestamp values, or undefined if not enough data.
87
+ */
63
88
  function decodeTimestamp(flags, mask, headers, bytebuf) {
64
89
  var timestamp;
65
90
  if (((flags & mask) != mask)) {
@@ -75,10 +100,20 @@
75
100
  return _.object(headers, timestamp);
76
101
  }
77
102
 
103
+ /**
104
+ * Creates a decode action for a single value.
105
+ * @param {Object} decoder Decoder descriptor with fun and obj properties.
106
+ * @returns {Function} Action function that decodes a single value from a byte buffer.
107
+ */
78
108
  function decodeSingle(decoder) {
79
109
  return f.actions([decoder.fun(decoder.obj)], codec.lastDecoded);
80
110
  }
81
111
 
112
+ /**
113
+ * Creates a media decoder descriptor from a media type object.
114
+ * @param {Object} obj Media type object with a decodeMedia method.
115
+ * @returns {Object} Decoder descriptor with obj and fun properties.
116
+ */
82
117
  function decoderMedia(obj) {
83
118
  return {
84
119
  obj: obj
@@ -89,10 +124,21 @@
89
124
  var EncodeMixin = (function() {
90
125
  var logger = u.logger('encoder');
91
126
 
127
+ var VERSION_BYTE_TO_STRING = {
128
+ 22: '2.2', 25: '2.5', 29: '2.9', 30: '3.0', 31: '3.1', 40: '4.0', 41: '4.1'
129
+ };
130
+
92
131
  return {
93
132
  buildFlags: function (opts) { // TODO: Move out to a Mixin (similar to expiry)
94
133
  return hasOptPrev(opts) ? 0x01 : 0;
95
134
  },
135
+ /**
136
+ * Returns the protocol version as a human-readable string (e.g. '3.1').
137
+ * @returns {string} Protocol version string.
138
+ */
139
+ getVersionString: function() {
140
+ return VERSION_BYTE_TO_STRING[this.version] || String(this.version);
141
+ },
96
142
  encodeHeader: function (op, topologyId, opts) {
97
143
  logger.tracef('Encode operation with topology id %d', topologyId);
98
144
  var protocolVersion = this.version;
@@ -110,24 +156,24 @@
110
156
  codec.encodeUByte(clientIntelligence), // basic client intelligence
111
157
  codec.encodeVInt(topologyId) // client topology id
112
158
  ];
113
- }
159
+ };
114
160
  },
115
161
  encodeKey: function (k) {
116
162
  var outer = this;
117
163
  return function() {
118
164
  return [outer.encodeMediaKey(k)]; // key
119
- }
165
+ };
120
166
  },
121
167
  encodeQuery: function (q) {
122
168
  return function() {
123
169
  return [codec.encodeQuery(q)]; // query
124
- }
170
+ };
125
171
  },
126
172
  encodeKeyVersion: function (k, version) {
127
173
  var outer = this;
128
174
  return function() {
129
175
  return [outer.encodeMediaKey(k), codec.encodeBytes(version)]; // key + version
130
- }
176
+ };
131
177
  },
132
178
  encodeKeyValue: function (k, v) {
133
179
  var outer = this;
@@ -137,7 +183,7 @@
137
183
  outer.encodeExpiry(opts), // lifespan & max idle
138
184
  [outer.encodeMediaValue(v)] // value
139
185
  );
140
- }
186
+ };
141
187
  },
142
188
  encodeKeyValueVersion: function (k, v, version) {
143
189
  var outer = this;
@@ -148,7 +194,7 @@
148
194
  [codec.encodeBytes(version), // version
149
195
  outer.encodeMediaValue(v)] // value
150
196
  );
151
- }
197
+ };
152
198
  },
153
199
  encodeMultiKey: function (keys) {
154
200
  var outer = this;
@@ -170,7 +216,7 @@
170
216
  return [
171
217
  outer.encodeMediaKey(pair.key), // key
172
218
  outer.encodeMediaValue(pair.value) // value
173
- ]
219
+ ];
174
220
  });
175
221
 
176
222
  return f.cat(
@@ -203,6 +249,11 @@
203
249
  }());
204
250
 
205
251
  var ExpiryEncodeMixin = (function() {
252
+ /**
253
+ * Parses a duration string into a numeric value and time unit byte.
254
+ * @param {string|number|undefined} d Duration string (e.g. '1h'), number, or undefined.
255
+ * @returns {Array} Two-element array of [value, unitByte].
256
+ */
206
257
  function parseDuration(d) {
207
258
  if (!f.existy(d)) {
208
259
  return [undefined, 7];
@@ -210,17 +261,22 @@
210
261
  // Numeric durations only allowed to describe infinite (negative) or default durations (0)
211
262
  if (d < 0) return [undefined, 8];
212
263
  else if (d == 0) return [undefined, 7];
213
- else throw new Error('Positive duration provided without time unit: ' + d);
264
+ else throw new Error(`Positive duration provided without time unit: ${ d}`);
214
265
  } else {
215
266
  var splitter = /(\d+)[\s,]*([a-zμ]+)/g;
216
267
  var matches = splitter.exec(d);
217
268
  if (f.existy(matches))
218
269
  return [parseInt(matches[1]), timeUnitToByte(matches[2])];
219
270
  else
220
- throw new Error('Unknown duration format for ' + d);
271
+ throw new Error(`Unknown duration format for ${ d}`);
221
272
  }
222
273
  }
223
274
 
275
+ /**
276
+ * Converts a time unit string to its protocol byte representation.
277
+ * @param {string} unit Time unit character (e.g. 's', 'ms', 'h').
278
+ * @returns {number} Byte value representing the time unit.
279
+ */
224
280
  function timeUnitToByte(unit) {
225
281
  switch (unit) {
226
282
  case 's': return 0;
@@ -231,7 +287,7 @@
231
287
  case 'h': return 5;
232
288
  case 'd': return 6;
233
289
  default: // TODO: Could it be caught in regular expression?
234
- throw new Error('Unknown duration unit in ' + unit);
290
+ throw new Error(`Unknown duration unit in ${ unit}`);
235
291
  }
236
292
  }
237
293
 
@@ -248,7 +304,7 @@
248
304
  }
249
305
  return [codec.encodeUByte(0x77)]; // default lifespan & max idle
250
306
  }
251
- }
307
+ };
252
308
  }());
253
309
 
254
310
  var DecodeMixin = (function() {
@@ -262,14 +318,14 @@
262
318
  codec.decodeUByte() // topology change marker
263
319
  ], function(values) {
264
320
  if (values.length < 5) {
265
- logger.tracef("Not enough to read (not array): %s", values);
321
+ logger.tracef('Not enough to read (not array): %s', values);
266
322
  return undefined;
267
323
  }
268
324
 
269
325
  return {
270
326
  msgId: values[1], opCode: values[2],
271
327
  status: values[3], hasNewTopology: values[4] == 1
272
- }
328
+ };
273
329
  });
274
330
  // var DECODE_VERSIONED = f.actions([codec.decodeFixedBytes(8), codec.decodeObject()],
275
331
  // function(values) { return {version: values[0], value: values[1]}; });
@@ -279,7 +335,7 @@
279
335
  [codec.decodeVInt(), codec.decodeVInt()],
280
336
  function(values) {
281
337
  if (values.length < 2) {
282
- logger.tracef("Not enough to read (not array): %s", values);
338
+ logger.tracef('Not enough to read (not array): %s', values);
283
339
  return undefined;
284
340
  }
285
341
 
@@ -289,7 +345,7 @@
289
345
  [codec.decodeUByte(), codec.decodeVInt()],
290
346
  function(values) {
291
347
  if (values.length < 2) {
292
- logger.tracef("Not enough to read (not array): %s", values);
348
+ logger.tracef('Not enough to read (not array): %s', values);
293
349
  return undefined;
294
350
  }
295
351
 
@@ -298,23 +354,43 @@
298
354
  var DECODE_HOST_PORT = f.actions([codec.decodeString(), codec.decodeShort()],
299
355
  function(values) { return {host: values[0], port: values[1]}; });
300
356
 
301
- var SUCCESS = 0x00, NOT_EXECUTED = 0x01, NOT_FOUND = 0x02, // Status codes
357
+ var SUCCESS = 0x00, NOT_EXECUTED = 0x01, // Status codes
302
358
  SUCCESS_WITH_PREV = 0x03, NOT_EXECUTED_WITH_PREV = 0x04; // Status codes
303
359
 
360
+ /**
361
+ * Checks whether a status code indicates success.
362
+ * @param {number} status Response status code.
363
+ * @returns {boolean} True if status is SUCCESS or SUCCESS_WITH_PREV.
364
+ */
304
365
  function isSuccess(status) {
305
366
  return status == SUCCESS || status == SUCCESS_WITH_PREV;
306
367
  }
307
368
 
369
+ /**
370
+ * Checks whether a status code indicates the operation was not executed.
371
+ * @param {number} status Response status code.
372
+ * @returns {boolean} True if status is NOT_EXECUTED or NOT_EXECUTED_WITH_PREV.
373
+ */
308
374
  function isNotExecuted(status) {
309
375
  return status == NOT_EXECUTED || status == NOT_EXECUTED_WITH_PREV;
310
376
  }
311
377
 
378
+ /**
379
+ * Checks whether a status code includes a previous value.
380
+ * @param {number} status Response status code.
381
+ * @returns {boolean} True if status is SUCCESS_WITH_PREV or NOT_EXECUTED_WITH_PREV.
382
+ */
312
383
  function hasPrevious(status) {
313
384
  return status == SUCCESS_WITH_PREV || status == NOT_EXECUTED_WITH_PREV;
314
385
  }
315
386
 
316
- function hasError(header) { return header.opCode == 0x50; }
317
-
387
+ /**
388
+ * Decodes a previous value from the byte buffer.
389
+ * @param {Object} header Response header.
390
+ * @param {Object} bytebuf Byte buffer to decode from.
391
+ * @param {Object} decoder Value decoder descriptor.
392
+ * @returns {Object} Result object with decoded previous value and continue flag.
393
+ */
318
394
  function decodePrev(header, bytebuf, decoder) {
319
395
  var decodeActions = decodeSingle(decoder);
320
396
  var prev = decodeActions(bytebuf);
@@ -324,6 +400,13 @@
324
400
  return {continue: false};
325
401
  }
326
402
 
403
+ /**
404
+ * Decodes an object from the byte buffer on success status.
405
+ * @param {Object} header Response header with status field.
406
+ * @param {Object} bytebuf Byte buffer to decode from.
407
+ * @param {Function} action Decode action function to apply.
408
+ * @returns {Object} Result object with decoded value and continue flag.
409
+ */
327
410
  function decodeObject(header, bytebuf, action) {
328
411
  if (isSuccess(header.status)) {
329
412
  var obj = action(bytebuf);
@@ -338,7 +421,7 @@
338
421
  try {
339
422
  var header = DECODE_HEADER(bytebuf);
340
423
  if (f.existy(header)) {
341
- logger.tracef("Read header(msgId=%d): opCode=%s, status=%s, hasNewTopology=%d",
424
+ logger.tracef('Read header(msgId=%d): opCode=%s, status=%s, hasNewTopology=%d',
342
425
  header.msgId, header.opCode, header.status, header.hasNewTopology);
343
426
  return {continue: true, result: header};
344
427
  }
@@ -365,13 +448,13 @@
365
448
  return function(header, bytebuf) {
366
449
  var decodeAction = decodeSingle(decoderValue);
367
450
  return decodeObject(header, bytebuf, decodeAction);
368
- }
451
+ };
369
452
  },
370
453
  decodeQuery: function() {
371
454
  return function(header, bytebuf) {
372
455
  var decodeAction = f.actions([f.partial2(codec.decodeQuery,ProtostreamType.lookupProtostreamTypeById,ProtobufRoot.findRootByTypeName)()],codec.lastDecoded);
373
456
  return decodeObject(header, bytebuf, decodeAction);
374
- }
457
+ };
375
458
  },
376
459
  decodeWithMeta: function() {
377
460
  var decoderValue = decoderMedia(this.valueMediaType);
@@ -403,7 +486,7 @@
403
486
  }
404
487
 
405
488
  return {result: undefined, continue: true};
406
- }
489
+ };
407
490
  },
408
491
  decodePrevOrElse: function(opts, cond, orElse) {
409
492
  var decoderValue = decoderMedia(this.valueMediaType);
@@ -415,7 +498,7 @@
415
498
  : {result: undefined, continue: true};
416
499
 
417
500
  return orElse(header);
418
- }
501
+ };
419
502
  },
420
503
  decodeCountValues: function() {
421
504
  var decoderKey = decoderMedia(this.keyMediaType);
@@ -430,7 +513,7 @@
430
513
  }
431
514
 
432
515
  return {result: pairs, continue: true};
433
- }
516
+ };
434
517
  },
435
518
  decodeStringPairs: function(header, bytebuf) {
436
519
  var count = DECODE_VINT(bytebuf);
@@ -503,9 +586,9 @@
503
586
  complete: function(f) {
504
587
  return function(header) {
505
588
  return {result: f(header), continue: true};
506
- }
589
+ };
507
590
  }
508
- }
591
+ };
509
592
  }());
510
593
 
511
594
  var ListenersMixin = (function() {
@@ -517,13 +600,20 @@
517
600
  codec.decodeUByte()], // event is retried
518
601
  function(values) {
519
602
  if (values.length < 3) {
520
- logger.tracef("Not enough to read (not array): %s", values);
603
+ logger.tracef('Not enough to read (not array): %s', values);
521
604
  return undefined;
522
605
  }
523
606
 
524
- return {listenerId: values[0], isCustom: values[1] == 1, isRetried: values[2] == 1}
607
+ return {listenerId: values[0], isCustom: values[1] == 1, isRetried: values[2] == 1};
525
608
  });
526
609
 
610
+ /**
611
+ * Returns an emit function for custom events or falls back to an alternative emitter.
612
+ * @param {boolean} isCustom Whether the event uses a custom converter.
613
+ * @param {Object} decoderBytes Decoder descriptor for byte decoding.
614
+ * @param {Function} alternativeEmit Fallback emit function factory.
615
+ * @returns {Function} Emit function that dispatches the event.
616
+ */
527
617
  function emitCustomOr(isCustom, decoderBytes, alternativeEmit) {
528
618
  if (isCustom) {
529
619
  return function(event, emitter, bytebuf, listenerId) {
@@ -553,6 +643,11 @@
553
643
  return alternativeEmit(decoderBytes);
554
644
  }
555
645
 
646
+ /**
647
+ * Creates an emit function that decodes and emits a key-version event.
648
+ * @param {Object} decoderKey Key decoder descriptor.
649
+ * @returns {Function} Emit function for key-version events.
650
+ */
556
651
  function emitKeyVersion(decoderKey) {
557
652
  return function(event, emitter, bytebuf, listenerId) {
558
653
  var decoder = f.actions(
@@ -572,6 +667,11 @@
572
667
  };
573
668
  }
574
669
 
670
+ /**
671
+ * Creates an emit function that decodes and emits a key-only event.
672
+ * @param {Object} decodeFn Decoder descriptor for the key.
673
+ * @returns {Function} Emit function for key-only events.
674
+ */
575
675
  function emitKey(decodeFn) {
576
676
  return function(event, emitter, bytebuf, listenerId) {
577
677
  var decodeActions = decodeSingle(decodeFn);
@@ -579,7 +679,7 @@
579
679
  logger.tracef('Emit %s event for key=%s', event, key);
580
680
  emitter.emit(event, key, listenerId);
581
681
  return true;
582
- }
682
+ };
583
683
  }
584
684
 
585
685
  return {
@@ -603,7 +703,7 @@
603
703
  codec.encodeUByte(0) // TODO filter factory name
604
704
  ];
605
705
 
606
- if (_.has(opts, "converterFactory") && _.has(opts.converterFactory, "name")) {
706
+ if (_.has(opts, 'converterFactory') && _.has(opts.converterFactory, 'name')) {
607
707
  steps.push(codec.encodeString(opts.converterFactory.name));
608
708
  steps.push(codec.encodeUByte(0)); // TODO add converter parameter support
609
709
  } else {
@@ -614,12 +714,12 @@
614
714
 
615
715
  return function() {
616
716
  return steps;
617
- }
717
+ };
618
718
  },
619
719
  encodeListenerId: function(listenerId) {
620
720
  return function() {
621
721
  return [codec.encodeString(listenerId)]; // listener id
622
- }
722
+ };
623
723
  },
624
724
  decodeEvent: function(header, bytebuf, listeners) {
625
725
  var common = DECODE_EVENT_COMMON(bytebuf);
@@ -639,7 +739,7 @@
639
739
  }
640
740
  return true;
641
741
  }
642
- }
742
+ };
643
743
  }());
644
744
 
645
745
  var IteratorMixin = (function() { // protocol 2.3+
@@ -650,11 +750,11 @@
650
750
  codec.decodeVInt()], // number of entries
651
751
  function(values) {
652
752
  if (values.length < 2) {
653
- logger.tracef("Not enough to read (not array): %s", values);
753
+ logger.tracef('Not enough to read (not array): %s', values);
654
754
  return undefined;
655
755
  }
656
756
 
657
- return {segments: values[0], count: values[1]}
757
+ return {segments: values[0], count: values[1]};
658
758
  });
659
759
  var DECODE_VERSION = f.actions(
660
760
  [codec.decodeFixedBytes(8)],
@@ -671,12 +771,12 @@
671
771
  codec.encodeVInt(batchSize), // batch size
672
772
  codec.encodeUByte(meta) // metadata
673
773
  ];
674
- }
774
+ };
675
775
  },
676
776
  encodeIterId: function(iterId) {
677
777
  return function() {
678
778
  return [codec.encodeString(iterId)];
679
- }
779
+ };
680
780
  },
681
781
  decodeIterId: function(header, bytebuf, conn) {
682
782
  var iterId = DECODE_STRING(bytebuf);
@@ -733,30 +833,35 @@
733
833
  }
734
834
 
735
835
  return {result: [], continue: true};
736
- }
836
+ };
737
837
  }
738
- }
838
+ };
739
839
  }());
740
840
 
741
841
  var NoListenerInterestsMixin = (function() {
742
842
  return {
743
- encodeListenerInterests: function (opts) {
843
+ encodeListenerInterests: function (_) {
744
844
  return [codec.encodeUByte(0x0F)]; // interested in all
745
845
  }
746
- }
846
+ };
747
847
  }());
748
848
 
749
849
  var ListenerInterestsMixin = (function() {
750
850
  return {
751
- encodeListenerInterests: function (opts) {
851
+ encodeListenerInterests: function (_) {
752
852
  return [codec.encodeUByte(0x0F)]; // interested in all
753
853
  }
754
- }
854
+ };
755
855
  }());
756
856
 
757
857
  var MediaType = function(mediaType) {
758
858
  var mediaCodecs = resolveEncoders(mediaType);
759
859
 
860
+ /**
861
+ * Resolves encoder and decoder functions for a given media type.
862
+ * @param {string} mediaType Media type string (e.g. 'application/json').
863
+ * @returns {Array} Three-element array of [idByte, encoderFn, decoderFn].
864
+ */
760
865
  function resolveEncoders(mediaType) {
761
866
  return f.dispatch(
762
867
  f.isa('application/json', encoder(2, encoderJson, decoderJson()))
@@ -765,30 +870,64 @@
765
870
  )(mediaType);
766
871
  }
767
872
 
873
+ /**
874
+ * Encodes a value as JSON.
875
+ * @param {*} json Value to encode as JSON.
876
+ * @returns {Buffer} Encoded JSON bytes.
877
+ */
768
878
  function encoderJson(json) {
769
879
  return codec.encodeJSON(json);
770
880
  }
771
881
 
882
+ /**
883
+ * Creates a JSON decoder function.
884
+ * @returns {Function} Decoder function for JSON values.
885
+ */
772
886
  function decoderJson() {
773
887
  return codec.decodeJSON();
774
888
  }
775
889
 
890
+ /**
891
+ * Encodes a value as a string.
892
+ * @param {string} str String value to encode.
893
+ * @returns {Buffer} Encoded string bytes.
894
+ */
776
895
  function encoderString(str) {
777
896
  return codec.encodeString(str);
778
897
  }
779
898
 
899
+ /**
900
+ * Creates a string decoder function.
901
+ * @returns {Function} Decoder function for string values.
902
+ */
780
903
  function decoderString() {
781
904
  return codec.decodeString();
782
905
  }
783
906
 
907
+ /**
908
+ * Encodes a value as Protobuf.
909
+ * @param {Object} message Protobuf message to encode.
910
+ * @returns {Buffer} Encoded Protobuf bytes.
911
+ */
784
912
  function encoderProtobuf(message){
785
913
  return codec.encodeProtobuf(message,ProtostreamType.lookupProtostreamTypeByName);
786
914
  }
787
915
 
916
+ /**
917
+ * Creates a Protobuf decoder function.
918
+ * @returns {Function} Decoder function for Protobuf values.
919
+ */
788
920
  function decoderProtobuf(){
789
921
  return f.partial2(codec.decodeProtobuf,ProtostreamType.lookupProtostreamTypeById,ProtobufRoot.findRootByTypeName)();
790
922
  }
791
923
 
924
+ /**
925
+ * Creates a media type encoder tuple with id, encoder, and decoder.
926
+ * @param {number} id Media type identifier byte.
927
+ * @param {Function} mediaEncoder Encoder function for this media type.
928
+ * @param {Function} mediaDecoder Decoder function for this media type.
929
+ * @returns {Function} Factory function returning [id, encoder, decoder] array.
930
+ */
792
931
  function encoder(id, mediaEncoder, mediaDecoder) {
793
932
  return function() {
794
933
  return [
@@ -796,7 +935,7 @@
796
935
  , mediaEncoder
797
936
  , mediaDecoder
798
937
  ];
799
- }
938
+ };
800
939
  }
801
940
 
802
941
  return {
@@ -817,7 +956,7 @@
817
956
 
818
957
  var NoMediaTypesMixin = (function() {
819
958
  return {
820
- init: function(clientOpts) {
959
+ init: function(_) {
821
960
  this.keyMediaType = new MediaType('text/plain');
822
961
  this.valueMediaType = new MediaType('text/plain');
823
962
  },
@@ -830,10 +969,15 @@
830
969
  encodeMediaValue: function(v) {
831
970
  return codec.encodeString(v);
832
971
  }
833
- }
972
+ };
834
973
  }());
835
974
 
836
975
  var MediaTypesMixin = (function() {
976
+ /**
977
+ * Encodes a media type into protocol bytes.
978
+ * @param {Object} mediaType MediaType instance to encode.
979
+ * @returns {Array} Array of encoded media type bytes.
980
+ */
837
981
  function encodeMediaType(mediaType) {
838
982
  return [
839
983
  codec.encodeUByte(1)
@@ -842,6 +986,11 @@
842
986
  ];
843
987
  }
844
988
 
989
+ /**
990
+ * Decodes a server media type from the byte buffer.
991
+ * @param {Object} bytebuf Byte buffer to decode from.
992
+ * @returns {Object} Result object with continue flag.
993
+ */
845
994
  function decodeServerMediaType(bytebuf) {
846
995
  var mediaType = DECODE_UBYTE(bytebuf);
847
996
  if (!f.existy(mediaType))
@@ -860,7 +1009,7 @@
860
1009
 
861
1010
  return {
862
1011
  init: function(clientOpts) {
863
- logger.debugf("Before init, key media type was: %s"
1012
+ logger.debugf('Before init, key media type was: %s'
864
1013
  , f.existy(this.keyMediaType) ? this.keyMediaType.getMediaType() : 'undefined'
865
1014
  );
866
1015
 
@@ -868,7 +1017,7 @@
868
1017
  this.valueMediaType = new MediaType(clientOpts.dataFormat.valueType);
869
1018
  this.authOpts = clientOpts.authentication;
870
1019
 
871
- logger.debugf("After init, key media type is: %s"
1020
+ logger.debugf('After init, key media type is: %s'
872
1021
  , f.existy(this.keyMediaType) ? this.keyMediaType.getMediaType() : 'undefined'
873
1022
  );
874
1023
  },
@@ -899,7 +1048,7 @@
899
1048
  }
900
1049
  return {continue: false};
901
1050
  }
902
- }
1051
+ };
903
1052
  }());
904
1053
 
905
1054
  var SASLMixin = (function() {
@@ -913,7 +1062,7 @@
913
1062
  if (f.existy(authMech)) {
914
1063
  authMechs.push(authMech);
915
1064
  } else {
916
- return {result: 'Unexpected error decoding auth mechanism ' + i, continue: false};
1065
+ return {result: `Unexpected error decoding auth mechanism ${ i}`, continue: false};
917
1066
  }
918
1067
  }
919
1068
  logger.tracef(authMechs);
@@ -938,7 +1087,7 @@
938
1087
  response = '';
939
1088
  }
940
1089
  } else {
941
- logger.tracef("SASL server challenge response [%s]", holder.mech.name);
1090
+ logger.tracef('SASL server challenge response [%s]', holder.mech.name);
942
1091
  response = holder.mech.challenge(holder.challenge).response({
943
1092
  username: authOpts.userName,
944
1093
  password: authOpts.password,
@@ -948,9 +1097,9 @@
948
1097
  host: 'infinispan'
949
1098
  });
950
1099
  }
951
- logger.tracef("SASL client response [%s]", holder.mech.name);
1100
+ logger.tracef('SASL client response [%s]', holder.mech.name);
952
1101
  return [codec.encodeString(authOpts.saslMechanism), codec.encodeString(response)];
953
- }
1102
+ };
954
1103
  },
955
1104
  decodeSasl: function(header, bytebuf) {
956
1105
  var authDone = DECODE_UBYTE(bytebuf);
@@ -961,23 +1110,23 @@
961
1110
  return {result: {response: DECODE_STRING(bytebuf)}, continue: true};
962
1111
  }
963
1112
  }
964
- }
1113
+ };
965
1114
  }());
966
1115
 
967
1116
  var Ping29Mixin = (function() {
968
1117
  return {
969
1118
  decodePingResponse: function(header,bytebuf){
970
- logger.debugf("header and bytebuf %s %s",header,bytebuf);
1119
+ logger.debugf('header and bytebuf %s %s',header,bytebuf);
971
1120
  return MediaTypesMixin.decodeServerMediaTypes(header,bytebuf);
972
1121
  }
973
- }
1122
+ };
974
1123
  }());
975
1124
 
976
1125
  var Ping30Mixin = (function() {
977
1126
  return {
978
1127
  decodePingResponse: function(header,bytebuf){
979
1128
  var serverMediaTypes= MediaTypesMixin.decodeServerMediaTypes(header,bytebuf);
980
- var version=DECODE_UBYTE(bytebuf);
1129
+ DECODE_UBYTE(bytebuf);
981
1130
  var opCount=DECODE_VINT(bytebuf);
982
1131
  var opRequestCodes=[];
983
1132
  for(let i=0;i<opCount;i++){
@@ -985,7 +1134,242 @@
985
1134
  }
986
1135
  return serverMediaTypes;
987
1136
  }
1137
+ };
1138
+ }());
1139
+
1140
+ var CounterMixin = (function() {
1141
+ var DECODE_LONG = f.actions([codec.decodeLong()], codec.lastDecoded);
1142
+
1143
+ // Counter status codes
1144
+ var COUNTER_SUCCESS = 0x00;
1145
+ var COUNTER_NOT_DEFINED = 0x02;
1146
+
1147
+ /**
1148
+ * Encodes only the counter name as the request body.
1149
+ * @param {string} name Counter name.
1150
+ * @returns {Function} Body encoder function.
1151
+ */
1152
+ function encodeCounterName(name) {
1153
+ return function() {
1154
+ return [codec.encodeString(name)];
1155
+ };
1156
+ }
1157
+
1158
+ /**
1159
+ * Encodes a counter name followed by a long value.
1160
+ * @param {string} name Counter name.
1161
+ * @param {number} value Long value.
1162
+ * @returns {Function} Body encoder function.
1163
+ */
1164
+ function encodeCounterNameValue(name, value) {
1165
+ return function() {
1166
+ return [codec.encodeString(name), codec.encodeLong(value)];
1167
+ };
1168
+ }
1169
+
1170
+ /**
1171
+ * Encodes a counter name followed by two long values (expect, update).
1172
+ * @param {string} name Counter name.
1173
+ * @param {number} expect Expected value.
1174
+ * @param {number} update New value.
1175
+ * @returns {Function} Body encoder function.
1176
+ */
1177
+ function encodeCounterNameTwoLongs(name, expect, update) {
1178
+ return function() {
1179
+ return [codec.encodeString(name), codec.encodeLong(expect), codec.encodeLong(update)];
1180
+ };
1181
+ }
1182
+
1183
+ /**
1184
+ * Encodes a counter create request body (name + configuration).
1185
+ * @param {string} name Counter name.
1186
+ * @param {Object} config Counter configuration.
1187
+ * @returns {Function} Body encoder function.
1188
+ */
1189
+ function encodeCounterCreate(name, config) {
1190
+ return function() {
1191
+ var isWeak = config.type === 'weak';
1192
+ var isBounded = config.type === 'strong' && (typeof config.lowerBound === 'number' || typeof config.upperBound === 'number');
1193
+ var isPersistent = config.storage === 'persistent';
1194
+ var flags = (isWeak ? 0x01 : 0x00)
1195
+ | (isBounded ? 0x02 : 0x00)
1196
+ | (isPersistent ? 0x04 : 0x00);
1197
+
1198
+ var steps = [
1199
+ codec.encodeString(name),
1200
+ codec.encodeUByte(flags)
1201
+ ];
1202
+
1203
+ if (isWeak) {
1204
+ steps.push(codec.encodeVInt(config.concurrencyLevel || 16));
1205
+ }
1206
+
1207
+ if (isBounded) {
1208
+ steps.push(codec.encodeLong(typeof config.lowerBound === 'number' ? config.lowerBound : 0));
1209
+ steps.push(codec.encodeLong(typeof config.upperBound === 'number' ? config.upperBound : 0));
1210
+ }
1211
+
1212
+ steps.push(codec.encodeLong(config.initialValue || 0));
1213
+
1214
+ return steps;
1215
+ };
1216
+ }
1217
+
1218
+ /**
1219
+ * Decodes a counter long value from the response.
1220
+ * @param {Object} header Response header.
1221
+ * @param {Object} bytebuf Byte buffer to decode from.
1222
+ * @returns {Object} Result object with decoded long value.
1223
+ */
1224
+ function decodeCounterValue(header, bytebuf) {
1225
+ if (header.status === COUNTER_SUCCESS) {
1226
+ var value = DECODE_LONG(bytebuf);
1227
+ if (!f.existy(value) && value !== 0)
1228
+ return {continue: false};
1229
+ return {result: value, continue: true};
1230
+ }
1231
+ if (header.status === COUNTER_NOT_DEFINED)
1232
+ return {result: undefined, continue: true};
1233
+ // Status 0x04 = boundary reached — still has a value
1234
+ if (header.status === 0x04) {
1235
+ var boundValue = DECODE_LONG(bytebuf);
1236
+ if (!f.existy(boundValue) && boundValue !== 0)
1237
+ return {continue: false};
1238
+ return {result: boundValue, continue: true};
1239
+ }
1240
+ return {result: undefined, continue: true};
1241
+ }
1242
+
1243
+ /**
1244
+ * Decodes a counter configuration from the response.
1245
+ * @param {Object} header Response header.
1246
+ * @param {Object} bytebuf Byte buffer to decode from.
1247
+ * @returns {Object} Result object with decoded configuration.
1248
+ */
1249
+ function decodeCounterConfig(header, bytebuf) {
1250
+ if (header.status !== COUNTER_SUCCESS)
1251
+ return {result: undefined, continue: true};
1252
+
1253
+ var flags = DECODE_UBYTE(bytebuf);
1254
+ if (!f.existy(flags))
1255
+ return {continue: false};
1256
+
1257
+ var isWeak = (flags & 0x01) !== 0;
1258
+ var isBounded = (flags & 0x02) !== 0;
1259
+ var isPersistent = (flags & 0x04) !== 0;
1260
+
1261
+ var config = {
1262
+ type: isWeak ? 'weak' : 'strong',
1263
+ storage: isPersistent ? 'persistent' : 'volatile'
1264
+ };
1265
+
1266
+ if (isWeak) {
1267
+ var concurrencyLevel = DECODE_VINT(bytebuf);
1268
+ if (!f.existy(concurrencyLevel))
1269
+ return {continue: false};
1270
+ config.concurrencyLevel = concurrencyLevel;
1271
+ }
1272
+
1273
+ if (isBounded) {
1274
+ var lowerBound = DECODE_LONG(bytebuf);
1275
+ if (!f.existy(lowerBound) && lowerBound !== 0)
1276
+ return {continue: false};
1277
+ config.lowerBound = lowerBound;
1278
+
1279
+ var upperBound = DECODE_LONG(bytebuf);
1280
+ if (!f.existy(upperBound) && upperBound !== 0)
1281
+ return {continue: false};
1282
+ config.upperBound = upperBound;
1283
+ }
1284
+
1285
+ var initialValue = DECODE_LONG(bytebuf);
1286
+ if (!f.existy(initialValue) && initialValue !== 0)
1287
+ return {continue: false};
1288
+ config.initialValue = initialValue;
1289
+
1290
+ return {result: config, continue: true};
1291
+ }
1292
+
1293
+ /**
1294
+ * Decodes a success/failure status for counter operations.
1295
+ * @param {Object} header Response header.
1296
+ * @returns {Object} Result object with boolean success.
1297
+ */
1298
+ function decodeCounterStatus(header) {
1299
+ return {result: header.status === COUNTER_SUCCESS, continue: true};
1300
+ }
1301
+
1302
+ return {
1303
+ encodeCounterCreate: encodeCounterCreate,
1304
+ encodeCounterName: encodeCounterName,
1305
+ encodeCounterNameValue: encodeCounterNameValue,
1306
+ encodeCounterNameTwoLongs: encodeCounterNameTwoLongs,
1307
+ decodeCounterValue: decodeCounterValue,
1308
+ decodeCounterConfig: decodeCounterConfig,
1309
+ decodeCounterStatus: decodeCounterStatus
1310
+ };
1311
+ }());
1312
+
1313
+ /**
1314
+ * Protocol 4.0+ requires an 'otherParams' map after media types in the header.
1315
+ * This mixin overrides stepsHeader to append the count (0 = no params).
1316
+ */
1317
+ var OtherParamsMixin = {
1318
+ stepsHeader: function(ctx, op, opts) {
1319
+ var header = this.encodeHeader(op, ctx.topologyId, opts)(ctx.id);
1320
+ var mediaType = this.encodeMediaTypes();
1321
+ return f.cat(header, mediaType, [codec.encodeVInt(0)]);
1322
+ }
1323
+ };
1324
+
1325
+ var DecodePrevWithMetaMixin = (function() {
1326
+ /**
1327
+ * Decodes a previous value with metadata from the response buffer.
1328
+ * @param {Object} header - Response header.
1329
+ * @param {Object} bytebuf - Byte buffer to decode from.
1330
+ * @param {Function} decoderValue - Value decoder function.
1331
+ * @returns {Object} Result object with decoded previous value and metadata.
1332
+ */
1333
+ function decodePrevWithMeta(header, bytebuf, decoderValue) {
1334
+ var flags = DECODE_UBYTE(bytebuf);
1335
+ if (!f.existy(flags))
1336
+ return {continue: false};
1337
+
1338
+ var lifespan = decodeTimestamp(flags, INFINITE_LIFESPAN, ['created', 'lifespan'], bytebuf);
1339
+ if (!f.existy(lifespan))
1340
+ return {continue: false};
1341
+
1342
+ var idle = decodeTimestamp(flags, INFINITE_MAXIDLE, ['lastUsed', 'maxIdle'], bytebuf);
1343
+ if (!f.existy(idle))
1344
+ return {continue: false};
1345
+
1346
+ var decoder = f.actions(
1347
+ [codec.decodeFixedBytes(8), decoderValue.fun(decoderValue.obj)]
1348
+ , function(values) {
1349
+ return {version: values[0], value: values[1]};
1350
+ });
1351
+ var versioned = decoder(bytebuf);
1352
+ if (f.existy(versioned)) {
1353
+ var result = f.merge(versioned, lifespan, idle);
1354
+ return {result: _.isEmpty(result.value) ? undefined : result, continue: true};
1355
+ }
1356
+ return {continue: false};
988
1357
  }
1358
+
1359
+ return {
1360
+ decodePrevOrElse: function(opts, cond, orElse) {
1361
+ var decoderValue = decoderMedia(this.valueMediaType);
1362
+ var shouldReturnPrev = hasOptPrev(opts);
1363
+ return function(header, bytebuf) {
1364
+ if (shouldReturnPrev)
1365
+ return cond(header)
1366
+ ? decodePrevWithMeta(header, bytebuf, decoderValue)
1367
+ : {result: undefined, continue: true};
1368
+
1369
+ return orElse(header);
1370
+ };
1371
+ }
1372
+ };
989
1373
  }());
990
1374
 
991
1375
  var ProtostreamType = (function() {
@@ -1001,15 +1385,15 @@
1001
1385
  },
1002
1386
  lookupProtostreamTypeByName: function (protostreamTypeName) {
1003
1387
  return _.find(protostreamTypes,function(protostreamType){
1004
- return _.isEqual(protostreamType.protostreamTypeName,protostreamTypeName)
1388
+ return _.isEqual(protostreamType.protostreamTypeName,protostreamTypeName);
1005
1389
  }).protostreamDescriptorId;
1006
1390
  },
1007
1391
  lookupProtostreamTypeById: function(protostreamDescriptorId){
1008
1392
  return _.find(protostreamTypes,function(protostreamType){
1009
- return _.isEqual(protostreamType.protostreamDescriptorId,protostreamDescriptorId)
1393
+ return _.isEqual(protostreamType.protostreamDescriptorId,protostreamDescriptorId);
1010
1394
  }).protostreamTypeName;
1011
1395
  }
1012
- }
1396
+ };
1013
1397
  }());
1014
1398
 
1015
1399
  var ProtobufRoot = (function() {
@@ -1024,13 +1408,20 @@
1024
1408
  var root= protobufRoot.lookupType(typeName);
1025
1409
  return root;
1026
1410
  }catch(err){
1027
- throw new Error("Protobuf root not found");
1411
+ throw new Error('Protobuf root not found');
1028
1412
  }
1029
1413
  }
1030
- }
1414
+ };
1031
1415
  }());
1032
1416
 
1033
1417
 
1418
+ /**
1419
+ * Constructs a Hot Rod protocol instance for the given version.
1420
+ * @param {number} v Protocol version number.
1421
+ * @param {Object} clientOpts Client options.
1422
+ * @constructs Protocol
1423
+ * @returns {void}
1424
+ */
1034
1425
  function Protocol(v, clientOpts) {
1035
1426
  this.version = v;
1036
1427
  this.clientOpts = clientOpts;
@@ -1055,11 +1446,23 @@
1055
1446
  Protocol.call(this, 30, clientOpts);
1056
1447
  };
1057
1448
 
1449
+ var Protocol31 = function(clientOpts) {
1450
+ Protocol.call(this, 31, clientOpts);
1451
+ };
1452
+
1453
+ var Protocol40 = function(clientOpts) {
1454
+ Protocol.call(this, 40, clientOpts);
1455
+ };
1456
+
1457
+ var Protocol41 = function(clientOpts) {
1458
+ Protocol.call(this, 41, clientOpts);
1459
+ };
1460
+
1058
1461
  // TODO: Missing operations, just for reference
1059
1462
  var IdsMixin = {
1060
- queryId: function() { return 0x1F },
1061
- authMechId: function() { return 0x21 },
1062
- authReqId: function() { return 0x23 }
1463
+ queryId: function() { return 0x1F; },
1464
+ authMechId: function() { return 0x21; },
1465
+ authReqId: function() { return 0x23; }
1063
1466
  };
1064
1467
 
1065
1468
  _.extend(Protocol22.prototype
@@ -1122,14 +1525,61 @@
1122
1525
  , Ping30Mixin
1123
1526
  , ProtostreamType
1124
1527
  , ProtobufRoot
1125
- // TODO 2.6 new ops: getStream and putStream
1126
- // TODO 2.6 add listener change: listener event interests
1127
- // TODO 2.7 new ops: prepare, commit and rollback
1128
- // TODO 2.7 new ops: counter operations
1129
- // TODO 2.7 new events: counter events
1130
- // TODO 2.8 listener events: can come from any connection
1131
- // TODO 2.8 header change: media types
1132
- );
1528
+ );
1529
+
1530
+ _.extend(Protocol31.prototype
1531
+ , EncodeMixin
1532
+ , ExpiryEncodeMixin
1533
+ , DecodeMixin
1534
+ , IdsMixin
1535
+ , ListenersMixin
1536
+ , ListenerInterestsMixin
1537
+ , IteratorMixin
1538
+ , MediaTypesMixin
1539
+ , SASLMixin
1540
+ , Ping30Mixin
1541
+ , CounterMixin
1542
+ , ProtostreamType
1543
+ , ProtobufRoot
1544
+ // TODO 3.1 new ops: bloom filter near-cache
1545
+ );
1546
+
1547
+ _.extend(Protocol40.prototype
1548
+ , EncodeMixin
1549
+ , ExpiryEncodeMixin
1550
+ , OtherParamsMixin
1551
+ , DecodeMixin
1552
+ , DecodePrevWithMetaMixin
1553
+ , IdsMixin
1554
+ , ListenersMixin
1555
+ , ListenerInterestsMixin
1556
+ , IteratorMixin
1557
+ , MediaTypesMixin
1558
+ , SASLMixin
1559
+ , Ping30Mixin
1560
+ , CounterMixin
1561
+ , ProtostreamType
1562
+ , ProtobufRoot
1563
+ );
1564
+
1565
+ _.extend(Protocol41.prototype
1566
+ , EncodeMixin
1567
+ , ExpiryEncodeMixin
1568
+ , OtherParamsMixin
1569
+ , DecodeMixin
1570
+ , DecodePrevWithMetaMixin
1571
+ , IdsMixin
1572
+ , ListenersMixin
1573
+ , ListenerInterestsMixin
1574
+ , IteratorMixin
1575
+ , MediaTypesMixin
1576
+ , SASLMixin
1577
+ , Ping30Mixin
1578
+ , CounterMixin
1579
+ , ProtostreamType
1580
+ , ProtobufRoot
1581
+ // TODO 4.1 new ops: chunked streaming (GetStreamStart/Next/End, PutStreamStart/Next/End)
1582
+ );
1133
1583
 
1134
1584
  exports.version22 = function(clientOpts) {
1135
1585
  return new Protocol22(clientOpts);
@@ -1147,4 +1597,39 @@
1147
1597
  return new Protocol30(clientOpts);
1148
1598
  };
1149
1599
 
1600
+ exports.version31 = function(clientOpts) {
1601
+ return new Protocol31(clientOpts);
1602
+ };
1603
+
1604
+ exports.version40 = function(clientOpts) {
1605
+ return new Protocol40(clientOpts);
1606
+ };
1607
+
1608
+ exports.version41 = function(clientOpts) {
1609
+ return new Protocol41(clientOpts);
1610
+ };
1611
+
1612
+ var VERSION_ORDER = ['4.1', '4.0', '3.1', '3.0', '2.9', '2.5', '2.2'];
1613
+
1614
+ exports.VERSION_ORDER = VERSION_ORDER;
1615
+
1616
+ /**
1617
+ * Creates a protocol instance for the given version string.
1618
+ * @param {string} version Protocol version (e.g. '3.1', '4.0').
1619
+ * @param {Object} clientOpts Client configuration options.
1620
+ * @returns {Object} Protocol instance.
1621
+ */
1622
+ exports.resolve = function(version, clientOpts) {
1623
+ switch (version) {
1624
+ case '4.1': return new Protocol41(clientOpts);
1625
+ case '4.0': return new Protocol40(clientOpts);
1626
+ case '3.1': return new Protocol31(clientOpts);
1627
+ case '3.0': return new Protocol30(clientOpts);
1628
+ case '2.9': return new Protocol29(clientOpts);
1629
+ case '2.5': return new Protocol25(clientOpts);
1630
+ case '2.2': return new Protocol22(clientOpts);
1631
+ default: throw new Error(`Unknown protocol version: ${version}`);
1632
+ }
1633
+ };
1634
+
1150
1635
  }.call(this));