msgpackr 1.10.1 → 1.11.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/README.md CHANGED
@@ -186,8 +186,10 @@ The following options properties can be provided to the Packr or Unpackr constru
186
186
  * `largeBigIntToFloat` - If a bigint needs to be encoded that is larger than will fit in 64-bit integers, it will be encoded as a float-64 (otherwise will throw a RangeError).
187
187
  * `useBigIntExtension` - If a bigint needs to be encoded that is larger than will fit in 64-bit integers, it will be encoded using a custom extension that supports up to about 1000-bits of integer precision.
188
188
  * `encodeUndefinedAsNil` - Encodes a value of `undefined` as a MessagePack `nil`, the same as a `null`.
189
- * `int64AsType` - This will decode uint64 and int64 numbers as the specified type. The type can be `bigint` (default), `number`, `string`, or `auto` (where range [-2^53...2^53] is represented by number and everything else by a bigint).
189
+ * `int64AsType` - This will decode uint64 and int64 numbers as the specified type. The type can be `bigint` (default), `number`, `string`, or `auto` (where range [-2^53...2^53] is represented by number and everything else by a bigint).
190
+ * `skipValues` - This can be an array of property values that will indicate properties that should be skipped when serializing objects. For example, to mimic `JSON.stringify`'s behavior of skipping properties with a value of `undefined`, you can provide `skipValues: [undefined]`. Note, that this will only apply to serializing objects as standard MessagePack maps, not to records. Also, the array is checked by calling the `include` method, so you can provide an object with an `includes` if you want a custom function to skip values.
190
191
  * `onInvalidDate` - This can be provided as function that will be called when an invalid date is provided. The function can throw an error, or return a value that will be encoded in place of the invalid date. If not provided, an invalid date will be encoded as an invalid timestamp (which decodes with msgpackr back to an invalid date).
192
+ * `writeFunction` - This can be provided as function that will be called when a function is encountered. The function can throw an error, or return a value that will be encoded in place of the function. If not provided, a function will be encoded as undefined (similar to `JSON.stringify`).
191
193
  * `mapAsEmptyObject` - Encodes JS `Map`s as empty objects (for back-compat with older libraries).
192
194
  * `setAsEmptyObject` - Encodes JS `Set`s as empty objects (for back-compat with older libraries).
193
195
 
@@ -912,8 +912,10 @@
912
912
  }
913
913
 
914
914
  function asSafeString(property) {
915
+ // protect against expensive (DoS) string conversions
915
916
  if (typeof property === 'string') return property;
916
- if (typeof property === 'number') return property.toString();
917
+ if (typeof property === 'number' || typeof property === 'boolean' || typeof property === 'bigint') return property.toString();
918
+ if (property == null) return property + '';
917
919
  throw new Error('Invalid property type for record', typeof property);
918
920
  }
919
921
  // the registration of the record definition extension (as "r")
@@ -953,7 +955,7 @@
953
955
  let errors = { Error, TypeError, ReferenceError };
954
956
  currentExtensions[0x65] = () => {
955
957
  let data = read();
956
- return (errors[data[0]] || Error)(data[1])
958
+ return (errors[data[0]] || Error)(data[1], { cause: data[2] })
957
959
  };
958
960
 
959
961
  currentExtensions[0x69] = (data) => {
@@ -997,8 +999,15 @@
997
999
  currentExtensions[0x74] = (data) => {
998
1000
  let typeCode = data[0];
999
1001
  let typedArrayName = typedArrays[typeCode];
1000
- if (!typedArrayName)
1002
+ if (!typedArrayName) {
1003
+ if (typeCode === 16) {
1004
+ let ab = new ArrayBuffer(data.length - 1);
1005
+ let u8 = new Uint8Array(ab);
1006
+ u8.set(data.subarray(1));
1007
+ return ab;
1008
+ }
1001
1009
  throw new Error('Could not find typed array for code ' + typeCode)
1010
+ }
1002
1011
  // we have to always slice/copy here to get a new ArrayBuffer that is word/byte aligned
1003
1012
  return new glbl[typedArrayName](Uint8Array.prototype.slice.call(data, 1).buffer)
1004
1013
  };
@@ -1158,7 +1167,7 @@
1158
1167
  if (!this.structures && options.useRecords != false)
1159
1168
  this.structures = [];
1160
1169
  // two byte record ids for shared structures
1161
- let useTwoByteRecords = maxSharedStructures > 32 || (maxOwnStructures + maxSharedStructures > 64);
1170
+ let useTwoByteRecords = maxSharedStructures > 32 || (maxOwnStructures + maxSharedStructures > 64);
1162
1171
  let sharedLimitId = maxSharedStructures + 0x40;
1163
1172
  let maxStructureId = maxSharedStructures + maxOwnStructures + 0x40;
1164
1173
  if (maxStructureId > 8256) {
@@ -1176,7 +1185,7 @@
1176
1185
  }
1177
1186
  safeEnd = target.length - 10;
1178
1187
  if (safeEnd - position < 0x800) {
1179
- // don't start too close to the end,
1188
+ // don't start too close to the end,
1180
1189
  target = new ByteArrayAllocate(target.length);
1181
1190
  targetView = target.dataView || (target.dataView = new DataView(target.buffer, 0, target.length));
1182
1191
  safeEnd = target.length - 10;
@@ -1294,10 +1303,14 @@
1294
1303
  return packr.pack(value, encodeOptions)
1295
1304
  }
1296
1305
  packr.lastNamedStructuresLength = sharedLength;
1306
+ // don't keep large buffers around
1307
+ if (target.length > 0x40000000) target = null;
1297
1308
  return returnBuffer
1298
1309
  }
