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/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 {
@@ -599,27 +699,40 @@
599
699
 
600
700
  var steps = [
601
701
  codec.encodeString(listenerId), // listener id
602
- codec.encodeUByte(includeState), // include state
603
- codec.encodeUByte(0) // TODO filter factory name
702
+ codec.encodeUByte(includeState) // include state
604
703
  ];
605
704
 
606
- if (_.has(opts, "converterFactory") && _.has(opts.converterFactory, "name")) {
705
+ // Filter factory
706
+ if (_.has(opts, 'filterFactory') && _.has(opts.filterFactory, 'name')) {
707
+ steps.push(codec.encodeString(opts.filterFactory.name));
708
+ var filterParams = opts.filterFactory.params || [];
709
+ steps.push(codec.encodeUByte(filterParams.length));
710
+ filterParams.forEach(function(p) { steps.push(codec.encodeBytesWithLength(p)); });
711
+ } else {
712
+ steps.push(codec.encodeUByte(0));
713
+ }
714
+
715
+ // Converter factory
716
+ if (_.has(opts, 'converterFactory') && _.has(opts.converterFactory, 'name')) {
607
717
  steps.push(codec.encodeString(opts.converterFactory.name));
608
- steps.push(codec.encodeUByte(0)); // TODO add converter parameter support
718
+ var converterParams = opts.converterFactory.params || [];
719
+ steps.push(codec.encodeUByte(converterParams.length));
720
+ converterParams.forEach(function(p) { steps.push(codec.encodeBytesWithLength(p)); });
609
721
  } else {
610
- steps.push(codec.encodeUByte(0)); // no converter
722
+ steps.push(codec.encodeUByte(0));
611
723
  }
612
724
 
613
- steps.push(codec.encodeUByte(0)); // raw data disabled
725
+ // Raw data
726
+ steps.push(codec.encodeUByte(hasOpt(opts, 'useRawData') ? 1 : 0));
614
727
 
615
728
  return function() {
616
729
  return steps;
617
- }
730
+ };
618
731
  },
619
732
  encodeListenerId: function(listenerId) {
620
733
  return function() {
621
734
  return [codec.encodeString(listenerId)]; // listener id
622
- }
735
+ };
623
736
  },
624
737
  decodeEvent: function(header, bytebuf, listeners) {
625
738
  var common = DECODE_EVENT_COMMON(bytebuf);
@@ -639,7 +752,7 @@
639
752
  }
640
753
  return true;
641
754
  }
642
- }
755
+ };
643
756
  }());
644
757
 
645
758
  var IteratorMixin = (function() { // protocol 2.3+
@@ -650,11 +763,11 @@
650
763
  codec.decodeVInt()], // number of entries
651
764
  function(values) {
652
765
  if (values.length < 2) {
653
- logger.tracef("Not enough to read (not array): %s", values);
766
+ logger.tracef('Not enough to read (not array): %s', values);
654
767
  return undefined;
655
768
  }
656
769
 
657
- return {segments: values[0], count: values[1]}
770
+ return {segments: values[0], count: values[1]};
658
771
  });
659
772
  var DECODE_VERSION = f.actions(
660
773
  [codec.decodeFixedBytes(8)],
@@ -671,12 +784,12 @@
671
784
  codec.encodeVInt(batchSize), // batch size
672
785
  codec.encodeUByte(meta) // metadata
673
786
  ];
674
- }
787
+ };
675
788
  },
676
789
  encodeIterId: function(iterId) {
677
790
  return function() {
678
791
  return [codec.encodeString(iterId)];
679
- }
792
+ };
680
793
  },
681
794
  decodeIterId: function(header, bytebuf, conn) {
682
795
  var iterId = DECODE_STRING(bytebuf);
@@ -733,30 +846,35 @@
733
846
  }
734
847
 
735
848
  return {result: [], continue: true};
736
- }
849
+ };
737
850
  }
738
- }
851
+ };
739
852
  }());
