@colyseus/schema 3.0.75 → 4.0.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.
Files changed (157) hide show
  1. package/build/cjs/index.js +780 -429
  2. package/build/cjs/index.js.map +1 -1
  3. package/build/esm/index.mjs +778 -430
  4. package/build/esm/index.mjs.map +1 -1
  5. package/build/umd/index.js +780 -429
  6. package/lib/Reflection.d.ts +50 -17
  7. package/lib/Reflection.js +151 -202
  8. package/lib/Reflection.js.map +1 -1
  9. package/lib/Schema.d.ts +13 -1
  10. package/lib/Schema.js +73 -9
  11. package/lib/Schema.js.map +1 -1
  12. package/lib/annotations.d.ts +6 -1
  13. package/lib/annotations.js +8 -34
  14. package/lib/annotations.js.map +1 -1
  15. package/lib/bench_encode.js +34 -1
  16. package/lib/bench_encode.js.map +1 -1
  17. package/lib/codegen/api.js +35 -2
  18. package/lib/codegen/api.js.map +1 -1
  19. package/lib/codegen/cli.js +4 -1
  20. package/lib/codegen/cli.js.map +1 -1
  21. package/lib/codegen/parser.js +35 -2
  22. package/lib/codegen/parser.js.map +1 -1
  23. package/lib/codegen/types.js +34 -1
  24. package/lib/codegen/types.js.map +1 -1
  25. package/lib/decoder/DecodeOperation.d.ts +2 -2
  26. package/lib/decoder/DecodeOperation.js +3 -3
  27. package/lib/decoder/DecodeOperation.js.map +1 -1
  28. package/lib/decoder/Decoder.d.ts +3 -3
  29. package/lib/decoder/Decoder.js +2 -2
  30. package/lib/decoder/Decoder.js.map +1 -1
  31. package/lib/decoder/ReferenceTracker.d.ts +0 -1
  32. package/lib/decoder/ReferenceTracker.js +9 -7
  33. package/lib/decoder/ReferenceTracker.js.map +1 -1
  34. package/lib/decoder/strategy/Callbacks.d.ts +154 -0
  35. package/lib/decoder/strategy/Callbacks.js +340 -0
  36. package/lib/decoder/strategy/Callbacks.js.map +1 -0
  37. package/lib/decoder/strategy/{StateCallbacks.d.ts → getDecoderStateCallbacks.d.ts} +6 -0
  38. package/lib/decoder/strategy/{StateCallbacks.js → getDecoderStateCallbacks.js} +17 -10
  39. package/lib/decoder/strategy/getDecoderStateCallbacks.js.map +1 -0
  40. package/lib/encoder/ChangeTree.d.ts +2 -2
  41. package/lib/encoder/ChangeTree.js.map +1 -1
  42. package/lib/encoder/EncodeOperation.d.ts +2 -2
  43. package/lib/encoder/EncodeOperation.js +3 -3
  44. package/lib/encoder/EncodeOperation.js.map +1 -1
  45. package/lib/encoder/Encoder.d.ts +6 -6
  46. package/lib/encoder/Encoder.js +19 -18
  47. package/lib/encoder/Encoder.js.map +1 -1
  48. package/lib/encoder/Root.js +17 -14
  49. package/lib/encoder/Root.js.map +1 -1
  50. package/lib/encoder/StateView.js +13 -12
  51. package/lib/encoder/StateView.js.map +1 -1
  52. package/lib/encoding/decode.d.ts +2 -2
  53. package/lib/encoding/encode.d.ts +3 -1
  54. package/lib/encoding/encode.js.map +1 -1
  55. package/lib/index.d.ts +3 -2
  56. package/lib/index.js +7 -3
  57. package/lib/index.js.map +1 -1
  58. package/lib/types/HelperTypes.d.ts +7 -14
  59. package/lib/types/HelperTypes.js.map +1 -1
  60. package/lib/types/custom/ArraySchema.d.ts +2 -1
  61. package/lib/types/custom/ArraySchema.js.map +1 -1
  62. package/lib/types/custom/CollectionSchema.d.ts +2 -1
  63. package/lib/types/custom/CollectionSchema.js.map +1 -1
  64. package/lib/types/custom/MapSchema.d.ts +3 -2
  65. package/lib/types/custom/MapSchema.js.map +1 -1
  66. package/lib/types/custom/SetSchema.d.ts +2 -1
  67. package/lib/types/custom/SetSchema.js.map +1 -1
  68. package/lib/types/symbols.d.ts +1 -0
  69. package/lib/types/symbols.js +2 -1
  70. package/lib/types/symbols.js.map +1 -1
  71. package/lib/utils.js +1 -1
  72. package/lib/utils.js.map +1 -1
  73. package/package.json +12 -16
  74. package/src/Reflection.ts +185 -174
  75. package/src/Schema.ts +81 -13
  76. package/src/annotations.ts +14 -40
  77. package/src/codegen/parser.ts +1 -1
  78. package/src/decoder/DecodeOperation.ts +9 -9
  79. package/src/decoder/Decoder.ts +6 -6
  80. package/src/decoder/ReferenceTracker.ts +10 -8
  81. package/src/decoder/strategy/Callbacks.ts +547 -0
  82. package/src/decoder/strategy/{StateCallbacks.ts → getDecoderStateCallbacks.ts} +17 -11
  83. package/src/encoder/ChangeTree.ts +4 -7
  84. package/src/encoder/EncodeOperation.ts +9 -9
  85. package/src/encoder/Encoder.ts +26 -18
  86. package/src/encoder/Root.ts +20 -15
  87. package/src/encoder/StateView.ts +15 -13
  88. package/src/encoding/encode.ts +1 -1
  89. package/src/index.ts +3 -2
  90. package/src/types/HelperTypes.ts +13 -11
  91. package/src/types/custom/ArraySchema.ts +2 -1
  92. package/src/types/custom/CollectionSchema.ts +4 -2
  93. package/src/types/custom/MapSchema.ts +4 -2
  94. package/src/types/custom/SetSchema.ts +3 -1
  95. package/src/types/symbols.ts +1 -0
  96. package/src/utils.ts +2 -2
  97. package/lib/Decoder.d.ts +0 -16
  98. package/lib/Decoder.js +0 -182
  99. package/lib/Decoder.js.map +0 -1
  100. package/lib/Encoder.d.ts +0 -13
  101. package/lib/Encoder.js +0 -79
  102. package/lib/Encoder.js.map +0 -1
  103. package/lib/changes/ChangeSet.d.ts +0 -12
  104. package/lib/changes/ChangeSet.js +0 -35
  105. package/lib/changes/ChangeSet.js.map +0 -1
  106. package/lib/changes/ChangeTree.d.ts +0 -53
  107. package/lib/changes/ChangeTree.js +0 -202
  108. package/lib/changes/ChangeTree.js.map +0 -1
  109. package/lib/changes/DecodeOperation.d.ts +0 -15
  110. package/lib/changes/DecodeOperation.js +0 -186
  111. package/lib/changes/DecodeOperation.js.map +0 -1
  112. package/lib/changes/EncodeOperation.d.ts +0 -18
  113. package/lib/changes/EncodeOperation.js +0 -130
  114. package/lib/changes/EncodeOperation.js.map +0 -1
  115. package/lib/changes/ReferenceTracker.d.ts +0 -14
  116. package/lib/changes/ReferenceTracker.js +0 -83
  117. package/lib/changes/ReferenceTracker.js.map +0 -1
  118. package/lib/changes/consts.d.ts +0 -14
  119. package/lib/changes/consts.js +0 -18
  120. package/lib/changes/consts.js.map +0 -1
  121. package/lib/decoder/strategy/StateCallbacks.js.map +0 -1
  122. package/lib/decoding/decode.d.ts +0 -48
  123. package/lib/decoding/decode.js +0 -267
  124. package/lib/decoding/decode.js.map +0 -1
  125. package/lib/ecs.d.ts +0 -11
  126. package/lib/ecs.js +0 -160
  127. package/lib/ecs.js.map +0 -1
  128. package/lib/filters/index.d.ts +0 -8
  129. package/lib/filters/index.js +0 -24
  130. package/lib/filters/index.js.map +0 -1
  131. package/lib/spec.d.ts +0 -13
  132. package/lib/spec.js +0 -42
  133. package/lib/spec.js.map +0 -1
  134. package/lib/types/ArraySchema.d.ts +0 -238
  135. package/lib/types/ArraySchema.js +0 -555
  136. package/lib/types/ArraySchema.js.map +0 -1
  137. package/lib/types/CollectionSchema.d.ts +0 -35
  138. package/lib/types/CollectionSchema.js +0 -150
  139. package/lib/types/CollectionSchema.js.map +0 -1
  140. package/lib/types/MapSchema.d.ts +0 -38
  141. package/lib/types/MapSchema.js +0 -215
  142. package/lib/types/MapSchema.js.map +0 -1
  143. package/lib/types/SetSchema.d.ts +0 -32
  144. package/lib/types/SetSchema.js +0 -162
  145. package/lib/types/SetSchema.js.map +0 -1
  146. package/lib/types/typeRegistry.d.ts +0 -5
  147. package/lib/types/typeRegistry.js +0 -13
  148. package/lib/types/typeRegistry.js.map +0 -1
  149. package/lib/usage.d.ts +0 -1
  150. package/lib/usage.js +0 -22
  151. package/lib/usage.js.map +0 -1
  152. package/lib/v3.d.ts +0 -1
  153. package/lib/v3.js +0 -427
  154. package/lib/v3.js.map +0 -1
  155. package/lib/v3_experiment.d.ts +0 -1
  156. package/lib/v3_experiment.js +0 -407
  157. package/lib/v3_experiment.js.map +0 -1
@@ -26,6 +26,7 @@ var OPERATION;
26
26
 
27
27
  Symbol.metadata ??= Symbol.for("Symbol.metadata");
28
28
 
29
+ const $refId = "~refId";
29
30
  const $track = "~track";
30
31
  const $encoder = "~encoder";
31
32
  const $decoder = "~decoder";
@@ -556,12 +557,16 @@ function defineCustomTypes(types) {
556
557
  }
557
558
 