1299
1310
  }
1300
1311
  }
1312
+ // don't keep large buffers around, they take too much memory and cause problems (limit at 1GB)
1313
+ if (target.length > 0x40000000) target = null;
1301
1314
  if (encodeOptions & RESET_BUFFER_MODE)
1302
1315
  position = start;
1303
1316
  }
@@ -1515,12 +1528,12 @@
1515
1528
  targetView.setUint32(position, referee.id);
1516
1529
  position += 4;
1517
1530
  return
1518
- } else
1531
+ } else
1519
1532
  referenceMap.set(value, { offset: position - start });
1520
1533
  }
1521
1534
  let constructor = value.constructor;
1522
1535
  if (constructor === Object) {
1523
- writeObject(value, true);
1536
+ writeObject(value);
1524
1537
  } else if (constructor === Array) {
1525
1538
  packArray(value);
1526
1539
  } else if (constructor === Map) {
@@ -1543,7 +1556,7 @@
1543
1556
  pack(entryValue);
1544
1557
  }
1545
1558
  }
1546
- } else {
1559
+ } else {
1547
1560
  for (let i = 0, l = extensions.length; i < l; i++) {
1548
1561
  let extensionClass = extensionClasses[i];
1549
1562
  if (value instanceof extensionClass) {
@@ -1611,13 +1624,13 @@
1611
1624
  if (json !== value)
1612
1625
  return pack(json)
1613
1626
  }
1614
-
1627
+
1615
1628
  // if there is a writeFunction, use it, otherwise just encode as undefined
1616
1629
  if (type === 'function')
1617
1630
  return pack(this.writeFunction && this.writeFunction(value));
1618
-
1619
- // no extension found, write as object
1620
- writeObject(value, !value.hasOwnProperty); // if it doesn't have hasOwnProperty, don't do hasOwnProperty checks
1631
+
1632
+ // no extension found, write as plain object
1633
+ writeObject(value);
1621
1634
  }
1622
1635
  }
1623
1636
  }
@@ -1673,9 +1686,19 @@
1673
1686
  }
1674
1687
  };
1675
1688
 
1676
- const writePlainObject = (this.variableMapSize || this.coercibleKeyAsNumber) ? (object) => {
1689
+ const writePlainObject = (this.variableMapSize || this.coercibleKeyAsNumber || this.skipValues) ? (object) => {
1677
1690
  // this method is slightly slower, but generates "preferred serialization" (optimally small for smaller objects)
1678
- let keys = Object.keys(object);
1691
+ let keys;
1692
+ if (this.skipValues) {
1693
+ keys = [];
1694
+ for (let key in object) {
1695
+ if ((typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) &&
1696
+ !this.skipValues.includes(object[key]))
1697
+ keys.push(key);
1698
+ }
1699
+ } else {
1700
+ keys = Object.keys(object);
1701
+ }
1679
1702
  let length = keys.length;
1680
1703
  if (length < 0x10) {
1681
1704
  target[position++] = 0x80 | length;
@@ -1704,13 +1727,13 @@
1704
1727
  }
1705
1728
  }
1706
1729
  } :