740
853
 
741
854
  var NoListenerInterestsMixin = (function() {
742
855
  return {
743
- encodeListenerInterests: function (opts) {
856
+ encodeListenerInterests: function (_) {
744
857
  return [codec.encodeUByte(0x0F)]; // interested in all
745
858
  }
746
- }
859
+ };
747
860
  }());
748
861
 
749
862
  var ListenerInterestsMixin = (function() {
750
863
  return {
751
- encodeListenerInterests: function (opts) {
864
+ encodeListenerInterests: function (_) {
752
865
  return [codec.encodeUByte(0x0F)]; // interested in all
753
866
  }
754
- }
867
+ };
755
868
  }());
756
869
 
757
870
  var MediaType = function(mediaType) {
758
871
  var mediaCodecs = resolveEncoders(mediaType);
759
872
 
873
+ /**
874
+ * Resolves encoder and decoder functions for a given media type.
875
+ * @param {string} mediaType Media type string (e.g. 'application/json').
876
+ * @returns {Array} Three-element array of [idByte, encoderFn, decoderFn].
877
+ */
760
878
  function resolveEncoders(mediaType) {
761
879
  return f.dispatch(
762
880
  f.isa('application/json', encoder(2, encoderJson, decoderJson()))
@@ -765,30 +883,64 @@
765
883
  )(mediaType);
766
884
  }
767
885
 
886
+ /**
887
+ * Encodes a value as JSON.
888
+ * @param {*} json Value to encode as JSON.
889
+ * @returns {Buffer} Encoded JSON bytes.
890
+ */
768
891
  function encoderJson(json) {
769
892
  return codec.encodeJSON(json);
770
893
  }
771
894
 
895
+ /**
896
+ * Creates a JSON decoder function.
897
+ * @returns {Function} Decoder function for JSON values.
898
+ */
772
899
  function decoderJson() {
773
900
  return codec.decodeJSON();
774
901
  }
775
902
 
903
+ /**
904
+ * Encodes a value as a string.
905
+ * @param {string} str String value to encode.
906
+ * @returns {Buffer} Encoded string bytes.
907
+ */
776
908
  function encoderString(str) {
777
909
  return codec.encodeString(str);
778
910
  }
779
911
 
912
+ /**
913
+ * Creates a string decoder function.
914
+ * @returns {Function} Decoder function for string values.
915
+ */
780
916
  function decoderString() {
781
917
  return codec.decodeString();
782
918
  }
783
919
 
920
+ /**
921
+ * Encodes a value as Protobuf.
922
+ * @param {Object} message Protobuf message to encode.
923
+ * @returns {Buffer} Encoded Protobuf bytes.
924
+ */
784
925
  function encoderProtobuf(message){
785
926
  return codec.encodeProtobuf(message,ProtostreamType.lookupProtostreamTypeByName);
786
927
  }
787
928
 
929
+ /**
930
+ * Creates a Protobuf decoder function.
931
+ * @returns {Function} Decoder function for Protobuf values.
932
+ */
788
933
  function decoderProtobuf(){
789
934
  return f.partial2(codec.decodeProtobuf,ProtostreamType.lookupProtostreamTypeById,ProtobufRoot.findRootByTypeName)();
790
935
  }
791
936
 
937
+ /**
938
+ * Creates a media type encoder tuple with id, encoder, and decoder.
939
+ * @param {number} id Media type identifier byte.
940
+ * @param {Function} mediaEncoder Encoder function for this media type.
941
+ * @param {Function} mediaDecoder Decoder function for this media type.
942
+ * @returns {Function} Factory function returning [id, encoder, decoder] array.
943
+ */
792
944
  function encoder(id, mediaEncoder, mediaDecoder) {
793
945
  return function() {
794
946
  return [
@@ -796,7 +948,7 @@
796
948
  , mediaEncoder
797
949
  , mediaDecoder
798
950
  ];
799
- }
951
+ };
800
952
  }
