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

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 (73) hide show
  1. package/build/cjs/index.js +383 -341
  2. package/build/cjs/index.js.map +1 -1
  3. package/build/esm/index.mjs +383 -341
  4. package/build/esm/index.mjs.map +1 -1
  5. package/build/umd/index.js +383 -341
  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 +33 -11
  17. package/lib/bench_encode.js.map +1 -1
  18. package/lib/decoder/DecodeOperation.js +4 -8
  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 +128 -115
  26. package/lib/encoder/ChangeTree.js.map +1 -1
  27. package/lib/encoder/EncodeOperation.d.ts +1 -4
  28. package/lib/encoder/EncodeOperation.js +46 -46
  29. package/lib/encoder/EncodeOperation.js.map +1 -1
  30. package/lib/encoder/Encoder.js +11 -3
  31. package/lib/encoder/Encoder.js.map +1 -1
  32. package/lib/encoder/StateView.js +3 -3
  33. package/lib/encoder/StateView.js.map +1 -1
  34. package/lib/encoding/assert.d.ts +2 -1
  35. package/lib/encoding/assert.js +2 -2
  36. package/lib/encoding/assert.js.map +1 -1
  37. package/lib/index.d.ts +1 -2
  38. package/lib/index.js +11 -10
  39. package/lib/index.js.map +1 -1
  40. package/lib/types/TypeContext.js +7 -14
  41. package/lib/types/TypeContext.js.map +1 -1
  42. package/lib/types/custom/ArraySchema.js +6 -0
  43. package/lib/types/custom/ArraySchema.js.map +1 -1
  44. package/lib/types/custom/CollectionSchema.js +1 -0
  45. package/lib/types/custom/CollectionSchema.js.map +1 -1
  46. package/lib/types/custom/MapSchema.js +5 -0
  47. package/lib/types/custom/MapSchema.js.map +1 -1
  48. package/lib/types/custom/SetSchema.js +1 -0
  49. package/lib/types/custom/SetSchema.js.map +1 -1
  50. package/lib/types/symbols.d.ts +1 -0
  51. package/lib/types/symbols.js +2 -1
  52. package/lib/types/symbols.js.map +1 -1
  53. package/package.json +1 -1
  54. package/src/Metadata.ts +60 -29
  55. package/src/Reflection.ts +5 -15
  56. package/src/Schema.ts +33 -45
  57. package/src/annotations.ts +75 -67
  58. package/src/bench_encode.ts +37 -13
  59. package/src/decoder/DecodeOperation.ts +4 -10
  60. package/src/decoder/ReferenceTracker.ts +3 -2
  61. package/src/decoder/strategy/StateCallbacks.ts +4 -3
  62. package/src/encoder/ChangeTree.ts +146 -135
  63. package/src/encoder/EncodeOperation.ts +64 -58
  64. package/src/encoder/Encoder.ts +16 -4
  65. package/src/encoder/StateView.ts +4 -4
  66. package/src/encoding/assert.ts +4 -3
  67. package/src/index.ts +1 -4
  68. package/src/types/TypeContext.ts +10 -15
  69. package/src/types/custom/ArraySchema.ts +8 -0
  70. package/src/types/custom/CollectionSchema.ts +1 -0
  71. package/src/types/custom/MapSchema.ts +6 -0
  72. package/src/types/custom/SetSchema.ts +1 -0
  73. 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,9 +464,7 @@ 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);
