@colyseus/schema 3.0.76 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/build/cjs/index.js +781 -434
  2. package/build/cjs/index.js.map +1 -1
  3. package/build/esm/index.mjs +779 -435
  4. package/build/esm/index.mjs.map +1 -1
  5. package/build/umd/index.js +781 -434
  6. package/lib/Metadata.js +1 -5
  7. package/lib/Metadata.js.map +1 -1
  8. package/lib/Reflection.d.ts +50 -17
  9. package/lib/Reflection.js +151 -202
  10. package/lib/Reflection.js.map +1 -1
  11. package/lib/Schema.d.ts +13 -1
  12. package/lib/Schema.js +73 -9
  13. package/lib/Schema.js.map +1 -1
  14. package/lib/annotations.d.ts +6 -1
  15. package/lib/annotations.js +8 -34
  16. package/lib/annotations.js.map +1 -1
  17. package/lib/bench_encode.js +34 -1
  18. package/lib/bench_encode.js.map +1 -1
  19. package/lib/codegen/api.js +35 -2
  20. package/lib/codegen/api.js.map +1 -1
  21. package/lib/codegen/cli.js +4 -1
  22. package/lib/codegen/cli.js.map +1 -1
  23. package/lib/codegen/parser.js +35 -2
  24. package/lib/codegen/parser.js.map +1 -1
  25. package/lib/codegen/types.js +34 -1
  26. package/lib/codegen/types.js.map +1 -1
  27. package/lib/decoder/DecodeOperation.d.ts +2 -2
  28. package/lib/decoder/DecodeOperation.js +3 -3
  29. package/lib/decoder/DecodeOperation.js.map +1 -1
  30. package/lib/decoder/Decoder.d.ts +3 -3
  31. package/lib/decoder/Decoder.js +2 -2
  32. package/lib/decoder/Decoder.js.map +1 -1
  33. package/lib/decoder/ReferenceTracker.d.ts +0 -1
  34. package/lib/decoder/ReferenceTracker.js +9 -7
  35. package/lib/decoder/ReferenceTracker.js.map +1 -1
  36. package/lib/decoder/strategy/Callbacks.d.ts +154 -0
  37. package/lib/decoder/strategy/Callbacks.js +340 -0
  38. package/lib/decoder/strategy/Callbacks.js.map +1 -0
  39. package/lib/decoder/strategy/{StateCallbacks.d.ts → getDecoderStateCallbacks.d.ts} +6 -0
  40. package/lib/decoder/strategy/{StateCallbacks.js → getDecoderStateCallbacks.js} +17 -10
  41. package/lib/decoder/strategy/getDecoderStateCallbacks.js.map +1 -0
  42. package/lib/encoder/ChangeTree.d.ts +2 -2
  43. package/lib/encoder/ChangeTree.js.map +1 -1
  44. package/lib/encoder/EncodeOperation.d.ts +2 -2
  45. package/lib/encoder/EncodeOperation.js +3 -3
  46. package/lib/encoder/EncodeOperation.js.map +1 -1
  47. package/lib/encoder/Encoder.d.ts +6 -6
  48. package/lib/encoder/Encoder.js +19 -18
  49. package/lib/encoder/Encoder.js.map +1 -1
  50. package/lib/encoder/Root.js +17 -14
  51. package/lib/encoder/Root.js.map +1 -1
  52. package/lib/encoder/StateView.js +13 -12
  53. package/lib/encoder/StateView.js.map +1 -1
  54. package/lib/encoding/decode.d.ts +2 -2
  55. package/lib/encoding/encode.d.ts +3 -1
  56. package/lib/encoding/encode.js.map +1 -1
  57. package/lib/index.d.ts +3 -2
  58. package/lib/index.js +7 -3
  59. package/lib/index.js.map +1 -1
  60. package/lib/types/HelperTypes.d.ts +7 -14
  61. package/lib/types/HelperTypes.js.map +1 -1
  62. package/lib/types/custom/ArraySchema.d.ts +2 -1
  63. package/lib/types/custom/ArraySchema.js.map +1 -1
  64. package/lib/types/custom/CollectionSchema.d.ts +2 -1
  65. package/lib/types/custom/CollectionSchema.js.map +1 -1
  66. package/lib/types/custom/MapSchema.d.ts +3 -2
  67. package/lib/types/custom/MapSchema.js.map +1 -1
  68. package/lib/types/custom/SetSchema.d.ts +2 -1
  69. package/lib/types/custom/SetSchema.js.map +1 -1
  70. package/lib/types/symbols.d.ts +1 -0
  71. package/lib/types/symbols.js +2 -1
  72. package/lib/types/symbols.js.map +1 -1
  73. package/lib/utils.js +1 -1
  74. package/lib/utils.js.map +1 -1
  75. package/package.json +12 -16
  76. package/src/Metadata.ts +1 -5
  77. package/src/Reflection.ts +185 -174
  78. package/src/Schema.ts +81 -13
  79. package/src/annotations.ts +14 -40
  80. package/src/codegen/parser.ts +1 -1
  81. package/src/decoder/DecodeOperation.ts +9 -9
  82. package/src/decoder/Decoder.ts +6 -6
  83. package/src/decoder/ReferenceTracker.ts +10 -8
  84. package/src/decoder/strategy/Callbacks.ts +547 -0
  85. package/src/decoder/strategy/{StateCallbacks.ts → getDecoderStateCallbacks.ts} +17 -11
  86. package/src/encoder/ChangeTree.ts +4 -7
  87. package/src/encoder/EncodeOperation.ts +9 -9
  88. package/src/encoder/Encoder.ts +47 -22
  89. package/src/encoder/Root.ts +20 -15
  90. package/src/encoder/StateView.ts +15 -13
  91. package/src/encoding/encode.ts +1 -1
  92. package/src/index.ts +3 -2
  93. package/src/types/HelperTypes.ts +13 -11
  94. package/src/types/custom/ArraySchema.ts +2 -1
  95. package/src/types/custom/CollectionSchema.ts +4 -2
  96. package/src/types/custom/MapSchema.ts +4 -2
  97. package/src/types/custom/SetSchema.ts +3 -1
  98. package/src/types/symbols.ts +1 -0
  99. package/src/utils.ts +2 -2
  100. package/lib/Decoder.d.ts +0 -16
  101. package/lib/Decoder.js +0 -182
  102. package/lib/Decoder.js.map +0 -1
  103. package/lib/Encoder.d.ts +0 -13
  104. package/lib/Encoder.js +0 -79
  105. package/lib/Encoder.js.map +0 -1
  106. package/lib/changes/ChangeSet.d.ts +0 -12
  107. package/lib/changes/ChangeSet.js +0 -35
  108. package/lib/changes/ChangeSet.js.map +0 -1
  109. package/lib/changes/ChangeTree.d.ts +0 -53
  110. package/lib/changes/ChangeTree.js +0 -202
  111. package/lib/changes/ChangeTree.js.map +0 -1
  112. package/lib/changes/DecodeOperation.d.ts +0 -15
  113. package/lib/changes/DecodeOperation.js +0 -186
  114. package/lib/changes/DecodeOperation.js.map +0 -1
  115. package/lib/changes/EncodeOperation.d.ts +0 -18
  116. package/lib/changes/EncodeOperation.js +0 -130
  117. package/lib/changes/EncodeOperation.js.map +0 -1
  118. package/lib/changes/ReferenceTracker.d.ts +0 -14
  119. package/lib/changes/ReferenceTracker.js +0 -83
  120. package/lib/changes/ReferenceTracker.js.map +0 -1
  121. package/lib/changes/consts.d.ts +0 -14
  122. package/lib/changes/consts.js +0 -18
  123. package/lib/changes/consts.js.map +0 -1
  124. package/lib/decoder/strategy/StateCallbacks.js.map +0 -1
  125. package/lib/decoding/decode.d.ts +0 -48
  126. package/lib/decoding/decode.js +0 -267
  127. package/lib/decoding/decode.js.map +0 -1
  128. package/lib/ecs.d.ts +0 -11
  129. package/lib/ecs.js +0 -160
  130. package/lib/ecs.js.map +0 -1
  131. package/lib/filters/index.d.ts +0 -8
  132. package/lib/filters/index.js +0 -24
  133. package/lib/filters/index.js.map +0 -1
  134. package/lib/spec.d.ts +0 -13
  135. package/lib/spec.js +0 -42
  136. package/lib/spec.js.map +0 -1
  137. package/lib/types/ArraySchema.d.ts +0 -238
  138. package/lib/types/ArraySchema.js +0 -555
  139. package/lib/types/ArraySchema.js.map +0 -1
  140. package/lib/types/CollectionSchema.d.ts +0 -35
  141. package/lib/types/CollectionSchema.js +0 -150
  142. package/lib/types/CollectionSchema.js.map +0 -1
  143. package/lib/types/MapSchema.d.ts +0 -38
  144. package/lib/types/MapSchema.js +0 -215
  145. package/lib/types/MapSchema.js.map +0 -1
  146. package/lib/types/SetSchema.d.ts +0 -32
  147. package/lib/types/SetSchema.js +0 -162
  148. package/lib/types/SetSchema.js.map +0 -1
  149. package/lib/types/typeRegistry.d.ts +0 -5
  150. package/lib/types/typeRegistry.js +0 -13
  151. package/lib/types/typeRegistry.js.map +0 -1
  152. package/lib/usage.d.ts +0 -1
  153. package/lib/usage.js +0 -22
  154. package/lib/usage.js.map +0 -1
  155. package/lib/v3.d.ts +0 -1
  156. package/lib/v3.js +0 -427
  157. package/lib/v3.js.map +0 -1
  158. package/lib/v3_experiment.d.ts +0 -1
  159. package/lib/v3_experiment.js +0 -407
  160. 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
  }
