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/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
- target[position++] = 0xd6
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
- targetView.setUint32(bundledStrings.position + start, position - bundledStrings.position - start)
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 >= 8 && strLength < 0x1000) {
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 (!nextTransition) {
509
- nextTransition = transition[key] = Object.create(null)
510
- nextTransition.__keys__ = (transition.__keys__ || []).concat([key])
511
- /*let keys = Object.keys(object)
512
- if
513
- let size = 0
514
- let startBranch = transition.__keys__ ? transition.__keys__.length : 0
515
- for (let i = 0, l = keys.length; i++) {
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
- size += key.length << 2
518
- if (i >= startBranch) {
519
- nextTransition = nextTransition[key] = Object.create(null)
520
- nextTransition.__keys__ = keys.slice(0, i + 1)
536
+ nextTransition = transition[key]
537
+ if (!nextTransition) {
538
+ nextTransition = transition[key] = Object.create(null)
539
+ newTransitions++
521
540
  }
541
+ transition = nextTransition
522
542
  }
523
- makeRoom(position + size)
524
- nextTransition = transition[key]
525
- target.copy(target, )
526
- objectOffset
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
- let id = transition.id
533
- if (!id) {
534
- id = transition.id = structures.push(transition.__keys__) + 63
535
- if (sharedStructures.onUpdate)
536
- sharedStructures.onUpdate(id, transition.__keys__)
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
- target[objectOffset + start] = id
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 i = 0, l = keys.length; i < l; i++) {
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
- recordId = structures.nextId
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 i = 0, l = keys.length; i < l; i++)
611
- pack(object[keys[i]])
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "msgpackr",
3
3
  "author": "Kris Zyp",
4
- "version": "1.5.2",
4
+ "version": "1.5.3",
5
5
  "description": "Ultra-fast MessagePack implementation with extensions for records and structured cloning",
6
6
  "license": "MIT",
7
7
  "types": "./index.d.ts",
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 - 4
915
- bundledStrings = [read(), read()]
949
+ position += dataSize - data.length
950
+ bundledStrings = TEMP_BUNDLE
951
+ bundledStrings = [readOnlyJSString(), readOnlyJSString()]
916
952
  bundledStrings.position0 = 0
917
953
  bundledStrings.position1 = 0
918
- let postBundlePosition = position
954
+ bundledStrings.postBundlePosition = position
919
955
  position = dataPosition
920
- try {
921
- return read()
922
- } finally {
923
- position = postBundlePosition
924
- }
956
+ return read()
925
957
  }
926
958
 
927
959
  currentExtensions[0xff] = (data) => {