@@ -439,7 +487,7 @@ class ChangeTree {
439
487
  //
440
488
  // FIXME: this is looking a bit ugly (and repeated from `.change()`)
441
489
  //
442
- if (isFiltered) {
490
+ if (this.filteredChanges) {
443
491
  this.root?.filteredChanges.set(this, this.filteredChanges);
444
492
  this.allFilteredChanges.delete(allChangesIndex);
445
493
  }
@@ -450,6 +498,7 @@ class ChangeTree {
450
498
  }
451
499
  endEncode() {
452
500
  this.changes.clear();
501
+ // ArraySchema and MapSchema have a custom "encode end" method
453
502
  this.ref[$onEncodeEnd]?.();
454
503
  // Not a new instance anymore
455
504
  delete this[$isNew];
@@ -462,12 +511,12 @@ class ChangeTree {
462
511
  //
463
512
  this.ref[$onEncodeEnd]?.();
464
513
  this.changes.clear();
465
- this.filteredChanges.clear();
514
+ this.filteredChanges?.clear();
466
515
  // reset operation index
467
516
  this.currentOperationIndex = 0;
468
517
  if (discardAll) {
469
518
  this.allChanges.clear();
470
- this.allFilteredChanges.clear();
519
+ this.allFilteredChanges?.clear();
471
520
  // remove children references
472
521
  this.forEachChild((changeTree, _) => this.root?.remove(changeTree));
473
522
  }
@@ -494,48 +543,41 @@ class ChangeTree {
494
543
  get changed() {
495
544
  return this.changes.size > 0;
496
545
  }
497
- checkIsFiltered(parent, parentIndex) {
546
+ checkIsFiltered(metadata, parent, parentIndex) {
498
547
  // 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;
548
+ this.isPartiallyFiltered = metadata?.[-2] !== undefined;
549
+ if (this.isPartiallyFiltered) {
550
+ this.filteredChanges = this.filteredChanges || new Map();
551
+ this.allFilteredChanges = this.allFilteredChanges || new Map();
552
+ }
553
+ if (parent) {
554
+ if (!Metadata.isValidInstance(parent)) {
555
+ const parentChangeTree = parent[$changes];
556
+ parent = parentChangeTree.parent;
557
+ parentIndex = parentChangeTree.parentIndex;
558
+ }
559
+ const parentMetadata = parent?.constructor?.[Symbol.metadata];
560
+ this.isFiltered = (parent && parentMetadata?.[-2]?.includes(parentIndex));
561
+ //
562
+ // TODO: refactor this!
563
+ //
564
+ // swapping `changes` and `filteredChanges` is required here
565
+ // because "isFiltered" may not be imedialely available on `change()`
566
+ //
567
+ if (this.isFiltered) {
568
+ this.filteredChanges = new Map();
569
+ this.allFilteredChanges = new Map();
570
+ if (this.changes.size > 0) {
571
+ // swap changes reference
572
+ const changes = this.changes;
573
+ this.changes = this.filteredChanges;
574
+ this.filteredChanges = changes;
575
+ // swap "all changes" reference
576
+ const allFilteredChanges = this.allFilteredChanges;
577
+ this.allFilteredChanges = this.allChanges;
578
+ this.allChanges = allFilteredChanges;
579
+ }
580
+ }
539
581
  }
540
582
  }
541
583
  }
@@ -819,62 +861,21 @@ var encode = /*#__PURE__*/Object.freeze({
819
861
  writeFloat64: writeFloat64
820
862
  });
821
863
 
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}`);
864
+ function encodeValue(encoder, bytes,
865
+ // ref: Ref,
866
+ type, value,
867
+ // field: string | number,
868
+ operation, it) {
869
+ if (typeof (type) === "string") {
870
+ //
871
+ // Primitive values
872
+ //
873
+ // assertType(value, type as string, ref as Schema, field);
874
+ encode[type]?.(bytes, value, it);
872
875
  }
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);
876
+ else if (type[Symbol.metadata] !== undefined) {
877
+ // // TODO: move this to the `@type()` annotation
878
+ // assertInstanceType(value, type as typeof Schema, ref as Schema, field);
878
879
  //
879
880
  // Encode refId for this instance.
880
881
  // The actual instance is going to be encoded on next `changeTree` iteration.
@@ -885,21 +886,15 @@ function encodeValue(encoder, bytes, ref, type, value, field, operation, it) {
885
886
  encoder.tryEncodeTypeId(bytes, type, value.constructor, it);
886
887
  }
887
888
  }
888
- else if (typeof (type) === "string") {
889
- //
890
- // Primitive values
891
- //
892
- encodePrimitiveType(type, bytes, value, ref, field, it);
893
- }
894
889
  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);
890
+ // //
891
+ // // Custom type (MapSchema, ArraySchema, etc)
892
+ // //
893
+ // const definition = getType(Object.keys(type)[0]);
894
+ // //
895
+ // // ensure a ArraySchema has been provided
896
+ // //
897
+ // assertInstanceType(ref[field], definition.constructor, ref as Schema, field);
903
898
  //
904
899
  // Encode refId for this instance.
905
900
  // The actual instance is going to be encoded on next `changeTree` iteration.
@@ -912,26 +907,27 @@ function encodeValue(encoder, bytes, ref, type, value, field, operation, it) {
912
907
  * @private
913
908
  */
914
909
  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
910
  // "compress" field index + operation
921
911
  bytes[it.offset++] = (index | operation) & 255;
922
912
  // Do not encode value for DELETE operations
923
913
  if (operation === OPERATION.DELETE) {
924
914
  return;
925
915
  }
916
+ const ref = changeTree.ref;
917
+ const metadata = ref['constructor'][Symbol.metadata];
918
+ const field = metadata[index];
926
919
  // TODO: inline this function call small performance gain
927
- encodeValue(encoder, bytes, ref, type, value, field, operation, it);
920
+ encodeValue(encoder, bytes,
921
+ // ref,
922
+ metadata[index].type, ref[field.name],
923
+ // index,
924
+ operation, it);
928
925
  };
929
926
  /**
930
927
  * Used for collections (MapSchema, CollectionSchema, SetSchema)
931
928
  * @private
932
929
  */
933
- const encodeKeyValueOperation = function (encoder, bytes, changeTree, field, operation, it) {
934
- const ref = changeTree.ref;
930
+ const encodeKeyValueOperation = function (encoder, bytes, changeTree, index, operation, it) {
935
931
  // encode operation
936
932
  bytes[it.offset++] = operation & 255;
937
933
  // custom operations
@@ -939,11 +935,12 @@ const encodeKeyValueOperation = function (encoder, bytes, changeTree, field, ope
939
935
  return;
940
936
  }
941
937
  // encode index
942
- number$1(bytes, field, it);
938
+ number$1(bytes, index, it);
943
939
  // Do not encode value for DELETE operations
944
940
  if (operation === OPERATION.DELETE) {
945
941
  return;
946
942
  }
943
+ const ref = changeTree.ref;
947
944
  //
948
945
  // encode "alias" for dynamic fields (maps)
949
946
  //
@@ -952,12 +949,12 @@ const encodeKeyValueOperation = function (encoder, bytes, changeTree, field, ope
952
949
  //
953
950
  // MapSchema dynamic key
954
951
  //
955
- const dynamicIndex = changeTree.ref['$indexes'].get(field);
952
+ const dynamicIndex = changeTree.ref['$indexes'].get(index);
956
953
  string$1(bytes, dynamicIndex, it);
957
954
  }
958
955
  }
959
- const type = changeTree.getType(field);
960
- const value = changeTree.getValue(field);
956
+ const type = changeTree.getType(index);
957
+ const value = changeTree.getValue(index);
961
958
  // try { throw new Error(); } catch (e) {
962
959
  // // only print if not coming from Reflection.ts
963
960
  // if (!e.stack.includes("src/Reflection.ts")) {
@@ -971,7 +968,11 @@ const encodeKeyValueOperation = function (encoder, bytes, changeTree, field, ope
971
968
  // }
972
969
  // }
973
970
  // TODO: inline this function call small performance gain
974
- encodeValue(encoder, bytes, ref, type, value, field, operation, it);
971
+ encodeValue(encoder, bytes,
972
+ // ref,
973
+ type, value,
974
+ // index,
975
+ operation, it);
975
976
  };
976
977
  /**
977
978
  * Used for collections (MapSchema, ArraySchema, etc.)
@@ -1015,7 +1016,11 @@ const encodeArray = function (encoder, bytes, changeTree, field, operation, it,
1015
1016
  // items: ref.toJSON(),
1016
1017
  // });
1017
1018
  // TODO: inline this function call small performance gain
1018
- encodeValue(encoder, bytes, ref, type, value, field, operation, it);
1019
+ encodeValue(encoder, bytes,
1020
+ // ref,
1021
+ type, value,
1022
+ // field,
1023
+ operation, it);
1019
1024
  };
1020
1025
 
1021
1026
  /**
@@ -1372,7 +1377,7 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
1372
1377
  }
1373
1378
  const decodeSchemaOperation = function (decoder, bytes, it, ref, allChanges) {
1374
1379
  const first_byte = bytes[it.offset++];
1375
- const metadata = ref['constructor'][Symbol.metadata];
1380
+ const metadata = ref.constructor[Symbol.metadata];
1376
1381
  // "compressed" index + operation
1377
1382
  const operation = (first_byte >> 6) << 6;
1378
1383
  const index = first_byte % (operation || 255);
@@ -1382,9 +1387,9 @@ const decodeSchemaOperation = function (decoder, bytes, it, ref, allChanges) {
1382
1387
  console.warn("@colyseus/schema: field not defined at", { index, ref: ref.constructor.name, metadata });
1383
1388
  return DEFINITION_MISMATCH;
1384
1389
  }
1385
- const { value, previousValue } = decodeValue(decoder, operation, ref, index, metadata[field].type, bytes, it, allChanges);
1390
+ const { value, previousValue } = decodeValue(decoder, operation, ref, index, field.type, bytes, it, allChanges);
1386
1391
  if (value !== null && value !== undefined) {
1387
- ref[field] = value;
1392
+ ref[field.name] = value;
1388
1393
  }
1389
1394
  // add change
1390
1395
  if (previousValue !== value) {
@@ -1392,7 +1397,7 @@ const decodeSchemaOperation = function (decoder, bytes, it, ref, allChanges) {
1392
1397
  ref,
1393
1398
  refId: decoder.currentRefId,
1394
1399
  op: operation,
1395
- field: field,
1400
+ field: field.name,
1396
1401
  value,
1397
1402
  previousValue,
1398
1403
  });
@@ -1490,7 +1495,6 @@ const decodeArray = function (decoder, bytes, it, ref, allChanges) {
1490
1495
  return;
1491
1496
  }
1492
1497
  else if (operation === OPERATION.ADD_BY_REFID) {
1493
- // operation = OPERATION.ADD;
1494
1498
  const refId = number(bytes, it);
1495
1499
  const itemByRefId = decoder.root.refs.get(refId);
1496
1500
  // use existing index, or push new value
@@ -1524,6 +1528,47 @@ const decodeArray = function (decoder, bytes, it, ref, allChanges) {
1524
1528
  }
1525
1529
  };
1526
1530
 
1531
+ class EncodeSchemaError extends Error {
1532
+ }
1533
+ function assertType(value, type, klass, field) {
1534
+ let typeofTarget;
1535
+ let allowNull = false;
1536
+ switch (type) {
1537
+ case "number":
1538
+ case "int8":
1539
+ case "uint8":
1540
+ case "int16":
1541
+ case "uint16":
1542
+ case "int32":
1543
+ case "uint32":
1544
+ case "int64":
1545
+ case "uint64":
1546
+ case "float32":
1547
+ case "float64":
1548
+ typeofTarget = "number";
1549
+ if (isNaN(value)) {
1550
+ console.log(`trying to encode "NaN" in ${klass.constructor.name}#${field}`);
1551
+ }
1552
+ break;
1553
+ case "string":
1554
+ typeofTarget = "string";
1555
+ allowNull = true;
1556
+ break;
1557
+ case "boolean":
1558
+ // boolean is always encoded as true/false based on truthiness
1559
+ return;
1560
+ }
1561
+ if (typeof (value) !== typeofTarget && (!allowNull || (allowNull && value !== null))) {
1562
+ let foundValue = `'${JSON.stringify(value)}'${(value && value.constructor && ` (${value.constructor.name})`) || ''}`;
1563
+ throw new EncodeSchemaError(`a '${typeofTarget}' was expected, but ${foundValue} was provided in ${klass.constructor.name}#${field}`);
1564
+ }
1565
+ }
1566
+ function assertInstanceType(value, type, instance, field) {
1567
+ if (!(value instanceof type)) {
1568
+ throw new EncodeSchemaError(`a '${type.name}' was expected, but '${value && value.constructor.name}' was provided in ${instance.constructor.name}#${field}`);
1569
+ }
1570
+ }
1571
+
1527
1572
  var _a$4, _b$4;
1528
1573
  const DEFAULT_SORT = (a, b) => {
1529
1574
  const A = a.toString();
@@ -1589,6 +1634,7 @@ class ArraySchema {
1589
1634
  }
1590
1635
  else {
1591
1636
  if (setValue[$changes]) {
1637
+ assertInstanceType(setValue, obj[$childType], obj, key);
1592
1638
  if (obj.items[key] !== undefined) {
1593
1639
  if (setValue[$changes][$isNew]) {
1594
1640
  this[$changes].indexedOperation(Number(key), OPERATION.MOVE_AND_ADD);
@@ -1635,6 +1681,7 @@ class ArraySchema {
1635
1681
  }
1636
1682
  });
1637
1683
  this[$changes] = new ChangeTree(proxy);
1684
+ this[$changes].indexes = {};
1638
1685
  this.push.apply(this, items);
1639
1686
  return proxy;
1640
1687
  }
@@ -1659,6 +1706,9 @@ class ArraySchema {
1659
1706
  if (value === undefined || value === null) {
1660
1707
  return;
1661
1708
  }
1709
+ else if (typeof (value) === "object" && this[$childType]) {
1710
+ assertInstanceType(value, this[$childType], this, i);
1711
+ }
1662
1712
  const changeTree = this[$changes];
1663
1713
  changeTree.indexedOperation(length, OPERATION.ADD, this.items.length);
1664
1714
  this.items.push(value);
@@ -2186,6 +2236,7 @@ class MapSchema {
2186
2236
  this.$items = new Map();
2187
2237
  this.$indexes = new Map();
2188
2238
  this[$changes] = new ChangeTree(this);
2239
+ this[$changes].indexes = {};
2189
2240
  if (initialValues) {
2190
2241
  if (initialValues instanceof Map ||
2191
2242
  initialValues instanceof MapSchema) {
@@ -2212,6 +2263,9 @@ class MapSchema {
2212
2263
  if (value === undefined || value === null) {
2213
2264
  throw new Error(`MapSchema#set('${key}', ${value}): trying to set ${value} value on '${key}'.`);
2214
2265
  }
2266
+ else if (typeof (value) === "object" && this[$childType]) {
2267
+ assertInstanceType(value, this[$childType], this, key);
2268
+ }
2215
2269
  // Force "key" as string
2216
2270
  // See: https://github.com/colyseus/colyseus/issues/561#issuecomment-1646733468
2217
2271
  key = key.toString();
@@ -2417,27 +2471,20 @@ class TypeContext {
2417
2471
  if (parentFieldViewTag !== undefined) {
2418
2472
  this.parentFiltered[`${this.schemas.get(klass)}-${parentIndex}`] = true;
2419
2473
  }
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;
2474
+ for (const fieldIndex in metadata) {
2475
+ const index = fieldIndex;
2476
+ const fieldType = metadata[index].type;
2477
+ const viewTag = metadata[index].tag;
2432
2478
  if (typeof (fieldType) === "string") {
2433
2479
  continue;
2434
2480
  }
2435
2481
  if (Array.isArray(fieldType)) {
2436
2482
  const type = fieldType[0];
2483
+ // skip primitive types
2437
2484
  if (type === "string") {
2438
2485
  continue;
2439
2486
  }
2440
- this.discoverTypes(type, metadata[field].index, viewTag);
2487
+ this.discoverTypes(type, index, viewTag);
2441
2488
  }
2442
2489
  else if (typeof (fieldType) === "function") {
2443
2490
  this.discoverTypes(fieldType, viewTag);
@@ -2448,7 +2495,7 @@ class TypeContext {
2448
2495
  if (typeof (type) === "string") {
2449
2496
  continue;
2450
2497
  }
2451
- this.discoverTypes(type, metadata[field].index, viewTag);
2498
+ this.discoverTypes(type, index, viewTag);
2452
2499
  }
2453
2500
  }
2454
2501
  }
@@ -2604,17 +2651,18 @@ function view(tag = DEFAULT_VIEW_TAG) {
2604
2651
  const parentMetadata = parentClass[Symbol.metadata];
2605
2652
  // TODO: use Metadata.initialize()
2606
2653
  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
- }
2654
+ // const fieldIndex = metadata[fieldName];
2655
+ // if (!metadata[fieldIndex]) {
2656
+ // //
2657
+ // // detect index for this field, considering inheritance
2658
+ // //
2659
+ // metadata[fieldIndex] = {
2660
+ // type: undefined,
2661
+ // index: (metadata[-1] // current structure already has fields defined
2662
+ // ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
2663
+ // ?? -1) + 1 // no fields defined
2664
+ // }
2665
+ // }
2618
2666
  Metadata.setTag(metadata, fieldName, tag);
2619
2667
  };
2620
2668
  }
@@ -2629,16 +2677,16 @@ function type(type, options) {
2629
2677
  const parentClass = Object.getPrototypeOf(constructor);
2630
2678
  const parentMetadata = parentClass && parentClass[Symbol.metadata];
2631
2679
  const metadata = Metadata.initialize(constructor, parentMetadata);
2632
- let fieldIndex;
2680
+ let fieldIndex = metadata[field];
2633
2681
  /**
2634
2682
  * skip if descriptor already exists for this field (`@deprecated()`)
2635
2683
  */
2636
- if (metadata[field]) {
2637
- if (metadata[field].deprecated) {
2684
+ if (metadata[fieldIndex]) {
2685
+ if (metadata[fieldIndex].deprecated) {
2638
2686
  // do not create accessors for deprecated properties.
2639
2687
  return;
2640
2688
  }
2641
- else if (metadata[field].descriptor !== undefined) {
2689
+ else if (metadata[fieldIndex].type !== undefined) {
2642
2690
  // trying to define same property multiple times across inheritance.
2643
2691
  // https://github.com/colyseus/colyseus-unity3d/issues/131#issuecomment-814308572
2644
2692
  try {
@@ -2649,9 +2697,6 @@ function type(type, options) {
2649
2697
  throw new Error(`${e.message} ${definitionAtLine}`);
2650
2698
  }
2651
2699
  }
2652
- else {
2653
- fieldIndex = metadata[field].index;
2654
- }
2655
2700
  }
2656
2701
  else {
2657
2702
  //
@@ -2677,11 +2722,11 @@ function type(type, options) {
2677
2722
  const childType = (complexTypeKlass)
2678
2723
  ? Object.values(type)[0]
2679
2724
  : type;
2680
- Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass, metadata, field));
2725
+ Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass));
2681
2726
  }
2682
2727
  };
2683
2728
  }
2684
- function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass, metadata, field) {
2729
+ function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass) {
2685
2730
  return {
2686
2731
  get: function () { return this[fieldCached]; },
2687
2732
  set: function (value) {
@@ -2703,22 +2748,27 @@ function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass,
2703
2748
  }
2704
2749
  value[$childType] = type;
2705
2750
  }
2751
+ else if (typeof (type) !== "string") {
2752
+ assertInstanceType(value, type, this, fieldCached.substring(1));
2753
+ }
2754
+ else {
2755
+ assertType(value, type, this, fieldCached.substring(1));
2756
+ }
2757
+ const changeTree = this[$changes];
2706
2758
  //
2707
2759
  // Replacing existing "ref", remove it from root.
2708
2760
  // TODO: if there are other references to this instance, we should not remove it from root.
2709
2761
  //
2710
2762
  if (previousValue !== undefined && previousValue[$changes]) {
2711
- this[$changes].root?.remove(previousValue[$changes]);
2763
+ changeTree.root?.remove(previousValue[$changes]);
2712
2764
  }
2713
2765
  // flag the change for encoding.
2714
- this.constructor[$track](this[$changes], fieldIndex, OPERATION.ADD);
2766
+ this.constructor[$track](changeTree, fieldIndex, OPERATION.ADD);
2715
2767
  //
2716
2768
  // call setParent() recursively for this and its child
2717
2769
  // structures.
2718
2770
  //
2719
- if (value[$changes]) {
2720
- value[$changes].setParent(this, this[$changes].root, metadata[field].index);
2721
- }
2771
+ value[$changes]?.setParent(this, changeTree.root, fieldIndex);
2722
2772
  }
2723
2773
  else if (previousValue !== undefined) {
2724
2774
  //
@@ -2745,20 +2795,22 @@ function deprecated(throws = true) {
2745
2795
  const parentClass = Object.getPrototypeOf(constructor);
2746
2796
  const parentMetadata = parentClass[Symbol.metadata];
2747
2797
  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;
2798
+ const fieldIndex = metadata[field];
2799
+ // if (!metadata[field]) {
2800
+ // //
2801
+ // // detect index for this field, considering inheritance
2802
+ // //
2803
+ // metadata[field] = {
2804
+ // type: undefined,
2805
+ // index: (metadata[-1] // current structure already has fields defined
2806
+ // ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
2807
+ // ?? -1) + 1 // no fields defined
2808
+ // }
2809
+ // }
2810
+ metadata[fieldIndex].deprecated = true;
2760
2811
  if (throws) {
2761
- metadata[field].descriptor = {
2812
+ metadata[$descriptors] ??= {};
2813
+ metadata[$descriptors][field] = {
2762
2814
  get: function () { throw new Error(`${field} is deprecated.`); },
2763
2815
  set: function (value) { },
2764
2816
  enumerable: false,
@@ -2766,8 +2818,8 @@ function deprecated(throws = true) {
2766
2818
  };
2767
2819
  }
2768
2820
  // flag metadata[field] as non-enumerable
2769
- Object.defineProperty(metadata, field, {
2770
- value: metadata[field],
2821
+ Object.defineProperty(metadata, fieldIndex, {
2822
+ value: metadata[fieldIndex],
2771
2823
  enumerable: false,
2772
2824
  configurable: true
2773
2825
  });
@@ -2833,35 +2885,7 @@ class Schema {
2833
2885
  enumerable: false,
2834
2886
  writable: true
2835
2887
  });
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
- }
2888
+ Object.defineProperties(instance, instance.constructor[Symbol.metadata]?.[$descriptors] || {});
2865
2889
  }
2866
2890
  static is(type) {
2867
2891
  return typeof (type[Symbol.metadata]) === "object";
@@ -2885,7 +2909,7 @@ class Schema {
2885
2909
  */
2886
2910
  static [$filter](ref, index, view) {
2887
2911
  const metadata = ref.constructor[Symbol.metadata];
2888
- const tag = metadata[metadata[index]].tag;
2912
+ const tag = metadata[index]?.tag;
2889
2913
  if (view === undefined) {
2890
2914
  // shared pass/encode: encode if doesn't have a tag
2891
2915
  return tag === undefined;
@@ -2906,12 +2930,21 @@ class Schema {
2906
2930
  }
2907
2931
  // allow inherited classes to have a constructor
2908
2932
  constructor(...args) {
2909
- Schema.initialize(this);
2933
+ //
2934
+ // inline
2935
+ // Schema.initialize(this);
2936
+ //
2937
+ Object.defineProperty(this, $changes, {
2938
+ value: new ChangeTree(this),
2939
+ enumerable: false,
2940
+ writable: true
2941
+ });
2942
+ Object.defineProperties(this, this.constructor[Symbol.metadata]?.[$descriptors] || {});
2910
2943
  //
2911
2944
  // Assign initial values
2912
2945
  //
2913
2946
  if (args[0]) {
2914
- this.assign(args[0]);
2947
+ Object.assign(this, args[0]);
2915
2948
  }
2916
2949
  }
2917
2950
  assign(props) {
@@ -2925,7 +2958,8 @@ class Schema {
2925
2958
  * @param operation OPERATION to perform (detected automatically)
2926
2959
  */
2927
2960
  setDirty(property, operation) {
2928
- this[$changes].change(this.constructor[Symbol.metadata][property].index, operation);
2961
+ const metadata = this.constructor[Symbol.metadata];
2962
+ this[$changes].change(metadata[metadata[property]].index, operation);
2929
2963
  }
2930
2964
  clone() {
2931
2965
  const cloned = new (this.constructor);
@@ -2934,7 +2968,9 @@ class Schema {
2934
2968
  // TODO: clone all properties, not only annotated ones
2935
2969
  //
2936
2970
  // for (const field in this) {
2937
- for (const field in metadata) {
2971
+ for (const fieldIndex in metadata) {
2972
+ // const field = metadata[metadata[fieldIndex]].name;
2973
+ const field = metadata[fieldIndex].name;
2938
2974
  if (typeof (this[field]) === "object" &&
2939
2975
  typeof (this[field]?.clone) === "function") {
2940
2976
  // deep clone
@@ -2948,10 +2984,11 @@ class Schema {
2948
2984
  return cloned;
2949
2985
  }
2950
2986
  toJSON() {
2951
- const metadata = this.constructor[Symbol.metadata];
2952
2987
  const obj = {};
2953
- for (const fieldName in metadata) {
2954
- const field = metadata[fieldName];
2988
+ const metadata = this.constructor[Symbol.metadata];
2989
+ for (const index in metadata) {
2990
+ const field = metadata[index];
2991
+ const fieldName = field.name;
2955
2992
  if (!field.deprecated && this[fieldName] !== null && typeof (this[fieldName]) !== "undefined") {
2956
2993
  obj[fieldName] = (typeof (this[fieldName]['toJSON']) === "function")
2957
2994
  ? this[fieldName]['toJSON']()
@@ -2964,10 +3001,12 @@ class Schema {
2964
3001
  this[$changes].discardAll();
2965
3002
  }
2966
3003
  [$getByIndex](index) {
2967
- return this[this.constructor[Symbol.metadata][index]];
3004
+ const metadata = this.constructor[Symbol.metadata];
3005
+ return this[metadata[index].name];
2968
3006
  }
2969
3007
  [$deleteByIndex](index) {
2970
- this[this.constructor[Symbol.metadata][index]] = undefined;
3008
+ const metadata = this.constructor[Symbol.metadata];
3009
+ this[metadata[index].name] = undefined;
2971
3010
  }
2972
3011
  static debugRefIds(instance, jsonContents = true, level = 0) {
2973
3012
  const ref = instance;
@@ -3091,6 +3130,7 @@ class CollectionSchema {
3091
3130
  this.$indexes = new Map();
3092
3131
  this.$refId = 0;
3093
3132
  this[$changes] = new ChangeTree(this);
3133
+ this[$changes].indexes = {};
3094
3134
  if (initialValues) {
3095
3135
  initialValues.forEach((v) => this.add(v));
3096
3136
  }
@@ -3246,6 +3286,7 @@ class SetSchema {
3246
3286
  this.$indexes = new Map();
3247
3287
  this.$refId = 0;
3248
3288
  this[$changes] = new ChangeTree(this);
3289
+ this[$changes].indexes = {};
3249
3290
  if (initialValues) {
3250
3291
  initialValues.forEach((v) => this.add(v));
3251
3292
  }
@@ -3480,8 +3521,8 @@ class Encoder {
3480
3521
  ) {
3481
3522
  const hasView = (view !== undefined);
3482
3523
  const rootChangeTree = this.state[$changes];
3483
- const changeTreesIterator = changeTrees.entries();
3484
- for (const [changeTree, changes] of changeTreesIterator) {
3524
+ const shouldClearChanges = !isEncodeAll && !hasView;
3525
+ for (const [changeTree, changes] of changeTrees.entries()) {
3485
3526
  const ref = changeTree.ref;
3486
3527
  const ctor = ref['constructor'];
3487
3528
  const encoder = ctor[$encoder];
@@ -3533,6 +3574,9 @@ class Encoder {
3533
3574
  // }
3534
3575
  encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView);
3535
3576
  }
3577
+ // if (shouldClearChanges) {
3578
+ // changeTree.endEncode();
3579
+ // }
3536
3580
  }
3537
3581
  if (it.offset > buffer.byteLength) {
3538
3582
  const newSize = getNextPowerOf2(buffer.byteLength * 2);
@@ -3555,7 +3599,7 @@ class Encoder {
3555
3599
  //
3556
3600
  // only clear changes after making sure buffer resize is not required.
3557
3601
  //
3558
- if (!isEncodeAll && !hasView) {
3602
+ if (shouldClearChanges) {
3559
3603
  //
3560
3604
  // FIXME: avoid iterating over change trees twice.
3561
3605
  //
@@ -3639,6 +3683,11 @@ class Encoder {
3639
3683
  const changeTreesIterator = changeTrees.entries();
3640
3684
  for (const [changeTree, _] of changeTreesIterator) {
3641
3685
  changeTree.endEncode();
3686
+ // changeTree.changes.clear();
3687
+ // // ArraySchema and MapSchema have a custom "encode end" method
3688
+ // changeTree.ref[$onEncodeEnd]?.();
3689
+ // // Not a new instance anymore
3690
+ // delete changeTree[$isNew];
3642
3691
  }
3643
3692
  }
3644
3693
  discardChanges() {
@@ -3754,8 +3803,9 @@ class ReferenceTracker {
3754
3803
  // Ensure child schema instances have their references removed as well.
3755
3804
  //
3756
3805
  if (Metadata.isValidInstance(ref)) {
3757
- const metadata = ref['constructor'][Symbol.metadata];
3758
- for (const field in metadata) {
3806
+ const metadata = ref.constructor[Symbol.metadata];
3807
+ for (const index in metadata) {
3808
+ const field = metadata[index].name;
3759
3809
  const childRefId = typeof (ref[field]) === "object" && this.refIds.get(ref[field]);
3760
3810
  if (childRefId) {
3761
3811
  this.removeRef(childRefId);
@@ -3942,7 +3992,9 @@ class Reflection extends Schema {
3942
3992
  const reflection = new Reflection();
3943
3993
  const encoder = new Encoder(reflection);
3944
3994
  const buildType = (currentType, metadata) => {
3945
- for (const fieldName in metadata) {
3995
+ for (const fieldIndex in metadata) {
3996
+ const index = Number(fieldIndex);
3997
+ const fieldName = metadata[index].name;
3946
3998
  // skip fields from parent classes
3947
3999
  if (!Object.prototype.hasOwnProperty.call(metadata, fieldName)) {
3948
4000
  continue;
@@ -3950,7 +4002,7 @@ class Reflection extends Schema {
3950
4002
  const field = new ReflectionField();
3951
4003
  field.name = fieldName;
3952
4004
  let fieldType;
3953
- const type = metadata[fieldName].type;
4005
+ const type = metadata[index].type;
3954
4006
  if (typeof (type) === "string") {
3955
4007
  fieldType = type;
3956
4008
  }
@@ -4016,18 +4068,7 @@ class Reflection extends Schema {
4016
4068
  reflection.types.forEach((reflectionType) => {
4017
4069
  const schemaType = typeContext.get(reflectionType.id);
4018
4070
  const metadata = schemaType[Symbol.metadata];
4019
- // FIXME: use metadata[-1] to get field count
4020
4071
  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
4072
  reflectionType.fields.forEach((field, i) => {
4032
4073
  const fieldIndex = parentFieldIndex + i;
4033
4074
  if (field.referencedType !== undefined) {
@@ -4192,7 +4233,7 @@ function getDecoderStateCallbacks(decoder) {
4192
4233
  //
4193
4234
  bindTo: function bindTo(targetObject, properties) {
4194
4235
  if (!properties) {
4195
- properties = Object.keys(metadata);
4236
+ properties = Object.keys(metadata).map((index) => metadata[index].name);
4196
4237
  }
4197
4238
  return $root.addCallback($root.refIds.get(context.instance), OPERATION.REPLACE, () => {
4198
4239
  properties.forEach((prop) => targetObject[prop] = context.instance[prop]);
@@ -4200,7 +4241,8 @@ function getDecoderStateCallbacks(decoder) {
4200
4241
  }
4201
4242
  }, {
4202
4243
  get(target, prop) {
4203
- if (metadata[prop]) {
4244
+ const metadataField = metadata[metadata[prop]];
4245
+ if (metadataField) {
4204
4246
  const instance = context.instance?.[prop];
4205
4247
  const onInstanceAvailable = ((callback) => {
4206
4248
  const unbind = $(context.instance).listen(prop, (value, _) => {
@@ -4216,7 +4258,7 @@ function getDecoderStateCallbacks(decoder) {
4216
4258
  callback(instance, true);
4217
4259
  }
4218
4260
  });
4219
- return getProxy(metadata[prop].type, {
4261
+ return getProxy(metadataField.type, {
4220
4262
  // make sure refId is available, otherwise need to wait for the instance to be available.
4221
4263
  instance: ($root.refIds.get(instance) && instance),
4222
4264
  parentInstance: context.instance,
@@ -4329,7 +4371,7 @@ class StateView {
4329
4371
  console.warn("StateView#add(), invalid object:", obj);
4330
4372
  return this;
4331
4373
  }
4332
- // FIXME: ArraySchema/MapSchema does not have metadata
4374
+ // FIXME: ArraySchema/MapSchema do not have metadata
4333
4375
  const metadata = obj.constructor[Symbol.metadata];
4334
4376
  const changeTree = obj[$changes];
4335
4377
  this.items.add(changeTree);
@@ -4375,7 +4417,7 @@ class StateView {
4375
4417
  ? changeTree.allFilteredChanges
4376
4418
  : changeTree.allChanges;
4377
4419
  changeSet.forEach((op, index) => {
4378
- const tagAtIndex = metadata?.[metadata?.[index]].tag;
4420
+ const tagAtIndex = metadata?.[index].tag;
4379
4421
  if ((isInvisible || // if "invisible", include all
4380
4422
  tagAtIndex === undefined || // "all change" with no tag
4381
4423
  tagAtIndex === tag // tagged property
@@ -4388,7 +4430,7 @@ class StateView {
4388
4430
  // Add children of this ChangeTree to this view
4389
4431
  changeTree.forEachChild((change, index) => {
4390
4432
  // Do not ADD children that don't have the same tag
4391
- if (metadata && metadata[metadata[index]].tag !== tag) {
4433
+ if (metadata && metadata[index].tag !== tag) {
4392
4434
  return;
4393
4435
  }
4394
4436
  this.add(change.ref, tag, false);