@colyseus/schema 3.0.0-alpha.30 → 3.0.0-alpha.32

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 (77) hide show
  1. package/build/cjs/index.js +389 -354
  2. package/build/cjs/index.js.map +1 -1
  3. package/build/esm/index.mjs +389 -354
  4. package/build/esm/index.mjs.map +1 -1
  5. package/build/umd/index.js +389 -354
  6. package/lib/Metadata.d.ts +14 -5
  7. package/lib/Metadata.js +49 -20
  8. package/lib/Metadata.js.map +1 -1
  9. package/lib/Reflection.js +4 -13
  10. package/lib/Reflection.js.map +1 -1
  11. package/lib/Schema.js +26 -39
  12. package/lib/Schema.js.map +1 -1
  13. package/lib/annotations.d.ts +1 -2
  14. package/lib/annotations.js +58 -52
  15. package/lib/annotations.js.map +1 -1
  16. package/lib/bench_encode.js +25 -22
  17. package/lib/bench_encode.js.map +1 -1
  18. package/lib/decoder/DecodeOperation.js +7 -9
  19. package/lib/decoder/DecodeOperation.js.map +1 -1
  20. package/lib/decoder/ReferenceTracker.js +3 -2
  21. package/lib/decoder/ReferenceTracker.js.map +1 -1
  22. package/lib/decoder/strategy/StateCallbacks.js +4 -3
  23. package/lib/decoder/strategy/StateCallbacks.js.map +1 -1
  24. package/lib/encoder/ChangeTree.d.ts +8 -7
  25. package/lib/encoder/ChangeTree.js +135 -117
  26. package/lib/encoder/ChangeTree.js.map +1 -1
  27. package/lib/encoder/EncodeOperation.d.ts +1 -4
  28. package/lib/encoder/EncodeOperation.js +17 -47
  29. package/lib/encoder/EncodeOperation.js.map +1 -1
  30. package/lib/encoder/Encoder.js +18 -6
  31. package/lib/encoder/Encoder.js.map +1 -1
  32. package/lib/encoder/Root.d.ts +2 -2
  33. package/lib/encoder/Root.js +18 -6
  34. package/lib/encoder/Root.js.map +1 -1
  35. package/lib/encoder/StateView.js +3 -3
  36. package/lib/encoder/StateView.js.map +1 -1
  37. package/lib/encoding/assert.d.ts +2 -1
  38. package/lib/encoding/assert.js +2 -2
  39. package/lib/encoding/assert.js.map +1 -1
  40. package/lib/index.d.ts +1 -2
  41. package/lib/index.js +11 -10
  42. package/lib/index.js.map +1 -1
  43. package/lib/types/TypeContext.js +7 -14
  44. package/lib/types/TypeContext.js.map +1 -1
  45. package/lib/types/custom/ArraySchema.js +6 -0
  46. package/lib/types/custom/ArraySchema.js.map +1 -1
  47. package/lib/types/custom/CollectionSchema.js +1 -0
  48. package/lib/types/custom/CollectionSchema.js.map +1 -1
  49. package/lib/types/custom/MapSchema.js +5 -0
  50. package/lib/types/custom/MapSchema.js.map +1 -1
  51. package/lib/types/custom/SetSchema.js +1 -0
  52. package/lib/types/custom/SetSchema.js.map +1 -1
  53. package/lib/types/symbols.d.ts +1 -0
  54. package/lib/types/symbols.js +2 -1
  55. package/lib/types/symbols.js.map +1 -1
  56. package/package.json +1 -1
  57. package/src/Metadata.ts +60 -29
  58. package/src/Reflection.ts +5 -15
  59. package/src/Schema.ts +33 -45
  60. package/src/annotations.ts +75 -67
  61. package/src/bench_encode.ts +29 -27
  62. package/src/decoder/DecodeOperation.ts +12 -11
  63. package/src/decoder/ReferenceTracker.ts +3 -2
  64. package/src/decoder/strategy/StateCallbacks.ts +4 -3
  65. package/src/encoder/ChangeTree.ts +154 -138
  66. package/src/encoder/EncodeOperation.ts +42 -62
  67. package/src/encoder/Encoder.ts +25 -8
  68. package/src/encoder/Root.ts +23 -6
  69. package/src/encoder/StateView.ts +4 -4
  70. package/src/encoding/assert.ts +4 -3
  71. package/src/index.ts +1 -4
  72. package/src/types/TypeContext.ts +10 -15
  73. package/src/types/custom/ArraySchema.ts +8 -0
  74. package/src/types/custom/CollectionSchema.ts +1 -0
  75. package/src/types/custom/MapSchema.ts +6 -0
  76. package/src/types/custom/SetSchema.ts +1 -0
  77. package/src/types/symbols.ts +2 -0
@@ -36,6 +36,7 @@ const $decoder = Symbol("$decoder");
36
36
  const $filter = Symbol("$filter");
37
37
  const $getByIndex = Symbol("$getByIndex");
38
38
  const $deleteByIndex = Symbol("$deleteByIndex");
39
+ const $descriptors = Symbol("$descriptors");
39
40
  /**
40
41
  * Used to hold ChangeTree instances whitin the structures
41
42
  */
@@ -71,34 +72,67 @@ function getType(identifier) {
71
72
  }
72
73
 
