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 +16 -2
- package/dist/index.js +278 -100
- package/dist/index.min.js +68 -99
- package/dist/node.cjs +281 -103
- package/dist/str.cjs +100 -0
- package/dist/test.js +44 -3
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/node-index.js +2 -4
- package/pack.js +191 -85
- package/package.json +7 -1
- package/unpack.d.ts +3 -0
- package/unpack.js +84 -11
- package/.vscode/launch.json +0 -23
- package/tests/benchmark-stream.cjs +0 -282
- package/tests/benchmark.cjs +0 -199
- package/tests/example.json +0 -52
- package/tests/example2.json +0 -26
- package/tests/example3.json +0 -22
- package/tests/example4.json +0 -1
- package/tests/example5.json +0 -12
- package/tests/floats.json +0 -1
- package/tests/index.html +0 -28
- package/tests/sample-large.json +0 -231
- package/tests/strings2.json +0 -1
- package/tests/test-compatibility.cjs +0 -64
- package/tests/test-incomplete.js +0 -41
- package/tests/test-node-iterators.js +0 -72
- package/tests/test-node-stream.js +0 -76
- package/tests/test.js +0 -640
package/README.md
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# msgpackr
|
|
2
|
-
[](LICENSE)
|
|
3
2
|
[](https://www.npmjs.org/package/msgpackr)
|
|
3
|
+
[](https://www.npmjs.org/package/msgpackr)
|
|
4
4
|
[](benchmark.md)
|
|
5
5
|
[](benchmark.md)
|
|
6
6
|
[](README.md)
|
|
7
7
|
[](README.md)
|
|
8
|
+
[](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
|
|
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
|
-
|
|
62
|
-
|
|
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:
|
|
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.
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
let
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
nextTransition
|
|
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
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
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
|
-
|
|
1428
|
-
pack(object[key])
|
|
1549
|
+
pack(object[key]);
|
|
1429
1550
|
}
|
|
1430
1551
|
}
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
1510
|
-
|
|
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),
|
|
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 =
|
|
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;
|