801
953
 
802
954
  return {
@@ -817,7 +969,7 @@
817
969
 
818
970
  var NoMediaTypesMixin = (function() {
819
971
  return {
820
- init: function(clientOpts) {
972
+ init: function(_) {
821
973
  this.keyMediaType = new MediaType('text/plain');
822
974
  this.valueMediaType = new MediaType('text/plain');
823
975
  },
@@ -830,10 +982,15 @@
830
982
  encodeMediaValue: function(v) {
831
983
  return codec.encodeString(v);
832
984
  }
833
- }
985
+ };
834
986
  }());
835
987
 
836
988
  var MediaTypesMixin = (function() {
989
+ /**
990
+ * Encodes a media type into protocol bytes.
991
+ * @param {Object} mediaType MediaType instance to encode.
992
+ * @returns {Array} Array of encoded media type bytes.
993
+ */
837
994
  function encodeMediaType(mediaType) {
838
995
  return [
839
996
  codec.encodeUByte(1)
@@ -842,6 +999,11 @@
842
999
  ];
843
1000
  }
844
1001
 
1002
+ /**
1003
+ * Decodes a server media type from the byte buffer.
1004
+ * @param {Object} bytebuf Byte buffer to decode from.
1005
+ * @returns {Object} Result object with continue flag.
1006
+ */
845
1007
  function decodeServerMediaType(bytebuf) {
846
1008
  var mediaType = DECODE_UBYTE(bytebuf);
847
1009
  if (!f.existy(mediaType))
@@ -860,7 +1022,7 @@
860
1022
 
861
1023
  return {
862
1024
  init: function(clientOpts) {
863
- logger.debugf("Before init, key media type was: %s"
1025
+ logger.debugf('Before init, key media type was: %s'
864
1026
  , f.existy(this.keyMediaType) ? this.keyMediaType.getMediaType() : 'undefined'
865
1027
  );
866
1028
 
@@ -868,7 +1030,7 @@
868
1030
  this.valueMediaType = new MediaType(clientOpts.dataFormat.valueType);
869
1031
  this.authOpts = clientOpts.authentication;
870
1032
 
871
- logger.debugf("After init, key media type is: %s"
1033
+ logger.debugf('After init, key media type is: %s'
872
1034
  , f.existy(this.keyMediaType) ? this.keyMediaType.getMediaType() : 'undefined'
873
1035
  );
874
1036
  },
@@ -899,7 +1061,7 @@
899
1061
  }
900
1062
  return {continue: false};
901
1063
  }
902
- }
1064
+ };
903
1065
  }());
904
1066
 
905
1067
  var SASLMixin = (function() {
@@ -913,7 +1075,7 @@
913
1075
  if (f.existy(authMech)) {
914
1076
  authMechs.push(authMech);
915
1077
  } else {
916
- return {result: 'Unexpected error decoding auth mechanism ' + i, continue: false};
1078
+ return {result: `Unexpected error decoding auth mechanism ${ i}`, continue: false};
917
1079
  }
918
1080
  }
919
1081
  logger.tracef(authMechs);
@@ -938,7 +1100,7 @@
938
1100
  response = '';
939
1101
  }
940
1102
  } else {
941
- logger.tracef("SASL server challenge response [%s]", holder.mech.name);
1103
+ logger.tracef('SASL server challenge response [%s]', holder.mech.name);
942
1104
  response = holder.mech.challenge(holder.challenge).response({
943
1105
  username: authOpts.userName,
944
1106
  password: authOpts.password,
@@ -948,36 +1110,36 @@
948
1110
  host: 'infinispan'
949
1111
  });
950
1112
  }
951
- logger.tracef("SASL client response [%s]", holder.mech.name);
1113
+ logger.tracef('SASL client response [%s]', holder.mech.name);
952
1114
  return [codec.encodeString(authOpts.saslMechanism), codec.encodeString(response)];
953
- }
1115
+ };
954
1116
  },