@@ -932,11 +933,7 @@
932
933
  });
933
934
  }
934
935
  }
935
- Object.defineProperty(constructor, Symbol.metadata, {
936
- value: metadata,
937
- writable: false,
938
- configurable: true
939
- });
936
+ constructor[Symbol.metadata] = metadata;
940
937
  return metadata;
941
938
  },
942
939
  isValidInstance(klass) {
@@ -988,25 +985,33 @@
988
985
  delete changeSet.indexes[index];
989
986
  }
990
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;
991
1014
  constructor(ref) {
992
- /**
993
- * Whether this structure is parent of a filtered structure.
994
- */
995
- this.isFiltered = false;
996
- this.indexedOperations = {};
997
- //
998
- // TODO:
999
- // try storing the index + operation per item.
1000
- // example: 1024 & 1025 => ADD, 1026 => DELETE
1001
- //
1002
- // => https://chatgpt.com/share/67107d0c-bc20-8004-8583-83b17dd7c196
1003
- //
1004
- this.changes = { indexes: {}, operations: [] };
1005
- this.allChanges = { indexes: {}, operations: [] };
1006
- /**
1007
- * Is this a new instance? Used on ArraySchema to determine OPERATION.MOVE_AND_ADD operation.
1008
- */
1009
- this.isNew = true;
1010
1015
  this.ref = ref;
1011
1016
  this.metadata = ref.constructor[Symbol.metadata];
1012
1017
  //
@@ -1478,7 +1483,7 @@
1478
1483
  // Encode refId for this instance.
1479
1484
  // The actual instance is going to be encoded on next `changeTree` iteration.
1480
1485
  //
1481
- encode.number(bytes, value[$changes].refId, it);
1486
+ encode.number(bytes, value[$refId], it);
1482
1487
  // Try to encode inherited TYPE_ID if it's an ADD operation.
1483
1488
  if ((operation & exports.OPERATION.ADD) === exports.OPERATION.ADD) {
1484
1489
  encoder.tryEncodeTypeId(bytes, type, value.constructor, it);
@@ -1489,7 +1494,7 @@
1489
1494
  // Encode refId for this instance.
1490
1495
  // The actual instance is going to be encoded on next `changeTree` iteration.
1491
1496
  //
1492
- encode.number(bytes, value[$changes].refId, it);
1497
+ encode.number(bytes, value[$refId], it);
1493
1498
  }
1494
1499
  }
1495
1500
  /**
@@ -1565,7 +1570,7 @@
1565
1570
  if (!item) {
1566
1571
  return;
1567
1572
  }
1568
- refOrIndex = item[$changes].refId;
1573
+ refOrIndex = item[$refId];
1569
1574
  if (operation === exports.OPERATION.DELETE) {
1570
1575
  operation = exports.OPERATION.DELETE_BY_REFID;
1571
1576
  }
@@ -1605,7 +1610,7 @@
1605
1610
  let value;
1606
1611
  if ((operation & exports.OPERATION.DELETE) === exports.OPERATION.DELETE) {
1607
1612
  // Flag `refId` for garbage collection.
1608
- const previousRefId = $root.refIds.get(previousValue);
1613
+ const previousRefId = previousValue?.[$refId];
1609
1614
  if (previousRefId !== undefined) {
1610
1615
  $root.removeRef(previousRefId);
1611
1616
  }
@@ -1646,7 +1651,7 @@
1646
1651
  value = valueRef.clone(true);
1647
1652
  value[$childType] = Object.values(type)[0]; // cache childType for ArraySchema and MapSchema
1648
1653
  if (previousValue) {
1649
- let previousRefId = $root.refIds.get(previousValue);
1654
+ let previousRefId = previousValue[$refId];
1650
1655
  if (previousRefId !== undefined && refId !== previousRefId) {
1651
1656
  //
1652
1657
  // enqueue onRemove if structure has been replaced.
@@ -1657,7 +1662,7 @@
1657
1662
  const [key, value] = iter.value;
1658
1663
  // if value is a schema, remove its reference
1659
1664
  if (typeof (value) === "object") {
1660
- previousRefId = $root.refIds.get(value);
1665
+ previousRefId = value[$refId];
1661
1666
  $root.removeRef(previousRefId);
1662
1667
  }
1663
1668
  allChanges.push({
@@ -1886,7 +1891,6 @@
1886
1891
  }
1887
1892
  }
1888
1893
 
1889
- var _a$4, _b$4;
1890
1894
  const DEFAULT_SORT = (a, b) => {
1891
1895
  const A = a.toString();
1892
1896
  const B = b.toString();
@@ -1898,8 +1902,15 @@
1898
1902
  return 0;
1899
1903
  };
1900
1904
  class ArraySchema {
1901
- static { this[_a$4] = encodeArray; }
1902
- 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;
1903
1914
  /**
1904
1915
  * Determine if a property must be filtered.
1905
1916
  * - If returns false, the property is NOT going to be encoded.
@@ -1909,7 +1920,7 @@
1909
1920
  * - First, the encoder iterates over all "not owned" properties and encodes them.
1910
1921
  * - Then, the encoder iterates over all "owned" properties per instance and encodes them.
1911
1922
  */
