@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
@@ -1,7 +1,5 @@
1
1
  'use strict';
2
2
 
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
3
  const SWITCH_TO_STRUCTURE = 255; // (decoding collides with DELETE_AND_ADD + fieldIndex = 63)
6
4
  const TYPE_ID = 213;
7
5
  /**
@@ -27,6 +25,7 @@ exports.OPERATION = void 0;
27
25
  OPERATION[OPERATION["REVERSE"] = 15] = "REVERSE";
28
26
  OPERATION[OPERATION["MOVE"] = 32] = "MOVE";
29
27
  OPERATION[OPERATION["DELETE_BY_REFID"] = 33] = "DELETE_BY_REFID";
28
+ OPERATION[OPERATION["ADD_BY_REFID"] = 129] = "ADD_BY_REFID";
30
29
  })(exports.OPERATION || (exports.OPERATION = {}));
31
30
 
32
31
  Symbol.metadata ??= Symbol.for("Symbol.metadata");
@@ -143,6 +142,45 @@ const Metadata = {
143
142
  isDeprecated(metadata, field) {
144
143
  return metadata[field].deprecated === true;
145
144
  },
145
+ init(klass) {
146
+ //
147
+ // Used only to initialize an empty Schema (Encoder#constructor)
148
+ // TODO: remove/refactor this...
149
+ //
150
+ const metadata = {};
151
+ klass.constructor[Symbol.metadata] = metadata;
152
+ Object.defineProperty(metadata, -1, {
153
+ value: 0,
154
+ enumerable: false,
155
+ configurable: true,
156
+ });
157
+ },
158
+ initialize(constructor, parentMetadata) {
159
+ let metadata = constructor[Symbol.metadata] ?? Object.create(null);
160
+ // make sure inherited classes have their own metadata object.
161
+ if (constructor[Symbol.metadata] === parentMetadata) {
162
+ metadata = Object.create(null);
163
+ if (parentMetadata) {
164
+ // assign parent metadata to current
165
+ Object.assign(metadata, parentMetadata);
166
+ for (let i = 0; i <= parentMetadata[-1]; i++) {
167
+ Object.defineProperty(metadata, i, {
168
+ value: parentMetadata[i],
169
+ enumerable: false,
170
+ configurable: true,
171
+ });
172
+ }
173
+ Object.defineProperty(metadata, -1, {
174
+ value: parentMetadata[-1],
175
+ enumerable: false,
176
+ configurable: true,
177
+ writable: true,
178
+ });
179
+ }
180
+ }
181
+ constructor[Symbol.metadata] = metadata;
182
+ return metadata;
183
+ },
146
184
  isValidInstance(klass) {
147
185
  return (klass.constructor[Symbol.metadata] &&
148
186
  Object.prototype.hasOwnProperty.call(klass.constructor[Symbol.metadata], -1));
@@ -262,14 +300,12 @@ class ChangeTree {
262
300
  this.checkIsFiltered(parent, parentIndex);
263
301
  if (!this.isFiltered) {
264
302
  this.root.changes.set(this, this.changes);
303
+ this.root.allChanges.set(this, this.allChanges);
265
304
  }
266
305
  if (this.isFiltered || this.isPartiallyFiltered) {
267
306
  this.root.filteredChanges.set(this, this.filteredChanges);
268
307
  this.root.allFilteredChanges.set(this, this.filteredChanges);
269
308
  }
270
- else {
271
- this.root.allChanges.set(this, this.allChanges);
272
- }
273
309
  this.ensureRefId();
274
310
  this.forEachChild((changeTree, atIndex) => {
275
311
  changeTree.setParent(this.ref, root, atIndex);
@@ -361,7 +397,6 @@ class ChangeTree {
361
397
  }
362
398
  _shiftAllChangeIndexes(shiftIndex, startIndex = 0, allChangeSet) {
363
399
  Array.from(allChangeSet.entries()).forEach(([index, op]) => {
364
- // console.log('shiftAllChangeIndexes', index >= startIndex, { index, op, shiftIndex, startIndex })
365
400
  if (index >= startIndex) {
366
401
  allChangeSet.delete(index);
367
402
  allChangeSet.set(index + shiftIndex, op);
@@ -502,7 +537,7 @@ class ChangeTree {
502
537
  }
503
538
  checkIsFiltered(parent, parentIndex) {
504
539
  // Detect if current structure has "filters" declared
505
- this.isPartiallyFiltered = this.ref['constructor']?.[Symbol.metadata]?.[-2];
540
+ this.isPartiallyFiltered = (this.ref['constructor']?.[Symbol.metadata]?.[-2] !== undefined);
506
541
  // TODO: support "partially filtered", where the instance is visible, but only a field is not.
507
542
  // Detect if parent has "filters" declared
508
543
  while (parent && !this.isFiltered) {
@@ -563,26 +598,29 @@ try {
563
598
  textEncoder = new TextEncoder();
564
599
  }
565
600
  catch (e) { }
566
- function utf8Length(str) {
567
- var c = 0, length = 0;
568
- for (var i = 0, l = str.length; i < l; i++) {
569
- c = str.charCodeAt(i);
570
- if (c < 0x80) {
571
- length += 1;
572
- }
573
- else if (c < 0x800) {
574
- length += 2;
575
- }
576
- else if (c < 0xd800 || c >= 0xe000) {
577
- length += 3;
578
- }
579
- else {
580
- i++;
581
- length += 4;
601
+ const hasBufferByteLength = (typeof Buffer !== 'undefined' && Buffer.byteLength);
602
+ const utf8Length = (hasBufferByteLength)
603
+ ? Buffer.byteLength // node
604
+ : function (str, _) {
605
+ var c = 0, length = 0;
606
+ for (var i = 0, l = str.length; i < l; i++) {
607
+ c = str.charCodeAt(i);
608
+ if (c < 0x80) {
609
+ length += 1;
610
+ }
611
+ else if (c < 0x800) {
612
+ length += 2;
613
+ }
614
+ else if (c < 0xd800 || c >= 0xe000) {
615
+ length += 3;
616
+ }
617
+ else {
618
+ i++;
619
+ length += 4;
620
+ }
582
621
  }
583
- }
584
- return length;
585
- }
622
+ return length;
623
+ };
586
624
  function utf8Write(view, str, it) {
587
625
  var c = 0;
588
626
  for (var i = 0, l = str.length; i < l; i++) {
@@ -677,8 +715,7 @@ function string$1(bytes, value, it) {
677
715
  if (!value) {
678
716
  value = "";
679
717
  }
680
- // let length = utf8Length(value);
681
- let length = Buffer.byteLength(value, "utf8");
718
+ let length = utf8Length(value, "utf8");
682
719
  let size = 0;
683
720
  // fixstr
684
721
  if (length < 0x20) {
@@ -789,23 +826,23 @@ function number$1(bytes, value, it) {
789
826
 
790
827
  var encode = /*#__PURE__*/Object.freeze({
791
828
  __proto__: null,
792
- utf8Length: utf8Length,
793
- utf8Write: utf8Write,
794
- int8: int8$1,
795
- uint8: uint8$1,
829
+ boolean: boolean$1,
830
+ float32: float32$1,
831
+ float64: float64$1,
796
832
  int16: int16$1,
797
- uint16: uint16$1,
798
833
  int32: int32$1,
799
- uint32: uint32$1,
800
834
  int64: int64$1,
835
+ int8: int8$1,
836
+ number: number$1,
837
+ string: string$1,
838
+ uint16: uint16$1,
839
+ uint32: uint32$1,
801
840
  uint64: uint64$1,
802
- float32: float32$1,
803
- float64: float64$1,
841
+ uint8: uint8$1,
842
+ utf8Length: utf8Length,
843
+ utf8Write: utf8Write,
804
844
  writeFloat32: writeFloat32,
805
- writeFloat64: writeFloat64,
806
- boolean: boolean$1,
807
- string: string$1,
808
- number: number$1
845
+ writeFloat64: writeFloat64
809
846
  });
