msgpackr 1.5.2 → 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 +2 -1
- package/dist/index.js +206 -108
- package/dist/index.min.js +66 -100
- package/dist/node.cjs +208 -108
- package/dist/test.js +20 -2
- package/pack.js +162 -93
- package/package.json +1 -1
- package/unpack.js +41 -9
package/pack.js
CHANGED
|
@@ -9,11 +9,12 @@ const hasNodeBuffer = typeof Buffer !== 'undefined'
|
|
|
9
9
|
const ByteArrayAllocate = hasNodeBuffer ? Buffer.allocUnsafeSlow : Uint8Array
|
|
10
10
|
const ByteArray = hasNodeBuffer ? Buffer : Uint8Array
|
|
11
11
|
const MAX_BUFFER_SIZE = hasNodeBuffer ? 0x100000000 : 0x7fd00000
|
|
12
|
-
let target
|
|
12
|
+
let target, keysTarget
|
|
13
13
|
let targetView
|
|
14
14
|
let position = 0
|
|
15
15
|
let safeEnd
|
|
16
16
|
let bundledStrings = null
|
|
17
|
+
const MAX_BUNDLE_SIZE = 0xf000
|
|
17
18
|
const hasNonLatin = /[\u0080-\uFFFF]/
|
|
18
19
|
const RECORD_SYMBOL = Symbol('record-id')
|
|
19
20
|
export class Packr extends Unpackr {
|
|
@@ -77,12 +78,9 @@ export class Packr extends Unpackr {
|
|
|
77
78
|
position = (position + 7) & 0x7ffffff8 // Word align to make any future copying of this buffer faster
|
|
78
79
|
start = position
|
|
79
80
|
referenceMap = packr.structuredClone ? new Map() : null
|
|
80
|
-
if (packr.bundleStrings) {
|
|
81
|
-
bundledStrings = [
|
|
82
|
-
|
|
83
|
-
target[position++] = 0x62 // 'b'
|
|
84
|
-
bundledStrings.position = position - start
|
|
85
|
-
position += 4
|
|
81
|
+
if (packr.bundleStrings && typeof value !== 'string') {
|
|
82
|
+
bundledStrings = []
|
|
83
|
+
bundledStrings.size = Infinity // force a new bundle start on first string
|
|
86
84
|
} else
|
|
87
85
|
bundledStrings = null
|
|
88
86
|
sharedStructures = packr.structures
|
|
@@ -120,15 +118,11 @@ export class Packr extends Unpackr {
|
|
|
120
118
|
}
|
|
121
119
|
if (hasSharedUpdate)
|
|
122
120
|
hasSharedUpdate = false
|
|
123
|
-
structures = sharedStructures || []
|
|
121
|
+
structures = sharedStructures || (packr.structures = [])
|
|
124
122
|
try {
|
|
125
123
|
pack(value)
|
|
126
124
|
if (bundledStrings) {
|
|
127
|
-
|
|
128
|
-
let writeStrings = bundledStrings
|
|
129
|
-
bundledStrings = null
|
|
130
|
-
pack(writeStrings[0])
|
|
131
|
-
pack(writeStrings[1])
|
|
125
|
+
writeBundles(start, pack)
|
|
132
126
|
}
|
|
133
127
|
packr.offset = position // update the offset so next serialization doesn't write over our buffer, but can continue writing to same buffer sequentially
|
|
134
128
|
if (referenceMap && referenceMap.idsToInsert) {
|
|
@@ -150,6 +144,8 @@ export class Packr extends Unpackr {
|
|
|
150
144
|
if (sharedStructures) {
|
|
151
145
|
if (serializationsSinceTransitionRebuild < 10)
|
|
152
146
|
serializationsSinceTransitionRebuild++
|
|
147
|
+
if (sharedStructures.length > maxSharedStructures)
|
|
148
|
+
sharedStructures.length = maxSharedStructures
|
|
153
149
|
if (transitionsCount > 10000) {
|
|
154
150
|
// force a rebuild occasionally after a lot of transitions so it can get cleaned up
|
|
155
151
|
sharedStructures.transitions = null
|
|
@@ -191,7 +187,30 @@ export class Packr extends Unpackr {
|
|
|
191
187
|
var length
|
|
192
188
|
if (type === 'string') {
|
|
193
189
|
let strLength = value.length
|
|
194
|
-
if (bundledStrings && strLength >=
|
|
190
|
+
if (bundledStrings && strLength >= 4 && strLength < 0x1000) {
|
|
191
|
+
if ((bundledStrings.size += strLength) > MAX_BUNDLE_SIZE) {
|
|
192
|
+
let extStart
|
|
193
|
+
let maxBytes = (bundledStrings[0] ? bundledStrings[0].length * 3 + bundledStrings[1].length : 0) + 10
|
|
194
|
+
if (position + maxBytes > safeEnd)
|
|
195
|
+
target = makeRoom(position + maxBytes)
|
|
196
|
+
if (bundledStrings.position) { // here we use the 0x62 extension to write the last bundle and reserve sapce for the reference pointer to the next/current bundle
|
|
197
|
+
target[position] = 0xc8 // ext 16
|
|
198
|
+
position += 3 // reserve for the writing bundle size
|
|
199
|
+
target[position++] = 0x62 // 'b'
|
|
200
|
+
extStart = position - start
|
|
201
|
+
position += 4 // reserve for writing bundle reference
|
|
202
|
+
writeBundles(start, pack) // write the last bundles
|
|
203
|
+
targetView.setUint16(extStart + start - 3, position - start - extStart)
|
|
204
|
+
} 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)
|
|
205
|
+
target[position++] = 0xd6 // fixext 4
|
|
206
|
+
target[position++] = 0x62 // 'b'
|
|
207
|
+
extStart = position - start
|
|
208
|
+
position += 4 // reserve for writing bundle reference
|
|
209
|
+
}
|
|
210
|
+
bundledStrings = ['', ''] // create new ones
|
|
211
|
+
bundledStrings.size = 0
|
|
212
|
+
bundledStrings.position = extStart
|
|
213
|
+
}
|
|
195
214
|
let twoByte = hasNonLatin.test(value)
|
|
196
215
|
bundledStrings[twoByte ? 0 : 1] += value
|
|
197
216
|
target[position++] = 0xc1
|
|
@@ -496,8 +515,7 @@ export class Packr extends Unpackr {
|
|
|
496
515
|
target[objectOffset++ + start] = size >> 8
|
|
497
516
|
target[objectOffset + start] = size & 0xff
|
|
498
517
|
} :
|
|
499
|
-
|
|
500
|
-
/* sharedStructures ? // For highly stable structures, using for-in can a little bit faster
|
|
518
|
+
(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)
|
|
501
519
|
(object, safePrototype) => {
|
|
502
520
|
let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null))
|
|
503
521
|
let objectOffset = position++ - start
|
|
@@ -505,44 +523,47 @@ export class Packr extends Unpackr {
|
|
|
505
523
|
for (let key in object) {
|
|
506
524
|
if (safePrototype || object.hasOwnProperty(key)) {
|
|
507
525
|
nextTransition = transition[key]
|
|
508
|
-
if (
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
let
|
|
514
|
-
|
|
515
|
-
|
|
526
|
+
if (nextTransition)
|
|
527
|
+
transition = nextTransition
|
|
528
|
+
else {
|
|
529
|
+
// record doesn't exist, create full new record and insert it
|
|
530
|
+
let keys = Object.keys(object)
|
|
531
|
+
let lastTransition = transition
|
|
532
|
+
transition = structures.transitions
|
|
533
|
+
let newTransitions = 0
|
|
534
|
+
for (let i = 0, l = keys.length; i < l; i++) {
|
|
516
535
|
let key = keys[i]
|
|
517
|
-
|
|
518
|
-
if (
|
|
519
|
-
nextTransition =
|
|
520
|
-
|
|
536
|
+
nextTransition = transition[key]
|
|
537
|
+
if (!nextTransition) {
|
|
538
|
+
nextTransition = transition[key] = Object.create(null)
|
|
539
|
+
newTransitions++
|
|
521
540
|
}
|
|
541
|
+
transition = nextTransition
|
|
522
542
|
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
543
|
+
if (objectOffset + start + 1 == position) {
|
|
544
|
+
// first key, so we don't need to insert, we can just write record directly
|
|
545
|
+
position--
|
|
546
|
+
newRecord(transition, keys, newTransitions)
|
|
547
|
+
} else // otherwise we need to insert the record, moving existing data after the record
|
|
548
|
+
insertNewRecord(transition, keys, objectOffset, newTransitions)
|
|
549
|
+
wroteKeys = true
|
|
550
|
+
transition = lastTransition[key]
|
|
527
551
|
}
|
|
528
|
-
transition = nextTransition
|
|
529
552
|
pack(object[key])
|
|
530
553
|
}
|
|
531
554
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
555
|
+
if (!wroteKeys) {
|
|
556
|
+
let recordId = transition[RECORD_SYMBOL]
|
|
557
|
+
if (recordId)
|
|
558
|
+
target[objectOffset + start] = recordId
|
|
559
|
+
else
|
|
560
|
+
insertNewRecord(transition, Object.keys(object), objectOffset, 0)
|
|
537
561
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
(object) => {
|
|
541
|
-
let keys = Object.keys(object)
|
|
562
|
+
} :
|
|
563
|
+
(object, safePrototype) => {
|
|
542
564
|
let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null))
|
|
543
565
|
let newTransitions = 0
|
|
544
|
-
for (let
|
|
545
|
-
let key = keys[i]
|
|
566
|
+
for (let key in object) if (safePrototype || object.hasOwnProperty(key)) {
|
|
546
567
|
nextTransition = transition[key]
|
|
547
568
|
if (!nextTransition) {
|
|
548
569
|
nextTransition = transition[key] = Object.create(null)
|
|
@@ -558,57 +579,12 @@ export class Packr extends Unpackr {
|
|
|
558
579
|
} else
|
|
559
580
|
target[position++] = recordId
|
|
560
581
|
} else {
|
|
561
|
-
|
|
562
|
-
if (!recordId)
|
|
563
|
-
recordId = 0x40
|
|
564
|
-
if (recordId < sharedLimitId && this.shouldShareStructure && !this.shouldShareStructure(keys)) {
|
|
565
|
-
recordId = structures.nextOwnId
|
|
566
|
-
if (!(recordId < maxStructureId))
|
|
567
|
-
recordId = sharedLimitId
|
|
568
|
-
structures.nextOwnId = recordId + 1
|
|
569
|
-
} else {
|
|
570
|
-
if (recordId >= maxStructureId)// cycle back around
|
|
571
|
-
recordId = sharedLimitId
|
|
572
|
-
structures.nextId = recordId + 1
|
|
573
|
-
}
|
|
574
|
-
let highByte = keys.highByte = recordId >= 0x60 && useTwoByteRecords ? (recordId - 0x60) >> 5 : -1
|
|
575
|
-
transition[RECORD_SYMBOL] = recordId
|
|
576
|
-
structures[recordId - 0x40] = keys
|
|
577
|
-
|
|
578
|
-
if (recordId < sharedLimitId) {
|
|
579
|
-
keys.isShared = true
|
|
580
|
-
structures.sharedLength = recordId - 0x3f
|
|
581
|
-
hasSharedUpdate = true
|
|
582
|
-
if (highByte >= 0) {
|
|
583
|
-
target[position++] = (recordId & 0x1f) + 0x60
|
|
584
|
-
target[position++] = highByte
|
|
585
|
-
} else {
|
|
586
|
-
target[position++] = recordId
|
|
587
|
-
}
|
|
588
|
-
} else {
|
|
589
|
-
if (highByte >= 0) {
|
|
590
|
-
target[position++] = 0xd5 // fixext 2
|
|
591
|
-
target[position++] = 0x72 // "r" record defintion extension type
|
|
592
|
-
target[position++] = (recordId & 0x1f) + 0x60
|
|
593
|
-
target[position++] = highByte
|
|
594
|
-
} else {
|
|
595
|
-
target[position++] = 0xd4 // fixext 1
|
|
596
|
-
target[position++] = 0x72 // "r" record defintion extension type
|
|
597
|
-
target[position++] = recordId
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
if (newTransitions)
|
|
601
|
-
transitionsCount += serializationsSinceTransitionRebuild * newTransitions
|
|
602
|
-
// record the removal of the id, we can maintain our shared structure
|
|
603
|
-
if (recordIdsToRemove.length >= maxOwnStructures)
|
|
604
|
-
recordIdsToRemove.shift()[RECORD_SYMBOL] = 0 // we are cycling back through, and have to remove old ones
|
|
605
|
-
recordIdsToRemove.push(transition)
|
|
606
|
-
pack(keys)
|
|
607
|
-
}
|
|
582
|
+
newRecord(transition, transition.__keys__ || Object.keys(object), newTransitions)
|
|
608
583
|
}
|
|
609
584
|
// now write the values
|
|
610
|
-
for (let
|
|
611
|
-
|
|
585
|
+
for (let key in object)
|
|
586
|
+
if (safePrototype || object.hasOwnProperty(key))
|
|
587
|
+
pack(object[key])
|
|
612
588
|
}
|
|
613
589
|
const makeRoom = (end) => {
|
|
614
590
|
let newSize
|
|
@@ -631,6 +607,86 @@ export class Packr extends Unpackr {
|
|
|
631
607
|
safeEnd = newBuffer.length - 10
|
|
632
608
|
return target = newBuffer
|
|
633
609
|
}
|
|
610
|
+
const newRecord = (transition, keys, newTransitions) => {
|
|
611
|
+
let recordId = structures.nextId
|
|
612
|
+
if (!recordId)
|
|
613
|
+
recordId = 0x40
|
|
614
|
+
if (recordId < sharedLimitId && this.shouldShareStructure && !this.shouldShareStructure(keys)) {
|
|
615
|
+
recordId = structures.nextOwnId
|
|
616
|
+
if (!(recordId < maxStructureId))
|
|
617
|
+
recordId = sharedLimitId
|
|
618
|
+
structures.nextOwnId = recordId + 1
|
|
619
|
+
} else {
|
|
620
|
+
if (recordId >= maxStructureId)// cycle back around
|
|
621
|
+
recordId = sharedLimitId
|
|
622
|
+
structures.nextId = recordId + 1
|
|
623
|
+
}
|
|
624
|
+
let highByte = keys.highByte = recordId >= 0x60 && useTwoByteRecords ? (recordId - 0x60) >> 5 : -1
|
|
625
|
+
transition[RECORD_SYMBOL] = recordId
|
|
626
|
+
transition.__keys__ = keys
|
|
627
|
+
structures[recordId - 0x40] = keys
|
|
628
|
+
|
|
629
|
+
if (recordId < sharedLimitId) {
|
|
630
|
+
keys.isShared = true
|
|
631
|
+
structures.sharedLength = recordId - 0x3f
|
|
632
|
+
hasSharedUpdate = true
|
|
633
|
+
if (highByte >= 0) {
|
|
634
|
+
target[position++] = (recordId & 0x1f) + 0x60
|
|
635
|
+
target[position++] = highByte
|
|
636
|
+
} else {
|
|
637
|
+
target[position++] = recordId
|
|
638
|
+
}
|
|
639
|
+
} else {
|
|
640
|
+
if (highByte >= 0) {
|
|
641
|
+
target[position++] = 0xd5 // fixext 2
|
|
642
|
+
target[position++] = 0x72 // "r" record defintion extension type
|
|
643
|
+
target[position++] = (recordId & 0x1f) + 0x60
|
|
644
|
+
target[position++] = highByte
|
|
645
|
+
} else {
|
|
646
|
+
target[position++] = 0xd4 // fixext 1
|
|
647
|
+
target[position++] = 0x72 // "r" record defintion extension type
|
|
648
|
+
target[position++] = recordId
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
if (newTransitions)
|
|
652
|
+
transitionsCount += serializationsSinceTransitionRebuild * newTransitions
|
|
653
|
+
// record the removal of the id, we can maintain our shared structure
|
|
654
|
+
if (recordIdsToRemove.length >= maxOwnStructures)
|
|
655
|
+
recordIdsToRemove.shift()[RECORD_SYMBOL] = 0 // we are cycling back through, and have to remove old ones
|
|
656
|
+
recordIdsToRemove.push(transition)
|
|
657
|
+
pack(keys)
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
const insertNewRecord = (transition, keys, insertionOffset, newTransitions) => {
|
|
661
|
+
let mainTarget = target
|
|
662
|
+
let mainPosition = position
|
|
663
|
+
let mainSafeEnd = safeEnd
|
|
664
|
+
let mainStart = start
|
|
665
|
+
target = keysTarget
|
|
666
|
+
position = 0
|
|
667
|
+
start = 0
|
|
668
|
+
if (!target)
|
|
669
|
+
keysTarget = target = new ByteArrayAllocate(8192)
|
|
670
|
+
safeEnd = target.length - 10
|
|
671
|
+
newRecord(transition, keys, newTransitions)
|
|
672
|
+
keysTarget = target
|
|
673
|
+
let keysPosition = position
|
|
674
|
+
target = mainTarget
|
|
675
|
+
position = mainPosition
|
|
676
|
+
safeEnd = mainSafeEnd
|
|
677
|
+
start = mainStart
|
|
678
|
+
if (keysPosition > 1) {
|
|
679
|
+
let newEnd = position + keysPosition - 1
|
|
680
|
+
if (newEnd > safeEnd)
|
|
681
|
+
makeRoom(newEnd)
|
|
682
|
+
let insertionPosition = insertionOffset + start
|
|
683
|
+
target.copyWithin(insertionPosition + keysPosition, insertionPosition + 1, position)
|
|
684
|
+
target.set(keysTarget.slice(0, keysPosition), insertionPosition)
|
|
685
|
+
position = newEnd
|
|
686
|
+
} else {
|
|
687
|
+
target[insertionOffset + start] = keysTarget[0]
|
|
688
|
+
}
|
|
689
|
+
}
|
|
634
690
|
}
|
|
635
691
|
useBuffer(buffer) {
|
|
636
692
|
// this means we are finished using our own buffer and we can write over it safely
|
|
@@ -638,6 +694,10 @@ export class Packr extends Unpackr {
|
|
|
638
694
|
targetView = new DataView(target.buffer, target.byteOffset, target.byteLength)
|
|
639
695
|
position = 0
|
|
640
696
|
}
|
|
697
|
+
clearSharedData() {
|
|
698
|
+
if (this.structures)
|
|
699
|
+
this.structures = []
|
|
700
|
+
}
|
|
641
701
|
}
|
|
642
702
|
|
|
643
703
|
function copyBinary(source, target, targetOffset, offset, endOffset) {
|
|
@@ -841,6 +901,15 @@ function insertIds(serialized, idsToInsert) {
|
|
|
841
901
|
return serialized
|
|
842
902
|
}
|
|
843
903
|
|
|
904
|
+
function writeBundles(start, pack) {
|
|
905
|
+
targetView.setUint32(bundledStrings.position + start, position - bundledStrings.position - start)
|
|
906
|
+
let writeStrings = bundledStrings
|
|
907
|
+
bundledStrings = null
|
|
908
|
+
let startPosition = position
|
|
909
|
+
pack(writeStrings[0])
|
|
910
|
+
pack(writeStrings[1])
|
|
911
|
+
}
|
|
912
|
+
|
|
844
913
|
export function addExtension(extension) {
|
|
845
914
|
if (extension.Class) {
|
|
846
915
|
if (!extension.pack && !extension.write)
|
package/package.json
CHANGED
package/unpack.js
CHANGED
|
@@ -156,6 +156,9 @@ export function checkedRead() {
|
|
|
156
156
|
currentStructures.length = sharedLength
|
|
157
157
|
}
|
|
158
158
|
let result = read()
|
|
159
|
+
if (bundledStrings) // bundled strings to skip past
|
|
160
|
+
position = bundledStrings.postBundlePosition
|
|
161
|
+
|
|
159
162
|
if (position == srcEnd) {
|
|
160
163
|
// finished reading this source, cleanup references
|
|
161
164
|
if (currentStructures.restoreStructures)
|
|
@@ -498,6 +501,8 @@ export function setExtractor(extractStrings) {
|
|
|
498
501
|
return function readString(length) {
|
|
499
502
|
let string = strings[stringPosition++]
|
|
500
503
|
if (string == null) {
|
|
504
|
+
if (bundledStrings)
|
|
505
|
+
return readStringJS(length)
|
|
501
506
|
let extraction = extractStrings(position - headerLength, srcEnd, src)
|
|
502
507
|
if (typeof extraction == 'string') {
|
|
503
508
|
string = extraction
|
|
@@ -756,6 +761,36 @@ function shortStringInJS(length) {
|
|
|
756
761
|
}
|
|
757
762
|
}
|
|
758
763
|
|
|
764
|
+
function readOnlyJSString() {
|
|
765
|
+
let token = src[position++]
|
|
766
|
+
let length
|
|
767
|
+
if (token < 0xc0) {
|
|
768
|
+
// fixstr
|
|
769
|
+
length = token - 0xa0
|
|
770
|
+
} else {
|
|
771
|
+
switch(token) {
|
|
772
|
+
case 0xd9:
|
|
773
|
+
// str 8
|
|
774
|
+
length = src[position++]
|
|
775
|
+
break
|
|
776
|
+
case 0xda:
|
|
777
|
+
// str 16
|
|
778
|
+
length = dataView.getUint16(position)
|
|
779
|
+
position += 2
|
|
780
|
+
break
|
|
781
|
+
case 0xdb:
|
|
782
|
+
// str 32
|
|
783
|
+
length = dataView.getUint32(position)
|
|
784
|
+
position += 4
|
|
785
|
+
break
|
|
786
|
+
default:
|
|
787
|
+
throw new Error('Expected string')
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
return readStringJS(length)
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
|
|
759
794
|
function readBin(length) {
|
|
760
795
|
return currentUnpackr.copyBuffers ?
|
|
761
796
|
// specifically use the copying slice (not the node one)
|
|
@@ -907,21 +942,18 @@ currentExtensions[0x78] = () => {
|
|
|
907
942
|
let data = read()
|
|
908
943
|
return new RegExp(data[0], data[1])
|
|
909
944
|
}
|
|
910
|
-
|
|
945
|
+
const TEMP_BUNDLE = []
|
|
911
946
|
currentExtensions[0x62] = (data) => {
|
|
912
947
|
let dataSize = (data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3]
|
|
913
948
|
let dataPosition = position
|
|
914
|
-
position += dataSize -
|
|
915
|
-
bundledStrings =
|
|
949
|
+
position += dataSize - data.length
|
|
950
|
+
bundledStrings = TEMP_BUNDLE
|
|
951
|
+
bundledStrings = [readOnlyJSString(), readOnlyJSString()]
|
|
916
952
|
bundledStrings.position0 = 0
|
|
917
953
|
bundledStrings.position1 = 0
|
|
918
|
-
|
|
954
|
+
bundledStrings.postBundlePosition = position
|
|
919
955
|
position = dataPosition
|
|
920
|
-
|
|
921
|
-
return read()
|
|
922
|
-
} finally {
|
|
923
|
-
position = postBundlePosition
|
|
924
|
-
}
|
|
956
|
+
return read()
|
|
925
957
|
}
|
|
926
958
|
|
|
927
959
|
currentExtensions[0xff] = (data) => {
|