@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
@@ -40,6 +40,7 @@
40
40
  const $filter = Symbol("$filter");
41
41
  const $getByIndex = Symbol("$getByIndex");
42
42
  const $deleteByIndex = Symbol("$deleteByIndex");
43
+ const $descriptors = Symbol("$descriptors");
43
44
  /**
44
45
  * Used to hold ChangeTree instances whitin the structures
45
46
  */
@@ -75,34 +76,67 @@
75
76
  }
76
77
 
77
78
  const Metadata = {
78
- addField(metadata, index, field, type, descriptor) {
79
+ addField(metadata, index, name, type, descriptor) {
79
80
  if (index > 64) {
80
- throw new Error(`Can't define field '${field}'.\nSchema instances may only have up to 64 fields.`);
81
+ throw new Error(`Can't define field '${name}'.\nSchema instances may only have up to 64 fields.`);
81
82
  }
82
- metadata[field] = Object.assign(metadata[field] || {}, // avoid overwriting previous field metadata (@owned / @deprecated)
83
+ metadata[index] = Object.assign(metadata[index] || {}, // avoid overwriting previous field metadata (@owned / @deprecated)
83
84
  {
84
85
  type: (Array.isArray(type))
85
86
  ? { array: type[0] }
86
87
  : type,
87
88
  index,
88
- descriptor,
89
+ name,
89
90
  });
91
+ // create "descriptors" map
92
+ metadata[$descriptors] ??= {};
93
+ if (descriptor) {
94
+ // for encoder
95
+ metadata[$descriptors][name] = descriptor;
96
+ metadata[$descriptors][`_${name}`] = {
97
+ value: undefined,
98
+ writable: true,
99
+ enumerable: false,
100
+ configurable: true,
101
+ };
102
+ }
103
+ else {
104
+ // for decoder
105
+ metadata[$descriptors][name] = {
106
+ value: undefined,
107
+ writable: true,
108
+ enumerable: true,
109
+ configurable: true,
110
+ };
111
+ }
90
112
  // map -1 as last field index
91
113
  Object.defineProperty(metadata, -1, {
92
114
  value: index,
93
115
  enumerable: false,
94
116
  configurable: true
95
117
  });
96
- // map index => field name (non enumerable)
97
- Object.defineProperty(metadata, index, {
98
- value: field,
118
+ // map field name => index (non enumerable)
119
+ Object.defineProperty(metadata, name, {
120
+ value: index,
99
121
  enumerable: false,
100
122
  configurable: true,
101
123
  });
124
+ // if child Ref/complex type, add to -4
125
+ if (typeof (metadata[index].type) !== "string") {
126
+ if (metadata[-4] === undefined) {
127
+ Object.defineProperty(metadata, -4, {
128
+ value: [],
129
+ enumerable: false,
130
+ configurable: true,
131
+ });
132
+ }
133
+ metadata[-4].push(index);
134
+ }
102
135
  },
103
136
  setTag(metadata, fieldName, tag) {
137
+ const index = metadata[fieldName];
138
+ const field = metadata[index];
104
139
  // add 'tag' to the field
105
- const field = metadata[fieldName];
106
140
  field.tag = tag;
107
141
  if (!metadata[-2]) {
108
142
  // -2: all field indexes with "view" tag
@@ -118,20 +152,14 @@
118
152
  configurable: true
119
153
  });
120
154
  }
121
- metadata[-2].push(field.index);
155
+ metadata[-2].push(index);
122
156
  if (!metadata[-3][tag]) {
123
157
  metadata[-3][tag] = [];
124
158
  }
125
- metadata[-3][tag].push(field.index);
159
+ metadata[-3][tag].push(index);
126
160
  },
127
161
  setFields(target, fields) {
128
162
  const metadata = (target.prototype.constructor[Symbol.metadata] ??= {});
129
- // target[$track] = function (changeTree, index: number, operation: OPERATION = OPERATION.ADD) {
130
- // changeTree.change(index, operation, encodeSchemaOperation);
131
- // };
132
- // target[$encoder] = encodeSchemaOperation;
133
- // target[$decoder] = decodeSchemaOperation;
134
- // if (!target.prototype.toJSON) { target.prototype.toJSON = Schema.prototype.toJSON; }
135
163
  let index = 0;
136
164
  for (const field in fields) {
137
165
  const type = fields[field];
@@ -139,7 +167,7 @@
139
167
  const complexTypeKlass = (Array.isArray(type))
140
168
  ? getType("array")
141
169
  : (typeof (Object.keys(type)[0]) === "string") && getType(Object.keys(type)[0]);
142
- Metadata.addField(metadata, index, field, type, getPropertyDescriptor(`_${field}`, index, type, complexTypeKlass, metadata, field));
170
+ Metadata.addField(metadata, index, field, type, getPropertyDescriptor(`_${field}`, index, type, complexTypeKlass));
143
171
  index++;
144
172
  }
145
173
  },
@@ -168,8 +196,9 @@
168
196
  // assign parent metadata to current
169
197
  Object.assign(metadata, parentMetadata);
170
198
  for (let i = 0; i <= parentMetadata[-1]; i++) {
171
- Object.defineProperty(metadata, i, {
172
- value: parentMetadata[i],
199
+ const fieldName = parentMetadata[i].name;
200
+ Object.defineProperty(metadata, fieldName, {
201
+ value: parentMetadata[fieldName],
173
202
  enumerable: false,
174
203
  configurable: true,
175
204
  });
@@ -193,7 +222,7 @@
193
222
  const metadata = klass[Symbol.metadata];
194
223
  const fields = {};
195
224
  for (let i = 0; i <= metadata[-1]; i++) {
196
- fields[metadata[i]] = metadata[metadata[i]].type;
225
+ fields[metadata[i].name] = metadata[i].type;
197
226
  }
198
227
  return fields;
199
228
  }
@@ -202,48 +231,59 @@
202
231
  var _a$5;
203
232
  class ChangeTree {
204
233
  static { _a$5 = $isNew; }
205
- ;
206
234
  constructor(ref) {
207
- this.indexes = {}; // TODO: remove this, only used by MapSchema/SetSchema/CollectionSchema (`encodeKeyValueOperation`)
235
+ this.isFiltered = false;
236
+ this.isPartiallyFiltered = false;
208
237
  this.currentOperationIndex = 0;
209
- this.allChanges = new Map();
210
- this.allFilteredChanges = new Map();
211
238
  this.changes = new Map();
212
- this.filteredChanges = new Map();
239
+ this.allChanges = new Map();
213
240
  this[_a$5] = true;
214
241
  this.ref = ref;
242
+ //
243
+ // Does this structure have "filters" declared?
244
+ //
245
+ if (ref.constructor[Symbol.metadata]?.[-2]) {
246
+ this.allFilteredChanges = new Map();
247
+ this.filteredChanges = new Map();
248
+ }
215
249
  }
216
250
  setRoot(root) {
217
251
  this.root = root;
218
252
  this.root.add(this);
219
- //
220
- // At Schema initialization, the "root" structure might not be available
221
- // yet, as it only does once the "Encoder" has been set up.
222
- //
223
- // So the "parent" may be already set without a "root".
224
- //
225
- this.checkIsFiltered(this.parent, this.parentIndex);
226
- // unique refId for the ChangeTree.
227
- this.ensureRefId();
253
+ const metadata = this.ref.constructor[Symbol.metadata];
254
+ if (this.root.types.hasFilters) {
255
+ //
256
+ // At Schema initialization, the "root" structure might not be available
257
+ // yet, as it only does once the "Encoder" has been set up.
258
+ //
259
+ // So the "parent" may be already set without a "root".
260
+ //
261
+ this.checkIsFiltered(metadata, this.parent, this.parentIndex);
262
+ if (this.isFiltered || this.isPartiallyFiltered) {
263
+ this.root.allFilteredChanges.set(this, this.allFilteredChanges);
264
+ this.root.filteredChanges.set(this, this.filteredChanges);
265
+ }
266
+ }
228
267
  if (!this.isFiltered) {
229
268
  this.root.changes.set(this, this.changes);
269
+ this.root.allChanges.set(this, this.allChanges);
230
270
  }
231
- if (this.isFiltered || this.isPartiallyFiltered) {
232
- this.root.allFilteredChanges.set(this, this.allFilteredChanges);
233
- this.root.filteredChanges.set(this, this.filteredChanges);
271
+ this.ensureRefId();
272
+ if (metadata) {
273
+ metadata[-4]?.forEach((index) => {
274
+ const field = metadata[index];
275
+ const value = this.ref[field.name];
276
+ if (value) {
277
+ value[$changes].setRoot(root);
278
+ }
279
+ });
234
280
  }
235
- if (!this.isFiltered) {
236
- this.root.allChanges.set(this, this.allChanges);
281
+ else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
282
+ // MapSchema / ArraySchema, etc.
283
+ this.ref.forEach((value, key) => {
284
+ value[$changes].setRoot(root);
285
+ });
237
286
  }
238
- this.forEachChild((changeTree, _) => {
239
- changeTree.setRoot(root);
240
- });
241
- // this.allChanges.forEach((_, index) => {
242
- // const childRef = this.ref[$getByIndex](index);
243
- // if (childRef && childRef[$changes]) {
244
- // childRef[$changes].setRoot(root);
245
- // }
246
- // });
247
287
  }
248
288
  setParent(parent, root, parentIndex) {
249
289
  this.parent = parent;
@@ -253,48 +293,60 @@
253
293
  return;
254
294
  }
255
295
  root.add(this);
296
+ const metadata = this.ref.constructor[Symbol.metadata];
256
297
  // skip if parent is already set
257
- if (root === this.root) {
258
- this.forEachChild((changeTree, atIndex) => {
259
- changeTree.setParent(this.ref, root, atIndex);
260
- });
261
- return;
298
+ if (root !== this.root) {
299
+ this.root = root;
300
+ if (root.types.hasFilters) {
301
+ this.checkIsFiltered(metadata, parent, parentIndex);
302
+ if (this.isFiltered || this.isPartiallyFiltered) {
303
+ this.root.filteredChanges.set(this, this.filteredChanges);
304
+ this.root.allFilteredChanges.set(this, this.filteredChanges);
305
+ }
306
+ }
307
+ if (!this.isFiltered) {
308
+ this.root.changes.set(this, this.changes);
309
+ this.root.allChanges.set(this, this.allChanges);
310
+ }
311
+ this.ensureRefId();
262
312
  }
263
- this.root = root;
264
- this.checkIsFiltered(parent, parentIndex);
265
- if (!this.isFiltered) {
266
- this.root.changes.set(this, this.changes);
267
- this.root.allChanges.set(this, this.allChanges);
313
+ // assign same parent on child structures
314
+ if (metadata) {
315
+ metadata[-4]?.forEach((index) => {
316
+ const field = metadata[index];
317
+ const value = this.ref[field.name];
318
+ value?.[$changes].setParent(this.ref, root, index);
319
+ // console.log(this.ref.constructor.name, field.name, value);
320
+ // try { throw new Error(); } catch (e) {
321
+ // console.log(e.stack);
322
+ // }
323
+ });
268
324
  }
269
- if (this.isFiltered || this.isPartiallyFiltered) {
270
- this.root.filteredChanges.set(this, this.filteredChanges);
271
- this.root.allFilteredChanges.set(this, this.filteredChanges);
325
+ else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
326
+ // MapSchema / ArraySchema, etc.
327
+ this.ref.forEach((value, key) => {
328
+ value[$changes].setParent(this.ref, root, this.indexes[key] ?? key);
329
+ });
272
330
  }
273
- this.ensureRefId();
274
- this.forEachChild((changeTree, atIndex) => {
275
- changeTree.setParent(this.ref, root, atIndex);
276
- });
277
331
  }
278
332
  forEachChild(callback) {
279
333
  //
280
334
  // assign same parent on child structures
281
335
  //
282
- if (Metadata.isValidInstance(this.ref)) {
283
- const metadata = this.ref['constructor'][Symbol.metadata];
284
- // FIXME: need to iterate over parent metadata instead.
285
- for (const field in metadata) {
286
- const value = this.ref[field];
287
- if (value && value[$changes]) {
288
- callback(value[$changes], metadata[field].index);
336
+ const metadata = this.ref.constructor[Symbol.metadata];
337
+ if (metadata) {
338
+ metadata[-4]?.forEach((index) => {
339
+ const field = metadata[index];
340
+ const value = this.ref[field.name];
341
+ if (value) {
342
+ callback(value[$changes], index);
289
343
  }
290
- }
344
+ });
291
345
  }
292
- else if (typeof (this.ref) === "object") {
346
+ else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
293
347
  // MapSchema / ArraySchema, etc.
294
348
  this.ref.forEach((value, key) => {
295
- if (Metadata.isValidInstance(value)) {
296
- callback(value[$changes], this.ref[$changes].indexes[key] ?? key);
297
- }
349
+ callback(value[$changes], this.indexes[key] ?? key);
298
350
  });
299
351
  }
300
352
  }
@@ -303,8 +355,8 @@
303
355
  this.root?.changes.set(this, this.changes);
304
356
  }
305
357
  change(index, operation = exports.OPERATION.ADD) {
306
- const metadata = this.ref['constructor'][Symbol.metadata];
307
- const isFiltered = this.isFiltered || (metadata && metadata[metadata[index]].tag !== undefined);
358
+ const metadata = this.ref.constructor[Symbol.metadata];
359
+ const isFiltered = this.isFiltered || (metadata?.[index]?.tag !== undefined);
308
360
  const changeSet = (isFiltered)
309
361
  ? this.filteredChanges
310
362
  : this.changes;
@@ -315,14 +367,14 @@
315
367
  : (previousOperation === exports.OPERATION.DELETE)
316
368
  ? exports.OPERATION.DELETE_AND_ADD
317
369
  : operation;
370
+ //
371
+ // TODO: are DELETE operations being encoded as ADD here ??
372
+ //
318
373
  changeSet.set(index, op);
319
374
  }
320
- //
321
- // TODO: are DELETE operations being encoded as ADD here ??
322
- //
323
375
  if (isFiltered) {
324
- this.root?.filteredChanges.set(this, this.filteredChanges);
325
376
  this.allFilteredChanges.set(index, exports.OPERATION.ADD);
377
+ this.root?.filteredChanges.set(this, this.filteredChanges);
326
378
  this.root?.allFilteredChanges.set(this, this.allFilteredChanges);
327
379
  }
328
380
  else {
@@ -369,9 +421,7 @@
369
421
  });
370
422
  }
371
423
  indexedOperation(index, operation, allChangesIndex = index) {
372
- const metadata = this.ref['constructor'][Symbol.metadata];
373
- const isFiltered = this.isFiltered || (metadata && metadata[metadata[index]].tag !== undefined);
374
- if (isFiltered) {
424
+ if (this.filteredChanges !== undefined) {
375
425
  this.allFilteredChanges.set(allChangesIndex, exports.OPERATION.ADD);
376
426
  this.filteredChanges.set(index, operation);
377
427
  this.root?.filteredChanges.set(this, this.filteredChanges);
@@ -384,8 +434,8 @@
384
434
  }
385
435
  getType(index) {
386
436
  if (Metadata.isValidInstance(this.ref)) {
387
- const metadata = this.ref['constructor'][Symbol.metadata];
388
- return metadata[metadata[index]].type;
437
+ const metadata = this.ref.constructor[Symbol.metadata];
438
+ return metadata[index].type;
389
439
  }
390
440
  else {
391
441
  //
@@ -399,7 +449,7 @@
399
449
  }
400
450
  getChange(index) {
401
451
  // TODO: optimize this. avoid checking against multiple instances
402
- return this.changes.get(index) ?? this.filteredChanges.get(index);
452
+ return this.changes.get(index) ?? this.filteredChanges?.get(index);
403
453
  }
404
454
  //
405
455
  // used during `.encode()`
@@ -420,9 +470,7 @@
420
470
  }
421
471
  return;
422
472
  }
423
- const metadata = this.ref['constructor'][Symbol.metadata];
424
- const isFiltered = this.isFiltered || (metadata && metadata[metadata[index]].tag !== undefined);
425
- const changeSet = (isFiltered)
473
+ const changeSet = (this.filteredChanges)
426
474
  ? this.filteredChanges
427
475
  : this.changes;
428
476
  const previousValue = this.getValue(index);
@@ -445,7 +493,7 @@
445
493
  //
446
494
  // FIXME: this is looking a bit ugly (and repeated from `.change()`)
447
495
  //
448
- if (isFiltered) {
496
+ if (this.filteredChanges) {
449
497
  this.root?.filteredChanges.set(this, this.filteredChanges);
450
498
  this.allFilteredChanges.delete(allChangesIndex);
451
499
  }
@@ -456,6 +504,7 @@
456
504
  }
457
505
  endEncode() {
458
506
  this.changes.clear();
507
+ // ArraySchema and MapSchema have a custom "encode end" method
459
508
  this.ref[$onEncodeEnd]?.();
460
509
  // Not a new instance anymore
461
510
  delete this[$isNew];
@@ -468,12 +517,12 @@
468
517
  //
469
518
  this.ref[$onEncodeEnd]?.();
470
519
  this.changes.clear();
471
- this.filteredChanges.clear();
520
+ this.filteredChanges?.clear();
472
521
  // reset operation index
473
522
  this.currentOperationIndex = 0;
474
523
  if (discardAll) {
475
524
  this.allChanges.clear();
476
- this.allFilteredChanges.clear();
525
+ this.allFilteredChanges?.clear();
477
526
  // remove children references
478
527
  this.forEachChild((changeTree, _) => this.root?.remove(changeTree));
479
528
  }
@@ -500,48 +549,41 @@
500
549
  get changed() {
501
550
  return this.changes.size > 0;
502
551
  }
503
- checkIsFiltered(parent, parentIndex) {
552
+ checkIsFiltered(metadata, parent, parentIndex) {
504
553
  // Detect if current structure has "filters" declared
505
- this.isPartiallyFiltered = (this.ref['constructor']?.[Symbol.metadata]?.[-2] !== undefined);
506
- if (parent && !Metadata.isValidInstance(parent)) {
507
- const parentChangeTree = parent[$changes];
508
- parent = parentChangeTree.parent;
509
- parentIndex = parentChangeTree.parentIndex;
510
- }
511
- const parentMetadata = parent?.['constructor']?.[Symbol.metadata];
512
- this.isFiltered = (parent &&
513
- parentMetadata?.[-2]?.includes(parentIndex));
514
- // this.isFiltered = this.ref['constructor']?.[Symbol.metadata]?.[-4];
515
- // // Detect if parent has "filters" declared
516
- // while (parent && !this.isFiltered) {
517
- // const metadata: Metadata = parent['constructor'][Symbol.metadata];
518
- // // this.isFiltered = metadata?.[-4];
519
- // const fieldName = metadata?.[parentIndex];
520
- // const isParentOwned = metadata?.[fieldName]?.tag !== undefined;
521
- // this.isFiltered = isParentOwned || parent[$changes].isFiltered; // metadata?.[-2]
522
- // parent = parent[$changes].parent;
523
- // };
524
- // console.log("ChangeTree.checkIsFiltered", {
525
- // parent: parent?.constructor.name,
526
- // ref: this.ref.constructor.name,
527
- // isFiltered: this.isFiltered,
528
- // isPartiallyFiltered: this.isPartiallyFiltered,
529
- // });
530
- //
531
- // TODO: refactor this!
532
- //
533
- // swapping `changes` and `filteredChanges` is required here
534
- // because "isFiltered" may not be imedialely available on `change()`
535
- //
536
- if (this.isFiltered && this.changes.size > 0) {
537
- // swap changes reference
538
- const changes = this.changes;
539
- this.changes = this.filteredChanges;
540
- this.filteredChanges = changes;
541
- // swap "all changes" reference
542
- const allFilteredChanges = this.allFilteredChanges;
543
- this.allFilteredChanges = this.allChanges;
544
- this.allChanges = allFilteredChanges;
554
+ this.isPartiallyFiltered = metadata?.[-2] !== undefined;
555
+ if (this.isPartiallyFiltered) {
556
+ this.filteredChanges = this.filteredChanges || new Map();
557
+ this.allFilteredChanges = this.allFilteredChanges || new Map();
558
+ }
559
+ if (parent) {
560
+ if (!Metadata.isValidInstance(parent)) {
561
+ const parentChangeTree = parent[$changes];
562
+ parent = parentChangeTree.parent;
563
+ parentIndex = parentChangeTree.parentIndex;
564
+ }
565
+ const parentMetadata = parent?.constructor?.[Symbol.metadata];
566
+ this.isFiltered = (parent && parentMetadata?.[-2]?.includes(parentIndex));
567
+ //
568
+ // TODO: refactor this!
569
+ //
570
+ // swapping `changes` and `filteredChanges` is required here
571
+ // because "isFiltered" may not be imedialely available on `change()`
572
+ //
573
+ if (this.isFiltered) {
574
+ this.filteredChanges = new Map();
575
+ this.allFilteredChanges = new Map();
576
+ if (this.changes.size > 0) {
577
+ // swap changes reference
578
+ const changes = this.changes;
579
+ this.changes = this.filteredChanges;
580
+ this.filteredChanges = changes;
581
+ // swap "all changes" reference
582
+ const allFilteredChanges = this.allFilteredChanges;
583
+ this.allFilteredChanges = this.allChanges;
584
+ this.allChanges = allFilteredChanges;
585
+ }
586
+ }
545
587
  }
546
588
  }
547
589
  }
@@ -825,62 +867,21 @@
825
867
  writeFloat64: writeFloat64
826
868
  });
827
869
 
828
- class EncodeSchemaError extends Error {
829
- }
830
- function assertType(value, type, klass, field) {
831
- let typeofTarget;
832
- let allowNull = false;
833
- switch (type) {
834
- case "number":
835
- case "int8":
836
- case "uint8":
837
- case "int16":
838
- case "uint16":
839
- case "int32":
840
- case "uint32":
841
- case "int64":
842
- case "uint64":
843
- case "float32":
844
- case "float64":
845
- typeofTarget = "number";
846
- if (isNaN(value)) {
847
- console.log(`trying to encode "NaN" in ${klass.constructor.name}#${field}`);
848
- }
849
- break;
850
- case "string":
851
- typeofTarget = "string";
852
- allowNull = true;
853
- break;
854
- case "boolean":
855
- // boolean is always encoded as true/false based on truthiness
856
- return;
857
- }
858
- if (typeof (value) !== typeofTarget && (!allowNull || (allowNull && value !== null))) {
859
- let foundValue = `'${JSON.stringify(value)}'${(value && value.constructor && ` (${value.constructor.name})`) || ''}`;
860
- throw new EncodeSchemaError(`a '${typeofTarget}' was expected, but ${foundValue} was provided in ${klass.constructor.name}#${field}`);
861
- }
862
- }
863
- function assertInstanceType(value, type, klass, field) {
864
- if (!(value instanceof type)) {
865
- throw new EncodeSchemaError(`a '${type.name}' was expected, but '${value && value.constructor.name}' was provided in ${klass.constructor.name}#${field}`);
866
- }
867
- }
868
-
869
- function encodePrimitiveType(type, bytes, value, klass, field, it) {
870
- assertType(value, type, klass, field);
871
- const encodeFunc = encode[type];
872
- if (encodeFunc) {
873
- encodeFunc(bytes, value, it);
874
- // encodeFunc(bytes, value);
875
- }
876
- else {
877
- throw new EncodeSchemaError(`a '${type}' was expected, but ${value} was provided in ${klass.constructor.name}#${field}`);
870
+ function encodeValue(encoder, bytes,
871
+ // ref: Ref,
872
+ type, value,
873
+ // field: string | number,
874
+ operation, it) {
875
+ if (typeof (type) === "string") {
876
+ //
877
+ // Primitive values
878
+ //
879
+ // assertType(value, type as string, ref as Schema, field);
880
+ encode[type]?.(bytes, value, it);
878
881
  }
879
- }
880
- function encodeValue(encoder, bytes, ref, type, value, field, operation, it) {
881
- if (type[Symbol.metadata] !== undefined) {
882
- // TODO: move this to the `@type()` annotation
883
- assertInstanceType(value, type, ref, field);
882
+ else if (type[Symbol.metadata] !== undefined) {
883
+ // // TODO: move this to the `@type()` annotation
884
+ // assertInstanceType(value, type as typeof Schema, ref as Schema, field);
884
885
  //
885
886
  // Encode refId for this instance.
886
887
  // The actual instance is going to be encoded on next `changeTree` iteration.
@@ -891,21 +892,15 @@
891
892
  encoder.tryEncodeTypeId(bytes, type, value.constructor, it);
892
893
  }
893
894
  }
894
- else if (typeof (type) === "string") {
895
- //
896
- // Primitive values
897
- //
898
- encodePrimitiveType(type, bytes, value, ref, field, it);
899
- }
900
895
  else {
901
- //
902
- // Custom type (MapSchema, ArraySchema, etc)
903
- //
904
- const definition = getType(Object.keys(type)[0]);
905
- //
906
- // ensure a ArraySchema has been provided
907
- //
908
- assertInstanceType(ref[field], definition.constructor, ref, field);
896
+ // //
897
+ // // Custom type (MapSchema, ArraySchema, etc)
898
+ // //
899
+ // const definition = getType(Object.keys(type)[0]);
900
+ // //
901
+ // // ensure a ArraySchema has been provided
902
+ // //
903
+ // assertInstanceType(ref[field], definition.constructor, ref as Schema, field);
909
904
  //
910
905
  // Encode refId for this instance.
911
906
  // The actual instance is going to be encoded on next `changeTree` iteration.
@@ -918,26 +913,27 @@
918
913
  * @private
919
914
  */
920
915
  const encodeSchemaOperation = function (encoder, bytes, changeTree, index, operation, it) {
921
- const ref = changeTree.ref;
922
- const metadata = ref['constructor'][Symbol.metadata];
923
- const field = metadata[index];
924
- const type = metadata[field].type;
925
- const value = ref[field];
926
916
  // "compress" field index + operation
927
917
  bytes[it.offset++] = (index | operation) & 255;
928
918
  // Do not encode value for DELETE operations
929
919
  if (operation === exports.OPERATION.DELETE) {
930
920
  return;
931
921
  }
922
+ const ref = changeTree.ref;
923
+ const metadata = ref['constructor'][Symbol.metadata];
924
+ const field = metadata[index];
932
925
  // TODO: inline this function call small performance gain
933
- encodeValue(encoder, bytes, ref, type, value, field, operation, it);
926
+ encodeValue(encoder, bytes,
927
+ // ref,
928
+ metadata[index].type, ref[field.name],
929
+ // index,
930
+ operation, it);
934
931
  };
935
932
  /**
936
933
  * Used for collections (MapSchema, CollectionSchema, SetSchema)
937
934
  * @private
938
935
  */
939
- const encodeKeyValueOperation = function (encoder, bytes, changeTree, field, operation, it) {
940
- const ref = changeTree.ref;
936
+ const encodeKeyValueOperation = function (encoder, bytes, changeTree, index, operation, it) {
941
937
  // encode operation
942
938
  bytes[it.offset++] = operation & 255;
943
939
  // custom operations
@@ -945,11 +941,12 @@
945
941
  return;
946
942
  }
947
943
  // encode index
948
- number$1(bytes, field, it);
944
+ number$1(bytes, index, it);
949
945
  // Do not encode value for DELETE operations
950
946
  if (operation === exports.OPERATION.DELETE) {
951
947
  return;
952
948
  }
949
+ const ref = changeTree.ref;
953
950
  //
954
951
  // encode "alias" for dynamic fields (maps)
955
952
  //
@@ -958,12 +955,12 @@
958
955
  //
959
956
  // MapSchema dynamic key
960
957
  //
961
- const dynamicIndex = changeTree.ref['$indexes'].get(field);
958
+ const dynamicIndex = changeTree.ref['$indexes'].get(index);
962
959
  string$1(bytes, dynamicIndex, it);
963
960
  }
964
961
  }
965
- const type = changeTree.getType(field);
966
- const value = changeTree.getValue(field);
962
+ const type = changeTree.getType(index);
963
+ const value = changeTree.getValue(index);
967
964
  // try { throw new Error(); } catch (e) {
968
965
  // // only print if not coming from Reflection.ts
969
966
  // if (!e.stack.includes("src/Reflection.ts")) {
@@ -977,7 +974,11 @@
977
974
  // }
978
975
  // }
979
976
  // TODO: inline this function call small performance gain
980
- encodeValue(encoder, bytes, ref, type, value, field, operation, it);
977
+ encodeValue(encoder, bytes,
978
+ // ref,
979
+ type, value,
980
+ // index,
981
+ operation, it);
981
982
  };
982
983
  /**
983
984
  * Used for collections (MapSchema, ArraySchema, etc.)
@@ -1021,7 +1022,11 @@
1021
1022
  // items: ref.toJSON(),
1022
1023
  // });
1023
1024
  // TODO: inline this function call small performance gain
1024
- encodeValue(encoder, bytes, ref, type, value, field, operation, it);
1025
+ encodeValue(encoder, bytes,
1026
+ // ref,
1027
+ type, value,
1028
+ // field,
1029
+ operation, it);
1025
1030
  };
1026
1031
 
1027
1032
  /**
@@ -1378,7 +1383,7 @@
1378
1383
  }
1379
1384
  const decodeSchemaOperation = function (decoder, bytes, it, ref, allChanges) {
1380
1385
  const first_byte = bytes[it.offset++];
1381
- const metadata = ref['constructor'][Symbol.metadata];
1386
+ const metadata = ref.constructor[Symbol.metadata];
1382
1387
  // "compressed" index + operation
1383
1388
  const operation = (first_byte >> 6) << 6;
1384
1389
  const index = first_byte % (operation || 255);
@@ -1388,9 +1393,9 @@
1388
1393
  console.warn("@colyseus/schema: field not defined at", { index, ref: ref.constructor.name, metadata });
1389
1394
  return DEFINITION_MISMATCH;
1390
1395
  }
1391
- const { value, previousValue } = decodeValue(decoder, operation, ref, index, metadata[field].type, bytes, it, allChanges);
1396
+ const { value, previousValue } = decodeValue(decoder, operation, ref, index, field.type, bytes, it, allChanges);
1392
1397
  if (value !== null && value !== undefined) {
1393
- ref[field] = value;
1398
+ ref[field.name] = value;
1394
1399
  }
1395
1400
  // add change
1396
1401
  if (previousValue !== value) {
@@ -1398,7 +1403,7 @@
1398
1403
  ref,
1399
1404
  refId: decoder.currentRefId,
1400
1405
  op: operation,
1401
- field: field,
1406
+ field: field.name,
1402
1407
  value,
1403
1408
  previousValue,
1404
1409
  });
@@ -1496,7 +1501,6 @@
1496
1501
  return;
1497
1502
  }
1498
1503
  else if (operation === exports.OPERATION.ADD_BY_REFID) {
1499
- // operation = OPERATION.ADD;
1500
1504
  const refId = number(bytes, it);
1501
1505
  const itemByRefId = decoder.root.refs.get(refId);
1502
1506
  // use existing index, or push new value
@@ -1530,6 +1534,47 @@
1530
1534
  }
1531
1535
  };
1532
1536
 
1537
+ class EncodeSchemaError extends Error {
1538
+ }
1539
+ function assertType(value, type, klass, field) {
1540
+ let typeofTarget;
1541
+ let allowNull = false;
1542
+ switch (type) {
1543
+ case "number":
1544
+ case "int8":
1545
+ case "uint8":
1546
+ case "int16":
1547
+ case "uint16":
1548
+ case "int32":
1549
+ case "uint32":
1550
+ case "int64":
1551
+ case "uint64":
1552
+ case "float32":
1553
+ case "float64":
1554
+ typeofTarget = "number";
1555
+ if (isNaN(value)) {
1556
+ console.log(`trying to encode "NaN" in ${klass.constructor.name}#${field}`);
1557
+ }
1558
+ break;
1559
+ case "string":
1560
+ typeofTarget = "string";
1561
+ allowNull = true;
1562
+ break;
1563
+ case "boolean":
1564
+ // boolean is always encoded as true/false based on truthiness
1565
+ return;
1566
+ }
1567
+ if (typeof (value) !== typeofTarget && (!allowNull || (allowNull && value !== null))) {
1568
+ let foundValue = `'${JSON.stringify(value)}'${(value && value.constructor && ` (${value.constructor.name})`) || ''}`;
1569
+ throw new EncodeSchemaError(`a '${typeofTarget}' was expected, but ${foundValue} was provided in ${klass.constructor.name}#${field}`);
1570
+ }
1571
+ }
1572
+ function assertInstanceType(value, type, instance, field) {
1573
+ if (!(value instanceof type)) {
1574
+ throw new EncodeSchemaError(`a '${type.name}' was expected, but '${value && value.constructor.name}' was provided in ${instance.constructor.name}#${field}`);
1575
+ }
1576
+ }
1577
+
1533
1578
  var _a$4, _b$4;
1534
1579
  const DEFAULT_SORT = (a, b) => {
1535
1580
  const A = a.toString();
@@ -1595,6 +1640,7 @@
1595
1640
  }
1596
1641
  else {
1597
1642
  if (setValue[$changes]) {
1643
+ assertInstanceType(setValue, obj[$childType], obj, key);
1598
1644
  if (obj.items[key] !== undefined) {
1599
1645
  if (setValue[$changes][$isNew]) {
1600
1646
  this[$changes].indexedOperation(Number(key), exports.OPERATION.MOVE_AND_ADD);
@@ -1641,6 +1687,7 @@
1641
1687
  }
1642
1688
  });
1643
1689
  this[$changes] = new ChangeTree(proxy);
1690
+ this[$changes].indexes = {};
1644
1691
  this.push.apply(this, items);
1645
1692
  return proxy;
1646
1693
  }
@@ -1665,6 +1712,9 @@
1665
1712
  if (value === undefined || value === null) {
1666
1713
  return;
1667
1714
  }
1715
+ else if (typeof (value) === "object" && this[$childType]) {
1716
+ assertInstanceType(value, this[$childType], this, i);
1717
+ }
1668
1718
  const changeTree = this[$changes];
1669
1719
  changeTree.indexedOperation(length, exports.OPERATION.ADD, this.items.length);
1670
1720
  this.items.push(value);
@@ -2192,6 +2242,7 @@
2192
2242
  this.$items = new Map();
2193
2243
  this.$indexes = new Map();
2194
2244
  this[$changes] = new ChangeTree(this);
2245
+ this[$changes].indexes = {};
2195
2246
  if (initialValues) {
2196
2247
  if (initialValues instanceof Map ||
2197
2248
  initialValues instanceof MapSchema) {
@@ -2218,6 +2269,9 @@
2218
2269
  if (value === undefined || value === null) {
2219
2270
  throw new Error(`MapSchema#set('${key}', ${value}): trying to set ${value} value on '${key}'.`);
2220
2271
  }
2272
+ else if (typeof (value) === "object" && this[$childType]) {
2273
+ assertInstanceType(value, this[$childType], this, key);
2274
+ }
2221
2275
  // Force "key" as string
2222
2276
  // See: https://github.com/colyseus/colyseus/issues/561#issuecomment-1646733468
2223
2277
  key = key.toString();
@@ -2423,27 +2477,20 @@
2423
2477
  if (parentFieldViewTag !== undefined) {
2424
2478
  this.parentFiltered[`${this.schemas.get(klass)}-${parentIndex}`] = true;
2425
2479
  }
2426
- for (const field in metadata) {
2427
- // //
2428
- // // Modify the field's metadata to include the parent field's view tag
2429
- // //
2430
- // if (
2431
- // parentFieldViewTag !== undefined &&
2432
- // metadata[field].tag === undefined
2433
- // ) {
2434
- // metadata[field].tag = parentFieldViewTag;
2435
- // }
2436
- const fieldType = metadata[field].type;
2437
- const viewTag = metadata[field].tag;
2480
+ for (const fieldIndex in metadata) {
2481
+ const index = fieldIndex;
2482
+ const fieldType = metadata[index].type;
2483
+ const viewTag = metadata[index].tag;
2438
2484
  if (typeof (fieldType) === "string") {
2439
2485
  continue;
2440
2486
  }
2441
2487
  if (Array.isArray(fieldType)) {
2442
2488
  const type = fieldType[0];
2489
+ // skip primitive types
2443
2490
  if (type === "string") {
2444
2491
  continue;
2445
2492
  }
2446
- this.discoverTypes(type, metadata[field].index, viewTag);
2493
+ this.discoverTypes(type, index, viewTag);
2447
2494
  }
2448
2495
  else if (typeof (fieldType) === "function") {
2449
2496
  this.discoverTypes(fieldType, viewTag);
@@ -2454,7 +2501,7 @@
2454
2501
  if (typeof (type) === "string") {
2455
2502
  continue;
2456
2503
  }
2457
- this.discoverTypes(type, metadata[field].index, viewTag);
2504
+ this.discoverTypes(type, index, viewTag);
2458
2505
  }
2459
2506
  }
2460
2507
  }
@@ -2610,17 +2657,18 @@
2610
2657
  const parentMetadata = parentClass[Symbol.metadata];
2611
2658
  // TODO: use Metadata.initialize()
2612
2659
  const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
2613
- if (!metadata[fieldName]) {
2614
- //
2615
- // detect index for this field, considering inheritance
2616
- //
2617
- metadata[fieldName] = {
2618
- type: undefined,
2619
- index: (metadata[-1] // current structure already has fields defined
2620
- ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
2621
- ?? -1) + 1 // no fields defined
2622
- };
2623
- }
2660
+ // const fieldIndex = metadata[fieldName];
2661
+ // if (!metadata[fieldIndex]) {
2662
+ // //
2663
+ // // detect index for this field, considering inheritance
2664
+ // //
2665
+ // metadata[fieldIndex] = {
2666
+ // type: undefined,
2667
+ // index: (metadata[-1] // current structure already has fields defined
2668
+ // ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
2669
+ // ?? -1) + 1 // no fields defined
2670
+ // }
2671
+ // }
2624
2672
  Metadata.setTag(metadata, fieldName, tag);
2625
2673
  };
2626
2674
  }
@@ -2635,16 +2683,16 @@
2635
2683
  const parentClass = Object.getPrototypeOf(constructor);
2636
2684
  const parentMetadata = parentClass && parentClass[Symbol.metadata];
2637
2685
  const metadata = Metadata.initialize(constructor, parentMetadata);
2638
- let fieldIndex;
2686
+ let fieldIndex = metadata[field];
2639
2687
  /**
2640
2688
  * skip if descriptor already exists for this field (`@deprecated()`)
2641
2689
  */
2642
- if (metadata[field]) {
2643
- if (metadata[field].deprecated) {
2690
+ if (metadata[fieldIndex]) {
2691
+ if (metadata[fieldIndex].deprecated) {
2644
2692
  // do not create accessors for deprecated properties.
2645
2693
  return;
2646
2694
  }
2647
- else if (metadata[field].descriptor !== undefined) {
2695
+ else if (metadata[fieldIndex].type !== undefined) {
2648
2696
  // trying to define same property multiple times across inheritance.
2649
2697
  // https://github.com/colyseus/colyseus-unity3d/issues/131#issuecomment-814308572
2650
2698
  try {
@@ -2655,9 +2703,6 @@
2655
2703
  throw new Error(`${e.message} ${definitionAtLine}`);
2656
2704
  }
2657
2705
  }
2658
- else {
2659
- fieldIndex = metadata[field].index;
2660
- }
2661
2706
  }
2662
2707
  else {
2663
2708
  //
@@ -2683,11 +2728,11 @@
2683
2728
  const childType = (complexTypeKlass)
2684
2729
  ? Object.values(type)[0]
2685
2730
  : type;
2686
- Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass, metadata, field));
2731
+ Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass));
2687
2732
  }
2688
2733
  };
2689
2734
  }
2690
- function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass, metadata, field) {
2735
+ function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass) {
2691
2736
  return {
2692
2737
  get: function () { return this[fieldCached]; },
2693
2738
  set: function (value) {
@@ -2709,22 +2754,27 @@
2709
2754
  }
2710
2755
  value[$childType] = type;
2711
2756
  }
2757
+ else if (typeof (type) !== "string") {
2758
+ assertInstanceType(value, type, this, fieldCached.substring(1));
2759
+ }
2760
+ else {
2761
+ assertType(value, type, this, fieldCached.substring(1));
2762
+ }
2763
+ const changeTree = this[$changes];
2712
2764
  //
2713
2765
  // Replacing existing "ref", remove it from root.
2714
2766
  // TODO: if there are other references to this instance, we should not remove it from root.
2715
2767
  //
2716
2768
  if (previousValue !== undefined && previousValue[$changes]) {
2717
- this[$changes].root?.remove(previousValue[$changes]);
2769
+ changeTree.root?.remove(previousValue[$changes]);
2718
2770
  }
2719
2771
  // flag the change for encoding.
2720
- this.constructor[$track](this[$changes], fieldIndex, exports.OPERATION.ADD);
2772
+ this.constructor[$track](changeTree, fieldIndex, exports.OPERATION.ADD);
2721
2773
  //
2722
2774
  // call setParent() recursively for this and its child
2723
2775
  // structures.
2724
2776
  //
2725
- if (value[$changes]) {
2726
- value[$changes].setParent(this, this[$changes].root, metadata[field].index);
2727
- }
2777
+ value[$changes]?.setParent(this, changeTree.root, fieldIndex);
2728
2778
  }
2729
2779
  else if (previousValue !== undefined) {
2730
2780
  //
@@ -2751,20 +2801,22 @@
2751
2801
  const parentClass = Object.getPrototypeOf(constructor);
2752
2802
  const parentMetadata = parentClass[Symbol.metadata];
2753
2803
  const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
2754
- if (!metadata[field]) {
2755
- //
2756
- // detect index for this field, considering inheritance
2757
- //
2758
- metadata[field] = {
2759
- type: undefined,
2760
- index: (metadata[-1] // current structure already has fields defined
2761
- ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
2762
- ?? -1) + 1 // no fields defined
2763
- };
2764
- }
2765
- metadata[field].deprecated = true;
2804
+ const fieldIndex = metadata[field];
2805
+ // if (!metadata[field]) {
2806
+ // //
2807
+ // // detect index for this field, considering inheritance
2808
+ // //
2809
+ // metadata[field] = {
2810
+ // type: undefined,
2811
+ // index: (metadata[-1] // current structure already has fields defined
2812
+ // ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
2813
+ // ?? -1) + 1 // no fields defined
2814
+ // }
2815
+ // }
2816
+ metadata[fieldIndex].deprecated = true;
2766
2817
  if (throws) {
2767
- metadata[field].descriptor = {
2818
+ metadata[$descriptors] ??= {};
2819
+ metadata[$descriptors][field] = {
2768
2820
  get: function () { throw new Error(`${field} is deprecated.`); },
2769
2821
  set: function (value) { },
2770
2822
  enumerable: false,
@@ -2772,8 +2824,8 @@
2772
2824
  };
2773
2825
  }
2774
2826
  // flag metadata[field] as non-enumerable
2775
- Object.defineProperty(metadata, field, {
2776
- value: metadata[field],
2827
+ Object.defineProperty(metadata, fieldIndex, {
2828
+ value: metadata[fieldIndex],
2777
2829
  enumerable: false,
2778
2830
  configurable: true
2779
2831
  });
@@ -2839,35 +2891,7 @@
2839
2891
  enumerable: false,
2840
2892
  writable: true
2841
2893
  });
2842
- const metadata = instance.constructor[Symbol.metadata];
2843
- // Define property descriptors
2844
- for (const field in metadata) {
2845
- if (metadata[field].descriptor) {
2846
- // for encoder
2847
- Object.defineProperty(instance, `_${field}`, {
2848
- value: undefined,
2849
- writable: true,
2850
- enumerable: false,
2851
- configurable: true,
2852
- });
2853
- Object.defineProperty(instance, field, metadata[field].descriptor);
2854
- }
2855
- else {
2856
- // for decoder
2857
- Object.defineProperty(instance, field, {
2858
- value: undefined,
2859
- writable: true,
2860
- enumerable: true,
2861
- configurable: true,
2862
- });
2863
- }
2864
- // Object.defineProperty(instance, field, {
2865
- // ...instance.constructor[Symbol.metadata][field].descriptor
2866
- // });
2867
- // if (args[0]?.hasOwnProperty(field)) {
2868
- // instance[field] = args[0][field];
2869
- // }
2870
- }
2894
+ Object.defineProperties(instance, instance.constructor[Symbol.metadata]?.[$descriptors] || {});
2871
2895
  }
2872
2896
  static is(type) {
2873
2897
  return typeof (type[Symbol.metadata]) === "object";
@@ -2891,7 +2915,7 @@
2891
2915
  */
2892
2916
  static [$filter](ref, index, view) {
2893
2917
  const metadata = ref.constructor[Symbol.metadata];
2894
- const tag = metadata[metadata[index]].tag;
2918
+ const tag = metadata[index]?.tag;
2895
2919
  if (view === undefined) {
2896
2920
  // shared pass/encode: encode if doesn't have a tag
2897
2921
  return tag === undefined;
@@ -2912,12 +2936,21 @@
2912
2936
  }
2913
2937
  // allow inherited classes to have a constructor
2914
2938
  constructor(...args) {
2915
- Schema.initialize(this);
2939
+ //
2940
+ // inline
2941
+ // Schema.initialize(this);
2942
+ //
2943
+ Object.defineProperty(this, $changes, {
2944
+ value: new ChangeTree(this),
2945
+ enumerable: false,
2946
+ writable: true
2947
+ });
2948
+ Object.defineProperties(this, this.constructor[Symbol.metadata]?.[$descriptors] || {});
2916
2949
  //
2917
2950
  // Assign initial values
2918
2951
  //
2919
2952
  if (args[0]) {
2920
- this.assign(args[0]);
2953
+ Object.assign(this, args[0]);
2921
2954
  }
2922
2955
  }
2923
2956
  assign(props) {
@@ -2931,7 +2964,8 @@
2931
2964
  * @param operation OPERATION to perform (detected automatically)
2932
2965
  */
2933
2966
  setDirty(property, operation) {
2934
- this[$changes].change(this.constructor[Symbol.metadata][property].index, operation);
2967
+ const metadata = this.constructor[Symbol.metadata];
2968
+ this[$changes].change(metadata[metadata[property]].index, operation);
2935
2969
  }
2936
2970
  clone() {
2937
2971
  const cloned = new (this.constructor);
@@ -2940,7 +2974,9 @@
2940
2974
  // TODO: clone all properties, not only annotated ones
2941
2975
  //
2942
2976
  // for (const field in this) {
2943
- for (const field in metadata) {
2977
+ for (const fieldIndex in metadata) {
2978
+ // const field = metadata[metadata[fieldIndex]].name;
2979
+ const field = metadata[fieldIndex].name;
2944
2980
  if (typeof (this[field]) === "object" &&
2945
2981
  typeof (this[field]?.clone) === "function") {
2946
2982
  // deep clone
@@ -2954,10 +2990,11 @@
2954
2990
  return cloned;
2955
2991
  }
2956
2992
  toJSON() {
2957
- const metadata = this.constructor[Symbol.metadata];
2958
2993
  const obj = {};
2959
- for (const fieldName in metadata) {
2960
- const field = metadata[fieldName];
2994
+ const metadata = this.constructor[Symbol.metadata];
2995
+ for (const index in metadata) {
2996
+ const field = metadata[index];
2997
+ const fieldName = field.name;
2961
2998
  if (!field.deprecated && this[fieldName] !== null && typeof (this[fieldName]) !== "undefined") {
2962
2999
  obj[fieldName] = (typeof (this[fieldName]['toJSON']) === "function")
2963
3000
  ? this[fieldName]['toJSON']()
@@ -2970,10 +3007,12 @@
2970
3007
  this[$changes].discardAll();
2971
3008
  }
2972
3009
  [$getByIndex](index) {
2973
- return this[this.constructor[Symbol.metadata][index]];
3010
+ const metadata = this.constructor[Symbol.metadata];
3011
+ return this[metadata[index].name];
2974
3012
  }
2975
3013
  [$deleteByIndex](index) {
2976
- this[this.constructor[Symbol.metadata][index]] = undefined;
3014
+ const metadata = this.constructor[Symbol.metadata];
3015
+ this[metadata[index].name] = undefined;
2977
3016
  }
2978
3017
  static debugRefIds(instance, jsonContents = true, level = 0) {
2979
3018
  const ref = instance;
@@ -3097,6 +3136,7 @@
3097
3136
  this.$indexes = new Map();
3098
3137
  this.$refId = 0;
3099
3138
  this[$changes] = new ChangeTree(this);
3139
+ this[$changes].indexes = {};
3100
3140
  if (initialValues) {
3101
3141
  initialValues.forEach((v) => this.add(v));
3102
3142
  }
@@ -3252,6 +3292,7 @@
3252
3292
  this.$indexes = new Map();
3253
3293
  this.$refId = 0;
3254
3294
  this[$changes] = new ChangeTree(this);
3295
+ this[$changes].indexes = {};
3255
3296
  if (initialValues) {
3256
3297
  initialValues.forEach((v) => this.add(v));
3257
3298
  }
@@ -3486,8 +3527,8 @@
3486
3527
  ) {
3487
3528
  const hasView = (view !== undefined);
3488
3529
  const rootChangeTree = this.state[$changes];
3489
- const changeTreesIterator = changeTrees.entries();
3490
- for (const [changeTree, changes] of changeTreesIterator) {
3530
+ const shouldClearChanges = !isEncodeAll && !hasView;
3531
+ for (const [changeTree, changes] of changeTrees.entries()) {
3491
3532
  const ref = changeTree.ref;
3492
3533
  const ctor = ref['constructor'];
3493
3534
  const encoder = ctor[$encoder];
@@ -3539,6 +3580,9 @@
3539
3580
  // }
3540
3581
  encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView);
3541
3582
  }
3583
+ // if (shouldClearChanges) {
3584
+ // changeTree.endEncode();
3585
+ // }
3542
3586
  }
3543
3587
  if (it.offset > buffer.byteLength) {
3544
3588
  const newSize = getNextPowerOf2(buffer.byteLength * 2);
@@ -3561,7 +3605,7 @@
3561
3605
  //
3562
3606
  // only clear changes after making sure buffer resize is not required.
3563
3607
  //
3564
- if (!isEncodeAll && !hasView) {
3608
+ if (shouldClearChanges) {
3565
3609
  //
3566
3610
  // FIXME: avoid iterating over change trees twice.
3567
3611
  //
@@ -3645,6 +3689,11 @@
3645
3689
  const changeTreesIterator = changeTrees.entries();
3646
3690
  for (const [changeTree, _] of changeTreesIterator) {
3647
3691
  changeTree.endEncode();
3692
+ // changeTree.changes.clear();
3693
+ // // ArraySchema and MapSchema have a custom "encode end" method
3694
+ // changeTree.ref[$onEncodeEnd]?.();
3695
+ // // Not a new instance anymore
3696
+ // delete changeTree[$isNew];
3648
3697
  }
3649
3698
  }
3650
3699
  discardChanges() {
@@ -3760,8 +3809,9 @@
3760
3809
  // Ensure child schema instances have their references removed as well.
3761
3810
  //
3762
3811
  if (Metadata.isValidInstance(ref)) {
3763
- const metadata = ref['constructor'][Symbol.metadata];
3764
- for (const field in metadata) {
3812
+ const metadata = ref.constructor[Symbol.metadata];
3813
+ for (const index in metadata) {
3814
+ const field = metadata[index].name;
3765
3815
  const childRefId = typeof (ref[field]) === "object" && this.refIds.get(ref[field]);
3766
3816
  if (childRefId) {
3767
3817
  this.removeRef(childRefId);
@@ -3948,7 +3998,9 @@
3948
3998
  const reflection = new Reflection();
3949
3999
  const encoder = new Encoder(reflection);
3950
4000
  const buildType = (currentType, metadata) => {
3951
- for (const fieldName in metadata) {
4001
+ for (const fieldIndex in metadata) {
4002
+ const index = Number(fieldIndex);
4003
+ const fieldName = metadata[index].name;
3952
4004
  // skip fields from parent classes
3953
4005
  if (!Object.prototype.hasOwnProperty.call(metadata, fieldName)) {
3954
4006
  continue;
@@ -3956,7 +4008,7 @@
3956
4008
  const field = new ReflectionField();
3957
4009
  field.name = fieldName;
3958
4010
  let fieldType;
3959
- const type = metadata[fieldName].type;
4011
+ const type = metadata[index].type;
3960
4012
  if (typeof (type) === "string") {
3961
4013
  fieldType = type;
3962
4014
  }
@@ -4022,18 +4074,7 @@
4022
4074
  reflection.types.forEach((reflectionType) => {
4023
4075
  const schemaType = typeContext.get(reflectionType.id);
4024
4076
  const metadata = schemaType[Symbol.metadata];
4025
- // FIXME: use metadata[-1] to get field count
4026
4077
  const parentFieldIndex = 0;
4027
- // console.log("--------------------");
4028
- // // console.log("reflectionType", reflectionType.toJSON());
4029
- // console.log("reflectionType.fields", reflectionType.fields.toJSON());
4030
- // console.log("parentFieldIndex", parentFieldIndex);
4031
- //
4032
- // FIXME: set fields using parentKlass as well
4033
- // currently the fields are duplicated on inherited classes
4034
- //
4035
- // // const parentKlass = reflection.types[reflectionType.extendsId];
4036
- // // parentKlass.fields
4037
4078
  reflectionType.fields.forEach((field, i) => {
4038
4079
  const fieldIndex = parentFieldIndex + i;
4039
4080
  if (field.referencedType !== undefined) {
@@ -4198,7 +4239,7 @@
4198
4239
  //
4199
4240
  bindTo: function bindTo(targetObject, properties) {
4200
4241
  if (!properties) {
4201
- properties = Object.keys(metadata);
4242
+ properties = Object.keys(metadata).map((index) => metadata[index].name);
4202
4243
  }
4203
4244
  return $root.addCallback($root.refIds.get(context.instance), exports.OPERATION.REPLACE, () => {
4204
4245
  properties.forEach((prop) => targetObject[prop] = context.instance[prop]);
@@ -4206,7 +4247,8 @@
4206
4247
  }
4207
4248
  }, {
4208
4249
  get(target, prop) {
4209
- if (metadata[prop]) {
4250
+ const metadataField = metadata[metadata[prop]];
4251
+ if (metadataField) {
4210
4252
  const instance = context.instance?.[prop];
4211
4253
  const onInstanceAvailable = ((callback) => {
4212
4254
  const unbind = $(context.instance).listen(prop, (value, _) => {
@@ -4222,7 +4264,7 @@
4222
4264
  callback(instance, true);
4223
4265
  }
4224
4266
  });
4225
- return getProxy(metadata[prop].type, {
4267
+ return getProxy(metadataField.type, {
4226
4268
  // make sure refId is available, otherwise need to wait for the instance to be available.
4227
4269
  instance: ($root.refIds.get(instance) && instance),
4228
4270
  parentInstance: context.instance,
@@ -4335,7 +4377,7 @@
4335
4377
  console.warn("StateView#add(), invalid object:", obj);
4336
4378
  return this;
4337
4379
  }
4338
- // FIXME: ArraySchema/MapSchema does not have metadata
4380
+ // FIXME: ArraySchema/MapSchema do not have metadata
4339
4381
  const metadata = obj.constructor[Symbol.metadata];
4340
4382
  const changeTree = obj[$changes];
4341
4383
  this.items.add(changeTree);
@@ -4381,7 +4423,7 @@
4381
4423
  ? changeTree.allFilteredChanges
4382
4424
  : changeTree.allChanges;
4383
4425
  changeSet.forEach((op, index) => {
4384
- const tagAtIndex = metadata?.[metadata?.[index]].tag;
4426
+ const tagAtIndex = metadata?.[index].tag;
4385
4427
  if ((isInvisible || // if "invisible", include all
4386
4428
  tagAtIndex === undefined || // "all change" with no tag
4387
4429
  tagAtIndex === tag // tagged property
@@ -4394,7 +4436,7 @@
4394
4436
  // Add children of this ChangeTree to this view
4395
4437
  changeTree.forEachChild((change, index) => {
4396
4438
  // Do not ADD children that don't have the same tag
4397
- if (metadata && metadata[metadata[index]].tag !== tag) {
4439
+ if (metadata && metadata[index].tag !== tag) {
4398
4440
  return;
4399
4441
  }
4400
4442
  this.add(change.ref, tag, false);