1912
- static [(_a$4 = $encoder, _b$4 = $decoder, $filter)](ref, index, view) {
1923
+ static [$filter](ref, index, view) {
1913
1924
  return (!view ||
1914
1925
  typeof (ref[$childType]) === "string" ||
1915
1926
  view.isChangeTreeVisible(ref['tmpItems'][index]?.[$changes]));
@@ -1925,10 +1936,6 @@
1925
1936
  return new ArraySchema(...Array.from(iterable));
1926
1937
  }
1927
1938
  constructor(...items) {
1928
- this.items = [];
1929
- this.tmpItems = [];
1930
- this.deletedIndexes = {};
1931
- this.isMovingItems = false;
1932
1939
  Object.defineProperty(this, $childType, {
1933
1940
  value: undefined,
1934
1941
  enumerable: false,
@@ -2445,6 +2452,10 @@
2445
2452
  static get [Symbol.species]() {
2446
2453
  return ArraySchema;
2447
2454
  }
2455
+ // WORKAROUND for compatibility
2456
+ // - TypeScript 4 defines @@unscopables as a function
2457
+ // - TypeScript 5 defines @@unscopables as an object
2458
+ [Symbol.unscopables];
2448
2459
  /**
2449
2460
  * Returns an iterable of key, value pairs for every entry in the array
2450
2461
  */
@@ -2554,7 +2565,7 @@
2554
2565
  this.isMovingItems = false;
2555
2566
  return this;
2556
2567
  }
2557
- [($getByIndex)](index, isEncodeAll = false) {
2568
+ [$getByIndex](index, isEncodeAll = false) {
2558
2569
  //
2559
2570
  // TODO: avoid unecessary `this.tmpItems` check during decoding.
2560
2571
  //
@@ -2609,10 +2620,16 @@
2609
2620
  }
2610
2621
  registerType("array", { constructor: ArraySchema });
2611
2622
 
2612
- var _a$3, _b$3;
2613
2623
  class MapSchema {
2614
- static { this[_a$3] = encodeKeyValueOperation; }
2615
- 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;
2616
2633
  /**
2617
2634
  * Determine if a property must be filtered.
2618
2635
  * - If returns false, the property is NOT going to be encoded.
@@ -2622,7 +2639,7 @@
2622
2639
  * - First, the encoder iterates over all "not owned" properties and encodes them.
2623
2640
  * - Then, the encoder iterates over all "owned" properties per instance and encodes them.
2624
2641
  */
2625
- static [(_a$3 = $encoder, _b$3 = $decoder, $filter)](ref, index, view) {
2642
+ static [$filter](ref, index, view) {
2626
2643
  return (!view ||
2627
2644
  typeof (ref[$childType]) === "string" ||
2628
2645
  view.isChangeTreeVisible((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
@@ -2631,9 +2648,6 @@
2631
2648
  return type['map'] !== undefined;
2632
2649
  }
2633
2650
  constructor(initialValues) {
2634
- this.$items = new Map();
2635
- this.$indexes = new Map();
2636
- this.deletedItems = {};
2637
2651
  const changeTree = new ChangeTree(this);
2638
2652
  changeTree.indexes = {};
2639
2653
  Object.defineProperty(this, $changes, {
@@ -2824,10 +2838,16 @@
2824
2838
  }
2825
2839
  registerType("map", { constructor: MapSchema });
2826
2840
 
2827
- var _a$2, _b$2;
2828
2841
  class CollectionSchema {
2829
- static { this[_a$2] = encodeKeyValueOperation; }
2830
- 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;
2831
2851
  /**
2832
2852
  * Determine if a property must be filtered.
2833
2853
  * - If returns false, the property is NOT going to be encoded.
@@ -2837,7 +2857,7 @@
2837
2857
  * - First, the encoder iterates over all "not owned" properties and encodes them.
2838
2858
  * - Then, the encoder iterates over all "owned" properties per instance and encodes them.
2839
2859
  */
2840
- static [(_a$2 = $encoder, _b$2 = $decoder, $filter)](ref, index, view) {
2860
+ static [$filter](ref, index, view) {
2841
2861
  return (!view ||
2842
2862
  typeof (ref[$childType]) === "string" ||
2843
2863
  view.isChangeTreeVisible((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
@@ -2846,10 +2866,6 @@
2846
2866
  return type['collection'] !== undefined;
2847
2867
  }
2848
2868
  constructor(initialValues) {
2849
- this.$items = new Map();
2850
- this.$indexes = new Map();
2851
- this.deletedItems = {};
2852
- this.$refId = 0;
2853
2869
  this[$changes] = new ChangeTree(this);
2854
2870
  this[$changes].indexes = {};
2855
2871
  if (initialValues) {
@@ -2988,10 +3004,16 @@
2988
3004
  }
2989
3005
  registerType("collection", { constructor: CollectionSchema, });
2990
3006
 
2991
- var _a$1, _b$1;
2992
3007
  class SetSchema {
2993
- static { this[_a$1] = encodeKeyValueOperation; }
2994
- 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;
2995
3017
  /**
2996
3018
  * Determine if a property must be filtered.
2997
3019
  * - If returns false, the property is NOT going to be encoded.
@@ -3001,7 +3023,7 @@
3001
3023
  * - First, the encoder iterates over all "not owned" properties and encodes them.
3002
3024
  * - Then, the encoder iterates over all "owned" properties per instance and encodes them.
3003
3025
  */
3004
- static [(_a$1 = $encoder, _b$1 = $decoder, $filter)](ref, index, view) {
3026
+ static [$filter](ref, index, view) {
3005
3027
  return (!view ||
3006
3028
  typeof (ref[$childType]) === "string" ||
3007
3029
  view.visible.has((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
@@ -3010,10 +3032,6 @@
3010
3032
  return type['set'] !== undefined;
3011
3033
  }
3012
3034
  constructor(initialValues) {
3013
- this.$items = new Map();
3014
- this.$indexes = new Map();
3015
- this.deletedItems = {};
3016
- this.$refId = 0;
3017
3035
  this[$changes] = new ChangeTree(this);
3018
3036
  this[$changes].indexes = {};
3019
3037
  if (initialValues) {
@@ -3512,7 +3530,10 @@
3512
3530
  ? DEFAULT_VIEW_TAG
3513
3531
  : value['view'];
3514
3532
  }
3515
- fields[fieldName] = getNormalizedType(value);
3533
+ // allow to define a field as not synced
3534
+ if (value['sync'] !== false) {
3535
+ fields[fieldName] = getNormalizedType(value);
3536
+ }
3516
3537
  // If no explicit default provided, handle automatic instantiation for collection types
3517
3538
  if (!Object.prototype.hasOwnProperty.call(value, 'default')) {
3518
3539
  // TODO: remove Array.isArray() check. Use ['array'] !== undefined only.
@@ -3534,12 +3555,7 @@
3534
3555
  }
3535
3556
  else if (value['type'] !== undefined && Schema.is(value['type'])) {
3536
3557
  // Direct Schema type: Type → new Type()
3537
- if (!value['type'].prototype.initialize || value['type'].prototype.initialize.length === 0) {
3538
- // only auto-initialize Schema instances if:
3539
- // - they don't have an initialize method
3540
- // - or initialize method doesn't accept any parameters
3541
- defaultValues[fieldName] = new value['type']();
3542
- }
3558
+ defaultValues[fieldName] = new value['type']();
3543
3559
  }
3544
3560
  }
3545
3561
  else {
@@ -3549,12 +3565,7 @@
3549
3565
  else if (typeof (value) === "function") {
3550
3566
  if (Schema.is(value)) {
3551
3567
  // Direct Schema type: Type → new Type()
3552
- if (!value.prototype.initialize || value.prototype.initialize.length === 0) {
3553
- // only auto-initialize Schema instances if:
3554
- // - they don't have an initialize method
3555
- // - or initialize method doesn't accept any parameters
3556
- defaultValues[fieldName] = new value();
3557
- }
3568
+ defaultValues[fieldName] = new value();
3558
3569
  fields[fieldName] = getNormalizedType(value);
3559
3570
  }
3560
3571
  else {
@@ -3581,32 +3592,13 @@
3581
3592
  }
3582
3593
  return defaults;
3583
3594
  };
3584
- const getParentProps = (props) => {
3585
- const fieldNames = Object.keys(fields);
3586
- const parentProps = {};
3587
- for (const key in props) {
3588
- if (!fieldNames.includes(key)) {
3589
- parentProps[key] = props[key];
3590
- }
3591
- }
3592
- return parentProps;
3593
- };
3594
3595
  /** @codegen-ignore */
3595
3596
  const klass = Metadata.setFields(class extends inherits {
3596
3597
  constructor(...args) {
3598
+ super(Object.assign({}, getDefaultValues(), args[0] || {}));
3597
3599
  // call initialize method
3598
3600
  if (methods.initialize && typeof methods.initialize === 'function') {
3599
- super(Object.assign({}, getDefaultValues(), getParentProps(args[0] || {})));
3600
- /**
3601
- * only call initialize() in the current class, not the parent ones.
3602
- * see "should not call initialize automatically when creating an instance of inherited Schema"
3603
- */
3604
- if (new.target === klass) {
3605
- methods.initialize.apply(this, args);
3606
- }
3607
- }
3608
- else {
3609
- super(Object.assign({}, getDefaultValues(), args[0] || {}));
3601
+ methods.initialize.apply(this, args);
3610
3602
  }
3611
3603
  }
3612
3604
  }, fields);
@@ -3643,7 +3635,7 @@
3643
3635
  continue;
3644
3636
  }
3645
3637
  const changes = changeTree.indexedOperations;
3646
- dump.refs.push(`refId#${changeTree.refId}`);
3638
+ dump.refs.push(`refId#${changeTree.ref[$refId]}`);
3647
3639
  for (const index in changes) {
3648
3640
  const op = changes[index];
3649
3641
  const opName = exports.OPERATION[op];
@@ -3657,13 +3649,14 @@
3657
3649
  return dump;
3658
3650
  }
3659
3651
 
3660
- var _a, _b;
3661
3652
  /**
3662
3653
  * Schema encoder / decoder
3663
3654
  */
3664
3655
  class Schema {
3665
- static { this[_a] = encodeSchemaOperation; }
3666
- static { this[_b] = decodeSchemaOperation; }
3656
+ static [Symbol.metadata];
3657
+ static [$encoder] = encodeSchemaOperation;
3658
+ static [$decoder] = decodeSchemaOperation;
3659
+ [$refId];
3667
3660
  /**
3668
3661
  * Assign the property descriptors required to track changes on this instance.
3669
3662
  * @param instance
@@ -3682,7 +3675,7 @@
3682
3675
  /**
3683
3676
  * Track property changes
3684
3677
  */
3685
- static [(_a = $encoder, _b = $decoder, $track)](changeTree, index, operation = exports.OPERATION.ADD) {
3678
+ static [$track](changeTree, index, operation = exports.OPERATION.ADD) {
3686
3679
  changeTree.change(index, operation);
3687
3680
  }
3688
3681
  /**
@@ -3729,10 +3722,74 @@
3729
3722
  Object.assign(this, arg);
3730
3723
  }
3731
3724
  }
3725
+ /**
3726
+ * Assign properties to the instance.
3727
+ * @param props Properties to assign to the instance
3728
+ * @returns
3729
+ */
3732
3730
  assign(props) {
3733
3731
  Object.assign(this, props);
3734
3732
  return this;
3735
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
+ }
3736
3793
  /**
3737
3794
  * (Server-side): Flag a property to be encoded for the next patch.
3738
3795
  * @param instance Schema instance
@@ -3805,7 +3862,7 @@
3805
3862
  static debugRefIds(ref, showContents = false, level = 0, decoder, keyPrefix = "") {
3806
3863
  const contents = (showContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
3807
3864
  const changeTree = ref[$changes];
3808
- const refId = (decoder) ? decoder.root.refIds.get(ref) : changeTree.refId;
3865
+ const refId = ref[$refId];
3809
3866
  const root = (decoder) ? decoder.root : changeTree.root;
3810
3867
  // log reference count if > 1
3811
3868
  const refCount = (root?.refCount?.[refId] > 1)
@@ -3828,7 +3885,7 @@
3828
3885
  let current = ref[$changes].root[changeSet].next;
3829
3886
  while (current) {
3830
3887
  if (current.changeTree) {
3831
- encodeOrder.push(current.changeTree.refId);
3888
+ encodeOrder.push(current.changeTree.ref[$refId]);
3832
3889
  }
3833
3890
  current = current.next;
3834
3891
  }
@@ -3849,7 +3906,7 @@
3849
3906
  const changeTree = instance[$changes];
3850
3907
  const changeSet = (isEncodeAll) ? changeTree.allChanges : changeTree.changes;
3851
3908
  const changeSetName = (isEncodeAll) ? "allChanges" : "changes";
3852
- let output = `${instance.constructor.name} (${changeTree.refId}) -> .${changeSetName}:\n`;
3909
+ let output = `${instance.constructor.name} (${instance[$refId]}) -> .${changeSetName}:\n`;
3853
3910
  function dumpChangeSet(changeSet) {
3854
3911
  changeSet.operations
3855
3912
  .filter(op => op)
@@ -3863,14 +3920,14 @@
3863
3920
  if (!isEncodeAll &&
3864
3921
  changeTree.filteredChanges &&
3865
3922
  (changeTree.filteredChanges.operations).filter(op => op).length > 0) {
3866
- output += `${instance.constructor.name} (${changeTree.refId}) -> .filteredChanges:\n`;
3923
+ output += `${instance.constructor.name} (${instance[$refId]}) -> .filteredChanges:\n`;
3867
3924
  dumpChangeSet(changeTree.filteredChanges);
3868
3925
  }
3869
3926
  // display filtered changes
3870
3927
  if (isEncodeAll &&
3871
3928
  changeTree.allFilteredChanges &&
3872
3929
  (changeTree.allFilteredChanges.operations).filter(op => op).length > 0) {
3873
- output += `${instance.constructor.name} (${changeTree.refId}) -> .allFilteredChanges:\n`;
3930
+ output += `${instance.constructor.name} (${instance[$refId]}) -> .allFilteredChanges:\n`;
3874
3931
  dumpChangeSet(changeTree.allFilteredChanges);
3875
3932
  }
3876
3933
  return output;
@@ -3905,13 +3962,13 @@
3905
3962
  }
3906
3963
  }
3907
3964
  if (includeChangeTree) {
3908
- instanceRefIds.push(changeTree.refId);
3965
+ instanceRefIds.push(changeTree.ref[$refId]);
3909
3966
  totalOperations += Object.keys(changes).length;
3910
3967
  changeTrees.set(changeTree, parentChangeTrees.reverse());
3911
3968
  }
3912
3969
  }
3913
3970
  output += "---\n";
3914
- output += `root refId: ${rootChangeTree.refId}\n`;
3971
+ output += `root refId: ${rootChangeTree.ref[$refId]}\n`;
3915
3972
  output += `Total instances: ${instanceRefIds.length} (refIds: ${instanceRefIds.join(", ")})\n`;
3916
3973
  output += `Total changes: ${totalOperations}\n`;
3917
3974
  output += "---\n";
@@ -3920,7 +3977,7 @@
3920
3977
  for (const [changeTree, parentChangeTrees] of changeTrees.entries()) {
3921
3978
  parentChangeTrees.forEach((parentChangeTree, level) => {
3922
3979
  if (!visitedParents.has(parentChangeTree)) {
3923
- output += `${getIndent(level)}${parentChangeTree.ref.constructor.name} (refId: ${parentChangeTree.refId})\n`;
3980
+ output += `${getIndent(level)}${parentChangeTree.ref.constructor.name} (refId: ${parentChangeTree.ref[$refId]})\n`;
3924
3981
  visitedParents.add(parentChangeTree);
3925
3982
  }
3926
3983
  });
@@ -3928,7 +3985,7 @@
3928
3985
  const level = parentChangeTrees.length;
3929
3986
  const indent = getIndent(level);
3930
3987
  const parentIndex = (level > 0) ? `(${changeTree.parentIndex}) ` : "";
3931
- 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`;
3932
3989
  for (const index in changes) {
3933
3990
  const operation = changes[index];
3934
3991
  output += `${getIndent(level + 1)}${exports.OPERATION[operation]}: ${index}\n`;
@@ -3938,61 +3995,35 @@
3938
3995
  }
3939
3996
  }
3940
3997
 
3941
- /******************************************************************************
3942
- Copyright (c) Microsoft Corporation.
3943
-
3944
- Permission to use, copy, modify, and/or distribute this software for any
3945
- purpose with or without fee is hereby granted.
3946
-
3947
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
3948
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
3949
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
3950
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
3951
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
3952
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
3953
- PERFORMANCE OF THIS SOFTWARE.
3954
- ***************************************************************************** */
3955
- /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
3956
-
3957
-
3958
- function __decorate(decorators, target, key, desc) {
3959
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3960
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
3961
- 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;
3962
- return c > 3 && r && Object.defineProperty(target, key, r), r;
3963
- }
3964
-
3965
- typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
3966
- var e = new Error(message);
3967
- return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
3968
- };
3969
-
3970
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
3971
4009
  constructor(types) {
3972
4010
  this.types = types;
3973
- this.nextUniqueId = 0;
3974
- this.refCount = {};
3975
- this.changeTrees = {};
3976
- // all changes
3977
- this.allChanges = createChangeTreeList();
3978
- this.allFilteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
3979
- // pending changes to be encoded
3980
- this.changes = createChangeTreeList();
3981
- this.filteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
3982
4011
  }
3983
4012
  getNextUniqueId() {
3984
4013
  return this.nextUniqueId++;
3985
4014
  }
3986
4015
  add(changeTree) {
3987
- // Assign unique `refId` to changeTree if it doesn't have one yet.
3988
- if (changeTree.refId === undefined) {
3989
- 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();
3990
4020
  }
3991
- const isNewChangeTree = (this.changeTrees[changeTree.refId] === undefined);
4021
+ const refId = ref[$refId];
4022
+ const isNewChangeTree = (this.changeTrees[refId] === undefined);
3992
4023
  if (isNewChangeTree) {
3993
- this.changeTrees[changeTree.refId] = changeTree;
4024
+ this.changeTrees[refId] = changeTree;
3994
4025
  }
3995
- const previousRefCount = this.refCount[changeTree.refId];
4026
+ const previousRefCount = this.refCount[refId];
3996
4027
  if (previousRefCount === 0) {
3997
4028
  //
3998
4029
  // When a ChangeTree is re-added, it means that it was previously removed.
@@ -4005,30 +4036,31 @@
4005
4036
  setOperationAtIndex(changeTree.changes, len);
4006
4037
  }
4007
4038
  }
4008
- this.refCount[changeTree.refId] = (previousRefCount || 0) + 1;
4009
- // 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 });
4010
4041
  return isNewChangeTree;
4011
4042
  }
4012
4043
  remove(changeTree) {
4013
- const refCount = (this.refCount[changeTree.refId]) - 1;
4014
- // 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 });
4015
4047
  if (refCount <= 0) {
4016
4048
  //
4017
4049
  // Only remove "root" reference if it's the last reference
4018
4050
  //
4019
4051
  changeTree.root = undefined;
4020
- delete this.changeTrees[changeTree.refId];
4052
+ delete this.changeTrees[refId];
4021
4053
  this.removeChangeFromChangeSet("allChanges", changeTree);
4022
4054
  this.removeChangeFromChangeSet("changes", changeTree);
4023
4055
  if (changeTree.filteredChanges) {
4024
4056
  this.removeChangeFromChangeSet("allFilteredChanges", changeTree);
4025
4057
  this.removeChangeFromChangeSet("filteredChanges", changeTree);
4026
4058
  }
4027
- this.refCount[changeTree.refId] = 0;
4059
+ this.refCount[refId] = 0;
4028
4060
  changeTree.forEachChild((child, _) => {
4029
4061
  if (child.removeParent(changeTree.ref)) {
4030
4062
  if ((child.parentChain === undefined || // no parent, remove it
4031
- (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
4032
4064
  )) {
4033
4065
  this.remove(child);
4034
4066
  }
@@ -4040,7 +4072,7 @@
4040
4072
  });
4041
4073
  }
4042
4074
  else {
4043
- this.refCount[changeTree.refId] = refCount;
4075
+ this.refCount[refId] = refCount;
4044
4076
  //
4045
4077
  // When losing a reference to an instance, it is best to move the
4046
4078
  // ChangeTree next to its parent in the encoding queue.
@@ -4190,10 +4222,19 @@
4190
4222
  }
4191
4223
  }
4192
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
+ }
4193
4231
  class Encoder {
4194
- 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;
4195
4237
  constructor(state) {
4196
- this.sharedBuffer = Buffer.allocUnsafe(Encoder.BUFFER_SIZE);
4197
4238
  //
4198
4239
  // Use .cache() here to avoid re-creating a new context for every new room instance.
4199
4240
  //
@@ -4221,7 +4262,7 @@
4221
4262
  const changeTree = current.changeTree;
4222
4263
  if (hasView) {
4223
4264
  if (!view.isChangeTreeVisible(changeTree)) {
4224
- // 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() });
4225
4266
  view.invisible.add(changeTree);
4226
4267
  continue; // skip this change tree
4227
4268
  }
@@ -4242,7 +4283,7 @@
4242
4283
  // (unless it "hasView", which will need to revisit the root)
4243
4284
  if (hasView || it.offset > initialOffset || changeTree !== rootChangeTree) {
4244
4285
  buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
4245
- encode.number(buffer, changeTree.refId, it);
4286
+ encode.number(buffer, ref[$refId], it);
4246
4287
  }
4247
4288
  for (let j = 0; j < numChanges; j++) {
4248
4289
  const fieldIndex = changeSet.operations[j];
@@ -4271,10 +4312,9 @@
4271
4312
  }
4272
4313
  }
4273
4314
  if (it.offset > buffer.byteLength) {
4274
- // we can assume that n + 1 poolSize will suffice given that we are likely done with encoding at this point
4275
- // multiples of poolSize are faster to allocate than arbitrary sizes
4276
- // if we are on an older platform that doesn't implement pooling use 8kb as poolSize (that's the default for node)
4277
- 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;
4278
4318
  console.warn(`@colyseus/schema buffer overflow. Encoded state is higher than default BUFFER_SIZE. Use the following to increase default BUFFER_SIZE:
4279
4319
 
4280
4320
  import { Encoder } from "@colyseus/schema";
@@ -4284,7 +4324,9 @@
4284
4324
  // resize buffer and re-encode (TODO: can we avoid re-encoding here?)
4285
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
4286
4326
  //
4287
- 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;
4288
4330
  // assign resized buffer to local sharedBuffer
4289
4331
  if (buffer === this.sharedBuffer) {
4290
4332
  this.sharedBuffer = buffer;
@@ -4302,10 +4344,7 @@
4302
4344
  const viewOffset = it.offset;
4303
4345
  // try to encode "filtered" changes
4304
4346
  this.encode(it, view, bytes, "allFilteredChanges", true, viewOffset);
4305
- return Buffer.concat([
4306
- bytes.subarray(0, sharedOffset),
4307
- bytes.subarray(viewOffset, it.offset)
4308
- ]);
4347
+ return concatBytes(bytes.subarray(0, sharedOffset), bytes.subarray(viewOffset, it.offset));
4309
4348
  }
4310
4349
  encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
4311
4350
  const viewOffset = it.offset;
@@ -4329,7 +4368,7 @@
4329
4368
  const encoder = ctor[$encoder];
4330
4369
  const metadata = ctor[Symbol.metadata];
4331
4370
  bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
4332
- encode.number(bytes, changeTree.refId, it);
4371
+ encode.number(bytes, ref[$refId], it);
4333
4372
  for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
4334
4373
  const index = Number(keys[i]);
4335
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")
@@ -4348,10 +4387,7 @@
4348
4387
  view.changes.clear();
4349
4388
  // try to encode "filtered" changes
4350
4389
  this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
4351
- return Buffer.concat([
4352
- bytes.subarray(0, sharedOffset),
4353
- bytes.subarray(viewOffset, it.offset)
4354
- ]);
4390
+ return concatBytes(bytes.subarray(0, sharedOffset), bytes.subarray(viewOffset, it.offset));
4355
4391
  }
4356
4392
  discardChanges() {
4357
4393
  // discard shared changes
@@ -4407,25 +4443,22 @@
4407
4443
  }
4408
4444
  }
4409
4445
  class ReferenceTracker {
4410
- constructor() {
4411
- //
4412
- // Relation of refId => Schema structure
4413
- // For direct access of structures during decoding time.
4414
- //
4415
- this.refs = new Map();
4416
- this.refIds = new WeakMap();
4417
- this.refCount = {};
4418
- this.deletedRefs = new Set();
4419
- this.callbacks = {};
4420
- this.nextUniqueId = 0;
4421
- }
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;
4422
4455
  getNextUniqueId() {
4423
4456
  return this.nextUniqueId++;
4424
4457
  }
4425
4458
  // for decoding
4426
4459
  addRef(refId, ref, incrementCount = true) {
4427
4460
  this.refs.set(refId, ref);
4428
- this.refIds.set(ref, refId);
4461
+ ref[$refId] = refId;
4429
4462
  if (incrementCount) {
4430
4463
  this.refCount[refId] = (this.refCount[refId] || 0) + 1;
4431
4464
  }
@@ -4482,9 +4515,12 @@
4482
4515
  const metadata = ref.constructor[Symbol.metadata];
4483
4516
  for (const index in metadata) {
4484
4517
  const field = metadata[index].name;
4485
- const childRefId = typeof (ref[field]) === "object" && this.refIds.get(ref[field]);
4486
- if (childRefId && !this.deletedRefs.has(childRefId)) {
4487
- 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
+ }
4488
4524
  }
4489
4525
  }
4490
4526
  }
@@ -4492,8 +4528,8 @@
4492
4528
  if (typeof (ref[$childType]) === "function") {
4493
4529
  Array.from(ref.values())
4494
4530
  .forEach((child) => {
4495
- const childRefId = this.refIds.get(child);
4496
- if (!this.deletedRefs.has(childRefId)) {
4531
+ const childRefId = child[$refId];
4532
+ if (childRefId !== undefined && !this.deletedRefs.has(childRefId)) {
4497
4533
  this.removeRef(childRefId);
4498
4534
  }
4499
4535
  });
@@ -4531,8 +4567,12 @@
4531
4567
  }
4532
4568
 
4533
4569
  class Decoder {
4570
+ context;
4571
+ state;
4572
+ root;
4573
+ currentRefId = 0;
4574
+ triggerChanges;
4534
4575
  constructor(root, context) {
4535
- this.currentRefId = 0;
4536
4576
  this.setState(root);
4537
4577
  this.context = context || new TypeContext(root.constructor);
4538
4578
  // console.log(">>>>>>>>>>>>>>>> Decoder types");
@@ -4621,7 +4661,7 @@
4621
4661
  }
4622
4662
  removeChildRefs(ref, allChanges) {
4623
4663
  const needRemoveRef = typeof (ref[$childType]) !== "string";
4624
- const refId = this.root.refIds.get(ref);
4664
+ const refId = ref[$refId];
4625
4665
  ref.forEach((value, key) => {
4626
4666
  allChanges.push({
4627
4667
  ref: ref,
@@ -4632,7 +4672,7 @@
4632
4672
  previousValue: value
4633
4673
  });
4634
4674
  if (needRemoveRef) {
4635
- this.root.removeRef(this.root.refIds.get(value));
4675
+ this.root.removeRef(value[$refId]);
4636
4676
  }
4637
4677
  });
4638
4678
  }
@@ -4641,217 +4681,182 @@
4641
4681
  /**
4642
4682
  * Reflection
4643
4683
  */
4644
- class ReflectionField extends Schema {
4645
- }
4646
- __decorate([
4647
- type("string")
4648
- ], ReflectionField.prototype, "name", void 0);
4649
- __decorate([
4650
- type("string")
4651
- ], ReflectionField.prototype, "type", void 0);
4652
- __decorate([
4653
- type("number")
4654
- ], ReflectionField.prototype, "referencedType", void 0);
4655
- class ReflectionType extends Schema {
4656
- constructor() {
4657
- super(...arguments);
4658
- this.fields = new ArraySchema();
4659
- }
4660
- }
4661
- __decorate([
4662
- type("number")
4663
- ], ReflectionType.prototype, "id", void 0);
4664
- __decorate([
4665
- type("number")
4666
- ], ReflectionType.prototype, "extendsId", void 0);
4667
- __decorate([
4668
- type([ReflectionField])
4669
- ], ReflectionType.prototype, "fields", void 0);
4670
- class Reflection extends Schema {
4671
- constructor() {
4672
- super(...arguments);
4673
- this.types = new ArraySchema();
4674
- }
4675
- /**
4676
- * Encodes the TypeContext of an Encoder into a buffer.
4677
- *
4678
- * @param encoder Encoder instance
4679
- * @param it
4680
- * @returns
4681
- */
4682
- static encode(encoder, it = { offset: 0 }) {
4683
- const context = encoder.context;
4684
- const reflection = new Reflection();
4685
- const reflectionEncoder = new Encoder(reflection);
4686
- // rootType is usually the first schema passed to the Encoder
4687
- // (unless it inherits from another schema)
4688
- const rootType = context.schemas.get(encoder.state.constructor);
4689
- if (rootType > 0) {
4690
- reflection.rootType = rootType;
4691
- }
4692
- const includedTypeIds = new Set();
4693
- const pendingReflectionTypes = {};
4694
- // add type to reflection in a way that respects inheritance
4695
- // (parent types should be added before their children)
4696
- const addType = (type) => {
4697
- if (type.extendsId === undefined || includedTypeIds.has(type.extendsId)) {
4698
- includedTypeIds.add(type.id);
4699
- reflection.types.push(type);
4700
- const deps = pendingReflectionTypes[type.id];
4701
- if (deps !== undefined) {
4702
- delete pendingReflectionTypes[type.id];
4703
- deps.forEach((childType) => addType(childType));
4704
- }
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));
4705
4720
  }
4706
- else {
4707
- if (pendingReflectionTypes[type.extendsId] === undefined) {
4708
- pendingReflectionTypes[type.extendsId] = [];
4709
- }
4710
- pendingReflectionTypes[type.extendsId].push(type);
4711
- }
4712
- };
4713
- context.schemas.forEach((typeid, klass) => {
4714
- const type = new ReflectionType();
4715
- type.id = Number(typeid);
4716
- // support inheritance
4717
- const inheritFrom = Object.getPrototypeOf(klass);
4718
- if (inheritFrom !== Schema) {
4719
- type.extendsId = context.schemas.get(inheritFrom);
4721
+ }
4722
+ else {
4723
+ if (pendingReflectionTypes[type.extendsId] === undefined) {
4724
+ pendingReflectionTypes[type.extendsId] = [];
4720
4725
  }
4721
- const metadata = klass[Symbol.metadata];
4722
- //
4723
- // FIXME: this is a workaround for inherited types without additional fields
4724
- // if metadata is the same reference as the parent class - it means the class has no own metadata
4725
- //
4726
- if (metadata !== inheritFrom[Symbol.metadata]) {
4727
- for (const fieldIndex in metadata) {
4728
- const index = Number(fieldIndex);
4729
- const fieldName = metadata[index].name;
4730
- // skip fields from parent classes
4731
- if (!Object.prototype.hasOwnProperty.call(metadata, fieldName)) {
4732
- continue;
4733
- }
4734
- const reflectionField = new ReflectionField();
4735
- reflectionField.name = fieldName;
4736
- let fieldType;
4737
- const field = metadata[index];
4738
- if (typeof (field.type) === "string") {
4739
- 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;
4740
4765
  }
4741
4766
  else {
4742
- let childTypeSchema;
4743
- //
4744
- // TODO: refactor below.
4745
- //
4746
- if (Schema.is(field.type)) {
4747
- fieldType = "ref";
4748
- childTypeSchema = field.type;
4767
+ fieldType = Object.keys(field.type)[0];
4768
+ if (typeof (field.type[fieldType]) === "string") {
4769
+ fieldType += ":" + field.type[fieldType]; // array:string
4749
4770
  }
4750
4771
  else {
4751
- fieldType = Object.keys(field.type)[0];
4752
- if (typeof (field.type[fieldType]) === "string") {
4753
- fieldType += ":" + field.type[fieldType]; // array:string
4754
- }
4755
- else {
4756
- childTypeSchema = field.type[fieldType];
4757
- }
4772
+ childTypeSchema = field.type[fieldType];
4758
4773
  }
4759
- reflectionField.referencedType = (childTypeSchema)
4760
- ? context.getTypeId(childTypeSchema)
4761
- : -1;
4762
4774
  }
4763
- reflectionField.type = fieldType;
4764
- type.fields.push(reflectionField);
4775
+ reflectionField.referencedType = (childTypeSchema)
4776
+ ? context.getTypeId(childTypeSchema)
4777
+ : -1;
4765
4778
  }
4779
+ reflectionField.type = fieldType;
4780
+ type.fields.push(reflectionField);
4766
4781
  }
4767
- addType(type);
4768
- });
4769
- // in case there are types that were not added due to inheritance
4770
- for (const typeid in pendingReflectionTypes) {
4771
- pendingReflectionTypes[typeid].forEach((type) => reflection.types.push(type));
4772
4782
  }
4773
- const buf = reflectionEncoder.encodeAll(it);
4774
- return buf.slice(0, it.offset);
4775
- // 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));
4776
4788
  }
4777
- /**
4778
- * Decodes the TypeContext from a buffer into a Decoder instance.
4779
- *
4780
- * @param bytes Reflection.encode() output
4781
- * @param it
4782
- * @returns Decoder instance
4783
- */
4784
- static decode(bytes, it) {
4785
- const reflection = new Reflection();
4786
- const reflectionDecoder = new Decoder(reflection);
4787
- reflectionDecoder.decode(bytes, it);
4788
- const typeContext = new TypeContext();
4789
- // 1st pass, initialize metadata + inheritance
4790
- reflection.types.forEach((reflectionType) => {
4791
- const parentClass = typeContext.get(reflectionType.extendsId) ?? Schema;
4792
- const schema = class _ extends parentClass {
4793
- };
4794
- // register for inheritance support
4795
- TypeContext.register(schema);
4796
- // // for inheritance support
4797
- // Metadata.initialize(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);
4815
- }
4816
- else {
4817
- Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType });
4818
- }
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);
4819
4821
  }
4820
4822
  else {
4821
- Metadata.addField(metadata, fieldIndex, field.name, field.type);
4823
+ Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType });
4822
4824
  }
4823
- });
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
- });
4825
+ }
4826
+ else {
4827
+ Metadata.addField(metadata, fieldIndex, field.name, field.type);
4828
+ }
4843
4829
  });
4844
- const state = new (typeContext.get(reflection.rootType || 0))();
4845
- return new Decoder(state, typeContext);
4846
- }
4847
- }
4848
- __decorate([
4849
- type([ReflectionType])
4850
- ], Reflection.prototype, "types", void 0);
4851
- __decorate([
4852
- type("number")
4853
- ], 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
+ };
4854
4853
 
4854
+ /**
4855
+ * Legacy callback system
4856
+ *
4857
+ * @param decoder
4858
+ * @returns
4859
+ */
4855
4860
  function getDecoderStateCallbacks(decoder) {
4856
4861
  const $root = decoder.root;
4857
4862
  const callbacks = $root.callbacks;
@@ -4872,7 +4877,7 @@
4872
4877
  //
4873
4878
  if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE &&
4874
4879
  change.previousValue instanceof Schema) {
4875
- const deleteCallbacks = callbacks[$root.refIds.get(change.previousValue)]?.[exports.OPERATION.DELETE];
4880
+ const deleteCallbacks = callbacks[change.previousValue[$refId]]?.[exports.OPERATION.DELETE];
4876
4881
  for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
4877
4882
  deleteCallbacks[i]();
4878
4883
  }
@@ -4961,7 +4966,7 @@
4961
4966
  ) {
4962
4967
  callback(context.instance[prop], undefined);
4963
4968
  }
4964
- return $root.addCallback($root.refIds.get(ref), prop, callback);
4969
+ return $root.addCallback(ref[$refId], prop, callback);
4965
4970
  };
4966
4971
  /**
4967
4972
  * Schema instances
@@ -4981,7 +4986,7 @@
4981
4986
  }
4982
4987
  },
4983
4988
  onChange: function onChange(callback) {
4984
- return $root.addCallback($root.refIds.get(context.instance), exports.OPERATION.REPLACE, callback);
4989
+ return $root.addCallback(context.instance[$refId], exports.OPERATION.REPLACE, callback);
4985
4990
  },
4986
4991
  //
4987
4992
  // TODO: refactor `bindTo()` implementation.
@@ -4991,7 +4996,7 @@
4991
4996
  if (!properties) {
4992
4997
  properties = Object.keys(metadata).map((index) => metadata[index].name);
4993
4998
  }
4994
- return $root.addCallback($root.refIds.get(context.instance), exports.OPERATION.REPLACE, () => {
4999
+ return $root.addCallback(context.instance[$refId], exports.OPERATION.REPLACE, () => {
4995
5000
  properties.forEach((prop) => targetObject[prop] = context.instance[prop]);
4996
5001
  });
4997
5002
  }
@@ -5010,13 +5015,13 @@
5010
5015
  unbind?.();
5011
5016
  }, false);
5012
5017
  // has existing value
5013
- if ($root.refIds.get(instance) !== undefined) {
5018
+ if (instance?.[$refId] !== undefined) {
5014
5019
  callback(instance, true);
5015
5020
  }
5016
5021
  });
5017
5022
  return getProxy(metadataField.type, {
5018
5023
  // make sure refId is available, otherwise need to wait for the instance to be available.
5019
- instance: ($root.refIds.get(instance) && instance),
5024
+ instance: (instance?.[$refId] !== undefined && instance),
5020
5025
  parentInstance: context.instance,
5021
5026
  onInstanceAvailable,
5022
5027
  });
@@ -5040,7 +5045,7 @@
5040
5045
  if (immediate) {
5041
5046
  ref.forEach((v, k) => callback(v, k));
5042
5047
  }
5043
- return $root.addCallback($root.refIds.get(ref), exports.OPERATION.ADD, (value, key) => {
5048
+ return $root.addCallback(ref[$refId], exports.OPERATION.ADD, (value, key) => {
5044
5049
  onAddCalls.set(callback, true);
5045
5050
  currentOnAddCallback = callback;
5046
5051
  callback(value, key);
@@ -5049,10 +5054,10 @@
5049
5054
  });
5050
5055
  };
5051
5056
  const onRemove = function (ref, callback) {
5052
- return $root.addCallback($root.refIds.get(ref), exports.OPERATION.DELETE, callback);
5057
+ return $root.addCallback(ref[$refId], exports.OPERATION.DELETE, callback);
5053
5058
  };
5054
5059
  const onChange = function (ref, callback) {
5055
- return $root.addCallback($root.refIds.get(ref), exports.OPERATION.REPLACE, callback);
5060
+ return $root.addCallback(ref[$refId], exports.OPERATION.REPLACE, callback);
5056
5061
  };
5057
5062
  return new Proxy({
5058
5063
  onAdd: function (callback, immediate = true) {
@@ -5121,22 +5126,360 @@
5121
5126
  decoder.triggerChanges = callback;
5122
5127
  }
5123
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
+
5124
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();
5125
5481
  constructor(iterable = false) {
5126
5482
  this.iterable = iterable;
5127
- /**
5128
- * List of ChangeTree's that are visible to this view
5129
- */
5130
- this.visible = new WeakSet();
5131
- /**
5132
- * List of ChangeTree's that are invisible to this view
5133
- */
5134
- this.invisible = new WeakSet();
5135
- /**
5136
- * Manual "ADD" operations for changes per ChangeTree, specific to this view.
5137
- * (This is used to force encoding a property, even if it was not changed)
5138
- */
5139
- this.changes = new Map();
5140
5483
  if (iterable) {
5141
5484
  this.items = [];
5142
5485
  }
@@ -5150,7 +5493,7 @@
5150
5493
  return false;
5151
5494
  }
5152
5495
  else if (!parentChangeTree &&
5153
- changeTree.refId !== 0 // allow root object
5496
+ obj[$refId] !== 0 // allow root object
5154
5497
  ) {
5155
5498
  /**
5156
5499
  * TODO: can we avoid this?
@@ -5174,11 +5517,11 @@
5174
5517
  if (checkIncludeParent && parentChangeTree) {
5175
5518
  this.addParentOf(changeTree, tag);
5176
5519
  }
5177
- let changes = this.changes.get(changeTree.refId);
5520
+ let changes = this.changes.get(obj[$refId]);
5178
5521
  if (changes === undefined) {
5179
5522
  changes = {};
5180
5523
  // FIXME / OPTIMIZE: do not add if no changes are needed
5181
- this.changes.set(changeTree.refId, changes);
5524
+ this.changes.set(obj[$refId], changes);
5182
5525
  }
5183
5526
  let isChildAdded = false;
5184
5527
  //
@@ -5258,10 +5601,10 @@
5258
5601
  }
5259
5602
  // add parent's tag properties
5260
5603
  if (changeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {
5261
- let changes = this.changes.get(changeTree.refId);
5604
+ let changes = this.changes.get(changeTree.ref[$refId]);
5262
5605
  if (changes === undefined) {
5263
5606
  changes = {};
5264
- this.changes.set(changeTree.refId, changes);
5607
+ this.changes.set(changeTree.ref[$refId], changes);
5265
5608
  }
5266
5609
  if (!this.tags) {
5267
5610
  this.tags = new WeakMap();
@@ -5293,27 +5636,28 @@
5293
5636
  }
5294
5637
  const ref = changeTree.ref;
5295
5638
  const metadata = ref.constructor[Symbol.metadata]; // ArraySchema/MapSchema do not have metadata
5296
- let changes = this.changes.get(changeTree.refId);
5639
+ const refId = ref[$refId];
5640
+ let changes = this.changes.get(refId);
5297
5641
  if (changes === undefined) {
5298
5642
  changes = {};
5299
- this.changes.set(changeTree.refId, changes);
5643
+ this.changes.set(refId, changes);
5300
5644
  }
5301
5645
  if (tag === DEFAULT_VIEW_TAG) {
5302
5646
  // parent is collection (Map/Array)
5303
5647
  const parent = changeTree.parent;
5304
5648
  if (parent && !Metadata.isValidInstance(parent) && changeTree.isFiltered) {
5305
- const parentChangeTree = parent[$changes];
5306
- let changes = this.changes.get(parentChangeTree.refId);
5649
+ const parentRefId = parent[$refId];
5650
+ let changes = this.changes.get(parentRefId);
5307
5651
  if (changes === undefined) {
5308
5652
  changes = {};
5309
- this.changes.set(parentChangeTree.refId, changes);
5653
+ this.changes.set(parentRefId, changes);
5310
5654
  }
5311
5655
  else if (changes[changeTree.parentIndex] === exports.OPERATION.ADD) {
5312
5656
  //
5313
5657
  // SAME PATCH ADD + REMOVE:
5314
5658
  // The 'changes' of deleted structure should be ignored.
5315
5659
  //
5316
- this.changes.delete(changeTree.refId);
5660
+ this.changes.delete(refId);
5317
5661
  }
5318
5662
  // DELETE / DELETE BY REF ID
5319
5663
  changes[changeTree.parentIndex] = exports.OPERATION.DELETE;
@@ -5373,7 +5717,7 @@
5373
5717
  if (!isVisible && changeTree.isVisibilitySharedWithParent) {
5374
5718
  // console.log("CHECK AGAINST PARENT...", {
5375
5719
  // ref: changeTree.ref.constructor.name,
5376
- // refId: changeTree.refId,
5720
+ // refId: changeTree.ref[$refId],
5377
5721
  // parent: changeTree.parent.constructor.name,
5378
5722
  // });
5379
5723
  if (this.visible.has(changeTree.parent[$changes])) {
@@ -5403,8 +5747,10 @@
5403
5747
  exports.$encoder = $encoder;
5404
5748
  exports.$filter = $filter;
5405
5749
  exports.$getByIndex = $getByIndex;
5750
+ exports.$refId = $refId;
5406
5751
  exports.$track = $track;
5407
5752
  exports.ArraySchema = ArraySchema;
5753
+ exports.Callbacks = Callbacks;
5408
5754
  exports.ChangeTree = ChangeTree;
5409
5755
  exports.CollectionSchema = CollectionSchema;
5410
5756
  exports.Decoder = Decoder;
@@ -5416,6 +5762,7 @@
5416
5762
  exports.ReflectionType = ReflectionType;
5417
5763
  exports.Schema = Schema;
5418
5764
  exports.SetSchema = SetSchema;
5765
+ exports.StateCallbackStrategy = StateCallbackStrategy;
5419
5766
  exports.StateView = StateView;
5420
5767
  exports.TypeContext = TypeContext;
5421
5768
  exports.decode = decode;