msgpackr 1.11.1 → 1.11.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
@@ -193,6 +193,7 @@ The following options properties can be provided to the Packr or Unpackr constru
193
193
  * `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`).
194
194
  * `mapAsEmptyObject` - Encodes JS `Map`s as empty objects (for back-compat with older libraries).
195
195
  * `setAsEmptyObject` - Encodes JS `Set`s as empty objects (for back-compat with older libraries).
196
+ * `allowArraysInMapKeys` - Allows arrays to be used as keys in Maps, as long as all elements are strings, numbers, booleans, or bigints. When enabled, such arrays are flattened and converted to a string representation.
196
197
 
197
198
  ### 32-bit Float Options
198
199
  By default all non-integer numbers are serialized as 64-bit float (double). This is fast, and ensures maximum precision. However, often real-world data doesn't not need 64-bits of precision, and using 32-bit encoding can be much more space efficient. There are several options that provide more efficient encodings. Using the decimal rounding options for encoding and decoding provides lossless storage of common decimal representations like 7.99, in more efficient 32-bit format (rather than 64-bit). The `useFloat32` property has several possible options, available from the module as constants:
@@ -916,7 +916,10 @@
916
916
  if (typeof property === 'string') return property;
917
917
  if (typeof property === 'number' || typeof property === 'boolean' || typeof property === 'bigint') return property.toString();
918
918
  if (property == null) return property + '';
919
- throw new Error('Invalid property type for record', typeof property);
919
+ if (currentUnpackr.allowArraysInMapKeys && Array.isArray(property) && property.flat().every(item => ['string', 'number', 'boolean', 'bigint'].includes(typeof item))) {
920
+ return property.flat().toString();
921
+ }
922
+ throw new Error(`Invalid property type for record: ${typeof property}`);
920
923
  }
921
924
  // the registration of the record definition extension (as "r")
