@colyseus/schema 3.0.0-alpha.2 → 3.0.0-alpha.22

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 (99) hide show
  1. package/README.md +131 -61
  2. package/build/cjs/index.js +472 -150
  3. package/build/cjs/index.js.map +1 -1
  4. package/build/esm/index.mjs +471 -149
  5. package/build/esm/index.mjs.map +1 -1
  6. package/build/umd/index.js +472 -150
  7. package/lib/Metadata.d.ts +2 -0
  8. package/lib/Metadata.js +39 -0
  9. package/lib/Metadata.js.map +1 -1
  10. package/lib/Reflection.d.ts +1 -2
  11. package/lib/Reflection.js +28 -22
  12. package/lib/Reflection.js.map +1 -1
  13. package/lib/Schema.d.ts +1 -1
  14. package/lib/annotations.js +12 -10
  15. package/lib/annotations.js.map +1 -1
  16. package/lib/bench_encode.d.ts +1 -0
  17. package/lib/bench_encode.js +93 -0
  18. package/lib/bench_encode.js.map +1 -0
  19. package/lib/codegen/api.js +1 -2
  20. package/lib/codegen/api.js.map +1 -1
  21. package/lib/codegen/languages/cpp.js +1 -2
  22. package/lib/codegen/languages/cpp.js.map +1 -1
  23. package/lib/codegen/languages/csharp.js +1 -2
  24. package/lib/codegen/languages/csharp.js.map +1 -1
  25. package/lib/codegen/languages/haxe.js +1 -2
  26. package/lib/codegen/languages/haxe.js.map +1 -1
  27. package/lib/codegen/languages/java.js +1 -2
  28. package/lib/codegen/languages/java.js.map +1 -1
  29. package/lib/codegen/languages/js.js +1 -2
  30. package/lib/codegen/languages/js.js.map +1 -1
  31. package/lib/codegen/languages/lua.js +1 -2
  32. package/lib/codegen/languages/lua.js.map +1 -1
  33. package/lib/codegen/languages/ts.js +1 -2
  34. package/lib/codegen/languages/ts.js.map +1 -1
  35. package/lib/codegen/parser.js +2 -3
  36. package/lib/codegen/parser.js.map +1 -1
  37. package/lib/codegen/types.js +3 -3
  38. package/lib/codegen/types.js.map +1 -1
  39. package/lib/decoder/DecodeOperation.d.ts +0 -1
  40. package/lib/decoder/DecodeOperation.js +22 -7
  41. package/lib/decoder/DecodeOperation.js.map +1 -1
  42. package/lib/decoder/Decoder.d.ts +1 -2
  43. package/lib/decoder/Decoder.js +4 -4
  44. package/lib/decoder/Decoder.js.map +1 -1
  45. package/lib/decoder/strategy/RawChanges.js +1 -2
  46. package/lib/decoder/strategy/RawChanges.js.map +1 -1
  47. package/lib/decoder/strategy/StateCallbacks.d.ts +36 -7
  48. package/lib/decoder/strategy/StateCallbacks.js +39 -46
  49. package/lib/decoder/strategy/StateCallbacks.js.map +1 -1
  50. package/lib/encoder/ChangeTree.js +2 -5
  51. package/lib/encoder/ChangeTree.js.map +1 -1
  52. package/lib/encoder/EncodeOperation.d.ts +0 -1
  53. package/lib/encoder/EncodeOperation.js +29 -13
  54. package/lib/encoder/EncodeOperation.js.map +1 -1
  55. package/lib/encoder/Encoder.d.ts +5 -4
  56. package/lib/encoder/Encoder.js +56 -38
  57. package/lib/encoder/Encoder.js.map +1 -1
  58. package/lib/encoder/StateView.js +11 -6
  59. package/lib/encoder/StateView.js.map +1 -1
  60. package/lib/encoding/assert.js +3 -3
  61. package/lib/encoding/assert.js.map +1 -1
  62. package/lib/encoding/decode.d.ts +21 -19
  63. package/lib/encoding/decode.js +24 -25
  64. package/lib/encoding/decode.js.map +1 -1
  65. package/lib/encoding/encode.d.ts +2 -2
  66. package/lib/encoding/encode.js +40 -39
  67. package/lib/encoding/encode.js.map +1 -1
  68. package/lib/encoding/spec.d.ts +2 -1
  69. package/lib/encoding/spec.js +1 -0
  70. package/lib/encoding/spec.js.map +1 -1
  71. package/lib/index.d.ts +3 -0
  72. package/lib/index.js +5 -1
  73. package/lib/index.js.map +1 -1
  74. package/lib/types/custom/ArraySchema.d.ts +2 -2
  75. package/lib/types/custom/ArraySchema.js +0 -8
  76. package/lib/types/custom/ArraySchema.js.map +1 -1
  77. package/lib/types/registry.js +3 -4
  78. package/lib/types/registry.js.map +1 -1
  79. package/lib/types/utils.js +1 -2
  80. package/lib/types/utils.js.map +1 -1
  81. package/lib/utils.js +3 -4
  82. package/lib/utils.js.map +1 -1
  83. package/package.json +5 -5
  84. package/src/Metadata.ts +47 -0
  85. package/src/Reflection.ts +31 -22
  86. package/src/annotations.ts +6 -2
  87. package/src/bench_encode.ts +66 -0
  88. package/src/decoder/DecodeOperation.ts +26 -6
  89. package/src/decoder/Decoder.ts +5 -5
  90. package/src/decoder/strategy/StateCallbacks.ts +93 -53
  91. package/src/encoder/ChangeTree.ts +2 -4
  92. package/src/encoder/EncodeOperation.ts +29 -12
  93. package/src/encoder/Encoder.ts +65 -41
  94. package/src/encoder/StateView.ts +10 -7
  95. package/src/encoding/decode.ts +24 -25
  96. package/src/encoding/encode.ts +25 -22
  97. package/src/encoding/spec.ts +1 -0
  98. package/src/index.ts +5 -0
  99. package/src/types/custom/ArraySchema.ts +2 -1
@@ -23,6 +23,7 @@ var OPERATION;
23
23
  OPERATION[OPERATION["REVERSE"] = 15] = "REVERSE";
24
24
  OPERATION[OPERATION["MOVE"] = 32] = "MOVE";
25
25
  OPERATION[OPERATION["DELETE_BY_REFID"] = 33] = "DELETE_BY_REFID";
26
+ OPERATION[OPERATION["ADD_BY_REFID"] = 129] = "ADD_BY_REFID";
26
27
  })(OPERATION || (OPERATION = {}));