955
1117
  decodeSasl: function(header, bytebuf) {
956
1118
  var authDone = DECODE_UBYTE(bytebuf);
957
1119
  if(authDone == 1) {
958
1120
  logger.tracef('SASL authentication complete');
959
- return {result: {response: DECODE_UBYTE(bytebuf)}, continue: true};
1121
+ return {result: {response: DECODE_UBYTE(bytebuf), complete: true}, continue: true};
960
1122
  } else {
961
- return {result: {response: DECODE_STRING(bytebuf)}, continue: true};
1123
+ return {result: {response: DECODE_STRING(bytebuf), complete: false}, continue: true};
962
1124
  }
963
1125
  }
964
- }
1126
+ };
965
1127
  }());
966
1128
 
967
1129
  var Ping29Mixin = (function() {
968
1130
  return {
969
1131
  decodePingResponse: function(header,bytebuf){
970
- logger.debugf("header and bytebuf %s %s",header,bytebuf);
1132
+ logger.debugf('header and bytebuf %s %s',header,bytebuf);
971
1133
  return MediaTypesMixin.decodeServerMediaTypes(header,bytebuf);
972
1134
  }
973
- }
1135
+ };
974
1136
  }());
975
1137
 
976
1138
  var Ping30Mixin = (function() {
977
1139
  return {
978
1140
  decodePingResponse: function(header,bytebuf){
979
1141
  var serverMediaTypes= MediaTypesMixin.decodeServerMediaTypes(header,bytebuf);
980
- var version=DECODE_UBYTE(bytebuf);
1142
+ DECODE_UBYTE(bytebuf);
981
1143
  var opCount=DECODE_VINT(bytebuf);
982
1144
  var opRequestCodes=[];
983
1145
  for(let i=0;i<opCount;i++){
@@ -985,7 +1147,242 @@
985
1147
  }
986
1148
  return serverMediaTypes;
987
1149
  }
1150
+ };
1151
+ }());
1152
+
1153
+ var CounterMixin = (function() {
1154
+ var DECODE_LONG = f.actions([codec.decodeLong()], codec.lastDecoded);
1155
+
1156
+ // Counter status codes
1157
+ var COUNTER_SUCCESS = 0x00;
1158
+ var COUNTER_NOT_DEFINED = 0x02;
1159
+
1160
+ /**
1161
+ * Encodes only the counter name as the request body.
1162
+ * @param {string} name Counter name.
1163
+ * @returns {Function} Body encoder function.
1164
+ */
1165
+ function encodeCounterName(name) {
1166
+ return function() {
1167
+ return [codec.encodeString(name)];
1168
+ };
1169
+ }
1170
+
1171
+ /**
1172
+ * Encodes a counter name followed by a long value.
1173
+ * @param {string} name Counter name.
1174
+ * @param {number} value Long value.
1175
+ * @returns {Function} Body encoder function.
1176
+ */
1177
+ function encodeCounterNameValue(name, value) {
1178
+ return function() {
1179
+ return [codec.encodeString(name), codec.encodeLong(value)];
1180
+ };
1181
+ }
1182
+
1183
+ /**
1184
+ * Encodes a counter name followed by two long values (expect, update).
1185
+ * @param {string} name Counter name.
1186
+ * @param {number} expect Expected value.
1187
+ * @param {number} update New value.
1188
+ * @returns {Function} Body encoder function.
1189
+ */
1190
+ function encodeCounterNameTwoLongs(name, expect, update) {
1191
+ return function() {
1192
+ return [codec.encodeString(name), codec.encodeLong(expect), codec.encodeLong(update)];
1193
+ };
1194
+ }
1195
+
1196
+ /**
1197
+ * Encodes a counter create request body (name + configuration).
1198
+ * @param {string} name Counter name.
1199
+ * @param {Object} config Counter configuration.
1200
+ * @returns {Function} Body encoder function.
1201
+ */
1202
+ function encodeCounterCreate(name, config) {
1203
+ return function() {
1204
+ var isWeak = config.type === 'weak';
1205
+ var isBounded = config.type === 'strong' && (typeof config.lowerBound === 'number' || typeof config.upperBound === 'number');
1206
+ var isPersistent = config.storage === 'persistent';
1207
+ var flags = (isWeak ? 0x01 : 0x00)
1208
+ | (isBounded ? 0x02 : 0x00)
1209
+ | (isPersistent ? 0x04 : 0x00);
1210
+
1211
+ var steps = [
1212
+ codec.encodeString(name),
1213
+ codec.encodeUByte(flags)
1214
+ ];
1215
+
1216
+ if (isWeak) {
1217
+ steps.push(codec.encodeVInt(config.concurrencyLevel || 16));
1218
+ }
1219
+
1220
+ if (isBounded) {
1221
+ steps.push(codec.encodeLong(typeof config.lowerBound === 'number' ? config.lowerBound : 0));
1222
+ steps.push(codec.encodeLong(typeof config.upperBound === 'number' ? config.upperBound : 0));
1223
+ }
1224
+
1225
+ steps.push(codec.encodeLong(config.initialValue || 0));
1226
+
1227
+ return steps;
1228
+ };
1229
+ }
1230
+
1231
+ /**
1232
+ * Decodes a counter long value from the response.
1233
+ * @param {Object} header Response header.
1234
+ * @param {Object} bytebuf Byte buffer to decode from.
1235
+ * @returns {Object} Result object with decoded long value.
1236
+ */
1237
+ function decodeCounterValue(header, bytebuf) {
1238
+ if (header.status === COUNTER_SUCCESS) {
1239
+ var value = DECODE_LONG(bytebuf);
1240
+ if (!f.existy(value) && value !== 0)
1241
+ return {continue: false};
1242
+ return {result: value, continue: true};
1243
+ }
1244
+ if (header.status === COUNTER_NOT_DEFINED)
1245
+ return {result: undefined, continue: true};
1246
+ // Status 0x04 = boundary reached — still has a value
1247
+ if (header.status === 0x04) {
1248
+ var boundValue = DECODE_LONG(bytebuf);
1249
+ if (!f.existy(boundValue) && boundValue !== 0)
1250
+ return {continue: false};
1251
+ return {result: boundValue, continue: true};
1252
+ }
1253
+ return {result: undefined, continue: true};
988
1254
  }
1255
+
1256
+ /**
1257
+ * Decodes a counter configuration from the response.
1258
+ * @param {Object} header Response header.
1259
+ * @param {Object} bytebuf Byte buffer to decode from.
1260
+ * @returns {Object} Result object with decoded configuration.
1261
+ */
1262
+ function decodeCounterConfig(header, bytebuf) {
1263
+ if (header.status !== COUNTER_SUCCESS)
1264
+ return {result: undefined, continue: true};
1265
+
1266
+ var flags = DECODE_UBYTE(bytebuf);
1267
+ if (!f.existy(flags))
1268
+ return {continue: false};
1269
+
1270
+ var isWeak = (flags & 0x01) !== 0;
1271
+ var isBounded = (flags & 0x02) !== 0;
1272
+ var isPersistent = (flags & 0x04) !== 0;
1273
+
1274
+ var config = {
1275
+ type: isWeak ? 'weak' : 'strong',
1276
+ storage: isPersistent ? 'persistent' : 'volatile'
1277
+ };
1278
+
1279
+ if (isWeak) {
1280
+ var concurrencyLevel = DECODE_VINT(bytebuf);
1281
+ if (!f.existy(concurrencyLevel))
1282
+ return {continue: false};
1283
+ config.concurrencyLevel = concurrencyLevel;
1284
+ }
1285
+
1286
+ if (isBounded) {
1287
+ var lowerBound = DECODE_LONG(bytebuf);
1288
+ if (!f.existy(lowerBound) && lowerBound !== 0)
1289
+ return {continue: false};
1290
+ config.lowerBound = lowerBound;
1291
+
1292
+ var upperBound = DECODE_LONG(bytebuf);
1293
+ if (!f.existy(upperBound) && upperBound !== 0)
1294
+ return {continue: false};
1295
+ config.upperBound = upperBound;
1296
+ }
1297
+
1298
+ var initialValue = DECODE_LONG(bytebuf);
1299
+ if (!f.existy(initialValue) && initialValue !== 0)
1300
+ return {continue: false};
1301
+ config.initialValue = initialValue;
1302
+
1303
+ return {result: config, continue: true};
1304
+ }
1305
+
1306
+ /**
1307
+ * Decodes a success/failure status for counter operations.
1308
+ * @param {Object} header Response header.
1309
+ * @returns {Object} Result object with boolean success.
1310
+ */
1311
+ function decodeCounterStatus(header) {
1312
+ return {result: header.status === COUNTER_SUCCESS, continue: true};
1313
+ }
1314
+
1315
+ return {
1316
+ encodeCounterCreate: encodeCounterCreate,
1317
+ encodeCounterName: encodeCounterName,
1318
+ encodeCounterNameValue: encodeCounterNameValue,
1319
+ encodeCounterNameTwoLongs: encodeCounterNameTwoLongs,
1320
+ decodeCounterValue: decodeCounterValue,
1321
+ decodeCounterConfig: decodeCounterConfig,
1322
+ decodeCounterStatus: decodeCounterStatus
1323
+ };
1324
+ }());
1325
+
1326
+ /**
1327
+ * Protocol 4.0+ requires an 'otherParams' map after media types in the header.
1328
+ * This mixin overrides stepsHeader to append the count (0 = no params).
1329
+ */
1330
+ var OtherParamsMixin = {
1331
+ stepsHeader: function(ctx, op, opts) {
1332
+ var header = this.encodeHeader(op, ctx.topologyId, opts)(ctx.id);
1333
+ var mediaType = this.encodeMediaTypes();
1334
+ return f.cat(header, mediaType, [codec.encodeVInt(0)]);
1335
+ }
1336
+ };
1337
+
1338
+ var DecodePrevWithMetaMixin = (function() {
1339
+ /**
1340
+ * Decodes a previous value with metadata from the response buffer.
1341
+ * @param {Object} header - Response header.
1342
+ * @param {Object} bytebuf - Byte buffer to decode from.
1343
+ * @param {Function} decoderValue - Value decoder function.
1344
+ * @returns {Object} Result object with decoded previous value and metadata.
1345
+ */
1346
+ function decodePrevWithMeta(header, bytebuf, decoderValue) {
1347
+ var flags = DECODE_UBYTE(bytebuf);
1348
+ if (!f.existy(flags))
1349
+ return {continue: false};
1350
+
1351
+ var lifespan = decodeTimestamp(flags, INFINITE_LIFESPAN, ['created', 'lifespan'], bytebuf);
1352
+ if (!f.existy(lifespan))
1353
+ return {continue: false};
1354
+
1355
+ var idle = decodeTimestamp(flags, INFINITE_MAXIDLE, ['lastUsed', 'maxIdle'], bytebuf);
1356
+ if (!f.existy(idle))
1357
+ return {continue: false};
1358
+
1359
+ var decoder = f.actions(
1360
+ [codec.decodeFixedBytes(8), decoderValue.fun(decoderValue.obj)]
1361
+ , function(values) {
1362
+ return {version: values[0], value: values[1]};
1363
+ });
1364
+ var versioned = decoder(bytebuf);
1365
+ if (f.existy(versioned)) {
1366
+ var result = f.merge(versioned, lifespan, idle);
1367
+ return {result: _.isEmpty(result.value) ? undefined : result, continue: true};
1368
+ }
1369
+ return {continue: false};
1370
+ }
1371
+
1372
+ return {
1373
+ decodePrevOrElse: function(opts, cond, orElse) {
1374
+ var decoderValue = decoderMedia(this.valueMediaType);
1375
+ var shouldReturnPrev = hasOptPrev(opts);
1376
+ return function(header, bytebuf) {
1377
+ if (shouldReturnPrev)
1378
+ return cond(header)
1379
+ ? decodePrevWithMeta(header, bytebuf, decoderValue)
1380
+ : {result: undefined, continue: true};
1381
+
1382
+ return orElse(header);
1383
+ };
1384
+ }
1385
+ };
989
1386
  }());
