infinispan 0.15.0 → 0.16.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/SECURITY.md ADDED
@@ -0,0 +1,27 @@
1
+ # Security policy
2
+
3
+ The Infinispan team and community take all security bugs very seriously.
4
+ You can find our guidelines here regarding our policy and security disclosure.
5
+
6
+ ## Reporting security issues
7
+
8
+ Please report any security issues you find in Infinispan to:
9
+
10
+ security at infinispan.org
11
+
12
+ ### Why follow this process
13
+
14
+ Due to the sensitive nature of security bugs, the disclosure process is more constrained than a regular bug.
15
+ We appreciate you following these industry accepted guidelines, which gives time for a proper fix and limit the time
16
+ window of attack.
17
+
18
+ ## Supported Versions
19
+
20
+ Use this section to tell people about which versions of your project are
21
+ currently being supported with security updates.
22
+
23
+ | Version | Supported |
24
+ | -------- | ------------------ |
25
+ | 0.14.0 | :white_check_mark: |
26
+ | < 0.14.0 | :x: |
27
+
package/lib/codec.js CHANGED
@@ -36,6 +36,7 @@
36
36
  exports.decodeSignedInt = f.lift(doDecodeSignedInt, _.identity);
37
37
  exports.decodeVariableBytes = f.lift(doDecodeVariableBytes, _.identity);
38
38
  exports.decodeShort = f.lift(doDecodeShort, _.identity);
39
+ exports.decodeInt = f.lift(doDecodeInt, _.identity);
39
40
  exports.decodeProtobuf = f.lift(doDecodeProtobuf, _.identity);
40
41
  exports.decodeQuery = f.lift(doDecodeQuery,_.identity);
41
42
 
@@ -560,6 +561,17 @@
560
561
  return uncheckedReadShort(bytebuf)();
561
562
  }
562
563
 
564
+ function doDecodeInt(bytebuf) {
565
+ if (4 > bytebuf.buf.length - bytebuf.offset) {
566
+ logger.tracef('Can not fully read 4 bytes (buffer size is %d, buffer offset %d)',
567
+ bytebuf.buf.length, bytebuf.offset);
568
+ return undefined;
569
+ }
570
+ var val = bytebuf.buf.readInt32BE(bytebuf.offset);
571
+ bytebuf.offset += 4;
572
+ return val;
573
+ }
574
+
563
575
  /**
564
576
  * Decode a Protobuf WrappedMessage from the buffer and unwrap it.
565
577
  * @param {Object} bytebuf - The byte buffer to read from.
package/lib/infinispan.js CHANGED
@@ -18,6 +18,7 @@
18
18
  var io = require('./io');
19
19
  var listeners = require('./listeners');
20
20
  var nearCacheFactory = require('./near-cache');
21
+ var tx = require('./transaction');
21
22
 
22
23
  var Client = function(addrs, clientOpts) {
23
24
  var logger = u.logger('client');
@@ -39,6 +40,7 @@
39
40
  var p = protocolResolver(clientOpts['version']);
40
41
  var listen = listeners(p);
41
42
  var nc = clientOpts.nearCache ? nearCacheFactory(clientOpts.nearCache.maxEntries) : null;
43
+ var txCtx = null;
42
44
 
43
45
  var TINY = 16, SMALL = 32, MEDIUM = 64, BIG = 128;
44
46
 
@@ -327,6 +329,17 @@
327
329
  * @since 0.3
328
330
  */
