@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
@@ -32,6 +32,7 @@
32
32
 
33
33
  Symbol.metadata ??= Symbol.for("Symbol.metadata");
34
34
 
35
+ const $refId = "~refId";
35
36
  const $track = "~track";
36
37
  const $encoder = "~encoder";
37
38
  const $decoder = "~decoder";
@@ -562,12 +563,16 @@
562
563
  }
563
564
 
564
565
  class TypeContext {
566
+ types = {};
567
+ schemas = new Map();
568
+ hasFilters = false;
569
+ parentFiltered = {};
565
570
  /**
566
571
  * For inheritance support
567
572
  * Keeps track of which classes extends which. (parent -> children)
568
573
  */
569
- static { this.inheritedTypes = new Map(); }
570
- static { this.cachedContexts = new Map(); }
574
+ static inheritedTypes = new Map();
575
+ static cachedContexts = new Map();
571
576
  static register(target) {
572
577
  const parent = Object.getPrototypeOf(target);
573
578
  if (parent !== Schema) {
@@ -588,10 +593,6 @@
588
593
  return context;
589
594
  }
590
595
  constructor(rootClass) {
591
- this.types = {};
592
- this.schemas = new Map();
593
- this.hasFilters = false;
594
- this.parentFiltered = {};
595
596
  if (rootClass) {
596
597
  this.discoverTypes(rootClass);
597
598
  }
@@ -984,25 +985,33 @@
984
985
  delete changeSet.indexes[index];
985
986
  }
986
987
  class ChangeTree {
988
+ ref;
989
+ metadata;
990
+ root;
991
+ parentChain; // Linked list for tracking parents
992
+ /**
993
+ * Whether this structure is parent of a filtered structure.
994
+ */
995
+ isFiltered = false;
996
+ isVisibilitySharedWithParent; // See test case: 'should not be required to manually call view.add() items to child arrays without @view() tag'
997
+ indexedOperations = {};
998
+ //
999
+ // TODO:
1000
+ // try storing the index + operation per item.
1001
+ // example: 1024 & 1025 => ADD, 1026 => DELETE
1002
+ //
1003
+ // => https://chatgpt.com/share/67107d0c-bc20-8004-8583-83b17dd7c196
1004
+ //
1005
+ changes = { indexes: {}, operations: [] };
1006
+ allChanges = { indexes: {}, operations: [] };
1007
+ filteredChanges;
1008
+ allFilteredChanges;
1009
+ indexes; // TODO: remove this, only used by MapSchema/SetSchema/CollectionSchema (`encodeKeyValueOperation`)
1010
+ /**
1011
+ * Is this a new instance? Used on ArraySchema to determine OPERATION.MOVE_AND_ADD operation.
1012
+ */
1013
+ isNew = true;
987
1014
  constructor(ref) {
988
- /**
989
- * Whether this structure is parent of a filtered structure.
990
- */
991
- this.isFiltered = false;
992
- this.indexedOperations = {};
993
- //
994
- // TODO:
995
- // try storing the index + operation per item.
996
- // example: 1024 & 1025 => ADD, 1026 => DELETE
997
- //
998
- // => https://chatgpt.com/share/67107d0c-bc20-8004-8583-83b17dd7c196
999
- //
1000
- this.changes = { indexes: {}, operations: [] };
1001
- this.allChanges = { indexes: {}, operations: [] };
1002
- /**
1003
- * Is this a new instance? Used on ArraySchema to determine OPERATION.MOVE_AND_ADD operation.
1004
- */
1005
- this.isNew = true;
1006
1015
  this.ref = ref;
1007
1016
  this.metadata = ref.constructor[Symbol.metadata];
1008
1017
  //
@@ -1474,7 +1483,7 @@
1474
1483
  // Encode refId for this instance.
1475
1484
  // The actual instance is going to be encoded on next `changeTree` iteration.
1476
1485
  //
1477
- encode.number(bytes, value[$changes].refId, it);
1486
+ encode.number(bytes, value[$refId], it);
1478
1487
  // Try to encode inherited TYPE_ID if it's an ADD operation.
1479
1488
  if ((operation & exports.OPERATION.ADD) === exports.OPERATION.ADD) {
1480
1489
  encoder.tryEncodeTypeId(bytes, type, value.constructor, it);
@@ -1485,7 +1494,7 @@
1485
1494
  // Encode refId for this instance.
1486
1495
  // The actual instance is going to be encoded on next `changeTree` iteration.
1487
1496
  //
1488
- encode.number(bytes, value[$changes].refId, it);
1497
+ encode.number(bytes, value[$refId], it);
1489
1498
  }
1490
1499
  }
1491
1500
  /**
@@ -1561,7 +1570,7 @@
1561
1570
  if (!item) {
1562
1571
  return;
1563
1572
  }
1564
- refOrIndex = item[$changes].refId;
1573
+ refOrIndex = item[$refId];
1565
1574
  if (operation === exports.OPERATION.DELETE) {
1566
1575
  operation = exports.OPERATION.DELETE_BY_REFID;
1567
1576
  }
@@ -1601,7 +1610,7 @@
1601
1610
  let value;
1602
1611
  if ((operation & exports.OPERATION.DELETE) === exports.OPERATION.DELETE) {
1603
1612
  // Flag `refId` for garbage collection.
1604
- const previousRefId = $root.refIds.get(previousValue);
1613
+ const previousRefId = previousValue?.[$refId];
1605
1614
  if (previousRefId !== undefined) {
1606
1615
  $root.removeRef(previousRefId);
1607
1616
  }
@@ -1642,7 +1651,7 @@
1642
1651
  value = valueRef.clone(true);
1643
1652
  value[$childType] = Object.values(type)[0]; // cache childType for ArraySchema and MapSchema
1644
1653
  if (previousValue) {
1645
- let previousRefId = $root.refIds.get(previousValue);
1654
+ let previousRefId = previousValue[$refId];
1646
1655
  if (previousRefId !== undefined && refId !== previousRefId) {
1647
1656
  //
1648
1657
  // enqueue onRemove if structure has been replaced.
@@ -1653,7 +1662,7 @@
1653
1662
  const [key, value] = iter.value;
1654
1663
  // if value is a schema, remove its reference
1655
1664
  if (typeof (value) === "object") {
1656
- previousRefId = $root.refIds.get(value);
1665
+ previousRefId = value[$refId];
1657
1666
  $root.removeRef(previousRefId);
1658
1667
  }
1659
1668
  allChanges.push({
@@ -1882,7 +1891,6 @@
1882
1891
  }
1883
1892
  }
1884
1893
 
1885
- var _a$4, _b$4;
1886
1894
  const DEFAULT_SORT = (a, b) => {
1887
1895
  const A = a.toString();
1888
1896
  const B = b.toString();
@@ -1894,8 +1902,15 @@
1894
1902
  return 0;
1895
1903
  };
1896
1904
  class ArraySchema {
1897
- static { this[_a$4] = encodeArray; }
1898
- static { this[_b$4] = decodeArray; }
1905
+ [$changes];
1906
+ [$refId];
1907
+ [$childType];
1908
+ items = [];
1909
+ tmpItems = [];
1910
+ deletedIndexes = {};
1911
+ isMovingItems = false;
1912
+ static [$encoder] = encodeArray;
1913
+ static [$decoder] = decodeArray;
1899
1914
  /**
1900
1915
  * Determine if a property must be filtered.
1901
1916
  * - If returns false, the property is NOT going to be encoded.
@@ -1905,7 +1920,7 @@
1905
1920
  * - First, the encoder iterates over all "not owned" properties and encodes them.
1906
1921
  * - Then, the encoder iterates over all "owned" properties per instance and encodes them.
1907
1922
  */
1908
- static [(_a$4 = $encoder, _b$4 = $decoder, $filter)](ref, index, view) {
1923
+ static [$filter](ref, index, view) {
1909
1924
  return (!view ||
1910
1925
  typeof (ref[$childType]) === "string" ||
1911
1926
  view.isChangeTreeVisible(ref['tmpItems'][index]?.[$changes]));
@@ -1921,10 +1936,6 @@
1921
1936
  return new ArraySchema(...Array.from(iterable));
1922
1937
  }
1923
1938
  constructor(...items) {
1924
- this.items = [];
1925
- this.tmpItems = [];
1926
- this.deletedIndexes = {};
1927
- this.isMovingItems = false;
1928
1939
  Object.defineProperty(this, $childType, {
1929
1940
  value: undefined,
1930
1941
  enumerable: false,
@@ -2441,6 +2452,10 @@
2441
2452
  static get [Symbol.species]() {
2442
2453
  return ArraySchema;
2443
2454
  }
2455
+ // WORKAROUND for compatibility
2456
+ // - TypeScript 4 defines @@unscopables as a function
2457
+ // - TypeScript 5 defines @@unscopables as an object
2458
+ [Symbol.unscopables];
2444
2459
  /**
2445
2460
  * Returns an iterable of key, value pairs for every entry in the array
2446
2461
  */
@@ -2550,7 +2565,7 @@
2550
2565
  this.isMovingItems = false;
2551
2566
  return this;
2552
2567
  }
2553
- [($getByIndex)](index, isEncodeAll = false) {
2568
+ [$getByIndex](index, isEncodeAll = false) {
2554
2569
  //
2555
2570
  // TODO: avoid unecessary `this.tmpItems` check during decoding.
2556
2571
  //
@@ -2605,10 +2620,16 @@
2605
2620
  }
2606
2621
  registerType("array", { constructor: ArraySchema });
2607
2622
 
2608
- var _a$3, _b$3;
2609
2623
  class MapSchema {
2610
- static { this[_a$3] = encodeKeyValueOperation; }
2611
- static { this[_b$3] = decodeKeyValueOperation; }
2624
+ [$changes];
2625
+ [$refId];
2626
+ childType;
2627
+ [$childType];
2628
+ $items = new Map();
2629
+ $indexes = new Map();
2630
+ deletedItems = {};
2631
+ static [$encoder] = encodeKeyValueOperation;
2632
+ static [$decoder] = decodeKeyValueOperation;
2612
2633
  /**
2613
2634
  * Determine if a property must be filtered.
2614
2635
  * - If returns false, the property is NOT going to be encoded.
@@ -2618,7 +2639,7 @@
2618
2639
  * - First, the encoder iterates over all "not owned" properties and encodes them.
2619
2640
  * - Then, the encoder iterates over all "owned" properties per instance and encodes them.
2620
2641
  */
2621
- static [(_a$3 = $encoder, _b$3 = $decoder, $filter)](ref, index, view) {
2642
+ static [$filter](ref, index, view) {
2622
2643
  return (!view ||
2623
2644
  typeof (ref[$childType]) === "string" ||
2624
2645
  view.isChangeTreeVisible((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
@@ -2627,9 +2648,6 @@
2627
2648
  return type['map'] !== undefined;
2628
2649
  }
2629
2650
  constructor(initialValues) {
2630
- this.$items = new Map();
2631
- this.$indexes = new Map();
2632
- this.deletedItems = {};
2633
2651
  const changeTree = new ChangeTree(this);
2634
2652
  changeTree.indexes = {};
2635
2653
  Object.defineProperty(this, $changes, {
@@ -2820,10 +2838,16 @@
2820
2838
  }
2821
2839
  registerType("map", { constructor: MapSchema });
2822
2840
 
2823
- var _a$2, _b$2;
2824
2841
  class CollectionSchema {
2825
- static { this[_a$2] = encodeKeyValueOperation; }
2826
- static { this[_b$2] = decodeKeyValueOperation; }
2842
+ [$changes];
2843
+ [$refId];
2844
+ [$childType];
2845
+ $items = new Map();
2846
+ $indexes = new Map();
2847
+ deletedItems = {};
2848
+ $refId = 0;
2849
+ static [$encoder] = encodeKeyValueOperation;
2850
+ static [$decoder] = decodeKeyValueOperation;
2827
2851
  /**
2828
2852
  * Determine if a property must be filtered.
2829
2853
  * - If returns false, the property is NOT going to be encoded.
@@ -2833,7 +2857,7 @@
2833
2857
  * - First, the encoder iterates over all "not owned" properties and encodes them.
2834
2858
  * - Then, the encoder iterates over all "owned" properties per instance and encodes them.
2835
2859
  */
2836
- static [(_a$2 = $encoder, _b$2 = $decoder, $filter)](ref, index, view) {
2860
+ static [$filter](ref, index, view) {
2837
2861
  return (!view ||
2838
2862
  typeof (ref[$childType]) === "string" ||
2839
2863
  view.isChangeTreeVisible((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
@@ -2842,10 +2866,6 @@
2842
2866
  return type['collection'] !== undefined;
2843
2867
  }
2844
2868
  constructor(initialValues) {
2845
- this.$items = new Map();
2846
- this.$indexes = new Map();
2847
- this.deletedItems = {};
2848
- this.$refId = 0;
2849
2869
  this[$changes] = new ChangeTree(this);
2850
2870
  this[$changes].indexes = {};
2851
2871
  if (initialValues) {
@@ -2984,10 +3004,16 @@
2984
3004
  }
2985
3005
  registerType("collection", { constructor: CollectionSchema, });
2986
3006
 
2987
- var _a$1, _b$1;
2988
3007
  class SetSchema {
2989
- static { this[_a$1] = encodeKeyValueOperation; }
2990
- static { this[_b$1] = decodeKeyValueOperation; }
3008
+ [$changes];
3009
+ [$refId];
3010
+ [$childType];
3011
+ $items = new Map();
3012
+ $indexes = new Map();
3013
+ deletedItems = {};
3014
+ $refId = 0;
3015
+ static [$encoder] = encodeKeyValueOperation;
3016
+ static [$decoder] = decodeKeyValueOperation;
2991
3017
  /**
2992
3018
  * Determine if a property must be filtered.
2993
3019
  * - If returns false, the property is NOT going to be encoded.
@@ -2997,7 +3023,7 @@
2997
3023
  * - First, the encoder iterates over all "not owned" properties and encodes them.
2998
3024
  * - Then, the encoder iterates over all "owned" properties per instance and encodes them.
2999
3025
  */
3000
- static [(_a$1 = $encoder, _b$1 = $decoder, $filter)](ref, index, view) {
3026
+ static [$filter](ref, index, view) {
3001
3027
  return (!view ||
3002
3028
  typeof (ref[$childType]) === "string" ||
3003
3029
  view.visible.has((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
@@ -3006,10 +3032,6 @@
3006
3032
  return type['set'] !== undefined;
3007
3033
  }
3008
3034
  constructor(initialValues) {
3009
- this.$items = new Map();
3010
- this.$indexes = new Map();
3011
- this.deletedItems = {};
3012
- this.$refId = 0;
3013
3035
  this[$changes] = new ChangeTree(this);
3014
3036
  this[$changes].indexes = {};
3015
3037
  if (initialValues) {
@@ -3508,7 +3530,10 @@
3508
3530
  ? DEFAULT_VIEW_TAG
3509
3531
  : value['view'];
3510
3532
  }
3511
- fields[fieldName] = getNormalizedType(value);
3533
+ // allow to define a field as not synced
3534
+ if (value['sync'] !== false) {
3535
+ fields[fieldName] = getNormalizedType(value);
3536
+ }
3512
3537
  // If no explicit default provided, handle automatic instantiation for collection types
3513
3538
  if (!Object.prototype.hasOwnProperty.call(value, 'default')) {
3514
3539
  // TODO: remove Array.isArray() check. Use ['array'] !== undefined only.
@@ -3530,12 +3555,7 @@
3530
3555
  }
3531
3556
  else if (value['type'] !== undefined && Schema.is(value['type'])) {
3532
3557
  // Direct Schema type: Type → new Type()
3533
- if (!value['type'].prototype.initialize || value['type'].prototype.initialize.length === 0) {
3534
- // only auto-initialize Schema instances if:
3535
- // - they don't have an initialize method
3536
- // - or initialize method doesn't accept any parameters
3537
- defaultValues[fieldName] = new value['type']();
3538
- }
3558
+ defaultValues[fieldName] = new value['type']();
3539
3559
  }
3540
3560
  }
3541
3561
  else {
@@ -3545,12 +3565,7 @@
3545
3565
  else if (typeof (value) === "function") {
3546
3566
  if (Schema.is(value)) {
3547
3567
  // Direct Schema type: Type → new Type()
3548
- if (!value.prototype.initialize || value.prototype.initialize.length === 0) {
3549
- // only auto-initialize Schema instances if:
3550
- // - they don't have an initialize method
3551
- // - or initialize method doesn't accept any parameters
3552
- defaultValues[fieldName] = new value();
3553
- }
3568
+ defaultValues[fieldName] = new value();
3554
3569
  fields[fieldName] = getNormalizedType(value);
3555
3570
  }
3556
3571
  else {
@@ -3577,32 +3592,13 @@
3577
3592
  }
3578
3593
  return defaults;
3579
3594
  };
3580
- const getParentProps = (props) => {
3581
- const fieldNames = Object.keys(fields);
3582
- const parentProps = {};
3583
- for (const key in props) {
3584
- if (!fieldNames.includes(key)) {
3585
- parentProps[key] = props[key];
3586
- }
3587
- }
3588
- return parentProps;
3589
- };
3590
3595
  /** @codegen-ignore */
3591
3596
  const klass = Metadata.setFields(class extends inherits {
3592
3597
  constructor(...args) {
3598
+ super(Object.assign({}, getDefaultValues(), args[0] || {}));
3593
3599
  // call initialize method
3594
3600
  if (methods.initialize && typeof methods.initialize === 'function') {
3595
- super(Object.assign({}, getDefaultValues(), getParentProps(args[0] || {})));
3596
- /**
3597
- * only call initialize() in the current class, not the parent ones.
3598
- * see "should not call initialize automatically when creating an instance of inherited Schema"
3599
- */
3600
- if (new.target === klass) {
3601
- methods.initialize.apply(this, args);
3602
- }
3603
- }
3604
- else {
3605
- super(Object.assign({}, getDefaultValues(), args[0] || {}));
3601
+ methods.initialize.apply(this, args);
3606
3602
  }
3607
3603
  }
3608
3604
  }, fields);
@@ -3639,7 +3635,7 @@
3639
3635
  continue;
3640
3636
  }
3641
3637
  const changes = changeTree.indexedOperations;
3642
- dump.refs.push(`refId#${changeTree.refId}`);
3638
+ dump.refs.push(`refId#${changeTree.ref[$refId]}`);
3643
3639
  for (const index in changes) {
3644
3640
  const op = changes[index];
3645
3641
  const opName = exports.OPERATION[op];
@@ -3653,13 +3649,14 @@
3653
3649
  return dump;
3654
3650
  }
3655
3651
 
3656
- var _a, _b;
3657
3652
  /**
3658
3653
  * Schema encoder / decoder
3659
3654
  */
3660
3655
  class Schema {
3661
- static { this[_a] = encodeSchemaOperation; }
3662
- static { this[_b] = decodeSchemaOperation; }
3656
+ static [Symbol.metadata];
3657
+ static [$encoder] = encodeSchemaOperation;
3658
+ static [$decoder] = decodeSchemaOperation;
3659
+ [$refId];
3663
3660
  /**
3664
3661
  * Assign the property descriptors required to track changes on this instance.
3665
3662
  * @param instance
@@ -3678,7 +3675,7 @@
3678
3675
  /**
3679
3676
  * Track property changes
3680
3677
  */
3681
- static [(_a = $encoder, _b = $decoder, $track)](changeTree, index, operation = exports.OPERATION.ADD) {
3678
+ static [$track](changeTree, index, operation = exports.OPERATION.ADD) {
3682
3679
  changeTree.change(index, operation);
3683
3680
  }
3684
3681
  /**
@@ -3725,10 +3722,74 @@
3725
3722
  Object.assign(this, arg);
3726
3723
  }
3727
3724
  }
3725
+ /**
3726
+ * Assign properties to the instance.
3727
+ * @param props Properties to assign to the instance
3728
+ * @returns
3729
+ */
3728
3730
  assign(props) {
3729
3731
  Object.assign(this, props);
3730
3732
  return this;
3731
3733
  }
3734
+ /**
3735
+ * Restore the instance from JSON data.
3736
+ * @param jsonData JSON data to restore the instance from
3737
+ * @returns
3738
+ */
3739
+ restore(jsonData) {
3740
+ const metadata = this.constructor[Symbol.metadata];
3741
+ for (const fieldIndex in metadata) {
3742
+ const field = metadata[fieldIndex];
3743
+ const fieldName = field.name;
3744
+ const fieldType = field.type;
3745
+ const value = jsonData[fieldName];
3746
+ if (value === undefined || value === null) {
3747
+ continue;
3748
+ }
3749
+ if (typeof fieldType === "string") {
3750
+ // Primitive type: assign directly
3751
+ this[fieldName] = value;
3752
+ }
3753
+ else if (Schema.is(fieldType)) {
3754
+ // Schema type: create instance and restore
3755
+ const instance = new fieldType();
3756
+ instance.restore(value);
3757
+ this[fieldName] = instance;
3758
+ }
3759
+ else if (typeof fieldType === "object") {
3760
+ // Collection types: { map: ... }, { array: ... }, etc.
3761
+ const collectionType = Object.keys(fieldType)[0];
3762
+ const childType = fieldType[collectionType];
3763
+ if (collectionType === "map") {
3764
+ const mapSchema = this[fieldName];
3765
+ for (const key in value) {
3766
+ if (Schema.is(childType)) {
3767
+ const childInstance = new childType();
3768
+ childInstance.restore(value[key]);
3769
+ mapSchema.set(key, childInstance);
3770
+ }
3771
+ else {
3772
+ mapSchema.set(key, value[key]);
3773
+ }
3774
+ }
3775
+ }
3776
+ else if (collectionType === "array") {
3777
+ const arraySchema = this[fieldName];
3778
+ for (let i = 0; i < value.length; i++) {
3779
+ if (Schema.is(childType)) {
3780
+ const childInstance = new childType();
3781
+ childInstance.restore(value[i]);
3782
+ arraySchema.push(childInstance);
3783
+ }
3784
+ else {
3785
+ arraySchema.push(value[i]);
3786
+ }
3787
+ }
3788
+ }
3789
+ }
3790
+ }
3791
+ return this;
3792
+ }
3732
3793
  /**
3733
3794
  * (Server-side): Flag a property to be encoded for the next patch.
3734
3795
  * @param instance Schema instance
@@ -3801,7 +3862,7 @@
3801
3862
  static debugRefIds(ref, showContents = false, level = 0, decoder, keyPrefix = "") {
3802
3863
  const contents = (showContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
3803
3864
  const changeTree = ref[$changes];
3804
- const refId = (decoder) ? decoder.root.refIds.get(ref) : changeTree.refId;
3865
+ const refId = ref[$refId];
3805
3866
  const root = (decoder) ? decoder.root : changeTree.root;
3806
3867
  // log reference count if > 1
3807
3868
  const refCount = (root?.refCount?.[refId] > 1)
@@ -3824,7 +3885,7 @@
3824
3885
  let current = ref[$changes].root[changeSet].next;
3825
3886
  while (current) {
3826
3887
  if (current.changeTree) {
3827
- encodeOrder.push(current.changeTree.refId);
3888
+ encodeOrder.push(current.changeTree.ref[$refId]);
3828
3889
  }
3829
3890
  current = current.next;
3830
3891
  }
@@ -3845,7 +3906,7 @@
3845
3906
  const changeTree = instance[$changes];
3846
3907
  const changeSet = (isEncodeAll) ? changeTree.allChanges : changeTree.changes;
3847
3908
  const changeSetName = (isEncodeAll) ? "allChanges" : "changes";
3848
- let output = `${instance.constructor.name} (${changeTree.refId}) -> .${changeSetName}:\n`;
3909
+ let output = `${instance.constructor.name} (${instance[$refId]}) -> .${changeSetName}:\n`;
3849
3910
  function dumpChangeSet(changeSet) {
3850
3911
  changeSet.operations
3851
3912
  .filter(op => op)
@@ -3859,14 +3920,14 @@
3859
3920
  if (!isEncodeAll &&
3860
3921
  changeTree.filteredChanges &&
3861
3922
  (changeTree.filteredChanges.operations).filter(op => op).length > 0) {
3862
- output += `${instance.constructor.name} (${changeTree.refId}) -> .filteredChanges:\n`;
3923
+ output += `${instance.constructor.name} (${instance[$refId]}) -> .filteredChanges:\n`;
3863
3924
  dumpChangeSet(changeTree.filteredChanges);
3864
3925
  }
3865
3926
  // display filtered changes
3866
3927
  if (isEncodeAll &&
3867
3928
  changeTree.allFilteredChanges &&
3868
3929
  (changeTree.allFilteredChanges.operations).filter(op => op).length > 0) {
3869
- output += `${instance.constructor.name} (${changeTree.refId}) -> .allFilteredChanges:\n`;
3930
+ output += `${instance.constructor.name} (${instance[$refId]}) -> .allFilteredChanges:\n`;
3870
3931
  dumpChangeSet(changeTree.allFilteredChanges);
3871
3932
  }
3872
3933
  return output;
@@ -3901,13 +3962,13 @@
3901
3962
  }
3902
3963
  }
3903
3964
  if (includeChangeTree) {
3904
- instanceRefIds.push(changeTree.refId);
3965
+ instanceRefIds.push(changeTree.ref[$refId]);
3905
3966
  totalOperations += Object.keys(changes).length;
3906
3967
  changeTrees.set(changeTree, parentChangeTrees.reverse());
3907
3968
  }
3908
3969
  }
3909
3970
  output += "---\n";
3910
- output += `root refId: ${rootChangeTree.refId}\n`;
3971
+ output += `root refId: ${rootChangeTree.ref[$refId]}\n`;
3911
3972
  output += `Total instances: ${instanceRefIds.length} (refIds: ${instanceRefIds.join(", ")})\n`;
3912
3973
  output += `Total changes: ${totalOperations}\n`;
3913
3974
  output += "---\n";
@@ -3916,7 +3977,7 @@
3916
3977
  for (const [changeTree, parentChangeTrees] of changeTrees.entries()) {
3917
3978
  parentChangeTrees.forEach((parentChangeTree, level) => {
3918
3979
  if (!visitedParents.has(parentChangeTree)) {
3919
- output += `${getIndent(level)}${parentChangeTree.ref.constructor.name} (refId: ${parentChangeTree.refId})\n`;
3980
+ output += `${getIndent(level)}${parentChangeTree.ref.constructor.name} (refId: ${parentChangeTree.ref[$refId]})\n`;
3920
3981
  visitedParents.add(parentChangeTree);
3921
3982
  }
3922
3983
  });
@@ -3924,7 +3985,7 @@
3924
3985
  const level = parentChangeTrees.length;
3925
3986
  const indent = getIndent(level);
3926
3987
  const parentIndex = (level > 0) ? `(${changeTree.parentIndex}) ` : "";
3927
- output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.refId}) - changes: ${Object.keys(changes).length}\n`;
3988
+ output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.ref[$refId]}) - changes: ${Object.keys(changes).length}\n`;
3928
3989
  for (const index in changes) {
3929
3990
  const operation = changes[index];
3930
3991
  output += `${getIndent(level + 1)}${exports.OPERATION[operation]}: ${index}\n`;
@@ -3934,61 +3995,35 @@
3934
3995
  }
3935
3996
  }
3936
3997
 
3937
- /******************************************************************************
3938
- Copyright (c) Microsoft Corporation.
3939
-
3940
- Permission to use, copy, modify, and/or distribute this software for any
3941
- purpose with or without fee is hereby granted.
3942
-
3943
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
3944
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
3945
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
3946
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
3947
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
3948
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
3949
- PERFORMANCE OF THIS SOFTWARE.
3950
- ***************************************************************************** */
3951
- /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
3952
-
3953
-
3954
- function __decorate(decorators, target, key, desc) {
3955
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3956
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
3957
- 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;
3958
- return c > 3 && r && Object.defineProperty(target, key, r), r;
3959
- }
3960
-
3961
- typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
3962
- var e = new Error(message);
3963
- return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
3964
- };
3965
-
3966
3998
  class Root {
3999
+ types;
4000
+ nextUniqueId = 0;
4001
+ refCount = {};
4002
+ changeTrees = {};
4003
+ // all changes
4004
+ allChanges = createChangeTreeList();
4005
+ allFilteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
4006
+ // pending changes to be encoded
4007
+ changes = createChangeTreeList();
4008
+ filteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
3967
4009
  constructor(types) {
3968
4010
  this.types = types;
3969
- this.nextUniqueId = 0;
3970
- this.refCount = {};
3971
- this.changeTrees = {};
3972
- // all changes
3973
- this.allChanges = createChangeTreeList();
3974
- this.allFilteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
3975
- // pending changes to be encoded
3976
- this.changes = createChangeTreeList();
3977
- this.filteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
3978
4011
  }
3979
4012
  getNextUniqueId() {
3980
4013
  return this.nextUniqueId++;
3981
4014
  }
3982
4015
  add(changeTree) {
3983
- // Assign unique `refId` to changeTree if it doesn't have one yet.
3984
- if (changeTree.refId === undefined) {
3985
- changeTree.refId = this.getNextUniqueId();
4016
+ const ref = changeTree.ref;
4017
+ // Assign unique `refId` to ref if it doesn't have one yet.
4018
+ if (ref[$refId] === undefined) {
4019
+ ref[$refId] = this.getNextUniqueId();
3986
4020
  }
3987
- const isNewChangeTree = (this.changeTrees[changeTree.refId] === undefined);
4021
+ const refId = ref[$refId];
4022
+ const isNewChangeTree = (this.changeTrees[refId] === undefined);
3988
4023
  if (isNewChangeTree) {
3989
- this.changeTrees[changeTree.refId] = changeTree;
4024
+ this.changeTrees[refId] = changeTree;
3990
4025
  }
3991
- const previousRefCount = this.refCount[changeTree.refId];
4026
+ const previousRefCount = this.refCount[refId];
3992
4027
  if (previousRefCount === 0) {
3993
4028
  //
3994
4029
  // When a ChangeTree is re-added, it means that it was previously removed.
@@ -4001,30 +4036,31 @@
4001
4036
  setOperationAtIndex(changeTree.changes, len);
4002
4037
  }
4003
4038
  }
4004
- this.refCount[changeTree.refId] = (previousRefCount || 0) + 1;
4005
- // console.log("ADD", { refId: changeTree.refId, ref: changeTree.ref.constructor.name, refCount: this.refCount[changeTree.refId], isNewChangeTree });
4039
+ this.refCount[refId] = (previousRefCount || 0) + 1;
4040
+ // console.log("ADD", { refId, ref: ref.constructor.name, refCount: this.refCount[refId], isNewChangeTree });
4006
4041
  return isNewChangeTree;
4007
4042
  }
4008
4043
  remove(changeTree) {
4009
- const refCount = (this.refCount[changeTree.refId]) - 1;
4010
- // console.log("REMOVE", { refId: changeTree.refId, ref: changeTree.ref.constructor.name, refCount, needRemove: refCount <= 0 });
4044
+ const refId = changeTree.ref[$refId];
4045
+ const refCount = (this.refCount[refId]) - 1;
4046
+ // console.log("REMOVE", { refId, ref: changeTree.ref.constructor.name, refCount, needRemove: refCount <= 0 });
4011
4047
  if (refCount <= 0) {
4012
4048
  //
4013
4049
  // Only remove "root" reference if it's the last reference
4014
4050
  //
4015
4051
  changeTree.root = undefined;
4016
- delete this.changeTrees[changeTree.refId];
4052
+ delete this.changeTrees[refId];
4017
4053
  this.removeChangeFromChangeSet("allChanges", changeTree);
4018
4054
  this.removeChangeFromChangeSet("changes", changeTree);
4019
4055
  if (changeTree.filteredChanges) {
4020
4056
  this.removeChangeFromChangeSet("allFilteredChanges", changeTree);
4021
4057
  this.removeChangeFromChangeSet("filteredChanges", changeTree);
4022
4058
  }
4023
- this.refCount[changeTree.refId] = 0;
4059
+ this.refCount[refId] = 0;
4024
4060
  changeTree.forEachChild((child, _) => {
4025
4061
  if (child.removeParent(changeTree.ref)) {
4026
4062
  if ((child.parentChain === undefined || // no parent, remove it
4027
- (child.parentChain && this.refCount[child.refId] > 0) // parent is still in use, but has more than one reference, remove it
4063
+ (child.parentChain && this.refCount[child.ref[$refId]] > 0) // parent is still in use, but has more than one reference, remove it
4028
4064
  )) {
4029
4065
  this.remove(child);
4030
4066
  }
@@ -4036,7 +4072,7 @@
4036
4072
  });
4037
4073
  }
4038
4074
  else {
4039
- this.refCount[changeTree.refId] = refCount;
4075
+ this.refCount[refId] = refCount;
4040
4076
  //
4041
4077
  // When losing a reference to an instance, it is best to move the
4042
4078
  // ChangeTree next to its parent in the encoding queue.
@@ -4186,10 +4222,19 @@
4186
4222
  }
4187
4223
  }
4188
4224
 
4225
+ function concatBytes(a, b) {
4226
+ const result = new Uint8Array(a.length + b.length);
4227
+ result.set(a, 0);
4228
+ result.set(b, a.length);
4229
+ return result;
4230
+ }
4189
4231
  class Encoder {
4190
- static { this.BUFFER_SIZE = (typeof (Buffer) !== "undefined") && Buffer.poolSize || 8 * 1024; } // 8KB
4232
+ static BUFFER_SIZE = 8 * 1024; // 8KB
4233
+ sharedBuffer = new Uint8Array(Encoder.BUFFER_SIZE);
4234
+ context;
4235
+ state;
4236
+ root;
4191
4237
  constructor(state) {
4192
- this.sharedBuffer = Buffer.allocUnsafe(Encoder.BUFFER_SIZE);
4193
4238
  //
4194
4239
  // Use .cache() here to avoid re-creating a new context for every new room instance.
4195
4240
  //
@@ -4217,7 +4262,7 @@
4217
4262
  const changeTree = current.changeTree;
4218
4263
  if (hasView) {
4219
4264
  if (!view.isChangeTreeVisible(changeTree)) {
4220
- // console.log("MARK AS INVISIBLE:", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, raw: changeTree.ref.toJSON() });
4265
+ // console.log("MARK AS INVISIBLE:", { ref: changeTree.ref.constructor.name, refId: changeTree.ref[$refId], raw: changeTree.ref.toJSON() });
4221
4266
  view.invisible.add(changeTree);
4222
4267
  continue; // skip this change tree
4223
4268
  }
@@ -4238,7 +4283,7 @@
4238
4283
  // (unless it "hasView", which will need to revisit the root)
4239
4284
  if (hasView || it.offset > initialOffset || changeTree !== rootChangeTree) {
4240
4285
  buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
4241
- encode.number(buffer, changeTree.refId, it);
4286
+ encode.number(buffer, ref[$refId], it);
4242
4287
  }
4243
4288
  for (let j = 0; j < numChanges; j++) {
4244
4289
  const fieldIndex = changeSet.operations[j];
@@ -4267,10 +4312,9 @@
4267
4312
  }
4268
4313
  }
4269
4314
  if (it.offset > buffer.byteLength) {
4270
- // we can assume that n + 1 poolSize will suffice given that we are likely done with encoding at this point
4271
- // multiples of poolSize are faster to allocate than arbitrary sizes
4272
- // if we are on an older platform that doesn't implement pooling use 8kb as poolSize (that's the default for node)
4273
- const newSize = Math.ceil(it.offset / (Buffer.poolSize ?? 8 * 1024)) * (Buffer.poolSize ?? 8 * 1024);
4315
+ // we can assume that n + 1 BUFFER_SIZE will suffice given that we are likely done with encoding at this point
4316
+ // multiples of BUFFER_SIZE are faster to allocate than arbitrary sizes
4317
+ const newSize = Math.ceil(it.offset / Encoder.BUFFER_SIZE) * Encoder.BUFFER_SIZE;
4274
4318
  console.warn(`@colyseus/schema buffer overflow. Encoded state is higher than default BUFFER_SIZE. Use the following to increase default BUFFER_SIZE:
4275
4319
 
4276
4320
  import { Encoder } from "@colyseus/schema";
@@ -4280,7 +4324,9 @@
4280
4324
  // resize buffer and re-encode (TODO: can we avoid re-encoding here?)
4281
4325
  // -> No we probably can't unless we catch the need for resize before encoding which is likely more computationally expensive than resizing on demand
4282
4326
  //
4283
- buffer = Buffer.alloc(newSize, buffer); // fill with buffer here to memcpy previous encoding steps beyond the initialOffset
4327
+ const newBuffer = new Uint8Array(newSize);
4328
+ newBuffer.set(buffer); // copy previous encoding steps beyond the initialOffset
4329
+ buffer = newBuffer;
4284
4330
  // assign resized buffer to local sharedBuffer
4285
4331
  if (buffer === this.sharedBuffer) {
4286
4332
  this.sharedBuffer = buffer;
@@ -4298,10 +4344,7 @@
4298
4344
  const viewOffset = it.offset;
4299
4345
  // try to encode "filtered" changes
4300
4346
  this.encode(it, view, bytes, "allFilteredChanges", true, viewOffset);
4301
- return Buffer.concat([
4302
- bytes.subarray(0, sharedOffset),
4303
- bytes.subarray(viewOffset, it.offset)
4304
- ]);
4347
+ return concatBytes(bytes.subarray(0, sharedOffset), bytes.subarray(viewOffset, it.offset));
4305
4348
  }
4306
4349
  encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
4307
4350
  const viewOffset = it.offset;
@@ -4325,7 +4368,7 @@
4325
4368
  const encoder = ctor[$encoder];
4326
4369
  const metadata = ctor[Symbol.metadata];
4327
4370
  bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
4328
- encode.number(bytes, changeTree.refId, it);
4371
+ encode.number(bytes, ref[$refId], it);
4329
4372
  for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
4330
4373
  const index = Number(keys[i]);
4331
4374
  // 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")
@@ -4344,10 +4387,7 @@
4344
4387
  view.changes.clear();
4345
4388
  // try to encode "filtered" changes
4346
4389
  this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
4347
- return Buffer.concat([
4348
- bytes.subarray(0, sharedOffset),
4349
- bytes.subarray(viewOffset, it.offset)
4350
- ]);
4390
+ return concatBytes(bytes.subarray(0, sharedOffset), bytes.subarray(viewOffset, it.offset));
4351
4391
  }
4352
4392
  discardChanges() {
4353
4393
  // discard shared changes
@@ -4403,25 +4443,22 @@
4403
4443
  }
4404
4444
  }
4405
4445
  class ReferenceTracker {
4406
- constructor() {
4407
- //
4408
- // Relation of refId => Schema structure
4409
- // For direct access of structures during decoding time.
4410
- //
4411
- this.refs = new Map();
4412
- this.refIds = new WeakMap();
4413
- this.refCount = {};
4414
- this.deletedRefs = new Set();
4415
- this.callbacks = {};
4416
- this.nextUniqueId = 0;
4417
- }
4446
+ //
4447
+ // Relation of refId => Schema structure
4448
+ // For direct access of structures during decoding time.
4449
+ //
4450
+ refs = new Map();
4451
+ refCount = {};
4452
+ deletedRefs = new Set();
4453
+ callbacks = {};
4454
+ nextUniqueId = 0;
4418
4455
  getNextUniqueId() {
4419
4456
  return this.nextUniqueId++;
4420
4457
  }
4421
4458
  // for decoding
4422
4459
  addRef(refId, ref, incrementCount = true) {
4423
4460
  this.refs.set(refId, ref);
4424
- this.refIds.set(ref, refId);
4461
+ ref[$refId] = refId;
4425
4462
  if (incrementCount) {
4426
4463
  this.refCount[refId] = (this.refCount[refId] || 0) + 1;
4427
4464
  }
@@ -4478,9 +4515,12 @@
4478
4515
  const metadata = ref.constructor[Symbol.metadata];
4479
4516
  for (const index in metadata) {
4480
4517
  const field = metadata[index].name;
4481
- const childRefId = typeof (ref[field]) === "object" && this.refIds.get(ref[field]);
4482
- if (childRefId && !this.deletedRefs.has(childRefId)) {
4483
- this.removeRef(childRefId);
4518
+ const child = ref[field];
4519
+ if (typeof (child) === "object" && child) {
4520
+ const childRefId = child[$refId];
4521
+ if (childRefId !== undefined && !this.deletedRefs.has(childRefId)) {
4522
+ this.removeRef(childRefId);
4523
+ }
4484
4524
  }
4485
4525
  }
4486
4526
  }
@@ -4488,8 +4528,8 @@
4488
4528
  if (typeof (ref[$childType]) === "function") {
4489
4529
  Array.from(ref.values())
4490
4530
  .forEach((child) => {
4491
- const childRefId = this.refIds.get(child);
4492
- if (!this.deletedRefs.has(childRefId)) {
4531
+ const childRefId = child[$refId];
4532
+ if (childRefId !== undefined && !this.deletedRefs.has(childRefId)) {
4493
4533
  this.removeRef(childRefId);
4494
4534
  }
4495
4535
  });
@@ -4527,8 +4567,12 @@
4527
4567
  }
4528
4568
 
4529
4569
  class Decoder {
4570
+ context;
4571
+ state;
4572
+ root;
4573
+ currentRefId = 0;
4574
+ triggerChanges;
4530
4575
  constructor(root, context) {
4531
- this.currentRefId = 0;
4532
4576
  this.setState(root);
4533
4577
  this.context = context || new TypeContext(root.constructor);
4534
4578
  // console.log(">>>>>>>>>>>>>>>> Decoder types");
@@ -4617,7 +4661,7 @@
4617
4661
  }
4618
4662
  removeChildRefs(ref, allChanges) {
4619
4663
  const needRemoveRef = typeof (ref[$childType]) !== "string";
4620
- const refId = this.root.refIds.get(ref);
4664
+ const refId = ref[$refId];
4621
4665
  ref.forEach((value, key) => {
4622
4666
  allChanges.push({
4623
4667
  ref: ref,
@@ -4628,7 +4672,7 @@
4628
4672
  previousValue: value
4629
4673
  });
4630
4674
  if (needRemoveRef) {
4631
- this.root.removeRef(this.root.refIds.get(value));
4675
+ this.root.removeRef(value[$refId]);
4632
4676
  }
4633
4677
  });
4634
4678
  }
@@ -4637,217 +4681,182 @@
4637
4681
  /**
4638
4682
  * Reflection
4639
4683
  */
4640
- class ReflectionField extends Schema {
4641
- }
4642
- __decorate([
4643
- type("string")
4644
- ], ReflectionField.prototype, "name", void 0);
4645
- __decorate([
4646
- type("string")
4647
- ], ReflectionField.prototype, "type", void 0);
4648
- __decorate([
4649
- type("number")
4650
- ], ReflectionField.prototype, "referencedType", void 0);
4651
- class ReflectionType extends Schema {
4652
- constructor() {
4653
- super(...arguments);
4654
- this.fields = new ArraySchema();
4655
- }
4656
- }
4657
- __decorate([
4658
- type("number")
4659
- ], ReflectionType.prototype, "id", void 0);
4660
- __decorate([
4661
- type("number")
4662
- ], ReflectionType.prototype, "extendsId", void 0);
4663
- __decorate([
4664
- type([ReflectionField])
4665
- ], ReflectionType.prototype, "fields", void 0);
4666
- class Reflection extends Schema {
4667
- constructor() {
4668
- super(...arguments);
4669
- this.types = new ArraySchema();
4670
- }
4671
- /**
4672
- * Encodes the TypeContext of an Encoder into a buffer.
4673
- *
4674
- * @param encoder Encoder instance
4675
- * @param it
4676
- * @returns
4677
- */
4678
- static encode(encoder, it = { offset: 0 }) {
4679
- const context = encoder.context;
4680
- const reflection = new Reflection();
4681
- const reflectionEncoder = new Encoder(reflection);
4682
- // rootType is usually the first schema passed to the Encoder
4683
- // (unless it inherits from another schema)
4684
- const rootType = context.schemas.get(encoder.state.constructor);
4685
- if (rootType > 0) {
4686
- reflection.rootType = rootType;
4687
- }
4688
- const includedTypeIds = new Set();
4689
- const pendingReflectionTypes = {};
4690
- // add type to reflection in a way that respects inheritance
4691
- // (parent types should be added before their children)
4692
- const addType = (type) => {
4693
- if (type.extendsId === undefined || includedTypeIds.has(type.extendsId)) {
4694
- includedTypeIds.add(type.id);
4695
- reflection.types.push(type);
4696
- const deps = pendingReflectionTypes[type.id];
4697
- if (deps !== undefined) {
4698
- delete pendingReflectionTypes[type.id];
4699
- deps.forEach((childType) => addType(childType));
4700
- }
4701
- }
4702
- else {
4703
- if (pendingReflectionTypes[type.extendsId] === undefined) {
4704
- pendingReflectionTypes[type.extendsId] = [];
4705
- }
4706
- pendingReflectionTypes[type.extendsId].push(type);
4684
+ const ReflectionField = schema({
4685
+ name: "string",
4686
+ type: "string",
4687
+ referencedType: "number",
4688
+ });
4689
+ const ReflectionType = schema({
4690
+ id: "number",
4691
+ extendsId: "number",
4692
+ fields: [ReflectionField],
4693
+ });
4694
+ const Reflection = schema({
4695
+ types: [ReflectionType],
4696
+ rootType: "number",
4697
+ });
4698
+ Reflection.encode = function (encoder, it = { offset: 0 }) {
4699
+ const context = encoder.context;
4700
+ const reflection = new Reflection();
4701
+ const reflectionEncoder = new Encoder(reflection);
4702
+ // rootType is usually the first schema passed to the Encoder
4703
+ // (unless it inherits from another schema)
4704
+ const rootType = context.schemas.get(encoder.state.constructor);
4705
+ if (rootType > 0) {
4706
+ reflection.rootType = rootType;
4707
+ }
4708
+ const includedTypeIds = new Set();
4709
+ const pendingReflectionTypes = {};
4710
+ // add type to reflection in a way that respects inheritance
4711
+ // (parent types should be added before their children)
4712
+ const addType = (type) => {
4713
+ if (type.extendsId === undefined || includedTypeIds.has(type.extendsId)) {
4714
+ includedTypeIds.add(type.id);
4715
+ reflection.types.push(type);
4716
+ const deps = pendingReflectionTypes[type.id];
4717
+ if (deps !== undefined) {
4718
+ delete pendingReflectionTypes[type.id];
4719
+ deps.forEach((childType) => addType(childType));
4707
4720
  }
4708
- };
4709
- context.schemas.forEach((typeid, klass) => {
4710
- const type = new ReflectionType();
4711
- type.id = Number(typeid);
4712
- // support inheritance
4713
- const inheritFrom = Object.getPrototypeOf(klass);
4714
- if (inheritFrom !== Schema) {
4715
- type.extendsId = context.schemas.get(inheritFrom);
4721
+ }
4722
+ else {
4723
+ if (pendingReflectionTypes[type.extendsId] === undefined) {
4724
+ pendingReflectionTypes[type.extendsId] = [];
4716
4725
  }
4717
- const metadata = klass[Symbol.metadata];
4718
- //
4719
- // FIXME: this is a workaround for inherited types without additional fields
4720
- // if metadata is the same reference as the parent class - it means the class has no own metadata
4721
- //
4722
- if (metadata !== inheritFrom[Symbol.metadata]) {
4723
- for (const fieldIndex in metadata) {
4724
- const index = Number(fieldIndex);
4725
- const fieldName = metadata[index].name;
4726
- // skip fields from parent classes
4727
- if (!Object.prototype.hasOwnProperty.call(metadata, fieldName)) {
4728
- continue;
4729
- }
4730
- const reflectionField = new ReflectionField();
4731
- reflectionField.name = fieldName;
4732
- let fieldType;
4733
- const field = metadata[index];
4734
- if (typeof (field.type) === "string") {
4735
- fieldType = field.type;
4726
+ pendingReflectionTypes[type.extendsId].push(type);
4727
+ }
4728
+ };
4729
+ context.schemas.forEach((typeid, klass) => {
4730
+ const type = new ReflectionType();
4731
+ type.id = Number(typeid);
4732
+ // support inheritance
4733
+ const inheritFrom = Object.getPrototypeOf(klass);
4734
+ if (inheritFrom !== Schema) {
4735
+ type.extendsId = context.schemas.get(inheritFrom);
4736
+ }
4737
+ const metadata = klass[Symbol.metadata];
4738
+ //
4739
+ // FIXME: this is a workaround for inherited types without additional fields
4740
+ // if metadata is the same reference as the parent class - it means the class has no own metadata
4741
+ //
4742
+ if (metadata !== inheritFrom[Symbol.metadata]) {
4743
+ for (const fieldIndex in metadata) {
4744
+ const index = Number(fieldIndex);
4745
+ const fieldName = metadata[index].name;
4746
+ // skip fields from parent classes
4747
+ if (!Object.prototype.hasOwnProperty.call(metadata, fieldName)) {
4748
+ continue;
4749
+ }
4750
+ const reflectionField = new ReflectionField();
4751
+ reflectionField.name = fieldName;
4752
+ let fieldType;
4753
+ const field = metadata[index];
4754
+ if (typeof (field.type) === "string") {
4755
+ fieldType = field.type;
4756
+ }
4757
+ else {
4758
+ let childTypeSchema;
4759
+ //
4760
+ // TODO: refactor below.
4761
+ //
4762
+ if (Schema.is(field.type)) {
4763
+ fieldType = "ref";
4764
+ childTypeSchema = field.type;
4736
4765
  }
4737
4766
  else {
4738
- let childTypeSchema;
4739
- //
4740
- // TODO: refactor below.
4741
- //
4742
- if (Schema.is(field.type)) {
4743
- fieldType = "ref";
4744
- childTypeSchema = field.type;
4767
+ fieldType = Object.keys(field.type)[0];
4768
+ if (typeof (field.type[fieldType]) === "string") {
4769
+ fieldType += ":" + field.type[fieldType]; // array:string
4745
4770
  }
4746
4771
  else {
4747
- fieldType = Object.keys(field.type)[0];
4748
- if (typeof (field.type[fieldType]) === "string") {
4749
- fieldType += ":" + field.type[fieldType]; // array:string
4750
- }
4751
- else {
4752
- childTypeSchema = field.type[fieldType];
4753
- }
4772
+ childTypeSchema = field.type[fieldType];
4754
4773
  }
4755
- reflectionField.referencedType = (childTypeSchema)
4756
- ? context.getTypeId(childTypeSchema)
4757
- : -1;
4758
4774
  }
4759
- reflectionField.type = fieldType;
4760
- type.fields.push(reflectionField);
4775
+ reflectionField.referencedType = (childTypeSchema)
4776
+ ? context.getTypeId(childTypeSchema)
4777
+ : -1;
4761
4778
  }
4779
+ reflectionField.type = fieldType;
4780
+ type.fields.push(reflectionField);
4762
4781
  }
4763
- addType(type);
4764
- });
4765
- // in case there are types that were not added due to inheritance
4766
- for (const typeid in pendingReflectionTypes) {
4767
- pendingReflectionTypes[typeid].forEach((type) => reflection.types.push(type));
4768
4782
  }
4769
- const buf = reflectionEncoder.encodeAll(it);
4770
- return buf.slice(0, it.offset);
4771
- // return Buffer.from(buf, 0, it.offset);
4783
+ addType(type);
4784
+ });
4785
+ // in case there are types that were not added due to inheritance
4786
+ for (const typeid in pendingReflectionTypes) {
4787
+ pendingReflectionTypes[typeid].forEach((type) => reflection.types.push(type));
4772
4788
  }
4773
- /**
4774
- * Decodes the TypeContext from a buffer into a Decoder instance.
4775
- *
4776
- * @param bytes Reflection.encode() output
4777
- * @param it
4778
- * @returns Decoder instance
4779
- */
4780
- static decode(bytes, it) {
4781
- const reflection = new Reflection();
4782
- const reflectionDecoder = new Decoder(reflection);
4783
- reflectionDecoder.decode(bytes, it);
4784
- const typeContext = new TypeContext();
4785
- // 1st pass, initialize metadata + inheritance
4786
- reflection.types.forEach((reflectionType) => {
4787
- const parentClass = typeContext.get(reflectionType.extendsId) ?? Schema;
4788
- const schema = class _ extends parentClass {
4789
- };
4790
- // register for inheritance support
4791
- TypeContext.register(schema);
4792
- // // for inheritance support
4793
- // Metadata.initialize(schema);
4794
- typeContext.add(schema, reflectionType.id);
4795
- }, {});
4796
- // define fields
4797
- const addFields = (metadata, reflectionType, parentFieldIndex) => {
4798
- reflectionType.fields.forEach((field, i) => {
4799
- const fieldIndex = parentFieldIndex + i;
4800
- if (field.referencedType !== undefined) {
4801
- let fieldType = field.type;
4802
- let refType = typeContext.get(field.referencedType);
4803
- // map or array of primitive type (-1)
4804
- if (!refType) {
4805
- const typeInfo = field.type.split(":");
4806
- fieldType = typeInfo[0];
4807
- refType = typeInfo[1]; // string
4808
- }
4809
- if (fieldType === "ref") {
4810
- Metadata.addField(metadata, fieldIndex, field.name, refType);
4811
- }
4812
- else {
4813
- Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType });
4814
- }
4789
+ const buf = reflectionEncoder.encodeAll(it);
4790
+ return buf.slice(0, it.offset);
4791
+ };
4792
+ Reflection.decode = function (bytes, it) {
4793
+ const reflection = new Reflection();
4794
+ const reflectionDecoder = new Decoder(reflection);
4795
+ reflectionDecoder.decode(bytes, it);
4796
+ const typeContext = new TypeContext();
4797
+ // 1st pass, initialize metadata + inheritance
4798
+ reflection.types.forEach((reflectionType) => {
4799
+ const parentClass = typeContext.get(reflectionType.extendsId) ?? Schema;
4800
+ const schema = class _ extends parentClass {
4801
+ };
4802
+ // register for inheritance support
4803
+ TypeContext.register(schema);
4804
+ typeContext.add(schema, reflectionType.id);
4805
+ }, {});
4806
+ // define fields
4807
+ const addFields = (metadata, reflectionType, parentFieldIndex) => {
4808
+ reflectionType.fields.forEach((field, i) => {
4809
+ const fieldIndex = parentFieldIndex + i;
4810
+ if (field.referencedType !== undefined) {
4811
+ let fieldType = field.type;
4812
+ let refType = typeContext.get(field.referencedType);
4813
+ // map or array of primitive type (-1)
4814
+ if (!refType) {
4815
+ const typeInfo = field.type.split(":");
4816
+ fieldType = typeInfo[0];
4817
+ refType = typeInfo[1]; // string
4818
+ }
4819
+ if (fieldType === "ref") {
4820
+ Metadata.addField(metadata, fieldIndex, field.name, refType);
4815
4821
  }
4816
4822
  else {
4817
- Metadata.addField(metadata, fieldIndex, field.name, field.type);
4823
+ Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType });
4818
4824
  }
4819
- });
4820
- };
4821
- // 2nd pass, set fields
4822
- reflection.types.forEach((reflectionType) => {
4823
- const schema = typeContext.get(reflectionType.id);
4824
- // for inheritance support
4825
- const metadata = Metadata.initialize(schema);
4826
- const inheritedTypes = [];
4827
- let parentType = reflectionType;
4828
- do {
4829
- inheritedTypes.push(parentType);
4830
- parentType = reflection.types.find((t) => t.id === parentType.extendsId);
4831
- } while (parentType);
4832
- let parentFieldIndex = 0;
4833
- inheritedTypes.reverse().forEach((reflectionType) => {
4834
- // add fields from all inherited classes
4835
- // TODO: refactor this to avoid adding fields from parent classes
4836
- addFields(metadata, reflectionType, parentFieldIndex);
4837
- parentFieldIndex += reflectionType.fields.length;
4838
- });
4825
+ }
4826
+ else {
4827
+ Metadata.addField(metadata, fieldIndex, field.name, field.type);
4828
+ }
4839
4829
  });
4840
- const state = new (typeContext.get(reflection.rootType || 0))();
4841
- return new Decoder(state, typeContext);
4842
- }
4843
- }
4844
- __decorate([
4845
- type([ReflectionType])
4846
- ], Reflection.prototype, "types", void 0);
4847
- __decorate([
4848
- type("number")
4849
- ], Reflection.prototype, "rootType", void 0);
4830
+ };
4831
+ // 2nd pass, set fields
4832
+ reflection.types.forEach((reflectionType) => {
4833
+ const schema = typeContext.get(reflectionType.id);
4834
+ // for inheritance support
4835
+ const metadata = Metadata.initialize(schema);
4836
+ const inheritedTypes = [];
4837
+ let parentType = reflectionType;
4838
+ do {
4839
+ inheritedTypes.push(parentType);
4840
+ parentType = reflection.types.find((t) => t.id === parentType.extendsId);
4841
+ } while (parentType);
4842
+ let parentFieldIndex = 0;
4843
+ inheritedTypes.reverse().forEach((reflectionType) => {
4844
+ // add fields from all inherited classes
4845
+ // TODO: refactor this to avoid adding fields from parent classes
4846
+ addFields(metadata, reflectionType, parentFieldIndex);
4847
+ parentFieldIndex += reflectionType.fields.length;
4848
+ });
4849
+ });
4850
+ const state = new (typeContext.get(reflection.rootType || 0))();
4851
+ return new Decoder(state, typeContext);
4852
+ };
4850
4853
 
4854
+ /**
4855
+ * Legacy callback system
4856
+ *
4857
+ * @param decoder
4858
+ * @returns
4859
+ */
4851
4860
  function getDecoderStateCallbacks(decoder) {
4852
4861
  const $root = decoder.root;
4853
4862
  const callbacks = $root.callbacks;
@@ -4868,7 +4877,7 @@
4868
4877
  //
4869
4878
  if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE &&
4870
4879
  change.previousValue instanceof Schema) {
4871
- const deleteCallbacks = callbacks[$root.refIds.get(change.previousValue)]?.[exports.OPERATION.DELETE];
4880
+ const deleteCallbacks = callbacks[change.previousValue[$refId]]?.[exports.OPERATION.DELETE];
4872
4881
  for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
4873
4882
  deleteCallbacks[i]();
4874
4883
  }
@@ -4957,7 +4966,7 @@
4957
4966
  ) {
4958
4967
  callback(context.instance[prop], undefined);
4959
4968
  }
4960
- return $root.addCallback($root.refIds.get(ref), prop, callback);
4969
+ return $root.addCallback(ref[$refId], prop, callback);
4961
4970
  };
4962
4971
  /**
4963
4972
  * Schema instances
@@ -4977,7 +4986,7 @@
4977
4986
  }
4978
4987
  },
4979
4988
  onChange: function onChange(callback) {
4980
- return $root.addCallback($root.refIds.get(context.instance), exports.OPERATION.REPLACE, callback);
4989
+ return $root.addCallback(context.instance[$refId], exports.OPERATION.REPLACE, callback);
4981
4990
  },
4982
4991
  //
4983
4992
  // TODO: refactor `bindTo()` implementation.
@@ -4987,7 +4996,7 @@
4987
4996
  if (!properties) {
4988
4997
  properties = Object.keys(metadata).map((index) => metadata[index].name);
4989
4998
  }
4990
- return $root.addCallback($root.refIds.get(context.instance), exports.OPERATION.REPLACE, () => {
4999
+ return $root.addCallback(context.instance[$refId], exports.OPERATION.REPLACE, () => {
4991
5000
  properties.forEach((prop) => targetObject[prop] = context.instance[prop]);
4992
5001
  });
4993
5002
  }
@@ -5006,13 +5015,13 @@
5006
5015
  unbind?.();
5007
5016
  }, false);
5008
5017
  // has existing value
5009
- if ($root.refIds.get(instance) !== undefined) {
5018
+ if (instance?.[$refId] !== undefined) {
5010
5019
  callback(instance, true);
5011
5020
  }
5012
5021
  });
5013
5022
  return getProxy(metadataField.type, {
5014
5023
  // make sure refId is available, otherwise need to wait for the instance to be available.
5015
- instance: ($root.refIds.get(instance) && instance),
5024
+ instance: (instance?.[$refId] !== undefined && instance),
5016
5025
  parentInstance: context.instance,
5017
5026
  onInstanceAvailable,
5018
5027
  });
@@ -5036,7 +5045,7 @@
5036
5045
  if (immediate) {
5037
5046
  ref.forEach((v, k) => callback(v, k));
5038
5047
  }
5039
- return $root.addCallback($root.refIds.get(ref), exports.OPERATION.ADD, (value, key) => {
5048
+ return $root.addCallback(ref[$refId], exports.OPERATION.ADD, (value, key) => {
5040
5049
  onAddCalls.set(callback, true);
5041
5050
  currentOnAddCallback = callback;
5042
5051
  callback(value, key);
@@ -5045,10 +5054,10 @@
5045
5054
  });
5046
5055
  };
5047
5056
  const onRemove = function (ref, callback) {
5048
- return $root.addCallback($root.refIds.get(ref), exports.OPERATION.DELETE, callback);
5057
+ return $root.addCallback(ref[$refId], exports.OPERATION.DELETE, callback);
5049
5058
  };
5050
5059
  const onChange = function (ref, callback) {
5051
- return $root.addCallback($root.refIds.get(ref), exports.OPERATION.REPLACE, callback);
5060
+ return $root.addCallback(ref[$refId], exports.OPERATION.REPLACE, callback);
5052
5061
  };
5053
5062
  return new Proxy({
5054
5063
  onAdd: function (callback, immediate = true) {
@@ -5117,22 +5126,360 @@
5117
5126
  decoder.triggerChanges = callback;
5118
5127
  }
5119
5128
 
5129
+ /**
5130
+ * State Callbacks handler
5131
+ *
5132
+ * Usage:
5133
+ * ```ts
5134
+ * const $ = Callbacks.get(decoder);
5135
+ *
5136
+ * // Listen to property changes
5137
+ * $.listen("currentTurn", (currentValue, previousValue) => { ... });
5138
+ *
5139
+ * // Listen to collection additions
5140
+ * $.onAdd("entities", (sessionId, entity) => {
5141
+ * // Nested property listening
5142
+ * $.listen(entity, "hp", (currentHp, previousHp) => { ... });
5143
+ * });
5144
+ *
5145
+ * // Listen to collection removals
5146
+ * $.onRemove("entities", (sessionId, entity) => { ... });
5147
+ *
5148
+ * // Listen to any property change on an instance
5149
+ * $.onChange(entity, () => { ... });
5150
+ *
5151
+ * // Bind properties to another object
5152
+ * $.bindTo(player, playerVisual);
5153
+ * ```
5154
+ */
5155
+ class StateCallbackStrategy {
5156
+ decoder;
5157
+ uniqueRefIds = new Set();
5158
+ isTriggering = false;
5159
+ constructor(decoder) {
5160
+ this.decoder = decoder;
5161
+ this.decoder.triggerChanges = this.triggerChanges.bind(this);
5162
+ }
5163
+ get callbacks() {
5164
+ return this.decoder.root.callbacks;
5165
+ }
5166
+ get state() {
5167
+ return this.decoder.state;
5168
+ }
5169
+ addCallback(refId, operationOrProperty, handler) {
5170
+ const $root = this.decoder.root;
5171
+ return $root.addCallback(refId, operationOrProperty, handler);
5172
+ }
5173
+ addCallbackOrWaitCollectionAvailable(instance, propertyName, operation, handler, immediate = true) {
5174
+ let removeHandler = () => { };
5175
+ const removeOnAdd = () => removeHandler();
5176
+ const collection = instance[propertyName];
5177
+ // Collection not available yet. Listen for its availability before attaching the handler.
5178
+ if (collection === null || collection === undefined) {
5179
+ removeHandler = this.addCallback(instance[$refId], propertyName, (value, _) => {
5180
+ if (value !== null && value !== undefined) {
5181
+ removeHandler = this.addCallback(value[$refId], operation, handler);
5182
+ }
5183
+ });
5184
+ return removeOnAdd;
5185
+ }
5186
+ else {
5187
+ //
5188
+ // Call immediately if collection is already available, if it's an ADD operation.
5189
+ //
5190
+ immediate = immediate && this.isTriggering === false;
5191
+ if (operation === exports.OPERATION.ADD && immediate) {
5192
+ collection.forEach((value, key) => {
5193
+ handler(key, value);
5194
+ });
5195
+ }
5196
+ return this.addCallback(collection[$refId], operation, handler);
5197
+ }
5198
+ }
5199
+ listen(...args) {
5200
+ if (typeof args[0] === 'string') {
5201
+ // listen(property, handler, immediate?)
5202
+ return this.listenInstance(this.state, args[0], args[1], args[2]);
5203
+ }
5204
+ else {
5205
+ // listen(instance, property, handler, immediate?)
5206
+ return this.listenInstance(args[0], args[1], args[2], args[3]);
5207
+ }
5208
+ }
5209
+ listenInstance(instance, propertyName, handler, immediate = true) {
5210
+ immediate = immediate && this.isTriggering === false;
5211
+ //
5212
+ // Call handler immediately if property is already available.
5213
+ //
5214
+ const currentValue = instance[propertyName];
5215
+ if (immediate && currentValue !== null && currentValue !== undefined) {
5216
+ handler(currentValue, undefined);
5217
+ }
5218
+ return this.addCallback(instance[$refId], propertyName, handler);
5219
+ }
5220
+ onChange(...args) {
5221
+ if (args.length === 2 && typeof args[0] !== 'string') {
5222
+ // onChange(instance, handler) - instance change
5223
+ const instance = args[0];
5224
+ const handler = args[1];
5225
+ return this.addCallback(instance[$refId], exports.OPERATION.REPLACE, handler);
5226
+ }
5227
+ if (typeof args[0] === 'string') {
5228
+ // onChange(property, handler) - collection on root state
5229
+ return this.addCallbackOrWaitCollectionAvailable(this.state, args[0], exports.OPERATION.REPLACE, args[1]);
5230
+ }
5231
+ else {
5232
+ // onChange(instance, property, handler) - nested collection
5233
+ return this.addCallbackOrWaitCollectionAvailable(args[0], args[1], exports.OPERATION.REPLACE, args[2]);
5234
+ }
5235
+ }
5236
+ onAdd(...args) {
5237
+ if (typeof args[0] === 'string') {
5238
+ // onAdd(property, handler, immediate?) - collection on root state
5239
+ return this.addCallbackOrWaitCollectionAvailable(this.state, args[0], exports.OPERATION.ADD, args[1], args[2] !== false);
5240
+ }
5241
+ else {
5242
+ // onAdd(instance, property, handler, immediate?) - nested collection
5243
+ return this.addCallbackOrWaitCollectionAvailable(args[0], args[1], exports.OPERATION.ADD, args[2], args[3] !== false);
5244
+ }
5245
+ }
5246
+ onRemove(...args) {
5247
+ if (typeof args[0] === 'string') {
5248
+ // onRemove(property, handler) - collection on root state
5249
+ return this.addCallbackOrWaitCollectionAvailable(this.state, args[0], exports.OPERATION.DELETE, args[1]);
5250
+ }
5251
+ else {
5252
+ // onRemove(instance, property, handler) - nested collection
5253
+ return this.addCallbackOrWaitCollectionAvailable(args[0], args[1], exports.OPERATION.DELETE, args[2]);
5254
+ }
5255
+ }
5256
+ /**
5257
+ * Bind properties from a Schema instance to a target object.
5258
+ * Changes will be automatically reflected on the target object.
5259
+ */
5260
+ bindTo(from, to, properties, immediate = true) {
5261
+ const metadata = from.constructor[Symbol.metadata];
5262
+ // If no properties specified, bind all properties
5263
+ if (!properties) {
5264
+ properties = Object.keys(metadata)
5265
+ .filter(key => !isNaN(Number(key)))
5266
+ .map((index) => metadata[index].name);
5267
+ }
5268
+ const action = () => {
5269
+ for (const prop of properties) {
5270
+ const fromValue = from[prop];
5271
+ if (fromValue !== undefined) {
5272
+ to[prop] = fromValue;
5273
+ }
5274
+ }
5275
+ };
5276
+ if (immediate) {
5277
+ action();
5278
+ }
5279
+ return this.addCallback(from[$refId], exports.OPERATION.REPLACE, action);
5280
+ }
5281
+ triggerChanges(allChanges) {
5282
+ this.uniqueRefIds.clear();
5283
+ for (let i = 0, l = allChanges.length; i < l; i++) {
5284
+ const change = allChanges[i];
5285
+ const refId = change.refId;
5286
+ const ref = change.ref;
5287
+ const $callbacks = this.callbacks[refId];
5288
+ if (!$callbacks) {
5289
+ continue;
5290
+ }
5291
+ //
5292
+ // trigger onRemove on child structure.
5293
+ //
5294
+ if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE &&
5295
+ change.previousValue instanceof Schema) {
5296
+ const childRefId = change.previousValue[$refId];
5297
+ const deleteCallbacks = this.callbacks[childRefId]?.[exports.OPERATION.DELETE];
5298
+ if (deleteCallbacks) {
5299
+ for (let j = deleteCallbacks.length - 1; j >= 0; j--) {
5300
+ deleteCallbacks[j]();
5301
+ }
5302
+ }
5303
+ }
5304
+ if (ref instanceof Schema) {
5305
+ //
5306
+ // Handle Schema instance
5307
+ //
5308
+ if (!this.uniqueRefIds.has(refId)) {
5309
+ // trigger onChange
5310
+ const replaceCallbacks = $callbacks[exports.OPERATION.REPLACE];
5311
+ if (replaceCallbacks) {
5312
+ for (let j = replaceCallbacks.length - 1; j >= 0; j--) {
5313
+ try {
5314
+ replaceCallbacks[j]();
5315
+ }
5316
+ catch (e) {
5317
+ console.error(e);
5318
+ }
5319
+ }
5320
+ }
5321
+ }
5322
+ // trigger field callbacks
5323
+ const fieldCallbacks = $callbacks[change.field];
5324
+ if (fieldCallbacks) {
5325
+ for (let j = fieldCallbacks.length - 1; j >= 0; j--) {
5326
+ try {
5327
+ this.isTriggering = true;
5328
+ fieldCallbacks[j](change.value, change.previousValue);
5329
+ }
5330
+ catch (e) {
5331
+ console.error(e);
5332
+ }
5333
+ finally {
5334
+ this.isTriggering = false;
5335
+ }
5336
+ }
5337
+ }
5338
+ }
5339
+ else {
5340
+ //
5341
+ // Handle collection of items
5342
+ //
5343
+ const dynamicIndex = change.dynamicIndex ?? change.field;
5344
+ if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE) {
5345
+ //
5346
+ // FIXME: `previousValue` should always be available.
5347
+ //
5348
+ if (change.previousValue !== undefined) {
5349
+ // trigger onRemove (key, value)
5350
+ const deleteCallbacks = $callbacks[exports.OPERATION.DELETE];
5351
+ if (deleteCallbacks) {
5352
+ for (let j = deleteCallbacks.length - 1; j >= 0; j--) {
5353
+ deleteCallbacks[j](dynamicIndex, change.previousValue);
5354
+ }
5355
+ }
5356
+ }
5357
+ // Handle DELETE_AND_ADD operation
5358
+ if ((change.op & exports.OPERATION.ADD) === exports.OPERATION.ADD) {
5359
+ const addCallbacks = $callbacks[exports.OPERATION.ADD];
5360
+ if (addCallbacks) {
5361
+ this.isTriggering = true;
5362
+ for (let j = addCallbacks.length - 1; j >= 0; j--) {
5363
+ addCallbacks[j](dynamicIndex, change.value);
5364
+ }
5365
+ this.isTriggering = false;
5366
+ }
5367
+ }
5368
+ }
5369
+ else if ((change.op & exports.OPERATION.ADD) === exports.OPERATION.ADD &&
5370
+ change.previousValue !== change.value) {
5371
+ // trigger onAdd (key, value)
5372
+ const addCallbacks = $callbacks[exports.OPERATION.ADD];
5373
+ if (addCallbacks) {
5374
+ this.isTriggering = true;
5375
+ for (let j = addCallbacks.length - 1; j >= 0; j--) {
5376
+ addCallbacks[j](dynamicIndex, change.value);
5377
+ }
5378
+ this.isTriggering = false;
5379
+ }
5380
+ }
5381
+ // trigger onChange (key, value)
5382
+ if (change.value !== change.previousValue) {
5383
+ const replaceCallbacks = $callbacks[exports.OPERATION.REPLACE];
5384
+ if (replaceCallbacks) {
5385
+ for (let j = replaceCallbacks.length - 1; j >= 0; j--) {
5386
+ replaceCallbacks[j](dynamicIndex, change.value);
5387
+ }
5388
+ }
5389
+ }
5390
+ }
5391
+ this.uniqueRefIds.add(refId);
5392
+ }
5393
+ }
5394
+ }
5395
+ /**
5396
+ * Factory class for retrieving the callbacks API.
5397
+ */
5398
+ const Callbacks = {
5399
+ /**
5400
+ * Get the new callbacks standard API.
5401
+ *
5402
+ * Usage:
5403
+ * ```ts
5404
+ * const callbacks = Callbacks.get(roomOrDecoder);
5405
+ *
5406
+ * // Listen to property changes
5407
+ * callbacks.listen("currentTurn", (currentValue, previousValue) => { ... });
5408
+ *
5409
+ * // Listen to collection additions
5410
+ * callbacks.onAdd("entities", (sessionId, entity) => {
5411
+ * // Nested property listening
5412
+ * callbacks.listen(entity, "hp", (currentHp, previousHp) => { ... });
5413
+ * });
5414
+ *
5415
+ * // Listen to collection removals
5416
+ * callbacks.onRemove("entities", (sessionId, entity) => { ... });
5417
+ *
5418
+ * // Listen to any property change on an instance
5419
+ * callbacks.onChange(entity, () => { ... });
5420
+ *
5421
+ * // Bind properties to another object
5422
+ * callbacks.bindTo(player, playerVisual);
5423
+ * ```
5424
+ *
5425
+ * @param roomOrDecoder - Room or Decoder instance to get the callbacks for.
5426
+ * @returns the new callbacks standard API.
5427
+ */
5428
+ get(roomOrDecoder) {
5429
+ if (roomOrDecoder instanceof Decoder) {
5430
+ return new StateCallbackStrategy(roomOrDecoder);
5431
+ }
5432
+ else if (roomOrDecoder.serializer.decoder) {
5433
+ return new StateCallbackStrategy(roomOrDecoder.serializer.decoder);
5434
+ }
5435
+ else {
5436
+ throw new Error('Invalid room or decoder');
5437
+ }
5438
+ },
5439
+ /**
5440
+ * Get the legacy callbacks API.
5441
+ *
5442
+ * We aim to deprecate this API on 1.0, and iterate on improving Callbacks.get() API.
5443
+ *
5444
+ * @param roomOrDecoder - Room or Decoder instance to get the legacy callbacks for.
5445
+ * @returns the legacy callbacks API.
5446
+ */
5447
+ getLegacy(roomOrDecoder) {
5448
+ if (roomOrDecoder instanceof Decoder) {
5449
+ return getDecoderStateCallbacks(roomOrDecoder);
5450
+ }
5451
+ else if (roomOrDecoder.serializer.decoder) {
5452
+ return getDecoderStateCallbacks(roomOrDecoder.serializer.decoder);
5453
+ }
5454
+ },
5455
+ getRawChanges(decoder, callback) {
5456
+ return getRawChangesCallback(decoder, callback);
5457
+ }
5458
+ };
5459
+
5120
5460
  class StateView {
5461
+ iterable;
5462
+ /**
5463
+ * Iterable list of items that are visible to this view
5464
+ * (Available only if constructed with `iterable: true`)
5465
+ */
5466
+ items;
5467
+ /**
5468
+ * List of ChangeTree's that are visible to this view
5469
+ */
5470
+ visible = new WeakSet();
5471
+ /**
5472
+ * List of ChangeTree's that are invisible to this view
5473
+ */
5474
+ invisible = new WeakSet();
5475
+ tags; // TODO: use bit manipulation instead of Set<number> ()
5476
+ /**
5477
+ * Manual "ADD" operations for changes per ChangeTree, specific to this view.
5478
+ * (This is used to force encoding a property, even if it was not changed)
5479
+ */
5480
+ changes = new Map();
5121
5481
  constructor(iterable = false) {
5122
5482
  this.iterable = iterable;
5123
- /**
5124
- * List of ChangeTree's that are visible to this view
5125
- */
5126
- this.visible = new WeakSet();
5127
- /**
5128
- * List of ChangeTree's that are invisible to this view
5129
- */
5130
- this.invisible = new WeakSet();
5131
- /**
5132
- * Manual "ADD" operations for changes per ChangeTree, specific to this view.
5133
- * (This is used to force encoding a property, even if it was not changed)
5134
- */
5135
- this.changes = new Map();
5136
5483
  if (iterable) {
5137
5484
  this.items = [];
5138
5485
  }
@@ -5146,7 +5493,7 @@
5146
5493
  return false;
5147
5494
  }
5148
5495
  else if (!parentChangeTree &&
5149
- changeTree.refId !== 0 // allow root object
5496
+ obj[$refId] !== 0 // allow root object
5150
5497
  ) {
5151
5498
  /**
5152
5499
  * TODO: can we avoid this?
@@ -5170,11 +5517,11 @@
5170
5517
  if (checkIncludeParent && parentChangeTree) {
5171
5518
  this.addParentOf(changeTree, tag);
5172
5519
  }
5173
- let changes = this.changes.get(changeTree.refId);
5520
+ let changes = this.changes.get(obj[$refId]);
5174
5521
  if (changes === undefined) {
5175
5522
  changes = {};
5176
5523
  // FIXME / OPTIMIZE: do not add if no changes are needed
5177
- this.changes.set(changeTree.refId, changes);
5524
+ this.changes.set(obj[$refId], changes);
5178
5525
  }
5179
5526
  let isChildAdded = false;
5180
5527
  //
@@ -5254,10 +5601,10 @@
5254
5601
  }
5255
5602
  // add parent's tag properties
5256
5603
  if (changeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {
5257
- let changes = this.changes.get(changeTree.refId);
5604
+ let changes = this.changes.get(changeTree.ref[$refId]);
5258
5605
  if (changes === undefined) {
5259
5606
  changes = {};
5260
- this.changes.set(changeTree.refId, changes);
5607
+ this.changes.set(changeTree.ref[$refId], changes);
5261
5608
  }
5262
5609
  if (!this.tags) {
5263
5610
  this.tags = new WeakMap();
@@ -5289,27 +5636,28 @@
5289
5636
  }
5290
5637
  const ref = changeTree.ref;
5291
5638
  const metadata = ref.constructor[Symbol.metadata]; // ArraySchema/MapSchema do not have metadata
5292
- let changes = this.changes.get(changeTree.refId);
5639
+ const refId = ref[$refId];
5640
+ let changes = this.changes.get(refId);
5293
5641
  if (changes === undefined) {
5294
5642
  changes = {};
5295
- this.changes.set(changeTree.refId, changes);
5643
+ this.changes.set(refId, changes);
5296
5644
  }
5297
5645
  if (tag === DEFAULT_VIEW_TAG) {
5298
5646
  // parent is collection (Map/Array)
5299
5647
  const parent = changeTree.parent;
5300
5648
  if (parent && !Metadata.isValidInstance(parent) && changeTree.isFiltered) {
5301
- const parentChangeTree = parent[$changes];
5302
- let changes = this.changes.get(parentChangeTree.refId);
5649
+ const parentRefId = parent[$refId];
5650
+ let changes = this.changes.get(parentRefId);
5303
5651
  if (changes === undefined) {
5304
5652
  changes = {};
5305
- this.changes.set(parentChangeTree.refId, changes);
5653
+ this.changes.set(parentRefId, changes);
5306
5654
  }
5307
5655
  else if (changes[changeTree.parentIndex] === exports.OPERATION.ADD) {
5308
5656
  //
5309
5657
  // SAME PATCH ADD + REMOVE:
5310
5658
  // The 'changes' of deleted structure should be ignored.
5311
5659
  //
5312
- this.changes.delete(changeTree.refId);
5660
+ this.changes.delete(refId);
5313
5661
  }
5314
5662
  // DELETE / DELETE BY REF ID
5315
5663
  changes[changeTree.parentIndex] = exports.OPERATION.DELETE;
@@ -5369,7 +5717,7 @@
5369
5717
  if (!isVisible && changeTree.isVisibilitySharedWithParent) {
5370
5718
  // console.log("CHECK AGAINST PARENT...", {
5371
5719
  // ref: changeTree.ref.constructor.name,
5372
- // refId: changeTree.refId,
5720
+ // refId: changeTree.ref[$refId],
5373
5721
  // parent: changeTree.parent.constructor.name,
5374
5722
  // });
5375
5723
  if (this.visible.has(changeTree.parent[$changes])) {
@@ -5399,8 +5747,10 @@
5399
5747
  exports.$encoder = $encoder;
5400
5748
  exports.$filter = $filter;
5401
5749
  exports.$getByIndex = $getByIndex;
5750
+ exports.$refId = $refId;
5402
5751
  exports.$track = $track;
5403
5752
  exports.ArraySchema = ArraySchema;
5753
+ exports.Callbacks = Callbacks;
5404
5754
  exports.ChangeTree = ChangeTree;
5405
5755
  exports.CollectionSchema = CollectionSchema;
5406
5756
  exports.Decoder = Decoder;
@@ -5412,6 +5762,7 @@
5412
5762
  exports.ReflectionType = ReflectionType;
5413
5763
  exports.Schema = Schema;
5414
5764
  exports.SetSchema = SetSchema;
5765
+ exports.StateCallbackStrategy = StateCallbackStrategy;
5415
5766
  exports.StateView = StateView;
5416
5767
  exports.TypeContext = TypeContext;
5417
5768
  exports.decode = decode;