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