329
331
  get: function(k) {
332
+ if (txCtx) {
333
+ var local = txCtx.getLocalValue(k);
334
+ if (local.found) return Promise.resolve(local.value);
335
+ var ctx = transport.context(SMALL);
336
+ logger.debugf('Invoke transactional get(msgId=%d,key=%s)', ctx.id, u.str(k));
337
+ var decoder = p.decodeWithMeta();
338
+ return futureKey(ctx, 0x1B, k, p.encodeKey(k), decoder).then(function(meta) {
339
+ txCtx.trackRead(k, meta);
340
+ return meta ? meta.value : undefined;
341
+ });
342
+ }
330
343
  if (nc) {
331
344
  var cached = nc.get(k);
332
345
  if (cached !== undefined) {
@@ -439,6 +452,8 @@
439
452
  * Lifespan for the stored entry.
440
453
  * @property {DurationUnit} maxIdle -
441
454
  * Max idle time for the stored entry.
455
+ * @property {Number} flags - Bitwise OR of Hot Rod flags
456
+ * (e.g. infinispan.flags.SKIP_CACHE_LOAD | infinispan.flags.SKIP_INDEXING).
442
457
  * @since 0.3
443
458
  */
444
459
  /**
@@ -455,6 +470,11 @@
455
470
  * @since 0.3
456
471
  */
457
472
  put: function(k, v, opts) {
473
+ if (txCtx) {
474
+ logger.debugf('Transactional put(key=%s)', u.str(k));
475
+ txCtx.trackPut(k, v);
476
+ return Promise.resolve(undefined);
477
+ }
458
478
  if (nc) nc.remove(k);
459
479
  var ctx = transport.context(MEDIUM);
460
480
  logger.debugl(function() { return ['Invoke put(msgId=%d,key=%s,value=%s,opts=%s)',
@@ -486,6 +506,11 @@
486
506
  * @since 0.3
487
507
  */
488
508
  remove: function(k, opts) {
509
+ if (txCtx) {
510
+ logger.debugf('Transactional remove(key=%s)', u.str(k));
511
+ txCtx.trackRemove(k);
512
+ return Promise.resolve(true);
513
+ }
489
514
  if (nc) nc.remove(k);
490
515
  var ctx = transport.context(SMALL);
491
516
  logger.debugl(function() {return ['Invoke remove(msgId=%d,key=%s,opts=%s)',
@@ -1073,6 +1098,122 @@
1073
1098
  logger.debugf('Invoke counterGetAndSet(msgId=%d,name=%s,value=%d)', ctx.id, name, value);
1074
1099
  return future(ctx, 0x7F, p.encodeCounterNameValue(name, value), p.decodeCounterValue);
1075
1100
  },
1101
+ /**
1102
+ * Get all values associated with a key in the multimap.
1103
+ *
1104
+ * @param {(String|Object)} k Key to retrieve.
1105
+ * @returns {Promise.<Array>}
1106
+ * A promise that will be completed with an array of values,
1107
+ * or an empty array if the key does not exist.
1108
+ * @memberof Client#
1109
+ * @since 0.16
1110
+ */
1111
+ multimapGet: function(k) {
1112
+ var ctx = transport.context(SMALL);
1113
+ logger.debugf('Invoke multimapGet(msgId=%d,key=%s)', ctx.id, u.str(k));
1114
+ return futureKey(ctx, 0x67, k, p.encodeMultimapKey(k), p.decodeMultimapCollection());
1115
+ },
1116
+ /**
1117
+ * Add a value to the collection associated with a key in the multimap.
1118
+ *
1119
+ * @param {(String|Object)} k Key.
1120
+ * @param {(String|Object)} v Value to add.
1121
+ * @param {Object=} opts Optional store options (lifespan, maxIdle).
1122
+ * @returns {Promise}
1123
+ * A promise that will be completed when the value has been added.
1124
+ * @memberof Client#
1125
+ * @since 0.16
1126
+ */
1127
+ multimapPut: function(k, v, opts) {
1128
+ var ctx = transport.context(MEDIUM);
1129
+ logger.debugf('Invoke multimapPut(msgId=%d,key=%s,value=%s)', ctx.id, u.str(k), u.str(v));
1130
+ return futureKey(ctx, 0x6B, k, p.encodeMultimapPut(k, v), p.complete(_.constant(undefined)), opts);
1131
+ },
1132
+ /**
1133
+ * Remove all values associated with a key in the multimap.
1134
+ *
1135
+ * @param {(String|Object)} k Key to remove.
1136
+ * @returns {Promise.<Boolean>}
1137
+ * A promise that will be completed with true if the key existed.
1138
+ * @memberof Client#
1139
+ * @since 0.16
1140
+ */
1141
+ multimapRemoveKey: function(k) {
1142
+ var ctx = transport.context(SMALL);
1143
+ logger.debugf('Invoke multimapRemoveKey(msgId=%d,key=%s)', ctx.id, u.str(k));
1144
+ return futureKey(ctx, 0x6D, k, p.encodeMultimapKey(k), p.decodeMultimapBoolean);
1145
+ },
1146
+ /**
1147
+ * Remove a specific value from the collection associated with a key.
1148
+ *
1149
+ * @param {(String|Object)} k Key.
1150
+ * @param {(String|Object)} v Value to remove.
1151
+ * @returns {Promise.<Boolean>}
1152
+ * A promise that will be completed with true if the entry existed.
1153
+ * @memberof Client#
1154
+ * @since 0.16
1155
+ */
1156
+ multimapRemoveEntry: function(k, v) {
1157
+ var ctx = transport.context(SMALL);
1158
+ logger.debugf('Invoke multimapRemoveEntry(msgId=%d,key=%s,value=%s)', ctx.id, u.str(k), u.str(v));
1159
+ return futureKey(ctx, 0x6F, k, p.encodeMultimapKeyValue(k, v), p.decodeMultimapBoolean);
1160
+ },
1161
+ /**
1162
+ * Get the total number of key-value pairs in the multimap.
1163
+ *
1164
+ * @returns {Promise.<Number>}
1165
+ * A promise that will be completed with the total size.
1166
+ * @memberof Client#
1167
+ * @since 0.16
1168
+ */
1169
+ multimapSize: function() {
1170
+ var ctx = transport.context(SMALL);
1171
+ logger.debugf('Invoke multimapSize(msgId=%d)', ctx.id);
1172
+ return future(ctx, 0x71, p.encodeMultimapSupportsDuplicates(), p.decodeMultimapSize);
1173
+ },
1174
+ /**
1175
+ * Check whether a specific key-value pair exists in the multimap.
1176
+ *
1177
+ * @param {(String|Object)} k Key.
1178
+ * @param {(String|Object)} v Value.
1179
+ * @returns {Promise.<Boolean>}
1180
+ * A promise that will be completed with true if the entry exists.
1181
+ * @memberof Client#
1182
+ * @since 0.16
1183
+ */
1184
+ multimapContainsEntry: function(k, v) {
1185
+ var ctx = transport.context(SMALL);
1186
+ logger.debugf('Invoke multimapContainsEntry(msgId=%d,key=%s,value=%s)', ctx.id, u.str(k), u.str(v));
1187
+ return futureKey(ctx, 0x73, k, p.encodeMultimapKeyValue(k, v), p.decodeMultimapBoolean);
1188
+ },
1189
+ /**
1190
+ * Check whether a key exists in the multimap.
1191
+ *
1192
+ * @param {(String|Object)} k Key to check.
1193
+ * @returns {Promise.<Boolean>}
1194
+ * A promise that will be completed with true if the key exists.
1195
+ * @memberof Client#
1196
+ * @since 0.16
1197
+ */
1198
+ multimapContainsKey: function(k) {
1199
+ var ctx = transport.context(SMALL);
1200
+ logger.debugf('Invoke multimapContainsKey(msgId=%d,key=%s)', ctx.id, u.str(k));
1201
+ return futureKey(ctx, 0x75, k, p.encodeMultimapKey(k), p.decodeMultimapBoolean);
1202
+ },
1203
+ /**
1204
+ * Check whether any key contains the given value in the multimap.
1205
+ *
1206
+ * @param {(String|Object)} v Value to check.
1207
+ * @returns {Promise.<Boolean>}
1208
+ * A promise that will be completed with true if any key contains the value.
1209
+ * @memberof Client#
1210
+ * @since 0.16
1211
+ */
1212
+ multimapContainsValue: function(v) {
1213
+ var ctx = transport.context(SMALL);
1214
+ logger.debugf('Invoke multimapContainsValue(msgId=%d,value=%s)', ctx.id, u.str(v));
1215
+ return future(ctx, 0x77, p.encodeMultimapValue(v), p.decodeMultimapBoolean);
1216
+ },
1076
1217
  /**
1077
1218
  * Get server topology related information.
1078
1219
  *
@@ -1261,6 +1402,73 @@
1261
1402
  logger.debugf('Invoke admin.removeSchema(msgId=%d,name=%s)', ctx.id, name);
1262
1403
  return future(ctx, 0x2B, p.encodeNameParams('@@schemas@delete', {name: name}), p.decodeValue());
1263
1404
  }
1405
+ },
1406
+ /**
1407
+ * Get the transaction manager for this client.
1408
+ * Transactions buffer put/remove operations locally and send them
1409
+ * to the server atomically on commit via PREPARE_TX_2.
1410
+ * During an active transaction, get() reads local writes first,
1411
+ * falling back to getWithMetadata to capture entry versions for
1412
+ * conflict detection.
1413
+ *
1414
+ * @returns {Object} Transaction manager with begin(), commit(), and rollback() methods.
1415
+ * @memberof Client#
1416
+ * @since 0.16
1417
+ */
1418
+ getTransactionManager: function() {
1419
+ return {
1420
+ begin: function() {
1421
+ if (txCtx) throw new Error('Transaction already active');
1422
+ txCtx = new tx.TransactionContext();
1423
+ logger.debugf('Transaction started (xid=%s)',
1424
+ txCtx.xid.globalTxId.toString('hex'));
1425
+ return Promise.resolve();
1426
+ },
1427
+ commit: function() {
1428
+ if (!txCtx) throw new Error('No active transaction');
1429
+ var xid = txCtx.xid;
1430
+ var mods = txCtx.getModifications();
1431
+ if (mods.length === 0) {
1432
+ txCtx = null;
1433
+ return Promise.resolve();
1434
+ }
1435
+ var ctx = transport.context(BIG);
1436
+ logger.debugf('Invoke prepareTx(msgId=%d, mods=%d)', ctx.id, mods.length);
1437
+ var decoder = p.decodeXaResponse();
1438
+ return future(ctx, 0x7D, p.encodePrepare(xid, mods, true, 60000), decoder)
1439
+ .then(function(xaCode) {
1440
+ txCtx = null;
1441
+ if (xaCode !== tx.XA_OK && xaCode !== tx.XA_RDONLY) {
1442
+ throw new Error(`Transaction prepare failed: XA code ${xaCode}`);
1443
+ }
1444
+ })
1445
+ .catch(function(err) {
1446
+ txCtx = null;
1447
+ throw err;
1448
+ });
1449
+ },
1450
+ rollback: function() {
1451
+ if (!txCtx) throw new Error('No active transaction');
1452
+ var xid = txCtx.xid;
1453
+ var mods = txCtx.getModifications();
1454
+ if (mods.length === 0) {
1455
+ txCtx = null;
1456
+ return Promise.resolve();
1457
+ }
1458
+ var ctx = transport.context(SMALL);
1459
+ logger.debugf('Invoke rollbackTx(msgId=%d)', ctx.id);
1460
+ var decoder = p.decodeXaResponse();
1461
+ return future(ctx, 0x3F, p.encodeXidOnly(xid), decoder)
1462
+ .then(function() { txCtx = null; })
1463
+ .catch(function(err) {
1464
+ txCtx = null;
1465
+ throw err;
1466
+ });
1467
+ },
1468
+ isActive: function() {
1469
+ return txCtx !== null;
1470
+ }
1471
+ };
1264
1472
  }
1265
1473
  };
1266
1474
  };
@@ -1390,6 +1598,8 @@
1390
1598
  * @returns {Promise<ReturnType<Client>>} A promise that resolves to a connected client.
1391
1599
  * @since 0.3
1392
1600
  */
1601
+ exports.flags = protocols.FLAGS;
1602
+
1393
1603
  exports.client = function client(args, options) {
1394
1604
  var addrs, uriOpts;
1395
1605
  if (uri.isHotrodURI(args)) {
package/lib/protocols.js CHANGED
@@ -23,6 +23,15 @@
23
23
  var INFINITE_LIFESPAN = 0x01, INFINITE_MAXIDLE = 0x02; // Duration flag masks
24
24
  var MAGIC = 0xA0;
25
25
 
26
+ var FLAGS = {
27
+ FORCE_RETURN_VALUE: 0x0001,
28
+ DEFAULT_LIFESPAN: 0x0002,
29
+ DEFAULT_MAXIDLE: 0x0004,
30
+ SKIP_CACHE_LOAD: 0x0008,
31
+ SKIP_INDEXING: 0x0010,
32
+ SKIP_LISTENER_NOTIFICATION: 0x0020
33
+ };
34
+
26
35
  /**
27
36
  * Creates decode actions for a key-value pair.
28
37
  * @param {Object} decoderKey Key decoder descriptor.
@@ -129,8 +138,10 @@
129
138
  };
130
139
 
131
140
  return {
132
- buildFlags: function (opts) { // TODO: Move out to a Mixin (similar to expiry)
133
- return hasOptPrev(opts) ? 0x01 : 0;
141
+ buildFlags: function (opts) {
142
+ var result = hasOptPrev(opts) ? FLAGS.FORCE_RETURN_VALUE : 0;
143
+ if (hasOpt(opts, 'flags')) result |= opts.flags;
144
+ return result;
134
145
  },
135
146
  /**
136
147
  * Returns the protocol version as a human-readable string (e.g. '3.1').
@@ -577,8 +588,8 @@
577
588
  return hasPrevious(header.status);
578
589
  },
579
590
  isEvent: function(header) {
580
- //return ((op(header) >> 4) & 0x06) == 0x06;
581
- return ((header.opCode >> 4) & 0x06) == 0x06;
591
+ var op = header.opCode;
592
+ return op >= 0x60 && op <= 0x64;
582
593
  },
583
594
  isError: function(header) {
584
595
  return header.opCode == 0x50;
@@ -1323,6 +1334,81 @@
1323
1334
  };
1324
1335
  }());
1325
1336
 
1337
+ var MultimapMixin = (function() {
1338
+ var SUPPORTS_DUPLICATES = codec.encodeUByte(0x00);
1339
+
1340
+ return {
1341
+ encodeMultimapKey: function(k) {
1342
+ var outer = this;
1343
+ return function() {
1344
+ return [outer.encodeMediaKey(k), SUPPORTS_DUPLICATES];
1345
+ };
1346
+ },
1347
+ encodeMultimapPut: function(k, v) {
1348
+ var outer = this;
1349
+ return function(opts) {
1350
+ return f.cat(
1351
+ [outer.encodeMediaKey(k)],
1352
+ outer.encodeExpiry(opts),
1353
+ [outer.encodeMediaValue(v), SUPPORTS_DUPLICATES]
1354
+ );
1355
+ };
1356
+ },
1357
+ encodeMultimapKeyValue: function(k, v) {
1358
+ var outer = this;
1359
+ return function() {
1360
+ return f.cat(
1361
+ [outer.encodeMediaKey(k)],
1362
+ [codec.encodeUByte(0x77)],
1363
+ [outer.encodeMediaValue(v), SUPPORTS_DUPLICATES]
1364
+ );
1365
+ };
1366
+ },
1367
+ encodeMultimapValue: function(v) {
1368
+ var outer = this;
1369
+ return function() {
1370
+ return [codec.encodeUByte(0x77), outer.encodeMediaValue(v), SUPPORTS_DUPLICATES];
1371
+ };
1372
+ },
1373
+ encodeMultimapSupportsDuplicates: function() {
1374
+ return function() {
1375
+ return [SUPPORTS_DUPLICATES];
1376
+ };
1377
+ },
1378
+ decodeMultimapCollection: function() {
1379
+ var decoderValue = decoderMedia(this.valueMediaType);
1380
+ var decodeSingleValue = decodeSingle(decoderValue);
1381
+ return function(header, bytebuf) {
1382
+ if (header.status !== 0x00 && header.status !== 0x03)
1383
+ return {result: [], continue: true};
1384
+ var count = DECODE_VINT(bytebuf);
1385
+ if (!f.existy(count))
1386
+ return {continue: false};
1387
+ var values = [];
1388
+ for (var i = 0; i < count; i++) {
1389
+ var v = decodeSingleValue(bytebuf);
1390
+ if (!f.existy(v))
1391
+ return {continue: false};
1392
+ values.push(v);
1393
+ }
1394
+ return {result: values, continue: true};
1395
+ };
1396
+ },
1397
+ decodeMultimapBoolean: function(header, bytebuf) {
1398
+ var b = DECODE_UBYTE(bytebuf);
1399
+ if (!f.existy(b))
1400
+ return {continue: false};
1401
+ return {result: b !== 0, continue: true};
1402
+ },
1403
+ decodeMultimapSize: function(header, bytebuf) {
1404
+ var size = f.actions([codec.decodeVLong()], codec.lastDecoded)(bytebuf);
1405
+ if (!f.existy(size) && size !== 0)
1406
+ return {continue: false};
1407
+ return {result: size, continue: true};
1408
+ }
1409
+ };
1410
+ }());
1411
+
1326
1412
  /**
1327
1413
  * Protocol 4.0+ requires an 'otherParams' map after media types in the header.
1328
1414
  * This mixin overrides stepsHeader to append the count (0 = no params).
@@ -1428,6 +1514,54 @@
1428
1514
  }());
1429
1515
 
1430
1516
 
1517
+ var DECODE_INT = f.actions([codec.decodeInt()], codec.lastDecoded);
1518
+
1519
+ var TransactionMixin = {
1520
+ encodeXid: function(xid) {
1521
+ return [
1522
+ codec.encodeSignedInt(xid.formatId),
1523
+ codec.encodeBytesWithLength(xid.globalTxId),
1524
+ codec.encodeBytesWithLength(xid.branchQualifier)
1525
+ ];
1526
+ },
1527
+ encodePrepare: function(xid, modifications, onePhaseCommit, timeout) {
1528
+ var outer = this;
1529
+ return function() {
1530
+ var steps = outer.encodeXid(xid);
1531
+ steps.push(codec.encodeUByte(onePhaseCommit ? 1 : 0));
1532
+ steps.push(codec.encodeUByte(0)); // not recoverable
1533
+ steps.push(codec.encodeLong(timeout));
1534
+ steps.push(codec.encodeVInt(modifications.length));
1535
+ for (var i = 0; i < modifications.length; i++) {
1536
+ var mod = modifications[i];
1537
+ steps.push(outer.encodeMediaKey(mod.key));
1538
+ steps.push(codec.encodeUByte(mod.controlByte));
1539
+ if (!(mod.controlByte & 0x1) && !(mod.controlByte & 0x2)) {
1540
+ steps.push(codec.encodeBytes(mod.versionRead));
1541
+ }
1542
+ if (!(mod.controlByte & 0x4)) {
1543
+ steps.push(codec.encodeUByte(0x77));
1544
+ steps.push(outer.encodeMediaValue(mod.value));
1545
+ }
1546
+ }
1547
+ return steps;
1548
+ };
1549
+ },
1550
+ encodeXidOnly: function(xid) {
1551
+ var outer = this;
1552
+ return function() {
1553
+ return outer.encodeXid(xid);
1554
+ };
1555
+ },
1556
+ decodeXaResponse: function() {
1557
+ return function(header, bytebuf) {
1558
+ var code = DECODE_INT(bytebuf);
1559
+ if (!f.existy(code)) return {continue: false};
1560
+ return {result: code, continue: true};
1561
+ };
1562
+ }
1563
+ };
1564
+
1431
1565
  /**
1432
1566
  * Constructs a Hot Rod protocol instance for the given version.
1433
1567
  * @param {number} v Protocol version number.
@@ -1516,13 +1650,7 @@
1516
1650
  , Ping29Mixin
1517
1651
  , ProtostreamType
1518
1652
  , ProtobufRoot
1519
- // TODO 2.6 new ops: getStream and putStream
1520
- // TODO 2.6 add listener change: listener event interests
1521
- // TODO 2.7 new ops: prepare, commit and rollback
1522
- // TODO 2.7 new ops: counter operations
1523
- // TODO 2.7 new events: counter events
1524
- // TODO 2.8 listener events: can come from any connection
1525
- // TODO 2.8 header change: media types
1653
+ , TransactionMixin
1526
1654
  );
1527
1655
 
1528
1656
  _.extend(Protocol30.prototype
@@ -1538,6 +1666,7 @@
1538
1666
  , Ping30Mixin
1539
1667
  , ProtostreamType
1540
1668
  , ProtobufRoot
1669
+ , TransactionMixin
1541
1670
  );
1542
1671
 
1543
1672
  _.extend(Protocol31.prototype
@@ -1552,9 +1681,10 @@
1552
1681
  , SASLMixin
1553
1682
  , Ping30Mixin
1554
1683
  , CounterMixin
1684
+ , MultimapMixin
1555
1685
  , ProtostreamType
1556
1686
  , ProtobufRoot
1557
- // TODO 3.1 new ops: bloom filter near-cache
1687
+ , TransactionMixin
1558
1688
  );
1559
1689
 
1560
1690
  _.extend(Protocol40.prototype
@@ -1571,8 +1701,10 @@
1571
1701
  , SASLMixin
1572
1702
  , Ping30Mixin
1573
1703
  , CounterMixin
1704
+ , MultimapMixin
1574
1705
  , ProtostreamType
1575
1706
  , ProtobufRoot
1707
+ , TransactionMixin
1576
1708
  );
1577
1709
 
1578
1710
  _.extend(Protocol41.prototype
@@ -1589,9 +1721,10 @@
1589
1721
  , SASLMixin
1590
1722
  , Ping30Mixin
1591
1723
  , CounterMixin
1724
+ , MultimapMixin
1592
1725
  , ProtostreamType
1593
1726
  , ProtobufRoot
1594
- // TODO 4.1 new ops: chunked streaming (GetStreamStart/Next/End, PutStreamStart/Next/End)
1727
+ , TransactionMixin
1595
1728
  );
1596
1729
 
1597
1730
  exports.version22 = function(clientOpts) {
@@ -1626,6 +1759,8 @@
1626
1759
 
1627
1760
  exports.VERSION_ORDER = VERSION_ORDER;
1628
1761
 
1762
+ exports.FLAGS = FLAGS;
1763
+
1629
1764
  /**
1630
1765
  * Creates a protocol instance for the given version string.
1631
1766
  * @param {string} version Protocol version (e.g. '3.1', '4.0').
@@ -0,0 +1,123 @@
1
+ 'use strict';
2
+
3
+ (function() {
4
+
5
+ var crypto = require('crypto');
6
+
7
+ var FORMAT_ID = 0x48525458;
8
+ var NOT_READ = 0x1, NON_EXISTING = 0x2, REMOVE_OP = 0x4;
9
+ var XA_OK = 0, XA_RDONLY = 3;
10
+
11
+ function generateXid() {
12
+ return {
13
+ formatId: FORMAT_ID,
14
+ globalTxId: crypto.randomBytes(16),
15
+ branchQualifier: crypto.randomBytes(16)
16
+ };
17
+ }
18
+
19
+ function TransactionContext() {
20
+ this.xid = generateXid();
21
+ // key (string) → { value, removed (bool), versionRead (Buffer|null), wasRead (bool), existed (bool) }
22
+ this.entries = new Map();
23
+ this.active = true;
24
+ }
25
+
26
+ TransactionContext.prototype.trackPut = function(key, value) {
27
+ var entry = this.entries.get(key);
28
+ if (entry) {
29
+ entry.value = value;
30
+ entry.removed = false;
31
+ } else {
32
+ this.entries.set(key, {
33
+ value: value,
34
+ removed: false,
35
+ versionRead: null,
36
+ wasRead: false,
37
+ existed: false
38
+ });
39
+ }
40
+ };
41
+
42
+ TransactionContext.prototype.trackRemove = function(key) {
43
+ var entry = this.entries.get(key);
44
+ if (entry) {
45
+ entry.removed = true;
46
+ entry.value = undefined;
47
+ } else {
48
+ this.entries.set(key, {
49
+ value: undefined,
50
+ removed: true,
51
+ versionRead: null,
52
+ wasRead: false,
53
+ existed: false
54
+ });
55
+ }
56
+ };
57
+
58
+ TransactionContext.prototype.trackRead = function(key, meta) {
59
+ var entry = this.entries.get(key);
60
+ var version = meta ? meta.version : null;
61
+ var existed = meta !== undefined;
62
+ if (entry) {
63
+ if (!entry.wasRead) {
64
+ entry.wasRead = true;
65
+ entry.versionRead = version;
66
+ entry.existed = existed;
67
+ }
68
+ } else {
69
+ this.entries.set(key, {
70
+ value: meta ? meta.value : undefined,
71
+ removed: false,
72
+ versionRead: version,
73
+ wasRead: true,
74
+ existed: existed
75
+ });
76
+ }
77
+ };
78
+
79
+ TransactionContext.prototype.getLocalValue = function(key) {
80
+ var entry = this.entries.get(key);
81
+ if (!entry) return { found: false };
82
+ if (entry.removed) return { found: true, value: undefined };
83
+ if (entry.value !== undefined) return { found: true, value: entry.value };
84
+ return { found: false };
85
+ };
86
+
87
+ TransactionContext.prototype.computeControlByte = function(entry) {
88
+ var control = 0;
89
+ if (!entry.wasRead) {
90
+ control |= NOT_READ;
91
+ } else if (!entry.existed) {
92
+ control |= NON_EXISTING;
93
+ }
94
+ if (entry.removed) {
95
+ control |= REMOVE_OP;
96
+ }
97
+ return control;
98
+ };
99
+
100
+ TransactionContext.prototype.getModifications = function() {
101
+ var mods = [];
102
+ this.entries.forEach(function(entry, key) {
103
+ var control = this.computeControlByte(entry);
104
+ mods.push({
105
+ key: key,
106
+ controlByte: control,
107
+ versionRead: entry.versionRead,
108
+ value: entry.value
109
+ });
110
+ }.bind(this));
111
+ return mods;
112
+ };
113
+
114
+ exports.TransactionContext = TransactionContext;
115
+ exports.generateXid = generateXid;
116
+ exports.FORMAT_ID = FORMAT_ID;
117
+ exports.NOT_READ = NOT_READ;
118
+ exports.NON_EXISTING = NON_EXISTING;
119
+ exports.REMOVE_OP = REMOVE_OP;
120
+ exports.XA_OK = XA_OK;
121
+ exports.XA_RDONLY = XA_RDONLY;
122
+
123
+ }.call(this));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "infinispan",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Infinispan Javascript client",
5
5
  "main": "index",
6
6
  "typings": "./types",
@@ -9,8 +9,7 @@
9
9
  },
10
10
  "scripts": {
11
11
  "lint": "eslint --ignore-path .gitignore lib spec index.js",
12
- "test": "jasmine",
13
- "test:docker": "node scripts/docker-test.js",
12
+ "test": "node scripts/docker-test.js",
14
13
  "docker:up": "docker compose -p ispn-test up -d --wait && docker compose -p ispn-test --profile failover create server-failover-one server-failover-two server-failover-three",
15
14
  "docker:down": "docker compose -p ispn-test --profile failover down --remove-orphans",
16
15
  "ssl:generate": "node scripts/make-ssl.js",