990
1387
 
991
1388
  var ProtostreamType = (function() {
@@ -1001,15 +1398,15 @@
1001
1398
  },
1002
1399
  lookupProtostreamTypeByName: function (protostreamTypeName) {
1003
1400
  return _.find(protostreamTypes,function(protostreamType){
1004
- return _.isEqual(protostreamType.protostreamTypeName,protostreamTypeName)
1401
+ return _.isEqual(protostreamType.protostreamTypeName,protostreamTypeName);
1005
1402
  }).protostreamDescriptorId;
1006
1403
  },
1007
1404
  lookupProtostreamTypeById: function(protostreamDescriptorId){
1008
1405
  return _.find(protostreamTypes,function(protostreamType){
1009
- return _.isEqual(protostreamType.protostreamDescriptorId,protostreamDescriptorId)
1406
+ return _.isEqual(protostreamType.protostreamDescriptorId,protostreamDescriptorId);
1010
1407
  }).protostreamTypeName;
1011
1408
  }
1012
- }
1409
+ };
1013
1410
  }());
1014
1411
 
1015
1412
  var ProtobufRoot = (function() {
@@ -1024,13 +1421,20 @@
1024
1421
  var root= protobufRoot.lookupType(typeName);
1025
1422
  return root;
1026
1423
  }catch(err){
1027
- throw new Error("Protobuf root not found");
1424
+ throw new Error('Protobuf root not found');
1028
1425
  }
1029
1426
  }