27
28
 
28
29
  Symbol.metadata ??= Symbol.for("Symbol.metadata");
@@ -139,6 +140,45 @@ const Metadata = {
139
140
  isDeprecated(metadata, field) {
140
141
  return metadata[field].deprecated === true;
141
142
  },
143
+ init(klass) {
144
+ //
145
+ // Used only to initialize an empty Schema (Encoder#constructor)
146
+ // TODO: remove/refactor this...
147
+ //
148
+ const metadata = {};
149
+ klass.constructor[Symbol.metadata] = metadata;
150
+ Object.defineProperty(metadata, -1, {
151
+ value: 0,
152
+ enumerable: false,
153
+ configurable: true,
154
+ });
155
+ },
156
+ initialize(constructor, parentMetadata) {
157
+ let metadata = constructor[Symbol.metadata] ?? Object.create(null);
158
+ // make sure inherited classes have their own metadata object.
159
+ if (constructor[Symbol.metadata] === parentMetadata) {
160
+ metadata = Object.create(null);
161
+ if (parentMetadata) {
162
+ // assign parent metadata to current
163
+ Object.assign(metadata, parentMetadata);
164
+ for (let i = 0; i <= parentMetadata[-1]; i++) {
165
+ Object.defineProperty(metadata, i, {
166
+ value: parentMetadata[i],
167
+ enumerable: false,
168
+ configurable: true,
169
+ });
170
+ }
171
+ Object.defineProperty(metadata, -1, {
172
+ value: parentMetadata[-1],
173
+ enumerable: false,
174
+ configurable: true,
175
+ writable: true,
176
+ });
177
+ }
178
+ }
179
+ constructor[Symbol.metadata] = metadata;
180
+ return metadata;
181
+ },
142
182
  isValidInstance(klass) {
143
183
  return (klass.constructor[Symbol.metadata] &&
144
184
  Object.prototype.hasOwnProperty.call(klass.constructor[Symbol.metadata], -1));
@@ -258,14 +298,12 @@ class ChangeTree {
258
298
  this.checkIsFiltered(parent, parentIndex);
259
299
  if (!this.isFiltered) {
260
300
  this.root.changes.set(this, this.changes);
301
+ this.root.allChanges.set(this, this.allChanges);
261
302
  }
262
303
  if (this.isFiltered || this.isPartiallyFiltered) {
263
304
  this.root.filteredChanges.set(this, this.filteredChanges);
264
305
  this.root.allFilteredChanges.set(this, this.filteredChanges);
265
306
  }
266
- else {
267
- this.root.allChanges.set(this, this.allChanges);
268
- }
269
307
  this.ensureRefId();
270
308
  this.forEachChild((changeTree, atIndex) => {
271
309
  changeTree.setParent(this.ref, root, atIndex);
@@ -357,7 +395,6 @@ class ChangeTree {
357
395
  }
358
396
  _shiftAllChangeIndexes(shiftIndex, startIndex = 0, allChangeSet) {
359
397
  Array.from(allChangeSet.entries()).forEach(([index, op]) => {
360
- // console.log('shiftAllChangeIndexes', index >= startIndex, { index, op, shiftIndex, startIndex })
361
398
  if (index >= startIndex) {
362
399
  allChangeSet.delete(index);
363
400
  allChangeSet.set(index + shiftIndex, op);
@@ -498,7 +535,7 @@ class ChangeTree {
498
535
  }
499
536
  checkIsFiltered(parent, parentIndex) {
500
537
  // Detect if current structure has "filters" declared
501
- this.isPartiallyFiltered = this.ref['constructor']?.[Symbol.metadata]?.[-2];
538
+ this.isPartiallyFiltered = (this.ref['constructor']?.[Symbol.metadata]?.[-2] !== undefined);
502
539
  // TODO: support "partially filtered", where the instance is visible, but only a field is not.
503
540
  // Detect if parent has "filters" declared
504
541
  while (parent && !this.isFiltered) {
@@ -559,26 +596,29 @@ try {
559
596
  textEncoder = new TextEncoder();
560
597
  }
561
598
  catch (e) { }
562
- function utf8Length(str) {
563
- var c = 0, length = 0;
564
- for (var i = 0, l = str.length; i < l; i++) {
565
- c = str.charCodeAt(i);
566
- if (c < 0x80) {
567
- length += 1;
568
- }
569
- else if (c < 0x800) {
570
- length += 2;
571
- }
572
- else if (c < 0xd800 || c >= 0xe000) {
573
- length += 3;
574
- }
575
- else {
576
- i++;
577
- length += 4;
599
+ const hasBufferByteLength = (typeof Buffer !== 'undefined' && Buffer.byteLength);
600
+ const utf8Length = (hasBufferByteLength)
601
+ ? Buffer.byteLength // node
602
+ : function (str, _) {
603
+ var c = 0, length = 0;
604
+ for (var i = 0, l = str.length; i < l; i++) {
605
+ c = str.charCodeAt(i);
606
+ if (c < 0x80) {
607
+ length += 1;
608
+ }
609
+ else if (c < 0x800) {
610
+ length += 2;
611
+ }
612
+ else if (c < 0xd800 || c >= 0xe000) {
613
+ length += 3;
614
+ }
615
+ else {
616
+ i++;
617
+ length += 4;
618
+ }
578
619
  }
579
- }
580
- return length;
581
- }
620
+ return length;
621
+ };
582
622
  function utf8Write(view, str, it) {
583
623
  var c = 0;
584
624
  for (var i = 0, l = str.length; i < l; i++) {
@@ -673,8 +713,7 @@ function string$1(bytes, value, it) {
673
713
  if (!value) {
674
714
  value = "";
675
715
  }
676
- // let length = utf8Length(value);
677
- let length = Buffer.byteLength(value, "utf8");
716
+ let length = utf8Length(value, "utf8");
678
717
  let size = 0;
679
718
  // fixstr
680
719
  if (length < 0x20) {
@@ -785,23 +824,23 @@ function number$1(bytes, value, it) {
785
824
 
786
825
  var encode = /*#__PURE__*/Object.freeze({
787
826
  __proto__: null,
788
- utf8Length: utf8Length,
789
- utf8Write: utf8Write,
790
- int8: int8$1,
791
- uint8: uint8$1,
827
+ boolean: boolean$1,
828
+ float32: float32$1,
829
+ float64: float64$1,
792
830
  int16: int16$1,
793
- uint16: uint16$1,
794
831
  int32: int32$1,
795
- uint32: uint32$1,
796
832
  int64: int64$1,
833
+ int8: int8$1,
834
+ number: number$1,
835
+ string: string$1,
836
+ uint16: uint16$1,
837
+ uint32: uint32$1,
797
838
  uint64: uint64$1,
798
- float32: float32$1,
799
- float64: float64$1,
839
+ uint8: uint8$1,
840
+ utf8Length: utf8Length,
841
+ utf8Write: utf8Write,
800
842
  writeFloat32: writeFloat32,
801
- writeFloat64: writeFloat64,
802
- boolean: boolean$1,
803
- string: string$1,
804
- number: number$1
843
+ writeFloat64: writeFloat64
805
844
  });
806
845
 
807
846
  class EncodeSchemaError extends Error {
@@ -943,6 +982,18 @@ const encodeKeyValueOperation = function (encoder, bytes, changeTree, field, ope
943
982
  }
944
983
  const type = changeTree.getType(field);
945
984
  const value = changeTree.getValue(field);
985
+ // try { throw new Error(); } catch (e) {
986
+ // // only print if not coming from Reflection.ts
987
+ // if (!e.stack.includes("src/Reflection.ts")) {
988
+ // console.log("encodeKeyValueOperation -> ", {
989
+ // ref: changeTree.ref.constructor.name,
990
+ // field,
991
+ // operation: OPERATION[operation],
992
+ // value: value?.toJSON(),
993
+ // items: ref.toJSON(),
994
+ // });
995
+ // }
996
+ // }
946
997
  // TODO: inline this function call small performance gain
947
998
  encodeValue(encoder, bytes, ref, type, value, field, operation, it);
948
999
  };
@@ -952,15 +1003,19 @@ const encodeKeyValueOperation = function (encoder, bytes, changeTree, field, ope
952
1003
  */
953
1004
  const encodeArray = function (encoder, bytes, changeTree, field, operation, it, isEncodeAll, hasView) {
954
1005
  const ref = changeTree.ref;
955
- if (hasView &&
956
- operation === OPERATION.DELETE &&
957
- typeof (changeTree.getType(field)) !== "string") {
958
- // encode delete by refId (array of schemas)
959
- bytes[it.offset++] = OPERATION.DELETE_BY_REFID;
960
- const value = ref['tmpItems'][field];
961
- const refId = value[$changes].refId;
962
- number$1(bytes, refId, it);
963
- return;
1006
+ const useOperationByRefId = hasView && changeTree.isFiltered && (typeof (changeTree.getType(field)) !== "string");
1007
+ let refOrIndex;
1008
+ if (useOperationByRefId) {
1009
+ refOrIndex = ref['tmpItems'][field][$changes].refId;
1010
+ if (operation === OPERATION.DELETE) {
1011
+ operation = OPERATION.DELETE_BY_REFID;
1012
+ }
1013
+ else if (operation === OPERATION.ADD) {
1014
+ operation = OPERATION.ADD_BY_REFID;
1015
+ }
1016
+ }
1017
+ else {
1018
+ refOrIndex = field;
964
1019
  }
965
1020
  // encode operation
966
1021
  bytes[it.offset++] = operation & 255;
@@ -969,7 +1024,7 @@ const encodeArray = function (encoder, bytes, changeTree, field, operation, it,
969
1024
  return;
970
1025
  }
971
1026
  // encode index
972
- number$1(bytes, field, it);
1027
+ number$1(bytes, refOrIndex, it);
973
1028
  // Do not encode value for DELETE operations
974
1029
  if (operation === OPERATION.DELETE) {
975
1030
  return;
@@ -1009,9 +1064,9 @@ const encodeArray = function (encoder, bytes, changeTree, field, operation, it,
1009
1064
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1010
1065
  * SOFTWARE
1011
1066
  */
1012
- function utf8Read(bytes, offset, length) {
1067
+ function utf8Read(bytes, it, length) {
1013
1068
  var string = '', chr = 0;
1014
- for (var i = offset, end = offset + length; i < end; i++) {
1069
+ for (var i = it.offset, end = it.offset + length; i < end; i++) {
1015
1070
  var byte = bytes[i];
1016
1071
  if ((byte & 0x80) === 0x00) {
1017
1072
  string += String.fromCharCode(byte);
@@ -1046,6 +1101,7 @@ function utf8Read(bytes, offset, length) {
1046
1101
  // (do not throw error to avoid server/client from crashing due to hack attemps)
1047
1102
  // throw new Error('Invalid byte ' + byte.toString(16));
1048
1103
  }
1104
+ it.offset += length;
1049
1105
  return string;
1050
1106
  }
1051
1107
  function int8(bytes, it) {
@@ -1113,9 +1169,7 @@ function string(bytes, it) {
1113
1169
  else if (prefix === 0xdb) {
1114
1170
  length = uint32(bytes, it);
1115
1171
  }
1116
- const value = utf8Read(bytes, it.offset, length);
1117
- it.offset += length;
1118
- return value;
1172
+ return utf8Read(bytes, it, length);
1119
1173
  }
1120
1174
  function stringCheck(bytes, it) {
1121
1175
  const prefix = bytes[it.offset];
@@ -1219,30 +1273,31 @@ function switchStructureCheck(bytes, it) {
1219
1273
 
1220
1274
  var decode = /*#__PURE__*/Object.freeze({
1221
1275
  __proto__: null,
1222
- int8: int8,
1223
- uint8: uint8,
1224
- int16: int16,
1225
- uint16: uint16,
1226
- int32: int32,
1227
- uint32: uint32,
1276
+ arrayCheck: arrayCheck,
1277
+ boolean: boolean,
1228
1278
  float32: float32,
1229
1279
  float64: float64,
1280
+ int16: int16,
1281
+ int32: int32,
1230
1282
  int64: int64,
1231
- uint64: uint64,
1283
+ int8: int8,
1284
+ number: number,
1285
+ numberCheck: numberCheck,
1232
1286
  readFloat32: readFloat32,
1233
1287
  readFloat64: readFloat64,
1234
- boolean: boolean,
1235
1288
  string: string,
1236
1289
  stringCheck: stringCheck,
1237
- number: number,
1238
- numberCheck: numberCheck,
1239
- arrayCheck: arrayCheck,
1240
- switchStructureCheck: switchStructureCheck
1290
+ switchStructureCheck: switchStructureCheck,
1291
+ uint16: uint16,
1292
+ uint32: uint32,
1293
+ uint64: uint64,
1294
+ uint8: uint8,
1295
+ utf8Read: utf8Read
1241
1296
  });
1242
1297
 
1243
1298
  const DEFINITION_MISMATCH = -1;
1244
1299
  function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges) {
1245
- const $root = decoder.$root;
1300
+ const $root = decoder.root;
1246
1301
  const previousValue = ref[$getByIndex](index);
1247
1302
  let value;
1248
1303
  if ((operation & OPERATION.DELETE) === OPERATION.DELETE) {
@@ -1428,7 +1483,8 @@ const decodeKeyValueOperation = function (decoder, bytes, it, ref, allChanges) {
1428
1483
  };
1429
1484
  const decodeArray = function (decoder, bytes, it, ref, allChanges) {
1430
1485
  // "uncompressed" index + operation (array/map items)
1431
- const operation = bytes[it.offset++];
1486
+ let operation = bytes[it.offset++];
1487
+ let index;
1432
1488
  if (operation === OPERATION.CLEAR) {
1433
1489
  //
1434
1490
  // When decoding:
@@ -1442,8 +1498,8 @@ const decodeArray = function (decoder, bytes, it, ref, allChanges) {
1442
1498
  else if (operation === OPERATION.DELETE_BY_REFID) {
1443
1499
  // TODO: refactor here, try to follow same flow as below
1444
1500
  const refId = number(bytes, it);
1445
- const previousValue = decoder.$root.refs.get(refId);
1446
- const index = ref.findIndex((value) => value === previousValue);
1501
+ const previousValue = decoder.root.refs.get(refId);
1502
+ index = ref.findIndex((value) => value === previousValue);
1447
1503
  ref[$deleteByIndex](index);
1448
1504
  allChanges.push({
1449
1505
  ref,
@@ -1456,7 +1512,18 @@ const decodeArray = function (decoder, bytes, it, ref, allChanges) {
1456
1512
  });
1457
1513
  return;
1458
1514
  }
1459
- const index = number(bytes, it);
1515
+ else if (operation === OPERATION.ADD_BY_REFID) {
1516
+ // operation = OPERATION.ADD;
1517
+ const refId = number(bytes, it);
1518
+ const itemByRefId = decoder.root.refs.get(refId);
1519
+ // use existing index, or push new value
1520
+ index = (itemByRefId)
1521
+ ? ref.findIndex((value) => value === itemByRefId)
1522
+ : ref.length;
1523
+ }
1524
+ else {
1525
+ index = number(bytes, it);
1526
+ }
1460
1527
  const type = ref[$childType];
1461
1528
  let dynamicIndex = index;
1462
1529
  const { value, previousValue } = decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges);
@@ -1865,14 +1932,6 @@ class ArraySchema {
1865
1932
  lastIndexOf(searchElement, fromIndex = this.length - 1) {
1866
1933
  return this.items.lastIndexOf(searchElement, fromIndex);
1867
1934
  }
1868
- /**
1869
- * Determines whether all the members of an array satisfy the specified test.
1870
- * @param callbackfn A function that accepts up to three arguments. The every method calls
1871
- * the callbackfn function for each element in the array until the callbackfn returns a value
1872
- * which is coercible to the Boolean value false, or until the end of the array.
1873
- * @param thisArg An object to which the this keyword can refer in the callbackfn function.
1874
- * If thisArg is omitted, undefined is used as the this value.
1875
- */
1876
1935
  every(callbackfn, thisArg) {
1877
1936
  return this.items.every(callbackfn, thisArg);
1878
1937
  }
@@ -2551,6 +2610,7 @@ function view(tag = DEFAULT_VIEW_TAG) {
2551
2610
  const constructor = target.constructor;
2552
2611
  const parentClass = Object.getPrototypeOf(constructor);
2553
2612
  const parentMetadata = parentClass[Symbol.metadata];
2613
+ // TODO: use Metadata.initialize()
2554
2614
  const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
2555
2615
  if (!metadata[fieldName]) {
2556
2616
  //
@@ -2575,8 +2635,8 @@ function type(type, options) {
2575
2635
  // for inheritance support
2576
2636
  TypeContext.register(constructor);
2577
2637
  const parentClass = Object.getPrototypeOf(constructor);
2578
- const parentMetadata = parentClass[Symbol.metadata];
2579
- const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
2638
+ const parentMetadata = parentClass && parentClass[Symbol.metadata];
2639
+ const metadata = Metadata.initialize(constructor, parentMetadata);
2580
2640
  let fieldIndex;
2581
2641
  /**
2582
2642
  * skip if descriptor already exists for this field (`@deprecated()`)
@@ -3349,6 +3409,8 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
3349
3409
  OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
3350
3410
  PERFORMANCE OF THIS SOFTWARE.
3351
3411
  ***************************************************************************** */
3412
+ /* global Reflect, Promise, SuppressedError, Symbol */
3413
+
3352
3414
 
3353
3415
  function __decorate(decorators, target, key, desc) {
3354
3416
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -3380,11 +3442,14 @@ class Encoder {
3380
3442
  setRoot(state) {
3381
3443
  this.root = new Root();
3382
3444
  this.state = state;
3445
+ // Workaround to allow using an empty Schema.
3446
+ if (state.constructor[Symbol.metadata] === undefined) {
3447
+ Metadata.init(state);
3448
+ }
3383
3449
  state[$changes].setRoot(this.root);
3384
3450
  }
3385
- encode(it = { offset: 0 }, view, bytes = this.sharedBuffer, changeTrees = this.root.changes) {
3451
+ encode(it = { offset: 0 }, view, buffer = this.sharedBuffer, changeTrees = this.root.changes, isEncodeAll = this.root.allChanges === changeTrees) {
3386
3452
  const initialOffset = it.offset; // cache current offset in case we need to resize the buffer
3387
- const isEncodeAll = this.root.allChanges === changeTrees;
3388
3453
  const hasView = (view !== undefined);
3389
3454
  const rootChangeTree = this.state[$changes];
3390
3455
  const changeTreesIterator = changeTrees.entries();
@@ -3393,6 +3458,12 @@ class Encoder {
3393
3458
  const ctor = ref['constructor'];
3394
3459
  const encoder = ctor[$encoder];
3395
3460
  const filter = ctor[$filter];
3461
+ // try { throw new Error(); } catch (e) {
3462
+ // // only print if not coming from Reflection.ts
3463
+ // if (!e.stack.includes("src/Reflection.ts")) {
3464
+ // console.log("ChangeTree:", { ref: ref.constructor.name, });
3465
+ // }
3466
+ // }
3396
3467
  if (hasView) {
3397
3468
  if (!view.items.has(changeTree)) {
3398
3469
  view.invisible.add(changeTree);
@@ -3404,8 +3475,8 @@ class Encoder {
3404
3475
  }
3405
3476
  // skip root `refId` if it's the first change tree
3406
3477
  if (it.offset !== initialOffset || changeTree !== rootChangeTree) {
3407
- bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
3408
- number$1(bytes, changeTree.refId, it);
3478
+ buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
3479
+ number$1(buffer, changeTree.refId, it);
3409
3480
  }
3410
3481
  const changesIterator = changes.entries();
3411
3482
  for (const [fieldIndex, operation] of changesIterator) {
@@ -3422,22 +3493,31 @@ class Encoder {
3422
3493
  // view?.invisible.add(changeTree);
3423
3494
  continue;
3424
3495
  }
3425
- // console.log("WILL ENCODE", {
3426
- // ref: changeTree.ref.constructor.name,
3427
- // fieldIndex,
3428
- // operation: OPERATION[operation],
3429
- // });
3430
- encoder(this, bytes, changeTree, fieldIndex, operation, it, isEncodeAll, hasView);
3496
+ // try { throw new Error(); } catch (e) {
3497
+ // // only print if not coming from Reflection.ts
3498
+ // if (!e.stack.includes("src/Reflection.ts")) {
3499
+ // console.log("WILL ENCODE", {
3500
+ // ref: changeTree.ref.constructor.name,
3501
+ // fieldIndex,
3502
+ // operation: OPERATION[operation],
3503
+ // });
3504
+ // }
3505
+ // }
3506
+ encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView);
3431
3507
  }
3432
3508
  }
3433
- if (it.offset > bytes.byteLength) {
3434
- const newSize = getNextPowerOf2(this.sharedBuffer.byteLength * 2);
3435
- console.warn("@colyseus/schema encode buffer overflow. Current buffer size: " + bytes.byteLength + ", encoding offset: " + it.offset + ", new size: " + newSize);
3509
+ if (it.offset > buffer.byteLength) {
3510
+ const newSize = getNextPowerOf2(buffer.byteLength * 2);
3511
+ console.warn("@colyseus/schema encode buffer overflow. Current buffer size: " + buffer.byteLength + ", encoding offset: " + it.offset + ", new size: " + newSize);
3436
3512
  //
3437
3513
  // resize buffer and re-encode (TODO: can we avoid re-encoding here?)
3438
3514
  //
3439
- this.sharedBuffer = Buffer.allocUnsafeSlow(newSize);
3440
- return this.encode({ offset: initialOffset }, view);
3515
+ buffer = Buffer.allocUnsafeSlow(newSize);
3516
+ // assign resized buffer to local sharedBuffer
3517
+ if (buffer === this.sharedBuffer) {
3518
+ this.sharedBuffer = buffer;
3519
+ }
3520
+ return this.encode({ offset: initialOffset }, view, buffer, changeTrees, isEncodeAll);
3441
3521
  }
3442
3522
  else {
3443
3523
  //
@@ -3449,38 +3529,37 @@ class Encoder {
3449
3529
  //
3450
3530
  this.onEndEncode(changeTrees);
3451
3531
  }
3452
- // return bytes;
3453
- return bytes.slice(0, it.offset);
3532
+ return buffer.subarray(0, it.offset);
3454
3533
  }
3455
3534
  }
3456
- encodeAll(it = { offset: 0 }) {
3457
- // console.log(`encodeAll(), this.$root.allChanges (${this.$root.allChanges.size})`);
3458
- // Array.from(this.$root.allChanges.entries()).map((item) => {
3459
- // console.log("->", item[0].refId, item[0].ref.toJSON());
3535
+ encodeAll(it = { offset: 0 }, buffer = this.sharedBuffer) {
3536
+ // console.log(`encodeAll(), this.root.allChanges (${this.root.allChanges.size})`);
3537
+ // Array.from(this.root.allChanges.entries()).map((item) => {
3538
+ // console.log("->", { ref: item[0].ref.constructor.name, refId: item[0].refId, changes: item[1].size });
3460
3539
  // });
3461
- return this.encode(it, undefined, this.sharedBuffer, this.root.allChanges);
3540
+ return this.encode(it, undefined, buffer, this.root.allChanges, true);
3462
3541
  }
3463
3542
  encodeAllView(view, sharedOffset, it, bytes = this.sharedBuffer) {
3464
3543
  const viewOffset = it.offset;
3465
- // console.log(`encodeAllView(), this.$root.allFilteredChanges (${this.$root.allFilteredChanges.size})`);
3544
+ // console.log(`encodeAllView(), this.root.allFilteredChanges (${this.root.allFilteredChanges.size})`);
3466
3545
  // this.debugAllFilteredChanges();
3467
3546
  // try to encode "filtered" changes
3468
- this.encode(it, view, bytes, this.root.allFilteredChanges);
3547
+ this.encode(it, view, bytes, this.root.allFilteredChanges, true);
3469
3548
  return Buffer.concat([
3470
- bytes.slice(0, sharedOffset),
3471
- bytes.slice(viewOffset, it.offset)
3549
+ bytes.subarray(0, sharedOffset),
3550
+ bytes.subarray(viewOffset, it.offset)
3472
3551
  ]);
3473
3552
  }
3474
- // debugAllFilteredChanges() {
3475
- // Array.from(this.$root.allFilteredChanges.entries()).map((item) => {
3476
- // console.log("->", { refId: item[0].refId }, item[0].ref.toJSON());
3477
- // if (Array.isArray(item[0].ref.toJSON())) {
3478
- // item[1].forEach((op, key) => {
3479
- // console.log(" ->", { key, op: OPERATION[op] });
3480
- // })
3481
- // }
3482
- // });
3483
- // }
3553
+ debugAllFilteredChanges() {
3554
+ Array.from(this.root.allFilteredChanges.entries()).map((item) => {
3555
+ console.log("->", { refId: item[0].refId, changes: item[1].size }, item[0].ref.toJSON());
3556
+ if (Array.isArray(item[0].ref.toJSON())) {
3557
+ item[1].forEach((op, key) => {
3558
+ console.log(" ->", { key, op: OPERATION[op] });
3559
+ });
3560
+ }
3561
+ });
3562
+ }
3484
3563
  encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
3485
3564
  const viewOffset = it.offset;
3486
3565
  // try to encode "filtered" changes
@@ -3512,8 +3591,8 @@ class Encoder {
3512
3591
  // clear "view" changes after encoding
3513
3592
  view.changes.clear();
3514
3593
  return Buffer.concat([
3515
- bytes.slice(0, sharedOffset),
3516
- bytes.slice(viewOffset, it.offset)
3594
+ bytes.subarray(0, sharedOffset),
3595
+ bytes.subarray(viewOffset, it.offset)
3517
3596
  ]);
3518
3597
  }
3519
3598
  onEndEncode(changeTrees = this.root.changes) {
@@ -3692,12 +3771,12 @@ class Decoder {
3692
3771
  }
3693
3772
  setRoot(root) {
3694
3773
  this.state = root;
3695
- this.$root = new ReferenceTracker();
3696
- this.$root.addRef(0, root);
3774
+ this.root = new ReferenceTracker();
3775
+ this.root.addRef(0, root);
3697
3776
  }
3698
3777
  decode(bytes, it = { offset: 0 }, ref = this.state) {
3699
3778
  const allChanges = [];
3700
- const $root = this.$root;
3779
+ const $root = this.root;
3701
3780
  const totalBytes = bytes.byteLength;
3702
3781
  let decoder = ref['constructor'][$decoder];
3703
3782
  this.currentRefId = 0;
@@ -3778,7 +3857,7 @@ class Decoder {
3778
3857
  previousValue: value
3779
3858
  });
3780
3859
  if (needRemoveRef) {
3781
- this.$root.removeRef(this.$root.refIds.get(value));
3860
+ this.root.removeRef(this.root.refIds.get(value));
3782
3861
  }
3783
3862
  });
3784
3863
  }
@@ -3818,7 +3897,7 @@ class Reflection extends Schema {
3818
3897
  super(...arguments);
3819
3898
  this.types = new ArraySchema();
3820
3899
  }
3821
- static encode(instance, context) {
3900
+ static encode(instance, context, it = { offset: 0 }) {
3822
3901
  if (!context) {
3823
3902
  context = new TypeContext(instance.constructor);
3824
3903
  }
@@ -3875,7 +3954,6 @@ class Reflection extends Schema {
3875
3954
  }
3876
3955
  buildType(type, klass[Symbol.metadata]);
3877
3956
  }
3878
- const it = { offset: 0 };
3879
3957
  const buf = encoder.encodeAll(it);
3880
3958
  return Buffer.from(buf, 0, it.offset);
3881
3959
  }
@@ -3883,59 +3961,298 @@ class Reflection extends Schema {
3883
3961
  const reflection = new Reflection();
3884
3962
  const reflectionDecoder = new Decoder(reflection);
3885
3963
  reflectionDecoder.decode(bytes, it);
3886
- const context = new TypeContext();
3887
- const schemaTypes = reflection.types.reduce((types, reflectionType) => {
3888
- const parentKlass = types[reflectionType.extendsId] || Schema;
3889
- const schema = class _ extends parentKlass {
3964
+ const typeContext = new TypeContext();
3965
+ // 1st pass, initialize metadata + inheritance
3966
+ reflection.types.forEach((reflectionType) => {
3967
+ const parentClass = typeContext.get(reflectionType.extendsId) ?? Schema;
3968
+ const schema = class _ extends parentClass {
3890
3969
  };
3891
- // const _metadata = Object.create(_classSuper[Symbol.metadata] ?? null);
3892
- const _metadata = parentKlass && parentKlass[Symbol.metadata] || Object.create(null);
3893
- Object.defineProperty(schema, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
3970
+ const parentMetadata = parentClass[Symbol.metadata];
3894
3971
  // register for inheritance support
3895
3972
  TypeContext.register(schema);
3896
- const typeid = reflectionType.id;
3897
- types[typeid] = schema;
3898
- context.add(schema, typeid);
3899
- return types;
3973
+ // for inheritance support
3974
+ Metadata.initialize(schema, parentMetadata);
3975
+ typeContext.add(schema, reflectionType.id);
3900
3976
  }, {});
3977
+ // 2nd pass, set fields
3901
3978
  reflection.types.forEach((reflectionType) => {
3902
- const schemaType = schemaTypes[reflectionType.id];
3979
+ const schemaType = typeContext.get(reflectionType.id);
3903
3980
  const metadata = schemaType[Symbol.metadata];
3904
- const parentKlass = reflection.types[reflectionType.extendsId];
3905
- const parentFieldIndex = parentKlass && parentKlass.fields.length || 0;
3981
+ // FIXME: use metadata[-1] to get field count
3982
+ const parentFieldIndex = 0;
3983
+ // console.log("--------------------");
3984
+ // // console.log("reflectionType", reflectionType.toJSON());
3985
+ // console.log("reflectionType.fields", reflectionType.fields.toJSON());
3986
+ // console.log("parentFieldIndex", parentFieldIndex);
3987
+ //
3988
+ // FIXME: set fields using parentKlass as well
3989
+ // currently the fields are duplicated on inherited classes
3990
+ //
3991
+ // // const parentKlass = reflection.types[reflectionType.extendsId];
3992
+ // // parentKlass.fields
3906
3993
  reflectionType.fields.forEach((field, i) => {
3907
3994
  const fieldIndex = parentFieldIndex + i;
3908
3995
  if (field.referencedType !== undefined) {
3909
3996
  let fieldType = field.type;
3910
- let refType = schemaTypes[field.referencedType];
3997
+ let refType = typeContext.get(field.referencedType);
3911
3998
  // map or array of primitive type (-1)
3912
3999
  if (!refType) {
3913
4000
  const typeInfo = field.type.split(":");
3914
4001
  fieldType = typeInfo[0];
3915
- refType = typeInfo[1];
4002
+ refType = typeInfo[1]; // string
3916
4003
  }
3917
4004
  if (fieldType === "ref") {
3918
- // type(refType)(schemaType.prototype, field.name);
3919
4005
  Metadata.addField(metadata, fieldIndex, field.name, refType);
3920
4006
  }
3921
4007
  else {
3922
- // type({ [fieldType]: refType } as DefinitionType)(schemaType.prototype, field.name);
3923
4008
  Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType });
3924
4009
  }
3925
4010
  }
3926
4011
  else {
3927
- // type(field.type as PrimitiveType)(schemaType.prototype, field.name);
3928
4012
  Metadata.addField(metadata, fieldIndex, field.name, field.type);
3929
4013
  }
3930
4014
  });
3931
4015
  });
3932
- return new (schemaTypes[0])();
4016
+ // @ts-ignore
4017
+ return new (typeContext.get(0))();
3933
4018
  }
3934
4019
  }
3935
4020
  __decorate([
3936
4021
  type([ReflectionType])
3937
4022
  ], Reflection.prototype, "types", void 0);
3938
4023
 
4024
+ function getDecoderStateCallbacks(decoder) {
4025
+ const $root = decoder.root;
4026
+ const callbacks = $root.callbacks;
4027
+ let isTriggeringOnAdd = false;
4028
+ decoder.triggerChanges = function (allChanges) {
4029
+ const uniqueRefIds = new Set();
4030
+ for (let i = 0, l = allChanges.length; i < l; i++) {
4031
+ const change = allChanges[i];
4032
+ const refId = change.refId;
4033
+ const ref = change.ref;
4034
+ const $callbacks = callbacks[refId];
4035
+ if (!$callbacks) {
4036
+ continue;
4037
+ }
4038
+ //
4039
+ // trigger onRemove on child structure.
4040
+ //
4041
+ if ((change.op & OPERATION.DELETE) === OPERATION.DELETE &&
4042
+ change.previousValue instanceof Schema) {
4043
+ const deleteCallbacks = callbacks[$root.refIds.get(change.previousValue)]?.[OPERATION.DELETE];
4044
+ for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
4045
+ deleteCallbacks[i]();
4046
+ }
4047
+ }
4048
+ if (ref instanceof Schema) {
4049
+ //
4050
+ // Handle schema instance
4051
+ //
4052
+ if (!uniqueRefIds.has(refId)) {
4053
+ // trigger onChange
4054
+ const replaceCallbacks = $callbacks?.[OPERATION.REPLACE];
4055
+ for (let i = replaceCallbacks?.length - 1; i >= 0; i--) {
4056
+ replaceCallbacks[i]();
4057
+ // try {
4058
+ // } catch (e) {
4059
+ // console.error(e);
4060
+ // }
4061
+ }
4062
+ }
4063
+ if ($callbacks.hasOwnProperty(change.field)) {
4064
+ const fieldCallbacks = $callbacks[change.field];
4065
+ for (let i = fieldCallbacks?.length - 1; i >= 0; i--) {
4066
+ fieldCallbacks[i](change.value, change.previousValue);
4067
+ // try {
4068
+ // } catch (e) {
4069
+ // console.error(e);
4070
+ // }
4071
+ }
4072
+ }
4073
+ }
4074
+ else {
4075
+ //
4076
+ // Handle collection of items
4077
+ //
4078
+ if ((change.op & OPERATION.DELETE) === OPERATION.DELETE) {
4079
+ //
4080
+ // FIXME: `previousValue` should always be available.
4081
+ //
4082
+ if (change.previousValue !== undefined) {
4083
+ // triger onRemove
4084
+ const deleteCallbacks = $callbacks[OPERATION.DELETE];
4085
+ for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
4086
+ deleteCallbacks[i](change.previousValue, change.dynamicIndex ?? change.field);
4087
+ }
4088
+ }
4089
+ // Handle DELETE_AND_ADD operations
4090
+ // FIXME: should we set "isTriggeringOnAdd" here?
4091
+ if ((change.op & OPERATION.ADD) === OPERATION.ADD) {
4092
+ const addCallbacks = $callbacks[OPERATION.ADD];
4093
+ for (let i = addCallbacks?.length - 1; i >= 0; i--) {
4094
+ addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
4095
+ }
4096
+ }
4097
+ }
4098
+ else if ((change.op & OPERATION.ADD) === OPERATION.ADD && change.previousValue === undefined) {
4099
+ // triger onAdd
4100
+ isTriggeringOnAdd = true;
4101
+ const addCallbacks = $callbacks[OPERATION.ADD];
4102
+ for (let i = addCallbacks?.length - 1; i >= 0; i--) {
4103
+ addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
4104
+ }
4105
+ isTriggeringOnAdd = false;
4106
+ }
4107
+ // trigger onChange
4108
+ if (change.value !== change.previousValue) {
4109
+ const replaceCallbacks = $callbacks[OPERATION.REPLACE];
4110
+ for (let i = replaceCallbacks?.length - 1; i >= 0; i--) {
4111
+ replaceCallbacks[i](change.value, change.dynamicIndex ?? change.field);
4112
+ }
4113
+ }
4114
+ }
4115
+ uniqueRefIds.add(refId);
4116
+ }
4117
+ };
4118
+ function getProxy(metadataOrType, context) {
4119
+ let metadata = context.instance?.constructor[Symbol.metadata] || metadataOrType;
4120
+ let isCollection = ((context.instance && typeof (context.instance['forEach']) === "function") ||
4121
+ (metadataOrType && typeof (metadataOrType[Symbol.metadata]) === "undefined"));
4122
+ if (metadata && !isCollection) {
4123
+ const onAdd = function (ref, prop, callback, immediate) {
4124
+ // immediate trigger
4125
+ if (immediate &&
4126
+ context.instance[prop] !== undefined &&
4127
+ !isTriggeringOnAdd // FIXME: This is a workaround (https://github.com/colyseus/schema/issues/147)
4128
+ ) {
4129
+ callback(context.instance[prop], undefined);
4130
+ }
4131
+ return $root.addCallback($root.refIds.get(ref), prop, callback);
4132
+ };
4133
+ /**
4134
+ * Schema instances
4135
+ */
4136
+ return new Proxy({
4137
+ listen: function listen(prop, callback, immediate = true) {
4138
+ if (context.instance) {
4139
+ return onAdd(context.instance, prop, callback, immediate);
4140
+ }
4141
+ else {
4142
+ // collection instance not received yet
4143
+ context.onInstanceAvailable((ref, existing) => onAdd(ref, prop, callback, immediate && existing));
4144
+ }
4145
+ },
4146
+ onChange: function onChange(callback) {
4147
+ return $root.addCallback($root.refIds.get(context.instance), OPERATION.REPLACE, callback);
4148
+ },
4149
+ bindTo: function bindTo(targetObject, properties) {
4150
+ //
4151
+ // TODO: refactor this implementation. There is room for improvement here.
4152
+ //
4153
+ if (!properties) {
4154
+ properties = Object.keys(metadata);
4155
+ }
4156
+ return $root.addCallback($root.refIds.get(context.instance), OPERATION.REPLACE, () => {
4157
+ properties.forEach((prop) => targetObject[prop] = context.instance[prop]);
4158
+ });
4159
+ }
4160
+ }, {
4161
+ get(target, prop) {
4162
+ if (metadata[prop]) {
4163
+ const instance = context.instance?.[prop];
4164
+ const onInstanceAvailable = ((callback) => {
4165
+ const unbind = $(context.instance).listen(prop, (value, _) => {
4166
+ callback(value, false);
4167
+ // FIXME: by "unbinding" the callback here,
4168
+ // it will not support when the server
4169
+ // re-instantiates the instance.
4170
+ //
4171
+ unbind?.();
4172
+ }, false);
4173
+ // has existing value
4174
+ if ($root.refIds.get(instance) !== undefined) {
4175
+ callback(instance, true);
4176
+ }
4177
+ });
4178
+ return getProxy(metadata[prop].type, {
4179
+ instance,
4180
+ parentInstance: context.instance,
4181
+ onInstanceAvailable,
4182
+ });
4183
+ }
4184
+ else {
4185
+ // accessing the function
4186
+ return target[prop];
4187
+ }
4188
+ },
4189
+ has(target, prop) { return metadata[prop] !== undefined; },
4190
+ set(_, _1, _2) { throw new Error("not allowed"); },
4191
+ deleteProperty(_, _1) { throw new Error("not allowed"); },
4192
+ });
4193
+ }
4194
+ else {
4195
+ /**
4196
+ * Collection instances
4197
+ */
4198
+ const onAdd = function (ref, callback, immediate) {
4199
+ // Trigger callback on existing items
4200
+ if (immediate) {
4201
+ ref.forEach((v, k) => callback(v, k));
4202
+ }
4203
+ return $root.addCallback($root.refIds.get(ref), OPERATION.ADD, callback);
4204
+ };
4205
+ const onRemove = function (ref, callback) {
4206
+ return $root.addCallback($root.refIds.get(ref), OPERATION.DELETE, callback);
4207
+ };
4208
+ return new Proxy({
4209
+ onAdd: function (callback, immediate = true) {
4210
+ //
4211
+ // https://github.com/colyseus/schema/issues/147
4212
+ // If parent instance has "onAdd" registered, avoid triggering immediate callback.
4213
+ //
4214
+ // FIXME: "isTriggeringOnAdd" is a workaround. We should find a better way to handle this.
4215
+ //
4216
+ if (context.onInstanceAvailable) {
4217
+ // collection instance not received yet
4218
+ context.onInstanceAvailable((ref, existing) => onAdd(ref, callback, immediate && existing && !isTriggeringOnAdd));
4219
+ }
4220
+ else if (context.instance) {
4221
+ onAdd(context.instance, callback, immediate && !isTriggeringOnAdd);
4222
+ }
4223
+ },
4224
+ onRemove: function (callback) {
4225
+ if (context.onInstanceAvailable) {
4226
+ // collection instance not received yet
4227
+ context.onInstanceAvailable((ref) => onRemove(ref, callback));
4228
+ }
4229
+ else if (context.instance) {
4230
+ onRemove(context.instance, callback);
4231
+ }
4232
+ },
4233
+ }, {
4234
+ get(target, prop) {
4235
+ if (!target[prop]) {
4236
+ throw new Error(`Can't access '${prop}' through callback proxy. access the instance directly.`);
4237
+ }
4238
+ return target[prop];
4239
+ },
4240
+ has(target, prop) { return target[prop] !== undefined; },
4241
+ set(_, _1, _2) { throw new Error("not allowed"); },
4242
+ deleteProperty(_, _1) { throw new Error("not allowed"); },
4243
+ });
4244
+ }
4245
+ }
4246
+ function $(instance) {
4247
+ return getProxy(undefined, { instance });
4248
+ }
4249
+ return $;
4250
+ }
4251
+
4252
+ function getRawChangesCallback(decoder, callback) {
4253
+ decoder.triggerChanges = callback;
4254
+ }
4255
+
3939
4256
  class StateView {
3940
4257
  constructor() {
3941
4258
  /**
@@ -3958,12 +4275,18 @@ class StateView {
3958
4275
  console.warn("StateView#add(), invalid object:", obj);
3959
4276
  return this;
3960
4277
  }
4278
+ // FIXME: ArraySchema/MapSchema does not have metadata
4279
+ const metadata = obj.constructor[Symbol.metadata];
3961
4280
  let changeTree = obj[$changes];
3962
4281
  this.items.add(changeTree);
3963
4282
  // Add children of this ChangeTree to this view
3964
- changeTree.forEachChild((change, _) => this.add(change.ref, tag));
3965
- // FIXME: ArraySchema/MapSchema does not have metadata
3966
- const metadata = obj.constructor[Symbol.metadata];
4283
+ changeTree.forEachChild((change, index) => {
4284
+ // Do not ADD children that don't have the same tag
4285
+ if (metadata && metadata[metadata[index]].tag !== tag) {
4286
+ return;
4287
+ }
4288
+ this.add(change.ref, tag);
4289
+ });
3967
4290
  // add parent ChangeTree's, if they are invisible to this view
3968
4291
  // TODO: REFACTOR addParent()
3969
4292
  this.addParent(changeTree, tag);
@@ -3990,7 +4313,6 @@ class StateView {
3990
4313
  tags = this.tags.get(changeTree);
3991
4314
  }
3992
4315
  tags.add(tag);
3993
- // console.log("BY TAG:", tag);
3994
4316
  // Ref: add tagged properties
3995
4317
  metadata?.[-3]?.[tag]?.forEach((index) => {
3996
4318
  if (changeTree.getChange(index) !== OPERATION.DELETE) {
@@ -4129,5 +4451,5 @@ registerType("array", { constructor: ArraySchema });
4129
4451
  registerType("set", { constructor: SetSchema });
4130
4452
  registerType("collection", { constructor: CollectionSchema, });
4131
4453
 
4132
- export { $changes, $childType, $decoder, $deleteByIndex, $encoder, $filter, $getByIndex, $track, ArraySchema, ChangeTree, CollectionSchema, Decoder, Encoder, MapSchema, Metadata, OPERATION, Reflection, ReflectionField, ReflectionType, Schema, SetSchema, StateView, TypeContext, decode, decodeKeyValueOperation, decodeSchemaOperation, defineTypes, deprecated, dumpChanges, encode, encodeArray as encodeKeyValueOperation, encodeSchemaOperation, registerType, type, view };
4454
+ export { $changes, $childType, $decoder, $deleteByIndex, $encoder, $filter, $getByIndex, $track, ArraySchema, ChangeTree, CollectionSchema, Decoder, Encoder, MapSchema, Metadata, OPERATION, Reflection, ReflectionField, ReflectionType, Schema, SetSchema, StateView, TypeContext, decode, decodeKeyValueOperation, decodeSchemaOperation, defineTypes, deprecated, dumpChanges, encode, encodeArray as encodeKeyValueOperation, encodeSchemaOperation, getDecoderStateCallbacks, getRawChangesCallback, registerType, type, view };
4133
4455
  //# sourceMappingURL=index.mjs.map