msgpackr 1.5.0 → 1.5.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
@@ -7,6 +7,7 @@
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
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
 
@@ -53,7 +54,7 @@ receivingStream.on('data', (data) => {
53
54
  The `PackrStream` and `UnpackrStream` instances will have also the record structure extension enabled by default (see below).
54
55
 
55
56
  ## Deno Usage
56
- Msgpackr modules are standard ESM modules and can be loaded directly from github (https://raw.githubusercontent.com/kriszyp/msgpackr/master/index.js) or downloaded and used directly in Deno. The standard pack/encode and unpack/decode functionality is available on Deno, like other platforms.
57
+ Msgpackr modules are standard ESM modules and can be loaded directly from the [deno.land registry for msgpackr](https://deno.land/x/msgpackr) for use in Deno. The standard pack/encode and unpack/decode functionality is available on Deno, like other platforms.
57
58
 
58
59
  ## Browser Usage
59
60
  Msgpackr works as standalone JavaScript as well, and runs on modern browsers. It includes a bundled script, at `dist/index.js` for ease of direct loading:
@@ -162,12 +163,14 @@ The following options properties can be provided to the Packr or Unpackr constru
162
163
  * `mapsAsObjects` - If `true`, this will decode MessagePack maps and JS `Object`s with the map entries decoded to object properties. If `false`, maps are decoded as JavaScript `Map`s. This is disabled by default if `useRecords` is enabled (which allows `Map`s to be preserved), and is enabled by default if `useRecords` is disabled.
163
164
  * `useFloat32` - This will enable msgpackr to encode non-integer numbers as `float32`. See next section for possible values.
164
165
  * `variableMapSize` - This will use varying map size definition (fixmap, map16, map32) based on the number of keys when encoding objects, which yields slightly more compact encodings (for small objects), but is typically 5-10% slower during encoding. This is necessary if you need to use objects with more than 65535 keys. This is only relevant when record extension is disabled.
166
+ * `bundleStrings` - If `true` this uses a custom extension that bundles strings together, so that they can be decoded more quickly on browsers and Deno that do not have access to the NodeJS addon. This a custom extension, so both encoder and decoder need to support this. This can yield significant decoding performance increases on browsers (30%-50%).
165
167
  * `copyBuffers` - When decoding a MessagePack with binary data (Buffers are encoded as binary data), copy the buffer rather than providing a slice/view of the buffer. If you want your input data to be collected or modified while the decoded embedded buffer continues to live on, you can use this option (there is extra overhead to copying).
166
168
  * `useTimestamp32` - Encode JS `Date`s in 32-bit format when possible by dropping the milliseconds. This is a more efficient encoding of dates. You can also cause dates to use 32-bit format by manually setting the milliseconds to zero (`date.setMilliseconds(0)`).
167
169
  * `sequential` - Encode structures in serialized data, and reference previously encoded structures with expectation that decoder will read the encoded structures in the same order as encoded, with `unpackMultiple`.
168
170
  * `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).
169
171
  * `encodeUndefinedAsNil` - Encodes a value of `undefined` as a MessagePack `nil`, the same as a `null`.
170
172
  * `int64AsNumber` - This will decode uint64 and int64 numbers as standard JS numbers rather than as bigint numbers.
173
+ * `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).
171
174
 
172
175
  ### 32-bit Float Options
173
176
  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:
package/dist/index.js CHANGED
@@ -16,6 +16,7 @@
16
16
  var srcString;
17
17
  var srcStringStart = 0;
18
18
  var srcStringEnd = 0;
19
+ var bundledStrings;
19
20
  var referenceMap;
20
21
  var currentExtensions = [];
21
22
  var dataView;
@@ -54,6 +55,7 @@
54
55
  position = 0;
55
56
  srcStringEnd = 0;
56
57
  srcString = null;
58
+ bundledStrings = null;
57
59
  src = source;
58
60
  // this provides cached access to the data view for a buffer if it is getting reused, which is a recommend
59
61
  // technique for getting data from a database where it can be copied into an existing buffer instead of creating
@@ -150,6 +152,9 @@
150
152
  currentStructures.length = sharedLength;
151
153
  }
152
154
  let result = read();
155
+ if (bundledStrings) // bundled strings to skip past
156
+ position = bundledStrings.postBundlePosition;
157
+
153
158
  if (position == srcEnd) {
154
159
  // finished reading this source, cleanup references
155
160
  if (currentStructures.restoreStructures)
@@ -244,7 +249,15 @@
244
249
  let value;
245
250
  switch (token) {
246
251
  case 0xc0: return null
247
- case 0xc1: return C1; // "never-used", return special object to denote that
252
+ case 0xc1:
253
+ if (bundledStrings) {
254
+ value = read(); // followed by the length of the string in characters (not bytes!)
255
+ if (value > 0)
256
+ return bundledStrings[1].slice(bundledStrings.position1, bundledStrings.position1 += value)
257
+ else
258
+ return bundledStrings[0].slice(bundledStrings.position0, bundledStrings.position0 -= value)
259
+ }
260
+ return C1; // "never-used", return special object to denote that
248
261
  case 0xc2: return false
249
262
  case 0xc3: return true
250
263
  case 0xc4:
@@ -705,6 +718,36 @@
705
718
  }
706
719
  }
707
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
+
708
751
  function readBin(length) {
709
752
  return currentUnpackr.copyBuffers ?
710
753
  // specifically use the copying slice (not the node one)
@@ -856,6 +899,19 @@
856
899
  let data = read();
857
900
  return new RegExp(data[0], data[1])
858
901
  };
902
+ const TEMP_BUNDLE = [];
903
+ currentExtensions[0x62] = (data) => {
904
+ let dataSize = (data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3];
905
+ let dataPosition = position;
906
+ position += dataSize - data.length;
907
+ bundledStrings = TEMP_BUNDLE;
908
+ bundledStrings = [readOnlyJSString(), readOnlyJSString()];
909
+ bundledStrings.position0 = 0;
910
+ bundledStrings.position1 = 0;
911
+ bundledStrings.postBundlePosition = position;
912
+ position = dataPosition;
913
+ return read()
914
+ };
859
915
 
860
916
  currentExtensions[0xff] = (data) => {
861
917
  // 32-bit date extension
@@ -870,7 +926,7 @@
870
926
  ((data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3]) / 1000000 +
871
927
  (((data[4] & 0x80) ? -0x1000000000000 : 0) + data[6] * 0x10000000000 + data[7] * 0x100000000 + data[8] * 0x1000000 + (data[9] << 16) + (data[10] << 8) + data[11]) * 1000)
872
928
  else
873
- throw new Error('Invalid timestamp length')
929
+ return new Date('invalid')
874
930
  }; // notepack defines extension 0 to mean undefined, so use that as the default here
875
931
  // registration of bulk record definition?
876
932
  // currentExtensions[0x52] = () =>
@@ -882,6 +938,7 @@
882
938
  let savedSrcStringEnd = srcStringEnd;
883
939
  let savedSrcString = srcString;
884
940
  let savedReferenceMap = referenceMap;
941
+ let savedBundledStrings = bundledStrings;
885
942
 
886
943
  // TODO: We may need to revisit this if we do more external calls to user code (since it could be slow)
887
944
  let savedSrc = new Uint8Array(src.slice(0, srcEnd)); // we copy the data in case it changes while external data is processed
@@ -896,6 +953,7 @@
896
953
  srcStringEnd = savedSrcStringEnd;
897
954
  srcString = savedSrcString;
898
955
  referenceMap = savedReferenceMap;
956
+ bundledStrings = savedBundledStrings;
899
957
  src = savedSrc;
900
958
  sequentialMode = savedSequentialMode;
901
959
  currentStructures = savedStructures;
@@ -949,17 +1007,19 @@
949
1007
  const ByteArrayAllocate = hasNodeBuffer ? Buffer.allocUnsafeSlow : Uint8Array;
950
1008
  const ByteArray = hasNodeBuffer ? Buffer : Uint8Array;
951
1009
  const MAX_BUFFER_SIZE = hasNodeBuffer ? 0x100000000 : 0x7fd00000;
952
- let target;
1010
+ let target, keysTarget;
953
1011
  let targetView;
954
1012
  let position$1 = 0;
955
1013
  let safeEnd;
1014
+ let bundledStrings$1 = null;
1015
+ const MAX_BUNDLE_SIZE = 0xf000;
1016
+ const hasNonLatin = /[\u0080-\uFFFF]/;
956
1017
  const RECORD_SYMBOL = Symbol('record-id');
957
1018
  class Packr extends Unpackr {
958
1019
  constructor(options) {
959
1020
  super(options);
960
1021
  this.offset = 0;
961
1022
  let start;
962
- let sharedStructures;
963
1023
  let hasSharedUpdate;
964
1024
  let structures;
965
1025
  let referenceMap;
@@ -984,7 +1044,7 @@
984
1044
  let maxOwnStructures = options.maxOwnStructures;
985
1045
  if (maxOwnStructures == null)
986
1046
  maxOwnStructures = hasSharedStructures ? 32 : 64;
987
- if (isSequential && !options.saveStructures)
1047
+ if (!this.structures && options.useRecords != false)
988
1048
  this.structures = [];
989
1049
  // two byte record ids for shared structures
990
1050
  let useTwoByteRecords = maxSharedStructures > 32 || (maxOwnStructures + maxSharedStructures > 64);
@@ -1014,23 +1074,28 @@
1014
1074
  position$1 = (position$1 + 7) & 0x7ffffff8; // Word align to make any future copying of this buffer faster
1015
1075
  start = position$1;
1016
1076
  referenceMap = packr.structuredClone ? new Map() : null;
1017
- sharedStructures = packr.structures;
1018
- if (sharedStructures) {
1019
- if (sharedStructures.uninitialized)
1020
- sharedStructures = packr._mergeStructures(packr.getStructures());
1021
- let sharedLength = sharedStructures.sharedLength || 0;
1077
+ if (packr.bundleStrings && typeof value !== 'string') {
1078
+ bundledStrings$1 = [];
1079
+ bundledStrings$1.size = Infinity; // force a new bundle start on first string
1080
+ } else
1081
+ bundledStrings$1 = null;
1082
+ structures = packr.structures;
1083
+ if (structures) {
1084
+ if (structures.uninitialized)
1085
+ structures = packr._mergeStructures(packr.getStructures());
1086
+ let sharedLength = structures.sharedLength || 0;
1022
1087
  if (sharedLength > maxSharedStructures) {
1023
- //if (maxSharedStructures <= 32 && sharedStructures.sharedLength > 32) // TODO: could support this, but would need to update the limit ids
1024
- throw new Error('Shared structures is larger than maximum shared structures, try increasing maxSharedStructures to ' + sharedStructures.sharedLength)
1088
+ //if (maxSharedStructures <= 32 && structures.sharedLength > 32) // TODO: could support this, but would need to update the limit ids
1089
+ throw new Error('Shared structures is larger than maximum shared structures, try increasing maxSharedStructures to ' + structures.sharedLength)
1025
1090
  }
1026
- if (!sharedStructures.transitions) {
1091
+ if (!structures.transitions) {
1027
1092
  // rebuild our structure transitions
1028
- sharedStructures.transitions = Object.create(null);
1093
+ structures.transitions = Object.create(null);
1029
1094
  for (let i = 0; i < sharedLength; i++) {
1030
- let keys = sharedStructures[i];
1095
+ let keys = structures[i];
1031
1096
  if (!keys)
1032
1097
  continue
1033
- let nextTransition, transition = sharedStructures.transitions;
1098
+ let nextTransition, transition = structures.transitions;
1034
1099
  for (let j = 0, l = keys.length; j < l; j++) {
1035
1100
  let key = keys[j];
1036
1101
  nextTransition = transition[key];
@@ -1044,14 +1109,16 @@
1044
1109
  lastSharedStructuresLength = sharedLength;
1045
1110
  }
1046
1111
  if (!isSequential) {
1047
- sharedStructures.nextId = sharedLength + 0x40;
1112
+ structures.nextId = sharedLength + 0x40;
1048
1113
  }
1049
1114
  }
1050
1115
  if (hasSharedUpdate)
1051
1116
  hasSharedUpdate = false;
1052
- structures = sharedStructures || [];
1053
1117
  try {
1054
1118
  pack(value);
1119
+ if (bundledStrings$1) {
1120
+ writeBundles(start, pack);
1121
+ }
1055
1122
  packr.offset = position$1; // update the offset so next serialization doesn't write over our buffer, but can continue writing to same buffer sequentially
1056
1123
  if (referenceMap && referenceMap.idsToInsert) {
1057
1124
  position$1 += referenceMap.idsToInsert.length * 6;
@@ -1062,19 +1129,22 @@
1062
1129
  referenceMap = null;
1063
1130
  return serialized
1064
1131
  }
1065
- if (encodeOptions === REUSE_BUFFER_MODE) {
1132
+ if (encodeOptions & REUSE_BUFFER_MODE) {
1066
1133
  target.start = start;
1067
1134
  target.end = position$1;
1068
1135
  return target
1069
1136
  }
1070
1137
  return target.subarray(start, position$1) // position can change if we call pack again in saveStructures, so we get the buffer now
1071
1138
  } finally {
1072
- if (sharedStructures) {
1139
+ if (structures) {
1073
1140
  if (serializationsSinceTransitionRebuild < 10)
1074
1141
  serializationsSinceTransitionRebuild++;
1142
+ let sharedLength = structures.sharedLength || maxSharedStructures;
1143
+ if (structures.length > sharedLength)
1144
+ structures.length = sharedLength;
1075
1145
  if (transitionsCount > 10000) {
1076
1146
  // force a rebuild occasionally after a lot of transitions so it can get cleaned up
1077
- sharedStructures.transitions = null;
1147
+ structures.transitions = null;
1078
1148
  serializationsSinceTransitionRebuild = 0;
1079
1149
  transitionsCount = 0;
1080
1150
  if (recordIdsToRemove.length > 0)
@@ -1086,13 +1156,9 @@
1086
1156
  recordIdsToRemove = [];
1087
1157
  }
1088
1158
  if (hasSharedUpdate && packr.saveStructures) {
1089
- let sharedLength = sharedStructures.sharedLength || maxSharedStructures;
1090
- if (sharedStructures.length > sharedLength) {
1091
- sharedStructures = sharedStructures.slice(0, sharedLength);
1092
- }
1093
1159
  // we can't rely on start/end with REUSE_BUFFER_MODE since they will (probably) change when we save
1094
1160
  let returnBuffer = target.subarray(start, position$1);
1095
- if (packr.saveStructures(sharedStructures, lastSharedStructuresLength) === false) {
1161
+ if (packr.saveStructures(structures, lastSharedStructuresLength) === false) {
1096
1162
  // get updated structures and try again if the update failed
1097
1163
  packr._mergeStructures(packr.getStructures());
1098
1164
  return packr.pack(value)
@@ -1101,6 +1167,8 @@
1101
1167
  return returnBuffer
1102
1168
  }
1103
1169
  }
1170
+ if (encodeOptions & RESET_BUFFER_MODE)
1171
+ position$1 = start;
1104
1172
  }
1105
1173
  };
1106
1174
  const pack = (value) => {
@@ -1111,6 +1179,36 @@
1111
1179
  var length;
1112
1180
  if (type === 'string') {
1113
1181
  let strLength = value.length;
1182
+ if (bundledStrings$1 && strLength >= 4 && strLength < 0x1000) {
1183
+ if ((bundledStrings$1.size += strLength) > MAX_BUNDLE_SIZE) {
1184
+ let extStart;
1185
+ let maxBytes = (bundledStrings$1[0] ? bundledStrings$1[0].length * 3 + bundledStrings$1[1].length : 0) + 10;
1186
+ if (position$1 + maxBytes > safeEnd)
1187
+ target = makeRoom(position$1 + maxBytes);
1188
+ 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
1189
+ target[position$1] = 0xc8; // ext 16
1190
+ position$1 += 3; // reserve for the writing bundle size
1191
+ target[position$1++] = 0x62; // 'b'
1192
+ extStart = position$1 - start;
1193
+ position$1 += 4; // reserve for writing bundle reference
1194
+ writeBundles(start, pack); // write the last bundles
1195
+ targetView.setUint16(extStart + start - 3, position$1 - start - extStart);
1196
+ } 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)
1197
+ target[position$1++] = 0xd6; // fixext 4
1198
+ target[position$1++] = 0x62; // 'b'
1199
+ extStart = position$1 - start;
1200
+ position$1 += 4; // reserve for writing bundle reference
1201
+ }
1202
+ bundledStrings$1 = ['', '']; // create new ones
1203
+ bundledStrings$1.size = 0;
1204
+ bundledStrings$1.position = extStart;
1205
+ }
1206
+ let twoByte = hasNonLatin.test(value);
1207
+ bundledStrings$1[twoByte ? 0 : 1] += value;
1208
+ target[position$1++] = 0xc1;
1209
+ pack(twoByte ? -strLength : strLength);
1210
+ return
1211
+ }
1114
1212
  let headerSize;
1115
1213
  // first we estimate the header size, so we can write to the correct location
1116
1214
  if (strLength < 0x20) {
@@ -1409,53 +1507,55 @@
1409
1507
  target[objectOffset++ + start] = size >> 8;
1410
1508
  target[objectOffset + start] = size & 0xff;
1411
1509
  } :
1412
-
1413
- /* sharedStructures ? // For highly stable structures, using for-in can a little bit faster
1510
+ (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)
1414
1511
  (object, safePrototype) => {
1415
- let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null))
1416
- let objectOffset = position++ - start
1417
- let wroteKeys
1512
+ let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null));
1513
+ let objectOffset = position$1++ - start;
1514
+ let wroteKeys;
1418
1515
  for (let key in object) {
1419
1516
  if (safePrototype || object.hasOwnProperty(key)) {
1420
- nextTransition = transition[key]
1421
- if (!nextTransition) {
1422
- nextTransition = transition[key] = Object.create(null)
1423
- nextTransition.__keys__ = (transition.__keys__ || []).concat([key])
1424
- /*let keys = Object.keys(object)
1425
- if
1426
- let size = 0
1427
- let startBranch = transition.__keys__ ? transition.__keys__.length : 0
1428
- for (let i = 0, l = keys.length; i++) {
1429
- let key = keys[i]
1430
- size += key.length << 2
1431
- if (i >= startBranch) {
1432
- nextTransition = nextTransition[key] = Object.create(null)
1433
- nextTransition.__keys__ = keys.slice(0, i + 1)
1517
+ nextTransition = transition[key];
1518
+ if (nextTransition)
1519
+ transition = nextTransition;
1520
+ else {
1521
+ // record doesn't exist, create full new record and insert it
1522
+ let keys = Object.keys(object);
1523
+ let lastTransition = transition;
1524
+ transition = structures.transitions;
1525
+ let newTransitions = 0;
1526
+ for (let i = 0, l = keys.length; i < l; i++) {
1527
+ let key = keys[i];
1528
+ nextTransition = transition[key];
1529
+ if (!nextTransition) {
1530
+ nextTransition = transition[key] = Object.create(null);
1531
+ newTransitions++;
1434
1532
  }
1533
+ transition = nextTransition;
1435
1534
  }
1436
- makeRoom(position + size)
1437
- nextTransition = transition[key]
1438
- target.copy(target, )
1439
- objectOffset
1535
+ if (objectOffset + start + 1 == position$1) {
1536
+ // first key, so we don't need to insert, we can just write record directly
1537
+ position$1--;
1538
+ newRecord(transition, keys, newTransitions);
1539
+ } else // otherwise we need to insert the record, moving existing data after the record
1540
+ insertNewRecord(transition, keys, objectOffset, newTransitions);
1541
+ wroteKeys = true;
1542
+ transition = lastTransition[key];
1440
1543
  }
1441
- transition = nextTransition
1442
- pack(object[key])
1544
+ pack(object[key]);
1443
1545
  }
1444
1546
  }
1445
- let id = transition.id
1446
- if (!id) {
1447
- id = transition.id = structures.push(transition.__keys__) + 63
1448
- if (sharedStructures.onUpdate)
1449
- sharedStructures.onUpdate(id, transition.__keys__)
1547
+ if (!wroteKeys) {
1548
+ let recordId = transition[RECORD_SYMBOL];
1549
+ if (recordId)
1550
+ target[objectOffset + start] = recordId;
1551
+ else
1552
+ insertNewRecord(transition, Object.keys(object), objectOffset, 0);
1450
1553
  }
1451
- target[objectOffset + start] = id
1452
- }*/
1453
- (object) => {
1454
- let keys = Object.keys(object);
1554
+ } :
1555
+ (object, safePrototype) => {
1455
1556
  let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null));
1456
1557
  let newTransitions = 0;
1457
- for (let i = 0, l = keys.length; i < l; i++) {
1458
- let key = keys[i];
1558
+ for (let key in object) if (safePrototype || object.hasOwnProperty(key)) {
1459
1559
  nextTransition = transition[key];
1460
1560
  if (!nextTransition) {
1461
1561
  nextTransition = transition[key] = Object.create(null);
@@ -1471,57 +1571,12 @@
1471
1571
  } else
1472
1572
  target[position$1++] = recordId;
1473
1573
  } else {
1474
- recordId = structures.nextId;
1475
- if (!recordId)
1476
- recordId = 0x40;
1477
- if (recordId < sharedLimitId && this.shouldShareStructure && !this.shouldShareStructure(keys)) {
1478
- recordId = structures.nextOwnId;
1479
- if (!(recordId < maxStructureId))
1480
- recordId = sharedLimitId;
1481
- structures.nextOwnId = recordId + 1;
1482
- } else {
1483
- if (recordId >= maxStructureId)// cycle back around
1484
- recordId = sharedLimitId;
1485
- structures.nextId = recordId + 1;
1486
- }
1487
- let highByte = keys.highByte = recordId >= 0x60 && useTwoByteRecords ? (recordId - 0x60) >> 5 : -1;
1488
- transition[RECORD_SYMBOL] = recordId;
1489
- structures[recordId - 0x40] = keys;
1490
-
1491
- if (recordId < sharedLimitId) {
1492
- keys.isShared = true;
1493
- structures.sharedLength = recordId - 0x3f;
1494
- hasSharedUpdate = true;
1495
- if (highByte >= 0) {
1496
- target[position$1++] = (recordId & 0x1f) + 0x60;
1497
- target[position$1++] = highByte;
1498
- } else {
1499
- target[position$1++] = recordId;
1500
- }
1501
- } else {
1502
- if (highByte >= 0) {
1503
- target[position$1++] = 0xd5; // fixext 2
1504
- target[position$1++] = 0x72; // "r" record defintion extension type
1505
- target[position$1++] = (recordId & 0x1f) + 0x60;
1506
- target[position$1++] = highByte;
1507
- } else {
1508
- target[position$1++] = 0xd4; // fixext 1
1509
- target[position$1++] = 0x72; // "r" record defintion extension type
1510
- target[position$1++] = recordId;
1511
- }
1512
-
1513
- if (newTransitions)
1514
- transitionsCount += serializationsSinceTransitionRebuild * newTransitions;
1515
- // record the removal of the id, we can maintain our shared structure
1516
- if (recordIdsToRemove.length >= maxOwnStructures)
1517
- recordIdsToRemove.shift()[RECORD_SYMBOL] = 0; // we are cycling back through, and have to remove old ones
1518
- recordIdsToRemove.push(transition);
1519
- pack(keys);
1520
- }
1574
+ newRecord(transition, transition.__keys__ || Object.keys(object), newTransitions);
1521
1575
  }
1522
1576
  // now write the values
1523
- for (let i = 0, l = keys.length; i < l; i++)
1524
- pack(object[keys[i]]);
1577
+ for (let key in object)
1578
+ if (safePrototype || object.hasOwnProperty(key))
1579
+ pack(object[key]);
1525
1580
  };
1526
1581
  const makeRoom = (end) => {
1527
1582
  let newSize;
@@ -1544,6 +1599,86 @@
1544
1599
  safeEnd = newBuffer.length - 10;
1545
1600
  return target = newBuffer
1546
1601
  };
1602
+ const newRecord = (transition, keys, newTransitions) => {
1603
+ let recordId = structures.nextId;
1604
+ if (!recordId)
1605
+ recordId = 0x40;
1606
+ if (recordId < sharedLimitId && this.shouldShareStructure && !this.shouldShareStructure(keys)) {
1607
+ recordId = structures.nextOwnId;
1608
+ if (!(recordId < maxStructureId))
1609
+ recordId = sharedLimitId;
1610
+ structures.nextOwnId = recordId + 1;
1611
+ } else {
1612
+ if (recordId >= maxStructureId)// cycle back around
1613
+ recordId = sharedLimitId;
1614
+ structures.nextId = recordId + 1;
1615
+ }
1616
+ let highByte = keys.highByte = recordId >= 0x60 && useTwoByteRecords ? (recordId - 0x60) >> 5 : -1;
1617
+ transition[RECORD_SYMBOL] = recordId;
1618
+ transition.__keys__ = keys;
1619
+ structures[recordId - 0x40] = keys;
1620
+
1621
+ if (recordId < sharedLimitId) {
1622
+ keys.isShared = true;
1623
+ structures.sharedLength = recordId - 0x3f;
1624
+ hasSharedUpdate = true;
1625
+ if (highByte >= 0) {
1626
+ target[position$1++] = (recordId & 0x1f) + 0x60;
1627
+ target[position$1++] = highByte;
1628
+ } else {
1629
+ target[position$1++] = recordId;
1630
+ }
1631
+ } else {
1632
+ if (highByte >= 0) {
1633
+ target[position$1++] = 0xd5; // fixext 2
1634
+ target[position$1++] = 0x72; // "r" record defintion extension type
1635
+ target[position$1++] = (recordId & 0x1f) + 0x60;
1636
+ target[position$1++] = highByte;
1637
+ } else {
1638
+ target[position$1++] = 0xd4; // fixext 1
1639
+ target[position$1++] = 0x72; // "r" record defintion extension type
1640
+ target[position$1++] = recordId;
1641
+ }
1642
+
1643
+ if (newTransitions)
1644
+ transitionsCount += serializationsSinceTransitionRebuild * newTransitions;
1645
+ // record the removal of the id, we can maintain our shared structure
1646
+ if (recordIdsToRemove.length >= maxOwnStructures)
1647
+ recordIdsToRemove.shift()[RECORD_SYMBOL] = 0; // we are cycling back through, and have to remove old ones
1648
+ recordIdsToRemove.push(transition);
1649
+ pack(keys);
1650
+ }
1651
+ };
1652
+ const insertNewRecord = (transition, keys, insertionOffset, newTransitions) => {
1653
+ let mainTarget = target;
1654
+ let mainPosition = position$1;
1655
+ let mainSafeEnd = safeEnd;
1656
+ let mainStart = start;
1657
+ target = keysTarget;
1658
+ position$1 = 0;
1659
+ start = 0;
1660
+ if (!target)
1661
+ keysTarget = target = new ByteArrayAllocate(8192);
1662
+ safeEnd = target.length - 10;
1663
+ newRecord(transition, keys, newTransitions);
1664
+ keysTarget = target;
1665
+ let keysPosition = position$1;
1666
+ target = mainTarget;
1667
+ position$1 = mainPosition;
1668
+ safeEnd = mainSafeEnd;
1669
+ start = mainStart;
1670
+ if (keysPosition > 1) {
1671
+ let newEnd = position$1 + keysPosition - 1;
1672
+ if (newEnd > safeEnd)
1673
+ makeRoom(newEnd);
1674
+ let insertionPosition = insertionOffset + start;
1675
+ target.copyWithin(insertionPosition + keysPosition, insertionPosition + 1, position$1);
1676
+ target.set(keysTarget.slice(0, keysPosition), insertionPosition);
1677
+ position$1 = newEnd;
1678
+ } else {
1679
+ target[insertionOffset + start] = keysTarget[0];
1680
+ }
1681
+ };
1547
1682
  }
1548
1683
  useBuffer(buffer) {
1549
1684
  // this means we are finished using our own buffer and we can write over it safely
@@ -1551,11 +1686,15 @@
1551
1686
  targetView = new DataView(target.buffer, target.byteOffset, target.byteLength);
1552
1687
  position$1 = 0;
1553
1688
  }
1689
+ clearSharedData() {
1690
+ if (this.structures)
1691
+ this.structures = [];
1692
+ }
1554
1693
  }
1555
1694
 
1556
1695
  extensionClasses = [ Date, Set, Error, RegExp, ArrayBuffer, Object.getPrototypeOf(Uint8Array.prototype).constructor /*TypedArray*/, C1Type ];
1557
1696
  extensions = [{
1558
- pack(date, allocateForWrite) {
1697
+ pack(date, allocateForWrite, pack) {
1559
1698
  let seconds = date.getTime() / 1000;
1560
1699
  if ((this.useTimestamp32 || date.getMilliseconds() === 0) && seconds >= 0 && seconds < 0x100000000) {
1561
1700
  // Timestamp 32
@@ -1570,6 +1709,16 @@
1570
1709
  target[position++] = 0xff;
1571
1710
  targetView.setUint32(position, date.getMilliseconds() * 4000000 + ((seconds / 1000 / 0x100000000) >> 0));
1572
1711
  targetView.setUint32(position + 4, seconds);
1712
+ } else if (isNaN(seconds)) {
1713
+ if (this.onInvalidDate) {
1714
+ allocateForWrite(0);
1715
+ return pack(this.onInvalidDate())
1716
+ }
1717
+ // Intentionally invalid timestamp
1718
+ let { target, targetView, position} = allocateForWrite(3);
1719
+ target[position++] = 0xd4;
1720
+ target[position++] = 0xff;
1721
+ target[position++] = 0xff;
1573
1722
  } else {
1574
1723
  // Timestamp 96
1575
1724
  let { target, targetView, position} = allocateForWrite(15);
@@ -1738,6 +1887,14 @@
1738
1887
  return serialized
1739
1888
  }
1740
1889
 
1890
+ function writeBundles(start, pack) {
1891
+ targetView.setUint32(bundledStrings$1.position + start, position$1 - bundledStrings$1.position - start);
1892
+ let writeStrings = bundledStrings$1;
1893
+ bundledStrings$1 = null;
1894
+ pack(writeStrings[0]);
1895
+ pack(writeStrings[1]);
1896
+ }
1897
+
1741
1898
  function addExtension$1(extension) {
1742
1899
  if (extension.Class) {
1743
1900
  if (!extension.pack && !extension.write)
@@ -1755,7 +1912,8 @@
1755
1912
  const encode = defaultPackr.pack;
1756
1913
  const Encoder = Packr;
1757
1914
  const { NEVER, ALWAYS, DECIMAL_ROUND, DECIMAL_FIT } = FLOAT32_OPTIONS;
1758
- const REUSE_BUFFER_MODE = 1000;
1915
+ const REUSE_BUFFER_MODE = 512;
1916
+ const RESET_BUFFER_MODE = 1024;
1759
1917
 
1760
1918
  /**
1761
1919
  * Given an Iterable first argument, returns an Iterable where each value is packed as a Buffer