cbor-x 0.9.4 → 1.2.0
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 +55 -21
- package/decode.d.ts +3 -0
- package/decode.js +355 -119
- package/dist/index.js +670 -342
- package/dist/index.min.js +99 -108
- package/dist/node-index.js +93 -0
- package/dist/node.cjs +672 -337
- package/dist/test.js +100 -8
- package/encode.d.ts +1 -1
- package/encode.js +312 -155
- package/index.js +0 -2
- package/package.json +13 -4
- package/.vscode/launch.json +0 -22
- package/tests/benchmark-stream.cjs +0 -282
- package/tests/benchmark.cjs +0 -245
- 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 -36
- package/tests/test-incomplete.js +0 -41
- package/tests/test-node-iterators.js +0 -72
- package/tests/test-node-stream.js +0 -60
- package/tests/test.js +0 -596
package/encode.js
CHANGED
|
@@ -7,7 +7,7 @@ let extensions, extensionClasses
|
|
|
7
7
|
const hasNodeBuffer = typeof Buffer !== 'undefined'
|
|
8
8
|
const ByteArrayAllocate = hasNodeBuffer ? Buffer.allocUnsafeSlow : Uint8Array
|
|
9
9
|
const ByteArray = hasNodeBuffer ? Buffer : Uint8Array
|
|
10
|
-
const
|
|
10
|
+
const RECORD_INLINE_ID = 0xdfff // temporary first-come first-serve tag // proposed tag: 0x7265 // 're'
|
|
11
11
|
const MAX_STRUCTURES = 0x100
|
|
12
12
|
const MAX_BUFFER_SIZE = hasNodeBuffer ? 0x100000000 : 0x7fd00000
|
|
13
13
|
let serializationId = 1
|
|
@@ -15,6 +15,9 @@ let target
|
|
|
15
15
|
let targetView
|
|
16
16
|
let position = 0
|
|
17
17
|
let safeEnd
|
|
18
|
+
let bundledStrings = null
|
|
19
|
+
const MAX_BUNDLE_SIZE = 0xf000
|
|
20
|
+
const hasNonLatin = /[\u0080-\uFFFF]/
|
|
18
21
|
const RECORD_SYMBOL = Symbol('record-id')
|
|
19
22
|
export class Encoder extends Decoder {
|
|
20
23
|
constructor(options) {
|
|
@@ -27,7 +30,6 @@ export class Encoder extends Decoder {
|
|
|
27
30
|
let structures
|
|
28
31
|
let referenceMap
|
|
29
32
|
options = options || {}
|
|
30
|
-
let lastSharedStructuresLength = 0
|
|
31
33
|
let encodeUtf8 = ByteArray.prototype.utf8Write ? function(string, position, maxBytes) {
|
|
32
34
|
return target.utf8Write(string, position, maxBytes)
|
|
33
35
|
} : (textEncoder && textEncoder.encodeInto) ?
|
|
@@ -36,12 +38,20 @@ export class Encoder extends Decoder {
|
|
|
36
38
|
} : false
|
|
37
39
|
|
|
38
40
|
let encoder = this
|
|
39
|
-
let
|
|
41
|
+
let hasSharedStructures = options.structures || options.saveStructures
|
|
42
|
+
let maxSharedStructures = options.maxSharedStructures
|
|
43
|
+
if (maxSharedStructures == null)
|
|
44
|
+
maxSharedStructures = hasSharedStructures ? 128 : 0
|
|
45
|
+
if (maxSharedStructures > 8190)
|
|
46
|
+
throw new Error('Maximum maxSharedStructure is 8190')
|
|
40
47
|
let isSequential = options.sequential
|
|
41
48
|
if (isSequential) {
|
|
42
49
|
maxSharedStructures = 0
|
|
43
|
-
this.structures = []
|
|
44
50
|
}
|
|
51
|
+
if (!this.structures)
|
|
52
|
+
this.structures = []
|
|
53
|
+
if (this.saveStructures)
|
|
54
|
+
this.saveShared = this.saveStructures
|
|
45
55
|
let samplingPackedValues, packedObjectMap, sharedValues = options.sharedValues
|
|
46
56
|
let sharedPackedObjectMap
|
|
47
57
|
if (sharedValues) {
|
|
@@ -49,13 +59,29 @@ export class Encoder extends Decoder {
|
|
|
49
59
|
for (let i = 0, l = sharedValues.length; i < l; i++) {
|
|
50
60
|
sharedPackedObjectMap[sharedValues[i]] = i
|
|
51
61
|
}
|
|
52
|
-
|
|
53
62
|
}
|
|
54
63
|
let recordIdsToRemove = []
|
|
55
64
|
let transitionsCount = 0
|
|
56
65
|
let serializationsSinceTransitionRebuild = 0
|
|
57
|
-
|
|
58
|
-
this.
|
|
66
|
+
|
|
67
|
+
this.mapEncode = function(value, encodeOptions) {
|
|
68
|
+
// Experimental support for premapping keys using _keyMap instad of keyMap - not optiimised yet)
|
|
69
|
+
if (this._keyMap && !this._mapped) {
|
|
70
|
+
//console.log('encoding ', value)
|
|
71
|
+
switch (value.constructor.name) {
|
|
72
|
+
case 'Array':
|
|
73
|
+
value = value.map(r => this.encodeKeys(r))
|
|
74
|
+
break
|
|
75
|
+
//case 'Map':
|
|
76
|
+
// value = this.encodeKeys(value)
|
|
77
|
+
// break
|
|
78
|
+
}
|
|
79
|
+
//this._mapped = true
|
|
80
|
+
}
|
|
81
|
+
return this.encode(value, encodeOptions)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
this.encode = function(value, encodeOptions) {
|
|
59
85
|
if (!target) {
|
|
60
86
|
target = new ByteArrayAllocate(8192)
|
|
61
87
|
targetView = new DataView(target.buffer, 0, 8192)
|
|
@@ -71,11 +97,30 @@ export class Encoder extends Decoder {
|
|
|
71
97
|
} else if (encodeOptions === REUSE_BUFFER_MODE)
|
|
72
98
|
position = (position + 7) & 0x7ffffff8 // Word align to make any future copying of this buffer faster
|
|
73
99
|
start = position
|
|
100
|
+
if (encoder.useSelfDescribedHeader) {
|
|
101
|
+
targetView.setUint32(position, 0xd9d9f700) // tag two byte, then self-descriptive tag
|
|
102
|
+
position += 3
|
|
103
|
+
}
|
|
74
104
|
referenceMap = encoder.structuredClone ? new Map() : null
|
|
105
|
+
if (encoder.bundleStrings && typeof value !== 'string') {
|
|
106
|
+
bundledStrings = []
|
|
107
|
+
bundledStrings.size = Infinity // force a new bundle start on first string
|
|
108
|
+
} else
|
|
109
|
+
bundledStrings = null
|
|
110
|
+
|
|
75
111
|
sharedStructures = encoder.structures
|
|
76
112
|
if (sharedStructures) {
|
|
77
|
-
if (sharedStructures.uninitialized)
|
|
78
|
-
|
|
113
|
+
if (sharedStructures.uninitialized) {
|
|
114
|
+
let sharedData = encoder.getShared() || {}
|
|
115
|
+
encoder.structures = sharedStructures = sharedData.structures || []
|
|
116
|
+
encoder.sharedVersion = sharedData.version
|
|
117
|
+
let sharedValues = encoder.sharedValues = sharedData.packedValues
|
|
118
|
+
if (sharedValues) {
|
|
119
|
+
sharedPackedObjectMap = {}
|
|
120
|
+
for (let i = 0, l = sharedValues.length; i < l; i++)
|
|
121
|
+
sharedPackedObjectMap[sharedValues[i]] = i
|
|
122
|
+
}
|
|
123
|
+
}
|
|
79
124
|
let sharedStructuresLength = sharedStructures.length
|
|
80
125
|
if (sharedStructuresLength > maxSharedStructures && !isSequential)
|
|
81
126
|
sharedStructuresLength = maxSharedStructures
|
|
@@ -84,10 +129,13 @@ export class Encoder extends Decoder {
|
|
|
84
129
|
sharedStructures.transitions = Object.create(null)
|
|
85
130
|
for (let i = 0; i < sharedStructuresLength; i++) {
|
|
86
131
|
let keys = sharedStructures[i]
|
|
132
|
+
//console.log('shared struct keys:', keys)
|
|
87
133
|
if (!keys)
|
|
88
134
|
continue
|
|
89
135
|
let nextTransition, transition = sharedStructures.transitions
|
|
90
136
|
for (let j = 0, l = keys.length; j < l; j++) {
|
|
137
|
+
if (transition[RECORD_SYMBOL] === undefined)
|
|
138
|
+
transition[RECORD_SYMBOL] = i
|
|
91
139
|
let key = keys[j]
|
|
92
140
|
nextTransition = transition[key]
|
|
93
141
|
if (!nextTransition) {
|
|
@@ -95,9 +143,8 @@ export class Encoder extends Decoder {
|
|
|
95
143
|
}
|
|
96
144
|
transition = nextTransition
|
|
97
145
|
}
|
|
98
|
-
transition[RECORD_SYMBOL] = i
|
|
146
|
+
transition[RECORD_SYMBOL] = i | 0x100000
|
|
99
147
|
}
|
|
100
|
-
lastSharedStructuresLength = sharedStructures.length
|
|
101
148
|
}
|
|
102
149
|
if (!isSequential)
|
|
103
150
|
sharedStructures.nextId = sharedStructuresLength
|
|
@@ -130,9 +177,12 @@ export class Encoder extends Decoder {
|
|
|
130
177
|
}
|
|
131
178
|
try {
|
|
132
179
|
encode(value)
|
|
180
|
+
if (bundledStrings) {
|
|
181
|
+
writeBundles(start, encode)
|
|
182
|
+
}
|
|
133
183
|
encoder.offset = position // update the offset so next serialization doesn't write over our buffer, but can continue writing to same buffer sequentially
|
|
134
184
|
if (referenceMap && referenceMap.idsToInsert) {
|
|
135
|
-
position += referenceMap.idsToInsert.length *
|
|
185
|
+
position += referenceMap.idsToInsert.length * 2
|
|
136
186
|
if (position > safeEnd)
|
|
137
187
|
makeRoom(position)
|
|
138
188
|
encoder.offset = position
|
|
@@ -140,16 +190,18 @@ export class Encoder extends Decoder {
|
|
|
140
190
|
referenceMap = null
|
|
141
191
|
return serialized
|
|
142
192
|
}
|
|
143
|
-
if (encodeOptions
|
|
193
|
+
if (encodeOptions & REUSE_BUFFER_MODE) {
|
|
144
194
|
target.start = start
|
|
145
195
|
target.end = position
|
|
146
196
|
return target
|
|
147
197
|
}
|
|
148
|
-
return target.subarray(start, position) // position can change if we call
|
|
198
|
+
return target.subarray(start, position) // position can change if we call encode again in saveShared, so we get the buffer now
|
|
149
199
|
} finally {
|
|
150
200
|
if (sharedStructures) {
|
|
151
201
|
if (serializationsSinceTransitionRebuild < 10)
|
|
152
202
|
serializationsSinceTransitionRebuild++
|
|
203
|
+
if (sharedStructures.length > maxSharedStructures)
|
|
204
|
+
sharedStructures.length = maxSharedStructures
|
|
153
205
|
if (transitionsCount > 10000) {
|
|
154
206
|
// force a rebuild occasionally after a lot of transitions so it can get cleaned up
|
|
155
207
|
sharedStructures.transitions = null
|
|
@@ -162,35 +214,29 @@ export class Encoder extends Decoder {
|
|
|
162
214
|
recordIdsToRemove[i][RECORD_SYMBOL] = undefined
|
|
163
215
|
}
|
|
164
216
|
recordIdsToRemove = []
|
|
217
|
+
//sharedStructures.nextId = maxSharedStructures
|
|
165
218
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
// we can't rely on start/end with REUSE_BUFFER_MODE since they will (probably) change when we save
|
|
171
|
-
let returnBuffer = target.subarray(start, position)
|
|
172
|
-
let shared = encoder.structures || []
|
|
173
|
-
if (sharedValues) {
|
|
174
|
-
shared = shared.concat(sharedValues)
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (encoder.saveStructures(encoder.structures, lastSharedStructuresLength) === false) {
|
|
178
|
-
// get updated structures and try again if the update failed
|
|
179
|
-
encoder.structures = encoder.getStructures() || []
|
|
180
|
-
return encoder.encode(value)
|
|
181
|
-
}
|
|
182
|
-
lastSharedStructuresLength = shared.length
|
|
183
|
-
return returnBuffer
|
|
219
|
+
}
|
|
220
|
+
if (hasSharedUpdate && encoder.saveShared) {
|
|
221
|
+
if (encoder.structures.length > maxSharedStructures) {
|
|
222
|
+
encoder.structures = encoder.structures.slice(0, maxSharedStructures)
|
|
184
223
|
}
|
|
224
|
+
// we can't rely on start/end with REUSE_BUFFER_MODE since they will (probably) change when we save
|
|
225
|
+
let returnBuffer = target.subarray(start, position)
|
|
226
|
+
if (encoder.updateSharedData() === false)
|
|
227
|
+
return encoder.encode(value) // re-encode if it fails
|
|
228
|
+
return returnBuffer
|
|
185
229
|
}
|
|
230
|
+
if (encodeOptions & RESET_BUFFER_MODE)
|
|
231
|
+
position = start
|
|
186
232
|
}
|
|
187
233
|
}
|
|
188
234
|
this.findCommonStringsToPack = () => {
|
|
189
235
|
samplingPackedValues = new Map()
|
|
190
236
|
if (!sharedPackedObjectMap)
|
|
191
237
|
sharedPackedObjectMap = Object.create(null)
|
|
192
|
-
return (
|
|
193
|
-
threshold = threshold || 4
|
|
238
|
+
return (options) => {
|
|
239
|
+
let threshold = options && options.threshold || 4
|
|
194
240
|
let position = this.pack ? options.maxPrivatePackedValues || 16 : 0
|
|
195
241
|
if (!sharedValues)
|
|
196
242
|
sharedValues = this.sharedValues = []
|
|
@@ -201,6 +247,7 @@ export class Encoder extends Decoder {
|
|
|
201
247
|
hasSharedUpdate = true
|
|
202
248
|
}
|
|
203
249
|
}
|
|
250
|
+
while (this.saveShared && this.updateSharedData() === false) {}
|
|
204
251
|
samplingPackedValues = null
|
|
205
252
|
}
|
|
206
253
|
}
|
|
@@ -249,6 +296,33 @@ export class Encoder extends Decoder {
|
|
|
249
296
|
}
|
|
250
297
|
}
|
|
251
298
|
let strLength = value.length
|
|
299
|
+
if (bundledStrings && strLength >= 4 && strLength < 0x400) {
|
|
300
|
+
if ((bundledStrings.size += strLength) > MAX_BUNDLE_SIZE) {
|
|
301
|
+
let extStart
|
|
302
|
+
let maxBytes = (bundledStrings[0] ? bundledStrings[0].length * 3 + bundledStrings[1].length : 0) + 10
|
|
303
|
+
if (position + maxBytes > safeEnd)
|
|
304
|
+
target = makeRoom(position + maxBytes)
|
|
305
|
+
target[position++] = 0xd9 // tag 16-bit
|
|
306
|
+
target[position++] = 0xdf // tag 0xdff9
|
|
307
|
+
target[position++] = 0xf9
|
|
308
|
+
// TODO: If we only have one bundle with any string data, only write one string bundle
|
|
309
|
+
target[position++] = bundledStrings.position ? 0x84 : 0x82 // array of 4 or 2 elements depending on if we write bundles
|
|
310
|
+
target[position++] = 0x1a // 32-bit unsigned int
|
|
311
|
+
extStart = position - start
|
|
312
|
+
position += 4 // reserve for writing bundle reference
|
|
313
|
+
if (bundledStrings.position) {
|
|
314
|
+
writeBundles(start, encode) // write the last bundles
|
|
315
|
+
}
|
|
316
|
+
bundledStrings = ['', ''] // create new ones
|
|
317
|
+
bundledStrings.size = 0
|
|
318
|
+
bundledStrings.position = extStart
|
|
319
|
+
}
|
|
320
|
+
let twoByte = hasNonLatin.test(value)
|
|
321
|
+
bundledStrings[twoByte ? 0 : 1] += value
|
|
322
|
+
target[position++] = twoByte ? 0xce : 0xcf
|
|
323
|
+
encode(strLength);
|
|
324
|
+
return
|
|
325
|
+
}
|
|
252
326
|
let headerSize
|
|
253
327
|
// first we estimate the header size, so we can write to the correct location
|
|
254
328
|
if (strLength < 0x20) {
|
|
@@ -375,16 +449,16 @@ export class Encoder extends Decoder {
|
|
|
375
449
|
if (referenceMap) {
|
|
376
450
|
let referee = referenceMap.get(value)
|
|
377
451
|
if (referee) {
|
|
378
|
-
|
|
452
|
+
target[position++] = 0xd8
|
|
453
|
+
target[position++] = 29 // http://cbor.schmorp.de/value-sharing
|
|
454
|
+
target[position++] = 0x19 // 16-bit uint
|
|
455
|
+
if (!referee.references) {
|
|
379
456
|
let idsToInsert = referenceMap.idsToInsert || (referenceMap.idsToInsert = [])
|
|
380
|
-
referee.
|
|
457
|
+
referee.references = []
|
|
458
|
+
idsToInsert.push(referee)
|
|
381
459
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
target[position++] = 40010 & 0xff
|
|
385
|
-
target[position++] = 0x1a // uint32
|
|
386
|
-
targetView.setUint32(position, referee.id)
|
|
387
|
-
position += 4
|
|
460
|
+
referee.references.push(position - start)
|
|
461
|
+
position += 2 // TODO: also support 32-bit
|
|
388
462
|
return
|
|
389
463
|
} else
|
|
390
464
|
referenceMap.set(value, { offset: position - start })
|
|
@@ -424,16 +498,23 @@ export class Encoder extends Decoder {
|
|
|
424
498
|
targetView.setUint32(position, length)
|
|
425
499
|
position += 4
|
|
426
500
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
501
|
+
if (encoder.keyMap) {
|
|
502
|
+
for (let [ key, entryValue ] of value) {
|
|
503
|
+
encode(encoder.encodeKey(key))
|
|
504
|
+
encode(entryValue)
|
|
505
|
+
}
|
|
506
|
+
} else {
|
|
507
|
+
for (let [ key, entryValue ] of value) {
|
|
508
|
+
encode(key)
|
|
509
|
+
encode(entryValue)
|
|
510
|
+
}
|
|
430
511
|
}
|
|
431
|
-
} else {
|
|
512
|
+
} else {
|
|
432
513
|
for (let i = 0, l = extensions.length; i < l; i++) {
|
|
433
514
|
let extensionClass = extensionClasses[i]
|
|
434
515
|
if (value instanceof extensionClass) {
|
|
435
516
|
let extension = extensions[i]
|
|
436
|
-
let tag = extension.tag
|
|
517
|
+
let tag = extension.tag || extension.getTag && extension.getTag(value)
|
|
437
518
|
if (tag < 0x18) {
|
|
438
519
|
target[position++] = 0xc0 | tag
|
|
439
520
|
} else if (tag < 0x100) {
|
|
@@ -495,6 +576,7 @@ export class Encoder extends Decoder {
|
|
|
495
576
|
const writeObject = this.useRecords === false ? this.variableMapSize ? (object) => {
|
|
496
577
|
// this method is slightly slower, but generates "preferred serialization" (optimally small for smaller objects)
|
|
497
578
|
let keys = Object.keys(object)
|
|
579
|
+
let vals = Object.values(object)
|
|
498
580
|
let length = keys.length
|
|
499
581
|
if (length < 0x18) {
|
|
500
582
|
target[position++] = 0xa0 | length
|
|
@@ -511,9 +593,16 @@ export class Encoder extends Decoder {
|
|
|
511
593
|
position += 4
|
|
512
594
|
}
|
|
513
595
|
let key
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
596
|
+
if (encoder.keyMap) {
|
|
597
|
+
for (let i = 0; i < length; i++) {
|
|
598
|
+
encode(encodeKey(keys[i]))
|
|
599
|
+
encode(vals[i])
|
|
600
|
+
}
|
|
601
|
+
} else {
|
|
602
|
+
for (let i = 0; i < length; i++) {
|
|
603
|
+
encode(keys[i])
|
|
604
|
+
encode(vals[i])
|
|
605
|
+
}
|
|
517
606
|
}
|
|
518
607
|
} :
|
|
519
608
|
(object, safePrototype) => {
|
|
@@ -521,95 +610,92 @@ export class Encoder extends Decoder {
|
|
|
521
610
|
let objectOffset = position - start
|
|
522
611
|
position += 2
|
|
523
612
|
let size = 0
|
|
524
|
-
|
|
525
|
-
if (safePrototype || object.hasOwnProperty(key)) {
|
|
526
|
-
encode(key)
|
|
613
|
+
if (encoder.keyMap) {
|
|
614
|
+
for (let key in object) if (safePrototype || object.hasOwnProperty(key)) {
|
|
615
|
+
encode(encoder.encodeKey(key))
|
|
527
616
|
encode(object[key])
|
|
528
617
|
size++
|
|
529
618
|
}
|
|
619
|
+
} else {
|
|
620
|
+
for (let key in object) if (safePrototype || object.hasOwnProperty(key)) {
|
|
621
|
+
encode(key)
|
|
622
|
+
encode(object[key])
|
|
623
|
+
size++
|
|
624
|
+
}
|
|
530
625
|
}
|
|
531
626
|
target[objectOffset++ + start] = size >> 8
|
|
532
627
|
target[objectOffset + start] = size & 0xff
|
|
533
628
|
} :
|
|
534
|
-
|
|
535
|
-
/* sharedStructures ? // For highly stable structures, using for-in can a little bit faster
|
|
536
629
|
(object, safePrototype) => {
|
|
537
630
|
let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null))
|
|
538
|
-
let
|
|
539
|
-
let
|
|
540
|
-
|
|
541
|
-
|
|
631
|
+
let newTransitions = 0
|
|
632
|
+
let length = 0
|
|
633
|
+
let parentRecordId
|
|
634
|
+
let keys
|
|
635
|
+
if (this.keyMap) {
|
|
636
|
+
keys = Object.keys(object).map(k => this.encodeKey(k))
|
|
637
|
+
length = keys.length
|
|
638
|
+
for (let i = 0; i < length; i++) {
|
|
639
|
+
let key = keys[i]
|
|
542
640
|
nextTransition = transition[key]
|
|
543
641
|
if (!nextTransition) {
|
|
544
642
|
nextTransition = transition[key] = Object.create(null)
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
nextTransition.__keys__ = keys.slice(0, i + 1)
|
|
556
|
-
}
|
|
643
|
+
newTransitions++
|
|
644
|
+
}
|
|
645
|
+
transition = nextTransition
|
|
646
|
+
}
|
|
647
|
+
} else {
|
|
648
|
+
for (let key in object) if (safePrototype || object.hasOwnProperty(key)) {
|
|
649
|
+
nextTransition = transition[key]
|
|
650
|
+
if (!nextTransition) {
|
|
651
|
+
if (transition[RECORD_SYMBOL] & 0x100000) {// this indicates it is a brancheable/extendable terminal node, so we will use this record id and extend it
|
|
652
|
+
parentRecordId = transition[RECORD_SYMBOL] & 0xffff
|
|
557
653
|
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
target.copy(target, )
|
|
561
|
-
objectOffset
|
|
654
|
+
nextTransition = transition[key] = Object.create(null)
|
|
655
|
+
newTransitions++
|
|
562
656
|
}
|
|
563
657
|
transition = nextTransition
|
|
564
|
-
|
|
658
|
+
length++
|
|
565
659
|
}
|
|
566
660
|
}
|
|
567
|
-
let id = transition.id
|
|
568
|
-
if (!id) {
|
|
569
|
-
id = transition.id = structures.push(transition.__keys__) + 63
|
|
570
|
-
if (sharedStructures.onUpdate)
|
|
571
|
-
sharedStructures.onUpdate(id, transition.__keys__)
|
|
572
|
-
}
|
|
573
|
-
target[objectOffset + start] = id
|
|
574
|
-
}*/
|
|
575
|
-
(object) => {
|
|
576
|
-
let keys = Object.keys(object)
|
|
577
|
-
let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null))
|
|
578
|
-
let newTransitions = 0
|
|
579
|
-
let length = keys.length
|
|
580
|
-
for (let i = 0; i < length; i++) {
|
|
581
|
-
let key = keys[i]
|
|
582
|
-
nextTransition = transition[key]
|
|
583
|
-
if (!nextTransition) {
|
|
584
|
-
nextTransition = transition[key] = Object.create(null)
|
|
585
|
-
newTransitions++
|
|
586
|
-
}
|
|
587
|
-
transition = nextTransition
|
|
588
|
-
}
|
|
589
661
|
let recordId = transition[RECORD_SYMBOL]
|
|
590
662
|
if (recordId !== undefined) {
|
|
591
|
-
|
|
592
|
-
target[position++] =
|
|
593
|
-
target[position++] = recordId
|
|
663
|
+
recordId &= 0xffff
|
|
664
|
+
target[position++] = 0xd9
|
|
665
|
+
target[position++] = (recordId >> 8) | 0xe0
|
|
666
|
+
target[position++] = recordId & 0xff
|
|
594
667
|
} else {
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
structures.nextId
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
668
|
+
if (!keys)
|
|
669
|
+
keys = transition.__keys__ || (transition.__keys__ = Object.keys(object))
|
|
670
|
+
if (parentRecordId === undefined) {
|
|
671
|
+
recordId = structures.nextId++
|
|
672
|
+
if (!recordId) {
|
|
673
|
+
recordId = 0
|
|
674
|
+
structures.nextId = 1
|
|
675
|
+
}
|
|
676
|
+
if (recordId >= MAX_STRUCTURES) {// cycle back around
|
|
677
|
+
structures.nextId = (recordId = maxSharedStructures) + 1
|
|
678
|
+
}
|
|
679
|
+
} else {
|
|
680
|
+
recordId = parentRecordId
|
|
602
681
|
}
|
|
603
|
-
transition[RECORD_SYMBOL] = recordId
|
|
604
682
|
structures[recordId] = keys
|
|
605
|
-
if (
|
|
606
|
-
target[position++] = 0xd9
|
|
607
|
-
target[position++] =
|
|
608
|
-
target[position++] = recordId
|
|
683
|
+
if (recordId < maxSharedStructures) {
|
|
684
|
+
target[position++] = 0xd9
|
|
685
|
+
target[position++] = (recordId >> 8) | 0xe0
|
|
686
|
+
target[position++] = recordId & 0xff
|
|
687
|
+
transition = structures.transitions
|
|
688
|
+
for (let i = 0; i < length; i++) {
|
|
689
|
+
if (transition[RECORD_SYMBOL] === undefined || (transition[RECORD_SYMBOL] & 0x100000))
|
|
690
|
+
transition[RECORD_SYMBOL] = recordId
|
|
691
|
+
transition = transition[keys[i]]
|
|
692
|
+
}
|
|
693
|
+
transition[RECORD_SYMBOL] = recordId | 0x100000 // indicates it is a extendable terminal
|
|
609
694
|
hasSharedUpdate = true
|
|
610
695
|
} else {
|
|
611
|
-
|
|
612
|
-
|
|
696
|
+
transition[RECORD_SYMBOL] = recordId
|
|
697
|
+
targetView.setUint32(position, 0xd9dfff00) // tag two byte, then record definition id
|
|
698
|
+
position += 3
|
|
613
699
|
if (newTransitions)
|
|
614
700
|
transitionsCount += serializationsSinceTransitionRebuild * newTransitions
|
|
615
701
|
// record the removal of the id, we can maintain our shared structure
|
|
@@ -617,13 +703,9 @@ export class Encoder extends Decoder {
|
|
|
617
703
|
recordIdsToRemove.shift()[RECORD_SYMBOL] = undefined // we are cycling back through, and have to remove old ones
|
|
618
704
|
recordIdsToRemove.push(transition)
|
|
619
705
|
writeArrayHeader(length + 2)
|
|
706
|
+
encode(0xe000 + recordId)
|
|
620
707
|
encode(keys)
|
|
621
|
-
|
|
622
|
-
target[position++] = RECORD_STARTING_ID_PREFIX
|
|
623
|
-
target[position++] = recordId
|
|
624
|
-
// now write the values
|
|
625
|
-
for (let i =0; i < length; i++)
|
|
626
|
-
encode(object[keys[i]])
|
|
708
|
+
for (let v of Object.values(object)) encode(v)
|
|
627
709
|
return
|
|
628
710
|
}
|
|
629
711
|
}
|
|
@@ -632,8 +714,9 @@ export class Encoder extends Decoder {
|
|
|
632
714
|
} else {
|
|
633
715
|
writeArrayHeader(length)
|
|
634
716
|
}
|
|
635
|
-
for (let
|
|
636
|
-
|
|
717
|
+
for (let key in object)
|
|
718
|
+
if (safePrototype || object.hasOwnProperty(key))
|
|
719
|
+
encode(object[key])
|
|
637
720
|
}
|
|
638
721
|
const makeRoom = (end) => {
|
|
639
722
|
let newSize
|
|
@@ -663,11 +746,39 @@ export class Encoder extends Decoder {
|
|
|
663
746
|
targetView = new DataView(target.buffer, target.byteOffset, target.byteLength)
|
|
664
747
|
position = 0
|
|
665
748
|
}
|
|
749
|
+
clearSharedData() {
|
|
750
|
+
if (this.structures)
|
|
751
|
+
this.structures = []
|
|
752
|
+
if (this.sharedValues)
|
|
753
|
+
this.sharedValues = undefined
|
|
754
|
+
}
|
|
755
|
+
updateSharedData() {
|
|
756
|
+
let lastVersion = this.sharedVersion || 0
|
|
757
|
+
this.sharedVersion = lastVersion + 1
|
|
758
|
+
let structuresCopy = this.structures.slice(0)
|
|
759
|
+
let sharedData = new SharedData(structuresCopy, this.sharedValues, this.sharedVersion)
|
|
760
|
+
let saveResults = this.saveShared(sharedData,
|
|
761
|
+
existingShared => (existingShared && existingShared.version || 0) == lastVersion)
|
|
762
|
+
if (saveResults === false) {
|
|
763
|
+
// get updated structures and try again if the update failed
|
|
764
|
+
sharedData = this.getShared() || {}
|
|
765
|
+
this.structures = sharedData.structures || []
|
|
766
|
+
this.sharedValues = sharedData.packedValues
|
|
767
|
+
this.sharedVersion = sharedData.version
|
|
768
|
+
this.structures.nextId = this.structures.length
|
|
769
|
+
} else {
|
|
770
|
+
// restore structures
|
|
771
|
+
structuresCopy.forEach((structure, i) => this.structures[i] = structure)
|
|
772
|
+
}
|
|
773
|
+
// saveShared may fail to write and reload, or may have reloaded to check compatibility and overwrite saved data, either way load the correct shared data
|
|
774
|
+
return saveResults
|
|
775
|
+
}
|
|
666
776
|
}
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
777
|
+
class SharedData {
|
|
778
|
+
constructor(structures, values, version) {
|
|
779
|
+
this.structures = structures
|
|
780
|
+
this.packedValues = values
|
|
781
|
+
this.version = version
|
|
671
782
|
}
|
|
672
783
|
}
|
|
673
784
|
|
|
@@ -737,12 +848,12 @@ function findRepetitiveStrings(value, packedValues) {
|
|
|
737
848
|
case 'function': console.log(value)
|
|
738
849
|
}
|
|
739
850
|
}
|
|
740
|
-
|
|
741
|
-
extensionClasses = [ Date, Set, Error, RegExp, ArrayBuffer, ByteArray,
|
|
851
|
+
const isLittleEndianMachine = new Uint8Array(new Uint16Array([1]).buffer)[0] == 1
|
|
852
|
+
extensionClasses = [ Date, Set, Error, RegExp, Tag, ArrayBuffer, ByteArray,
|
|
742
853
|
Uint8Array, Uint8ClampedArray, Uint16Array, Uint32Array,
|
|
743
854
|
typeof BigUint64Array == 'undefined' ? function() {} : BigUint64Array, Int8Array, Int16Array, Int32Array,
|
|
744
855
|
typeof BigInt64Array == 'undefined' ? function() {} : BigInt64Array,
|
|
745
|
-
Float32Array, Float64Array]
|
|
856
|
+
Float32Array, Float64Array, SharedData]
|
|
746
857
|
|
|
747
858
|
//Object.getPrototypeOf(Uint8Array.prototype).constructor /*TypedArray*/
|
|
748
859
|
extensions = [{
|
|
@@ -777,6 +888,13 @@ extensions = [{
|
|
|
777
888
|
encode(regex, encode) {
|
|
778
889
|
encode([ 'RegExp', regex.source, regex.flags ])
|
|
779
890
|
}
|
|
891
|
+
}, {
|
|
892
|
+
getTag(tag) {
|
|
893
|
+
return tag.tag
|
|
894
|
+
},
|
|
895
|
+
encode(tag, encode) {
|
|
896
|
+
encode(tag.value)
|
|
897
|
+
}
|
|
780
898
|
}, {
|
|
781
899
|
encode(arrayBuffer, encode, makeRoom) {
|
|
782
900
|
writeBuffer(arrayBuffer, makeRoom)
|
|
@@ -785,19 +903,48 @@ extensions = [{
|
|
|
785
903
|
encode(arrayBuffer, encode, makeRoom) {
|
|
786
904
|
writeBuffer(arrayBuffer, makeRoom)
|
|
787
905
|
}
|
|
788
|
-
}, typedArrayEncoder(64),
|
|
789
|
-
typedArrayEncoder(68),
|
|
790
|
-
typedArrayEncoder(69),
|
|
791
|
-
typedArrayEncoder(70),
|
|
792
|
-
typedArrayEncoder(71),
|
|
793
|
-
typedArrayEncoder(72),
|
|
794
|
-
typedArrayEncoder(77),
|
|
795
|
-
typedArrayEncoder(78),
|
|
796
|
-
typedArrayEncoder(79),
|
|
797
|
-
typedArrayEncoder(
|
|
798
|
-
typedArrayEncoder(
|
|
799
|
-
|
|
800
|
-
|
|
906
|
+
}, typedArrayEncoder(64, 1),
|
|
907
|
+
typedArrayEncoder(68, 1),
|
|
908
|
+
typedArrayEncoder(69, 2),
|
|
909
|
+
typedArrayEncoder(70, 4),
|
|
910
|
+
typedArrayEncoder(71, 8),
|
|
911
|
+
typedArrayEncoder(72, 1),
|
|
912
|
+
typedArrayEncoder(77, 2),
|
|
913
|
+
typedArrayEncoder(78, 4),
|
|
914
|
+
typedArrayEncoder(79, 8),
|
|
915
|
+
typedArrayEncoder(85, 4),
|
|
916
|
+
typedArrayEncoder(86, 8),
|
|
917
|
+
{
|
|
918
|
+
encode(sharedData, encode) { // write SharedData
|
|
919
|
+
let packedValues = sharedData.packedValues || []
|
|
920
|
+
let sharedStructures = sharedData.structures || []
|
|
921
|
+
if (packedValues.values.length > 0) {
|
|
922
|
+
target[position++] = 0xd8 // one-byte tag
|
|
923
|
+
target[position++] = 51 // tag 51 for packed shared structures https://www.potaroo.net/ietf/ids/draft-ietf-cbor-packed-03.txt
|
|
924
|
+
writeArrayHeader(4)
|
|
925
|
+
let valuesArray = packedValues.values
|
|
926
|
+
encode(valuesArray)
|
|
927
|
+
writeArrayHeader(0) // prefixes
|
|
928
|
+
writeArrayHeader(0) // suffixes
|
|
929
|
+
packedObjectMap = Object.create(sharedPackedObjectMap || null)
|
|
930
|
+
for (let i = 0, l = valuesArray.length; i < l; i++) {
|
|
931
|
+
packedObjectMap[valuesArray[i]] = i
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
if (sharedStructures) {
|
|
935
|
+
targetView.setUint32(position, 0xd9dffe00)
|
|
936
|
+
position += 3
|
|
937
|
+
let definitions = sharedStructures.slice(0)
|
|
938
|
+
definitions.unshift(0xe000)
|
|
939
|
+
definitions.push(new Tag(sharedData.version, 0x53687264))
|
|
940
|
+
encode(definitions)
|
|
941
|
+
} else
|
|
942
|
+
encode(new Tag(sharedData.version, 0x53687264))
|
|
943
|
+
}
|
|
944
|
+
}]
|
|
945
|
+
function typedArrayEncoder(tag, size) {
|
|
946
|
+
if (!isLittleEndianMachine && size > 1)
|
|
947
|
+
tag -= 4 // the big endian equivalents are 4 less
|
|
801
948
|
return {
|
|
802
949
|
tag: tag,
|
|
803
950
|
encode: function writeExtBuffer(typedArray, encode) {
|
|
@@ -835,27 +982,35 @@ function writeBuffer(buffer, makeRoom) {
|
|
|
835
982
|
function insertIds(serialized, idsToInsert) {
|
|
836
983
|
// insert the ids that need to be referenced for structured clones
|
|
837
984
|
let nextId
|
|
838
|
-
let distanceToMove = idsToInsert.length *
|
|
985
|
+
let distanceToMove = idsToInsert.length * 2
|
|
839
986
|
let lastEnd = serialized.length - distanceToMove
|
|
840
987
|
idsToInsert.sort((a, b) => a.offset > b.offset ? 1 : -1)
|
|
988
|
+
for (let id = 0; id < idsToInsert.length; id++) {
|
|
989
|
+
let referee = idsToInsert[id]
|
|
990
|
+
referee.id = id
|
|
991
|
+
for (let position of referee.references) {
|
|
992
|
+
serialized[position++] = id >> 8
|
|
993
|
+
serialized[position] = id & 0xff
|
|
994
|
+
}
|
|
995
|
+
}
|
|
841
996
|
while (nextId = idsToInsert.pop()) {
|
|
842
997
|
let offset = nextId.offset
|
|
843
|
-
let id = nextId.id
|
|
844
998
|
serialized.copyWithin(offset + distanceToMove, offset, lastEnd)
|
|
845
|
-
distanceToMove -=
|
|
999
|
+
distanceToMove -= 2
|
|
846
1000
|
let position = offset + distanceToMove
|
|
847
|
-
serialized[position++] =
|
|
848
|
-
serialized[position++] =
|
|
849
|
-
serialized[position++] = 40009 & 0xff
|
|
850
|
-
serialized[position++] = 0x1a // uint32
|
|
851
|
-
serialized[position++] = id >> 24
|
|
852
|
-
serialized[position++] = (id >> 16) & 0xff
|
|
853
|
-
serialized[position++] = (id >> 8) & 0xff
|
|
854
|
-
serialized[position++] = id & 0xff
|
|
1001
|
+
serialized[position++] = 0xd8
|
|
1002
|
+
serialized[position++] = 28 // http://cbor.schmorp.de/value-sharing
|
|
855
1003
|
lastEnd = offset
|
|
856
1004
|
}
|
|
857
1005
|
return serialized
|
|
858
1006
|
}
|
|
1007
|
+
function writeBundles(start, encode) {
|
|
1008
|
+
targetView.setUint32(bundledStrings.position + start, position - bundledStrings.position - start + 1) // the offset to bundle
|
|
1009
|
+
let writeStrings = bundledStrings
|
|
1010
|
+
bundledStrings = null
|
|
1011
|
+
encode(writeStrings[0])
|
|
1012
|
+
encode(writeStrings[1])
|
|
1013
|
+
}
|
|
859
1014
|
|
|
860
1015
|
export function addExtension(extension) {
|
|
861
1016
|
if (extension.Class) {
|
|
@@ -871,4 +1026,6 @@ export const encode = defaultEncoder.encode
|
|
|
871
1026
|
export { FLOAT32_OPTIONS } from './decode.js'
|
|
872
1027
|
import { FLOAT32_OPTIONS } from './decode.js'
|
|
873
1028
|
export const { NEVER, ALWAYS, DECIMAL_ROUND, DECIMAL_FIT } = FLOAT32_OPTIONS
|
|
874
|
-
export const REUSE_BUFFER_MODE =
|
|
1029
|
+
export const REUSE_BUFFER_MODE = 512
|
|
1030
|
+
export const RESET_BUFFER_MODE = 1024
|
|
1031
|
+
|