msgpackr 1.5.2 → 1.5.3

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/README.md CHANGED
@@ -7,8 +7,9 @@
7
7
  [![module](https://img.shields.io/badge/module-ESM%2FCJS-blue)](README.md)
8
8
  [![license](https://img.shields.io/badge/license-MIT-brightgreen)](LICENSE)
9
9
 
10
+ <img align="right" src="./assets/performance.png" width="380"/>
10
11
 
11
- The msgpackr package is an extremely fast MessagePack NodeJS/JavaScript implementation. Currently, it is significantly faster than any other known implementations, faster than Avro (for JS), and generally faster than native V8 JSON.stringify/parse, on NodeJS. It also includes an optional record extension (the `r` in msgpackr), for defining record structures that makes MessagePack even faster and more compact, often over twice as fast as even native JSON functions, several times faster than other JS implementations, and 15-50% more compact. See the performance section for more details. Structured cloning (with support for cyclical references) is also supported through optional extensions.<img align="right" src="./assets/performance.png" width="380"/>
12
+ The msgpackr package is an extremely fast MessagePack NodeJS/JavaScript implementation. Currently, it is significantly faster than any other known implementations, faster than Avro (for JS), and generally faster than native V8 JSON.stringify/parse, on NodeJS. It also includes an optional record extension (the `r` in msgpackr), for defining record structures that makes MessagePack even faster and more compact, often over twice as fast as even native JSON functions, several times faster than other JS implementations, and 15-50% more compact. See the performance section for more details. Structured cloning (with support for cyclical references) is also supported through optional extensions.
12
13
 
13
14
  ## Basic Usage
14
15
 
package/dist/index.js CHANGED
@@ -152,6 +152,9 @@
152
152
  currentStructures.length = sharedLength;
153
153
  }
154
154
  let result = read();
155
+ if (bundledStrings) // bundled strings to skip past
156
+ position = bundledStrings.postBundlePosition;
157
+
155
158
  if (position == srcEnd) {
156
159
  // finished reading this source, cleanup references
157
160
  if (currentStructures.restoreStructures)
@@ -715,6 +718,36 @@
715
718
  }
716
719
  }
717
720
 
721
+ function readOnlyJSString() {
722
+ let token = src[position++];
723
+ let length;
724
+ if (token < 0xc0) {
725
+ // fixstr
726
+ length = token - 0xa0;
727
+ } else {
728
+ switch(token) {
729
+ case 0xd9:
730
+ // str 8
731
+ length = src[position++];
732
+ break
733
+ case 0xda:
734
+ // str 16
735
+ length = dataView.getUint16(position);
736
+ position += 2;
737
+ break
738
+ case 0xdb:
739
+ // str 32
740
+ length = dataView.getUint32(position);
741
+ position += 4;
742
+ break
743
+ default:
744
+ throw new Error('Expected string')
745
+ }
746
+ }
747
+ return readStringJS(length)
748
+ }
749
+
750
+
718
751
  function readBin(length) {
719
752
  return currentUnpackr.copyBuffers ?
720
753
  // specifically use the copying slice (not the node one)
@@ -866,21 +899,18 @@
866
899
  let data = read();
867
900
  return new RegExp(data[0], data[1])
868
901
  };
869
-
902
+ const TEMP_BUNDLE = [];
870
903
  currentExtensions[0x62] = (data) => {
871
904
  let dataSize = (data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3];
872
905
  let dataPosition = position;
873
- position += dataSize - 4;
874
- bundledStrings = [read(), read()];
906
+ position += dataSize - data.length;
907
+ bundledStrings = TEMP_BUNDLE;
908
+ bundledStrings = [readOnlyJSString(), readOnlyJSString()];
875
909
  bundledStrings.position0 = 0;
876
910
  bundledStrings.position1 = 0;
877
- let postBundlePosition = position;
911
+ bundledStrings.postBundlePosition = position;
878
912
  position = dataPosition;
879
- try {
880
- return read()
881
- } finally {
882
- position = postBundlePosition;
883
- }
913
+ return read()
884
914
  };
885
915
 
886
916
  currentExtensions[0xff] = (data) => {
@@ -977,11 +1007,12 @@
977
1007
  const ByteArrayAllocate = hasNodeBuffer ? Buffer.allocUnsafeSlow : Uint8Array;
978
1008
  const ByteArray = hasNodeBuffer ? Buffer : Uint8Array;
979
1009
  const MAX_BUFFER_SIZE = hasNodeBuffer ? 0x100000000 : 0x7fd00000;
980
- let target;
1010
+ let target, keysTarget;
981
1011
  let targetView;
982
1012
  let position$1 = 0;
983
1013
  let safeEnd;
984
1014
  let bundledStrings$1 = null;
1015
+ const MAX_BUNDLE_SIZE = 0xf000;
985
1016
  const hasNonLatin = /[\u0080-\uFFFF]/;
986
1017
  const RECORD_SYMBOL = Symbol('record-id');
987
1018
  class Packr extends Unpackr {
@@ -1044,12 +1075,9 @@
1044
1075
  position$1 = (position$1 + 7) & 0x7ffffff8; // Word align to make any future copying of this buffer faster
1045
1076
  start = position$1;
1046
1077
  referenceMap = packr.structuredClone ? new Map() : null;
1047
- if (packr.bundleStrings) {
1048
- bundledStrings$1 = ['', ''];
1049
- target[position$1++] = 0xd6;
1050
- target[position$1++] = 0x62; // 'b'
1051
- bundledStrings$1.position = position$1 - start;
1052
- position$1 += 4;
1078
+ if (packr.bundleStrings && typeof value !== 'string') {
1079
+ bundledStrings$1 = [];
1080
+ bundledStrings$1.size = Infinity; // force a new bundle start on first string
1053
1081
  } else
1054
1082
  bundledStrings$1 = null;
1055
1083
  sharedStructures = packr.structures;
@@ -1087,15 +1115,11 @@
1087
1115
  }
1088
1116
  if (hasSharedUpdate)
1089
1117
  hasSharedUpdate = false;
1090
- structures = sharedStructures || [];
1118
+ structures = sharedStructures || (packr.structures = []);
1091
1119
  try {
1092
1120
  pack(value);
1093
1121
  if (bundledStrings$1) {
1094
- targetView.setUint32(bundledStrings$1.position + start, position$1 - bundledStrings$1.position - start);
1095
- let writeStrings = bundledStrings$1;
1096
- bundledStrings$1 = null;
1097
- pack(writeStrings[0]);
1098
- pack(writeStrings[1]);
1122
+ writeBundles(start, pack);
1099
1123
  }
1100
1124
  packr.offset = position$1; // update the offset so next serialization doesn't write over our buffer, but can continue writing to same buffer sequentially
1101
1125
  if (referenceMap && referenceMap.idsToInsert) {
@@ -1117,6 +1141,8 @@
1117
1141
  if (sharedStructures) {
1118
1142
  if (serializationsSinceTransitionRebuild < 10)
1119
1143
  serializationsSinceTransitionRebuild++;
1144
+ if (sharedStructures.length > maxSharedStructures)
1145
+ sharedStructures.length = maxSharedStructures;
1120
1146
  if (transitionsCount > 10000) {
1121
1147
  // force a rebuild occasionally after a lot of transitions so it can get cleaned up
1122
1148
  sharedStructures.transitions = null;
@@ -1158,7 +1184,30 @@
1158
1184
  var length;
1159
1185
  if (type === 'string') {
1160
1186
  let strLength = value.length;
1161
- if (bundledStrings$1 && strLength >= 8 && strLength < 0x1000) {
1187
+ if (bundledStrings$1 && strLength >= 4 && strLength < 0x1000) {
1188
+ if ((bundledStrings$1.size += strLength) > MAX_BUNDLE_SIZE) {
1189
+ let extStart;
1190
+ let maxBytes = (bundledStrings$1[0] ? bundledStrings$1[0].length * 3 + bundledStrings$1[1].length : 0) + 10;
1191
+ if (position$1 + maxBytes > safeEnd)
1192
+ target = makeRoom(position$1 + maxBytes);
1193
+ if (bundledStrings$1.position) { // here we use the 0x62 extension to write the last bundle and reserve sapce for the reference pointer to the next/current bundle
1194
+ target[position$1] = 0xc8; // ext 16
1195
+ position$1 += 3; // reserve for the writing bundle size
1196
+ target[position$1++] = 0x62; // 'b'
1197
+ extStart = position$1 - start;
1198
+ position$1 += 4; // reserve for writing bundle reference
1199
+ writeBundles(start, pack); // write the last bundles
1200
+ targetView.setUint16(extStart + start - 3, position$1 - start - extStart);
1201
+ } else { // here we use the 0x62 extension just to reserve the space for the reference pointer to the bundle (will be updated once the bundle is written)
1202
+ target[position$1++] = 0xd6; // fixext 4
1203
+ target[position$1++] = 0x62; // 'b'
1204
+ extStart = position$1 - start;
1205
+ position$1 += 4; // reserve for writing bundle reference
1206
+ }
1207
+ bundledStrings$1 = ['', '']; // create new ones
1208
+ bundledStrings$1.size = 0;
1209
+ bundledStrings$1.position = extStart;
1210
+ }
1162
1211
  let twoByte = hasNonLatin.test(value);
1163
1212
  bundledStrings$1[twoByte ? 0 : 1] += value;
1164
1213
  target[position$1++] = 0xc1;
@@ -1463,53 +1512,55 @@
1463
1512
  target[objectOffset++ + start] = size >> 8;
1464
1513
  target[objectOffset + start] = size & 0xff;
1465
1514
  } :
1466
-
1467
- /* sharedStructures ? // For highly stable structures, using for-in can a little bit faster
1515
+ (options.progressiveRecords && !useTwoByteRecords) ? // this is about 2% faster for highly stable structures, since it only requires one for-in loop (but much more expensive when new structure needs to be written)
1468
1516
  (object, safePrototype) => {
1469
- let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null))
1470
- let objectOffset = position++ - start
1471
- let wroteKeys
1517
+ let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null));
1518
+ let objectOffset = position$1++ - start;
1519
+ let wroteKeys;
1472
1520
  for (let key in object) {
1473
1521
  if (safePrototype || object.hasOwnProperty(key)) {
1474
- nextTransition = transition[key]
1475
- if (!nextTransition) {
1476
- nextTransition = transition[key] = Object.create(null)
1477
- nextTransition.__keys__ = (transition.__keys__ || []).concat([key])
1478
- /*let keys = Object.keys(object)
1479
- if
1480
- let size = 0
1481
- let startBranch = transition.__keys__ ? transition.__keys__.length : 0
1482
- for (let i = 0, l = keys.length; i++) {
1483
- let key = keys[i]
1484
- size += key.length << 2
1485
- if (i >= startBranch) {
1486
- nextTransition = nextTransition[key] = Object.create(null)
1487
- nextTransition.__keys__ = keys.slice(0, i + 1)
1522
+ nextTransition = transition[key];
1523
+ if (nextTransition)
1524
+ transition = nextTransition;
1525
+ else {
1526
+ // record doesn't exist, create full new record and insert it
1527
+ let keys = Object.keys(object);
1528
+ let lastTransition = transition;
1529
+ transition = structures.transitions;
1530
+ let newTransitions = 0;
1531
+ for (let i = 0, l = keys.length; i < l; i++) {
1532
+ let key = keys[i];
1533
+ nextTransition = transition[key];
1534
+ if (!nextTransition) {
1535
+ nextTransition = transition[key] = Object.create(null);
1536
+ newTransitions++;
1488
1537
  }
1538
+ transition = nextTransition;
1489
1539
  }
1490
- makeRoom(position + size)
1491
- nextTransition = transition[key]
1492
- target.copy(target, )
1493
- objectOffset
1540
+ if (objectOffset + start + 1 == position$1) {
1541
+ // first key, so we don't need to insert, we can just write record directly
1542
+ position$1--;
1543
+ newRecord(transition, keys, newTransitions);
1544
+ } else // otherwise we need to insert the record, moving existing data after the record
1545
+ insertNewRecord(transition, keys, objectOffset, newTransitions);
1546
+ wroteKeys = true;
1547
+ transition = lastTransition[key];
1494
1548
  }
1495
- transition = nextTransition
1496
- pack(object[key])
1549
+ pack(object[key]);
1497
1550
  }
1498
1551
  }
1499
- let id = transition.id
1500
- if (!id) {
1501
- id = transition.id = structures.push(transition.__keys__) + 63
1502
- if (sharedStructures.onUpdate)
1503
- sharedStructures.onUpdate(id, transition.__keys__)
1552
+ if (!wroteKeys) {
1553
+ let recordId = transition[RECORD_SYMBOL];
1554
+ if (recordId)
1555
+ target[objectOffset + start] = recordId;
1556
+ else
1557
+ insertNewRecord(transition, Object.keys(object), objectOffset, 0);
1504
1558
  }
1505
- target[objectOffset + start] = id
1506
- }*/
1507
- (object) => {
1508
- let keys = Object.keys(object);
1559
+ } :
1560
+ (object, safePrototype) => {
1509
1561
  let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null));
1510
1562
  let newTransitions = 0;
1511
- for (let i = 0, l = keys.length; i < l; i++) {
1512
- let key = keys[i];
1563
+ for (let key in object) if (safePrototype || object.hasOwnProperty(key)) {
1513
1564
  nextTransition = transition[key];
1514
1565
  if (!nextTransition) {
1515
1566
  nextTransition = transition[key] = Object.create(null);
@@ -1525,57 +1576,12 @@
1525
1576
  } else
1526
1577
  target[position$1++] = recordId;
1527
1578
  } else {
1528
- recordId = structures.nextId;
1529
- if (!recordId)
1530
- recordId = 0x40;
1531
- if (recordId < sharedLimitId && this.shouldShareStructure && !this.shouldShareStructure(keys)) {
1532
- recordId = structures.nextOwnId;
1533
- if (!(recordId < maxStructureId))
1534
- recordId = sharedLimitId;
1535
- structures.nextOwnId = recordId + 1;
1536
- } else {
1537
- if (recordId >= maxStructureId)// cycle back around
1538
- recordId = sharedLimitId;
1539
- structures.nextId = recordId + 1;
1540
- }
1541
- let highByte = keys.highByte = recordId >= 0x60 && useTwoByteRecords ? (recordId - 0x60) >> 5 : -1;
1542
- transition[RECORD_SYMBOL] = recordId;
1543
- structures[recordId - 0x40] = keys;
1544
-
1545
- if (recordId < sharedLimitId) {
1546
- keys.isShared = true;
1547
- structures.sharedLength = recordId - 0x3f;
1548
- hasSharedUpdate = true;
1549
- if (highByte >= 0) {
1550
- target[position$1++] = (recordId & 0x1f) + 0x60;
1551
- target[position$1++] = highByte;
1552
- } else {
1553
- target[position$1++] = recordId;
1554
- }
1555
- } else {
1556
- if (highByte >= 0) {
1557
- target[position$1++] = 0xd5; // fixext 2
1558
- target[position$1++] = 0x72; // "r" record defintion extension type
1559
- target[position$1++] = (recordId & 0x1f) + 0x60;
1560
- target[position$1++] = highByte;
1561
- } else {
1562
- target[position$1++] = 0xd4; // fixext 1
1563
- target[position$1++] = 0x72; // "r" record defintion extension type
1564
- target[position$1++] = recordId;
1565
- }
1566
-
1567
- if (newTransitions)
1568
- transitionsCount += serializationsSinceTransitionRebuild * newTransitions;
1569
- // record the removal of the id, we can maintain our shared structure
1570
- if (recordIdsToRemove.length >= maxOwnStructures)
1571
- recordIdsToRemove.shift()[RECORD_SYMBOL] = 0; // we are cycling back through, and have to remove old ones
1572
- recordIdsToRemove.push(transition);
1573
- pack(keys);
1574
- }
1579
+ newRecord(transition, transition.__keys__ || Object.keys(object), newTransitions);
1575
1580
  }
1576
1581
  // now write the values
1577
- for (let i = 0, l = keys.length; i < l; i++)
1578
- pack(object[keys[i]]);
1582
+ for (let key in object)
1583
+ if (safePrototype || object.hasOwnProperty(key))
1584
+ pack(object[key]);
1579
1585
  };
1580
1586
  const makeRoom = (end) => {
1581
1587
  let newSize;
@@ -1598,6 +1604,86 @@
1598
1604
  safeEnd = newBuffer.length - 10;
1599
1605
  return target = newBuffer
1600
1606
  };
1607
+ const newRecord = (transition, keys, newTransitions) => {
1608
+ let recordId = structures.nextId;
1609
+ if (!recordId)
1610
+ recordId = 0x40;
1611
+ if (recordId < sharedLimitId && this.shouldShareStructure && !this.shouldShareStructure(keys)) {
1612
+ recordId = structures.nextOwnId;
1613
+ if (!(recordId < maxStructureId))
1614
+ recordId = sharedLimitId;
1615
+ structures.nextOwnId = recordId + 1;
1616
+ } else {
1617
+ if (recordId >= maxStructureId)// cycle back around
1618
+ recordId = sharedLimitId;
1619
+ structures.nextId = recordId + 1;
1620
+ }
1621
+ let highByte = keys.highByte = recordId >= 0x60 && useTwoByteRecords ? (recordId - 0x60) >> 5 : -1;
1622
+ transition[RECORD_SYMBOL] = recordId;
1623
+ transition.__keys__ = keys;
1624
+ structures[recordId - 0x40] = keys;
1625
+
1626
+ if (recordId < sharedLimitId) {
1627
+ keys.isShared = true;
1628
+ structures.sharedLength = recordId - 0x3f;
1629
+ hasSharedUpdate = true;
1630
+ if (highByte >= 0) {
1631
+ target[position$1++] = (recordId & 0x1f) + 0x60;
1632
+ target[position$1++] = highByte;
1633
+ } else {
1634
+ target[position$1++] = recordId;
1635
+ }
1636
+ } else {
1637
+ if (highByte >= 0) {
1638
+ target[position$1++] = 0xd5; // fixext 2
1639
+ target[position$1++] = 0x72; // "r" record defintion extension type
1640
+ target[position$1++] = (recordId & 0x1f) + 0x60;
1641
+ target[position$1++] = highByte;
1642
+ } else {
1643
+ target[position$1++] = 0xd4; // fixext 1
1644
+ target[position$1++] = 0x72; // "r" record defintion extension type
1645
+ target[position$1++] = recordId;
1646
+ }
1647
+
1648
+ if (newTransitions)
1649
+ transitionsCount += serializationsSinceTransitionRebuild * newTransitions;
1650
+ // record the removal of the id, we can maintain our shared structure
1651
+ if (recordIdsToRemove.length >= maxOwnStructures)
1652
+ recordIdsToRemove.shift()[RECORD_SYMBOL] = 0; // we are cycling back through, and have to remove old ones
1653
+ recordIdsToRemove.push(transition);
1654
+ pack(keys);
1655
+ }
1656
+ };
1657
+ const insertNewRecord = (transition, keys, insertionOffset, newTransitions) => {
1658
+ let mainTarget = target;
1659
+ let mainPosition = position$1;
1660
+ let mainSafeEnd = safeEnd;
1661
+ let mainStart = start;
1662
+ target = keysTarget;
1663
+ position$1 = 0;
1664
+ start = 0;
1665
+ if (!target)
1666
+ keysTarget = target = new ByteArrayAllocate(8192);
1667
+ safeEnd = target.length - 10;
1668
+ newRecord(transition, keys, newTransitions);
1669
+ keysTarget = target;
1670
+ let keysPosition = position$1;
1671
+ target = mainTarget;
1672
+ position$1 = mainPosition;
1673
+ safeEnd = mainSafeEnd;
1674
+ start = mainStart;
1675
+ if (keysPosition > 1) {
1676
+ let newEnd = position$1 + keysPosition - 1;
1677
+ if (newEnd > safeEnd)
1678
+ makeRoom(newEnd);
1679
+ let insertionPosition = insertionOffset + start;
1680
+ target.copyWithin(insertionPosition + keysPosition, insertionPosition + 1, position$1);
1681
+ target.set(keysTarget.slice(0, keysPosition), insertionPosition);
1682
+ position$1 = newEnd;
1683
+ } else {
1684
+ target[insertionOffset + start] = keysTarget[0];
1685
+ }
1686
+ };
1601
1687
  }
1602
1688
  useBuffer(buffer) {
1603
1689
  // this means we are finished using our own buffer and we can write over it safely
@@ -1605,6 +1691,10 @@
1605
1691
  targetView = new DataView(target.buffer, target.byteOffset, target.byteLength);
1606
1692
  position$1 = 0;
1607
1693
  }
1694
+ clearSharedData() {
1695
+ if (this.structures)
1696
+ this.structures = [];
1697
+ }
1608
1698
  }
1609
1699
 
1610
1700
  extensionClasses = [ Date, Set, Error, RegExp, ArrayBuffer, Object.getPrototypeOf(Uint8Array.prototype).constructor /*TypedArray*/, C1Type ];
@@ -1802,6 +1892,14 @@
1802
1892
  return serialized
1803
1893
  }
1804
1894
 
1895
+ function writeBundles(start, pack) {
1896
+ targetView.setUint32(bundledStrings$1.position + start, position$1 - bundledStrings$1.position - start);
1897
+ let writeStrings = bundledStrings$1;
1898
+ bundledStrings$1 = null;
1899
+ pack(writeStrings[0]);
1900
+ pack(writeStrings[1]);
1901
+ }
1902
+
1805
1903
  function addExtension$1(extension) {
1806
1904
  if (extension.Class) {
1807
1905
  if (!extension.pack && !extension.write)