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