1030
- }
1427
+ };
1031
1428
  }());
1032
1429
 
1033
1430
 
1431
+ /**
1432
+ * Constructs a Hot Rod protocol instance for the given version.
1433
+ * @param {number} v Protocol version number.
1434
+ * @param {Object} clientOpts Client options.
1435
+ * @constructs Protocol
1436
+ * @returns {void}
1437
+ */
1034
1438
  function Protocol(v, clientOpts) {
1035
1439
  this.version = v;
1036
1440
  this.clientOpts = clientOpts;
@@ -1055,11 +1459,23 @@
1055
1459
  Protocol.call(this, 30, clientOpts);
1056
1460
  };
1057
1461
 
1462
+ var Protocol31 = function(clientOpts) {
1463
+ Protocol.call(this, 31, clientOpts);
1464
+ };
1465
+
1466
+ var Protocol40 = function(clientOpts) {
1467
+ Protocol.call(this, 40, clientOpts);
1468
+ };
1469
+
1470
+ var Protocol41 = function(clientOpts) {
1471
+ Protocol.call(this, 41, clientOpts);
1472
+ };
1473
+
1058
1474
  // TODO: Missing operations, just for reference
1059
1475
  var IdsMixin = {
1060
- queryId: function() { return 0x1F },
1061
- authMechId: function() { return 0x21 },
1062
- authReqId: function() { return 0x23 }
1476
+ queryId: function() { return 0x1F; },
1477
+ authMechId: function() { return 0x21; },
1478
+ authReqId: function() { return 0x23; }
1063
1479
  };