810
847
 
811
848
  class EncodeSchemaError extends Error {
@@ -947,6 +984,18 @@ const encodeKeyValueOperation = function (encoder, bytes, changeTree, field, ope
947
984
  }
948
985
  const type = changeTree.getType(field);
949
986
  const value = changeTree.getValue(field);
987
+ // try { throw new Error(); } catch (e) {
988
+ // // only print if not coming from Reflection.ts
989
+ // if (!e.stack.includes("src/Reflection.ts")) {
990
+ // console.log("encodeKeyValueOperation -> ", {
991
+ // ref: changeTree.ref.constructor.name,
992
+ // field,
993
+ // operation: OPERATION[operation],
994
+ // value: value?.toJSON(),
995
+ // items: ref.toJSON(),
996
+ // });
997
+ // }
998
+ // }
950
999
  // TODO: inline this function call small performance gain
951
1000
  encodeValue(encoder, bytes, ref, type, value, field, operation, it);
952
1001
  };
@@ -956,15 +1005,19 @@ const encodeKeyValueOperation = function (encoder, bytes, changeTree, field, ope
956
1005
  */
957
1006
  const encodeArray = function (encoder, bytes, changeTree, field, operation, it, isEncodeAll, hasView) {
958
1007
  const ref = changeTree.ref;
959
- if (hasView &&
960
- operation === exports.OPERATION.DELETE &&
961
- typeof (changeTree.getType(field)) !== "string") {
962
- // encode delete by refId (array of schemas)
963
- bytes[it.offset++] = exports.OPERATION.DELETE_BY_REFID;
964
- const value = ref['tmpItems'][field];
965
- const refId = value[$changes].refId;
966
- number$1(bytes, refId, it);
967
- return;
1008
+ const useOperationByRefId = hasView && changeTree.isFiltered && (typeof (changeTree.getType(field)) !== "string");
1009
+ let refOrIndex;
1010
+ if (useOperationByRefId) {
1011
+ refOrIndex = ref['tmpItems'][field][$changes].refId;
1012
+ if (operation === exports.OPERATION.DELETE) {
1013
+ operation = exports.OPERATION.DELETE_BY_REFID;
1014
+ }
1015
+ else if (operation === exports.OPERATION.ADD) {
1016
+ operation = exports.OPERATION.ADD_BY_REFID;
1017
+ }
1018
+ }
1019
+ else {
1020
+ refOrIndex = field;
968
1021
  }
969
1022
  // encode operation
970
1023
  bytes[it.offset++] = operation & 255;
@@ -973,7 +1026,7 @@ const encodeArray = function (encoder, bytes, changeTree, field, operation, it,
973
1026
  return;
974
1027
  }
975
1028
  // encode index
976
- number$1(bytes, field, it);
1029
+ number$1(bytes, refOrIndex, it);
977
1030
  // Do not encode value for DELETE operations
978
1031
  if (operation === exports.OPERATION.DELETE) {
979
1032
  return;
@@ -1013,9 +1066,9 @@ const encodeArray = function (encoder, bytes, changeTree, field, operation, it,
1013
1066
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1014
1067
  * SOFTWARE
1015
1068
  */
1016
- function utf8Read(bytes, offset, length) {
1069
+ function utf8Read(bytes, it, length) {
1017
1070
  var string = '', chr = 0;
1018
- for (var i = offset, end = offset + length; i < end; i++) {
1071
+ for (var i = it.offset, end = it.offset + length; i < end; i++) {
1019
1072
  var byte = bytes[i];
1020
1073
  if ((byte & 0x80) === 0x00) {
1021
1074
  string += String.fromCharCode(byte);
@@ -1050,6 +1103,7 @@ function utf8Read(bytes, offset, length) {
1050
1103
  // (do not throw error to avoid server/client from crashing due to hack attemps)
1051
1104
  // throw new Error('Invalid byte ' + byte.toString(16));
1052
1105
  }
1106
+ it.offset += length;
1053
1107
  return string;
1054
1108
  }
1055
1109
  function int8(bytes, it) {
@@ -1117,9 +1171,7 @@ function string(bytes, it) {
1117
1171
  else if (prefix === 0xdb) {
1118
1172
  length = uint32(bytes, it);
1119
1173
  }
1120
- const value = utf8Read(bytes, it.offset, length);
1121
- it.offset += length;
1122
- return value;
1174
+ return utf8Read(bytes, it, length);
1123
1175
  }
1124
1176
  function stringCheck(bytes, it) {
1125
1177
  const prefix = bytes[it.offset];
@@ -1223,30 +1275,31 @@ function switchStructureCheck(bytes, it) {
1223
1275
 
1224
1276
  var decode = /*#__PURE__*/Object.freeze({
1225
1277
  __proto__: null,
1226
- int8: int8,
1227
- uint8: uint8,
1228
- int16: int16,
1229
- uint16: uint16,
1230
- int32: int32,
1231
- uint32: uint32,
1278
+ arrayCheck: arrayCheck,
1279
+ boolean: boolean,
1232
1280
  float32: float32,
1233
1281
  float64: float64,
1282
+ int16: int16,
1283
+ int32: int32,
1234
1284
  int64: int64,
1235
- uint64: uint64,
1285
+ int8: int8,
1286
+ number: number,
1287
+ numberCheck: numberCheck,
1236
1288
  readFloat32: readFloat32,
1237
1289
  readFloat64: readFloat64,
1238
- boolean: boolean,
1239
1290
  string: string,
1240
1291
  stringCheck: stringCheck,
1241
- number: number,
1242
- numberCheck: numberCheck,
1243
- arrayCheck: arrayCheck,
1244
- switchStructureCheck: switchStructureCheck
1292
+ switchStructureCheck: switchStructureCheck,
1293
+ uint16: uint16,
1294
+ uint32: uint32,
1295
+ uint64: uint64,
1296
+ uint8: uint8,
1297
+ utf8Read: utf8Read
1245
1298
  });
1246
1299
 
1247
1300
  const DEFINITION_MISMATCH = -1;
1248
1301
  function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges) {
1249
- const $root = decoder.$root;
1302
+ const $root = decoder.root;
1250
1303
  const previousValue = ref[$getByIndex](index);
1251
1304
  let value;
1252
1305
  if ((operation & exports.OPERATION.DELETE) === exports.OPERATION.DELETE) {
@@ -1432,7 +1485,8 @@ const decodeKeyValueOperation = function (decoder, bytes, it, ref, allChanges) {
1432
1485
  };
1433
1486
  const decodeArray = function (decoder, bytes, it, ref, allChanges) {
1434
1487
  // "uncompressed" index + operation (array/map items)
1435
- const operation = bytes[it.offset++];
1488
+ let operation = bytes[it.offset++];
1489
+ let index;
1436
1490
  if (operation === exports.OPERATION.CLEAR) {
1437
1491
  //
1438
1492
  // When decoding:
@@ -1446,8 +1500,8 @@ const decodeArray = function (decoder, bytes, it, ref, allChanges) {
1446
1500
  else if (operation === exports.OPERATION.DELETE_BY_REFID) {
1447
1501
  // TODO: refactor here, try to follow same flow as below
1448
1502
  const refId = number(bytes, it);
1449
- const previousValue = decoder.$root.refs.get(refId);
1450
- const index = ref.findIndex((value) => value === previousValue);
1503
+ const previousValue = decoder.root.refs.get(refId);
1504
+ index = ref.findIndex((value) => value === previousValue);
1451
1505
  ref[$deleteByIndex](index);
1452
1506
  allChanges.push({
1453
1507
  ref,
@@ -1460,7 +1514,18 @@ const decodeArray = function (decoder, bytes, it, ref, allChanges) {
1460
1514
  });
1461
1515
  return;
1462
1516
  }
1463
- const index = number(bytes, it);
1517
+ else if (operation === exports.OPERATION.ADD_BY_REFID) {
1518
+ // operation = OPERATION.ADD;
1519
+ const refId = number(bytes, it);
1520
+ const itemByRefId = decoder.root.refs.get(refId);
1521
+ // use existing index, or push new value
1522
+ index = (itemByRefId)
1523
+ ? ref.findIndex((value) => value === itemByRefId)
1524
+ : ref.length;
1525
+ }
1526
+ else {
1527
+ index = number(bytes, it);
1528
+ }
1464
1529
  const type = ref[$childType];
1465
1530
  let dynamicIndex = index;
1466
1531
  const { value, previousValue } = decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges);
@@ -1869,14 +1934,6 @@ class ArraySchema {
1869
1934
  lastIndexOf(searchElement, fromIndex = this.length - 1) {
1870
1935
  return this.items.lastIndexOf(searchElement, fromIndex);
1871
1936
  }
1872
- /**
1873
- * Determines whether all the members of an array satisfy the specified test.
1874
- * @param callbackfn A function that accepts up to three arguments. The every method calls
1875
- * the callbackfn function for each element in the array until the callbackfn returns a value
1876
- * which is coercible to the Boolean value false, or until the end of the array.
1877
- * @param thisArg An object to which the this keyword can refer in the callbackfn function.
1878
- * If thisArg is omitted, undefined is used as the this value.
1879
- */
1880
1937
  every(callbackfn, thisArg) {
1881
1938
  return this.items.every(callbackfn, thisArg);
1882
1939
  }
@@ -2555,6 +2612,7 @@ function view(tag = DEFAULT_VIEW_TAG) {
2555
2612
  const constructor = target.constructor;
2556
2613
  const parentClass = Object.getPrototypeOf(constructor);
2557
2614
  const parentMetadata = parentClass[Symbol.metadata];
2615
+ // TODO: use Metadata.initialize()
2558
2616
  const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
2559
2617
  if (!metadata[fieldName]) {
2560
2618
  //
@@ -2579,8 +2637,8 @@ function type(type, options) {
2579
2637
  // for inheritance support
2580
2638
  TypeContext.register(constructor);
2581
2639
  const parentClass = Object.getPrototypeOf(constructor);
2582
- const parentMetadata = parentClass[Symbol.metadata];
2583
- const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
2640
+ const parentMetadata = parentClass && parentClass[Symbol.metadata];
2641
+ const metadata = Metadata.initialize(constructor, parentMetadata);
2584
2642
  let fieldIndex;
2585
2643
  /**
2586
2644
  * skip if descriptor already exists for this field (`@deprecated()`)
@@ -3353,6 +3411,8 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
3353
3411
  OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
3354
3412
  PERFORMANCE OF THIS SOFTWARE.
3355
3413
  ***************************************************************************** */
3414
+ /* global Reflect, Promise, SuppressedError, Symbol */
3415
+
3356
3416
 
3357
3417
  function __decorate(decorators, target, key, desc) {
3358
3418
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -3384,11 +3444,14 @@ class Encoder {
3384
3444
  setRoot(state) {
3385
3445
  this.root = new Root();
3386
3446
  this.state = state;
3447
+ // Workaround to allow using an empty Schema.
3448
+ if (state.constructor[Symbol.metadata] === undefined) {
3449
+ Metadata.init(state);
3450
+ }
3387
3451
  state[$changes].setRoot(this.root);
3388
3452
  }
3389
- encode(it = { offset: 0 }, view, bytes = this.sharedBuffer, changeTrees = this.root.changes) {
3453
+ encode(it = { offset: 0 }, view, buffer = this.sharedBuffer, changeTrees = this.root.changes, isEncodeAll = this.root.allChanges === changeTrees) {
3390
3454
  const initialOffset = it.offset; // cache current offset in case we need to resize the buffer
3391
- const isEncodeAll = this.root.allChanges === changeTrees;
3392
3455
  const hasView = (view !== undefined);
3393
3456
  const rootChangeTree = this.state[$changes];
3394
3457
  const changeTreesIterator = changeTrees.entries();
@@ -3397,6 +3460,12 @@ class Encoder {
3397
3460
  const ctor = ref['constructor'];
3398
3461
  const encoder = ctor[$encoder];
3399
3462
  const filter = ctor[$filter];
3463
+ // try { throw new Error(); } catch (e) {
3464
+ // // only print if not coming from Reflection.ts
3465
+ // if (!e.stack.includes("src/Reflection.ts")) {
3466
+ // console.log("ChangeTree:", { ref: ref.constructor.name, });
3467
+ // }
3468
+ // }
3400
3469
  if (hasView) {
3401
3470
  if (!view.items.has(changeTree)) {
3402
3471
  view.invisible.add(changeTree);
@@ -3408,8 +3477,8 @@ class Encoder {
3408
3477
  }
3409
3478
  // skip root `refId` if it's the first change tree
3410
3479
  if (it.offset !== initialOffset || changeTree !== rootChangeTree) {
3411
- bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
3412
- number$1(bytes, changeTree.refId, it);
3480
+ buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
3481
+ number$1(buffer, changeTree.refId, it);
3413
3482
  }
3414
3483
  const changesIterator = changes.entries();
3415
3484
  for (const [fieldIndex, operation] of changesIterator) {
@@ -3426,22 +3495,31 @@ class Encoder {
3426
3495
  // view?.invisible.add(changeTree);
3427
3496
  continue;
3428
3497
  }
3429
- // console.log("WILL ENCODE", {
3430
- // ref: changeTree.ref.constructor.name,
3431
- // fieldIndex,
3432
- // operation: OPERATION[operation],
3433
- // });
3434
- encoder(this, bytes, changeTree, fieldIndex, operation, it, isEncodeAll, hasView);
3498
+ // try { throw new Error(); } catch (e) {
3499
+ // // only print if not coming from Reflection.ts
3500
+ // if (!e.stack.includes("src/Reflection.ts")) {
3501
+ // console.log("WILL ENCODE", {
3502
+ // ref: changeTree.ref.constructor.name,
3503
+ // fieldIndex,
3504
+ // operation: OPERATION[operation],
3505
+ // });
3506
+ // }
3507
+ // }
3508
+ encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView);
3435
3509
  }
3436
3510
  }
3437
- if (it.offset > bytes.byteLength) {
3438
- const newSize = getNextPowerOf2(this.sharedBuffer.byteLength * 2);
3439
- console.warn("@colyseus/schema encode buffer overflow. Current buffer size: " + bytes.byteLength + ", encoding offset: " + it.offset + ", new size: " + newSize);
3511
+ if (it.offset > buffer.byteLength) {
3512
+ const newSize = getNextPowerOf2(buffer.byteLength * 2);
3513
+ console.warn("@colyseus/schema encode buffer overflow. Current buffer size: " + buffer.byteLength + ", encoding offset: " + it.offset + ", new size: " + newSize);
3440
3514
  //
3441
3515
  // resize buffer and re-encode (TODO: can we avoid re-encoding here?)
3442
3516
  //
3443
- this.sharedBuffer = Buffer.allocUnsafeSlow(newSize);
3444
- return this.encode({ offset: initialOffset }, view);
3517
+ buffer = Buffer.allocUnsafeSlow(newSize);
3518
+ // assign resized buffer to local sharedBuffer
3519
+ if (buffer === this.sharedBuffer) {
3520
+ this.sharedBuffer = buffer;
3521
+ }
3522
+ return this.encode({ offset: initialOffset }, view, buffer, changeTrees, isEncodeAll);
3445
3523
  }
3446
3524
  else {
3447
3525
  //
@@ -3453,38 +3531,37 @@ class Encoder {
3453
3531
  //
3454
3532
  this.onEndEncode(changeTrees);
3455
3533
  }
3456
- // return bytes;
3457
- return bytes.slice(0, it.offset);
3534
+ return buffer.subarray(0, it.offset);
3458
3535
  }
3459
3536
  }
3460
- encodeAll(it = { offset: 0 }) {
3461
- // console.log(`encodeAll(), this.$root.allChanges (${this.$root.allChanges.size})`);
3462
- // Array.from(this.$root.allChanges.entries()).map((item) => {
3463
- // console.log("->", item[0].refId, item[0].ref.toJSON());
3537
+ encodeAll(it = { offset: 0 }, buffer = this.sharedBuffer) {
3538
+ // console.log(`encodeAll(), this.root.allChanges (${this.root.allChanges.size})`);
3539
+ // Array.from(this.root.allChanges.entries()).map((item) => {
3540
+ // console.log("->", { ref: item[0].ref.constructor.name, refId: item[0].refId, changes: item[1].size });
3464
3541
  // });
3465
- return this.encode(it, undefined, this.sharedBuffer, this.root.allChanges);
3542
+ return this.encode(it, undefined, buffer, this.root.allChanges, true);
3466
3543
  }
3467
3544
  encodeAllView(view, sharedOffset, it, bytes = this.sharedBuffer) {
3468
3545
  const viewOffset = it.offset;
3469
- // console.log(`encodeAllView(), this.$root.allFilteredChanges (${this.$root.allFilteredChanges.size})`);
3546
+ // console.log(`encodeAllView(), this.root.allFilteredChanges (${this.root.allFilteredChanges.size})`);
3470
3547
  // this.debugAllFilteredChanges();
3471
3548
  // try to encode "filtered" changes
3472
- this.encode(it, view, bytes, this.root.allFilteredChanges);
3549
+ this.encode(it, view, bytes, this.root.allFilteredChanges, true);
3473
3550
  return Buffer.concat([
3474
- bytes.slice(0, sharedOffset),
3475
- bytes.slice(viewOffset, it.offset)
3551
+ bytes.subarray(0, sharedOffset),
3552
+ bytes.subarray(viewOffset, it.offset)
3476
3553
  ]);
3477
3554
  }
3478
- // debugAllFilteredChanges() {
3479
- // Array.from(this.$root.allFilteredChanges.entries()).map((item) => {
3480
- // console.log("->", { refId: item[0].refId }, item[0].ref.toJSON());
3481
- // if (Array.isArray(item[0].ref.toJSON())) {
3482
- // item[1].forEach((op, key) => {
3483
- // console.log(" ->", { key, op: OPERATION[op] });
3484
- // })
3485
- // }
3486
- // });
3487
- // }
3555
+ debugAllFilteredChanges() {
3556
+ Array.from(this.root.allFilteredChanges.entries()).map((item) => {
3557
+ console.log("->", { refId: item[0].refId, changes: item[1].size }, item[0].ref.toJSON());
3558
+ if (Array.isArray(item[0].ref.toJSON())) {
3559
+ item[1].forEach((op, key) => {
3560
+ console.log(" ->", { key, op: exports.OPERATION[op] });
3561
+ });
3562
+ }
3563
+ });
3564
+ }
3488
3565
  encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
3489
3566
  const viewOffset = it.offset;
3490
3567
  // try to encode "filtered" changes
@@ -3516,8 +3593,8 @@ class Encoder {
3516
3593
  // clear "view" changes after encoding
3517
3594
  view.changes.clear();
3518
3595
  return Buffer.concat([
3519
- bytes.slice(0, sharedOffset),
3520
- bytes.slice(viewOffset, it.offset)
3596
+ bytes.subarray(0, sharedOffset),
3597
+ bytes.subarray(viewOffset, it.offset)
3521
3598
  ]);
3522
3599
  }
3523
3600
  onEndEncode(changeTrees = this.root.changes) {
@@ -3696,12 +3773,12 @@ class Decoder {
3696
3773
  }
3697
3774
  setRoot(root) {
3698
3775
  this.state = root;
3699
- this.$root = new ReferenceTracker();
3700
- this.$root.addRef(0, root);
3776
+ this.root = new ReferenceTracker();
3777
+ this.root.addRef(0, root);
3701
3778
  }
3702
3779
  decode(bytes, it = { offset: 0 }, ref = this.state) {
3703
3780
  const allChanges = [];
3704
- const $root = this.$root;
3781
+ const $root = this.root;
3705
3782
  const totalBytes = bytes.byteLength;
3706
3783
  let decoder = ref['constructor'][$decoder];
3707
3784
  this.currentRefId = 0;
@@ -3782,7 +3859,7 @@ class Decoder {
3782
3859
  previousValue: value
3783
3860
  });
3784
3861
  if (needRemoveRef) {
3785
- this.$root.removeRef(this.$root.refIds.get(value));
3862
+ this.root.removeRef(this.root.refIds.get(value));
3786
3863
  }
3787
3864
  });
3788
3865
  }
@@ -3822,7 +3899,7 @@ class Reflection extends Schema {
3822
3899
  super(...arguments);
3823
3900
  this.types = new ArraySchema();
3824
3901
  }
3825
- static encode(instance, context) {
3902
+ static encode(instance, context, it = { offset: 0 }) {
3826
3903
  if (!context) {
3827
3904
  context = new TypeContext(instance.constructor);
3828
3905
  }
@@ -3879,7 +3956,6 @@ class Reflection extends Schema {
3879
3956
  }
3880
3957
  buildType(type, klass[Symbol.metadata]);
3881
3958
  }
3882
- const it = { offset: 0 };
3883
3959
  const buf = encoder.encodeAll(it);
3884
3960
  return Buffer.from(buf, 0, it.offset);
3885
3961
  }
@@ -3887,59 +3963,298 @@ class Reflection extends Schema {
3887
3963
  const reflection = new Reflection();
3888
3964
  const reflectionDecoder = new Decoder(reflection);
3889
3965
  reflectionDecoder.decode(bytes, it);
3890
- const context = new TypeContext();
3891
- const schemaTypes = reflection.types.reduce((types, reflectionType) => {
3892
- const parentKlass = types[reflectionType.extendsId] || Schema;
3893
- const schema = class _ extends parentKlass {
3966
+ const typeContext = new TypeContext();
3967
+ // 1st pass, initialize metadata + inheritance
3968
+ reflection.types.forEach((reflectionType) => {
3969
+ const parentClass = typeContext.get(reflectionType.extendsId) ?? Schema;
3970
+ const schema = class _ extends parentClass {
3894
3971
  };
3895
- // const _metadata = Object.create(_classSuper[Symbol.metadata] ?? null);
3896
- const _metadata = parentKlass && parentKlass[Symbol.metadata] || Object.create(null);
3897
- Object.defineProperty(schema, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
3972
+ const parentMetadata = parentClass[Symbol.metadata];
3898
3973
  // register for inheritance support
3899
3974
  TypeContext.register(schema);
3900
- const typeid = reflectionType.id;
3901
- types[typeid] = schema;
3902
- context.add(schema, typeid);
3903
- return types;
3975
+ // for inheritance support
3976
+ Metadata.initialize(schema, parentMetadata);
3977
+ typeContext.add(schema, reflectionType.id);
3904
3978
  }, {});
3979
+ // 2nd pass, set fields
3905
3980
  reflection.types.forEach((reflectionType) => {
3906
- const schemaType = schemaTypes[reflectionType.id];
3981
+ const schemaType = typeContext.get(reflectionType.id);
3907
3982
  const metadata = schemaType[Symbol.metadata];
3908
- const parentKlass = reflection.types[reflectionType.extendsId];
3909
- const parentFieldIndex = parentKlass && parentKlass.fields.length || 0;
3983
+ // FIXME: use metadata[-1] to get field count
3984
+ const parentFieldIndex = 0;
3985
+ // console.log("--------------------");
3986
+ // // console.log("reflectionType", reflectionType.toJSON());
3987
+ // console.log("reflectionType.fields", reflectionType.fields.toJSON());
3988
+ // console.log("parentFieldIndex", parentFieldIndex);
3989
+ //
3990
+ // FIXME: set fields using parentKlass as well
3991
+ // currently the fields are duplicated on inherited classes
3992
+ //
3993
+ // // const parentKlass = reflection.types[reflectionType.extendsId];
3994
+ // // parentKlass.fields
3910
3995
  reflectionType.fields.forEach((field, i) => {
3911
3996
  const fieldIndex = parentFieldIndex + i;
3912
3997
  if (field.referencedType !== undefined) {
3913
3998
  let fieldType = field.type;
3914
- let refType = schemaTypes[field.referencedType];
3999
+ let refType = typeContext.get(field.referencedType);
3915
4000
  // map or array of primitive type (-1)
3916
4001
  if (!refType) {
3917
4002
  const typeInfo = field.type.split(":");
3918
4003
  fieldType = typeInfo[0];
3919
- refType = typeInfo[1];
4004
+ refType = typeInfo[1]; // string
3920
4005
  }
3921
4006
  if (fieldType === "ref") {
3922
- // type(refType)(schemaType.prototype, field.name);
3923
4007
  Metadata.addField(metadata, fieldIndex, field.name, refType);
3924
4008
  }
3925
4009
  else {
3926
- // type({ [fieldType]: refType } as DefinitionType)(schemaType.prototype, field.name);
3927
4010
  Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType });
3928
4011
  }
3929
4012
  }
3930
4013
  else {
3931
- // type(field.type as PrimitiveType)(schemaType.prototype, field.name);
3932
4014
  Metadata.addField(metadata, fieldIndex, field.name, field.type);
3933
4015
  }
3934
4016
  });
3935
4017
  });
3936
- return new (schemaTypes[0])();
4018
+ // @ts-ignore
4019
+ return new (typeContext.get(0))();
3937
4020
  }
3938
4021
  }
3939
4022
  __decorate([
3940
4023
  type([ReflectionType])
3941
4024
  ], Reflection.prototype, "types", void 0);
3942
4025
 
4026
+ function getDecoderStateCallbacks(decoder) {
4027
+ const $root = decoder.root;
4028
+ const callbacks = $root.callbacks;
4029
+ let isTriggeringOnAdd = false;
4030
+ decoder.triggerChanges = function (allChanges) {
4031
+ const uniqueRefIds = new Set();
4032
+ for (let i = 0, l = allChanges.length; i < l; i++) {
4033
+ const change = allChanges[i];
4034
+ const refId = change.refId;
4035
+ const ref = change.ref;
4036
+ const $callbacks = callbacks[refId];
4037
+ if (!$callbacks) {
4038
+ continue;
4039
+ }
4040
+ //
4041
+ // trigger onRemove on child structure.
4042
+ //
4043
+ if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE &&
4044
+ change.previousValue instanceof Schema) {
4045
+ const deleteCallbacks = callbacks[$root.refIds.get(change.previousValue)]?.[exports.OPERATION.DELETE];
4046
+ for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
4047
+ deleteCallbacks[i]();
4048
+ }
4049
+ }
4050
+ if (ref instanceof Schema) {
4051
+ //
4052
+ // Handle schema instance
4053
+ //
4054
+ if (!uniqueRefIds.has(refId)) {
4055
+ // trigger onChange
4056
+ const replaceCallbacks = $callbacks?.[exports.OPERATION.REPLACE];
4057
+ for (let i = replaceCallbacks?.length - 1; i >= 0; i--) {
4058
+ replaceCallbacks[i]();
4059
+ // try {
4060
+ // } catch (e) {
4061
+ // console.error(e);
4062
+ // }
4063
+ }
4064
+ }
4065
+ if ($callbacks.hasOwnProperty(change.field)) {
4066
+ const fieldCallbacks = $callbacks[change.field];
4067
+ for (let i = fieldCallbacks?.length - 1; i >= 0; i--) {
4068
+ fieldCallbacks[i](change.value, change.previousValue);
4069
+ // try {
4070
+ // } catch (e) {
4071
+ // console.error(e);
4072
+ // }
4073
+ }
4074
+ }
4075
+ }
4076
+ else {
4077
+ //
4078
+ // Handle collection of items
4079
+ //
4080
+ if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE) {
4081
+ //
4082
+ // FIXME: `previousValue` should always be available.
4083
+ //
4084
+ if (change.previousValue !== undefined) {
4085
+ // triger onRemove
4086
+ const deleteCallbacks = $callbacks[exports.OPERATION.DELETE];
4087
+ for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
4088
+ deleteCallbacks[i](change.previousValue, change.dynamicIndex ?? change.field);
4089
+ }
4090
+ }
4091
+ // Handle DELETE_AND_ADD operations
4092
+ // FIXME: should we set "isTriggeringOnAdd" here?
4093
+ if ((change.op & exports.OPERATION.ADD) === exports.OPERATION.ADD) {
4094
+ const addCallbacks = $callbacks[exports.OPERATION.ADD];
4095
+ for (let i = addCallbacks?.length - 1; i >= 0; i--) {
4096
+ addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
4097
+ }
4098
+ }
4099
+ }
4100
+ else if ((change.op & exports.OPERATION.ADD) === exports.OPERATION.ADD && change.previousValue === undefined) {
4101
+ // triger onAdd
4102
+ isTriggeringOnAdd = true;
4103
+ const addCallbacks = $callbacks[exports.OPERATION.ADD];
4104
+ for (let i = addCallbacks?.length - 1; i >= 0; i--) {
4105
+ addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
4106
+ }
4107
+ isTriggeringOnAdd = false;
4108
+ }
4109
+ // trigger onChange
4110
+ if (change.value !== change.previousValue) {
4111
+ const replaceCallbacks = $callbacks[exports.OPERATION.REPLACE];
4112
+ for (let i = replaceCallbacks?.length - 1; i >= 0; i--) {
4113
+ replaceCallbacks[i](change.value, change.dynamicIndex ?? change.field);
4114
+ }
4115
+ }
4116
+ }
4117
+ uniqueRefIds.add(refId);
4118
+ }
4119
+ };
4120
+ function getProxy(metadataOrType, context) {
4121
+ let metadata = context.instance?.constructor[Symbol.metadata] || metadataOrType;
4122
+ let isCollection = ((context.instance && typeof (context.instance['forEach']) === "function") ||
4123
+ (metadataOrType && typeof (metadataOrType[Symbol.metadata]) === "undefined"));
4124
+ if (metadata && !isCollection) {
4125
+ const onAdd = function (ref, prop, callback, immediate) {
4126
+ // immediate trigger
4127
+ if (immediate &&
4128
+ context.instance[prop] !== undefined &&
4129
+ !isTriggeringOnAdd // FIXME: This is a workaround (https://github.com/colyseus/schema/issues/147)
4130
+ ) {
4131
+ callback(context.instance[prop], undefined);
4132
+ }
4133
+ return $root.addCallback($root.refIds.get(ref), prop, callback);
4134
+ };
4135
+ /**
4136
+ * Schema instances
4137
+ */
4138
+ return new Proxy({
4139
+ listen: function listen(prop, callback, immediate = true) {
4140
+ if (context.instance) {
4141
+ return onAdd(context.instance, prop, callback, immediate);
4142
+ }
4143
+ else {
4144
+ // collection instance not received yet
4145
+ context.onInstanceAvailable((ref, existing) => onAdd(ref, prop, callback, immediate && existing));
4146
+ }
4147
+ },
4148
+ onChange: function onChange(callback) {
4149
+ return $root.addCallback($root.refIds.get(context.instance), exports.OPERATION.REPLACE, callback);
4150
+ },
4151
+ bindTo: function bindTo(targetObject, properties) {
4152
+ //
4153
+ // TODO: refactor this implementation. There is room for improvement here.
4154
+ //
4155
+ if (!properties) {
4156
+ properties = Object.keys(metadata);
4157
+ }
4158
+ return $root.addCallback($root.refIds.get(context.instance), exports.OPERATION.REPLACE, () => {
4159
+ properties.forEach((prop) => targetObject[prop] = context.instance[prop]);
4160
+ });
4161
+ }
4162
+ }, {
4163
+ get(target, prop) {
4164
+ if (metadata[prop]) {
4165
+ const instance = context.instance?.[prop];
4166
+ const onInstanceAvailable = ((callback) => {
4167
+ const unbind = $(context.instance).listen(prop, (value, _) => {
4168
+ callback(value, false);
4169
+ // FIXME: by "unbinding" the callback here,
4170
+ // it will not support when the server
4171
+ // re-instantiates the instance.
4172
+ //
4173
+ unbind?.();
4174
+ }, false);
4175
+ // has existing value
4176
+ if ($root.refIds.get(instance) !== undefined) {
4177
+ callback(instance, true);
4178
+ }
4179
+ });
4180
+ return getProxy(metadata[prop].type, {
4181
+ instance,
4182
+ parentInstance: context.instance,
4183
+ onInstanceAvailable,
4184
+ });
4185
+ }
4186
+ else {
4187
+ // accessing the function
4188
+ return target[prop];
4189
+ }
4190
+ },
4191
+ has(target, prop) { return metadata[prop] !== undefined; },
4192
+ set(_, _1, _2) { throw new Error("not allowed"); },
4193
+ deleteProperty(_, _1) { throw new Error("not allowed"); },
4194
+ });
4195
+ }
4196
+ else {
4197
+ /**
4198
+ * Collection instances
4199
+ */
4200
+ const onAdd = function (ref, callback, immediate) {
4201
+ // Trigger callback on existing items
4202
+ if (immediate) {
4203
+ ref.forEach((v, k) => callback(v, k));
4204
+ }
4205
+ return $root.addCallback($root.refIds.get(ref), exports.OPERATION.ADD, callback);
4206
+ };
4207
+ const onRemove = function (ref, callback) {
4208
+ return $root.addCallback($root.refIds.get(ref), exports.OPERATION.DELETE, callback);
4209
+ };
4210
+ return new Proxy({
4211
+ onAdd: function (callback, immediate = true) {
4212
+ //
4213
+ // https://github.com/colyseus/schema/issues/147
4214
+ // If parent instance has "onAdd" registered, avoid triggering immediate callback.
4215
+ //
4216
+ // FIXME: "isTriggeringOnAdd" is a workaround. We should find a better way to handle this.
4217
+ //
4218
+ if (context.onInstanceAvailable) {
4219
+ // collection instance not received yet
4220
+ context.onInstanceAvailable((ref, existing) => onAdd(ref, callback, immediate && existing && !isTriggeringOnAdd));
4221
+ }
4222
+ else if (context.instance) {
4223
+ onAdd(context.instance, callback, immediate && !isTriggeringOnAdd);
4224
+ }
4225
+ },
4226
+ onRemove: function (callback) {
4227
+ if (context.onInstanceAvailable) {
4228
+ // collection instance not received yet
4229
+ context.onInstanceAvailable((ref) => onRemove(ref, callback));
4230
+ }
4231
+ else if (context.instance) {
4232
+ onRemove(context.instance, callback);
4233
+ }
4234
+ },
4235
+ }, {
4236
+ get(target, prop) {
4237
+ if (!target[prop]) {
4238
+ throw new Error(`Can't access '${prop}' through callback proxy. access the instance directly.`);
4239
+ }
4240
+ return target[prop];
4241
+ },
4242
+ has(target, prop) { return target[prop] !== undefined; },
4243
+ set(_, _1, _2) { throw new Error("not allowed"); },
4244
+ deleteProperty(_, _1) { throw new Error("not allowed"); },
4245
+ });
4246
+ }
4247
+ }
4248
+ function $(instance) {
4249
+ return getProxy(undefined, { instance });
4250
+ }
4251
+ return $;
4252
+ }
4253
+
4254
+ function getRawChangesCallback(decoder, callback) {
4255
+ decoder.triggerChanges = callback;
4256
+ }
4257
+
3943
4258
  class StateView {
3944
4259
  constructor() {
3945
4260
  /**
@@ -3962,12 +4277,18 @@ class StateView {
3962
4277
  console.warn("StateView#add(), invalid object:", obj);
3963
4278
  return this;
3964
4279
  }
4280
+ // FIXME: ArraySchema/MapSchema does not have metadata
4281
+ const metadata = obj.constructor[Symbol.metadata];
3965
4282
  let changeTree = obj[$changes];
3966
4283
  this.items.add(changeTree);
3967
4284
  // Add children of this ChangeTree to this view
3968
- changeTree.forEachChild((change, _) => this.add(change.ref, tag));
3969
- // FIXME: ArraySchema/MapSchema does not have metadata
3970
- const metadata = obj.constructor[Symbol.metadata];
4285
+ changeTree.forEachChild((change, index) => {
4286
+ // Do not ADD children that don't have the same tag
4287
+ if (metadata && metadata[metadata[index]].tag !== tag) {
4288
+ return;
4289
+ }
4290
+ this.add(change.ref, tag);
4291
+ });
3971
4292
  // add parent ChangeTree's, if they are invisible to this view
3972
4293
  // TODO: REFACTOR addParent()
3973
4294
  this.addParent(changeTree, tag);
@@ -3994,7 +4315,6 @@ class StateView {
3994
4315
  tags = this.tags.get(changeTree);
3995
4316
  }
3996
4317
  tags.add(tag);
3997
- // console.log("BY TAG:", tag);
3998
4318
  // Ref: add tagged properties
3999
4319
  metadata?.[-3]?.[tag]?.forEach((index) => {
4000
4320
  if (changeTree.getChange(index) !== exports.OPERATION.DELETE) {
@@ -4164,6 +4484,8 @@ exports.dumpChanges = dumpChanges;
4164
4484
  exports.encode = encode;
4165
4485
  exports.encodeKeyValueOperation = encodeArray;
4166
4486
  exports.encodeSchemaOperation = encodeSchemaOperation;
4487
+ exports.getDecoderStateCallbacks = getDecoderStateCallbacks;
4488
+ exports.getRawChangesCallback = getRawChangesCallback;
4167
4489
  exports.registerType = registerType;
4168
4490
  exports.type = type;
4169
4491
  exports.view = view;