1707
- (object, safePrototype) => {
1730
+ (object) => {
1708
1731
  target[position++] = 0xde; // always using map 16, so we can preallocate and set the length afterwards
1709
1732
  let objectOffset = position - start;
1710
1733
  position += 2;
1711
1734
  let size = 0;
1712
1735
  for (let key in object) {
1713
- if (safePrototype || object.hasOwnProperty(key)) {
1736
+ if (typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) {
1714
1737
  pack(key);
1715
1738
  pack(object[key]);
1716
1739
  size++;
@@ -1722,12 +1745,12 @@
1722
1745
 
1723
1746
  const writeRecord = this.useRecords === false ? writePlainObject :
1724
1747
  (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)
1725
- (object, safePrototype) => {
1748
+ (object) => {
1726
1749
  let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null));
1727
1750
  let objectOffset = position++ - start;
1728
1751
  let wroteKeys;
1729
1752
  for (let key in object) {
1730
- if (safePrototype || object.hasOwnProperty(key)) {
1753
+ if (typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) {
1731
1754
  nextTransition = transition[key];
1732
1755
  if (nextTransition)
1733
1756
  transition = nextTransition;
@@ -1766,10 +1789,10 @@
1766
1789
  insertNewRecord(transition, Object.keys(object), objectOffset, 0);
1767
1790
  }
1768
1791
  } :
1769
- (object, safePrototype) => {
1792
+ (object) => {
1770
1793
  let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null));
1771
1794
  let newTransitions = 0;
1772
- for (let key in object) if (safePrototype || object.hasOwnProperty(key)) {
1795
+ for (let key in object) if (typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) {
1773
1796
  nextTransition = transition[key];
1774
1797
  if (!nextTransition) {
1775
1798
  nextTransition = transition[key] = Object.create(null);
@@ -1789,16 +1812,16 @@
1789
1812
  }
1790
1813
  // now write the values
1791
1814
  for (let key in object)
1792
- if (safePrototype || object.hasOwnProperty(key)) {
1815
+ if (typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) {
1793
1816
  pack(object[key]);
1794
1817
  }
1795
1818
  };
1796
1819
 
1797
- // craete reference to useRecords if useRecords is a function
1820
+ // create reference to useRecords if useRecords is a function
1798
1821
  const checkUseRecords = typeof this.useRecords == 'function' && this.useRecords;
1799
-
1800
- const writeObject = checkUseRecords ? (object, safePrototype) => {
1801
- checkUseRecords(object) ? writeRecord(object,safePrototype) : writePlainObject(object,safePrototype);
1822
+
1823
+ const writeObject = checkUseRecords ? (object) => {
1824
+ checkUseRecords(object) ? writeRecord(object) : writePlainObject(object);
1802
1825
  } : writeRecord;
1803
1826
 
1804
1827
  const makeRoom = (end) => {
@@ -1903,7 +1926,7 @@
1903
1926
  target[insertionOffset + start] = keysTarget[0];
1904
1927
  }
1905
1928
  };
1906
- const writeStruct = (object, safePrototype) => {
1929
+ const writeStruct = (object) => {
1907
1930
  let newPosition = writeStructSlots(object, target, start, position, structures, makeRoom, (value, newPosition, notifySharedUpdate) => {
1908
1931
  if (notifySharedUpdate)
1909
1932
  return hasSharedUpdate = true;
@@ -1917,16 +1940,22 @@
1917
1940
  return position;
1918
1941
  }, this);
1919
1942
  if (newPosition === 0) // bail and go to a msgpack object
1920
- return writeObject(object, true);
1943
+ return writeObject(object);
1921
1944
  position = newPosition;
1922
1945
  };
1923
1946
  }
1924
1947
  useBuffer(buffer) {
1925
1948
  // this means we are finished using our own buffer and we can write over it safely
1926
1949
  target = buffer;
1927
- targetView = new DataView(target.buffer, target.byteOffset, target.byteLength);
1950
+ target.dataView || (target.dataView = new DataView(target.buffer, target.byteOffset, target.byteLength));
1928
1951
  position = 0;
1929
1952
  }
1953
+ set position (value) {
1954
+ position = value;
1955
+ }
1956
+ get position() {
1957
+ return position;
1958
+ }
1930
1959
  clearSharedData() {
1931
1960
  if (this.structures)
1932
1961
  this.structures = [];
@@ -1995,7 +2024,7 @@
1995
2024
  target[position++] = 0x65; // 'e' for error
1996
2025
  target[position++] = 0;
1997
2026
  }
1998
- pack([ error.name, error.message ]);
2027
+ pack([ error.name, error.message, error.cause ]);
1999
2028
  }
2000
2029
  }, {
2001
2030
  pack(regex, allocateForWrite, pack) {
@@ -2048,6 +2077,7 @@
2048
2077
  }
2049
2078
  target[position++] = 0x74; // "t" for typed array
2050
2079
  target[position++] = type;
2080
+ if (!typedArray.buffer) typedArray = new Uint8Array(typedArray);
2051
2081
  target.set(new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength), position);
2052
2082
  }
2053
2083
  function writeBuffer(buffer, allocateForWrite) {
@@ -2271,6 +2301,8 @@
2271
2301
  exports.FLOAT32_OPTIONS = FLOAT32_OPTIONS;
2272
2302
  exports.NEVER = NEVER;
2273
2303
  exports.Packr = Packr;
2304
+ exports.RESERVE_START_SPACE = RESERVE_START_SPACE;
2305
+ exports.RESET_BUFFER_MODE = RESET_BUFFER_MODE;
2274
2306
  exports.REUSE_BUFFER_MODE = REUSE_BUFFER_MODE;
2275
2307
  exports.Unpackr = Unpackr;
2276
2308
  exports.addExtension = addExtension;