1064
1480
 
1065
1481
  _.extend(Protocol22.prototype
@@ -1122,14 +1538,61 @@
1122
1538
  , Ping30Mixin
1123
1539
  , ProtostreamType
1124
1540
  , 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
- );
1541
+ );
1542
+
1543
+ _.extend(Protocol31.prototype
1544
+ , EncodeMixin
1545
+ , ExpiryEncodeMixin
1546
+ , DecodeMixin
1547
+ , IdsMixin
1548
+ , ListenersMixin
1549
+ , ListenerInterestsMixin
1550
+ , IteratorMixin
1551
+ , MediaTypesMixin
1552
+ , SASLMixin
1553
+ , Ping30Mixin
1554
+ , CounterMixin
1555
+ , ProtostreamType
1556
+ , ProtobufRoot
1557
+ // TODO 3.1 new ops: bloom filter near-cache
1558
+ );
1559
+
1560
+ _.extend(Protocol40.prototype
1561
+ , EncodeMixin
1562
+ , ExpiryEncodeMixin
1563
+ , OtherParamsMixin
1564
+ , DecodeMixin
1565
+ , DecodePrevWithMetaMixin
1566
+ , IdsMixin
1567
+ , ListenersMixin
1568
+ , ListenerInterestsMixin
1569
+ , IteratorMixin
1570
+ , MediaTypesMixin
1571
+ , SASLMixin
1572
+ , Ping30Mixin
1573
+ , CounterMixin
1574
+ , ProtostreamType
1575
+ , ProtobufRoot
1576
+ );
1577
+
1578
+ _.extend(Protocol41.prototype
1579
+ , EncodeMixin
1580
+ , ExpiryEncodeMixin
1581
+ , OtherParamsMixin
1582
+ , DecodeMixin
1583
+ , DecodePrevWithMetaMixin
1584
+ , IdsMixin
1585
+ , ListenersMixin
1586
+ , ListenerInterestsMixin
1587
+ , IteratorMixin
1588
+ , MediaTypesMixin
1589
+ , SASLMixin
1590
+ , Ping30Mixin
1591
+ , CounterMixin
1592
+ , ProtostreamType
1593
+ , ProtobufRoot
1594
+ // TODO 4.1 new ops: chunked streaming (GetStreamStart/Next/End, PutStreamStart/Next/End)
1595
+ );
1133
1596
 
