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

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 (103) hide show
  1. package/README.md +131 -61
  2. package/build/cjs/index.js +476 -150
  3. package/build/cjs/index.js.map +1 -1
  4. package/build/esm/index.mjs +475 -149
  5. package/build/esm/index.mjs.map +1 -1
  6. package/build/umd/index.js +476 -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/debug.d.ts +1 -0
  40. package/lib/debug.js +52 -0
  41. package/lib/debug.js.map +1 -0
  42. package/lib/decoder/DecodeOperation.d.ts +0 -1
  43. package/lib/decoder/DecodeOperation.js +22 -7
  44. package/lib/decoder/DecodeOperation.js.map +1 -1
  45. package/lib/decoder/Decoder.d.ts +4 -5
  46. package/lib/decoder/Decoder.js +4 -4
  47. package/lib/decoder/Decoder.js.map +1 -1
  48. package/lib/decoder/strategy/RawChanges.js +1 -2
  49. package/lib/decoder/strategy/RawChanges.js.map +1 -1
  50. package/lib/decoder/strategy/StateCallbacks.d.ts +36 -7
  51. package/lib/decoder/strategy/StateCallbacks.js +39 -46
  52. package/lib/decoder/strategy/StateCallbacks.js.map +1 -1
  53. package/lib/encoder/ChangeTree.js +2 -5
  54. package/lib/encoder/ChangeTree.js.map +1 -1
  55. package/lib/encoder/EncodeOperation.d.ts +0 -1
  56. package/lib/encoder/EncodeOperation.js +29 -13
  57. package/lib/encoder/EncodeOperation.js.map +1 -1
  58. package/lib/encoder/Encoder.d.ts +5 -4
  59. package/lib/encoder/Encoder.js +60 -38
  60. package/lib/encoder/Encoder.js.map +1 -1
  61. package/lib/encoder/StateView.js +11 -6
  62. package/lib/encoder/StateView.js.map +1 -1
  63. package/lib/encoding/assert.js +3 -3
  64. package/lib/encoding/assert.js.map +1 -1
  65. package/lib/encoding/decode.d.ts +21 -19
  66. package/lib/encoding/decode.js +24 -25
  67. package/lib/encoding/decode.js.map +1 -1
  68. package/lib/encoding/encode.d.ts +2 -2
  69. package/lib/encoding/encode.js +40 -39
  70. package/lib/encoding/encode.js.map +1 -1
  71. package/lib/encoding/spec.d.ts +2 -1
  72. package/lib/encoding/spec.js +1 -0
  73. package/lib/encoding/spec.js.map +1 -1
  74. package/lib/index.d.ts +3 -0
  75. package/lib/index.js +5 -1
  76. package/lib/index.js.map +1 -1
  77. package/lib/types/custom/ArraySchema.d.ts +2 -2
  78. package/lib/types/custom/ArraySchema.js +0 -8
  79. package/lib/types/custom/ArraySchema.js.map +1 -1
  80. package/lib/types/registry.js +3 -4
  81. package/lib/types/registry.js.map +1 -1
  82. package/lib/types/utils.js +1 -2
  83. package/lib/types/utils.js.map +1 -1
  84. package/lib/utils.js +3 -4
  85. package/lib/utils.js.map +1 -1
  86. package/package.json +5 -5
  87. package/src/Metadata.ts +47 -0
  88. package/src/Reflection.ts +31 -22
  89. package/src/annotations.ts +6 -2
  90. package/src/bench_encode.ts +66 -0
  91. package/src/debug.ts +56 -0
  92. package/src/decoder/DecodeOperation.ts +26 -6
  93. package/src/decoder/Decoder.ts +8 -8
  94. package/src/decoder/strategy/StateCallbacks.ts +93 -53
  95. package/src/encoder/ChangeTree.ts +2 -5
  96. package/src/encoder/EncodeOperation.ts +29 -12
  97. package/src/encoder/Encoder.ts +71 -42
  98. package/src/encoder/StateView.ts +10 -7
  99. package/src/encoding/decode.ts +24 -25
  100. package/src/encoding/encode.ts +25 -22
  101. package/src/encoding/spec.ts +1 -0
  102. package/src/index.ts +5 -0
  103. 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,35 @@ 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 buffer overflow. Encoded state is higher than default BUFFER_SIZE. Use the following to increase default BUFFER_SIZE:
