msgpackr 1.11.2 → 1.11.4

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) => {
@@ -966,20 +969,33 @@
966
969
  referenceMap = new Map();
967
970
  let token = src[position$1];
968
971
  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
972
+ // TODO: handle any other types that can cycle and make the code more robust if there are other extensions
971
973
  if (token >= 0x90 && token < 0xa0 || token == 0xdc || token == 0xdd)
972
974
  target = [];
975
+ else if (token >= 0x80 && token < 0x90 || token == 0xde || token == 0xdf)
976
+ target = new Map();
977
+ else if ((token >= 0xc7 && token <= 0xc9 || token >= 0xd4 && token <= 0xd8) && src[position$1 + 1] === 0x73)
978
+ target = new Set();
973
979
  else
974
980
  target = {};
975
981
 
976
982
  let refEntry = { target }; // a placeholder object
977
983
  referenceMap.set(id, refEntry);
978
984
  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
985
+ if (!refEntry.used) {
986
+ // no cycle, can just use the returned read object
987
+ return refEntry.target = targetProperties // replace the placeholder with the real one
988
+ } else {
989
+ // there is a cycle, so we have to assign properties to original target
990
+ Object.assign(target, targetProperties);
991
+ }
992
+
993
+ // copy over map/set entries if we're able to
994
+ if (target instanceof Map)
995
+ for (let [k, v] of targetProperties.entries()) target.set(k, v);
996
+ if (target instanceof Set)
997
+ for (let i of Array.from(targetProperties)) target.add(i);
998
+ return target
983
999
  };
984
1000
 
985
1001
  currentExtensions[0x70] = (data) => {
@@ -998,18 +1014,16 @@
998
1014
  let glbl = typeof globalThis === 'object' ? globalThis : window;
999
1015
  currentExtensions[0x74] = (data) => {
1000
1016
  let typeCode = data[0];
1017
+ // we always have to slice to get a new ArrayBuffer that is aligned
1018
+ let buffer = Uint8Array.prototype.slice.call(data, 1).buffer;
1019
+
1001
1020
  let typedArrayName = typedArrays[typeCode];
1002
1021
  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
- }
1022
+ if (typeCode === 16) return buffer
1023
+ if (typeCode === 17) return new DataView(buffer)
1009
1024
  throw new Error('Could not find typed array for code ' + typeCode)
1010
1025
  }
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)
1026
+ return new glbl[typedArrayName](buffer)
1013
1027
  };
1014
1028
  currentExtensions[0x78] = () => {
1015
1029
  let data = read();
@@ -1037,13 +1051,13 @@
1037
1051
  return new Date(
1038
1052
  ((data[0] << 22) + (data[1] << 14) + (data[2] << 6) + (data[3] >> 2)) / 1000000 +
1039
1053
  ((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
1054
+ else if (data.length == 12)
1041
1055
  return new Date(
1042
1056
  ((data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3]) / 1000000 +
1043
1057
  (((data[4] & 0x80) ? -0x1000000000000 : 0) + data[6] * 0x10000000000 + data[7] * 0x100000000 + data[8] * 0x1000000 + (data[9] << 16) + (data[10] << 8) + data[11]) * 1000)
1044
1058
  else
1045
1059
  return new Date('invalid')
1046
- }; // notepack defines extension 0 to mean undefined, so use that as the default here
1060
+ };
1047
1061
  // registration of bulk record definition?
1048
1062
  // currentExtensions[0x52] = () =>
1049
1063
 
@@ -1637,11 +1651,11 @@
1637
1651
  } else if (type === 'boolean') {
1638
1652
  target[position++] = value ? 0xc3 : 0xc2;
1639
1653
  } else if (type === 'bigint') {
1640
- if (value < (BigInt(1)<<BigInt(63)) && value >= -(BigInt(1)<<BigInt(63))) {
1654
+ if (value < 0x8000000000000000 && value >= -0x8000000000000000) {
1641
1655
  // use a signed int as long as it fits
1642
1656
  target[position++] = 0xd3;
1643
1657
  targetView.setBigInt64(position, value);
1644
- } else if (value < (BigInt(1)<<BigInt(64)) && value > 0) {
1658
+ } else if (value < 0x10000000000000000 && value > 0) {
1645
1659
  // if we can fit an unsigned int, use that
1646
1660
  target[position++] = 0xcf;
1647
1661
  targetView.setBigUint64(position, value);
@@ -1652,7 +1666,7 @@
1652
1666
  targetView.setFloat64(position, Number(value));
1653
1667
  } else if (this.largeBigIntToString) {
1654
1668
  return pack(value.toString());
1655
- } else if (this.useBigIntExtension && value < BigInt(2)**BigInt(1023) && value > -(BigInt(2)**BigInt(1023))) {
1669
+ } else if ((this.useBigIntExtension || this.moreTypes) && value < BigInt(2)**BigInt(1023) && value > -(BigInt(2)**BigInt(1023))) {
1656
1670
  target[position++] = 0xc7;
1657
1671
  position++;
1658
1672
  target[position++] = 0x42; // "B" for BigInt
@@ -1971,7 +1985,7 @@
1971
1985
  }
1972
1986
  }
1973
1987
 
1974
- extensionClasses = [ Date, Set, Error, RegExp, ArrayBuffer, Object.getPrototypeOf(Uint8Array.prototype).constructor /*TypedArray*/, C1Type ];
1988
+ extensionClasses = [ Date, Set, Error, RegExp, ArrayBuffer, Object.getPrototypeOf(Uint8Array.prototype).constructor /*TypedArray*/, DataView, C1Type ];
1975
1989
  extensions = [{
1976
1990
  pack(date, allocateForWrite, pack) {
1977
1991
  let seconds = date.getTime() / 1000;
@@ -2058,6 +2072,13 @@
2058
2072
  else
2059
2073
  writeBuffer(typedArray, allocateForWrite);
2060
2074
  }
2075
+ }, {
2076
+ pack(arrayBuffer, allocateForWrite) {
2077
+ if (this.moreTypes)
2078
+ writeExtBuffer(arrayBuffer, 0x11, allocateForWrite);
2079
+ else
2080
+ writeBuffer(hasNodeBuffer ? Buffer.from(arrayBuffer) : new Uint8Array(arrayBuffer), allocateForWrite);
2081
+ }
2061
2082
  }, {
2062
2083
  pack(c1, allocateForWrite) { // specific 0xC1 object
2063
2084
  let { target, position} = allocateForWrite(1);