1134
1597
  exports.version22 = function(clientOpts) {
1135
1598
  return new Protocol22(clientOpts);
@@ -1147,4 +1610,39 @@
1147
1610
  return new Protocol30(clientOpts);
1148
1611
  };
1149
1612
 
1613
+ exports.version31 = function(clientOpts) {
1614
+ return new Protocol31(clientOpts);
1615
+ };
1616
+
1617
+ exports.version40 = function(clientOpts) {
1618
+ return new Protocol40(clientOpts);
1619
+ };
1620
+
1621
+ exports.version41 = function(clientOpts) {
1622
+ return new Protocol41(clientOpts);
1623
+ };
1624
+
1625
+ var VERSION_ORDER = ['4.1', '4.0', '3.1', '3.0', '2.9', '2.5', '2.2'];
1626
+
1627
+ exports.VERSION_ORDER = VERSION_ORDER;
1628
+
1629
+ /**
1630
+ * Creates a protocol instance for the given version string.
1631
+ * @param {string} version Protocol version (e.g. '3.1', '4.0').
1632
+ * @param {Object} clientOpts Client configuration options.
1633
+ * @returns {Object} Protocol instance.
1634
+ */
1635
+ exports.resolve = function(version, clientOpts) {
1636
+ switch (version) {
1637
+ case '4.1': return new Protocol41(clientOpts);
1638
+ case '4.0': return new Protocol40(clientOpts);
1639
+ case '3.1': return new Protocol31(clientOpts);
1640
+ case '3.0': return new Protocol30(clientOpts);
1641
+ case '2.9': return new Protocol29(clientOpts);
1642
+ case '2.5': return new Protocol25(clientOpts);
1643
+ case '2.2': return new Protocol22(clientOpts);
1644
+ default: throw new Error(`Unknown protocol version: ${version}`);
1645
+ }
1646
+ };
1647
+
1150
1648
  }.call(this));