558
559
  class TypeContext {
560
+ types = {};
561
+ schemas = new Map();
562
+ hasFilters = false;
563
+ parentFiltered = {};
559
564
  /**
560
565
  * For inheritance support
561
566
  * Keeps track of which classes extends which. (parent -> children)
562
567
  */
563
- static { this.inheritedTypes = new Map(); }
564
- static { this.cachedContexts = new Map(); }
568
+ static inheritedTypes = new Map();
569
+ static cachedContexts = new Map();
565
570
  static register(target) {
566
571
  const parent = Object.getPrototypeOf(target);
567
572
  if (parent !== Schema) {
@@ -582,10 +587,6 @@ class TypeContext {
582
587
  return context;
583
588
  }
584
589
  constructor(rootClass) {
585
- this.types = {};
586
- this.schemas = new Map();
587
- this.hasFilters = false;
588
- this.parentFiltered = {};
589
590
  if (rootClass) {
590
591
  this.discoverTypes(rootClass);
591
592
  }
@@ -978,25 +979,33 @@ function deleteOperationAtIndex(changeSet, index) {
978
979
  delete changeSet.indexes[index];
979
980
  }
980
981
  class ChangeTree {
982
+ ref;
983
+ metadata;
984
+ root;
985
+ parentChain; // Linked list for tracking parents
986
+ /**
987
+ * Whether this structure is parent of a filtered structure.
988
+ */
989
+ isFiltered = false;
990
+ isVisibilitySharedWithParent; // See test case: 'should not be required to manually call view.add() items to child arrays without @view() tag'
991
+ indexedOperations = {};
992
+ //
993
+ // TODO:
994
+ // try storing the index + operation per item.
995
+ // example: 1024 & 1025 => ADD, 1026 => DELETE
996
+ //
997
+ // => https://chatgpt.com/share/67107d0c-bc20-8004-8583-83b17dd7c196
998
+ //
999
+ changes = { indexes: {}, operations: [] };
1000
+ allChanges = { indexes: {}, operations: [] };
1001
+ filteredChanges;
1002
+ allFilteredChanges;
1003
+ indexes; // TODO: remove this, only used by MapSchema/SetSchema/CollectionSchema (`encodeKeyValueOperation`)
1004
+ /**
1005
+ * Is this a new instance? Used on ArraySchema to determine OPERATION.MOVE_AND_ADD operation.
1006
+ */
1007
+ isNew = true;
981
1008
  constructor(ref) {
982
- /**
983
- * Whether this structure is parent of a filtered structure.
984
- */
985
- this.isFiltered = false;
986
- this.indexedOperations = {};
987
- //
988
- // TODO:
989
- // try storing the index + operation per item.
990
- // example: 1024 & 1025 => ADD, 1026 => DELETE
991
- //
992
- // => https://chatgpt.com/share/67107d0c-bc20-8004-8583-83b17dd7c196
993
- //
994
- this.changes = { indexes: {}, operations: [] };
995
- this.allChanges = { indexes: {}, operations: [] };
996
- /**
997
- * Is this a new instance? Used on ArraySchema to determine OPERATION.MOVE_AND_ADD operation.
998
- */
999
- this.isNew = true;
1000
1009
  this.ref = ref;
1001
1010
  this.metadata = ref.constructor[Symbol.metadata];
1002
1011
  //
@@ -1468,7 +1477,7 @@ function encodeValue(encoder, bytes, type, value, operation, it) {
1468
1477
  // Encode refId for this instance.
1469
1478
  // The actual instance is going to be encoded on next `changeTree` iteration.
1470
1479
  //
1471
- encode.number(bytes, value[$changes].refId, it);
1480
+ encode.number(bytes, value[$refId], it);
1472
1481
  // Try to encode inherited TYPE_ID if it's an ADD operation.
1473
1482
  if ((operation & OPERATION.ADD) === OPERATION.ADD) {
1474
1483
  encoder.tryEncodeTypeId(bytes, type, value.constructor, it);
@@ -1479,7 +1488,7 @@ function encodeValue(encoder, bytes, type, value, operation, it) {
1479
1488
  // Encode refId for this instance.
1480
1489
  // The actual instance is going to be encoded on next `changeTree` iteration.
1481
1490
  //
1482
- encode.number(bytes, value[$changes].refId, it);
1491
+ encode.number(bytes, value[$refId], it);
1483
1492
  }
1484
1493
  }
1485
1494
  /**
@@ -1555,7 +1564,7 @@ const encodeArray = function (encoder, bytes, changeTree, field, operation, it,
1555
1564
  if (!item) {
1556
1565
  return;
1557
1566
  }
1558
- refOrIndex = item[$changes].refId;
1567
+ refOrIndex = item[$refId];
1559
1568
  if (operation === OPERATION.DELETE) {
1560
1569
  operation = OPERATION.DELETE_BY_REFID;
1561
1570
  }
@@ -1595,7 +1604,7 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
1595
1604
  let value;
1596
1605
  if ((operation & OPERATION.DELETE) === OPERATION.DELETE) {
1597
1606
  // Flag `refId` for garbage collection.
1598
- const previousRefId = $root.refIds.get(previousValue);
1607
+ const previousRefId = previousValue?.[$refId];
1599
1608
  if (previousRefId !== undefined) {
1600
1609
  $root.removeRef(previousRefId);
1601
1610
  }
@@ -1636,7 +1645,7 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
1636
1645
  value = valueRef.clone(true);
1637
1646
  value[$childType] = Object.values(type)[0]; // cache childType for ArraySchema and MapSchema
1638
1647
  if (previousValue) {
1639
- let previousRefId = $root.refIds.get(previousValue);
1648
+ let previousRefId = previousValue[$refId];
1640
1649
  if (previousRefId !== undefined && refId !== previousRefId) {
1641
1650
  //
1642
1651
  // enqueue onRemove if structure has been replaced.
@@ -1647,7 +1656,7 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
1647
1656
  const [key, value] = iter.value;
1648
1657
  // if value is a schema, remove its reference
1649
1658
  if (typeof (value) === "object") {
1650
- previousRefId = $root.refIds.get(value);
1659
+ previousRefId = value[$refId];
1651
1660
  $root.removeRef(previousRefId);
1652
1661
  }
1653
1662
  allChanges.push({
@@ -1876,7 +1885,6 @@ function assertInstanceType(value, type, instance, field) {
1876
1885
  }
1877
1886
  }
1878
1887
 
1879
- var _a$4, _b$4;
1880
1888
  const DEFAULT_SORT = (a, b) => {
1881
1889
  const A = a.toString();
1882
1890
  const B = b.toString();
@@ -1888,8 +1896,15 @@ const DEFAULT_SORT = (a, b) => {
1888
1896
  return 0;
1889
1897
  };
1890
1898
  class ArraySchema {
1891
- static { this[_a$4] = encodeArray; }
1892
- static { this[_b$4] = decodeArray; }
1899
+ [$changes];
1900
+ [$refId];
1901
+ [$childType];
1902
+ items = [];
1903
+ tmpItems = [];
1904
+ deletedIndexes = {};
1905
+ isMovingItems = false;
1906
+ static [$encoder] = encodeArray;
1907
+ static [$decoder] = decodeArray;
1893
1908
  /**
1894
1909
  * Determine if a property must be filtered.
1895
1910
  * - If returns false, the property is NOT going to be encoded.
@@ -1899,7 +1914,7 @@ class ArraySchema {
1899
1914
  * - First, the encoder iterates over all "not owned" properties and encodes them.
1900
1915
  * - Then, the encoder iterates over all "owned" properties per instance and encodes them.
1901
1916
  */
1902
- static [(_a$4 = $encoder, _b$4 = $decoder, $filter)](ref, index, view) {
1917
+ static [$filter](ref, index, view) {
1903
1918
  return (!view ||
1904
1919
  typeof (ref[$childType]) === "string" ||
1905
1920
  view.isChangeTreeVisible(ref['tmpItems'][index]?.[$changes]));
@@ -1915,10 +1930,6 @@ class ArraySchema {
1915
1930
  return new ArraySchema(...Array.from(iterable));
1916
1931
  }
1917
1932
  constructor(...items) {
1918
- this.items = [];
1919
- this.tmpItems = [];
1920
- this.deletedIndexes = {};
1921
- this.isMovingItems = false;
1922
1933
  Object.defineProperty(this, $childType, {
1923
1934
  value: undefined,
1924
1935
  enumerable: false,
@@ -2435,6 +2446,10 @@ class ArraySchema {
2435
2446
  static get [Symbol.species]() {
2436
2447
  return ArraySchema;
2437
2448
  }
2449
+ // WORKAROUND for compatibility
2450
+ // - TypeScript 4 defines @@unscopables as a function
2451
+ // - TypeScript 5 defines @@unscopables as an object
2452
+ [Symbol.unscopables];
2438
2453
  /**
2439
2454
  * Returns an iterable of key, value pairs for every entry in the array
2440
2455
  */
@@ -2544,7 +2559,7 @@ class ArraySchema {
2544
2559
  this.isMovingItems = false;
2545
2560
  return this;
2546
2561
  }
2547
- [($getByIndex)](index, isEncodeAll = false) {
2562
+ [$getByIndex](index, isEncodeAll = false) {
2548
2563
  //
2549
2564
  // TODO: avoid unecessary `this.tmpItems` check during decoding.
2550
2565
  //
@@ -2599,10 +2614,16 @@ class ArraySchema {
2599
2614
  }
2600
2615
  registerType("array", { constructor: ArraySchema });
2601
2616
 
2602
- var _a$3, _b$3;
2603
2617
  class MapSchema {
2604
- static { this[_a$3] = encodeKeyValueOperation; }
2605
- static { this[_b$3] = decodeKeyValueOperation; }
2618
+ [$changes];
2619
+ [$refId];
2620
+ childType;
2621
+ [$childType];
2622
+ $items = new Map();
2623
+ $indexes = new Map();
2624
+ deletedItems = {};
2625
+ static [$encoder] = encodeKeyValueOperation;
2626
+ static [$decoder] = decodeKeyValueOperation;
2606
2627
  /**
2607
2628
  * Determine if a property must be filtered.
2608
2629
  * - If returns false, the property is NOT going to be encoded.
@@ -2612,7 +2633,7 @@ class MapSchema {
2612
2633
  * - First, the encoder iterates over all "not owned" properties and encodes them.
2613
2634
  * - Then, the encoder iterates over all "owned" properties per instance and encodes them.
2614
2635
  */
2615
- static [(_a$3 = $encoder, _b$3 = $decoder, $filter)](ref, index, view) {
2636
+ static [$filter](ref, index, view) {
2616
2637
  return (!view ||
2617
2638
  typeof (ref[$childType]) === "string" ||
2618
2639
  view.isChangeTreeVisible((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
@@ -2621,9 +2642,6 @@ class MapSchema {
2621
2642
  return type['map'] !== undefined;
2622
2643
  }
2623
2644
  constructor(initialValues) {
2624
- this.$items = new Map();
2625
- this.$indexes = new Map();
2626
- this.deletedItems = {};
2627
2645
  const changeTree = new ChangeTree(this);
2628
2646
  changeTree.indexes = {};
2629
2647
  Object.defineProperty(this, $changes, {
@@ -2814,10 +2832,16 @@ class MapSchema {
2814
2832
  }
2815
2833
  registerType("map", { constructor: MapSchema });
2816
2834
 
2817
- var _a$2, _b$2;
2818
2835
  class CollectionSchema {
2819
- static { this[_a$2] = encodeKeyValueOperation; }
2820
- static { this[_b$2] = decodeKeyValueOperation; }
2836
+ [$changes];
2837
+ [$refId];
2838
+ [$childType];
2839
+ $items = new Map();
2840
+ $indexes = new Map();
2841
+ deletedItems = {};
2842
+ $refId = 0;
2843
+ static [$encoder] = encodeKeyValueOperation;
2844
+ static [$decoder] = decodeKeyValueOperation;
2821
2845
  /**
2822
2846
  * Determine if a property must be filtered.
2823
2847
  * - If returns false, the property is NOT going to be encoded.
@@ -2827,7 +2851,7 @@ class CollectionSchema {
2827
2851
  * - First, the encoder iterates over all "not owned" properties and encodes them.
2828
2852
  * - Then, the encoder iterates over all "owned" properties per instance and encodes them.
2829
2853
  */
2830
- static [(_a$2 = $encoder, _b$2 = $decoder, $filter)](ref, index, view) {
2854
+ static [$filter](ref, index, view) {
2831
2855
  return (!view ||
2832
2856
  typeof (ref[$childType]) === "string" ||
2833
2857
  view.isChangeTreeVisible((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
@@ -2836,10 +2860,6 @@ class CollectionSchema {
2836
2860
  return type['collection'] !== undefined;
2837
2861
  }
2838
2862
  constructor(initialValues) {
2839
- this.$items = new Map();
2840
- this.$indexes = new Map();
2841
- this.deletedItems = {};
2842
- this.$refId = 0;
2843
2863
  this[$changes] = new ChangeTree(this);
2844
2864
  this[$changes].indexes = {};
2845
2865
  if (initialValues) {
@@ -2978,10 +2998,16 @@ class CollectionSchema {
2978
2998
  }
2979
2999
  registerType("collection", { constructor: CollectionSchema, });
2980
3000
 
2981
- var _a$1, _b$1;
2982
3001
  class SetSchema {
2983
- static { this[_a$1] = encodeKeyValueOperation; }
2984
- static { this[_b$1] = decodeKeyValueOperation; }
3002
+ [$changes];
3003
+ [$refId];
3004
+ [$childType];
3005
+ $items = new Map();
3006
+ $indexes = new Map();
3007
+ deletedItems = {};
3008
+ $refId = 0;
3009
+ static [$encoder] = encodeKeyValueOperation;
3010
+ static [$decoder] = decodeKeyValueOperation;
2985
3011
  /**
2986
3012
  * Determine if a property must be filtered.
2987
3013
  * - If returns false, the property is NOT going to be encoded.
@@ -2991,7 +3017,7 @@ class SetSchema {
2991
3017
  * - First, the encoder iterates over all "not owned" properties and encodes them.
2992
3018
  * - Then, the encoder iterates over all "owned" properties per instance and encodes them.
2993
3019
  */
2994
- static [(_a$1 = $encoder, _b$1 = $decoder, $filter)](ref, index, view) {
3020
+ static [$filter](ref, index, view) {
2995
3021
  return (!view ||
2996
3022
  typeof (ref[$childType]) === "string" ||
2997
3023
  view.visible.has((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
@@ -3000,10 +3026,6 @@ class SetSchema {
3000
3026
  return type['set'] !== undefined;
3001
3027
  }
3002
3028
  constructor(initialValues) {
3003
- this.$items = new Map();
3004
- this.$indexes = new Map();
3005
- this.deletedItems = {};
3006
- this.$refId = 0;
3007
3029
  this[$changes] = new ChangeTree(this);
3008
3030
  this[$changes].indexes = {};
3009
3031
  if (initialValues) {
@@ -3502,7 +3524,10 @@ function schema(fieldsAndMethods, name, inherits = Schema) {
3502
3524
  ? DEFAULT_VIEW_TAG
3503
3525
  : value['view'];
3504
3526
  }
3505
- fields[fieldName] = getNormalizedType(value);
3527
+ // allow to define a field as not synced
3528
+ if (value['sync'] !== false) {
3529
+ fields[fieldName] = getNormalizedType(value);
3530
+ }
3506
3531
  // If no explicit default provided, handle automatic instantiation for collection types
3507
3532
  if (!Object.prototype.hasOwnProperty.call(value, 'default')) {
3508
3533
  // TODO: remove Array.isArray() check. Use ['array'] !== undefined only.
@@ -3524,12 +3549,7 @@ function schema(fieldsAndMethods, name, inherits = Schema) {
3524
3549
  }
3525
3550
  else if (value['type'] !== undefined && Schema.is(value['type'])) {
3526
3551
  // Direct Schema type: Type → new Type()
3527
- if (!value['type'].prototype.initialize || value['type'].prototype.initialize.length === 0) {
3528
- // only auto-initialize Schema instances if:
3529
- // - they don't have an initialize method
3530
- // - or initialize method doesn't accept any parameters
3531
- defaultValues[fieldName] = new value['type']();
3532
- }
3552
+ defaultValues[fieldName] = new value['type']();
3533
3553
  }
3534
3554
  }
3535
3555
  else {
@@ -3539,12 +3559,7 @@ function schema(fieldsAndMethods, name, inherits = Schema) {
3539
3559
  else if (typeof (value) === "function") {
3540
3560
  if (Schema.is(value)) {
3541
3561
  // Direct Schema type: Type → new Type()
3542
- if (!value.prototype.initialize || value.prototype.initialize.length === 0) {
3543
- // only auto-initialize Schema instances if:
3544
- // - they don't have an initialize method
3545
- // - or initialize method doesn't accept any parameters
3546
- defaultValues[fieldName] = new value();
3547
- }
3562
+ defaultValues[fieldName] = new value();
3548
3563
  fields[fieldName] = getNormalizedType(value);
3549
3564
  }
3550
3565
  else {
@@ -3571,32 +3586,13 @@ function schema(fieldsAndMethods, name, inherits = Schema) {
3571
3586
  }
3572
3587
  return defaults;
3573
3588
  };
3574
- const getParentProps = (props) => {
3575
- const fieldNames = Object.keys(fields);
3576
- const parentProps = {};
3577
- for (const key in props) {
3578
- if (!fieldNames.includes(key)) {
3579
- parentProps[key] = props[key];
3580
- }
3581
- }
3582
- return parentProps;
3583
- };
3584
3589
  /** @codegen-ignore */
3585
3590
  const klass = Metadata.setFields(class extends inherits {
3586
3591
  constructor(...args) {
3592
+ super(Object.assign({}, getDefaultValues(), args[0] || {}));
3587
3593
  // call initialize method
3588
3594
  if (methods.initialize && typeof methods.initialize === 'function') {
3589
- super(Object.assign({}, getDefaultValues(), getParentProps(args[0] || {})));
3590
- /**
3591
- * only call initialize() in the current class, not the parent ones.
3592
- * see "should not call initialize automatically when creating an instance of inherited Schema"
3593
- */
3594
- if (new.target === klass) {
3595
- methods.initialize.apply(this, args);
3596
- }
3597
- }
3598
- else {
3599
- super(Object.assign({}, getDefaultValues(), args[0] || {}));
3595
+ methods.initialize.apply(this, args);
3600
3596
  }
3601
3597
  }
3602
3598
  }, fields);
@@ -3633,7 +3629,7 @@ function dumpChanges(schema) {
3633
3629
  continue;
3634
3630
  }
3635
3631
  const changes = changeTree.indexedOperations;
3636
- dump.refs.push(`refId#${changeTree.refId}`);
3632
+ dump.refs.push(`refId#${changeTree.ref[$refId]}`);
3637
3633
  for (const index in changes) {
3638
3634
  const op = changes[index];
3639
3635
  const opName = OPERATION[op];
@@ -3647,13 +3643,14 @@ function dumpChanges(schema) {
3647
3643
  return dump;
3648
3644
  }
3649
3645
 
3650
- var _a, _b;
3651
3646
  /**
3652
3647
  * Schema encoder / decoder
3653
3648
  */
3654
3649
  class Schema {
3655
- static { this[_a] = encodeSchemaOperation; }
3656
- static { this[_b] = decodeSchemaOperation; }
3650
+ static [Symbol.metadata];
3651
+ static [$encoder] = encodeSchemaOperation;
3652
+ static [$decoder] = decodeSchemaOperation;
3653
+ [$refId];
3657
3654
  /**
3658
3655
  * Assign the property descriptors required to track changes on this instance.
3659
3656
  * @param instance
@@ -3672,7 +3669,7 @@ class Schema {
3672
3669
  /**
3673
3670
  * Track property changes
3674
3671
  */
3675
- static [(_a = $encoder, _b = $decoder, $track)](changeTree, index, operation = OPERATION.ADD) {
3672
+ static [$track](changeTree, index, operation = OPERATION.ADD) {
3676
3673
  changeTree.change(index, operation);
3677
3674
  }
3678
3675
  /**
@@ -3719,10 +3716,74 @@ class Schema {
3719
3716
  Object.assign(this, arg);
3720
3717
  }
3721
3718
  }
3719
+ /**
3720
+ * Assign properties to the instance.
3721
+ * @param props Properties to assign to the instance
3722
+ * @returns
3723
+ */
3722
3724
  assign(props) {
3723
3725
  Object.assign(this, props);
3724
3726
  return this;
3725
3727
  }
3728
+ /**
3729
+ * Restore the instance from JSON data.
3730
+ * @param jsonData JSON data to restore the instance from
3731
+ * @returns
3732
+ */
3733
+ restore(jsonData) {
3734
+ const metadata = this.constructor[Symbol.metadata];
3735
+ for (const fieldIndex in metadata) {
3736
+ const field = metadata[fieldIndex];
3737
+ const fieldName = field.name;
3738
+ const fieldType = field.type;
3739
+ const value = jsonData[fieldName];
3740
+ if (value === undefined || value === null) {
3741
+ continue;
3742
+ }
3743
+ if (typeof fieldType === "string") {
3744
+ // Primitive type: assign directly
3745
+ this[fieldName] = value;
3746
+ }
3747
+ else if (Schema.is(fieldType)) {
3748
+ // Schema type: create instance and restore
3749
+ const instance = new fieldType();
3750
+ instance.restore(value);
3751
+ this[fieldName] = instance;
3752
+ }
3753
+ else if (typeof fieldType === "object") {
3754
+ // Collection types: { map: ... }, { array: ... }, etc.
3755
+ const collectionType = Object.keys(fieldType)[0];
3756
+ const childType = fieldType[collectionType];
3757
+ if (collectionType === "map") {
3758
+ const mapSchema = this[fieldName];
3759
+ for (const key in value) {
3760
+ if (Schema.is(childType)) {
3761
+ const childInstance = new childType();
3762
+ childInstance.restore(value[key]);
3763
+ mapSchema.set(key, childInstance);
3764
+ }
3765
+ else {
3766
+ mapSchema.set(key, value[key]);
3767
+ }
3768
+ }
3769
+ }
3770
+ else if (collectionType === "array") {
3771
+ const arraySchema = this[fieldName];
3772
+ for (let i = 0; i < value.length; i++) {
3773
+ if (Schema.is(childType)) {
3774
+ const childInstance = new childType();
3775
+ childInstance.restore(value[i]);
3776
+ arraySchema.push(childInstance);
3777
+ }
3778
+ else {
3779
+ arraySchema.push(value[i]);
3780
+ }
3781
+ }
3782
+ }
3783
+ }
3784
+ }
3785
+ return this;
3786
+ }
3726
3787
  /**
3727
3788
  * (Server-side): Flag a property to be encoded for the next patch.
3728
3789
  * @param instance Schema instance
@@ -3795,7 +3856,7 @@ class Schema {
3795
3856
  static debugRefIds(ref, showContents = false, level = 0, decoder, keyPrefix = "") {
3796
3857
  const contents = (showContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
3797
3858
  const changeTree = ref[$changes];
3798
- const refId = (decoder) ? decoder.root.refIds.get(ref) : changeTree.refId;
3859
+ const refId = ref[$refId];
3799
3860
  const root = (decoder) ? decoder.root : changeTree.root;
3800
3861
  // log reference count if > 1
3801
3862
  const refCount = (root?.refCount?.[refId] > 1)
@@ -3818,7 +3879,7 @@ class Schema {
3818
3879
  let current = ref[$changes].root[changeSet].next;
3819
3880
  while (current) {
3820
3881
  if (current.changeTree) {
3821
- encodeOrder.push(current.changeTree.refId);
3882
+ encodeOrder.push(current.changeTree.ref[$refId]);
3822
3883
  }
3823
3884
  current = current.next;
3824
3885
  }
@@ -3839,7 +3900,7 @@ class Schema {
3839
3900
  const changeTree = instance[$changes];
3840
3901
  const changeSet = (isEncodeAll) ? changeTree.allChanges : changeTree.changes;
3841
3902
  const changeSetName = (isEncodeAll) ? "allChanges" : "changes";
3842
- let output = `${instance.constructor.name} (${changeTree.refId}) -> .${changeSetName}:\n`;
3903
+ let output = `${instance.constructor.name} (${instance[$refId]}) -> .${changeSetName}:\n`;
3843
3904
  function dumpChangeSet(changeSet) {
3844
3905
  changeSet.operations
3845
3906
  .filter(op => op)
@@ -3853,14 +3914,14 @@ class Schema {
3853
3914
  if (!isEncodeAll &&
3854
3915
  changeTree.filteredChanges &&
3855
3916
  (changeTree.filteredChanges.operations).filter(op => op).length > 0) {
3856
- output += `${instance.constructor.name} (${changeTree.refId}) -> .filteredChanges:\n`;
3917
+ output += `${instance.constructor.name} (${instance[$refId]}) -> .filteredChanges:\n`;
3857
3918
  dumpChangeSet(changeTree.filteredChanges);
3858
3919
  }
3859
3920
  // display filtered changes
3860
3921
  if (isEncodeAll &&
3861
3922
  changeTree.allFilteredChanges &&
3862
3923
  (changeTree.allFilteredChanges.operations).filter(op => op).length > 0) {
3863
- output += `${instance.constructor.name} (${changeTree.refId}) -> .allFilteredChanges:\n`;
3924
+ output += `${instance.constructor.name} (${instance[$refId]}) -> .allFilteredChanges:\n`;
3864
3925
  dumpChangeSet(changeTree.allFilteredChanges);
3865
3926
  }
3866
3927
  return output;
@@ -3895,13 +3956,13 @@ class Schema {
3895
3956
  }
3896
3957
  }
3897
3958
  if (includeChangeTree) {
3898
- instanceRefIds.push(changeTree.refId);
3959
+ instanceRefIds.push(changeTree.ref[$refId]);
3899
3960
  totalOperations += Object.keys(changes).length;
3900
3961
  changeTrees.set(changeTree, parentChangeTrees.reverse());
3901
3962
  }
3902
3963
  }
3903
3964
  output += "---\n";
3904
- output += `root refId: ${rootChangeTree.refId}\n`;
3965
+ output += `root refId: ${rootChangeTree.ref[$refId]}\n`;
3905
3966
  output += `Total instances: ${instanceRefIds.length} (refIds: ${instanceRefIds.join(", ")})\n`;
3906
3967
  output += `Total changes: ${totalOperations}\n`;
3907
3968
  output += "---\n";
@@ -3910,7 +3971,7 @@ class Schema {
3910
3971
  for (const [changeTree, parentChangeTrees] of changeTrees.entries()) {
3911
3972
  parentChangeTrees.forEach((parentChangeTree, level) => {
3912
3973
  if (!visitedParents.has(parentChangeTree)) {
3913
- output += `${getIndent(level)}${parentChangeTree.ref.constructor.name} (refId: ${parentChangeTree.refId})\n`;
3974
+ output += `${getIndent(level)}${parentChangeTree.ref.constructor.name} (refId: ${parentChangeTree.ref[$refId]})\n`;
3914
3975
  visitedParents.add(parentChangeTree);
3915
3976
  }
3916
3977
  });
@@ -3918,7 +3979,7 @@ class Schema {
3918
3979
  const level = parentChangeTrees.length;
3919
3980
  const indent = getIndent(level);
3920
3981
  const parentIndex = (level > 0) ? `(${changeTree.parentIndex}) ` : "";
3921
- output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.refId}) - changes: ${Object.keys(changes).length}\n`;
3982
+ output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.ref[$refId]}) - changes: ${Object.keys(changes).length}\n`;
3922
3983
  for (const index in changes) {
3923
3984
  const operation = changes[index];
3924
3985
  output += `${getIndent(level + 1)}${OPERATION[operation]}: ${index}\n`;
@@ -3928,61 +3989,35 @@ class Schema {
3928
3989
  }
3929
3990
  }
3930
3991
 
3931
- /******************************************************************************
3932
- Copyright (c) Microsoft Corporation.
3933
-
3934
- Permission to use, copy, modify, and/or distribute this software for any
3935
- purpose with or without fee is hereby granted.
3936
-
3937
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
3938
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
3939
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
3940
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
3941
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
3942
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
3943
- PERFORMANCE OF THIS SOFTWARE.
3944
- ***************************************************************************** */
3945
- /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
3946
-
3947
-
3948
- function __decorate(decorators, target, key, desc) {
3949
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3950
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
3951
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
3952
- return c > 3 && r && Object.defineProperty(target, key, r), r;
3953
- }
3954
-
3955
- typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
3956
- var e = new Error(message);
3957
- return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
3958
- };
3959
-
3960
3992
  class Root {
3993
+ types;
3994
+ nextUniqueId = 0;
3995
+ refCount = {};
3996
+ changeTrees = {};
3997
+ // all changes
3998
+ allChanges = createChangeTreeList();
3999
+ allFilteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
4000
+ // pending changes to be encoded
4001
+ changes = createChangeTreeList();
4002
+ filteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
3961
4003
  constructor(types) {
3962
4004
  this.types = types;
3963
- this.nextUniqueId = 0;
3964
- this.refCount = {};
3965
- this.changeTrees = {};
3966
- // all changes
3967
- this.allChanges = createChangeTreeList();
3968
- this.allFilteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
3969
- // pending changes to be encoded
3970
- this.changes = createChangeTreeList();
3971
- this.filteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
3972
4005
  }
3973
4006
  getNextUniqueId() {
3974
4007
  return this.nextUniqueId++;
3975
4008
  }
3976
4009
  add(changeTree) {
3977
- // Assign unique `refId` to changeTree if it doesn't have one yet.
3978
- if (changeTree.refId === undefined) {
3979
- changeTree.refId = this.getNextUniqueId();
4010
+ const ref = changeTree.ref;
4011
+ // Assign unique `refId` to ref if it doesn't have one yet.
4012
+ if (ref[$refId] === undefined) {
4013
+ ref[$refId] = this.getNextUniqueId();
3980
4014
  }
3981
- const isNewChangeTree = (this.changeTrees[changeTree.refId] === undefined);
4015
+ const refId = ref[$refId];
4016
+ const isNewChangeTree = (this.changeTrees[refId] === undefined);
3982
4017
  if (isNewChangeTree) {
3983
- this.changeTrees[changeTree.refId] = changeTree;
4018
+ this.changeTrees[refId] = changeTree;
3984
4019
  }
3985
- const previousRefCount = this.refCount[changeTree.refId];
4020
+ const previousRefCount = this.refCount[refId];
3986
4021
  if (previousRefCount === 0) {
3987
4022
  //
3988
4023
  // When a ChangeTree is re-added, it means that it was previously removed.
@@ -3995,30 +4030,31 @@ class Root {
3995
4030
  setOperationAtIndex(changeTree.changes, len);
3996
4031
  }
3997
4032
  }
3998
- this.refCount[changeTree.refId] = (previousRefCount || 0) + 1;
3999
- // console.log("ADD", { refId: changeTree.refId, ref: changeTree.ref.constructor.name, refCount: this.refCount[changeTree.refId], isNewChangeTree });
4033
+ this.refCount[refId] = (previousRefCount || 0) + 1;
4034
+ // console.log("ADD", { refId, ref: ref.constructor.name, refCount: this.refCount[refId], isNewChangeTree });
4000
4035
  return isNewChangeTree;
4001
4036
  }
4002
4037
  remove(changeTree) {
4003
- const refCount = (this.refCount[changeTree.refId]) - 1;
4004
- // console.log("REMOVE", { refId: changeTree.refId, ref: changeTree.ref.constructor.name, refCount, needRemove: refCount <= 0 });
4038
+ const refId = changeTree.ref[$refId];
4039
+ const refCount = (this.refCount[refId]) - 1;
4040
+ // console.log("REMOVE", { refId, ref: changeTree.ref.constructor.name, refCount, needRemove: refCount <= 0 });
4005
4041
  if (refCount <= 0) {
4006
4042
  //
4007
4043
  // Only remove "root" reference if it's the last reference
4008
4044
  //
4009
4045
  changeTree.root = undefined;
4010
- delete this.changeTrees[changeTree.refId];
4046
+ delete this.changeTrees[refId];
4011
4047
  this.removeChangeFromChangeSet("allChanges", changeTree);
4012
4048
  this.removeChangeFromChangeSet("changes", changeTree);
4013
4049
  if (changeTree.filteredChanges) {
4014
4050
  this.removeChangeFromChangeSet("allFilteredChanges", changeTree);
4015
4051
  this.removeChangeFromChangeSet("filteredChanges", changeTree);
4016
4052
  }
4017
- this.refCount[changeTree.refId] = 0;
4053
+ this.refCount[refId] = 0;
4018
4054
  changeTree.forEachChild((child, _) => {
4019
4055
  if (child.removeParent(changeTree.ref)) {
4020
4056
  if ((child.parentChain === undefined || // no parent, remove it
4021
- (child.parentChain && this.refCount[child.refId] > 0) // parent is still in use, but has more than one reference, remove it
4057
+ (child.parentChain && this.refCount[child.ref[$refId]] > 0) // parent is still in use, but has more than one reference, remove it
4022
4058
  )) {
4023
4059
  this.remove(child);
4024
4060
  }
@@ -4030,7 +4066,7 @@ class Root {
4030
4066
  });
4031
4067
  }
4032
4068
  else {
4033
- this.refCount[changeTree.refId] = refCount;
4069
+ this.refCount[refId] = refCount;
4034
4070
  //
4035
4071
  // When losing a reference to an instance, it is best to move the
4036
4072
  // ChangeTree next to its parent in the encoding queue.
@@ -4180,10 +4216,19 @@ class Root {
4180
4216
  }
4181
4217
  }
4182
4218
 
4219
+ function concatBytes(a, b) {
4220
+ const result = new Uint8Array(a.length + b.length);
4221
+ result.set(a, 0);
4222
+ result.set(b, a.length);
4223
+ return result;
4224
+ }
4183
4225
  class Encoder {
4184
- static { this.BUFFER_SIZE = (typeof (Buffer) !== "undefined") && Buffer.poolSize || 8 * 1024; } // 8KB
4226
+ static BUFFER_SIZE = 8 * 1024; // 8KB
4227
+ sharedBuffer = new Uint8Array(Encoder.BUFFER_SIZE);
4228
+ context;
4229
+ state;
4230
+ root;
4185
4231
  constructor(state) {
4186
- this.sharedBuffer = Buffer.allocUnsafe(Encoder.BUFFER_SIZE);
4187
4232
  //
4188
4233
  // Use .cache() here to avoid re-creating a new context for every new room instance.
4189
4234
  //
@@ -4211,7 +4256,7 @@ class Encoder {
4211
4256
  const changeTree = current.changeTree;
4212
4257
  if (hasView) {
4213
4258
  if (!view.isChangeTreeVisible(changeTree)) {
4214
- // console.log("MARK AS INVISIBLE:", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, raw: changeTree.ref.toJSON() });
4259
+ // console.log("MARK AS INVISIBLE:", { ref: changeTree.ref.constructor.name, refId: changeTree.ref[$refId], raw: changeTree.ref.toJSON() });
4215
4260
  view.invisible.add(changeTree);
4216
4261
  continue; // skip this change tree
4217
4262
  }
@@ -4232,7 +4277,7 @@ class Encoder {
4232
4277
  // (unless it "hasView", which will need to revisit the root)
4233
4278
  if (hasView || it.offset > initialOffset || changeTree !== rootChangeTree) {
4234
4279
  buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
4235
- encode.number(buffer, changeTree.refId, it);
4280
+ encode.number(buffer, ref[$refId], it);
4236
4281
  }
4237
4282
  for (let j = 0; j < numChanges; j++) {
4238
4283
  const fieldIndex = changeSet.operations[j];
@@ -4261,10 +4306,9 @@ class Encoder {
4261
4306
  }
4262
4307
  }
4263
4308
  if (it.offset > buffer.byteLength) {
4264
- // we can assume that n + 1 poolSize will suffice given that we are likely done with encoding at this point
4265
- // multiples of poolSize are faster to allocate than arbitrary sizes
4266
- // if we are on an older platform that doesn't implement pooling use 8kb as poolSize (that's the default for node)
4267
- const newSize = Math.ceil(it.offset / (Buffer.poolSize ?? 8 * 1024)) * (Buffer.poolSize ?? 8 * 1024);
4309
+ // we can assume that n + 1 BUFFER_SIZE will suffice given that we are likely done with encoding at this point
4310
+ // multiples of BUFFER_SIZE are faster to allocate than arbitrary sizes
4311
+ const newSize = Math.ceil(it.offset / Encoder.BUFFER_SIZE) * Encoder.BUFFER_SIZE;
4268
4312
  console.warn(`@colyseus/schema buffer overflow. Encoded state is higher than default BUFFER_SIZE. Use the following to increase default BUFFER_SIZE:
4269
4313
 
4270
4314
  import { Encoder } from "@colyseus/schema";
@@ -4274,7 +4318,9 @@ class Encoder {
4274
4318
  // resize buffer and re-encode (TODO: can we avoid re-encoding here?)
4275
4319
  // -> No we probably can't unless we catch the need for resize before encoding which is likely more computationally expensive than resizing on demand
4276
4320
  //
4277
- buffer = Buffer.alloc(newSize, buffer); // fill with buffer here to memcpy previous encoding steps beyond the initialOffset
4321
+ const newBuffer = new Uint8Array(newSize);
4322
+ newBuffer.set(buffer); // copy previous encoding steps beyond the initialOffset
4323
+ buffer = newBuffer;
4278
4324
  // assign resized buffer to local sharedBuffer
4279
4325
  if (buffer === this.sharedBuffer) {
4280
4326
  this.sharedBuffer = buffer;
@@ -4292,10 +4338,7 @@ class Encoder {
4292
4338
  const viewOffset = it.offset;
4293
4339
  // try to encode "filtered" changes
4294
4340
  this.encode(it, view, bytes, "allFilteredChanges", true, viewOffset);
4295
- return Buffer.concat([
4296
- bytes.subarray(0, sharedOffset),
4297
- bytes.subarray(viewOffset, it.offset)
4298
- ]);
4341
+ return concatBytes(bytes.subarray(0, sharedOffset), bytes.subarray(viewOffset, it.offset));
4299
4342
  }
4300
4343
  encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
4301
4344
  const viewOffset = it.offset;
@@ -4319,7 +4362,7 @@ class Encoder {
4319
4362
  const encoder = ctor[$encoder];
4320
4363
  const metadata = ctor[Symbol.metadata];
4321
4364
  bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
4322
- encode.number(bytes, changeTree.refId, it);
4365
+ encode.number(bytes, ref[$refId], it);
4323
4366
  for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
4324
4367
  const index = Number(keys[i]);
4325
4368
  // workaround when using view.add() on item that has been deleted from state (see test "adding to view item that has been removed from state")
@@ -4338,10 +4381,7 @@ class Encoder {
4338
4381
  view.changes.clear();
4339
4382
  // try to encode "filtered" changes
4340
4383
  this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
4341
- return Buffer.concat([
4342
- bytes.subarray(0, sharedOffset),
4343
- bytes.subarray(viewOffset, it.offset)
4344
- ]);
4384
+ return concatBytes(bytes.subarray(0, sharedOffset), bytes.subarray(viewOffset, it.offset));
4345
4385
  }
4346
4386
  discardChanges() {
4347
4387
  // discard shared changes
@@ -4397,25 +4437,22 @@ class DecodingWarning extends Error {
4397
4437
  }
4398
4438
  }
4399
4439
  class ReferenceTracker {
4400
- constructor() {
4401
- //
4402
- // Relation of refId => Schema structure
4403
- // For direct access of structures during decoding time.
4404
- //
4405
- this.refs = new Map();
4406
- this.refIds = new WeakMap();
4407
- this.refCount = {};
4408
- this.deletedRefs = new Set();
4409
- this.callbacks = {};
4410
- this.nextUniqueId = 0;
4411
- }
4440
+ //
4441
+ // Relation of refId => Schema structure
4442
+ // For direct access of structures during decoding time.
4443
+ //
4444
+ refs = new Map();
4445
+ refCount = {};
4446
+ deletedRefs = new Set();
4447
+ callbacks = {};
4448
+ nextUniqueId = 0;
4412
4449
  getNextUniqueId() {
4413
4450
  return this.nextUniqueId++;
4414
4451
  }
4415
4452
  // for decoding
4416
4453
  addRef(refId, ref, incrementCount = true) {
4417
4454
  this.refs.set(refId, ref);
4418
- this.refIds.set(ref, refId);
4455
+ ref[$refId] = refId;
4419
4456
  if (incrementCount) {
4420
4457
  this.refCount[refId] = (this.refCount[refId] || 0) + 1;
4421
4458
  }
@@ -4472,9 +4509,12 @@ class ReferenceTracker {
4472
4509
  const metadata = ref.constructor[Symbol.metadata];
4473
4510
  for (const index in metadata) {
4474
4511
  const field = metadata[index].name;
4475
- const childRefId = typeof (ref[field]) === "object" && this.refIds.get(ref[field]);
4476
- if (childRefId && !this.deletedRefs.has(childRefId)) {
4477
- this.removeRef(childRefId);
4512
+ const child = ref[field];
4513
+ if (typeof (child) === "object" && child) {
4514
+ const childRefId = child[$refId];
4515
+ if (childRefId !== undefined && !this.deletedRefs.has(childRefId)) {
4516
+ this.removeRef(childRefId);
4517
+ }
4478
4518
  }
4479
4519
  }
4480
4520
  }
@@ -4482,8 +4522,8 @@ class ReferenceTracker {
4482
4522
  if (typeof (ref[$childType]) === "function") {
4483
4523
  Array.from(ref.values())
4484
4524
  .forEach((child) => {
4485
- const childRefId = this.refIds.get(child);
4486
- if (!this.deletedRefs.has(childRefId)) {
4525
+ const childRefId = child[$refId];
4526
+ if (childRefId !== undefined && !this.deletedRefs.has(childRefId)) {
4487
4527
  this.removeRef(childRefId);
4488
4528
  }
4489
4529
  });
@@ -4521,8 +4561,12 @@ class ReferenceTracker {
4521
4561
  }
4522
4562
 
4523
4563
  class Decoder {
4564
+ context;
4565
+ state;
4566
+ root;
4567
+ currentRefId = 0;
4568
+ triggerChanges;
4524
4569
  constructor(root, context) {
4525
- this.currentRefId = 0;
4526
4570
  this.setState(root);
4527
4571
  this.context = context || new TypeContext(root.constructor);
4528
4572
  // console.log(">>>>>>>>>>>>>>>> Decoder types");
@@ -4611,7 +4655,7 @@ class Decoder {
4611
4655
  }
4612
4656
  removeChildRefs(ref, allChanges) {
4613
4657
  const needRemoveRef = typeof (ref[$childType]) !== "string";
4614
- const refId = this.root.refIds.get(ref);
4658
+ const refId = ref[$refId];
4615
4659
  ref.forEach((value, key) => {
4616
4660
  allChanges.push({
4617
4661
  ref: ref,
@@ -4622,7 +4666,7 @@ class Decoder {
4622
4666
  previousValue: value
4623
4667
  });
4624
4668
  if (needRemoveRef) {
4625
- this.root.removeRef(this.root.refIds.get(value));
4669
+ this.root.removeRef(value[$refId]);
4626
4670
  }
4627
4671
  });
4628
4672
  }
@@ -4631,217 +4675,182 @@ class Decoder {
4631
4675
  /**
4632
4676
  * Reflection
4633
4677
  */
4634
- class ReflectionField extends Schema {
4635
- }
4636
- __decorate([
4637
- type("string")
4638
- ], ReflectionField.prototype, "name", void 0);
4639
- __decorate([
4640
- type("string")
4641
- ], ReflectionField.prototype, "type", void 0);
4642
- __decorate([
4643
- type("number")
4644
- ], ReflectionField.prototype, "referencedType", void 0);
4645
- class ReflectionType extends Schema {
4646
- constructor() {
4647
- super(...arguments);
4648
- this.fields = new ArraySchema();
4649
- }
4650
- }
4651
- __decorate([
4652
- type("number")
4653
- ], ReflectionType.prototype, "id", void 0);
4654
- __decorate([
4655
- type("number")
4656
- ], ReflectionType.prototype, "extendsId", void 0);
4657
- __decorate([
4658
- type([ReflectionField])
4659
- ], ReflectionType.prototype, "fields", void 0);
4660
- class Reflection extends Schema {
4661
- constructor() {
4662
- super(...arguments);
4663
- this.types = new ArraySchema();
4664
- }
4665
- /**
4666
- * Encodes the TypeContext of an Encoder into a buffer.
4667
- *
4668
- * @param encoder Encoder instance
4669
- * @param it
4670
- * @returns
4671
- */
4672
- static encode(encoder, it = { offset: 0 }) {
4673
- const context = encoder.context;
4674
- const reflection = new Reflection();
4675
- const reflectionEncoder = new Encoder(reflection);
4676
- // rootType is usually the first schema passed to the Encoder
4677
- // (unless it inherits from another schema)
4678
- const rootType = context.schemas.get(encoder.state.constructor);
4679
- if (rootType > 0) {
4680
- reflection.rootType = rootType;
4681
- }
4682
- const includedTypeIds = new Set();
4683
- const pendingReflectionTypes = {};
4684
- // add type to reflection in a way that respects inheritance
4685
- // (parent types should be added before their children)
4686
- const addType = (type) => {
4687
- if (type.extendsId === undefined || includedTypeIds.has(type.extendsId)) {
4688
- includedTypeIds.add(type.id);
4689
- reflection.types.push(type);
4690
- const deps = pendingReflectionTypes[type.id];
4691
- if (deps !== undefined) {
4692
- delete pendingReflectionTypes[type.id];
4693
- deps.forEach((childType) => addType(childType));
4694
- }
4678
+ const ReflectionField = schema({
4679
+ name: "string",
4680
+ type: "string",
4681
+ referencedType: "number",
4682
+ });
4683
+ const ReflectionType = schema({
4684
+ id: "number",
4685
+ extendsId: "number",
4686
+ fields: [ReflectionField],
4687
+ });
4688
+ const Reflection = schema({
4689
+ types: [ReflectionType],
4690
+ rootType: "number",
4691
+ });
4692
+ Reflection.encode = function (encoder, it = { offset: 0 }) {
4693
+ const context = encoder.context;
4694
+ const reflection = new Reflection();
4695
+ const reflectionEncoder = new Encoder(reflection);
4696
+ // rootType is usually the first schema passed to the Encoder
4697
+ // (unless it inherits from another schema)
4698
+ const rootType = context.schemas.get(encoder.state.constructor);
4699
+ if (rootType > 0) {
4700
+ reflection.rootType = rootType;
4701
+ }
4702
+ const includedTypeIds = new Set();
4703
+ const pendingReflectionTypes = {};
4704
+ // add type to reflection in a way that respects inheritance
4705
+ // (parent types should be added before their children)
4706
+ const addType = (type) => {
4707
+ if (type.extendsId === undefined || includedTypeIds.has(type.extendsId)) {
4708
+ includedTypeIds.add(type.id);
4709
+ reflection.types.push(type);
4710
+ const deps = pendingReflectionTypes[type.id];
4711
+ if (deps !== undefined) {
4712
+ delete pendingReflectionTypes[type.id];
4713
+ deps.forEach((childType) => addType(childType));
4695
4714
  }
4696
- else {
4697
- if (pendingReflectionTypes[type.extendsId] === undefined) {
4698
- pendingReflectionTypes[type.extendsId] = [];
4699
- }
4700
- pendingReflectionTypes[type.extendsId].push(type);
4715
+ }
4716
+ else {
4717
+ if (pendingReflectionTypes[type.extendsId] === undefined) {
4718
+ pendingReflectionTypes[type.extendsId] = [];
4701
4719
  }
4702
- };
4703
- context.schemas.forEach((typeid, klass) => {
4704
- const type = new ReflectionType();
4705
- type.id = Number(typeid);
4706
- // support inheritance
4707
- const inheritFrom = Object.getPrototypeOf(klass);
4708
- if (inheritFrom !== Schema) {
4709
- type.extendsId = context.schemas.get(inheritFrom);
4710
- }
4711
- const metadata = klass[Symbol.metadata];
4712
- //
4713
- // FIXME: this is a workaround for inherited types without additional fields
4714
- // if metadata is the same reference as the parent class - it means the class has no own metadata
4715
- //
4716
- if (metadata !== inheritFrom[Symbol.metadata]) {
4717
- for (const fieldIndex in metadata) {
4718
- const index = Number(fieldIndex);
4719
- const fieldName = metadata[index].name;
4720
- // skip fields from parent classes
4721
- if (!Object.prototype.hasOwnProperty.call(metadata, fieldName)) {
4722
- continue;
4723
- }
4724
- const reflectionField = new ReflectionField();
4725
- reflectionField.name = fieldName;
4726
- let fieldType;
4727
- const field = metadata[index];
4728
- if (typeof (field.type) === "string") {
4729
- fieldType = field.type;
4720
+ pendingReflectionTypes[type.extendsId].push(type);
4721
+ }
4722
+ };
4723
+ context.schemas.forEach((typeid, klass) => {
4724
+ const type = new ReflectionType();
4725
+ type.id = Number(typeid);
4726
+ // support inheritance
4727
+ const inheritFrom = Object.getPrototypeOf(klass);
4728
+ if (inheritFrom !== Schema) {
4729
+ type.extendsId = context.schemas.get(inheritFrom);
4730
+ }
4731
+ const metadata = klass[Symbol.metadata];
4732
+ //
4733
+ // FIXME: this is a workaround for inherited types without additional fields
4734
+ // if metadata is the same reference as the parent class - it means the class has no own metadata
4735
+ //
4736
+ if (metadata !== inheritFrom[Symbol.metadata]) {
4737
+ for (const fieldIndex in metadata) {
4738
+ const index = Number(fieldIndex);
4739
+ const fieldName = metadata[index].name;
4740
+ // skip fields from parent classes
4741
+ if (!Object.prototype.hasOwnProperty.call(metadata, fieldName)) {
4742
+ continue;
4743
+ }
4744
+ const reflectionField = new ReflectionField();
4745
+ reflectionField.name = fieldName;
4746
+ let fieldType;
4747
+ const field = metadata[index];
4748
+ if (typeof (field.type) === "string") {
4749
+ fieldType = field.type;
4750
+ }
4751
+ else {
4752
+ let childTypeSchema;
4753
+ //
4754
+ // TODO: refactor below.
4755
+ //
4756
+ if (Schema.is(field.type)) {
4757
+ fieldType = "ref";
4758
+ childTypeSchema = field.type;
4730
4759
  }
4731
4760
  else {
4732
- let childTypeSchema;
4733
- //
4734
- // TODO: refactor below.
4735
- //
4736
- if (Schema.is(field.type)) {
4737
- fieldType = "ref";
4738
- childTypeSchema = field.type;
4761
+ fieldType = Object.keys(field.type)[0];
4762
+ if (typeof (field.type[fieldType]) === "string") {
4763
+ fieldType += ":" + field.type[fieldType]; // array:string
4739
4764
  }
4740
4765
  else {
4741
- fieldType = Object.keys(field.type)[0];
4742
- if (typeof (field.type[fieldType]) === "string") {
4743
- fieldType += ":" + field.type[fieldType]; // array:string
4744
- }
4745
- else {
4746
- childTypeSchema = field.type[fieldType];
4747
- }
4766
+ childTypeSchema = field.type[fieldType];
4748
4767
  }
4749
- reflectionField.referencedType = (childTypeSchema)
4750
- ? context.getTypeId(childTypeSchema)
4751
- : -1;
4752
4768
  }
4753
- reflectionField.type = fieldType;
4754
- type.fields.push(reflectionField);
4769
+ reflectionField.referencedType = (childTypeSchema)
4770
+ ? context.getTypeId(childTypeSchema)
4771
+ : -1;
4755
4772
  }
4773
+ reflectionField.type = fieldType;
4774
+ type.fields.push(reflectionField);
4756
4775
  }
4757
- addType(type);
4758
- });
4759
- // in case there are types that were not added due to inheritance
4760
- for (const typeid in pendingReflectionTypes) {
4761
- pendingReflectionTypes[typeid].forEach((type) => reflection.types.push(type));
4762
4776
  }
4763
- const buf = reflectionEncoder.encodeAll(it);
4764
- return buf.slice(0, it.offset);
4765
- // return Buffer.from(buf, 0, it.offset);
4777
+ addType(type);
4778
+ });
4779
+ // in case there are types that were not added due to inheritance
4780
+ for (const typeid in pendingReflectionTypes) {
4781
+ pendingReflectionTypes[typeid].forEach((type) => reflection.types.push(type));
4766
4782
  }
4767
- /**
4768
- * Decodes the TypeContext from a buffer into a Decoder instance.
4769
- *
4770
- * @param bytes Reflection.encode() output
4771
- * @param it
4772
- * @returns Decoder instance
4773
- */
4774
- static decode(bytes, it) {
4775
- const reflection = new Reflection();
4776
- const reflectionDecoder = new Decoder(reflection);
4777
- reflectionDecoder.decode(bytes, it);
4778
- const typeContext = new TypeContext();
4779
- // 1st pass, initialize metadata + inheritance
4780
- reflection.types.forEach((reflectionType) => {
4781
- const parentClass = typeContext.get(reflectionType.extendsId) ?? Schema;
4782
- const schema = class _ extends parentClass {
4783
- };
4784
- // register for inheritance support
4785
- TypeContext.register(schema);
4786
- // // for inheritance support
4787
- // Metadata.initialize(schema);
4788
- typeContext.add(schema, reflectionType.id);
4789
- }, {});
4790
- // define fields
4791
- const addFields = (metadata, reflectionType, parentFieldIndex) => {
4792
- reflectionType.fields.forEach((field, i) => {
4793
- const fieldIndex = parentFieldIndex + i;
4794
- if (field.referencedType !== undefined) {
4795
- let fieldType = field.type;
4796
- let refType = typeContext.get(field.referencedType);
4797
- // map or array of primitive type (-1)
4798
- if (!refType) {
4799
- const typeInfo = field.type.split(":");
4800
- fieldType = typeInfo[0];
4801
- refType = typeInfo[1]; // string
4802
- }
4803
- if (fieldType === "ref") {
4804
- Metadata.addField(metadata, fieldIndex, field.name, refType);
4805
- }
4806
- else {
4807
- Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType });
4808
- }
4783
+ const buf = reflectionEncoder.encodeAll(it);
4784
+ return buf.slice(0, it.offset);
4785
+ };
4786
+ Reflection.decode = function (bytes, it) {
4787
+ const reflection = new Reflection();
4788
+ const reflectionDecoder = new Decoder(reflection);
4789
+ reflectionDecoder.decode(bytes, it);
4790
+ const typeContext = new TypeContext();
4791
+ // 1st pass, initialize metadata + inheritance
4792
+ reflection.types.forEach((reflectionType) => {
4793
+ const parentClass = typeContext.get(reflectionType.extendsId) ?? Schema;
4794
+ const schema = class _ extends parentClass {
4795
+ };
4796
+ // register for inheritance support
4797
+ TypeContext.register(schema);
4798
+ typeContext.add(schema, reflectionType.id);
4799
+ }, {});
4800
+ // define fields
4801
+ const addFields = (metadata, reflectionType, parentFieldIndex) => {
4802
+ reflectionType.fields.forEach((field, i) => {
4803
+ const fieldIndex = parentFieldIndex + i;
4804
+ if (field.referencedType !== undefined) {
4805
+ let fieldType = field.type;
4806
+ let refType = typeContext.get(field.referencedType);
4807
+ // map or array of primitive type (-1)
4808
+ if (!refType) {
4809
+ const typeInfo = field.type.split(":");
4810
+ fieldType = typeInfo[0];
4811
+ refType = typeInfo[1]; // string
4812
+ }
4813
+ if (fieldType === "ref") {
4814
+ Metadata.addField(metadata, fieldIndex, field.name, refType);
4809
4815
  }
4810
4816
  else {
4811
- Metadata.addField(metadata, fieldIndex, field.name, field.type);
4817
+ Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType });
4812
4818
  }
4813
- });
4814
- };
4815
- // 2nd pass, set fields
4816
- reflection.types.forEach((reflectionType) => {
4817
- const schema = typeContext.get(reflectionType.id);
4818
- // for inheritance support
4819
- const metadata = Metadata.initialize(schema);
4820
- const inheritedTypes = [];
4821
- let parentType = reflectionType;
4822
- do {
4823
- inheritedTypes.push(parentType);
4824
- parentType = reflection.types.find((t) => t.id === parentType.extendsId);
4825
- } while (parentType);
4826
- let parentFieldIndex = 0;
4827
- inheritedTypes.reverse().forEach((reflectionType) => {
4828
- // add fields from all inherited classes
4829
- // TODO: refactor this to avoid adding fields from parent classes
4830
- addFields(metadata, reflectionType, parentFieldIndex);
4831
- parentFieldIndex += reflectionType.fields.length;
4832
- });
4819
+ }
4820
+ else {
4821
+ Metadata.addField(metadata, fieldIndex, field.name, field.type);
4822
+ }
4833
4823
  });
4834
- const state = new (typeContext.get(reflection.rootType || 0))();
4835
- return new Decoder(state, typeContext);
4836
- }
4837
- }
4838
- __decorate([
4839
- type([ReflectionType])
4840
- ], Reflection.prototype, "types", void 0);
4841
- __decorate([
4842
- type("number")
4843
- ], Reflection.prototype, "rootType", void 0);
4824
+ };
4825
+ // 2nd pass, set fields
4826
+ reflection.types.forEach((reflectionType) => {
4827
+ const schema = typeContext.get(reflectionType.id);
4828
+ // for inheritance support
4829
+ const metadata = Metadata.initialize(schema);
4830
+ const inheritedTypes = [];
4831
+ let parentType = reflectionType;
4832
+ do {
4833
+ inheritedTypes.push(parentType);
4834
+ parentType = reflection.types.find((t) => t.id === parentType.extendsId);
4835
+ } while (parentType);
4836
+ let parentFieldIndex = 0;
4837
+ inheritedTypes.reverse().forEach((reflectionType) => {
4838
+ // add fields from all inherited classes
4839
+ // TODO: refactor this to avoid adding fields from parent classes
4840
+ addFields(metadata, reflectionType, parentFieldIndex);
4841
+ parentFieldIndex += reflectionType.fields.length;
4842
+ });
4843
+ });
4844
+ const state = new (typeContext.get(reflection.rootType || 0))();
4845
+ return new Decoder(state, typeContext);
4846
+ };
4844
4847
 
4848
+ /**
4849
+ * Legacy callback system
4850
+ *
4851
+ * @param decoder
4852
+ * @returns
4853
+ */
4845
4854
  function getDecoderStateCallbacks(decoder) {
4846
4855
  const $root = decoder.root;
4847
4856
  const callbacks = $root.callbacks;
@@ -4862,7 +4871,7 @@ function getDecoderStateCallbacks(decoder) {
4862
4871
  //
4863
4872
  if ((change.op & OPERATION.DELETE) === OPERATION.DELETE &&
4864
4873
  change.previousValue instanceof Schema) {
4865
- const deleteCallbacks = callbacks[$root.refIds.get(change.previousValue)]?.[OPERATION.DELETE];
4874
+ const deleteCallbacks = callbacks[change.previousValue[$refId]]?.[OPERATION.DELETE];
4866
4875
  for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
4867
4876
  deleteCallbacks[i]();
4868
4877
  }
@@ -4951,7 +4960,7 @@ function getDecoderStateCallbacks(decoder) {
4951
4960
  ) {
4952
4961
  callback(context.instance[prop], undefined);
4953
4962
  }
4954
- return $root.addCallback($root.refIds.get(ref), prop, callback);
4963
+ return $root.addCallback(ref[$refId], prop, callback);
4955
4964
  };
4956
4965
  /**
4957
4966
  * Schema instances
@@ -4971,7 +4980,7 @@ function getDecoderStateCallbacks(decoder) {
4971
4980
  }
4972
4981
  },
4973
4982
  onChange: function onChange(callback) {
4974
- return $root.addCallback($root.refIds.get(context.instance), OPERATION.REPLACE, callback);
4983
+ return $root.addCallback(context.instance[$refId], OPERATION.REPLACE, callback);
4975
4984
  },
4976
4985
  //
4977
4986
  // TODO: refactor `bindTo()` implementation.
@@ -4981,7 +4990,7 @@ function getDecoderStateCallbacks(decoder) {
4981
4990
  if (!properties) {
4982
4991
  properties = Object.keys(metadata).map((index) => metadata[index].name);
4983
4992
  }
4984
- return $root.addCallback($root.refIds.get(context.instance), OPERATION.REPLACE, () => {
4993
+ return $root.addCallback(context.instance[$refId], OPERATION.REPLACE, () => {
4985
4994
  properties.forEach((prop) => targetObject[prop] = context.instance[prop]);
4986
4995
  });
4987
4996
  }
@@ -5000,13 +5009,13 @@ function getDecoderStateCallbacks(decoder) {
5000
5009
  unbind?.();
5001
5010
  }, false);
5002
5011
  // has existing value
5003
- if ($root.refIds.get(instance) !== undefined) {
5012
+ if (instance?.[$refId] !== undefined) {
5004
5013
  callback(instance, true);
5005
5014
  }
5006
5015
  });
5007
5016
  return getProxy(metadataField.type, {
5008
5017
  // make sure refId is available, otherwise need to wait for the instance to be available.
5009
- instance: ($root.refIds.get(instance) && instance),
5018
+ instance: (instance?.[$refId] !== undefined && instance),
5010
5019
  parentInstance: context.instance,
5011
5020
  onInstanceAvailable,
5012
5021
  });
@@ -5030,7 +5039,7 @@ function getDecoderStateCallbacks(decoder) {
5030
5039
  if (immediate) {
5031
5040
  ref.forEach((v, k) => callback(v, k));
5032
5041
  }
5033
- return $root.addCallback($root.refIds.get(ref), OPERATION.ADD, (value, key) => {
5042
+ return $root.addCallback(ref[$refId], OPERATION.ADD, (value, key) => {
5034
5043
  onAddCalls.set(callback, true);
5035
5044
  currentOnAddCallback = callback;
5036
5045
  callback(value, key);
@@ -5039,10 +5048,10 @@ function getDecoderStateCallbacks(decoder) {
5039
5048
  });
5040
5049
  };
5041
5050
  const onRemove = function (ref, callback) {
5042
- return $root.addCallback($root.refIds.get(ref), OPERATION.DELETE, callback);
5051
+ return $root.addCallback(ref[$refId], OPERATION.DELETE, callback);
5043
5052
  };
5044
5053
  const onChange = function (ref, callback) {
5045
- return $root.addCallback($root.refIds.get(ref), OPERATION.REPLACE, callback);
5054
+ return $root.addCallback(ref[$refId], OPERATION.REPLACE, callback);
5046
5055
  };
5047
5056
  return new Proxy({
5048
5057
  onAdd: function (callback, immediate = true) {
@@ -5111,22 +5120,360 @@ function getRawChangesCallback(decoder, callback) {
5111
5120
  decoder.triggerChanges = callback;
5112
5121
  }
5113
5122
 
5123
+ /**
5124
+ * State Callbacks handler
5125
+ *
5126
+ * Usage:
5127
+ * ```ts
5128
+ * const $ = Callbacks.get(decoder);
5129
+ *
5130
+ * // Listen to property changes
5131
+ * $.listen("currentTurn", (currentValue, previousValue) => { ... });
5132
+ *
5133
+ * // Listen to collection additions
5134
+ * $.onAdd("entities", (sessionId, entity) => {
5135
+ * // Nested property listening
5136
+ * $.listen(entity, "hp", (currentHp, previousHp) => { ... });
5137
+ * });
5138
+ *
5139
+ * // Listen to collection removals
5140
+ * $.onRemove("entities", (sessionId, entity) => { ... });
5141
+ *
5142
+ * // Listen to any property change on an instance
5143
+ * $.onChange(entity, () => { ... });
5144
+ *
5145
+ * // Bind properties to another object
5146
+ * $.bindTo(player, playerVisual);
5147
+ * ```
5148
+ */
5149
+ class StateCallbackStrategy {
5150
+ decoder;
5151
+ uniqueRefIds = new Set();
5152
+ isTriggering = false;
5153
+ constructor(decoder) {
5154
+ this.decoder = decoder;
5155
+ this.decoder.triggerChanges = this.triggerChanges.bind(this);
5156
+ }
5157
+ get callbacks() {
5158
+ return this.decoder.root.callbacks;
5159
+ }
5160
+ get state() {
5161
+ return this.decoder.state;
5162
+ }
5163
+ addCallback(refId, operationOrProperty, handler) {
5164
+ const $root = this.decoder.root;
5165
+ return $root.addCallback(refId, operationOrProperty, handler);
5166
+ }
5167
+ addCallbackOrWaitCollectionAvailable(instance, propertyName, operation, handler, immediate = true) {
5168
+ let removeHandler = () => { };
5169
+ const removeOnAdd = () => removeHandler();
5170
+ const collection = instance[propertyName];
5171
+ // Collection not available yet. Listen for its availability before attaching the handler.
5172
+ if (collection === null || collection === undefined) {
5173
+ removeHandler = this.addCallback(instance[$refId], propertyName, (value, _) => {
5174
+ if (value !== null && value !== undefined) {
5175
+ removeHandler = this.addCallback(value[$refId], operation, handler);
5176
+ }
5177
+ });
5178
+ return removeOnAdd;
5179
+ }
5180
+ else {
5181
+ //
5182
+ // Call immediately if collection is already available, if it's an ADD operation.
5183
+ //
5184
+ immediate = immediate && this.isTriggering === false;
5185
+ if (operation === OPERATION.ADD && immediate) {
5186
+ collection.forEach((value, key) => {
5187
+ handler(key, value);
5188
+ });
5189
+ }
5190
+ return this.addCallback(collection[$refId], operation, handler);
5191
+ }
5192
+ }
5193
+ listen(...args) {
5194
+ if (typeof args[0] === 'string') {
5195
+ // listen(property, handler, immediate?)
5196
+ return this.listenInstance(this.state, args[0], args[1], args[2]);
5197
+ }
5198
+ else {
5199
+ // listen(instance, property, handler, immediate?)
5200
+ return this.listenInstance(args[0], args[1], args[2], args[3]);
5201
+ }
5202
+ }
5203
+ listenInstance(instance, propertyName, handler, immediate = true) {
5204
+ immediate = immediate && this.isTriggering === false;
5205
+ //
5206
+ // Call handler immediately if property is already available.
5207
+ //
5208
+ const currentValue = instance[propertyName];
5209
+ if (immediate && currentValue !== null && currentValue !== undefined) {
5210
+ handler(currentValue, undefined);
5211
+ }
5212
+ return this.addCallback(instance[$refId], propertyName, handler);
5213
+ }
5214
+ onChange(...args) {
5215
+ if (args.length === 2 && typeof args[0] !== 'string') {
5216
+ // onChange(instance, handler) - instance change
5217
+ const instance = args[0];
5218
+ const handler = args[1];
5219
+ return this.addCallback(instance[$refId], OPERATION.REPLACE, handler);
5220
+ }
5221
+ if (typeof args[0] === 'string') {
5222
+ // onChange(property, handler) - collection on root state
5223
+ return this.addCallbackOrWaitCollectionAvailable(this.state, args[0], OPERATION.REPLACE, args[1]);
5224
+ }
5225
+ else {
5226
+ // onChange(instance, property, handler) - nested collection
5227
+ return this.addCallbackOrWaitCollectionAvailable(args[0], args[1], OPERATION.REPLACE, args[2]);
5228
+ }
5229
+ }
5230
+ onAdd(...args) {
5231
+ if (typeof args[0] === 'string') {
5232
+ // onAdd(property, handler, immediate?) - collection on root state
5233
+ return this.addCallbackOrWaitCollectionAvailable(this.state, args[0], OPERATION.ADD, args[1], args[2] !== false);
5234
+ }
5235
+ else {
5236
+ // onAdd(instance, property, handler, immediate?) - nested collection
5237
+ return this.addCallbackOrWaitCollectionAvailable(args[0], args[1], OPERATION.ADD, args[2], args[3] !== false);
5238
+ }
5239
+ }
5240
+ onRemove(...args) {
5241
+ if (typeof args[0] === 'string') {
5242
+ // onRemove(property, handler) - collection on root state
5243
+ return this.addCallbackOrWaitCollectionAvailable(this.state, args[0], OPERATION.DELETE, args[1]);
5244
+ }
5245
+ else {
5246
+ // onRemove(instance, property, handler) - nested collection
5247
+ return this.addCallbackOrWaitCollectionAvailable(args[0], args[1], OPERATION.DELETE, args[2]);
5248
+ }
5249
+ }
5250
+ /**
5251
+ * Bind properties from a Schema instance to a target object.
5252
+ * Changes will be automatically reflected on the target object.
5253
+ */
5254
+ bindTo(from, to, properties, immediate = true) {
5255
+ const metadata = from.constructor[Symbol.metadata];
5256
+ // If no properties specified, bind all properties
5257
+ if (!properties) {
5258
+ properties = Object.keys(metadata)
5259
+ .filter(key => !isNaN(Number(key)))
5260
+ .map((index) => metadata[index].name);
5261
+ }
5262
+ const action = () => {
5263
+ for (const prop of properties) {
5264
+ const fromValue = from[prop];
5265
+ if (fromValue !== undefined) {
5266
+ to[prop] = fromValue;
5267
+ }
5268
+ }
5269
+ };
5270
+ if (immediate) {
5271
+ action();
5272
+ }
5273
+ return this.addCallback(from[$refId], OPERATION.REPLACE, action);
5274
+ }
5275
+ triggerChanges(allChanges) {
5276
+ this.uniqueRefIds.clear();
5277
+ for (let i = 0, l = allChanges.length; i < l; i++) {
5278
+ const change = allChanges[i];
5279
+ const refId = change.refId;
5280
+ const ref = change.ref;
5281
+ const $callbacks = this.callbacks[refId];
5282
+ if (!$callbacks) {
5283
+ continue;
5284
+ }
5285
+ //
5286
+ // trigger onRemove on child structure.
5287
+ //
5288
+ if ((change.op & OPERATION.DELETE) === OPERATION.DELETE &&
5289
+ change.previousValue instanceof Schema) {
5290
+ const childRefId = change.previousValue[$refId];
5291
+ const deleteCallbacks = this.callbacks[childRefId]?.[OPERATION.DELETE];
5292
+ if (deleteCallbacks) {
5293
+ for (let j = deleteCallbacks.length - 1; j >= 0; j--) {
5294
+ deleteCallbacks[j]();
5295
+ }
5296
+ }
5297
+ }
5298
+ if (ref instanceof Schema) {
5299
+ //
5300
+ // Handle Schema instance
5301
+ //
5302
+ if (!this.uniqueRefIds.has(refId)) {
5303
+ // trigger onChange
5304
+ const replaceCallbacks = $callbacks[OPERATION.REPLACE];
5305
+ if (replaceCallbacks) {
5306
+ for (let j = replaceCallbacks.length - 1; j >= 0; j--) {
5307
+ try {
5308
+ replaceCallbacks[j]();
5309
+ }
5310
+ catch (e) {
5311
+ console.error(e);
5312
+ }
5313
+ }
5314
+ }
5315
+ }
5316
+ // trigger field callbacks
5317
+ const fieldCallbacks = $callbacks[change.field];
5318
+ if (fieldCallbacks) {
5319
+ for (let j = fieldCallbacks.length - 1; j >= 0; j--) {
5320
+ try {
5321
+ this.isTriggering = true;
5322
+ fieldCallbacks[j](change.value, change.previousValue);
5323
+ }
5324
+ catch (e) {
5325
+ console.error(e);
5326
+ }
5327
+ finally {
5328
+ this.isTriggering = false;
5329
+ }
5330
+ }
5331
+ }
5332
+ }
5333
+ else {
5334
+ //
5335
+ // Handle collection of items
5336
+ //
5337
+ const dynamicIndex = change.dynamicIndex ?? change.field;
5338
+ if ((change.op & OPERATION.DELETE) === OPERATION.DELETE) {
5339
+ //
5340
+ // FIXME: `previousValue` should always be available.
5341
+ //
5342
+ if (change.previousValue !== undefined) {
5343
+ // trigger onRemove (key, value)
5344
+ const deleteCallbacks = $callbacks[OPERATION.DELETE];
5345
+ if (deleteCallbacks) {
5346
+ for (let j = deleteCallbacks.length - 1; j >= 0; j--) {
5347
+ deleteCallbacks[j](dynamicIndex, change.previousValue);
5348
+ }
5349
+ }
5350
+ }
5351
+ // Handle DELETE_AND_ADD operation
5352
+ if ((change.op & OPERATION.ADD) === OPERATION.ADD) {
5353
+ const addCallbacks = $callbacks[OPERATION.ADD];
5354
+ if (addCallbacks) {
5355
+ this.isTriggering = true;
5356
+ for (let j = addCallbacks.length - 1; j >= 0; j--) {
5357
+ addCallbacks[j](dynamicIndex, change.value);
5358
+ }
5359
+ this.isTriggering = false;
5360
+ }
5361
+ }
5362
+ }
5363
+ else if ((change.op & OPERATION.ADD) === OPERATION.ADD &&
5364
+ change.previousValue !== change.value) {
5365
+ // trigger onAdd (key, value)
5366
+ const addCallbacks = $callbacks[OPERATION.ADD];
5367
+ if (addCallbacks) {
5368
+ this.isTriggering = true;
5369
+ for (let j = addCallbacks.length - 1; j >= 0; j--) {
5370
+ addCallbacks[j](dynamicIndex, change.value);
5371
+ }
5372
+ this.isTriggering = false;
5373
+ }
5374
+ }
5375
+ // trigger onChange (key, value)
5376
+ if (change.value !== change.previousValue) {
5377
+ const replaceCallbacks = $callbacks[OPERATION.REPLACE];
5378
+ if (replaceCallbacks) {
5379
+ for (let j = replaceCallbacks.length - 1; j >= 0; j--) {
5380
+ replaceCallbacks[j](dynamicIndex, change.value);
5381
+ }
5382
+ }
5383
+ }
5384
+ }
5385
+ this.uniqueRefIds.add(refId);
5386
+ }
5387
+ }
5388
+ }
5389
+ /**
5390
+ * Factory class for retrieving the callbacks API.
5391
+ */
5392
+ const Callbacks = {
5393
+ /**
5394
+ * Get the new callbacks standard API.
5395
+ *
5396
+ * Usage:
5397
+ * ```ts
5398
+ * const callbacks = Callbacks.get(roomOrDecoder);
5399
+ *
5400
+ * // Listen to property changes
5401
+ * callbacks.listen("currentTurn", (currentValue, previousValue) => { ... });
5402
+ *
5403
+ * // Listen to collection additions
5404
+ * callbacks.onAdd("entities", (sessionId, entity) => {
5405
+ * // Nested property listening
5406
+ * callbacks.listen(entity, "hp", (currentHp, previousHp) => { ... });
5407
+ * });
5408
+ *
5409
+ * // Listen to collection removals
5410
+ * callbacks.onRemove("entities", (sessionId, entity) => { ... });
5411
+ *
5412
+ * // Listen to any property change on an instance
5413
+ * callbacks.onChange(entity, () => { ... });
5414
+ *
5415
+ * // Bind properties to another object
5416
+ * callbacks.bindTo(player, playerVisual);
5417
+ * ```
5418
+ *
5419
+ * @param roomOrDecoder - Room or Decoder instance to get the callbacks for.
5420
+ * @returns the new callbacks standard API.
5421
+ */
5422
+ get(roomOrDecoder) {
5423
+ if (roomOrDecoder instanceof Decoder) {
5424
+ return new StateCallbackStrategy(roomOrDecoder);
5425
+ }
5426
+ else if (roomOrDecoder.serializer.decoder) {
5427
+ return new StateCallbackStrategy(roomOrDecoder.serializer.decoder);
5428
+ }
5429
+ else {
5430
+ throw new Error('Invalid room or decoder');
5431
+ }
5432
+ },
5433
+ /**
5434
+ * Get the legacy callbacks API.
5435
+ *
5436
+ * We aim to deprecate this API on 1.0, and iterate on improving Callbacks.get() API.
5437
+ *
5438
+ * @param roomOrDecoder - Room or Decoder instance to get the legacy callbacks for.
5439
+ * @returns the legacy callbacks API.
5440
+ */
5441
+ getLegacy(roomOrDecoder) {
5442
+ if (roomOrDecoder instanceof Decoder) {
5443
+ return getDecoderStateCallbacks(roomOrDecoder);
5444
+ }
5445
+ else if (roomOrDecoder.serializer.decoder) {
5446
+ return getDecoderStateCallbacks(roomOrDecoder.serializer.decoder);
5447
+ }
5448
+ },
5449
+ getRawChanges(decoder, callback) {
5450
+ return getRawChangesCallback(decoder, callback);
5451
+ }
5452
+ };
5453
+
5114
5454
  class StateView {
5455
+ iterable;
5456
+ /**
5457
+ * Iterable list of items that are visible to this view
5458
+ * (Available only if constructed with `iterable: true`)
5459
+ */
5460
+ items;
5461
+ /**
5462
+ * List of ChangeTree's that are visible to this view
5463
+ */
5464
+ visible = new WeakSet();
5465
+ /**
5466
+ * List of ChangeTree's that are invisible to this view
5467
+ */
5468
+ invisible = new WeakSet();
5469
+ tags; // TODO: use bit manipulation instead of Set<number> ()
5470
+ /**
5471
+ * Manual "ADD" operations for changes per ChangeTree, specific to this view.
5472
+ * (This is used to force encoding a property, even if it was not changed)
5473
+ */
5474
+ changes = new Map();
5115
5475
  constructor(iterable = false) {
5116
5476
  this.iterable = iterable;
5117
- /**
5118
- * List of ChangeTree's that are visible to this view
5119
- */
5120
- this.visible = new WeakSet();
5121
- /**
5122
- * List of ChangeTree's that are invisible to this view
5123
- */
5124
- this.invisible = new WeakSet();
5125
- /**
5126
- * Manual "ADD" operations for changes per ChangeTree, specific to this view.
5127
- * (This is used to force encoding a property, even if it was not changed)
5128
- */
5129
- this.changes = new Map();
5130
5477
  if (iterable) {
5131
5478
  this.items = [];
5132
5479
  }
@@ -5140,7 +5487,7 @@ class StateView {
5140
5487
  return false;
5141
5488
  }
5142
5489
  else if (!parentChangeTree &&
5143
- changeTree.refId !== 0 // allow root object
5490
+ obj[$refId] !== 0 // allow root object
5144
5491
  ) {
5145
5492
  /**
5146
5493
  * TODO: can we avoid this?
@@ -5164,11 +5511,11 @@ class StateView {
5164
5511
  if (checkIncludeParent && parentChangeTree) {
5165
5512
  this.addParentOf(changeTree, tag);
5166
5513
  }
5167
- let changes = this.changes.get(changeTree.refId);
5514
+ let changes = this.changes.get(obj[$refId]);
5168
5515
  if (changes === undefined) {
5169
5516
  changes = {};
5170
5517
  // FIXME / OPTIMIZE: do not add if no changes are needed
5171
- this.changes.set(changeTree.refId, changes);
5518
+ this.changes.set(obj[$refId], changes);
5172
5519
  }
5173
5520
  let isChildAdded = false;
5174
5521
  //
@@ -5248,10 +5595,10 @@ class StateView {
5248
5595
  }
5249
5596
  // add parent's tag properties
5250
5597
  if (changeTree.getChange(parentIndex) !== OPERATION.DELETE) {
5251
- let changes = this.changes.get(changeTree.refId);
5598
+ let changes = this.changes.get(changeTree.ref[$refId]);
5252
5599
  if (changes === undefined) {
5253
5600
  changes = {};
5254
- this.changes.set(changeTree.refId, changes);
5601
+ this.changes.set(changeTree.ref[$refId], changes);
5255
5602
  }
5256
5603
  if (!this.tags) {
5257
5604
  this.tags = new WeakMap();
@@ -5283,27 +5630,28 @@ class StateView {
5283
5630
  }
5284
5631
  const ref = changeTree.ref;
5285
5632
  const metadata = ref.constructor[Symbol.metadata]; // ArraySchema/MapSchema do not have metadata
5286
- let changes = this.changes.get(changeTree.refId);
5633
+ const refId = ref[$refId];
5634
+ let changes = this.changes.get(refId);
5287
5635
  if (changes === undefined) {
5288
5636
  changes = {};
5289
- this.changes.set(changeTree.refId, changes);
5637
+ this.changes.set(refId, changes);
5290
5638
  }
5291
5639
  if (tag === DEFAULT_VIEW_TAG) {
5292
5640
  // parent is collection (Map/Array)
5293
5641
  const parent = changeTree.parent;
5294
5642
  if (parent && !Metadata.isValidInstance(parent) && changeTree.isFiltered) {
5295
- const parentChangeTree = parent[$changes];
5296
- let changes = this.changes.get(parentChangeTree.refId);
5643
+ const parentRefId = parent[$refId];
5644
+ let changes = this.changes.get(parentRefId);
5297
5645
  if (changes === undefined) {
5298
5646
  changes = {};
5299
- this.changes.set(parentChangeTree.refId, changes);
5647
+ this.changes.set(parentRefId, changes);
5300
5648
  }
5301
5649
  else if (changes[changeTree.parentIndex] === OPERATION.ADD) {
5302
5650
  //
5303
5651
  // SAME PATCH ADD + REMOVE:
5304
5652
  // The 'changes' of deleted structure should be ignored.
5305
5653
  //
5306
- this.changes.delete(changeTree.refId);
5654
+ this.changes.delete(refId);
5307
5655
  }
5308
5656
  // DELETE / DELETE BY REF ID
5309
5657
  changes[changeTree.parentIndex] = OPERATION.DELETE;
@@ -5363,7 +5711,7 @@ class StateView {
5363
5711
  if (!isVisible && changeTree.isVisibilitySharedWithParent) {
5364
5712
  // console.log("CHECK AGAINST PARENT...", {
5365
5713
  // ref: changeTree.ref.constructor.name,
5366
- // refId: changeTree.refId,
5714
+ // refId: changeTree.ref[$refId],
5367
5715
  // parent: changeTree.parent.constructor.name,
5368
5716
  // });
5369
5717
  if (this.visible.has(changeTree.parent[$changes])) {
@@ -5386,5 +5734,5 @@ registerType("array", { constructor: ArraySchema });
5386
5734
  registerType("set", { constructor: SetSchema });
5387
5735
  registerType("collection", { constructor: CollectionSchema, });
5388
5736
 
5389
- export { $changes, $childType, $decoder, $deleteByIndex, $encoder, $filter, $getByIndex, $track, ArraySchema, ChangeTree, CollectionSchema, Decoder, Encoder, MapSchema, Metadata, OPERATION, Reflection, ReflectionField, ReflectionType, Schema, SetSchema, StateView, TypeContext, decode, decodeKeyValueOperation, decodeSchemaOperation, defineCustomTypes, defineTypes, deprecated, dumpChanges, encode, encodeArray, encodeKeyValueOperation, encodeSchemaOperation, entity, getDecoderStateCallbacks, getRawChangesCallback, registerType, schema, type, view };
5737
+ export { $changes, $childType, $decoder, $deleteByIndex, $encoder, $filter, $getByIndex, $refId, $track, ArraySchema, Callbacks, ChangeTree, CollectionSchema, Decoder, Encoder, MapSchema, Metadata, OPERATION, Reflection, ReflectionField, ReflectionType, Schema, SetSchema, StateCallbackStrategy, StateView, TypeContext, decode, decodeKeyValueOperation, decodeSchemaOperation, defineCustomTypes, defineTypes, deprecated, dumpChanges, encode, encodeArray, encodeKeyValueOperation, encodeSchemaOperation, entity, getDecoderStateCallbacks, getRawChangesCallback, registerType, schema, type, view };
5390
5738
  //# sourceMappingURL=index.mjs.map