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 +4 -1
- package/dist/index.js +271 -113
- package/dist/index.min.js +66 -100
- package/dist/node.cjs +273 -113
- package/dist/str.cjs +100 -0
- package/dist/test.js +33 -2
- package/pack.js +206 -105
- package/package.json +7 -1
- package/unpack.d.ts +2 -0
- package/unpack.js +62 -2
- 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 -650
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
[](README.md)
|
|
8
8
|
[](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
|
|
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:
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
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 &&
|
|
1024
|
-
throw new Error('Shared structures is larger than maximum shared structures, try increasing maxSharedStructures to ' +
|
|
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 (!
|
|
1091
|
+
if (!structures.transitions) {
|
|
1027
1092
|
// rebuild our structure transitions
|
|
1028
|
-
|
|
1093
|
+
structures.transitions = Object.create(null);
|
|
1029
1094
|
for (let i = 0; i < sharedLength; i++) {
|
|
1030
|
-
let keys =
|
|
1095
|
+
let keys = structures[i];
|
|
1031
1096
|
if (!keys)
|
|
1032
1097
|
continue
|
|
1033
|
-
let nextTransition, transition =
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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(
|
|
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 (
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
let
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
nextTransition
|
|
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
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
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
|
-
|
|
1442
|
-
pack(object[key])
|
|
1544
|
+
pack(object[key]);
|
|
1443
1545
|
}
|
|
1444
1546
|
}
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
1524
|
-
|
|
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 =
|
|
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
|