cbor-x 1.0.0 → 1.2.1

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 CHANGED
@@ -7,10 +7,9 @@
7
7
  [![module](https://img.shields.io/badge/module-ESM%2FCJS-blue)](README.md)
8
8
  [![license](https://img.shields.io/badge/license-MIT-brightgreen)](LICENSE)
9
9
 
10
-
11
- The cbor-x package is an extremely fast and conformant CBOR NodeJS/JavaScript implementation. Currently, it is over 3-10x faster than any other CBOR JS implementation (including cbor-js and cborg) and faster than most MessagePack encoders, Avro, and generally faster than native V8 JSON.stringify/parse, on NodeJS. It implements the CBOR format as specificed in [RFC-8949](https://www.rfc-editor.org/rfc/rfc8949.html), [RFC-8746](https://tools.ietf.org/html/rfc8746), [RFC-8742](https://datatracker.ietf.org/doc/html/rfc8742), [Packed CBOR](https://www.ietf.org/id/draft-ietf-cbor-packed-03.html), numerous [registered IANA tag extensions](https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml) (the `x` in cbor-x), and proposed optional [record extension](https://github.com/kriszyp/cbor-records), for defining record structures that makes CBOR even faster and more compact, often over twice as fast as even native JSON functions, and 15-50% more compact. See the performance section for more details. Structured cloning (with support for cyclical references) is supported through these tag extensions.
12
10
  <img align="right" src="./assets/performance.png" width="380"/>
13
11
 
12
+ The cbor-x package is an extremely fast and conformant CBOR NodeJS/JavaScript implementation. Currently, it is over 3-10x faster than any other CBOR JS implementation (including cbor-js and cborg) and faster than most MessagePack encoders, Avro, and generally faster than native V8 JSON.stringify/parse, on NodeJS. It implements the CBOR format as specificed in [RFC-8949](https://www.rfc-editor.org/rfc/rfc8949.html), [RFC-8746](https://tools.ietf.org/html/rfc8746), [RFC-8742](https://datatracker.ietf.org/doc/html/rfc8742), [Packed CBOR](https://www.ietf.org/id/draft-ietf-cbor-packed-03.html), numerous [registered IANA tag extensions](https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml) (the `x` in cbor-x), and proposed optional [record extension](https://github.com/kriszyp/cbor-records), for defining record structures that makes CBOR even faster and more compact, often over twice as fast as even native JSON functions, and 15-50% more compact. See the performance section for more details. Structured cloning (with support for cyclical references) is supported through these tag extensions.
14
13
 
15
14
  ## Basic Usage
16
15
  Install on NodeJS with:
package/decode.d.ts CHANGED
@@ -19,6 +19,7 @@ export interface Options {
19
19
  encodeUndefinedAsNil?: boolean
20
20
  maxSharedStructures?: number
21
21
  maxOwnStructures?: number
22
+ useSelfDescribedHeader?: boolean
22
23
  shouldShareStructure?: (keys: string[]) => boolean
23
24
  getStructures?(): {}[]
24
25
  saveStructures?(structures: {}[]): boolean | void
package/decode.js CHANGED
@@ -254,7 +254,11 @@ export function read() {
254
254
  position += 8
255
255
  return value
256
256
  }
257
- if (currentDecoder.int64AsNumber) {
257
+ if (majorType > 1) {
258
+ if (dataView.getUint32(position) > 0)
259
+ throw new Error('JavaScript does not support arrays, maps, or strings with length over 4294967295')
260
+ token = dataView.getUint32(position + 4)
261
+ } else if (currentDecoder.int64AsNumber) {
258
262
  token = dataView.getUint32(position) * 0x100000000
259
263
  token += dataView.getUint32(position + 4)
260
264
  } else
@@ -434,21 +438,30 @@ function createStructureReader(structure) {
434
438
  }
435
439
  }
436
440
  // This initial function is quick to instantiate, but runs slower. After several iterations pay the cost to build the faster function
437
- if (this.objectLiteralSize === length) // we have a fast object literal reader, use it (assuming it is the right length)
438
- return this.objectLiteral(read)
439
- if (this.count++ == 3) { // create a fast reader
440
- this.objectLiteralSize = length
441
- this.objectLiteral = currentDecoder.keyMap
442
- ? new Function('r', 'return {' + this.map(k => currentDecoder.decodeKey(k)).map(k => validName.test(k) ? k + ':r()' : ('[' + JSON.stringify(k) + ']:r()')).join(',') + '}')
443
- : new Function('r', 'return {' + this.map(key => validName.test(key) ? key + ':r()' : ('[' + JSON.stringify(key) + ']:r()')).join(',') + '}')
444
- return this.objectLiteral(read)
441
+ let compiledReader = this.compiledReader // first look to see if we have the fast compiled function
442
+ while(compiledReader) {
443
+ // we have a fast compiled object literal reader
444
+ if (compiledReader.propertyCount === length)
445
+ return compiledReader(read) // with the right length, so we use it
446
+ compiledReader = compiledReader.next // see if there is another reader with the right length
447
+ }
448
+ if (this.slowReads++ >= 3) { // create a fast compiled reader
449
+ let array = this.length == length ? this : this.slice(0, length)
450
+ compiledReader = currentDecoder.keyMap
451
+ ? new Function('r', 'return {' + array.map(k => currentDecoder.decodeKey(k)).map(k => validName.test(k) ? k + ':r()' : ('[' + JSON.stringify(k) + ']:r()')).join(',') + '}')
452
+ : new Function('r', 'return {' + array.map(key => validName.test(key) ? key + ':r()' : ('[' + JSON.stringify(key) + ']:r()')).join(',') + '}')
453
+ if (this.compiledReader)
454
+ compiledReader.next = this.compiledReader // if there is an existing one, we store multiple readers as a linked list because it is usually pretty rare to have multiple readers (of different length) for the same structure
455
+ compiledReader.propertyCount = length
456
+ this.compiledReader = compiledReader
457
+ return compiledReader(read)
445
458
  }
446
459
  let object = {}
447
460
  if (currentDecoder.keyMap) for (let i = 0; i < length; i++) object[currentDecoder.decodeKey(this[i])] = read()
448
461
  else for (let i = 0; i < length; i++) object[this[i]] = read()
449
462
  return object
450
463
  }
451
- structure.count = 0
464
+ structure.slowReads = 0
452
465
  return readObject
453
466
  }
454
467
 
@@ -811,7 +824,7 @@ currentExtensions[0] = (dateString) => {
811
824
 
812
825
  currentExtensions[1] = (epochSec) => {
813
826
  // numeric date extension
814
- return new Date(epochSec * 1000)
827
+ return new Date(Math.round(epochSec * 1000))
815
828
  }
816
829
 
817
830
  currentExtensions[2] = (buffer) => {
@@ -837,11 +850,16 @@ currentExtensions[5] = (fraction) => {
837
850
  return fraction[1] * Math.exp(fraction[0] * Math.log(2))
838
851
  }
839
852
 
840
- // the registration of the record definition extension (tag 105)
853
+ // the registration of the record definition extension
841
854
  const recordDefinition = (definition) => {
842
855
  let id = definition[0] - 0xe000
843
856
  let structure = definition[1]
857
+ let existingStructure = currentStructures[id]
858
+ if (existingStructure && existingStructure.isShared) {
859
+ (currentStructures.restoreStructures || (currentStructures.restoreStructures = []))[id] = existingStructure
860
+ }
844
861
  currentStructures[id] = structure
862
+
845
863
  structure.read = createStructureReader(structure)
846
864
  let object = {}
847
865
  if (currentDecoder.keyMap) for (let i = 2,l = definition.length; i < l; i++) {
@@ -985,19 +1003,40 @@ currentExtensionRanges.push((tag, input) => {
985
1003
  version: input,
986
1004
  }
987
1005
  }
1006
+ if (tag == 55799) // self-descriptive CBOR tag, just return input value
1007
+ return input
988
1008
  })
989
1009
 
990
- export const typedArrays = ['Uint8', 'Uint8Clamped', 'Uint16', 'Uint32', 'BigUint64','Int8', 'Int16', 'Int32', 'BigInt64', 'Float32', 'Float64'].map(type => type + 'Array')
991
- const typedArrayTags = [64, 68, 69, 70, 71, 72, 77, 78, 79, 81, 82]
1010
+ const isLittleEndianMachine = new Uint8Array(new Uint16Array([1]).buffer)[0] == 1
1011
+ export const typedArrays = ['Uint8', 'Uint8Clamped', 'Uint16', 'Uint32', 'BigUint64','Int8', 'Int16', 'Int32', 'BigInt64', 'Float32', 'Float64']
1012
+ const typedArrayTags = [64, 68, 69, 70, 71, 72, 77, 78, 79, 85, 86]
992
1013
  for (let i = 0; i < typedArrays.length; i++) {
993
1014
  registerTypedArray(typedArrays[i], typedArrayTags[i])
994
1015
  }
995
- function registerTypedArray(typedArrayName, tag) {
996
- currentExtensions[tag] = (buffer) => {
997
- if (!typedArrayName)
998
- throw new Error('Could not find typed array for code ' + typeCode)
999
- // we have to always slice/copy here to get a new ArrayBuffer that is word/byte aligned
1000
- return new glbl[typedArrayName](Uint8Array.prototype.slice.call(buffer, 0).buffer)
1016
+ function registerTypedArray(typedArrayId, tag, littleEndian) {
1017
+ let TypedArray = glbl[typedArrayId + 'Array']
1018
+ let bytesPerElement = TypedArray.BYTES_PER_ELEMENT
1019
+ for (let littleEndian = 0; littleEndian < 2; littleEndian++) {
1020
+ if (!littleEndian && bytesPerElement == 1)
1021
+ continue
1022
+ let sizeShift = bytesPerElement == 2 ? 1 : bytesPerElement == 4 ? 2 : 3
1023
+ currentExtensions[littleEndian ? tag : (tag - 4)] = (bytesPerElement == 1 || littleEndian == isLittleEndianMachine) ? (buffer) => {
1024
+ if (!TypedArray)
1025
+ throw new Error('Could not find typed array for code ' + tag)
1026
+ // we have to always slice/copy here to get a new ArrayBuffer that is word/byte aligned
1027
+ return new TypedArray(Uint8Array.prototype.slice.call(buffer, 0).buffer)
1028
+ } : buffer => {
1029
+ if (!TypedArray)
1030
+ throw new Error('Could not find typed array for code ' + tag)
1031
+ let dv = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength)
1032
+ let elements = buffer.length >> sizeShift
1033
+ let ta = new TypedArray(elements)
1034
+ let method = dv['get' + typedArrayId]
1035
+ for (let i = 0; i < elements; i++) {
1036
+ ta[i] = method.call(dv, i << sizeShift, littleEndian)
1037
+ }
1038
+ return ta
1039
+ }
1001
1040
  }
1002
1041
  }
1003
1042
 
package/dist/index.js CHANGED
@@ -250,7 +250,11 @@
250
250
  position += 8;
251
251
  return value
252
252
  }
253
- if (currentDecoder.int64AsNumber) {
253
+ if (majorType > 1) {
254
+ if (dataView.getUint32(position) > 0)
255
+ throw new Error('JavaScript does not support arrays, maps, or strings with length over 4294967295')
256
+ token = dataView.getUint32(position + 4);
257
+ } else if (currentDecoder.int64AsNumber) {
254
258
  token = dataView.getUint32(position) * 0x100000000;
255
259
  token += dataView.getUint32(position + 4);
256
260
  } else
@@ -430,21 +434,30 @@
430
434
  }
431
435
  }
432
436
  // This initial function is quick to instantiate, but runs slower. After several iterations pay the cost to build the faster function
433
- if (this.objectLiteralSize === length) // we have a fast object literal reader, use it (assuming it is the right length)
434
- return this.objectLiteral(read)
435
- if (this.count++ == 3) { // create a fast reader
436
- this.objectLiteralSize = length;
437
- this.objectLiteral = currentDecoder.keyMap
438
- ? new Function('r', 'return {' + this.map(k => currentDecoder.decodeKey(k)).map(k => validName.test(k) ? k + ':r()' : ('[' + JSON.stringify(k) + ']:r()')).join(',') + '}')
439
- : new Function('r', 'return {' + this.map(key => validName.test(key) ? key + ':r()' : ('[' + JSON.stringify(key) + ']:r()')).join(',') + '}');
440
- return this.objectLiteral(read)
437
+ let compiledReader = this.compiledReader; // first look to see if we have the fast compiled function
438
+ while(compiledReader) {
439
+ // we have a fast compiled object literal reader
440
+ if (compiledReader.propertyCount === length)
441
+ return compiledReader(read) // with the right length, so we use it
442
+ compiledReader = compiledReader.next; // see if there is another reader with the right length
443
+ }
444
+ if (this.slowReads++ >= 3) { // create a fast compiled reader
445
+ let array = this.length == length ? this : this.slice(0, length);
446
+ compiledReader = currentDecoder.keyMap
447
+ ? new Function('r', 'return {' + array.map(k => currentDecoder.decodeKey(k)).map(k => validName.test(k) ? k + ':r()' : ('[' + JSON.stringify(k) + ']:r()')).join(',') + '}')
448
+ : new Function('r', 'return {' + array.map(key => validName.test(key) ? key + ':r()' : ('[' + JSON.stringify(key) + ']:r()')).join(',') + '}');
449
+ if (this.compiledReader)
450
+ compiledReader.next = this.compiledReader; // if there is an existing one, we store multiple readers as a linked list because it is usually pretty rare to have multiple readers (of different length) for the same structure
451
+ compiledReader.propertyCount = length;
452
+ this.compiledReader = compiledReader;
453
+ return compiledReader(read)
441
454
  }
442
455
  let object = {};
443
456
  if (currentDecoder.keyMap) for (let i = 0; i < length; i++) object[currentDecoder.decodeKey(this[i])] = read();
444
457
  else for (let i = 0; i < length; i++) object[this[i]] = read();
445
458
  return object
446
459
  }
447
- structure.count = 0;
460
+ structure.slowReads = 0;
448
461
  return readObject
449
462
  }
450
463
 
@@ -696,7 +709,7 @@
696
709
 
697
710
  currentExtensions[1] = (epochSec) => {
698
711
  // numeric date extension
699
- return new Date(epochSec * 1000)
712
+ return new Date(Math.round(epochSec * 1000))
700
713
  };
701
714
 
702
715
  currentExtensions[2] = (buffer) => {
@@ -722,11 +735,16 @@
722
735
  return fraction[1] * Math.exp(fraction[0] * Math.log(2))
723
736
  };
724
737
 
725
- // the registration of the record definition extension (tag 105)
738
+ // the registration of the record definition extension
726
739
  const recordDefinition = (definition) => {
727
740
  let id = definition[0] - 0xe000;
728
741
  let structure = definition[1];
742
+ let existingStructure = currentStructures[id];
743
+ if (existingStructure && existingStructure.isShared) {
744
+ (currentStructures.restoreStructures || (currentStructures.restoreStructures = []))[id] = existingStructure;
745
+ }
729
746
  currentStructures[id] = structure;
747
+
730
748
  structure.read = createStructureReader(structure);
731
749
  let object = {};
732
750
  if (currentDecoder.keyMap) for (let i = 2,l = definition.length; i < l; i++) {
@@ -870,20 +888,41 @@
870
888
  version: input,
871
889
  }
872
890
  }
891
+ if (tag == 55799) // self-descriptive CBOR tag, just return input value
892
+ return input
873
893
  });
874
894
 
875
- const typedArrays = ['Uint8', 'Uint8Clamped', 'Uint16', 'Uint32', 'BigUint64','Int8', 'Int16', 'Int32', 'BigInt64', 'Float32', 'Float64'].map(type => type + 'Array');
876
- const typedArrayTags = [64, 68, 69, 70, 71, 72, 77, 78, 79, 81, 82];
895
+ const isLittleEndianMachine = new Uint8Array(new Uint16Array([1]).buffer)[0] == 1;
896
+ const typedArrays = ['Uint8', 'Uint8Clamped', 'Uint16', 'Uint32', 'BigUint64','Int8', 'Int16', 'Int32', 'BigInt64', 'Float32', 'Float64'];
897
+ const typedArrayTags = [64, 68, 69, 70, 71, 72, 77, 78, 79, 85, 86];
877
898
  for (let i = 0; i < typedArrays.length; i++) {
878
899
  registerTypedArray(typedArrays[i], typedArrayTags[i]);
879
900
  }
880
- function registerTypedArray(typedArrayName, tag) {
881
- currentExtensions[tag] = (buffer) => {
882
- if (!typedArrayName)
883
- throw new Error('Could not find typed array for code ' + typeCode)
884
- // we have to always slice/copy here to get a new ArrayBuffer that is word/byte aligned
885
- return new glbl[typedArrayName](Uint8Array.prototype.slice.call(buffer, 0).buffer)
886
- };
901
+ function registerTypedArray(typedArrayId, tag, littleEndian) {
902
+ let TypedArray = glbl[typedArrayId + 'Array'];
903
+ let bytesPerElement = TypedArray.BYTES_PER_ELEMENT;
904
+ for (let littleEndian = 0; littleEndian < 2; littleEndian++) {
905
+ if (!littleEndian && bytesPerElement == 1)
906
+ continue
907
+ let sizeShift = bytesPerElement == 2 ? 1 : bytesPerElement == 4 ? 2 : 3;
908
+ currentExtensions[littleEndian ? tag : (tag - 4)] = (bytesPerElement == 1 || littleEndian == isLittleEndianMachine) ? (buffer) => {
909
+ if (!TypedArray)
910
+ throw new Error('Could not find typed array for code ' + tag)
911
+ // we have to always slice/copy here to get a new ArrayBuffer that is word/byte aligned
912
+ return new TypedArray(Uint8Array.prototype.slice.call(buffer, 0).buffer)
913
+ } : buffer => {
914
+ if (!TypedArray)
915
+ throw new Error('Could not find typed array for code ' + tag)
916
+ let dv = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
917
+ let elements = buffer.length >> sizeShift;
918
+ let ta = new TypedArray(elements);
919
+ let method = dv['get' + typedArrayId];
920
+ for (let i = 0; i < elements; i++) {
921
+ ta[i] = method.call(dv, i << sizeShift, littleEndian);
922
+ }
923
+ return ta
924
+ };
925
+ }
887
926
  }
888
927
 
889
928
  function readBundleExt() {
@@ -1037,12 +1076,18 @@
1037
1076
  } : false;
1038
1077
 
1039
1078
  let encoder = this;
1040
- let maxSharedStructures = options.maxSharedStructures || 128;
1079
+ let hasSharedStructures = options.structures || options.saveStructures;
1080
+ let maxSharedStructures = options.maxSharedStructures;
1081
+ if (maxSharedStructures == null)
1082
+ maxSharedStructures = hasSharedStructures ? 128 : 0;
1083
+ if (maxSharedStructures > 8190)
1084
+ throw new Error('Maximum maxSharedStructure is 8190')
1041
1085
  let isSequential = options.sequential;
1042
1086
  if (isSequential) {
1043
1087
  maxSharedStructures = 0;
1044
- this.structures = [];
1045
1088
  }
1089
+ if (!this.structures)
1090
+ this.structures = [];
1046
1091
  if (this.saveStructures)
1047
1092
  this.saveShared = this.saveStructures;
1048
1093
  let samplingPackedValues, packedObjectMap, sharedValues = options.sharedValues;
@@ -1090,6 +1135,10 @@
1090
1135
  } else if (encodeOptions === REUSE_BUFFER_MODE)
1091
1136
  position$1 = (position$1 + 7) & 0x7ffffff8; // Word align to make any future copying of this buffer faster
1092
1137
  start = position$1;
1138
+ if (encoder.useSelfDescribedHeader) {
1139
+ targetView.setUint32(position$1, 0xd9d9f700); // tag two byte, then self-descriptive tag
1140
+ position$1 += 3;
1141
+ }
1093
1142
  referenceMap = encoder.structuredClone ? new Map() : null;
1094
1143
  if (encoder.bundleStrings && typeof value !== 'string') {
1095
1144
  bundledStrings$1 = [];
@@ -1123,6 +1172,8 @@
1123
1172
  continue
1124
1173
  let nextTransition, transition = sharedStructures.transitions;
1125
1174
  for (let j = 0, l = keys.length; j < l; j++) {
1175
+ if (transition[RECORD_SYMBOL] === undefined)
1176
+ transition[RECORD_SYMBOL] = i;
1126
1177
  let key = keys[j];
1127
1178
  nextTransition = transition[key];
1128
1179
  if (!nextTransition) {
@@ -1130,7 +1181,7 @@
1130
1181
  }
1131
1182
  transition = nextTransition;
1132
1183
  }
1133
- transition[RECORD_SYMBOL] = i;
1184
+ transition[RECORD_SYMBOL] = i | 0x100000;
1134
1185
  }
1135
1186
  }
1136
1187
  if (!isSequential)
@@ -1182,11 +1233,13 @@
1182
1233
  target.end = position$1;
1183
1234
  return target
1184
1235
  }
1185
- return target.subarray(start, position$1) // position can change if we call pack again in saveShared, so we get the buffer now
1236
+ return target.subarray(start, position$1) // position can change if we call encode again in saveShared, so we get the buffer now
1186
1237
  } finally {
1187
1238
  if (sharedStructures) {
1188
1239
  if (serializationsSinceTransitionRebuild < 10)
1189
1240
  serializationsSinceTransitionRebuild++;
1241
+ if (sharedStructures.length > maxSharedStructures)
1242
+ sharedStructures.length = maxSharedStructures;
1190
1243
  if (transitionsCount > 10000) {
1191
1244
  // force a rebuild occasionally after a lot of transitions so it can get cleaned up
1192
1245
  sharedStructures.transitions = null;
@@ -1199,6 +1252,7 @@
1199
1252
  recordIdsToRemove[i][RECORD_SYMBOL] = undefined;
1200
1253
  }
1201
1254
  recordIdsToRemove = [];
1255
+ //sharedStructures.nextId = maxSharedStructures
1202
1256
  }
1203
1257
  }
1204
1258
  if (hasSharedUpdate && encoder.saveShared) {
@@ -1498,7 +1552,9 @@
1498
1552
  let extensionClass = extensionClasses[i];
1499
1553
  if (value instanceof extensionClass) {
1500
1554
  let extension = extensions[i];
1501
- let tag = extension.tag || extension.getTag && extension.getTag(value);
1555
+ let tag = extension.tag;
1556
+ if (tag == undefined)
1557
+ tag = extension.getTag && extension.getTag(value);
1502
1558
  if (tag < 0x18) {
1503
1559
  target[position$1++] = 0xc0 | tag;
1504
1560
  } else if (tag < 0x100) {
@@ -1609,88 +1665,74 @@
1609
1665
  target[objectOffset++ + start] = size >> 8;
1610
1666
  target[objectOffset + start] = size & 0xff;
1611
1667
  } :
1612
-
1613
- /* sharedStructures ? // For highly stable structures, using for-in can a little bit faster
1614
1668
  (object, safePrototype) => {
1615
- let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null))
1616
- let objectOffset = position++ - start
1617
- let wroteKeys
1618
- for (let key in object) {
1619
- if (safePrototype || object.hasOwnProperty(key)) {
1620
- nextTransition = transition[key]
1669
+ let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null));
1670
+ let newTransitions = 0;
1671
+ let length = 0;
1672
+ let parentRecordId;
1673
+ let keys;
1674
+ if (this.keyMap) {
1675
+ keys = Object.keys(object).map(k => this.encodeKey(k));
1676
+ length = keys.length;
1677
+ for (let i = 0; i < length; i++) {
1678
+ let key = keys[i];
1679
+ nextTransition = transition[key];
1621
1680
  if (!nextTransition) {
1622
- nextTransition = transition[key] = Object.create(null)
1623
- nextTransition.__keys__ = (transition.__keys__ || []).concat([key])
1624
- /*let keys = Object.keys(object)
1625
- if
1626
- let size = 0
1627
- let startBranch = transition.__keys__ ? transition.__keys__.length : 0
1628
- for (let i = 0, l = keys.length; i++) {
1629
- let key = keys[i]
1630
- size += key.length << 2
1631
- if (i >= startBranch) {
1632
- nextTransition = nextTransition[key] = Object.create(null)
1633
- nextTransition.__keys__ = keys.slice(0, i + 1)
1634
- }
1681
+ nextTransition = transition[key] = Object.create(null);
1682
+ newTransitions++;
1683
+ }
1684
+ transition = nextTransition;
1685
+ }
1686
+ } else {
1687
+ for (let key in object) if (safePrototype || object.hasOwnProperty(key)) {
1688
+ nextTransition = transition[key];
1689
+ if (!nextTransition) {
1690
+ if (transition[RECORD_SYMBOL] & 0x100000) {// this indicates it is a brancheable/extendable terminal node, so we will use this record id and extend it
1691
+ parentRecordId = transition[RECORD_SYMBOL] & 0xffff;
1635
1692
  }
1636
- makeRoom(position + size)
1637
- nextTransition = transition[key]
1638
- target.copy(target, )
1639
- objectOffset
1693
+ nextTransition = transition[key] = Object.create(null);
1694
+ newTransitions++;
1640
1695
  }
1641
- transition = nextTransition
1642
- encode(object[key])
1696
+ transition = nextTransition;
1697
+ length++;
1643
1698
  }
1644
1699
  }
1645
- let id = transition.id
1646
- if (!id) {
1647
- id = transition.id = structures.push(transition.__keys__) + 63
1648
- if (sharedStructures.onUpdate)
1649
- sharedStructures.onUpdate(id, transition.__keys__)
1650
- }
1651
- target[objectOffset + start] = id
1652
- }*/
1653
- (object) => {
1654
- let keys = Object.keys(object);
1655
- let vals = Object.values(object);
1656
- if (this.keyMap) keys = keys.map(k => this.encodeKey(k));
1657
- let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null));
1658
- let newTransitions = 0;
1659
- let length = keys.length;
1660
- //let parentRecordId
1661
- for (let i = 0; i < length; i++) {
1662
- //if (!parentRecordId)
1663
- let key = keys[i];
1664
- // parentRecordId = transition[RECORD_SYMBOL]
1665
- nextTransition = transition[key];
1666
- if (!nextTransition) {
1667
- nextTransition = transition[key] = Object.create(null);
1668
- newTransitions++;
1669
- }
1670
- transition = nextTransition;
1671
- }
1672
1700
  let recordId = transition[RECORD_SYMBOL];
1673
1701
  if (recordId !== undefined) {
1702
+ recordId &= 0xffff;
1674
1703
  target[position$1++] = 0xd9;
1675
1704
  target[position$1++] = (recordId >> 8) | 0xe0;
1676
1705
  target[position$1++] = recordId & 0xff;
1677
1706
  } else {
1678
- recordId = structures.nextId++;
1679
- if (!recordId) {
1680
- recordId = 0;
1681
- structures.nextId = 1;
1682
- }
1683
- if (recordId >= MAX_STRUCTURES) {// cycle back around
1684
- structures.nextId = (recordId = maxSharedStructures) + 1;
1707
+ if (!keys)
1708
+ keys = transition.__keys__ || (transition.__keys__ = Object.keys(object));
1709
+ if (parentRecordId === undefined) {
1710
+ recordId = structures.nextId++;
1711
+ if (!recordId) {
1712
+ recordId = 0;
1713
+ structures.nextId = 1;
1714
+ }
1715
+ if (recordId >= MAX_STRUCTURES) {// cycle back around
1716
+ structures.nextId = (recordId = maxSharedStructures) + 1;
1717
+ }
1718
+ } else {
1719
+ recordId = parentRecordId;
1685
1720
  }
1686
- transition[RECORD_SYMBOL] = recordId;
1687
1721
  structures[recordId] = keys;
1688
- if (sharedStructures && sharedStructures.length <= maxSharedStructures) {
1722
+ if (recordId < maxSharedStructures) {
1689
1723
  target[position$1++] = 0xd9;
1690
1724
  target[position$1++] = (recordId >> 8) | 0xe0;
1691
1725
  target[position$1++] = recordId & 0xff;
1726
+ transition = structures.transitions;
1727
+ for (let i = 0; i < length; i++) {
1728
+ if (transition[RECORD_SYMBOL] === undefined || (transition[RECORD_SYMBOL] & 0x100000))
1729
+ transition[RECORD_SYMBOL] = recordId;
1730
+ transition = transition[keys[i]];
1731
+ }
1732
+ transition[RECORD_SYMBOL] = recordId | 0x100000; // indicates it is a extendable terminal
1692
1733
  hasSharedUpdate = true;
1693
1734
  } else {
1735
+ transition[RECORD_SYMBOL] = recordId;
1694
1736
  targetView.setUint32(position$1, 0xd9dfff00); // tag two byte, then record definition id
1695
1737
  position$1 += 3;
1696
1738
  if (newTransitions)
@@ -1711,7 +1753,9 @@
1711
1753
  } else {
1712
1754
  writeArrayHeader(length);
1713
1755
  }
1714
- for (let i =0; i < length; i++) encode(vals[i]);
1756
+ for (let key in object)
1757
+ if (safePrototype || object.hasOwnProperty(key))
1758
+ encode(object[key]);
1715
1759
  };
1716
1760
  const makeRoom = (end) => {
1717
1761
  let newSize;
@@ -1750,15 +1794,22 @@
1750
1794
  updateSharedData() {
1751
1795
  let lastVersion = this.sharedVersion || 0;
1752
1796
  this.sharedVersion = lastVersion + 1;
1753
- let saveResults = this.saveShared(new SharedData(this.structures, this.sharedValues, this.sharedVersion),
1797
+ let structuresCopy = this.structures.slice(0);
1798
+ let sharedData = new SharedData(structuresCopy, this.sharedValues, this.sharedVersion);
1799
+ let saveResults = this.saveShared(sharedData,
1754
1800
  existingShared => (existingShared && existingShared.version || 0) == lastVersion);
1755
1801
  if (saveResults === false) {
1756
1802
  // get updated structures and try again if the update failed
1757
- let sharedData = this.getShared() || {};
1803
+ sharedData = this.getShared() || {};
1758
1804
  this.structures = sharedData.structures || [];
1759
1805
  this.sharedValues = sharedData.packedValues;
1760
1806
  this.sharedVersion = sharedData.version;
1807
+ this.structures.nextId = this.structures.length;
1808
+ } else {
1809
+ // restore structures
1810
+ structuresCopy.forEach((structure, i) => this.structures[i] = structure);
1761
1811
  }
1812
+ // 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
1762
1813
  return saveResults
1763
1814
  }
1764
1815
  }
@@ -1836,7 +1887,7 @@
1836
1887
  case 'function': console.log(value);
1837
1888
  }
1838
1889
  }
1839
-
1890
+ const isLittleEndianMachine$1 = new Uint8Array(new Uint16Array([1]).buffer)[0] == 1;
1840
1891
  extensionClasses = [ Date, Set, Error, RegExp, Tag, ArrayBuffer, ByteArray,
1841
1892
  Uint8Array, Uint8ClampedArray, Uint16Array, Uint32Array,
1842
1893
  typeof BigUint64Array == 'undefined' ? function() {} : BigUint64Array, Int8Array, Int16Array, Int32Array,
@@ -1891,17 +1942,17 @@
1891
1942
  encode(arrayBuffer, encode, makeRoom) {
1892
1943
  writeBuffer(arrayBuffer, makeRoom);
1893
1944
  }
1894
- }, typedArrayEncoder(64),
1895
- typedArrayEncoder(68),
1896
- typedArrayEncoder(69),
1897
- typedArrayEncoder(70),
1898
- typedArrayEncoder(71),
1899
- typedArrayEncoder(72),
1900
- typedArrayEncoder(77),
1901
- typedArrayEncoder(78),
1902
- typedArrayEncoder(79),
1903
- typedArrayEncoder(81),
1904
- typedArrayEncoder(82),
1945
+ }, typedArrayEncoder(64, 1),
1946
+ typedArrayEncoder(68, 1),
1947
+ typedArrayEncoder(69, 2),
1948
+ typedArrayEncoder(70, 4),
1949
+ typedArrayEncoder(71, 8),
1950
+ typedArrayEncoder(72, 1),
1951
+ typedArrayEncoder(77, 2),
1952
+ typedArrayEncoder(78, 4),
1953
+ typedArrayEncoder(79, 8),
1954
+ typedArrayEncoder(85, 4),
1955
+ typedArrayEncoder(86, 8),
1905
1956
  {
1906
1957
  encode(sharedData, encode) { // write SharedData
1907
1958
  let packedValues = sharedData.packedValues || [];
@@ -1930,8 +1981,9 @@
1930
1981
  encode(new Tag(sharedData.version, 0x53687264));
1931
1982
  }
1932
1983
  }];
1933
-
1934
- function typedArrayEncoder(tag) {
1984
+ function typedArrayEncoder(tag, size) {
1985
+ if (!isLittleEndianMachine$1 && size > 1)
1986
+ tag -= 4; // the big endian equivalents are 4 less
1935
1987
  return {
1936
1988
  tag: tag,
1937
1989
  encode: function writeExtBuffer(typedArray, encode) {