922
925
  const recordDefinition = (id, highByte) => {
@@ -941,21 +944,47 @@
941
944
  currentExtensions[0] = () => {}; // notepack defines extension 0 to mean undefined, so use that as the default here
942
945
  currentExtensions[0].noBuffer = true;
943
946
 
944
- currentExtensions[0x42] = (data) => {
945
- // decode bigint
946
- let length = data.length;
947
- let value = BigInt(data[0] & 0x80 ? data[0] - 0x100 : data[0]);
948
- for (let i = 1; i < length; i++) {
949
- value <<= BigInt(8);
950
- value += BigInt(data[i]);
947
+ currentExtensions[0x42] = data => {
948
+ let headLength = (data.byteLength % 8) || 8;
949
+ let head = BigInt(data[0] & 0x80 ? data[0] - 0x100 : data[0]);
950
+ for (let i = 1; i < headLength; i++) {
951
+ head <<= BigInt(8);
952
+ head += BigInt(data[i]);
951
953
  }
952
- return value;
954
+ if (data.byteLength !== headLength) {
955
+ let view = new DataView(data.buffer, data.byteOffset, data.byteLength);
956
+ let decode = (start, end) => {
957
+ let length = end - start;
958
+ if (length <= 40) {
959
+ let out = view.getBigUint64(start);
960
+ for (let i = start + 8; i < end; i += 8) {
961
+ out <<= BigInt(64n);
962
+ out |= view.getBigUint64(i);
963
+ }
964
+ return out
965
+ }
966
+ // if (length === 8) return view.getBigUint64(start)
967
+ let middle = start + (length >> 4 << 3);
968
+ let left = decode(start, middle);
969
+ let right = decode(middle, end);
970
+ return (left << BigInt((end - middle) * 8)) | right
971
+ };
972
+ head = (head << BigInt((view.byteLength - headLength) * 8)) | decode(headLength, view.byteLength);
973
+ }
974
+ return head
953
975
  };
954
976
 
955
- let errors = { Error, TypeError, ReferenceError };
977
+ let errors = {
978
+ Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError, AggregateError: typeof AggregateError === 'function' ? AggregateError : null,
979
+ };
956
980
  currentExtensions[0x65] = () => {
957
981
  let data = read();
958
- return (errors[data[0]] || Error)(data[1], { cause: data[2] })
982
+ if (!errors[data[0]]) {
983
+ let error = Error(data[1], { cause: data[2] });
984
+ error.name = data[0];
985
+ return error
986
+ }
987
+ return errors[data[0]](data[1], { cause: data[2] })
959
988
  };
960
989
 
961
990
  currentExtensions[0x69] = (data) => {
@@ -966,20 +995,33 @@
966
995
  referenceMap = new Map();
967
996
  let token = src[position$1];
968
997
  let target;
969
- // TODO: handle Maps, Sets, and other types that can cycle; this is complicated, because you potentially need to read
970
- // ahead past references to record structure definitions
998
+ // TODO: handle any other types that can cycle and make the code more robust if there are other extensions
971
999
  if (token >= 0x90 && token < 0xa0 || token == 0xdc || token == 0xdd)
972
1000
  target = [];
1001
+ else if (token >= 0x80 && token < 0x90 || token == 0xde || token == 0xdf)
1002
+ target = new Map();
1003
+ else if ((token >= 0xc7 && token <= 0xc9 || token >= 0xd4 && token <= 0xd8) && src[position$1 + 1] === 0x73)
1004
+ target = new Set();
973
1005
  else
974
1006
  target = {};
975
1007
 
976
1008
  let refEntry = { target }; // a placeholder object
977
1009
  referenceMap.set(id, refEntry);
978
1010
  let targetProperties = read(); // read the next value as the target object to id
979
- if (refEntry.used) // there is a cycle, so we have to assign properties to original target
980
- return Object.assign(target, targetProperties)
981
- refEntry.target = targetProperties; // the placeholder wasn't used, replace with the deserialized one
982
- return targetProperties // no cycle, can just use the returned read object
1011
+ if (!refEntry.used) {
1012
+ // no cycle, can just use the returned read object
1013
+ return refEntry.target = targetProperties // replace the placeholder with the real one
1014
+ } else {
1015
+ // there is a cycle, so we have to assign properties to original target
1016
+ Object.assign(target, targetProperties);
1017
+ }
1018
+
1019
+ // copy over map/set entries if we're able to
1020
+ if (target instanceof Map)
1021
+ for (let [k, v] of targetProperties.entries()) target.set(k, v);
1022
+ if (target instanceof Set)
1023
+ for (let i of Array.from(targetProperties)) target.add(i);
1024
+ return target
983
1025
  };
984
1026
 
985
1027
  currentExtensions[0x70] = (data) => {
@@ -998,18 +1040,16 @@
998
1040
  let glbl = typeof globalThis === 'object' ? globalThis : window;
999
1041
  currentExtensions[0x74] = (data) => {
1000
1042
  let typeCode = data[0];
1043
+ // we always have to slice to get a new ArrayBuffer that is aligned
1044
+ let buffer = Uint8Array.prototype.slice.call(data, 1).buffer;
1045
+
1001
1046
  let typedArrayName = typedArrays[typeCode];
1002
1047
  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
- }
1048
+ if (typeCode === 16) return buffer
1049
+ if (typeCode === 17) return new DataView(buffer)
1009
1050
  throw new Error('Could not find typed array for code ' + typeCode)
1010
1051
  }
1011
- // we have to always slice/copy here to get a new ArrayBuffer that is word/byte aligned
1012
- return new glbl[typedArrayName](Uint8Array.prototype.slice.call(data, 1).buffer)
1052
+ return new glbl[typedArrayName](buffer)
1013
1053
  };
1014
1054
  currentExtensions[0x78] = () => {
1015
1055
  let data = read();
@@ -1037,13 +1077,13 @@
1037
1077
  return new Date(
1038
1078
  ((data[0] << 22) + (data[1] << 14) + (data[2] << 6) + (data[3] >> 2)) / 1000000 +
1039
1079
  ((data[3] & 0x3) * 0x100000000 + data[4] * 0x1000000 + (data[5] << 16) + (data[6] << 8) + data[7]) * 1000)
1040
- else if (data.length == 12)// TODO: Implement support for negative
1080
+ else if (data.length == 12)
1041
1081
  return new Date(
1042
1082
  ((data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3]) / 1000000 +
1043
1083
  (((data[4] & 0x80) ? -0x1000000000000 : 0) + data[6] * 0x10000000000 + data[7] * 0x100000000 + data[8] * 0x1000000 + (data[9] << 16) + (data[10] << 8) + data[11]) * 1000)
1044
1084
  else
1045
1085
  return new Date('invalid')
1046
- }; // notepack defines extension 0 to mean undefined, so use that as the default here
1086
+ };
1047
1087
  // registration of bulk record definition?
1048
1088
  // currentExtensions[0x52] = () =>
1049
1089
 
@@ -1637,11 +1677,11 @@
1637
1677
  } else if (type === 'boolean') {
1638
1678
  target[position++] = value ? 0xc3 : 0xc2;
1639
1679
  } else if (type === 'bigint') {
1640
- if (value < (BigInt(1)<<BigInt(63)) && value >= -(BigInt(1)<<BigInt(63))) {
1680
+ if (value < 0x8000000000000000 && value >= -0x8000000000000000) {
1641
1681
  // use a signed int as long as it fits
1642
1682
  target[position++] = 0xd3;
1643
1683
  targetView.setBigInt64(position, value);
1644
- } else if (value < (BigInt(1)<<BigInt(64)) && value > 0) {
1684
+ } else if (value < 0x10000000000000000 && value > 0) {
1645
1685
  // if we can fit an unsigned int, use that
1646
1686
  target[position++] = 0xcf;
1647
1687
  targetView.setBigUint64(position, value);
@@ -1652,22 +1692,46 @@
1652
1692
  targetView.setFloat64(position, Number(value));
1653
1693
  } else if (this.largeBigIntToString) {
1654
1694
  return pack(value.toString());
1655
- } else if (this.useBigIntExtension && value < BigInt(2)**BigInt(1023) && value > -(BigInt(2)**BigInt(1023))) {
1656
- target[position++] = 0xc7;
1657
- position++;
1658
- target[position++] = 0x42; // "B" for BigInt
1659
- let bytes = [];
1660
- let alignedSign;
1661
- do {
1662
- let byte = value & BigInt(0xff);
1663
- alignedSign = (byte & BigInt(0x80)) === (value < BigInt(0) ? BigInt(0x80) : BigInt(0));
1664
- bytes.push(byte);
1665
- value >>= BigInt(8);
1666
- } while (!((value === BigInt(0) || value === BigInt(-1)) && alignedSign));
1667
- target[position-2] = bytes.length;
1668
- for (let i = bytes.length; i > 0;) {
1669
- target[position++] = Number(bytes[--i]);
1695
+ } else if (this.useBigIntExtension || this.moreTypes) {
1696
+ let empty = value < 0 ? BigInt(-1) : BigInt(0);
1697
+
1698
+ let array;
1699
+ if (value >> BigInt(0x10000) === empty) {
1700
+ let mask = BigInt(0x10000000000000000) - BigInt(1); // literal would overflow
1701
+ let chunks = [];
1702
+ do {
1703
+ chunks.push(value & mask);
1704
+ value >>= BigInt(64);
1705
+ } while (value !== empty)
1706
+
1707
+ array = new Uint8Array(new BigUint64Array(chunks).buffer);
1708
+ array.reverse();
1709
+ } else {
1710
+ let invert = value < 0;
1711
+ let string = (invert ? ~value : value).toString(16);
1712
+ if (string.length % 2) {
1713
+ string = '0' + string;
1714
+ } else if (parseInt(string.charAt(0), 16) >= 8) {
1715
+ string = '00' + string;
1716
+ }
1717
+
1718
+ if (hasNodeBuffer) {
1719
+ array = Buffer.from(string, 'hex');
1720
+ } else {
1721
+ array = new Uint8Array(string.length / 2);
1722
+ for (let i = 0; i < array.length; i++) {
1723
+ array[i] = parseInt(string.slice(i * 2, i * 2 + 2), 16);
1724
+ }
1725
+ }
1726
+
1727
+ if (invert) {
1728
+ for (let i = 0; i < array.length; i++) array[i] = ~array[i];
1729
+ }
1670
1730
  }
1731
+
1732
+ if (array.length + position > safeEnd)
1733
+ makeRoom(array.length + position);
1734
+ position = writeExtensionData(array, target, position, 0x42);
1671
1735
  return
1672
1736
  } else {
1673
1737
  throw new RangeError(value + ' was too large to fit in MessagePack 64-bit integer format, use' +
@@ -1971,7 +2035,7 @@
1971
2035
  }
1972
2036
  }
1973
2037
 
1974
- extensionClasses = [ Date, Set, Error, RegExp, ArrayBuffer, Object.getPrototypeOf(Uint8Array.prototype).constructor /*TypedArray*/, C1Type ];
2038
+ extensionClasses = [ Date, Set, Error, RegExp, ArrayBuffer, Object.getPrototypeOf(Uint8Array.prototype).constructor /*TypedArray*/, DataView, C1Type ];
1975
2039
  extensions = [{
1976
2040
  pack(date, allocateForWrite, pack) {
1977
2041
  let seconds = date.getTime() / 1000;
@@ -2058,6 +2122,13 @@
2058
2122
  else
2059
2123
  writeBuffer(typedArray, allocateForWrite);
2060
2124
  }
2125
+ }, {
2126
+ pack(arrayBuffer, allocateForWrite) {
2127
+ if (this.moreTypes)
2128
+ writeExtBuffer(arrayBuffer, 0x11, allocateForWrite);
2129
+ else
2130
+ writeBuffer(hasNodeBuffer ? Buffer.from(arrayBuffer) : new Uint8Array(arrayBuffer), allocateForWrite);
2131
+ }
2061
2132
  }, {
2062
2133
  pack(c1, allocateForWrite) { // specific 0xC1 object
2063
2134
  let { target, position} = allocateForWrite(1);
@@ -2100,7 +2171,7 @@
2100
2171
  target[position++] = length >> 8;
2101
2172
  target[position++] = length & 0xff;
2102
2173
  } else {
2103
- let { target, position, targetView } = allocateForWrite(length + 5);
2174
+ var { target, position, targetView } = allocateForWrite(length + 5);
2104
2175
  target[position++] = 0xc6;
2105
2176
  targetView.setUint32(position, length);
2106
2177
  position += 4;