73
74
  const Metadata = {
74
- addField(metadata, index, field, type, descriptor) {
75
+ addField(metadata, index, name, type, descriptor) {
75
76
  if (index > 64) {
76
- throw new Error(`Can't define field '${field}'.\nSchema instances may only have up to 64 fields.`);
77
+ throw new Error(`Can't define field '${name}'.\nSchema instances may only have up to 64 fields.`);
77
78
  }
78
- metadata[field] = Object.assign(metadata[field] || {}, // avoid overwriting previous field metadata (@owned / @deprecated)
79
+ metadata[index] = Object.assign(metadata[index] || {}, // avoid overwriting previous field metadata (@owned / @deprecated)
79
80
  {
80
81
  type: (Array.isArray(type))
81
82
  ? { array: type[0] }
82
83
  : type,
83
84
  index,
84
- descriptor,
85
+ name,
85
86
  });
87
+ // create "descriptors" map
88
+ metadata[$descriptors] ??= {};
89
+ if (descriptor) {
90
+ // for encoder
91
+ metadata[$descriptors][name] = descriptor;
92
+ metadata[$descriptors][`_${name}`] = {
93
+ value: undefined,
94
+ writable: true,
95
+ enumerable: false,
96
+ configurable: true,
97
+ };
98
+ }
99
+ else {
100
+ // for decoder
101
+ metadata[$descriptors][name] = {
102
+ value: undefined,
103
+ writable: true,
104
+ enumerable: true,
105
+ configurable: true,
106
+ };
107
+ }
86
108
  // map -1 as last field index
87
109
  Object.defineProperty(metadata, -1, {
88
110
  value: index,
89
111
  enumerable: false,
90
112
  configurable: true
91
113
  });
92
- // map index => field name (non enumerable)
93
- Object.defineProperty(metadata, index, {
94
- value: field,
114
+ // map field name => index (non enumerable)
115
+ Object.defineProperty(metadata, name, {
116
+ value: index,
95
117
  enumerable: false,
96
118
  configurable: true,
97
119
  });
120
+ // if child Ref/complex type, add to -4
121
+ if (typeof (metadata[index].type) !== "string") {
122
+ if (metadata[-4] === undefined) {
123
+ Object.defineProperty(metadata, -4, {
124
+ value: [],
125
+ enumerable: false,
126
+ configurable: true,
127
+ });
128
+ }
129
+ metadata[-4].push(index);
130
+ }
98
131
  },
99
132
  setTag(metadata, fieldName, tag) {
133
+ const index = metadata[fieldName];
134
+ const field = metadata[index];
100
135
  // add 'tag' to the field
101
- const field = metadata[fieldName];
102
136
  field.tag = tag;
103
137
  if (!metadata[-2]) {
104
138
  // -2: all field indexes with "view" tag
@@ -114,20 +148,14 @@ const Metadata = {
114
148
  configurable: true
115
149
  });
116
150
  }
117
- metadata[-2].push(field.index);
151
+ metadata[-2].push(index);
118
152
  if (!metadata[-3][tag]) {
119
153
  metadata[-3][tag] = [];
120
154
  }
121
- metadata[-3][tag].push(field.index);
155
+ metadata[-3][tag].push(index);
122
156
  },
123
157
  setFields(target, fields) {
124
158
  const metadata = (target.prototype.constructor[Symbol.metadata] ??= {});
125
- // target[$track] = function (changeTree, index: number, operation: OPERATION = OPERATION.ADD) {
126
- // changeTree.change(index, operation, encodeSchemaOperation);
127
- // };
128
- // target[$encoder] = encodeSchemaOperation;
129
- // target[$decoder] = decodeSchemaOperation;
130
- // if (!target.prototype.toJSON) { target.prototype.toJSON = Schema.prototype.toJSON; }
131
159
  let index = 0;
132
160
  for (const field in fields) {
133
161
  const type = fields[field];
@@ -135,7 +163,7 @@ const Metadata = {
135
163
  const complexTypeKlass = (Array.isArray(type))
136
164
  ? getType("array")
137
165
  : (typeof (Object.keys(type)[0]) === "string") && getType(Object.keys(type)[0]);
138
- Metadata.addField(metadata, index, field, type, getPropertyDescriptor(`_${field}`, index, type, complexTypeKlass, metadata, field));
166
+ Metadata.addField(metadata, index, field, type, getPropertyDescriptor(`_${field}`, index, type, complexTypeKlass));
139
167
  index++;
140
168
  }
141
169
  },
@@ -164,8 +192,9 @@ const Metadata = {
164
192
  // assign parent metadata to current
165
193
  Object.assign(metadata, parentMetadata);
166
194
  for (let i = 0; i <= parentMetadata[-1]; i++) {
167
- Object.defineProperty(metadata, i, {
168
- value: parentMetadata[i],
195
+ const fieldName = parentMetadata[i].name;
196
+ Object.defineProperty(metadata, fieldName, {
197
+ value: parentMetadata[fieldName],
169
198
  enumerable: false,
170
199
  configurable: true,
171
200
  });
@@ -189,7 +218,7 @@ const Metadata = {
189
218
  const metadata = klass[Symbol.metadata];
190
219
  const fields = {};
191
220
  for (let i = 0; i <= metadata[-1]; i++) {
192
- fields[metadata[i]] = metadata[metadata[i]].type;
221
+ fields[metadata[i].name] = metadata[i].type;
193
222
  }
194
223
  return fields;
195
224
  }
@@ -198,48 +227,59 @@ const Metadata = {
198
227
  var _a$5;
199
228
  class ChangeTree {
200
229
  static { _a$5 = $isNew; }
201
- ;
202
230
  constructor(ref) {
203
- this.indexes = {}; // TODO: remove this, only used by MapSchema/SetSchema/CollectionSchema (`encodeKeyValueOperation`)
231
+ this.isFiltered = false;
232
+ this.isPartiallyFiltered = false;
204
233
  this.currentOperationIndex = 0;
205
- this.allChanges = new Map();
206
- this.allFilteredChanges = new Map();
207
234
  this.changes = new Map();
208
- this.filteredChanges = new Map();
235
+ this.allChanges = new Map();
209
236
  this[_a$5] = true;
210
237
  this.ref = ref;
238
+ //
239
+ // Does this structure have "filters" declared?
240
+ //
241
+ if (ref.constructor[Symbol.metadata]?.[-2]) {
242
+ this.allFilteredChanges = new Map();
243
+ this.filteredChanges = new Map();
244
+ }
211
245
  }
212
246
  setRoot(root) {
213
247
  this.root = root;
214
248
  this.root.add(this);
215
- //
216
- // At Schema initialization, the "root" structure might not be available
217
- // yet, as it only does once the "Encoder" has been set up.
218
- //
219
- // So the "parent" may be already set without a "root".
220
- //
221
- this.checkIsFiltered(this.parent, this.parentIndex);
222
- // unique refId for the ChangeTree.
223
- this.ensureRefId();
249
+ const metadata = this.ref.constructor[Symbol.metadata];
250
+ if (this.root.types.hasFilters) {
251
+ //
252
+ // At Schema initialization, the "root" structure might not be available
253
+ // yet, as it only does once the "Encoder" has been set up.
254
+ //
255
+ // So the "parent" may be already set without a "root".
256
+ //
257
+ this.checkIsFiltered(metadata, this.parent, this.parentIndex);
258
+ if (this.isFiltered || this.isPartiallyFiltered) {
259
+ this.root.allFilteredChanges.set(this, this.allFilteredChanges);
260
+ this.root.filteredChanges.set(this, this.filteredChanges);
261
+ }
262
+ }
224
263
  if (!this.isFiltered) {
225
264
  this.root.changes.set(this, this.changes);
265
+ this.root.allChanges.set(this, this.allChanges);
226
266
  }
227
- if (this.isFiltered || this.isPartiallyFiltered) {
228
- this.root.allFilteredChanges.set(this, this.allFilteredChanges);
229
- this.root.filteredChanges.set(this, this.filteredChanges);
267
+ this.ensureRefId();
268
+ if (metadata) {
269
+ metadata[-4]?.forEach((index) => {
270
+ const field = metadata[index];
271
+ const value = this.ref[field.name];
272
+ if (value) {
273
+ value[$changes].setRoot(root);
274
+ }
275
+ });
230
276
  }
231
- if (!this.isFiltered) {
232
- this.root.allChanges.set(this, this.allChanges);
277
+ else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
278
+ // MapSchema / ArraySchema, etc.
279
+ this.ref.forEach((value, key) => {
280
+ value[$changes].setRoot(root);
281
+ });
233
282
  }
234
- this.forEachChild((changeTree, _) => {
235
- changeTree.setRoot(root);
236
- });
237
- // this.allChanges.forEach((_, index) => {
238
- // const childRef = this.ref[$getByIndex](index);
239
- // if (childRef && childRef[$changes]) {
240
- // childRef[$changes].setRoot(root);
241
- // }
242
- // });
243
283
  }
244
284
  setParent(parent, root, parentIndex) {
245
285
  this.parent = parent;
@@ -249,48 +289,60 @@ class ChangeTree {
249
289
  return;
250
290
  }
251
291
  root.add(this);
292
+ const metadata = this.ref.constructor[Symbol.metadata];
252
293
  // skip if parent is already set
253
- if (root === this.root) {
254
- this.forEachChild((changeTree, atIndex) => {
255
- changeTree.setParent(this.ref, root, atIndex);
256
- });
257
- return;
294
+ if (root !== this.root) {
295
+ this.root = root;
296
+ if (root.types.hasFilters) {
297
+ this.checkIsFiltered(metadata, parent, parentIndex);
298
+ if (this.isFiltered || this.isPartiallyFiltered) {
299
+ this.root.filteredChanges.set(this, this.filteredChanges);
300
+ this.root.allFilteredChanges.set(this, this.filteredChanges);
301
+ }
302
+ }
303
+ if (!this.isFiltered) {
304
+ this.root.changes.set(this, this.changes);
305
+ this.root.allChanges.set(this, this.allChanges);
306
+ }
307
+ this.ensureRefId();
258
308
  }
259
- this.root = root;
260
- this.checkIsFiltered(parent, parentIndex);
261
- if (!this.isFiltered) {
262
- this.root.changes.set(this, this.changes);
263
- this.root.allChanges.set(this, this.allChanges);
309
+ // assign same parent on child structures
310
+ if (metadata) {
311
+ metadata[-4]?.forEach((index) => {
312
+ const field = metadata[index];
313
+ const value = this.ref[field.name];
314
+ value?.[$changes].setParent(this.ref, root, index);
315
+ // console.log(this.ref.constructor.name, field.name, value);
316
+ // try { throw new Error(); } catch (e) {
317
+ // console.log(e.stack);
318
+ // }
319
+ });
264
320
  }
265
- if (this.isFiltered || this.isPartiallyFiltered) {
266
- this.root.filteredChanges.set(this, this.filteredChanges);
267
- this.root.allFilteredChanges.set(this, this.filteredChanges);
321
+ else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
322
+ // MapSchema / ArraySchema, etc.
323
+ this.ref.forEach((value, key) => {
324
+ value[$changes].setParent(this.ref, root, this.indexes[key] ?? key);
325
+ });
268
326
  }
269
- this.ensureRefId();
270
- this.forEachChild((changeTree, atIndex) => {
271
- changeTree.setParent(this.ref, root, atIndex);
272
- });
273
327
  }
274
328
  forEachChild(callback) {
275
329
  //
276
330
  // assign same parent on child structures
277
331
  //
278
- if (Metadata.isValidInstance(this.ref)) {
279
- const metadata = this.ref['constructor'][Symbol.metadata];
280
- // FIXME: need to iterate over parent metadata instead.
281
- for (const field in metadata) {
282
- const value = this.ref[field];
283
- if (value && value[$changes]) {
284
- callback(value[$changes], metadata[field].index);
332
+ const metadata = this.ref.constructor[Symbol.metadata];
333
+ if (metadata) {
334
+ metadata[-4]?.forEach((index) => {
335
+ const field = metadata[index];
336
+ const value = this.ref[field.name];
337
+ if (value) {
338
+ callback(value[$changes], index);
285
339
  }
286
- }
340
+ });
287
341
  }
288
- else if (typeof (this.ref) === "object") {
342
+ else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
289
343
  // MapSchema / ArraySchema, etc.
290
344
  this.ref.forEach((value, key) => {
291
- if (Metadata.isValidInstance(value)) {
292
- callback(value[$changes], this.ref[$changes].indexes[key] ?? key);
293
- }
345
+ callback(value[$changes], this.indexes[key] ?? key);
294
346
  });
295
347
  }
296
348
  }
@@ -299,8 +351,8 @@ class ChangeTree {
299
351
  this.root?.changes.set(this, this.changes);
300
352
  }
301
353
  change(index, operation = exports.OPERATION.ADD) {
302
- const metadata = this.ref['constructor'][Symbol.metadata];
303
- const isFiltered = this.isFiltered || (metadata && metadata[metadata[index]].tag !== undefined);
354
+ const metadata = this.ref.constructor[Symbol.metadata];
355
+ const isFiltered = this.isFiltered || (metadata?.[index]?.tag !== undefined);
304
356
  const changeSet = (isFiltered)
305
357
  ? this.filteredChanges
306
358
  : this.changes;
@@ -311,14 +363,14 @@ class ChangeTree {
311
363
  : (previousOperation === exports.OPERATION.DELETE)
312
364
  ? exports.OPERATION.DELETE_AND_ADD
313
365
  : operation;
366
+ //
367
+ // TODO: are DELETE operations being encoded as ADD here ??
368
+ //
314
369
  changeSet.set(index, op);
315
370
  }
316
- //
317
- // TODO: are DELETE operations being encoded as ADD here ??
318
- //
319
371
  if (isFiltered) {
320
- this.root?.filteredChanges.set(this, this.filteredChanges);
321
372
  this.allFilteredChanges.set(index, exports.OPERATION.ADD);
373
+ this.root?.filteredChanges.set(this, this.filteredChanges);
322
374
  this.root?.allFilteredChanges.set(this, this.allFilteredChanges);
323
375
  }
324
376
  else {
@@ -365,9 +417,7 @@ class ChangeTree {
365
417
  });
366
418
  }
367
419
  indexedOperation(index, operation, allChangesIndex = index) {
368
- const metadata = this.ref['constructor'][Symbol.metadata];
369
- const isFiltered = this.isFiltered || (metadata && metadata[metadata[index]].tag !== undefined);
370
- if (isFiltered) {
420
+ if (this.filteredChanges !== undefined) {
371
421
  this.allFilteredChanges.set(allChangesIndex, exports.OPERATION.ADD);
372
422
  this.filteredChanges.set(index, operation);
373
423
  this.root?.filteredChanges.set(this, this.filteredChanges);
@@ -380,8 +430,8 @@ class ChangeTree {
380
430
  }
381
431
  getType(index) {
382
432
  if (Metadata.isValidInstance(this.ref)) {
383
- const metadata = this.ref['constructor'][Symbol.metadata];
384
- return metadata[metadata[index]].type;
433
+ const metadata = this.ref.constructor[Symbol.metadata];
434
+ return metadata[index].type;
385
435
  }
386
436
  else {
387
437
  //
@@ -395,7 +445,7 @@ class ChangeTree {
395
445
  }
396
446
  getChange(index) {
397
447
  // TODO: optimize this. avoid checking against multiple instances
398
- return this.changes.get(index) ?? this.filteredChanges.get(index);
448
+ return this.changes.get(index) ?? this.filteredChanges?.get(index);
399
449
  }
400
450
  //
401
451
  // used during `.encode()`
@@ -416,16 +466,13 @@ class ChangeTree {
416
466
  }
417
467
  return;
418
468
  }
419
- const metadata = this.ref['constructor'][Symbol.metadata];
420
- const isFiltered = this.isFiltered || (metadata && metadata[metadata[index]].tag !== undefined);
421
- const changeSet = (isFiltered)
469
+ const changeSet = (this.filteredChanges)
422
470
  ? this.filteredChanges
423
471
  : this.changes;
424
472
  const previousValue = this.getValue(index);
425
473
  changeSet.set(index, operation ?? exports.OPERATION.DELETE);
426
474
  // remove `root` reference
427
475
  if (previousValue && previousValue[$changes]) {
428
- previousValue[$changes].root = undefined;
429
476
  //
430
477
  // FIXME: this.root is "undefined"
431
478
  //
@@ -436,12 +483,18 @@ class ChangeTree {
436
483
  //
437
484
  // (the property descriptors should NOT be used at decoding time. only at encoding time.)
438
485
  //
439
- this.root?.remove(previousValue[$changes]);
486
+ const refCount = this.root?.remove(previousValue[$changes]);
487
+ //
488
+ // Only remove "root" reference if it's the last reference
489
+ //
490
+ if (refCount <= 0) {
491
+ previousValue[$changes].root = undefined;
492
+ }
440
493
  }
441
494
  //
442
495
  // FIXME: this is looking a bit ugly (and repeated from `.change()`)
443
496
  //
444
- if (isFiltered) {
497
+ if (this.filteredChanges) {
445
498
  this.root?.filteredChanges.set(this, this.filteredChanges);
446
499
  this.allFilteredChanges.delete(allChangesIndex);
447
500
  }
@@ -452,6 +505,7 @@ class ChangeTree {
452
505
  }
453
506
  endEncode() {
454
507
  this.changes.clear();
508
+ // ArraySchema and MapSchema have a custom "encode end" method
455
509
  this.ref[$onEncodeEnd]?.();
456
510
  // Not a new instance anymore
457
511
  delete this[$isNew];
@@ -464,12 +518,12 @@ class ChangeTree {
464
518
  //
465
519
  this.ref[$onEncodeEnd]?.();
466
520
  this.changes.clear();
467
- this.filteredChanges.clear();
521
+ this.filteredChanges?.clear();
468
522
  // reset operation index
469
523
  this.currentOperationIndex = 0;
470
524
  if (discardAll) {
471
525
  this.allChanges.clear();
472
- this.allFilteredChanges.clear();
526
+ this.allFilteredChanges?.clear();
473
527
  // remove children references
474
528
  this.forEachChild((changeTree, _) => this.root?.remove(changeTree));
475
529
  }
@@ -496,48 +550,41 @@ class ChangeTree {
496
550
  get changed() {
497
551
  return this.changes.size > 0;
498
552
  }
499
- checkIsFiltered(parent, parentIndex) {
553
+ checkIsFiltered(metadata, parent, parentIndex) {
500
554
  // Detect if current structure has "filters" declared
501
- this.isPartiallyFiltered = (this.ref['constructor']?.[Symbol.metadata]?.[-2] !== undefined);
502
- if (parent && !Metadata.isValidInstance(parent)) {
503
- const parentChangeTree = parent[$changes];
504
- parent = parentChangeTree.parent;
505
- parentIndex = parentChangeTree.parentIndex;
506
- }
507
- const parentMetadata = parent?.['constructor']?.[Symbol.metadata];
508
- this.isFiltered = (parent &&
509
- parentMetadata?.[-2]?.includes(parentIndex));
510
- // this.isFiltered = this.ref['constructor']?.[Symbol.metadata]?.[-4];
511
- // // Detect if parent has "filters" declared
512
- // while (parent && !this.isFiltered) {
513
- // const metadata: Metadata = parent['constructor'][Symbol.metadata];
514
- // // this.isFiltered = metadata?.[-4];
515
- // const fieldName = metadata?.[parentIndex];
516
- // const isParentOwned = metadata?.[fieldName]?.tag !== undefined;
517
- // this.isFiltered = isParentOwned || parent[$changes].isFiltered; // metadata?.[-2]
518
- // parent = parent[$changes].parent;
519
- // };
520
- // console.log("ChangeTree.checkIsFiltered", {
521
- // parent: parent?.constructor.name,
522
- // ref: this.ref.constructor.name,
523
- // isFiltered: this.isFiltered,
524
- // isPartiallyFiltered: this.isPartiallyFiltered,
525
- // });
526
- //
527
- // TODO: refactor this!
528
- //
529
- // swapping `changes` and `filteredChanges` is required here
530
- // because "isFiltered" may not be imedialely available on `change()`
531
- //
532
- if (this.isFiltered && this.changes.size > 0) {
533
- // swap changes reference
534
- const changes = this.changes;
535
- this.changes = this.filteredChanges;
536
- this.filteredChanges = changes;
537
- // swap "all changes" reference
538
- const allFilteredChanges = this.allFilteredChanges;
539
- this.allFilteredChanges = this.allChanges;
540
- this.allChanges = allFilteredChanges;
555
+ this.isPartiallyFiltered = metadata?.[-2] !== undefined;
556
+ if (this.isPartiallyFiltered) {
557
+ this.filteredChanges = this.filteredChanges || new Map();
558
+ this.allFilteredChanges = this.allFilteredChanges || new Map();
559
+ }
560
+ if (parent) {
561
+ if (!Metadata.isValidInstance(parent)) {
562
+ const parentChangeTree = parent[$changes];
563
+ parent = parentChangeTree.parent;
564
+ parentIndex = parentChangeTree.parentIndex;
565
+ }
566
+ const parentMetadata = parent?.constructor?.[Symbol.metadata];
567
+ this.isFiltered = (parent && parentMetadata?.[-2]?.includes(parentIndex));
568
+ //
569
+ // TODO: refactor this!
570
+ //
571
+ // swapping `changes` and `filteredChanges` is required here
572
+ // because "isFiltered" may not be imedialely available on `change()`
573
+ //
574
+ if (this.isFiltered) {
575
+ this.filteredChanges = new Map();
576
+ this.allFilteredChanges = new Map();
577
+ if (this.changes.size > 0) {
578
+ // swap changes reference
579
+ const changes = this.changes;
580
+ this.changes = this.filteredChanges;
581
+ this.filteredChanges = changes;
582
+ // swap "all changes" reference
583
+ const allFilteredChanges = this.allFilteredChanges;
584
+ this.allFilteredChanges = this.allChanges;
585
+ this.allChanges = allFilteredChanges;
586
+ }
587
+ }
541
588
  }
542
589
  }
543
590
  }
@@ -821,62 +868,11 @@ var encode = /*#__PURE__*/Object.freeze({
821
868
  writeFloat64: writeFloat64
822
869
  });
823
870
 
824
- class EncodeSchemaError extends Error {
825
- }
826
- function assertType(value, type, klass, field) {
827
- let typeofTarget;
828
- let allowNull = false;
829
- switch (type) {
830
- case "number":
831
- case "int8":
832
- case "uint8":
833
- case "int16":
834
- case "uint16":
835
- case "int32":
836
- case "uint32":
837
- case "int64":
838
- case "uint64":
839
- case "float32":
840
- case "float64":
841
- typeofTarget = "number";
842
- if (isNaN(value)) {
843
- console.log(`trying to encode "NaN" in ${klass.constructor.name}#${field}`);
844
- }
845
- break;
846
- case "string":
847
- typeofTarget = "string";
848
- allowNull = true;
849
- break;
850
- case "boolean":
851
- // boolean is always encoded as true/false based on truthiness
852
- return;
853
- }
854
- if (typeof (value) !== typeofTarget && (!allowNull || (allowNull && value !== null))) {
855
- let foundValue = `'${JSON.stringify(value)}'${(value && value.constructor && ` (${value.constructor.name})`) || ''}`;
856
- throw new EncodeSchemaError(`a '${typeofTarget}' was expected, but ${foundValue} was provided in ${klass.constructor.name}#${field}`);
857
- }
858
- }
859
- function assertInstanceType(value, type, klass, field) {
860
- if (!(value instanceof type)) {
861
- throw new EncodeSchemaError(`a '${type.name}' was expected, but '${value && value.constructor.name}' was provided in ${klass.constructor.name}#${field}`);
862
- }
863
- }
864
-
865
- function encodePrimitiveType(type, bytes, value, klass, field, it) {
866
- assertType(value, type, klass, field);
867
- const encodeFunc = encode[type];
868
- if (encodeFunc) {
869
- encodeFunc(bytes, value, it);
870
- // encodeFunc(bytes, value);
871
- }
872
- else {
873
- throw new EncodeSchemaError(`a '${type}' was expected, but ${value} was provided in ${klass.constructor.name}#${field}`);
871
+ function encodeValue(encoder, bytes, type, value, operation, it) {
872
+ if (typeof (type) === "string") {
873
+ encode[type]?.(bytes, value, it);
874
874
  }
875
- }
876
- function encodeValue(encoder, bytes, ref, type, value, field, operation, it) {
877
- if (type[Symbol.metadata] !== undefined) {
878
- // TODO: move this to the `@type()` annotation
879
- assertInstanceType(value, type, ref, field);
875
+ else if (type[Symbol.metadata] !== undefined) {
880
876
  //
881
877
  // Encode refId for this instance.
882
878
  // The actual instance is going to be encoded on next `changeTree` iteration.
@@ -887,21 +883,7 @@ function encodeValue(encoder, bytes, ref, type, value, field, operation, it) {
887
883
  encoder.tryEncodeTypeId(bytes, type, value.constructor, it);
888
884
  }
889
885
  }
890
- else if (typeof (type) === "string") {
891
- //
892
- // Primitive values
893
- //
894
- encodePrimitiveType(type, bytes, value, ref, field, it);
895
- }
896
886
  else {
897
- //
898
- // Custom type (MapSchema, ArraySchema, etc)
899
- //
900
- const definition = getType(Object.keys(type)[0]);
901
- //
902
- // ensure a ArraySchema has been provided
903
- //
904
- assertInstanceType(ref[field], definition.constructor, ref, field);
905
887
  //
906
888
  // Encode refId for this instance.
907
889
  // The actual instance is going to be encoded on next `changeTree` iteration.
@@ -914,26 +896,23 @@ function encodeValue(encoder, bytes, ref, type, value, field, operation, it) {
914
896
  * @private
915
897
  */
916
898
  const encodeSchemaOperation = function (encoder, bytes, changeTree, index, operation, it) {
917
- const ref = changeTree.ref;
918
- const metadata = ref['constructor'][Symbol.metadata];
919
- const field = metadata[index];
920
- const type = metadata[field].type;
921
- const value = ref[field];
922
899
  // "compress" field index + operation
923
900
  bytes[it.offset++] = (index | operation) & 255;
924
901
  // Do not encode value for DELETE operations
925
902
  if (operation === exports.OPERATION.DELETE) {
926
903
  return;
927
904
  }
905
+ const ref = changeTree.ref;
906
+ const metadata = ref.constructor[Symbol.metadata];
907
+ const field = metadata[index];
928
908
  // TODO: inline this function call small performance gain
929
- encodeValue(encoder, bytes, ref, type, value, field, operation, it);
909
+ encodeValue(encoder, bytes, metadata[index].type, ref[field.name], operation, it);
930
910
  };
931
911
  /**
932
912
  * Used for collections (MapSchema, CollectionSchema, SetSchema)
933
913
  * @private
934
914
  */
935
- const encodeKeyValueOperation = function (encoder, bytes, changeTree, field, operation, it) {
936
- const ref = changeTree.ref;
915
+ const encodeKeyValueOperation = function (encoder, bytes, changeTree, index, operation, it) {
937
916
  // encode operation
938
917
  bytes[it.offset++] = operation & 255;
939
918
  // custom operations
@@ -941,25 +920,26 @@ const encodeKeyValueOperation = function (encoder, bytes, changeTree, field, ope
941
920
  return;
942
921
  }
943
922
  // encode index
944
- number$1(bytes, field, it);
923
+ number$1(bytes, index, it);
945
924
  // Do not encode value for DELETE operations
946
925
  if (operation === exports.OPERATION.DELETE) {
947
926
  return;
948
927
  }
928
+ const ref = changeTree.ref;
949
929
  //
950
930
  // encode "alias" for dynamic fields (maps)
951
931
  //
952
- if ((operation & exports.OPERATION.ADD) == exports.OPERATION.ADD) { // ADD or DELETE_AND_ADD
932
+ if ((operation & exports.OPERATION.ADD) === exports.OPERATION.ADD) { // ADD or DELETE_AND_ADD
953
933
  if (typeof (ref['set']) === "function") {
954
934
  //
955
935
  // MapSchema dynamic key
956
936
  //
957
- const dynamicIndex = changeTree.ref['$indexes'].get(field);
937
+ const dynamicIndex = changeTree.ref['$indexes'].get(index);
958
938
  string$1(bytes, dynamicIndex, it);
959
939
  }
960
940
  }
961
- const type = changeTree.getType(field);
962
- const value = changeTree.getValue(field);
941
+ const type = ref[$childType];
942
+ const value = ref[$getByIndex](index);
963
943
  // try { throw new Error(); } catch (e) {
964
944
  // // only print if not coming from Reflection.ts
965
945
  // if (!e.stack.includes("src/Reflection.ts")) {
@@ -973,7 +953,7 @@ const encodeKeyValueOperation = function (encoder, bytes, changeTree, field, ope
973
953
  // }
974
954
  // }
975
955
  // TODO: inline this function call small performance gain
976
- encodeValue(encoder, bytes, ref, type, value, field, operation, it);
956
+ encodeValue(encoder, bytes, type, value, operation, it);
977
957
  };
978
958
  /**
979
959
  * Used for collections (MapSchema, ArraySchema, etc.)
@@ -1017,7 +997,7 @@ const encodeArray = function (encoder, bytes, changeTree, field, operation, it,
1017
997
  // items: ref.toJSON(),
1018
998
  // });
1019
999
  // TODO: inline this function call small performance gain
1020
- encodeValue(encoder, bytes, ref, type, value, field, operation, it);
1000
+ encodeValue(encoder, bytes, type, value, operation, it);
1021
1001
  };
1022
1002
 
1023
1003
  /**
@@ -1323,7 +1303,9 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
1323
1303
  if (!value) {
1324
1304
  value = decoder.createInstanceOfType(childType);
1325
1305
  }
1326
- $root.addRef(refId, value, (value !== previousValue));
1306
+ $root.addRef(refId, value, (value !== previousValue || // increment ref count if value has changed
1307
+ (operation === exports.OPERATION.DELETE_AND_ADD && value === previousValue) // increment ref count if the same instance is being added again
1308
+ ));
1327
1309
  }
1328
1310
  }
1329
1311
  else if (typeof (type) === "string") {
@@ -1374,7 +1356,7 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
1374
1356
  }
1375
1357
  const decodeSchemaOperation = function (decoder, bytes, it, ref, allChanges) {
1376
1358
  const first_byte = bytes[it.offset++];
1377
- const metadata = ref['constructor'][Symbol.metadata];
1359
+ const metadata = ref.constructor[Symbol.metadata];
1378
1360
  // "compressed" index + operation
1379
1361
  const operation = (first_byte >> 6) << 6;
1380
1362
  const index = first_byte % (operation || 255);
@@ -1384,9 +1366,9 @@ const decodeSchemaOperation = function (decoder, bytes, it, ref, allChanges) {
1384
1366
  console.warn("@colyseus/schema: field not defined at", { index, ref: ref.constructor.name, metadata });
1385
1367
  return DEFINITION_MISMATCH;
1386
1368
  }
1387
- const { value, previousValue } = decodeValue(decoder, operation, ref, index, metadata[field].type, bytes, it, allChanges);
1369
+ const { value, previousValue } = decodeValue(decoder, operation, ref, index, field.type, bytes, it, allChanges);
1388
1370
  if (value !== null && value !== undefined) {
1389
- ref[field] = value;
1371
+ ref[field.name] = value;
1390
1372
  }
1391
1373
  // add change
1392
1374
  if (previousValue !== value) {
@@ -1394,7 +1376,7 @@ const decodeSchemaOperation = function (decoder, bytes, it, ref, allChanges) {
1394
1376
  ref,
1395
1377
  refId: decoder.currentRefId,
1396
1378
  op: operation,
1397
- field: field,
1379
+ field: field.name,
1398
1380
  value,
1399
1381
  previousValue,
1400
1382
  });
@@ -1492,7 +1474,6 @@ const decodeArray = function (decoder, bytes, it, ref, allChanges) {
1492
1474
  return;
1493
1475
  }
1494
1476
  else if (operation === exports.OPERATION.ADD_BY_REFID) {
1495
- // operation = OPERATION.ADD;
1496
1477
  const refId = number(bytes, it);
1497
1478
  const itemByRefId = decoder.root.refs.get(refId);
1498
1479
  // use existing index, or push new value
@@ -1526,6 +1507,47 @@ const decodeArray = function (decoder, bytes, it, ref, allChanges) {
1526
1507
  }
1527
1508
  };
1528
1509
 
1510
+ class EncodeSchemaError extends Error {
1511
+ }
1512
+ function assertType(value, type, klass, field) {
1513
+ let typeofTarget;
1514
+ let allowNull = false;
1515
+ switch (type) {
1516
+ case "number":
1517
+ case "int8":
1518
+ case "uint8":
1519
+ case "int16":
1520
+ case "uint16":
1521
+ case "int32":
1522
+ case "uint32":
1523
+ case "int64":
1524
+ case "uint64":
1525
+ case "float32":
1526
+ case "float64":
1527
+ typeofTarget = "number";
1528
+ if (isNaN(value)) {
1529
+ console.log(`trying to encode "NaN" in ${klass.constructor.name}#${field}`);
1530
+ }
1531
+ break;
1532
+ case "string":
1533
+ typeofTarget = "string";
1534
+ allowNull = true;
1535
+ break;
1536
+ case "boolean":
1537
+ // boolean is always encoded as true/false based on truthiness
1538
+ return;
1539
+ }
1540
+ if (typeof (value) !== typeofTarget && (!allowNull || (allowNull && value !== null))) {
1541
+ let foundValue = `'${JSON.stringify(value)}'${(value && value.constructor && ` (${value.constructor.name})`) || ''}`;
1542
+ throw new EncodeSchemaError(`a '${typeofTarget}' was expected, but ${foundValue} was provided in ${klass.constructor.name}#${field}`);
1543
+ }
1544
+ }
1545
+ function assertInstanceType(value, type, instance, field) {
1546
+ if (!(value instanceof type)) {
1547
+ throw new EncodeSchemaError(`a '${type.name}' was expected, but '${value && value.constructor.name}' was provided in ${instance.constructor.name}#${field}`);
1548
+ }
1549
+ }
1550
+
1529
1551
  var _a$4, _b$4;
1530
1552
  const DEFAULT_SORT = (a, b) => {
1531
1553
  const A = a.toString();
@@ -1591,6 +1613,7 @@ class ArraySchema {
1591
1613
  }
1592
1614
  else {
1593
1615
  if (setValue[$changes]) {
1616
+ assertInstanceType(setValue, obj[$childType], obj, key);
1594
1617
  if (obj.items[key] !== undefined) {
1595
1618
  if (setValue[$changes][$isNew]) {
1596
1619
  this[$changes].indexedOperation(Number(key), exports.OPERATION.MOVE_AND_ADD);
@@ -1637,6 +1660,7 @@ class ArraySchema {
1637
1660
  }
1638
1661
  });
1639
1662
  this[$changes] = new ChangeTree(proxy);
1663
+ this[$changes].indexes = {};
1640
1664
  this.push.apply(this, items);
1641
1665
  return proxy;
1642
1666
  }
@@ -1661,6 +1685,9 @@ class ArraySchema {
1661
1685
  if (value === undefined || value === null) {
1662
1686
  return;
1663
1687
  }
1688
+ else if (typeof (value) === "object" && this[$childType]) {
1689
+ assertInstanceType(value, this[$childType], this, i);
1690
+ }
1664
1691
  const changeTree = this[$changes];
1665
1692
  changeTree.indexedOperation(length, exports.OPERATION.ADD, this.items.length);
1666
1693
  this.items.push(value);
@@ -2188,6 +2215,7 @@ class MapSchema {
2188
2215
  this.$items = new Map();
2189
2216
  this.$indexes = new Map();
2190
2217
  this[$changes] = new ChangeTree(this);
2218
+ this[$changes].indexes = {};
2191
2219
  if (initialValues) {
2192
2220
  if (initialValues instanceof Map ||
2193
2221
  initialValues instanceof MapSchema) {
@@ -2214,6 +2242,9 @@ class MapSchema {
2214
2242
  if (value === undefined || value === null) {
2215
2243
  throw new Error(`MapSchema#set('${key}', ${value}): trying to set ${value} value on '${key}'.`);
2216
2244
  }
2245
+ else if (typeof (value) === "object" && this[$childType]) {
2246
+ assertInstanceType(value, this[$childType], this, key);
2247
+ }
2217
2248
  // Force "key" as string
2218
2249
  // See: https://github.com/colyseus/colyseus/issues/561#issuecomment-1646733468
2219
2250
  key = key.toString();
@@ -2419,27 +2450,20 @@ class TypeContext {
2419
2450
  if (parentFieldViewTag !== undefined) {
2420
2451
  this.parentFiltered[`${this.schemas.get(klass)}-${parentIndex}`] = true;
2421
2452
  }
2422
- for (const field in metadata) {
2423
- // //
2424
- // // Modify the field's metadata to include the parent field's view tag
2425
- // //
2426
- // if (
2427
- // parentFieldViewTag !== undefined &&
2428
- // metadata[field].tag === undefined
2429
- // ) {
2430
- // metadata[field].tag = parentFieldViewTag;
2431
- // }
2432
- const fieldType = metadata[field].type;
2433
- const viewTag = metadata[field].tag;
2453
+ for (const fieldIndex in metadata) {
2454
+ const index = fieldIndex;
2455
+ const fieldType = metadata[index].type;
2456
+ const viewTag = metadata[index].tag;
2434
2457
  if (typeof (fieldType) === "string") {
2435
2458
  continue;
2436
2459
  }
2437
2460
  if (Array.isArray(fieldType)) {
2438
2461
  const type = fieldType[0];
2462
+ // skip primitive types
2439
2463
  if (type === "string") {
2440
2464
  continue;
2441
2465
  }
2442
- this.discoverTypes(type, metadata[field].index, viewTag);
2466
+ this.discoverTypes(type, index, viewTag);
2443
2467
  }
2444
2468
  else if (typeof (fieldType) === "function") {
2445
2469
  this.discoverTypes(fieldType, viewTag);
@@ -2450,7 +2474,7 @@ class TypeContext {
2450
2474
  if (typeof (type) === "string") {
2451
2475
  continue;
2452
2476
  }
2453
- this.discoverTypes(type, metadata[field].index, viewTag);
2477
+ this.discoverTypes(type, index, viewTag);
2454
2478
  }
2455
2479
  }
2456
2480
  }
@@ -2606,17 +2630,18 @@ function view(tag = DEFAULT_VIEW_TAG) {
2606
2630
  const parentMetadata = parentClass[Symbol.metadata];
2607
2631
  // TODO: use Metadata.initialize()
2608
2632
  const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
2609
- if (!metadata[fieldName]) {
2610
- //
2611
- // detect index for this field, considering inheritance
2612
- //
2613
- metadata[fieldName] = {
2614
- type: undefined,
2615
- index: (metadata[-1] // current structure already has fields defined
2616
- ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
2617
- ?? -1) + 1 // no fields defined
2618
- };
2619
- }
2633
+ // const fieldIndex = metadata[fieldName];
2634
+ // if (!metadata[fieldIndex]) {
2635
+ // //
2636
+ // // detect index for this field, considering inheritance
2637
+ // //
2638
+ // metadata[fieldIndex] = {
2639
+ // type: undefined,
2640
+ // index: (metadata[-1] // current structure already has fields defined
2641
+ // ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
2642
+ // ?? -1) + 1 // no fields defined
2643
+ // }
2644
+ // }
2620
2645
  Metadata.setTag(metadata, fieldName, tag);
2621
2646
  };
2622
2647
  }
@@ -2631,16 +2656,16 @@ function type(type, options) {
2631
2656
  const parentClass = Object.getPrototypeOf(constructor);
2632
2657
  const parentMetadata = parentClass && parentClass[Symbol.metadata];
2633
2658
  const metadata = Metadata.initialize(constructor, parentMetadata);
2634
- let fieldIndex;
2659
+ let fieldIndex = metadata[field];
2635
2660
  /**
2636
2661
  * skip if descriptor already exists for this field (`@deprecated()`)
2637
2662
  */
2638
- if (metadata[field]) {
2639
- if (metadata[field].deprecated) {
2663
+ if (metadata[fieldIndex]) {
2664
+ if (metadata[fieldIndex].deprecated) {
2640
2665
  // do not create accessors for deprecated properties.
2641
2666
  return;
2642
2667
  }
2643
- else if (metadata[field].descriptor !== undefined) {
2668
+ else if (metadata[fieldIndex].type !== undefined) {
2644
2669
  // trying to define same property multiple times across inheritance.
2645
2670
  // https://github.com/colyseus/colyseus-unity3d/issues/131#issuecomment-814308572
2646
2671
  try {
@@ -2651,9 +2676,6 @@ function type(type, options) {
2651
2676
  throw new Error(`${e.message} ${definitionAtLine}`);
2652
2677
  }
2653
2678
  }
2654
- else {
2655
- fieldIndex = metadata[field].index;
2656
- }
2657
2679
  }
2658
2680
  else {
2659
2681
  //
@@ -2679,11 +2701,11 @@ function type(type, options) {
2679
2701
  const childType = (complexTypeKlass)
2680
2702
  ? Object.values(type)[0]
2681
2703
  : type;
2682
- Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass, metadata, field));
2704
+ Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass));
2683
2705
  }
2684
2706
  };
2685
2707
  }
2686
- function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass, metadata, field) {
2708
+ function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass) {
2687
2709
  return {
2688
2710
  get: function () { return this[fieldCached]; },
2689
2711
  set: function (value) {
@@ -2705,22 +2727,27 @@ function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass,
2705
2727
  }
2706
2728
  value[$childType] = type;
2707
2729
  }
2730
+ else if (typeof (type) !== "string") {
2731
+ assertInstanceType(value, type, this, fieldCached.substring(1));
2732
+ }
2733
+ else {
2734
+ assertType(value, type, this, fieldCached.substring(1));
2735
+ }
2736
+ const changeTree = this[$changes];
2708
2737
  //
2709
2738
  // Replacing existing "ref", remove it from root.
2710
2739
  // TODO: if there are other references to this instance, we should not remove it from root.
2711
2740
  //
2712
2741
  if (previousValue !== undefined && previousValue[$changes]) {
2713
- this[$changes].root?.remove(previousValue[$changes]);
2742
+ changeTree.root?.remove(previousValue[$changes]);
2714
2743
  }
2715
2744
  // flag the change for encoding.
2716
- this.constructor[$track](this[$changes], fieldIndex, exports.OPERATION.ADD);
2745
+ this.constructor[$track](changeTree, fieldIndex, exports.OPERATION.ADD);
2717
2746
  //
2718
2747
  // call setParent() recursively for this and its child
2719
2748
  // structures.
2720
2749
  //
2721
- if (value[$changes]) {
2722
- value[$changes].setParent(this, this[$changes].root, metadata[field].index);
2723
- }
2750
+ value[$changes]?.setParent(this, changeTree.root, fieldIndex);
2724
2751
  }
2725
2752
  else if (previousValue !== undefined) {
2726
2753
  //
@@ -2747,20 +2774,22 @@ function deprecated(throws = true) {
2747
2774
  const parentClass = Object.getPrototypeOf(constructor);
2748
2775
  const parentMetadata = parentClass[Symbol.metadata];
2749
2776
  const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
2750
- if (!metadata[field]) {
2751
- //
2752
- // detect index for this field, considering inheritance
2753
- //
2754
- metadata[field] = {
2755
- type: undefined,
2756
- index: (metadata[-1] // current structure already has fields defined
2757
- ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
2758
- ?? -1) + 1 // no fields defined
2759
- };
2760
- }
2761
- metadata[field].deprecated = true;
2777
+ const fieldIndex = metadata[field];
2778
+ // if (!metadata[field]) {
2779
+ // //
2780
+ // // detect index for this field, considering inheritance
2781
+ // //
2782
+ // metadata[field] = {
2783
+ // type: undefined,
2784
+ // index: (metadata[-1] // current structure already has fields defined
2785
+ // ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
2786
+ // ?? -1) + 1 // no fields defined
2787
+ // }
2788
+ // }
2789
+ metadata[fieldIndex].deprecated = true;
2762
2790
  if (throws) {
2763
- metadata[field].descriptor = {
2791
+ metadata[$descriptors] ??= {};
2792
+ metadata[$descriptors][field] = {
2764
2793
  get: function () { throw new Error(`${field} is deprecated.`); },
2765
2794
  set: function (value) { },
2766
2795
  enumerable: false,
@@ -2768,8 +2797,8 @@ function deprecated(throws = true) {
2768
2797
  };
2769
2798
  }
2770
2799
  // flag metadata[field] as non-enumerable
2771
- Object.defineProperty(metadata, field, {
2772
- value: metadata[field],
2800
+ Object.defineProperty(metadata, fieldIndex, {
2801
+ value: metadata[fieldIndex],
2773
2802
  enumerable: false,
2774
2803
  configurable: true
2775
2804
  });
@@ -2835,35 +2864,7 @@ class Schema {
2835
2864
  enumerable: false,
2836
2865
  writable: true
2837
2866
  });
2838
- const metadata = instance.constructor[Symbol.metadata];
2839
- // Define property descriptors
2840
- for (const field in metadata) {
2841
- if (metadata[field].descriptor) {
2842
- // for encoder
2843
- Object.defineProperty(instance, `_${field}`, {
2844
- value: undefined,
2845
- writable: true,
2846
- enumerable: false,
2847
- configurable: true,
2848
- });
2849
- Object.defineProperty(instance, field, metadata[field].descriptor);
2850
- }
2851
- else {
2852
- // for decoder
2853
- Object.defineProperty(instance, field, {
2854
- value: undefined,
2855
- writable: true,
2856
- enumerable: true,
2857
- configurable: true,
2858
- });
2859
- }
2860
- // Object.defineProperty(instance, field, {
2861
- // ...instance.constructor[Symbol.metadata][field].descriptor
2862
- // });
2863
- // if (args[0]?.hasOwnProperty(field)) {
2864
- // instance[field] = args[0][field];
2865
- // }
2866
- }
2867
+ Object.defineProperties(instance, instance.constructor[Symbol.metadata]?.[$descriptors] || {});
2867
2868
  }
2868
2869
  static is(type) {
2869
2870
  return typeof (type[Symbol.metadata]) === "object";
@@ -2887,7 +2888,7 @@ class Schema {
2887
2888
  */
2888
2889
  static [$filter](ref, index, view) {
2889
2890
  const metadata = ref.constructor[Symbol.metadata];
2890
- const tag = metadata[metadata[index]].tag;
2891
+ const tag = metadata[index]?.tag;
2891
2892
  if (view === undefined) {
2892
2893
  // shared pass/encode: encode if doesn't have a tag
2893
2894
  return tag === undefined;
@@ -2908,12 +2909,21 @@ class Schema {
2908
2909
  }
2909
2910
  // allow inherited classes to have a constructor
2910
2911
  constructor(...args) {
2911
- Schema.initialize(this);
2912
+ //
2913
+ // inline
2914
+ // Schema.initialize(this);
2915
+ //
2916
+ Object.defineProperty(this, $changes, {
2917
+ value: new ChangeTree(this),
2918
+ enumerable: false,
2919
+ writable: true
2920
+ });
2921
+ Object.defineProperties(this, this.constructor[Symbol.metadata]?.[$descriptors] || {});
2912
2922
  //
2913
2923
  // Assign initial values
2914
2924
  //
2915
2925
  if (args[0]) {
2916
- this.assign(args[0]);
2926
+ Object.assign(this, args[0]);
2917
2927
  }
2918
2928
  }
2919
2929
  assign(props) {
@@ -2927,7 +2937,8 @@ class Schema {
2927
2937
  * @param operation OPERATION to perform (detected automatically)
2928
2938
  */
2929
2939
  setDirty(property, operation) {
2930
- this[$changes].change(this.constructor[Symbol.metadata][property].index, operation);
2940
+ const metadata = this.constructor[Symbol.metadata];
2941
+ this[$changes].change(metadata[metadata[property]].index, operation);
2931
2942
  }
2932
2943
  clone() {
2933
2944
  const cloned = new (this.constructor);
@@ -2936,7 +2947,9 @@ class Schema {
2936
2947
  // TODO: clone all properties, not only annotated ones
2937
2948
  //
2938
2949
  // for (const field in this) {
2939
- for (const field in metadata) {
2950
+ for (const fieldIndex in metadata) {
2951
+ // const field = metadata[metadata[fieldIndex]].name;
2952
+ const field = metadata[fieldIndex].name;
2940
2953
  if (typeof (this[field]) === "object" &&
2941
2954
  typeof (this[field]?.clone) === "function") {
2942
2955
  // deep clone
@@ -2950,10 +2963,11 @@ class Schema {
2950
2963
  return cloned;
2951
2964
  }
2952
2965
  toJSON() {
2953
- const metadata = this.constructor[Symbol.metadata];
2954
2966
  const obj = {};
2955
- for (const fieldName in metadata) {
2956
- const field = metadata[fieldName];
2967
+ const metadata = this.constructor[Symbol.metadata];
2968
+ for (const index in metadata) {
2969
+ const field = metadata[index];
2970
+ const fieldName = field.name;
2957
2971
  if (!field.deprecated && this[fieldName] !== null && typeof (this[fieldName]) !== "undefined") {
2958
2972
  obj[fieldName] = (typeof (this[fieldName]['toJSON']) === "function")
2959
2973
  ? this[fieldName]['toJSON']()
@@ -2966,10 +2980,12 @@ class Schema {
2966
2980
  this[$changes].discardAll();
2967
2981
  }
2968
2982
  [$getByIndex](index) {
2969
- return this[this.constructor[Symbol.metadata][index]];
2983
+ const metadata = this.constructor[Symbol.metadata];
2984
+ return this[metadata[index].name];
2970
2985
  }
2971
2986
  [$deleteByIndex](index) {
2972
- this[this.constructor[Symbol.metadata][index]] = undefined;
2987
+ const metadata = this.constructor[Symbol.metadata];
2988
+ this[metadata[index].name] = undefined;
2973
2989
  }
2974
2990
  static debugRefIds(instance, jsonContents = true, level = 0) {
2975
2991
  const ref = instance;
@@ -3093,6 +3109,7 @@ class CollectionSchema {
3093
3109
  this.$indexes = new Map();
3094
3110
  this.$refId = 0;
3095
3111
  this[$changes] = new ChangeTree(this);
3112
+ this[$changes].indexes = {};
3096
3113
  if (initialValues) {
3097
3114
  initialValues.forEach((v) => this.add(v));
3098
3115
  }
@@ -3248,6 +3265,7 @@ class SetSchema {
3248
3265
  this.$indexes = new Map();
3249
3266
  this.$refId = 0;
3250
3267
  this[$changes] = new ChangeTree(this);
3268
+ this[$changes].indexes = {};
3251
3269
  if (initialValues) {
3252
3270
  initialValues.forEach((v) => this.add(v));
3253
3271
  }
@@ -3434,24 +3452,36 @@ class Root {
3434
3452
  return this.nextUniqueId++;
3435
3453
  }
3436
3454
  add(changeTree) {
3437
- const refCount = this.refCount.get(changeTree) || 0;
3438
- this.refCount.set(changeTree, refCount + 1);
3455
+ const previousRefCount = this.refCount.get(changeTree);
3456
+ if (previousRefCount === 0) {
3457
+ //
3458
+ // When a ChangeTree is re-added, it means that it was previously removed.
3459
+ // We need to re-add all changes to the `changes` map.
3460
+ //
3461
+ changeTree.allChanges.forEach((operation, index) => {
3462
+ changeTree.changes.set(index, operation);
3463
+ });
3464
+ }
3465
+ const refCount = (previousRefCount || 0) + 1;
3466
+ this.refCount.set(changeTree, refCount);
3467
+ return refCount;
3439
3468
  }
3440
3469
  remove(changeTree) {
3441
- const refCount = this.refCount.get(changeTree);
3442
- if (refCount <= 1) {
3470
+ const refCount = (this.refCount.get(changeTree)) - 1;
3471
+ if (refCount <= 0) {
3443
3472
  this.allChanges.delete(changeTree);
3444
3473
  this.changes.delete(changeTree);
3445
3474
  if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
3446
3475
  this.allFilteredChanges.delete(changeTree);
3447
3476
  this.filteredChanges.delete(changeTree);
3448
3477
  }
3449
- this.refCount.delete(changeTree);
3478
+ this.refCount.set(changeTree, 0);
3450
3479
  }
3451
3480
  else {
3452
- this.refCount.set(changeTree, refCount - 1);
3481
+ this.refCount.set(changeTree, refCount);
3453
3482
  }
3454
3483
  changeTree.forEachChild((child, _) => this.remove(child));
3484
+ return refCount;
3455
3485
  }
3456
3486
  clear() {
3457
3487
  this.changes.clear();
@@ -3482,10 +3512,10 @@ class Encoder {
3482
3512
  ) {
3483
3513
  const hasView = (view !== undefined);
3484
3514
  const rootChangeTree = this.state[$changes];
3485
- const changeTreesIterator = changeTrees.entries();
3486
- for (const [changeTree, changes] of changeTreesIterator) {
3515
+ const shouldClearChanges = !isEncodeAll && !hasView;
3516
+ for (const [changeTree, changes] of changeTrees.entries()) {
3487
3517
  const ref = changeTree.ref;
3488
- const ctor = ref['constructor'];
3518
+ const ctor = ref.constructor;
3489
3519
  const encoder = ctor[$encoder];
3490
3520
  const filter = ctor[$filter];
3491
3521
  // try { throw new Error(); } catch (e) {
@@ -3509,8 +3539,7 @@ class Encoder {
3509
3539
  buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
3510
3540
  number$1(buffer, changeTree.refId, it);
3511
3541
  }
3512
- const changesIterator = changes.entries();
3513
- for (const [fieldIndex, operation] of changesIterator) {
3542
+ for (const [fieldIndex, operation] of changes.entries()) {
3514
3543
  //
3515
3544
  // first pass (encodeAll), identify "filtered" operations without encoding them
3516
3545
  // they will be encoded per client, based on their view.
@@ -3535,6 +3564,14 @@ class Encoder {
3535
3564
  // }
3536
3565
  encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView);
3537
3566
  }
3567
+ // if (shouldClearChanges) {
3568
+ // // changeTree.endEncode();
3569
+ // changeTree.changes.clear();
3570
+ // // ArraySchema and MapSchema have a custom "encode end" method
3571
+ // changeTree.ref[$onEncodeEnd]?.();
3572
+ // // Not a new instance anymore
3573
+ // delete changeTree[$isNew];
3574
+ // }
3538
3575
  }
3539
3576
  if (it.offset > buffer.byteLength) {
3540
3577
  const newSize = getNextPowerOf2(buffer.byteLength * 2);
@@ -3557,7 +3594,7 @@ class Encoder {
3557
3594
  //
3558
3595
  // only clear changes after making sure buffer resize is not required.
3559
3596
  //
3560
- if (!isEncodeAll && !hasView) {
3597
+ if (shouldClearChanges) {
3561
3598
  //
3562
3599
  // FIXME: avoid iterating over change trees twice.
3563
3600
  //
@@ -3641,6 +3678,11 @@ class Encoder {
3641
3678
  const changeTreesIterator = changeTrees.entries();
3642
3679
  for (const [changeTree, _] of changeTreesIterator) {
3643
3680
  changeTree.endEncode();
3681
+ // changeTree.changes.clear();
3682
+ // // ArraySchema and MapSchema have a custom "encode end" method
3683
+ // changeTree.ref[$onEncodeEnd]?.();
3684
+ // // Not a new instance anymore
3685
+ // delete changeTree[$isNew];
3644
3686
  }
3645
3687
  }
3646
3688
  discardChanges() {
@@ -3756,8 +3798,9 @@ class ReferenceTracker {
3756
3798
  // Ensure child schema instances have their references removed as well.
3757
3799
  //
3758
3800
  if (Metadata.isValidInstance(ref)) {
3759
- const metadata = ref['constructor'][Symbol.metadata];
3760
- for (const field in metadata) {
3801
+ const metadata = ref.constructor[Symbol.metadata];
3802
+ for (const index in metadata) {
3803
+ const field = metadata[index].name;
3761
3804
  const childRefId = typeof (ref[field]) === "object" && this.refIds.get(ref[field]);
3762
3805
  if (childRefId) {
3763
3806
  this.removeRef(childRefId);
@@ -3944,7 +3987,9 @@ class Reflection extends Schema {
3944
3987
  const reflection = new Reflection();
3945
3988
  const encoder = new Encoder(reflection);
3946
3989
  const buildType = (currentType, metadata) => {
3947
- for (const fieldName in metadata) {
3990
+ for (const fieldIndex in metadata) {
3991
+ const index = Number(fieldIndex);
3992
+ const fieldName = metadata[index].name;
3948
3993
  // skip fields from parent classes
3949
3994
  if (!Object.prototype.hasOwnProperty.call(metadata, fieldName)) {
3950
3995
  continue;
@@ -3952,7 +3997,7 @@ class Reflection extends Schema {
3952
3997
  const field = new ReflectionField();
3953
3998
  field.name = fieldName;
3954
3999
  let fieldType;
3955
- const type = metadata[fieldName].type;
4000
+ const type = metadata[index].type;
3956
4001
  if (typeof (type) === "string") {
3957
4002
  fieldType = type;
3958
4003
  }
@@ -4018,18 +4063,7 @@ class Reflection extends Schema {
4018
4063
  reflection.types.forEach((reflectionType) => {
4019
4064
  const schemaType = typeContext.get(reflectionType.id);
4020
4065
  const metadata = schemaType[Symbol.metadata];
4021
- // FIXME: use metadata[-1] to get field count
4022
4066
  const parentFieldIndex = 0;
4023
- // console.log("--------------------");
4024
- // // console.log("reflectionType", reflectionType.toJSON());
4025
- // console.log("reflectionType.fields", reflectionType.fields.toJSON());
4026
- // console.log("parentFieldIndex", parentFieldIndex);
4027
- //
4028
- // FIXME: set fields using parentKlass as well
4029
- // currently the fields are duplicated on inherited classes
4030
- //
4031
- // // const parentKlass = reflection.types[reflectionType.extendsId];
4032
- // // parentKlass.fields
4033
4067
  reflectionType.fields.forEach((field, i) => {
4034
4068
  const fieldIndex = parentFieldIndex + i;
4035
4069
  if (field.referencedType !== undefined) {
@@ -4194,7 +4228,7 @@ function getDecoderStateCallbacks(decoder) {
4194
4228
  //
4195
4229
  bindTo: function bindTo(targetObject, properties) {
4196
4230
  if (!properties) {
4197
- properties = Object.keys(metadata);
4231
+ properties = Object.keys(metadata).map((index) => metadata[index].name);
4198
4232
  }
4199
4233
  return $root.addCallback($root.refIds.get(context.instance), exports.OPERATION.REPLACE, () => {
4200
4234
  properties.forEach((prop) => targetObject[prop] = context.instance[prop]);
@@ -4202,7 +4236,8 @@ function getDecoderStateCallbacks(decoder) {
4202
4236
  }
4203
4237
  }, {
4204
4238
  get(target, prop) {
4205
- if (metadata[prop]) {
4239
+ const metadataField = metadata[metadata[prop]];
4240
+ if (metadataField) {
4206
4241
  const instance = context.instance?.[prop];
4207
4242
  const onInstanceAvailable = ((callback) => {
4208
4243
  const unbind = $(context.instance).listen(prop, (value, _) => {
@@ -4218,7 +4253,7 @@ function getDecoderStateCallbacks(decoder) {
4218
4253
  callback(instance, true);
4219
4254
  }
4220
4255
  });
4221
- return getProxy(metadata[prop].type, {
4256
+ return getProxy(metadataField.type, {
4222
4257
  // make sure refId is available, otherwise need to wait for the instance to be available.
4223
4258
  instance: ($root.refIds.get(instance) && instance),
4224
4259
  parentInstance: context.instance,
@@ -4331,7 +4366,7 @@ class StateView {
4331
4366
  console.warn("StateView#add(), invalid object:", obj);
4332
4367
  return this;
4333
4368
  }
4334
- // FIXME: ArraySchema/MapSchema does not have metadata
4369
+ // FIXME: ArraySchema/MapSchema do not have metadata
4335
4370
  const metadata = obj.constructor[Symbol.metadata];
4336
4371
  const changeTree = obj[$changes];
4337
4372
  this.items.add(changeTree);
@@ -4377,7 +4412,7 @@ class StateView {
4377
4412
  ? changeTree.allFilteredChanges
4378
4413
  : changeTree.allChanges;
4379
4414
  changeSet.forEach((op, index) => {
4380
- const tagAtIndex = metadata?.[metadata?.[index]].tag;
4415
+ const tagAtIndex = metadata?.[index].tag;
4381
4416
  if ((isInvisible || // if "invisible", include all
4382
4417
  tagAtIndex === undefined || // "all change" with no tag
4383
4418
  tagAtIndex === tag // tagged property
@@ -4390,7 +4425,7 @@ class StateView {
4390
4425
  // Add children of this ChangeTree to this view
4391
4426
  changeTree.forEachChild((change, index) => {
4392
4427
  // Do not ADD children that don't have the same tag
4393
- if (metadata && metadata[metadata[index]].tag !== tag) {
4428
+ if (metadata && metadata[index].tag !== tag) {
4394
4429
  return;
4395
4430
  }
4396
4431
  this.add(change.ref, tag, false);