msgpackr 1.5.2 → 1.5.5

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 {
@@ -22,7 +23,6 @@ export class Packr extends Unpackr {
22
23
  this.offset = 0
23
24
  let typeBuffer
24
25
  let start
25
- let sharedStructures
26
26
  let hasSharedUpdate
27
27
  let structures
28
28
  let referenceMap
@@ -44,10 +44,13 @@ export class Packr extends Unpackr {
44
44
  maxSharedStructures = hasSharedStructures ? 32 : 0
45
45
  if (maxSharedStructures > 8160)
46
46
  throw new Error('Maximum maxSharedStructure is 8160')
47
+ if (options.structuredClone && options.moreTypes == undefined) {
48
+ options.moreTypes = true
49
+ }
47
50
  let maxOwnStructures = options.maxOwnStructures
48
51
  if (maxOwnStructures == null)
49
52
  maxOwnStructures = hasSharedStructures ? 32 : 64
50
- if (isSequential && !options.saveStructures)
53
+ if (!this.structures && options.useRecords != false)
51
54
  this.structures = []
52
55
  // two byte record ids for shared structures
53
56
  let useTwoByteRecords = maxSharedStructures > 32 || (maxOwnStructures + maxSharedStructures > 64)
@@ -77,31 +80,28 @@ export class Packr extends Unpackr {
77
80
  position = (position + 7) & 0x7ffffff8 // Word align to make any future copying of this buffer faster
78
81
  start = position
79
82
  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
83
+ if (packr.bundleStrings && typeof value !== 'string') {
84
+ bundledStrings = []
85
+ bundledStrings.size = Infinity // force a new bundle start on first string
86
86
  } else
87
87
  bundledStrings = null
88
- sharedStructures = packr.structures
89
- if (sharedStructures) {
90
- if (sharedStructures.uninitialized)
91
- sharedStructures = packr._mergeStructures(packr.getStructures())
92
- let sharedLength = sharedStructures.sharedLength || 0
88
+ structures = packr.structures
89
+ if (structures) {
90
+ if (structures.uninitialized)
91
+ structures = packr._mergeStructures(packr.getStructures())
92
+ let sharedLength = structures.sharedLength || 0
93
93
  if (sharedLength > maxSharedStructures) {
94
- //if (maxSharedStructures <= 32 && sharedStructures.sharedLength > 32) // TODO: could support this, but would need to update the limit ids
95
- throw new Error('Shared structures is larger than maximum shared structures, try increasing maxSharedStructures to ' + sharedStructures.sharedLength)
94
+ //if (maxSharedStructures <= 32 && structures.sharedLength > 32) // TODO: could support this, but would need to update the limit ids
95
+ throw new Error('Shared structures is larger than maximum shared structures, try increasing maxSharedStructures to ' + structures.sharedLength)
96
96
  }
97
- if (!sharedStructures.transitions) {
97
+ if (!structures.transitions) {
98
98
  // rebuild our structure transitions
99
- sharedStructures.transitions = Object.create(null)
99
+ structures.transitions = Object.create(null)
100
100
  for (let i = 0; i < sharedLength; i++) {
101
- let keys = sharedStructures[i]
101
+ let keys = structures[i]
102
102
  if (!keys)
103
103
  continue
104
- let nextTransition, transition = sharedStructures.transitions
104
+ let nextTransition, transition = structures.transitions
105
105
  for (let j = 0, l = keys.length; j < l; j++) {
106
106
  let key = keys[j]
107
107
  nextTransition = transition[key]
@@ -115,20 +115,15 @@ export class Packr extends Unpackr {
115
115
  lastSharedStructuresLength = sharedLength
116
116
  }
117
117
  if (!isSequential) {
118
- sharedStructures.nextId = sharedLength + 0x40
118
+ structures.nextId = sharedLength + 0x40
119
119
  }
120
120
  }
121
121
  if (hasSharedUpdate)
122
122
  hasSharedUpdate = false
123
- structures = sharedStructures || []
124
123
  try {
125
124
  pack(value)
126
125
  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])
126
+ writeBundles(start, pack)
132
127
  }
133
128
  packr.offset = position // update the offset so next serialization doesn't write over our buffer, but can continue writing to same buffer sequentially
134
129
  if (referenceMap && referenceMap.idsToInsert) {
@@ -147,12 +142,15 @@ export class Packr extends Unpackr {
147
142
  }
148
143
  return target.subarray(start, position) // position can change if we call pack again in saveStructures, so we get the buffer now
149
144
  } finally {
150
- if (sharedStructures) {
145
+ if (structures) {
151
146
  if (serializationsSinceTransitionRebuild < 10)
152
147
  serializationsSinceTransitionRebuild++
148
+ let sharedLength = structures.sharedLength || maxSharedStructures
149
+ if (structures.length > sharedLength)
150
+ structures.length = sharedLength
153
151
  if (transitionsCount > 10000) {
154
152
  // force a rebuild occasionally after a lot of transitions so it can get cleaned up
155
- sharedStructures.transitions = null
153
+ structures.transitions = null
156
154
  serializationsSinceTransitionRebuild = 0
157
155
  transitionsCount = 0
158
156
  if (recordIdsToRemove.length > 0)
@@ -164,13 +162,9 @@ export class Packr extends Unpackr {
164
162
  recordIdsToRemove = []
165
163
  }
166
164
  if (hasSharedUpdate && packr.saveStructures) {
167
- let sharedLength = sharedStructures.sharedLength || maxSharedStructures
168
- if (sharedStructures.length > sharedLength) {
169
- sharedStructures = sharedStructures.slice(0, sharedLength)
170
- }
171
165
  // we can't rely on start/end with REUSE_BUFFER_MODE since they will (probably) change when we save
172
166
  let returnBuffer = target.subarray(start, position)
173
- if (packr.saveStructures(sharedStructures, lastSharedStructuresLength) === false) {
167
+ if (packr.saveStructures(structures, lastSharedStructuresLength) === false) {
174
168
  // get updated structures and try again if the update failed
175
169
  packr._mergeStructures(packr.getStructures())
176
170
  return packr.pack(value)
@@ -191,7 +185,30 @@ export class Packr extends Unpackr {
191
185
  var length
192
186
  if (type === 'string') {
193
187
  let strLength = value.length
194
- if (bundledStrings && strLength >= 8 && strLength < 0x1000) {
188
+ if (bundledStrings && strLength >= 4 && strLength < 0x1000) {
189
+ if ((bundledStrings.size += strLength) > MAX_BUNDLE_SIZE) {
190
+ let extStart
191
+ let maxBytes = (bundledStrings[0] ? bundledStrings[0].length * 3 + bundledStrings[1].length : 0) + 10
192
+ if (position + maxBytes > safeEnd)
193
+ target = makeRoom(position + maxBytes)
194
+ 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
195
+ target[position] = 0xc8 // ext 16
196
+ position += 3 // reserve for the writing bundle size
197
+ target[position++] = 0x62 // 'b'
198
+ extStart = position - start
199
+ position += 4 // reserve for writing bundle reference
200
+ writeBundles(start, pack) // write the last bundles
201
+ targetView.setUint16(extStart + start - 3, position - start - extStart)
202
+ } 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)
203
+ target[position++] = 0xd6 // fixext 4
204
+ target[position++] = 0x62 // 'b'
205
+ extStart = position - start
206
+ position += 4 // reserve for writing bundle reference
207
+ }
208
+ bundledStrings = ['', ''] // create new ones
209
+ bundledStrings.size = 0
210
+ bundledStrings.position = extStart
211
+ }
195
212
  let twoByte = hasNonLatin.test(value)
196
213
  bundledStrings[twoByte ? 0 : 1] += value
197
214
  target[position++] = 0xc1
@@ -496,8 +513,7 @@ export class Packr extends Unpackr {
496
513
  target[objectOffset++ + start] = size >> 8
497
514
  target[objectOffset + start] = size & 0xff
498
515
  } :
499
-
500
- /* sharedStructures ? // For highly stable structures, using for-in can a little bit faster
516
+ (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
517
  (object, safePrototype) => {
502
518
  let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null))
503
519
  let objectOffset = position++ - start
@@ -505,44 +521,47 @@ export class Packr extends Unpackr {
505
521
  for (let key in object) {
506
522
  if (safePrototype || object.hasOwnProperty(key)) {
507
523
  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++) {
524
+ if (nextTransition)
525
+ transition = nextTransition
526
+ else {
527
+ // record doesn't exist, create full new record and insert it
528
+ let keys = Object.keys(object)
529
+ let lastTransition = transition
530
+ transition = structures.transitions
531
+ let newTransitions = 0
532
+ for (let i = 0, l = keys.length; i < l; i++) {
516
533
  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)
534
+ nextTransition = transition[key]
535
+ if (!nextTransition) {
536
+ nextTransition = transition[key] = Object.create(null)
537
+ newTransitions++
521
538
  }
539
+ transition = nextTransition
522
540
  }
523
- makeRoom(position + size)
524
- nextTransition = transition[key]
525
- target.copy(target, )
526
- objectOffset
541
+ if (objectOffset + start + 1 == position) {
542
+ // first key, so we don't need to insert, we can just write record directly
543
+ position--
544
+ newRecord(transition, keys, newTransitions)
545
+ } else // otherwise we need to insert the record, moving existing data after the record
546
+ insertNewRecord(transition, keys, objectOffset, newTransitions)
547
+ wroteKeys = true
548
+ transition = lastTransition[key]
527
549
  }
528
- transition = nextTransition
529
550
  pack(object[key])
530
551
  }
531
552
  }
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__)
553
+ if (!wroteKeys) {
554
+ let recordId = transition[RECORD_SYMBOL]
555
+ if (recordId)
556
+ target[objectOffset + start] = recordId
557
+ else
558
+ insertNewRecord(transition, Object.keys(object), objectOffset, 0)
537
559
  }
538
- target[objectOffset + start] = id
539
- }*/
540
- (object) => {
541
- let keys = Object.keys(object)
560
+ } :
561
+ (object, safePrototype) => {
542
562
  let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null))
543
563
  let newTransitions = 0
544
- for (let i = 0, l = keys.length; i < l; i++) {
545
- let key = keys[i]
564
+ for (let key in object) if (safePrototype || object.hasOwnProperty(key)) {
546
565
  nextTransition = transition[key]
547
566
  if (!nextTransition) {
548
567
  nextTransition = transition[key] = Object.create(null)
@@ -558,57 +577,12 @@ export class Packr extends Unpackr {
558
577
  } else
559
578
  target[position++] = recordId
560
579
  } 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
- }
580
+ newRecord(transition, transition.__keys__ || Object.keys(object), newTransitions)
608
581
  }
609
582
  // now write the values
610
- for (let i = 0, l = keys.length; i < l; i++)
611
- pack(object[keys[i]])
583
+ for (let key in object)
584
+ if (safePrototype || object.hasOwnProperty(key))
585
+ pack(object[key])
612
586
  }
613
587
  const makeRoom = (end) => {
614
588
  let newSize
@@ -631,6 +605,86 @@ export class Packr extends Unpackr {
631
605
  safeEnd = newBuffer.length - 10
632
606
  return target = newBuffer
633
607
  }
608
+ const newRecord = (transition, keys, newTransitions) => {
609
+ let recordId = structures.nextId
610
+ if (!recordId)
611
+ recordId = 0x40
612
+ if (recordId < sharedLimitId && this.shouldShareStructure && !this.shouldShareStructure(keys)) {
613
+ recordId = structures.nextOwnId
614
+ if (!(recordId < maxStructureId))
615
+ recordId = sharedLimitId
616
+ structures.nextOwnId = recordId + 1
617
+ } else {
618
+ if (recordId >= maxStructureId)// cycle back around
619
+ recordId = sharedLimitId
620
+ structures.nextId = recordId + 1
621
+ }
622
+ let highByte = keys.highByte = recordId >= 0x60 && useTwoByteRecords ? (recordId - 0x60) >> 5 : -1
623
+ transition[RECORD_SYMBOL] = recordId
624
+ transition.__keys__ = keys
625
+ structures[recordId - 0x40] = keys
626
+
627
+ if (recordId < sharedLimitId) {
628
+ keys.isShared = true
629
+ structures.sharedLength = recordId - 0x3f
630
+ hasSharedUpdate = true
631
+ if (highByte >= 0) {
632
+ target[position++] = (recordId & 0x1f) + 0x60
633
+ target[position++] = highByte
634
+ } else {
635
+ target[position++] = recordId
636
+ }
637
+ } else {
638
+ if (highByte >= 0) {
639
+ target[position++] = 0xd5 // fixext 2
640
+ target[position++] = 0x72 // "r" record defintion extension type
641
+ target[position++] = (recordId & 0x1f) + 0x60
642
+ target[position++] = highByte
643
+ } else {
644
+ target[position++] = 0xd4 // fixext 1
645
+ target[position++] = 0x72 // "r" record defintion extension type
646
+ target[position++] = recordId
647
+ }
648
+
649
+ if (newTransitions)
650
+ transitionsCount += serializationsSinceTransitionRebuild * newTransitions
651
+ // record the removal of the id, we can maintain our shared structure
652
+ if (recordIdsToRemove.length >= maxOwnStructures)
653
+ recordIdsToRemove.shift()[RECORD_SYMBOL] = 0 // we are cycling back through, and have to remove old ones
654
+ recordIdsToRemove.push(transition)
655
+ pack(keys)
656
+ }
657
+ }
658
+ const insertNewRecord = (transition, keys, insertionOffset, newTransitions) => {
659
+ let mainTarget = target
660
+ let mainPosition = position
661
+ let mainSafeEnd = safeEnd
662
+ let mainStart = start
663
+ target = keysTarget
664
+ position = 0
665
+ start = 0
666
+ if (!target)
667
+ keysTarget = target = new ByteArrayAllocate(8192)
668
+ safeEnd = target.length - 10
669
+ newRecord(transition, keys, newTransitions)
670
+ keysTarget = target
671
+ let keysPosition = position
672
+ target = mainTarget
673
+ position = mainPosition
674
+ safeEnd = mainSafeEnd
675
+ start = mainStart
676
+ if (keysPosition > 1) {
677
+ let newEnd = position + keysPosition - 1
678
+ if (newEnd > safeEnd)
679
+ makeRoom(newEnd)
680
+ let insertionPosition = insertionOffset + start
681
+ target.copyWithin(insertionPosition + keysPosition, insertionPosition + 1, position)
682
+ target.set(keysTarget.slice(0, keysPosition), insertionPosition)
683
+ position = newEnd
684
+ } else {
685
+ target[insertionOffset + start] = keysTarget[0]
686
+ }
687
+ }
634
688
  }
635
689
  useBuffer(buffer) {
636
690
  // this means we are finished using our own buffer and we can write over it safely
@@ -638,6 +692,10 @@ export class Packr extends Unpackr {
638
692
  targetView = new DataView(target.buffer, target.byteOffset, target.byteLength)
639
693
  position = 0
640
694
  }
695
+ clearSharedData() {
696
+ if (this.structures)
697
+ this.structures = []
698
+ }
641
699
  }
642
700
 
643
701
  function copyBinary(source, target, targetOffset, offset, endOffset) {
@@ -686,8 +744,8 @@ extensions = [{
686
744
  }, {
687
745
  pack(set, allocateForWrite, pack) {
688
746
  let array = Array.from(set)
689
- let { target, position} = allocateForWrite(this.structuredClone ? 3 : 0)
690
- if (this.structuredClone) {
747
+ let { target, position} = allocateForWrite(this.moreTypes ? 3 : 0)
748
+ if (this.moreTypes) {
691
749
  target[position++] = 0xd4
692
750
  target[position++] = 0x73 // 's' for Set
693
751
  target[position++] = 0
@@ -696,8 +754,8 @@ extensions = [{
696
754
  }
697
755
  }, {
698
756
  pack(error, allocateForWrite, pack) {
699
- let { target, position} = allocateForWrite(this.structuredClone ? 3 : 0)
700
- if (this.structuredClone) {
757
+ let { target, position} = allocateForWrite(this.moreTypes ? 3 : 0)
758
+ if (this.moreTypes) {
701
759
  target[position++] = 0xd4
702
760
  target[position++] = 0x65 // 'e' for error
703
761
  target[position++] = 0
@@ -706,8 +764,8 @@ extensions = [{
706
764
  }
707
765
  }, {
708
766
  pack(regex, allocateForWrite, pack) {
709
- let { target, position} = allocateForWrite(this.structuredClone ? 3 : 0)
710
- if (this.structuredClone) {
767
+ let { target, position} = allocateForWrite(this.moreTypes ? 3 : 0)
768
+ if (this.moreTypes) {
711
769
  target[position++] = 0xd4
712
770
  target[position++] = 0x78 // 'x' for regeXp
713
771
  target[position++] = 0
@@ -716,7 +774,7 @@ extensions = [{
716
774
  }
717
775
  }, {
718
776
  pack(arrayBuffer, allocateForWrite) {
719
- if (this.structuredClone)
777
+ if (this.moreTypes)
720
778
  writeExtBuffer(arrayBuffer, 0x10, allocateForWrite)
721
779
  else
722
780
  writeBuffer(hasNodeBuffer ? Buffer.from(arrayBuffer) : new Uint8Array(arrayBuffer), allocateForWrite)
@@ -724,7 +782,7 @@ extensions = [{
724
782
  }, {
725
783
  pack(typedArray, allocateForWrite) {
726
784
  let constructor = typedArray.constructor
727
- if (constructor !== ByteArray && this.structuredClone)
785
+ if (constructor !== ByteArray && this.moreTypes)
728
786
  writeExtBuffer(typedArray, typedArrays.indexOf(constructor.name), allocateForWrite)
729
787
  else
730
788
  writeBuffer(typedArray, allocateForWrite)
@@ -841,6 +899,15 @@ function insertIds(serialized, idsToInsert) {
841
899
  return serialized
842
900
  }
843
901
 
902
+ function writeBundles(start, pack) {
903
+ targetView.setUint32(bundledStrings.position + start, position - bundledStrings.position - start)
904
+ let writeStrings = bundledStrings
905
+ bundledStrings = null
906
+ let startPosition = position
907
+ pack(writeStrings[0])
908
+ pack(writeStrings[1])
909
+ }
910
+
844
911
  export function addExtension(extension) {
845
912
  if (extension.Class) {
846
913
  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.5",
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.d.ts CHANGED
@@ -9,6 +9,7 @@ export interface Options {
9
9
  useFloat32?: FLOAT32_OPTIONS
10
10
  useRecords?: boolean
11
11
  structures?: {}[]
12
+ moreTypes?: boolean
12
13
  structuredClone?: boolean
13
14
  mapsAsObjects?: boolean
14
15
  variableMapSize?: boolean
@@ -19,6 +20,7 @@ export interface Options {
19
20
  encodeUndefinedAsNil?: boolean
20
21
  maxSharedStructures?: number
21
22
  maxOwnStructures?: number
23
+ int64AsNumber?: boolean
22
24
  shouldShareStructure?: (keys: string[]) => boolean
23
25
  getStructures?(): {}[]
24
26
  saveStructures?(structures: {}[]): boolean | void
package/unpack.js CHANGED
@@ -27,6 +27,13 @@ export class C1Type {}
27
27
  export const C1 = new C1Type()
28
28
  C1.name = 'MessagePack 0xC1'
29
29
  var sequentialMode = false
30
+ var inlineObjectReadThreshold = 2
31
+ try {
32
+ new Function('')
33
+ } catch(error) {
34
+ // if eval variants are not supported, do not create inline object readers ever
35
+ inlineObjectReadThreshold = Infinity
36
+ }
30
37
 
31
38
  export class Unpackr {
32
39
  constructor(options) {
@@ -156,6 +163,9 @@ export function checkedRead() {
156
163
  currentStructures.length = sharedLength
157
164
  }
158
165
  let result = read()
166
+ if (bundledStrings) // bundled strings to skip past
167
+ position = bundledStrings.postBundlePosition
168
+
159
169
  if (position == srcEnd) {
160
170
  // finished reading this source, cleanup references
161
171
  if (currentStructures.restoreStructures)
@@ -437,7 +447,7 @@ const validName = /^[a-zA-Z_$][a-zA-Z\d_$]*$/
437
447
  function createStructureReader(structure, firstId) {
438
448
  function readObject() {
439
449
  // This initial function is quick to instantiate, but runs slower. After several iterations pay the cost to build the faster function
440
- if (readObject.count++ > 2) {
450
+ if (readObject.count++ > inlineObjectReadThreshold) {
441
451
  let readObject = structure.read = (new Function('r', 'return function(){return {' + structure.map(key => validName.test(key) ? key + ':r()' : ('[' + JSON.stringify(key) + ']:r()')).join(',') + '}}'))(read)
442
452
  if (structure.highByte === 0)
443
453
  structure.read = createSecondByteReader(firstId, structure.read)
@@ -498,6 +508,8 @@ export function setExtractor(extractStrings) {
498
508
  return function readString(length) {
499
509
  let string = strings[stringPosition++]
500
510
  if (string == null) {
511
+ if (bundledStrings)
512
+ return readStringJS(length)
501
513
  let extraction = extractStrings(position - headerLength, srcEnd, src)
502
514
  if (typeof extraction == 'string') {
503
515
  string = extraction
@@ -756,6 +768,36 @@ function shortStringInJS(length) {
756
768
  }
757
769
  }
758
770
 
771
+ function readOnlyJSString() {
772
+ let token = src[position++]
773
+ let length
774
+ if (token < 0xc0) {
775
+ // fixstr
776
+ length = token - 0xa0
777
+ } else {
778
+ switch(token) {
779
+ case 0xd9:
780
+ // str 8
781
+ length = src[position++]
782
+ break
783
+ case 0xda:
784
+ // str 16
785
+ length = dataView.getUint16(position)
786
+ position += 2
787
+ break
788
+ case 0xdb:
789
+ // str 32
790
+ length = dataView.getUint32(position)
791
+ position += 4
792
+ break
793
+ default:
794
+ throw new Error('Expected string')
795
+ }
796
+ }
797
+ return readStringJS(length)
798
+ }
799
+
800
+
759
801
  function readBin(length) {
760
802
  return currentUnpackr.copyBuffers ?
761
803
  // specifically use the copying slice (not the node one)
@@ -907,21 +949,18 @@ currentExtensions[0x78] = () => {
907
949
  let data = read()
908
950
  return new RegExp(data[0], data[1])
909
951
  }
910
-
952
+ const TEMP_BUNDLE = []
911
953
  currentExtensions[0x62] = (data) => {
912
954
  let dataSize = (data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3]
913
955
  let dataPosition = position
914
- position += dataSize - 4
915
- bundledStrings = [read(), read()]
956
+ position += dataSize - data.length
957
+ bundledStrings = TEMP_BUNDLE
958
+ bundledStrings = [readOnlyJSString(), readOnlyJSString()]
916
959
  bundledStrings.position0 = 0
917
960
  bundledStrings.position1 = 0
918
- let postBundlePosition = position
961
+ bundledStrings.postBundlePosition = position
919
962
  position = dataPosition
920
- try {
921
- return read()
922
- } finally {
923
- position = postBundlePosition
924
- }
963
+ return read()
925
964
  }
926
965
 
927
966
  currentExtensions[0xff] = (data) => {