msgpackr 1.4.7 → 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
@@ -1,11 +1,13 @@
1
1
  # msgpackr
2
- [![license](https://img.shields.io/badge/license-MIT-brightgreen)](LICENSE)
3
2
  [![npm version](https://img.shields.io/npm/v/msgpackr.svg?style=flat-square)](https://www.npmjs.org/package/msgpackr)
3
+ [![npm version](https://img.shields.io/npm/dw/msgpackr)](https://www.npmjs.org/package/msgpackr)
4
4
  [![encode](https://img.shields.io/badge/encode-1.5GB%2Fs-yellow)](benchmark.md)
5
5
  [![decode](https://img.shields.io/badge/decode-2GB%2Fs-yellow)](benchmark.md)
6
6
  [![types](https://img.shields.io/npm/types/msgpackr)](README.md)
7
7
  [![module](https://img.shields.io/badge/module-ESM%2FCJS-blue)](README.md)
8
+ [![license](https://img.shields.io/badge/license-MIT-brightgreen)](LICENSE)
8
9
 
10
+ <img align="right" src="./assets/performance.png" width="380"/>
9
11
 
10
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.
11
13
 
@@ -52,7 +54,7 @@ receivingStream.on('data', (data) => {
52
54
  The `PackrStream` and `UnpackrStream` instances will have also the record structure extension enabled by default (see below).
53
55
 
54
56
  ## Deno Usage
55
- 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.
56
58
 
57
59
  ## Browser Usage
58
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:
@@ -161,11 +163,14 @@ The following options properties can be provided to the Packr or Unpackr constru
161
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.
162
164
  * `useFloat32` - This will enable msgpackr to encode non-integer numbers as `float32`. See next section for possible values.
163
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%).
164
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).
165
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)`).
166
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`.
167
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).
168
171
  * `encodeUndefinedAsNil` - Encodes a value of `undefined` as a MessagePack `nil`, the same as a `null`.
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).
169
174
 
170
175
  ### 32-bit Float Options
171
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:
@@ -183,6 +188,15 @@ Note, that the performance is decreased with decimal rounding by about 20-25%, a
183
188
  In addition, msgpackr exports a `roundFloat32(number)` function that can be used to round floating point numbers to the maximum significant decimal digits that can be stored in 32-bit float, just as DECIMAL_ROUND does when decoding. This can be useful for determining how a number will be decoded prior to encoding it.
184
189
 
185
190
  ## Performance
191
+ ### Native Acceleration
192
+ Msgpackr employs an optional native node-addon to accelerate the parsing of strings. This should be automatically installed and utilized on NodeJS. However, you can verify this by checking the `isNativeAccelerationEnabled` property that is exported from msgpackr. If this is `false`, the `msgpackr-extract` package may not have been properly installed, and you may want to verify that it is installed correctly:
193
+ ```js
194
+ import { isNativeAccelerationEnabled } from 'msgpackr'
195
+ if (!isNativeAccelerationEnabled)
196
+ console.warn('Native acceleration not enabled, verify that install finished properly')
197
+ ```
198
+
199
+ ### Benchmarks
186
200
  Msgpackr is fast. Really fast. Here is comparison with the next fastest JS projects using the benchmark tool from `msgpack-lite` (and the sample data is from some clinical research data we use that has a good mix of different value types and structures). It also includes comparison to V8 native JSON functionality, and JavaScript Avro (`avsc`, a very optimized Avro implementation):
187
201
 
188
202
  operation | op | ms | op/s
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,12 +55,21 @@
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
60
62
  // new ones
61
- dataView = source.dataView || (source.dataView = new DataView(source.buffer, source.byteOffset, source.byteLength));
62
- if (this) {
63
+ try {
64
+ dataView = source.dataView || (source.dataView = new DataView(source.buffer, source.byteOffset, source.byteLength));
65
+ } catch(error) {
66
+ // if it doesn't have a buffer, maybe it is the wrong type of object
67
+ src = null;
68
+ if (source instanceof Uint8Array)
69
+ throw error
70
+ throw new Error('Source must be a Uint8Array or Buffer but was a ' + ((source && typeof source == 'object') ? source.constructor.name : typeof source))
71
+ }
72
+ if (this instanceof Unpackr) {
63
73
  currentUnpackr = this;
64
74
  if (this.structures) {
65
75
  currentStructures = this.structures;
@@ -142,6 +152,9 @@
142
152
  currentStructures.length = sharedLength;
143
153
  }
144
154
  let result = read();
155
+ if (bundledStrings) // bundled strings to skip past
156
+ position = bundledStrings.postBundlePosition;
157
+
145
158
  if (position == srcEnd) {
146
159
  // finished reading this source, cleanup references
147
160
  if (currentStructures.restoreStructures)
@@ -236,7 +249,15 @@
236
249
  let value;
237
250
  switch (token) {
238
251
  case 0xc0: return null
239
- 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
240
261
  case 0xc2: return false
241
262
  case 0xc3: return true
242
263
  case 0xc4:
@@ -291,10 +312,11 @@
291
312
  position += 4;
292
313
  return value
293
314
  case 0xcf:
294
- if (currentUnpackr.uint64AsNumber)
295
- return src[position++] * 0x100000000000000 + src[position++] * 0x1000000000000 + src[position++] * 0x10000000000 + src[position++] * 0x100000000 +
296
- src[position++] * 0x1000000 + (src[position++] << 16) + (src[position++] << 8) + src[position++]
297
- value = dataView.getBigUint64(position);
315
+ if (currentUnpackr.int64AsNumber) {
316
+ value = dataView.getUint32(position) * 0x100000000;
317
+ value += dataView.getUint32(position + 4);
318
+ } else
319
+ value = dataView.getBigUint64(position);
298
320
  position += 8;
299
321
  return value
300
322
 
@@ -310,7 +332,11 @@
310
332
  position += 4;
311
333
  return value
312
334
  case 0xd3:
313
- value = dataView.getBigInt64(position);
335
+ if (currentUnpackr.int64AsNumber) {
336
+ value = dataView.getInt32(position) * 0x100000000;
337
+ value += dataView.getUint32(position + 4);
338
+ } else
339
+ value = dataView.getBigInt64(position);
314
340
  position += 8;
315
341
  return value
316
342
 
@@ -459,6 +485,7 @@
459
485
  var readString8 = readStringJS;
460
486
  var readString16 = readStringJS;
461
487
  var readString32 = readStringJS;
488
+ let isNativeAccelerationEnabled = false;
462
489
  function readStringJS(length) {
463
490
  let result;
464
491
  if (length < 16) {
@@ -691,6 +718,36 @@
691
718
  }
692
719
  }
693
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
+
694
751
  function readBin(length) {
695
752
  return currentUnpackr.copyBuffers ?
696
753
  // specifically use the copying slice (not the node one)
@@ -842,6 +899,19 @@
842
899
  let data = read();
843
900
  return new RegExp(data[0], data[1])
844
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
+ };
845
915
 
846
916
  currentExtensions[0xff] = (data) => {
847
917
  // 32-bit date extension
@@ -856,7 +926,7 @@
856
926
  ((data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3]) / 1000000 +
857
927
  (((data[4] & 0x80) ? -0x1000000000000 : 0) + data[6] * 0x10000000000 + data[7] * 0x100000000 + data[8] * 0x1000000 + (data[9] << 16) + (data[10] << 8) + data[11]) * 1000)
858
928
  else
859
- throw new Error('Invalid timestamp length')
929
+ return new Date('invalid')
860
930
  }; // notepack defines extension 0 to mean undefined, so use that as the default here
861
931
  // registration of bulk record definition?
862
932
  // currentExtensions[0x52] = () =>
@@ -868,6 +938,7 @@
868
938
  let savedSrcStringEnd = srcStringEnd;
869
939
  let savedSrcString = srcString;
870
940
  let savedReferenceMap = referenceMap;
941
+ let savedBundledStrings = bundledStrings;
871
942
 
872
943
  // TODO: We may need to revisit this if we do more external calls to user code (since it could be slow)
873
944
  let savedSrc = new Uint8Array(src.slice(0, srcEnd)); // we copy the data in case it changes while external data is processed
@@ -882,6 +953,7 @@
882
953
  srcStringEnd = savedSrcStringEnd;
883
954
  srcString = savedSrcString;
884
955
  referenceMap = savedReferenceMap;
956
+ bundledStrings = savedBundledStrings;
885
957
  src = savedSrc;
886
958
  sequentialMode = savedSequentialMode;
887
959
  currentStructures = savedStructures;
@@ -935,10 +1007,13 @@
935
1007
  const ByteArrayAllocate = hasNodeBuffer ? Buffer.allocUnsafeSlow : Uint8Array;
936
1008
  const ByteArray = hasNodeBuffer ? Buffer : Uint8Array;
937
1009
  const MAX_BUFFER_SIZE = hasNodeBuffer ? 0x100000000 : 0x7fd00000;
938
- let target;
1010
+ let target, keysTarget;
939
1011
  let targetView;
940
1012
  let position$1 = 0;
941
1013
  let safeEnd;
1014
+ let bundledStrings$1 = null;
1015
+ const MAX_BUNDLE_SIZE = 0xf000;
1016
+ const hasNonLatin = /[\u0080-\uFFFF]/;
942
1017
  const RECORD_SYMBOL = Symbol('record-id');
943
1018
  class Packr extends Unpackr {
944
1019
  constructor(options) {
@@ -1000,6 +1075,11 @@
1000
1075
  position$1 = (position$1 + 7) & 0x7ffffff8; // Word align to make any future copying of this buffer faster
1001
1076
  start = position$1;
1002
1077
  referenceMap = packr.structuredClone ? new Map() : null;
1078
+ if (packr.bundleStrings && typeof value !== 'string') {
1079
+ bundledStrings$1 = [];
1080
+ bundledStrings$1.size = Infinity; // force a new bundle start on first string
1081
+ } else
1082
+ bundledStrings$1 = null;
1003
1083
  sharedStructures = packr.structures;
1004
1084
  if (sharedStructures) {
1005
1085
  if (sharedStructures.uninitialized)
@@ -1035,9 +1115,12 @@
1035
1115
  }
1036
1116
  if (hasSharedUpdate)
1037
1117
  hasSharedUpdate = false;
1038
- structures = sharedStructures || [];
1118
+ structures = sharedStructures || (packr.structures = []);
1039
1119
  try {
1040
1120
  pack(value);
1121
+ if (bundledStrings$1) {
1122
+ writeBundles(start, pack);
1123
+ }
1041
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
1042
1125
  if (referenceMap && referenceMap.idsToInsert) {
1043
1126
  position$1 += referenceMap.idsToInsert.length * 6;
@@ -1048,7 +1131,7 @@
1048
1131
  referenceMap = null;
1049
1132
  return serialized
1050
1133
  }
1051
- if (encodeOptions === REUSE_BUFFER_MODE) {
1134
+ if (encodeOptions & REUSE_BUFFER_MODE) {
1052
1135
  target.start = start;
1053
1136
  target.end = position$1;
1054
1137
  return target
@@ -1058,6 +1141,8 @@
1058
1141
  if (sharedStructures) {
1059
1142
  if (serializationsSinceTransitionRebuild < 10)
1060
1143
  serializationsSinceTransitionRebuild++;
1144
+ if (sharedStructures.length > maxSharedStructures)
1145
+ sharedStructures.length = maxSharedStructures;
1061
1146
  if (transitionsCount > 10000) {
1062
1147
  // force a rebuild occasionally after a lot of transitions so it can get cleaned up
1063
1148
  sharedStructures.transitions = null;
@@ -1087,6 +1172,8 @@
1087
1172
  return returnBuffer
1088
1173
  }
1089
1174
  }
1175
+ if (encodeOptions & RESET_BUFFER_MODE)
1176
+ position$1 = start;
1090
1177
  }
1091
1178
  };
1092
1179
  const pack = (value) => {
@@ -1097,6 +1184,36 @@
1097
1184
  var length;
1098
1185
  if (type === 'string') {
1099
1186
  let strLength = value.length;
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
+ }
1211
+ let twoByte = hasNonLatin.test(value);
1212
+ bundledStrings$1[twoByte ? 0 : 1] += value;
1213
+ target[position$1++] = 0xc1;
1214
+ pack(twoByte ? -strLength : strLength);
1215
+ return
1216
+ }
1100
1217
  let headerSize;
1101
1218
  // first we estimate the header size, so we can write to the correct location
1102
1219
  if (strLength < 0x20) {
@@ -1395,53 +1512,55 @@
1395
1512
  target[objectOffset++ + start] = size >> 8;
1396
1513
  target[objectOffset + start] = size & 0xff;
1397
1514
  } :
1398
-
1399
- /* 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)
1400
1516
  (object, safePrototype) => {
1401
- let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null))
1402
- let objectOffset = position++ - start
1403
- let wroteKeys
1517
+ let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null));
1518
+ let objectOffset = position$1++ - start;
1519
+ let wroteKeys;
1404
1520
  for (let key in object) {
1405
1521
  if (safePrototype || object.hasOwnProperty(key)) {
1406
- nextTransition = transition[key]
1407
- if (!nextTransition) {
1408
- nextTransition = transition[key] = Object.create(null)
1409
- nextTransition.__keys__ = (transition.__keys__ || []).concat([key])
1410
- /*let keys = Object.keys(object)
1411
- if
1412
- let size = 0
1413
- let startBranch = transition.__keys__ ? transition.__keys__.length : 0
1414
- for (let i = 0, l = keys.length; i++) {
1415
- let key = keys[i]
1416
- size += key.length << 2
1417
- if (i >= startBranch) {
1418
- nextTransition = nextTransition[key] = Object.create(null)
1419
- 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++;
1420
1537
  }
1538
+ transition = nextTransition;
1421
1539
  }
1422
- makeRoom(position + size)
1423
- nextTransition = transition[key]
1424
- target.copy(target, )
1425
- 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];
1426
1548
  }
1427
- transition = nextTransition
1428
- pack(object[key])
1549
+ pack(object[key]);
1429
1550
  }
1430
1551
  }
1431
- let id = transition.id
1432
- if (!id) {
1433
- id = transition.id = structures.push(transition.__keys__) + 63
1434
- if (sharedStructures.onUpdate)
1435
- 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);
1436
1558
  }
1437
- target[objectOffset + start] = id
1438
- }*/
1439
- (object) => {
1440
- let keys = Object.keys(object);
1559
+ } :
1560
+ (object, safePrototype) => {
1441
1561
  let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null));
1442
1562
  let newTransitions = 0;
1443
- for (let i = 0, l = keys.length; i < l; i++) {
1444
- let key = keys[i];
1563
+ for (let key in object) if (safePrototype || object.hasOwnProperty(key)) {
1445
1564
  nextTransition = transition[key];
1446
1565
  if (!nextTransition) {
1447
1566
  nextTransition = transition[key] = Object.create(null);
@@ -1457,57 +1576,12 @@
1457
1576
  } else
1458
1577
  target[position$1++] = recordId;
1459
1578
  } else {
1460
- recordId = structures.nextId;
1461
- if (!recordId)
1462
- recordId = 0x40;
1463
- if (recordId < sharedLimitId && this.shouldShareStructure && !this.shouldShareStructure(keys)) {
1464
- recordId = structures.nextOwnId;
1465
- if (!(recordId < maxStructureId))
1466
- recordId = sharedLimitId;
1467
- structures.nextOwnId = recordId + 1;
1468
- } else {
1469
- if (recordId >= maxStructureId)// cycle back around
1470
- recordId = sharedLimitId;
1471
- structures.nextId = recordId + 1;
1472
- }
1473
- let highByte = keys.highByte = recordId >= 0x60 && useTwoByteRecords ? (recordId - 0x60) >> 5 : -1;
1474
- transition[RECORD_SYMBOL] = recordId;
1475
- structures[recordId - 0x40] = keys;
1476
-
1477
- if (recordId < sharedLimitId) {
1478
- keys.isShared = true;
1479
- structures.sharedLength = recordId - 0x3f;
1480
- hasSharedUpdate = true;
1481
- if (highByte >= 0) {
1482
- target[position$1++] = (recordId & 0x1f) + 0x60;
1483
- target[position$1++] = highByte;
1484
- } else {
1485
- target[position$1++] = recordId;
1486
- }
1487
- } else {
1488
- if (highByte >= 0) {
1489
- target[position$1++] = 0xd5; // fixext 2
1490
- target[position$1++] = 0x72; // "r" record defintion extension type
1491
- target[position$1++] = (recordId & 0x1f) + 0x60;
1492
- target[position$1++] = highByte;
1493
- } else {
1494
- target[position$1++] = 0xd4; // fixext 1
1495
- target[position$1++] = 0x72; // "r" record defintion extension type
1496
- target[position$1++] = recordId;
1497
- }
1498
-
1499
- if (newTransitions)
1500
- transitionsCount += serializationsSinceTransitionRebuild * newTransitions;
1501
- // record the removal of the id, we can maintain our shared structure
1502
- if (recordIdsToRemove.length >= maxOwnStructures)
1503
- recordIdsToRemove.shift()[RECORD_SYMBOL] = 0; // we are cycling back through, and have to remove old ones
1504
- recordIdsToRemove.push(transition);
1505
- pack(keys);
1506
- }
1579
+ newRecord(transition, transition.__keys__ || Object.keys(object), newTransitions);
1507
1580
  }
1508
1581
  // now write the values
1509
- for (let i = 0, l = keys.length; i < l; i++)
1510
- pack(object[keys[i]]);
1582
+ for (let key in object)
1583
+ if (safePrototype || object.hasOwnProperty(key))
1584
+ pack(object[key]);
1511
1585
  };
1512
1586
  const makeRoom = (end) => {
1513
1587
  let newSize;
@@ -1516,7 +1590,7 @@
1516
1590
  if ((end - start) > MAX_BUFFER_SIZE)
1517
1591
  throw new Error('Packed buffer would be larger than maximum buffer size')
1518
1592
  newSize = Math.min(MAX_BUFFER_SIZE,
1519
- Math.round(Math.max((end - start) * (end > 0x4000000 ? 1.25 : 2), 0x1000000) / 0x1000) * 0x1000);
1593
+ Math.round(Math.max((end - start) * (end > 0x4000000 ? 1.25 : 2), 0x400000) / 0x1000) * 0x1000);
1520
1594
  } else // faster handling for smaller buffers
1521
1595
  newSize = ((Math.max((end - start) << 2, target.length - 1) >> 12) + 1) << 12;
1522
1596
  let newBuffer = new ByteArrayAllocate(newSize);
@@ -1530,6 +1604,86 @@
1530
1604
  safeEnd = newBuffer.length - 10;
1531
1605
  return target = newBuffer
1532
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
+ };
1533
1687
  }
1534
1688
  useBuffer(buffer) {
1535
1689
  // this means we are finished using our own buffer and we can write over it safely
@@ -1537,11 +1691,15 @@
1537
1691
  targetView = new DataView(target.buffer, target.byteOffset, target.byteLength);
1538
1692
  position$1 = 0;
1539
1693
  }
1694
+ clearSharedData() {
1695
+ if (this.structures)
1696
+ this.structures = [];
1697
+ }
1540
1698
  }
1541
1699
 
1542
1700
  extensionClasses = [ Date, Set, Error, RegExp, ArrayBuffer, Object.getPrototypeOf(Uint8Array.prototype).constructor /*TypedArray*/, C1Type ];
1543
1701
  extensions = [{
1544
- pack(date, allocateForWrite) {
1702
+ pack(date, allocateForWrite, pack) {
1545
1703
  let seconds = date.getTime() / 1000;
1546
1704
  if ((this.useTimestamp32 || date.getMilliseconds() === 0) && seconds >= 0 && seconds < 0x100000000) {
1547
1705
  // Timestamp 32
@@ -1556,6 +1714,16 @@
1556
1714
  target[position++] = 0xff;
1557
1715
  targetView.setUint32(position, date.getMilliseconds() * 4000000 + ((seconds / 1000 / 0x100000000) >> 0));
1558
1716
  targetView.setUint32(position + 4, seconds);
1717
+ } else if (isNaN(seconds)) {
1718
+ if (this.onInvalidDate) {
1719
+ allocateForWrite(0);
1720
+ return pack(this.onInvalidDate())
1721
+ }
1722
+ // Intentionally invalid timestamp
1723
+ let { target, targetView, position} = allocateForWrite(3);
1724
+ target[position++] = 0xd4;
1725
+ target[position++] = 0xff;
1726
+ target[position++] = 0xff;
1559
1727
  } else {
1560
1728
  // Timestamp 96
1561
1729
  let { target, targetView, position} = allocateForWrite(15);
@@ -1724,6 +1892,14 @@
1724
1892
  return serialized
1725
1893
  }
1726
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
+
1727
1903
  function addExtension$1(extension) {
1728
1904
  if (extension.Class) {
1729
1905
  if (!extension.pack && !extension.write)
@@ -1741,7 +1917,8 @@
1741
1917
  const encode = defaultPackr.pack;
1742
1918
  const Encoder = Packr;
1743
1919
  const { NEVER, ALWAYS, DECIMAL_ROUND, DECIMAL_FIT } = FLOAT32_OPTIONS;
1744
- const REUSE_BUFFER_MODE = 1000;
1920
+ const REUSE_BUFFER_MODE = 512;
1921
+ const RESET_BUFFER_MODE = 1024;
1745
1922
 
1746
1923
  /**
1747
1924
  * Given an Iterable first argument, returns an Iterable where each value is packed as a Buffer
@@ -1848,6 +2025,7 @@
1848
2025
  exports.decodeIter = decodeIter;
1849
2026
  exports.encode = encode;
1850
2027
  exports.encodeIter = encodeIter;
2028
+ exports.isNativeAccelerationEnabled = isNativeAccelerationEnabled;
1851
2029
  exports.mapsAsObjects = mapsAsObjects;
1852
2030
  exports.pack = pack;
1853
2031
  exports.roundFloat32 = roundFloat32;