3514
+
3515
+ import { Encoder } from "@colyseus/schema";
3516
+ Encoder.BUFFER_SIZE = ${Math.round(newSize / 1024)} * 1024; // ${Math.round(newSize / 1024)} KB
3517
+ `);
3440
3518
  //
3441
3519
  // resize buffer and re-encode (TODO: can we avoid re-encoding here?)
3442
3520
  //
3443
- this.sharedBuffer = Buffer.allocUnsafeSlow(newSize);
3444
- return this.encode({ offset: initialOffset }, view);
3521
+ buffer = Buffer.allocUnsafeSlow(newSize);
3522
+ // assign resized buffer to local sharedBuffer
3523
+ if (buffer === this.sharedBuffer) {
3524
+ this.sharedBuffer = buffer;
3525
+ }
3526
+ return this.encode({ offset: initialOffset }, view, buffer, changeTrees, isEncodeAll);
3445
3527
  }
3446
3528
  else {
3447
3529
  //
@@ -3453,38 +3535,37 @@ class Encoder {
3453
3535
  //
3454
3536
  this.onEndEncode(changeTrees);
3455
3537
  }
3456
- // return bytes;
3457
- return bytes.slice(0, it.offset);
3538
+ return buffer.subarray(0, it.offset);
3458
3539
  }
3459
3540
  }
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());
3541
+ encodeAll(it = { offset: 0 }, buffer = this.sharedBuffer) {
3542
+ // console.log(`encodeAll(), this.root.allChanges (${this.root.allChanges.size})`);
3543
+ // Array.from(this.root.allChanges.entries()).map((item) => {
3544
+ // console.log("->", { ref: item[0].ref.constructor.name, refId: item[0].refId, changes: item[1].size });
3464
3545
  // });
3465
- return this.encode(it, undefined, this.sharedBuffer, this.root.allChanges);
3546
+ return this.encode(it, undefined, buffer, this.root.allChanges, true);
3466
3547
  }
3467
3548
  encodeAllView(view, sharedOffset, it, bytes = this.sharedBuffer) {
3468
3549
  const viewOffset = it.offset;
3469
- // console.log(`encodeAllView(), this.$root.allFilteredChanges (${this.$root.allFilteredChanges.size})`);
3550
+ // console.log(`encodeAllView(), this.root.allFilteredChanges (${this.root.allFilteredChanges.size})`);
3470
3551
  // this.debugAllFilteredChanges();
3471
3552
  // try to encode "filtered" changes
3472
- this.encode(it, view, bytes, this.root.allFilteredChanges);
3553
+ this.encode(it, view, bytes, this.root.allFilteredChanges, true);
3473
3554
  return Buffer.concat([
3474
- bytes.slice(0, sharedOffset),
3475
- bytes.slice(viewOffset, it.offset)
3555
+ bytes.subarray(0, sharedOffset),
3556
+ bytes.subarray(viewOffset, it.offset)
3476
3557
  ]);
3477
3558
  }
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
- // }
3559
+ debugAllFilteredChanges() {
3560
+ Array.from(this.root.allFilteredChanges.entries()).map((item) => {
3561
+ console.log("->", { refId: item[0].refId, changes: item[1].size }, item[0].ref.toJSON());
3562
+ if (Array.isArray(item[0].ref.toJSON())) {
3563
+ item[1].forEach((op, key) => {
3564
+ console.log(" ->", { key, op: exports.OPERATION[op] });
3565
+ });
3566
+ }
3567
+ });
3568
+ }
3488
3569
  encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
3489
3570
  const viewOffset = it.offset;
3490
3571
  // try to encode "filtered" changes
@@ -3516,8 +3597,8 @@ class Encoder {
3516
3597
  // clear "view" changes after encoding
3517
3598
  view.changes.clear();
3518
3599
  return Buffer.concat([
3519
- bytes.slice(0, sharedOffset),
3520
- bytes.slice(viewOffset, it.offset)
3600
+ bytes.subarray(0, sharedOffset),
3601
+ bytes.subarray(viewOffset, it.offset)
3521
3602
  ]);
3522
3603
  }
3523
3604
  onEndEncode(changeTrees = this.root.changes) {
@@ -3696,12 +3777,12 @@ class Decoder {
3696
3777
  }
3697
3778
  setRoot(root) {
3698
3779
  this.state = root;
3699
- this.$root = new ReferenceTracker();
3700
- this.$root.addRef(0, root);
3780
+ this.root = new ReferenceTracker();
3781
+ this.root.addRef(0, root);
3701
3782
  }
3702
3783
  decode(bytes, it = { offset: 0 }, ref = this.state) {
3703
3784
  const allChanges = [];
3704
- const $root = this.$root;
3785
+ const $root = this.root;
3705
3786
  const totalBytes = bytes.byteLength;
3706
3787
  let decoder = ref['constructor'][$decoder];
3707
3788
  this.currentRefId = 0;
@@ -3782,7 +3863,7 @@ class Decoder {
3782
3863
  previousValue: value
3783
3864
  });
3784
3865
  if (needRemoveRef) {
3785
- this.$root.removeRef(this.$root.refIds.get(value));
3866
+ this.root.removeRef(this.root.refIds.get(value));
3786
3867
  }
3787
3868
  });
3788
3869
  }
@@ -3822,7 +3903,7 @@ class Reflection extends Schema {
3822
3903
  super(...arguments);
3823
3904
  this.types = new ArraySchema();
3824
3905
  }
3825
- static encode(instance, context) {
3906
+ static encode(instance, context, it = { offset: 0 }) {
3826
3907
  if (!context) {
3827
3908
  context = new TypeContext(instance.constructor);
3828
3909
  }
@@ -3879,7 +3960,6 @@ class Reflection extends Schema {
3879
3960
  }
3880
3961
  buildType(type, klass[Symbol.metadata]);
3881
3962
  }
3882
- const it = { offset: 0 };
3883
3963
  const buf = encoder.encodeAll(it);
3884
3964
  return Buffer.from(buf, 0, it.offset);
3885
3965
  }
@@ -3887,59 +3967,298 @@ class Reflection extends Schema {
3887
3967
  const reflection = new Reflection();
3888
3968
  const reflectionDecoder = new Decoder(reflection);
3889
3969
  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 {
3970
+ const typeContext = new TypeContext();
3971
+ // 1st pass, initialize metadata + inheritance
3972
+ reflection.types.forEach((reflectionType) => {
3973
+ const parentClass = typeContext.get(reflectionType.extendsId) ?? Schema;
3974
+ const schema = class _ extends parentClass {
3894
3975
  };
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 });
3976
+ const parentMetadata = parentClass[Symbol.metadata];
3898
3977
  // register for inheritance support
3899
3978
  TypeContext.register(schema);
3900
- const typeid = reflectionType.id;
3901
- types[typeid] = schema;
3902
- context.add(schema, typeid);
3903
- return types;
3979
+ // for inheritance support
3980
+ Metadata.initialize(schema, parentMetadata);
3981
+ typeContext.add(schema, reflectionType.id);
3904
3982
  }, {});
3983
+ // 2nd pass, set fields
3905
3984
  reflection.types.forEach((reflectionType) => {
3906
- const schemaType = schemaTypes[reflectionType.id];
3985
+ const schemaType = typeContext.get(reflectionType.id);
3907
3986
  const metadata = schemaType[Symbol.metadata];
3908
- const parentKlass = reflection.types[reflectionType.extendsId];
3909
- const parentFieldIndex = parentKlass && parentKlass.fields.length || 0;
3987
+ // FIXME: use metadata[-1] to get field count
3988
+ const parentFieldIndex = 0;
3989
+ // console.log("--------------------");
3990
+ // // console.log("reflectionType", reflectionType.toJSON());
3991
+ // console.log("reflectionType.fields", reflectionType.fields.toJSON());
3992
+ // console.log("parentFieldIndex", parentFieldIndex);
3993
+ //
3994
+ // FIXME: set fields using parentKlass as well
3995
+ // currently the fields are duplicated on inherited classes
3996
+ //
3997
+ // // const parentKlass = reflection.types[reflectionType.extendsId];
3998
+ // // parentKlass.fields
3910
3999
  reflectionType.fields.forEach((field, i) => {
3911
4000
  const fieldIndex = parentFieldIndex + i;
3912
4001
  if (field.referencedType !== undefined) {
3913
4002
  let fieldType = field.type;
3914
- let refType = schemaTypes[field.referencedType];
4003
+ let refType = typeContext.get(field.referencedType);
3915
4004
  // map or array of primitive type (-1)
3916
4005
  if (!refType) {
3917
4006
  const typeInfo = field.type.split(":");
3918
4007
  fieldType = typeInfo[0];
3919
- refType = typeInfo[1];
4008
+ refType = typeInfo[1]; // string
3920
4009
  }
3921
4010
  if (fieldType === "ref") {
3922
- // type(refType)(schemaType.prototype, field.name);
3923
4011
  Metadata.addField(metadata, fieldIndex, field.name, refType);
3924
4012
  }
3925
4013
  else {
3926
- // type({ [fieldType]: refType } as DefinitionType)(schemaType.prototype, field.name);
3927
4014
  Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType });
3928
4015
  }
3929
4016
  }
3930
4017
  else {
3931
- // type(field.type as PrimitiveType)(schemaType.prototype, field.name);
3932
4018
  Metadata.addField(metadata, fieldIndex, field.name, field.type);
3933
4019
  }
3934
4020
  });
3935
4021
  });
3936
- return new (schemaTypes[0])();
4022
+ // @ts-ignore
4023
+ return new (typeContext.get(0))();
3937
4024
  }
3938
4025
  }
3939
4026
  __decorate([
3940
4027
  type([ReflectionType])
3941
4028
  ], Reflection.prototype, "types", void 0);
3942
4029
 
4030
+ function getDecoderStateCallbacks(decoder) {
4031
+ const $root = decoder.root;
4032
+ const callbacks = $root.callbacks;
4033
+ let isTriggeringOnAdd = false;
4034
+ decoder.triggerChanges = function (allChanges) {
4035
+ const uniqueRefIds = new Set();
4036
+ for (let i = 0, l = allChanges.length; i < l; i++) {
4037
+ const change = allChanges[i];
4038
+ const refId = change.refId;
4039
+ const ref = change.ref;
4040
+ const $callbacks = callbacks[refId];
4041
+ if (!$callbacks) {
4042
+ continue;
4043
+ }
4044
+ //
4045
+ // trigger onRemove on child structure.
4046
+ //
4047
+ if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE &&
4048
+ change.previousValue instanceof Schema) {
4049
+ const deleteCallbacks = callbacks[$root.refIds.get(change.previousValue)]?.[exports.OPERATION.DELETE];
4050
+ for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
4051
+ deleteCallbacks[i]();
4052
+ }
4053
+ }
4054
+ if (ref instanceof Schema) {
4055
+ //
4056
+ // Handle schema instance
4057
+ //
4058
+ if (!uniqueRefIds.has(refId)) {
4059
+ // trigger onChange
4060
+ const replaceCallbacks = $callbacks?.[exports.OPERATION.REPLACE];
4061
+ for (let i = replaceCallbacks?.length - 1; i >= 0; i--) {
4062
+ replaceCallbacks[i]();
4063
+ // try {
4064
+ // } catch (e) {
4065
+ // console.error(e);
4066
+ // }
4067
+ }
4068
+ }
4069
+ if ($callbacks.hasOwnProperty(change.field)) {
4070
+ const fieldCallbacks = $callbacks[change.field];
4071
+ for (let i = fieldCallbacks?.length - 1; i >= 0; i--) {
4072
+ fieldCallbacks[i](change.value, change.previousValue);
4073
+ // try {
4074
+ // } catch (e) {
4075
+ // console.error(e);
4076
+ // }
4077
+ }
4078
+ }
4079
+ }
4080
+ else {
4081
+ //
4082
+ // Handle collection of items
4083
+ //
4084
+ if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE) {
4085
+ //
4086
+ // FIXME: `previousValue` should always be available.
4087
+ //
4088
+ if (change.previousValue !== undefined) {
4089
+ // triger onRemove
4090
+ const deleteCallbacks = $callbacks[exports.OPERATION.DELETE];
4091
+ for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
4092
+ deleteCallbacks[i](change.previousValue, change.dynamicIndex ?? change.field);
4093
+ }
4094
+ }
4095
+ // Handle DELETE_AND_ADD operations
4096
+ // FIXME: should we set "isTriggeringOnAdd" here?
4097
+ if ((change.op & exports.OPERATION.ADD) === exports.OPERATION.ADD) {
4098
+ const addCallbacks = $callbacks[exports.OPERATION.ADD];
4099
+ for (let i = addCallbacks?.length - 1; i >= 0; i--) {
4100
+ addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
4101
+ }
4102
+ }
4103
+ }
4104
+ else if ((change.op & exports.OPERATION.ADD) === exports.OPERATION.ADD && change.previousValue === undefined) {
4105
+ // triger onAdd
4106
+ isTriggeringOnAdd = true;
4107
+ const addCallbacks = $callbacks[exports.OPERATION.ADD];
4108
+ for (let i = addCallbacks?.length - 1; i >= 0; i--) {
4109
+ addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
4110
+ }
4111
+ isTriggeringOnAdd = false;
4112
+ }
4113
+ // trigger onChange
4114
+ if (change.value !== change.previousValue) {
4115
+ const replaceCallbacks = $callbacks[exports.OPERATION.REPLACE];
4116
+ for (let i = replaceCallbacks?.length - 1; i >= 0; i--) {
4117
+ replaceCallbacks[i](change.value, change.dynamicIndex ?? change.field);
4118
+ }
4119
+ }
4120
+ }
4121
+ uniqueRefIds.add(refId);
4122
+ }
4123
+ };
4124
+ function getProxy(metadataOrType, context) {
4125
+ let metadata = context.instance?.constructor[Symbol.metadata] || metadataOrType;
4126
+ let isCollection = ((context.instance && typeof (context.instance['forEach']) === "function") ||
4127
+ (metadataOrType && typeof (metadataOrType[Symbol.metadata]) === "undefined"));
4128
+ if (metadata && !isCollection) {
4129
+ const onAdd = function (ref, prop, callback, immediate) {
4130
+ // immediate trigger
4131
+ if (immediate &&
4132
+ context.instance[prop] !== undefined &&
4133
+ !isTriggeringOnAdd // FIXME: This is a workaround (https://github.com/colyseus/schema/issues/147)
4134
+ ) {
4135
+ callback(context.instance[prop], undefined);
4136
+ }
4137
+ return $root.addCallback($root.refIds.get(ref), prop, callback);
4138
+ };
4139
+ /**
4140
+ * Schema instances
4141
+ */
4142
+ return new Proxy({
4143
+ listen: function listen(prop, callback, immediate = true) {
4144
+ if (context.instance) {
4145
+ return onAdd(context.instance, prop, callback, immediate);
4146
+ }
4147
+ else {
4148
+ // collection instance not received yet
4149
+ context.onInstanceAvailable((ref, existing) => onAdd(ref, prop, callback, immediate && existing));
4150
+ }
4151
+ },
4152
+ onChange: function onChange(callback) {
4153
+ return $root.addCallback($root.refIds.get(context.instance), exports.OPERATION.REPLACE, callback);
4154
+ },
4155
+ bindTo: function bindTo(targetObject, properties) {
4156
+ //
4157
+ // TODO: refactor this implementation. There is room for improvement here.
4158
+ //
4159
+ if (!properties) {
4160
+ properties = Object.keys(metadata);
4161
+ }
4162
+ return $root.addCallback($root.refIds.get(context.instance), exports.OPERATION.REPLACE, () => {
4163
+ properties.forEach((prop) => targetObject[prop] = context.instance[prop]);
4164
+ });
4165
+ }
4166
+ }, {
4167
+ get(target, prop) {
4168
+ if (metadata[prop]) {
4169
+ const instance = context.instance?.[prop];
4170
+ const onInstanceAvailable = ((callback) => {
4171
+ const unbind = $(context.instance).listen(prop, (value, _) => {
4172
+ callback(value, false);
4173
+ // FIXME: by "unbinding" the callback here,
4174
+ // it will not support when the server
4175
+ // re-instantiates the instance.
4176
+ //
4177
+ unbind?.();
4178
+ }, false);
4179
+ // has existing value
4180
+ if ($root.refIds.get(instance) !== undefined) {
4181
+ callback(instance, true);
4182
+ }
4183
+ });
4184
+ return getProxy(metadata[prop].type, {
4185
+ instance,
4186
+ parentInstance: context.instance,
4187
+ onInstanceAvailable,
4188
+ });
4189
+ }
4190
+ else {
4191
+ // accessing the function
4192
+ return target[prop];
4193
+ }
4194
+ },
4195
+ has(target, prop) { return metadata[prop] !== undefined; },
4196
+ set(_, _1, _2) { throw new Error("not allowed"); },
4197
+ deleteProperty(_, _1) { throw new Error("not allowed"); },
4198
+ });
4199
+ }
4200
+ else {
4201
+ /**
4202
+ * Collection instances
4203
+ */
4204
+ const onAdd = function (ref, callback, immediate) {
4205
+ // Trigger callback on existing items
4206
+ if (immediate) {
4207
+ ref.forEach((v, k) => callback(v, k));
4208
+ }
4209
+ return $root.addCallback($root.refIds.get(ref), exports.OPERATION.ADD, callback);
4210
+ };
4211
+ const onRemove = function (ref, callback) {
4212
+ return $root.addCallback($root.refIds.get(ref), exports.OPERATION.DELETE, callback);
4213
+ };
4214
+ return new Proxy({
4215
+ onAdd: function (callback, immediate = true) {
4216
+ //
4217
+ // https://github.com/colyseus/schema/issues/147
4218
+ // If parent instance has "onAdd" registered, avoid triggering immediate callback.
4219
+ //
4220
+ // FIXME: "isTriggeringOnAdd" is a workaround. We should find a better way to handle this.
4221
+ //
4222
+ if (context.onInstanceAvailable) {
4223
+ // collection instance not received yet
4224
+ context.onInstanceAvailable((ref, existing) => onAdd(ref, callback, immediate && existing && !isTriggeringOnAdd));
4225
+ }
4226
+ else if (context.instance) {
4227
+ onAdd(context.instance, callback, immediate && !isTriggeringOnAdd);
4228
+ }
4229
+ },
4230
+ onRemove: function (callback) {
4231
+ if (context.onInstanceAvailable) {
4232
+ // collection instance not received yet
4233
+ context.onInstanceAvailable((ref) => onRemove(ref, callback));
4234
+ }
4235
+ else if (context.instance) {
4236
+ onRemove(context.instance, callback);
4237
+ }
4238
+ },
4239
+ }, {
4240
+ get(target, prop) {
4241
+ if (!target[prop]) {
4242
+ throw new Error(`Can't access '${prop}' through callback proxy. access the instance directly.`);
4243
+ }
4244
+ return target[prop];
4245
+ },
4246
+ has(target, prop) { return target[prop] !== undefined; },
4247
+ set(_, _1, _2) { throw new Error("not allowed"); },
4248
+ deleteProperty(_, _1) { throw new Error("not allowed"); },
4249
+ });
4250
+ }
4251
+ }
4252
+ function $(instance) {
4253
+ return getProxy(undefined, { instance });
4254
+ }
4255
+ return $;
4256
+ }
4257
+
4258
+ function getRawChangesCallback(decoder, callback) {
4259
+ decoder.triggerChanges = callback;
4260
+ }
4261
+
3943
4262
  class StateView {
3944
4263
  constructor() {
3945
4264
  /**
@@ -3962,12 +4281,18 @@ class StateView {
3962
4281
  console.warn("StateView#add(), invalid object:", obj);
3963
4282
  return this;
3964
4283
  }
4284
+ // FIXME: ArraySchema/MapSchema does not have metadata
4285
+ const metadata = obj.constructor[Symbol.metadata];
3965
4286
  let changeTree = obj[$changes];
3966
4287
  this.items.add(changeTree);
3967
4288
  // 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];
4289
+ changeTree.forEachChild((change, index) => {
4290
+ // Do not ADD children that don't have the same tag
4291
+ if (metadata && metadata[metadata[index]].tag !== tag) {
4292
+ return;
4293
+ }
4294
+ this.add(change.ref, tag);
4295
+ });
3971
4296
  // add parent ChangeTree's, if they are invisible to this view
3972
4297
  // TODO: REFACTOR addParent()
3973
4298
  this.addParent(changeTree, tag);
@@ -3994,7 +4319,6 @@ class StateView {
3994
4319
  tags = this.tags.get(changeTree);
3995
4320
  }
3996
4321
  tags.add(tag);
3997
- // console.log("BY TAG:", tag);
3998
4322
  // Ref: add tagged properties
3999
4323
  metadata?.[-3]?.[tag]?.forEach((index) => {
4000
4324
  if (changeTree.getChange(index) !== exports.OPERATION.DELETE) {
@@ -4164,6 +4488,8 @@ exports.dumpChanges = dumpChanges;
4164
4488
  exports.encode = encode;
4165
4489
  exports.encodeKeyValueOperation = encodeArray;
4166
4490
  exports.encodeSchemaOperation = encodeSchemaOperation;
4491
+ exports.getDecoderStateCallbacks = getDecoderStateCallbacks;
4492
+ exports.getRawChangesCallback = getRawChangesCallback;
4167
4493
  exports.registerType = registerType;
4168
4494
  exports.type = type;
4169
4495
  exports.view = view;