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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/build/cjs/index.js +389 -354
  2. package/build/cjs/index.js.map +1 -1
  3. package/build/esm/index.mjs +389 -354
  4. package/build/esm/index.mjs.map +1 -1
  5. package/build/umd/index.js +389 -354
  6. package/lib/Metadata.d.ts +14 -5
  7. package/lib/Metadata.js +49 -20
  8. package/lib/Metadata.js.map +1 -1
  9. package/lib/Reflection.js +4 -13
  10. package/lib/Reflection.js.map +1 -1
  11. package/lib/Schema.js +26 -39
  12. package/lib/Schema.js.map +1 -1
  13. package/lib/annotations.d.ts +1 -2
  14. package/lib/annotations.js +58 -52
  15. package/lib/annotations.js.map +1 -1
  16. package/lib/bench_encode.js +25 -22
  17. package/lib/bench_encode.js.map +1 -1
  18. package/lib/decoder/DecodeOperation.js +7 -9
  19. package/lib/decoder/DecodeOperation.js.map +1 -1
  20. package/lib/decoder/ReferenceTracker.js +3 -2
  21. package/lib/decoder/ReferenceTracker.js.map +1 -1
  22. package/lib/decoder/strategy/StateCallbacks.js +4 -3
  23. package/lib/decoder/strategy/StateCallbacks.js.map +1 -1
  24. package/lib/encoder/ChangeTree.d.ts +8 -7
  25. package/lib/encoder/ChangeTree.js +135 -117
  26. package/lib/encoder/ChangeTree.js.map +1 -1
  27. package/lib/encoder/EncodeOperation.d.ts +1 -4
  28. package/lib/encoder/EncodeOperation.js +17 -47
  29. package/lib/encoder/EncodeOperation.js.map +1 -1
  30. package/lib/encoder/Encoder.js +18 -6
  31. package/lib/encoder/Encoder.js.map +1 -1
  32. package/lib/encoder/Root.d.ts +2 -2
  33. package/lib/encoder/Root.js +18 -6
  34. package/lib/encoder/Root.js.map +1 -1
  35. package/lib/encoder/StateView.js +3 -3
  36. package/lib/encoder/StateView.js.map +1 -1
  37. package/lib/encoding/assert.d.ts +2 -1
  38. package/lib/encoding/assert.js +2 -2
  39. package/lib/encoding/assert.js.map +1 -1
  40. package/lib/index.d.ts +1 -2
  41. package/lib/index.js +11 -10
  42. package/lib/index.js.map +1 -1
  43. package/lib/types/TypeContext.js +7 -14
  44. package/lib/types/TypeContext.js.map +1 -1
  45. package/lib/types/custom/ArraySchema.js +6 -0
  46. package/lib/types/custom/ArraySchema.js.map +1 -1
  47. package/lib/types/custom/CollectionSchema.js +1 -0
  48. package/lib/types/custom/CollectionSchema.js.map +1 -1
  49. package/lib/types/custom/MapSchema.js +5 -0
  50. package/lib/types/custom/MapSchema.js.map +1 -1
  51. package/lib/types/custom/SetSchema.js +1 -0
  52. package/lib/types/custom/SetSchema.js.map +1 -1
  53. package/lib/types/symbols.d.ts +1 -0
  54. package/lib/types/symbols.js +2 -1
  55. package/lib/types/symbols.js.map +1 -1
  56. package/package.json +1 -1
  57. package/src/Metadata.ts +60 -29
  58. package/src/Reflection.ts +5 -15
  59. package/src/Schema.ts +33 -45
  60. package/src/annotations.ts +75 -67
  61. package/src/bench_encode.ts +29 -27
  62. package/src/decoder/DecodeOperation.ts +12 -11
  63. package/src/decoder/ReferenceTracker.ts +3 -2
  64. package/src/decoder/strategy/StateCallbacks.ts +4 -3
  65. package/src/encoder/ChangeTree.ts +154 -138
  66. package/src/encoder/EncodeOperation.ts +42 -62
  67. package/src/encoder/Encoder.ts +25 -8
  68. package/src/encoder/Root.ts +23 -6
  69. package/src/encoder/StateView.ts +4 -4
  70. package/src/encoding/assert.ts +4 -3
  71. package/src/index.ts +1 -4
  72. package/src/types/TypeContext.ts +10 -15
  73. package/src/types/custom/ArraySchema.ts +8 -0
  74. package/src/types/custom/CollectionSchema.ts +1 -0
  75. package/src/types/custom/MapSchema.ts +6 -0
  76. package/src/types/custom/SetSchema.ts +1 -0
  77. package/src/types/symbols.ts +2 -0
@@ -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,16 +470,13 @@
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);
429
477
  changeSet.set(index, operation ?? exports.OPERATION.DELETE);
430
478
  // remove `root` reference
431
479
  if (previousValue && previousValue[$changes]) {
432
- previousValue[$changes].root = undefined;
433
480
  //
434
481
  // FIXME: this.root is "undefined"
435
482
  //
@@ -440,12 +487,18 @@
440
487
  //
441
488
  // (the property descriptors should NOT be used at decoding time. only at encoding time.)
442
489
  //
443
- this.root?.remove(previousValue[$changes]);
490
+ const refCount = this.root?.remove(previousValue[$changes]);
491
+ //
492
+ // Only remove "root" reference if it's the last reference
493
+ //
494
+ if (refCount <= 0) {
495
+ previousValue[$changes].root = undefined;
496
+ }
444
497
  }
445
498
  //
446
499
  // FIXME: this is looking a bit ugly (and repeated from `.change()`)
447
500
  //
448
- if (isFiltered) {
501
+ if (this.filteredChanges) {
449
502
  this.root?.filteredChanges.set(this, this.filteredChanges);
450
503
  this.allFilteredChanges.delete(allChangesIndex);
451
504
  }
@@ -456,6 +509,7 @@
456
509
  }
457
510
  endEncode() {
458
511
  this.changes.clear();
512
+ // ArraySchema and MapSchema have a custom "encode end" method
459
513
  this.ref[$onEncodeEnd]?.();
460
514
  // Not a new instance anymore
461
515
  delete this[$isNew];
@@ -468,12 +522,12 @@
468
522
  //
469
523
  this.ref[$onEncodeEnd]?.();
470
524
  this.changes.clear();
471
- this.filteredChanges.clear();
525
+ this.filteredChanges?.clear();
472
526
  // reset operation index
473
527
  this.currentOperationIndex = 0;
474
528
  if (discardAll) {
475
529
  this.allChanges.clear();
476
- this.allFilteredChanges.clear();
530
+ this.allFilteredChanges?.clear();
477
531
  // remove children references
478
532
  this.forEachChild((changeTree, _) => this.root?.remove(changeTree));
479
533
  }
@@ -500,48 +554,41 @@
500
554
  get changed() {
501
555
  return this.changes.size > 0;
502
556
  }
503
- checkIsFiltered(parent, parentIndex) {
557
+ checkIsFiltered(metadata, parent, parentIndex) {
504
558
  // 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;
559
+ this.isPartiallyFiltered = metadata?.[-2] !== undefined;
560
+ if (this.isPartiallyFiltered) {
561
+ this.filteredChanges = this.filteredChanges || new Map();
562
+ this.allFilteredChanges = this.allFilteredChanges || new Map();
563
+ }
564
+ if (parent) {
565
+ if (!Metadata.isValidInstance(parent)) {
566
+ const parentChangeTree = parent[$changes];
567
+ parent = parentChangeTree.parent;
568
+ parentIndex = parentChangeTree.parentIndex;
569
+ }
570
+ const parentMetadata = parent?.constructor?.[Symbol.metadata];
571
+ this.isFiltered = (parent && parentMetadata?.[-2]?.includes(parentIndex));
572
+ //
573
+ // TODO: refactor this!
574
+ //
575
+ // swapping `changes` and `filteredChanges` is required here
576
+ // because "isFiltered" may not be imedialely available on `change()`
577
+ //
578
+ if (this.isFiltered) {
579
+ this.filteredChanges = new Map();
580
+ this.allFilteredChanges = new Map();
581
+ if (this.changes.size > 0) {
582
+ // swap changes reference
583
+ const changes = this.changes;
584
+ this.changes = this.filteredChanges;
585
+ this.filteredChanges = changes;
586
+ // swap "all changes" reference
587
+ const allFilteredChanges = this.allFilteredChanges;
588
+ this.allFilteredChanges = this.allChanges;
589
+ this.allChanges = allFilteredChanges;
590
+ }
591
+ }
545
592
  }
546
593
  }
547
594
  }
@@ -825,62 +872,11 @@
825
872
  writeFloat64: writeFloat64
826
873
  });
827
874
 
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}`);
875
+ function encodeValue(encoder, bytes, type, value, operation, it) {
876
+ if (typeof (type) === "string") {
877
+ encode[type]?.(bytes, value, it);
878
878
  }
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);
879
+ else if (type[Symbol.metadata] !== undefined) {
884
880
  //
885
881
  // Encode refId for this instance.
886
882
  // The actual instance is going to be encoded on next `changeTree` iteration.
@@ -891,21 +887,7 @@
891
887
  encoder.tryEncodeTypeId(bytes, type, value.constructor, it);
892
888
  }
893
889
  }
894
- else if (typeof (type) === "string") {
895
- //
896
- // Primitive values
897
- //
898
- encodePrimitiveType(type, bytes, value, ref, field, it);
899
- }
900
890
  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);
909
891
  //
910
892
  // Encode refId for this instance.
911
893
  // The actual instance is going to be encoded on next `changeTree` iteration.
@@ -918,26 +900,23 @@
918
900
  * @private
919
901
  */
920
902
  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
903
  // "compress" field index + operation
927
904
  bytes[it.offset++] = (index | operation) & 255;
928
905
  // Do not encode value for DELETE operations
929
906
  if (operation === exports.OPERATION.DELETE) {
930
907
  return;
931
908
  }
909
+ const ref = changeTree.ref;
910
+ const metadata = ref.constructor[Symbol.metadata];
911
+ const field = metadata[index];
932
912
  // TODO: inline this function call small performance gain
933
- encodeValue(encoder, bytes, ref, type, value, field, operation, it);
913
+ encodeValue(encoder, bytes, metadata[index].type, ref[field.name], operation, it);
934
914
  };
935
915
  /**
936
916
  * Used for collections (MapSchema, CollectionSchema, SetSchema)
937
917
  * @private
938
918
  */
939
- const encodeKeyValueOperation = function (encoder, bytes, changeTree, field, operation, it) {
940
- const ref = changeTree.ref;
919
+ const encodeKeyValueOperation = function (encoder, bytes, changeTree, index, operation, it) {
941
920
  // encode operation
942
921
  bytes[it.offset++] = operation & 255;
943
922
  // custom operations
@@ -945,25 +924,26 @@
945
924
  return;
946
925
  }
947
926
  // encode index
948
- number$1(bytes, field, it);
927
+ number$1(bytes, index, it);
949
928
  // Do not encode value for DELETE operations
950
929
  if (operation === exports.OPERATION.DELETE) {
951
930
  return;
952
931
  }
932
+ const ref = changeTree.ref;
953
933
  //
954
934
  // encode "alias" for dynamic fields (maps)
955
935
  //
956
- if ((operation & exports.OPERATION.ADD) == exports.OPERATION.ADD) { // ADD or DELETE_AND_ADD
936
+ if ((operation & exports.OPERATION.ADD) === exports.OPERATION.ADD) { // ADD or DELETE_AND_ADD
957
937
  if (typeof (ref['set']) === "function") {
958
938
  //
959
939
  // MapSchema dynamic key
960
940
  //
961
- const dynamicIndex = changeTree.ref['$indexes'].get(field);
941
+ const dynamicIndex = changeTree.ref['$indexes'].get(index);
962
942
  string$1(bytes, dynamicIndex, it);
963
943
  }
964
944
  }
965
- const type = changeTree.getType(field);
966
- const value = changeTree.getValue(field);
945
+ const type = ref[$childType];
946
+ const value = ref[$getByIndex](index);
967
947
  // try { throw new Error(); } catch (e) {
968
948
  // // only print if not coming from Reflection.ts
969
949
  // if (!e.stack.includes("src/Reflection.ts")) {
@@ -977,7 +957,7 @@
977
957
  // }
978
958
  // }
979
959
  // TODO: inline this function call small performance gain
980
- encodeValue(encoder, bytes, ref, type, value, field, operation, it);
960
+ encodeValue(encoder, bytes, type, value, operation, it);
981
961
  };
982
962
  /**
983
963
  * Used for collections (MapSchema, ArraySchema, etc.)
@@ -1021,7 +1001,7 @@
1021
1001
  // items: ref.toJSON(),
1022
1002
  // });
1023
1003
  // TODO: inline this function call small performance gain
1024
- encodeValue(encoder, bytes, ref, type, value, field, operation, it);
1004
+ encodeValue(encoder, bytes, type, value, operation, it);
1025
1005
  };
1026
1006
 
1027
1007
  /**
@@ -1327,7 +1307,9 @@
1327
1307
  if (!value) {
1328
1308
  value = decoder.createInstanceOfType(childType);
1329
1309
  }
1330
- $root.addRef(refId, value, (value !== previousValue));
1310
+ $root.addRef(refId, value, (value !== previousValue || // increment ref count if value has changed
1311
+ (operation === exports.OPERATION.DELETE_AND_ADD && value === previousValue) // increment ref count if the same instance is being added again
1312
+ ));
1331
1313
  }
1332
1314
  }
1333
1315
  else if (typeof (type) === "string") {
@@ -1378,7 +1360,7 @@
1378
1360
  }
1379
1361
  const decodeSchemaOperation = function (decoder, bytes, it, ref, allChanges) {
1380
1362
  const first_byte = bytes[it.offset++];
1381
- const metadata = ref['constructor'][Symbol.metadata];
1363
+ const metadata = ref.constructor[Symbol.metadata];
1382
1364
  // "compressed" index + operation
1383
1365
  const operation = (first_byte >> 6) << 6;
1384
1366
  const index = first_byte % (operation || 255);
@@ -1388,9 +1370,9 @@
1388
1370
  console.warn("@colyseus/schema: field not defined at", { index, ref: ref.constructor.name, metadata });
1389
1371
  return DEFINITION_MISMATCH;
1390
1372
  }
1391
- const { value, previousValue } = decodeValue(decoder, operation, ref, index, metadata[field].type, bytes, it, allChanges);
1373
+ const { value, previousValue } = decodeValue(decoder, operation, ref, index, field.type, bytes, it, allChanges);
1392
1374
  if (value !== null && value !== undefined) {
1393
- ref[field] = value;
1375
+ ref[field.name] = value;
1394
1376
  }
1395
1377
  // add change
1396
1378
  if (previousValue !== value) {
@@ -1398,7 +1380,7 @@
1398
1380
  ref,
1399
1381
  refId: decoder.currentRefId,
1400
1382
  op: operation,
1401
- field: field,
1383
+ field: field.name,
1402
1384
  value,
1403
1385
  previousValue,
1404
1386
  });
@@ -1496,7 +1478,6 @@
1496
1478
  return;
1497
1479
  }
1498
1480
  else if (operation === exports.OPERATION.ADD_BY_REFID) {
1499
- // operation = OPERATION.ADD;
1500
1481
  const refId = number(bytes, it);
1501
1482
  const itemByRefId = decoder.root.refs.get(refId);
1502
1483
  // use existing index, or push new value
@@ -1530,6 +1511,47 @@
1530
1511
  }
1531
1512
  };
1532
1513
 
1514
+ class EncodeSchemaError extends Error {
1515
+ }
1516
+ function assertType(value, type, klass, field) {
1517
+ let typeofTarget;
1518
+ let allowNull = false;
1519
+ switch (type) {
1520
+ case "number":
1521
+ case "int8":
1522
+ case "uint8":
1523
+ case "int16":
1524
+ case "uint16":
1525
+ case "int32":
1526
+ case "uint32":
1527
+ case "int64":
1528
+ case "uint64":
1529
+ case "float32":
1530
+ case "float64":
1531
+ typeofTarget = "number";
1532
+ if (isNaN(value)) {
1533
+ console.log(`trying to encode "NaN" in ${klass.constructor.name}#${field}`);
1534
+ }
1535
+ break;
1536
+ case "string":
1537
+ typeofTarget = "string";
1538
+ allowNull = true;
1539
+ break;
1540
+ case "boolean":
1541
+ // boolean is always encoded as true/false based on truthiness
1542
+ return;
1543
+ }
1544
+ if (typeof (value) !== typeofTarget && (!allowNull || (allowNull && value !== null))) {
1545
+ let foundValue = `'${JSON.stringify(value)}'${(value && value.constructor && ` (${value.constructor.name})`) || ''}`;
1546
+ throw new EncodeSchemaError(`a '${typeofTarget}' was expected, but ${foundValue} was provided in ${klass.constructor.name}#${field}`);
1547
+ }
1548
+ }
1549
+ function assertInstanceType(value, type, instance, field) {
1550
+ if (!(value instanceof type)) {
1551
+ throw new EncodeSchemaError(`a '${type.name}' was expected, but '${value && value.constructor.name}' was provided in ${instance.constructor.name}#${field}`);
1552
+ }
1553
+ }
1554
+
1533
1555
  var _a$4, _b$4;
1534
1556
  const DEFAULT_SORT = (a, b) => {
1535
1557
  const A = a.toString();
@@ -1595,6 +1617,7 @@
1595
1617
  }
1596
1618
  else {
1597
1619
  if (setValue[$changes]) {
1620
+ assertInstanceType(setValue, obj[$childType], obj, key);
1598
1621
  if (obj.items[key] !== undefined) {
1599
1622
  if (setValue[$changes][$isNew]) {
1600
1623
  this[$changes].indexedOperation(Number(key), exports.OPERATION.MOVE_AND_ADD);
@@ -1641,6 +1664,7 @@
1641
1664
  }
1642
1665
  });
1643
1666
  this[$changes] = new ChangeTree(proxy);
1667
+ this[$changes].indexes = {};
1644
1668
  this.push.apply(this, items);
1645
1669
  return proxy;
1646
1670
  }
@@ -1665,6 +1689,9 @@
1665
1689
  if (value === undefined || value === null) {
1666
1690
  return;
1667
1691
  }
1692
+ else if (typeof (value) === "object" && this[$childType]) {
1693
+ assertInstanceType(value, this[$childType], this, i);
1694
+ }
1668
1695
  const changeTree = this[$changes];
1669
1696
  changeTree.indexedOperation(length, exports.OPERATION.ADD, this.items.length);
1670
1697
  this.items.push(value);
@@ -2192,6 +2219,7 @@
2192
2219
  this.$items = new Map();
2193
2220
  this.$indexes = new Map();
2194
2221
  this[$changes] = new ChangeTree(this);
2222
+ this[$changes].indexes = {};
2195
2223
  if (initialValues) {
2196
2224
  if (initialValues instanceof Map ||
2197
2225
  initialValues instanceof MapSchema) {
@@ -2218,6 +2246,9 @@
2218
2246
  if (value === undefined || value === null) {
2219
2247
  throw new Error(`MapSchema#set('${key}', ${value}): trying to set ${value} value on '${key}'.`);
2220
2248
  }
2249
+ else if (typeof (value) === "object" && this[$childType]) {
2250
+ assertInstanceType(value, this[$childType], this, key);
2251
+ }
2221
2252
  // Force "key" as string
2222
2253
  // See: https://github.com/colyseus/colyseus/issues/561#issuecomment-1646733468
2223
2254
  key = key.toString();
@@ -2423,27 +2454,20 @@
2423
2454
  if (parentFieldViewTag !== undefined) {
2424
2455
  this.parentFiltered[`${this.schemas.get(klass)}-${parentIndex}`] = true;
2425
2456
  }
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;
2457
+ for (const fieldIndex in metadata) {
2458
+ const index = fieldIndex;
2459
+ const fieldType = metadata[index].type;
2460
+ const viewTag = metadata[index].tag;
2438
2461
  if (typeof (fieldType) === "string") {
2439
2462
  continue;
2440
2463
  }
2441
2464
  if (Array.isArray(fieldType)) {
2442
2465
  const type = fieldType[0];
2466
+ // skip primitive types
2443
2467
  if (type === "string") {
2444
2468
  continue;
2445
2469
  }
2446
- this.discoverTypes(type, metadata[field].index, viewTag);
2470
+ this.discoverTypes(type, index, viewTag);
2447
2471
  }
2448
2472
  else if (typeof (fieldType) === "function") {
2449
2473
  this.discoverTypes(fieldType, viewTag);
@@ -2454,7 +2478,7 @@
2454
2478
  if (typeof (type) === "string") {
2455
2479
  continue;
2456
2480
  }
2457
- this.discoverTypes(type, metadata[field].index, viewTag);
2481
+ this.discoverTypes(type, index, viewTag);
2458
2482
  }
2459
2483
  }
2460
2484
  }
@@ -2610,17 +2634,18 @@
2610
2634
  const parentMetadata = parentClass[Symbol.metadata];
2611
2635
  // TODO: use Metadata.initialize()
2612
2636
  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
- }
2637
+ // const fieldIndex = metadata[fieldName];
2638
+ // if (!metadata[fieldIndex]) {
2639
+ // //
2640
+ // // detect index for this field, considering inheritance
2641
+ // //
2642
+ // metadata[fieldIndex] = {
2643
+ // type: undefined,
2644
+ // index: (metadata[-1] // current structure already has fields defined
2645
+ // ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
2646
+ // ?? -1) + 1 // no fields defined
2647
+ // }
2648
+ // }
2624
2649
  Metadata.setTag(metadata, fieldName, tag);
2625
2650
  };
2626
2651
  }
@@ -2635,16 +2660,16 @@
2635
2660
  const parentClass = Object.getPrototypeOf(constructor);
2636
2661
  const parentMetadata = parentClass && parentClass[Symbol.metadata];
2637
2662
  const metadata = Metadata.initialize(constructor, parentMetadata);
2638
- let fieldIndex;
2663
+ let fieldIndex = metadata[field];
2639
2664
  /**
2640
2665
  * skip if descriptor already exists for this field (`@deprecated()`)
2641
2666
  */
2642
- if (metadata[field]) {
2643
- if (metadata[field].deprecated) {
2667
+ if (metadata[fieldIndex]) {
2668
+ if (metadata[fieldIndex].deprecated) {
2644
2669
  // do not create accessors for deprecated properties.
2645
2670
  return;
2646
2671
  }
2647
- else if (metadata[field].descriptor !== undefined) {
2672
+ else if (metadata[fieldIndex].type !== undefined) {
2648
2673
  // trying to define same property multiple times across inheritance.
2649
2674
  // https://github.com/colyseus/colyseus-unity3d/issues/131#issuecomment-814308572
2650
2675
  try {
@@ -2655,9 +2680,6 @@
2655
2680
  throw new Error(`${e.message} ${definitionAtLine}`);
2656
2681
  }
2657
2682
  }
2658
- else {
2659
- fieldIndex = metadata[field].index;
2660
- }
2661
2683
  }
2662
2684
  else {
2663
2685
  //
@@ -2683,11 +2705,11 @@
2683
2705
  const childType = (complexTypeKlass)
2684
2706
  ? Object.values(type)[0]
2685
2707
  : type;
2686
- Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass, metadata, field));
2708
+ Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass));
2687
2709
  }
2688
2710
  };
2689
2711
  }
2690
- function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass, metadata, field) {
2712
+ function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass) {
2691
2713
  return {
2692
2714
  get: function () { return this[fieldCached]; },
2693
2715
  set: function (value) {
@@ -2709,22 +2731,27 @@
2709
2731
  }
2710
2732
  value[$childType] = type;
2711
2733
  }
2734
+ else if (typeof (type) !== "string") {
2735
+ assertInstanceType(value, type, this, fieldCached.substring(1));
2736
+ }
2737
+ else {
2738
+ assertType(value, type, this, fieldCached.substring(1));
2739
+ }
2740
+ const changeTree = this[$changes];
2712
2741
  //
2713
2742
  // Replacing existing "ref", remove it from root.
2714
2743
  // TODO: if there are other references to this instance, we should not remove it from root.
2715
2744
  //
2716
2745
  if (previousValue !== undefined && previousValue[$changes]) {
2717
- this[$changes].root?.remove(previousValue[$changes]);
2746
+ changeTree.root?.remove(previousValue[$changes]);
2718
2747
  }
2719
2748
  // flag the change for encoding.
2720
- this.constructor[$track](this[$changes], fieldIndex, exports.OPERATION.ADD);
2749
+ this.constructor[$track](changeTree, fieldIndex, exports.OPERATION.ADD);
2721
2750
  //
2722
2751
  // call setParent() recursively for this and its child
2723
2752
  // structures.
2724
2753
  //
2725
- if (value[$changes]) {
2726
- value[$changes].setParent(this, this[$changes].root, metadata[field].index);
2727
- }
2754
+ value[$changes]?.setParent(this, changeTree.root, fieldIndex);
2728
2755
  }
2729
2756
  else if (previousValue !== undefined) {
2730
2757
  //
@@ -2751,20 +2778,22 @@
2751
2778
  const parentClass = Object.getPrototypeOf(constructor);
2752
2779
  const parentMetadata = parentClass[Symbol.metadata];
2753
2780
  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;
2781
+ const fieldIndex = metadata[field];
2782
+ // if (!metadata[field]) {
2783
+ // //
2784
+ // // detect index for this field, considering inheritance
2785
+ // //
2786
+ // metadata[field] = {
2787
+ // type: undefined,
2788
+ // index: (metadata[-1] // current structure already has fields defined
2789
+ // ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
2790
+ // ?? -1) + 1 // no fields defined
2791
+ // }
2792
+ // }
2793
+ metadata[fieldIndex].deprecated = true;
2766
2794
  if (throws) {
2767
- metadata[field].descriptor = {
2795
+ metadata[$descriptors] ??= {};
2796
+ metadata[$descriptors][field] = {
2768
2797
  get: function () { throw new Error(`${field} is deprecated.`); },
2769
2798
  set: function (value) { },
2770
2799
  enumerable: false,
@@ -2772,8 +2801,8 @@
2772
2801
  };
2773
2802
  }
2774
2803
  // flag metadata[field] as non-enumerable
2775
- Object.defineProperty(metadata, field, {
2776
- value: metadata[field],
2804
+ Object.defineProperty(metadata, fieldIndex, {
2805
+ value: metadata[fieldIndex],
2777
2806
  enumerable: false,
2778
2807
  configurable: true
2779
2808
  });
@@ -2839,35 +2868,7 @@
2839
2868
  enumerable: false,
2840
2869
  writable: true
2841
2870
  });
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
- }
2871
+ Object.defineProperties(instance, instance.constructor[Symbol.metadata]?.[$descriptors] || {});
2871
2872
  }
2872
2873
  static is(type) {
2873
2874
  return typeof (type[Symbol.metadata]) === "object";
@@ -2891,7 +2892,7 @@
2891
2892
  */
2892
2893
  static [$filter](ref, index, view) {
2893
2894
  const metadata = ref.constructor[Symbol.metadata];
2894
- const tag = metadata[metadata[index]].tag;
2895
+ const tag = metadata[index]?.tag;
2895
2896
  if (view === undefined) {
2896
2897
  // shared pass/encode: encode if doesn't have a tag
2897
2898
  return tag === undefined;
@@ -2912,12 +2913,21 @@
2912
2913
  }
2913
2914
  // allow inherited classes to have a constructor
2914
2915
  constructor(...args) {
2915
- Schema.initialize(this);
2916
+ //
2917
+ // inline
2918
+ // Schema.initialize(this);
2919
+ //
2920
+ Object.defineProperty(this, $changes, {
2921
+ value: new ChangeTree(this),
2922
+ enumerable: false,
2923
+ writable: true
2924
+ });
2925
+ Object.defineProperties(this, this.constructor[Symbol.metadata]?.[$descriptors] || {});
2916
2926
  //
2917
2927
  // Assign initial values
2918
2928
  //
2919
2929
  if (args[0]) {
2920
- this.assign(args[0]);
2930
+ Object.assign(this, args[0]);
2921
2931
  }
2922
2932
  }
2923
2933
  assign(props) {
@@ -2931,7 +2941,8 @@
2931
2941
  * @param operation OPERATION to perform (detected automatically)
2932
2942
  */
2933
2943
  setDirty(property, operation) {
2934
- this[$changes].change(this.constructor[Symbol.metadata][property].index, operation);
2944
+ const metadata = this.constructor[Symbol.metadata];
2945
+ this[$changes].change(metadata[metadata[property]].index, operation);
2935
2946
  }
2936
2947
  clone() {
2937
2948
  const cloned = new (this.constructor);
@@ -2940,7 +2951,9 @@
2940
2951
  // TODO: clone all properties, not only annotated ones
2941
2952
  //
2942
2953
  // for (const field in this) {
2943
- for (const field in metadata) {
2954
+ for (const fieldIndex in metadata) {
2955
+ // const field = metadata[metadata[fieldIndex]].name;
2956
+ const field = metadata[fieldIndex].name;
2944
2957
  if (typeof (this[field]) === "object" &&
2945
2958
  typeof (this[field]?.clone) === "function") {
2946
2959
  // deep clone
@@ -2954,10 +2967,11 @@
2954
2967
  return cloned;
2955
2968
  }
2956
2969
  toJSON() {
2957
- const metadata = this.constructor[Symbol.metadata];
2958
2970
  const obj = {};
2959
- for (const fieldName in metadata) {
2960
- const field = metadata[fieldName];
2971
+ const metadata = this.constructor[Symbol.metadata];
2972
+ for (const index in metadata) {
2973
+ const field = metadata[index];
2974
+ const fieldName = field.name;
2961
2975
  if (!field.deprecated && this[fieldName] !== null && typeof (this[fieldName]) !== "undefined") {
2962
2976
  obj[fieldName] = (typeof (this[fieldName]['toJSON']) === "function")
2963
2977
  ? this[fieldName]['toJSON']()
@@ -2970,10 +2984,12 @@
2970
2984
  this[$changes].discardAll();
2971
2985
  }
2972
2986
  [$getByIndex](index) {
2973
- return this[this.constructor[Symbol.metadata][index]];
2987
+ const metadata = this.constructor[Symbol.metadata];
2988
+ return this[metadata[index].name];
2974
2989
  }
2975
2990
  [$deleteByIndex](index) {
2976
- this[this.constructor[Symbol.metadata][index]] = undefined;
2991
+ const metadata = this.constructor[Symbol.metadata];
2992
+ this[metadata[index].name] = undefined;
2977
2993
  }
2978
2994
  static debugRefIds(instance, jsonContents = true, level = 0) {
2979
2995
  const ref = instance;
@@ -3097,6 +3113,7 @@
3097
3113
  this.$indexes = new Map();
3098
3114
  this.$refId = 0;
3099
3115
  this[$changes] = new ChangeTree(this);
3116
+ this[$changes].indexes = {};
3100
3117
  if (initialValues) {
3101
3118
  initialValues.forEach((v) => this.add(v));
3102
3119
  }
@@ -3252,6 +3269,7 @@
3252
3269
  this.$indexes = new Map();
3253
3270
  this.$refId = 0;
3254
3271
  this[$changes] = new ChangeTree(this);
3272
+ this[$changes].indexes = {};
3255
3273
  if (initialValues) {
3256
3274
  initialValues.forEach((v) => this.add(v));
3257
3275
  }
@@ -3438,24 +3456,36 @@
3438
3456
  return this.nextUniqueId++;
3439
3457
  }
3440
3458
  add(changeTree) {
3441
- const refCount = this.refCount.get(changeTree) || 0;
3442
- this.refCount.set(changeTree, refCount + 1);
3459
+ const previousRefCount = this.refCount.get(changeTree);
3460
+ if (previousRefCount === 0) {
3461
+ //
3462
+ // When a ChangeTree is re-added, it means that it was previously removed.
3463
+ // We need to re-add all changes to the `changes` map.
3464
+ //
3465
+ changeTree.allChanges.forEach((operation, index) => {
3466
+ changeTree.changes.set(index, operation);
3467
+ });
3468
+ }
3469
+ const refCount = (previousRefCount || 0) + 1;
3470
+ this.refCount.set(changeTree, refCount);
3471
+ return refCount;
3443
3472
  }
3444
3473
  remove(changeTree) {
3445
- const refCount = this.refCount.get(changeTree);
3446
- if (refCount <= 1) {
3474
+ const refCount = (this.refCount.get(changeTree)) - 1;
3475
+ if (refCount <= 0) {
3447
3476
  this.allChanges.delete(changeTree);
3448
3477
  this.changes.delete(changeTree);
3449
3478
  if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
3450
3479
  this.allFilteredChanges.delete(changeTree);
3451
3480
  this.filteredChanges.delete(changeTree);
3452
3481
  }
3453
- this.refCount.delete(changeTree);
3482
+ this.refCount.set(changeTree, 0);
3454
3483
  }
3455
3484
  else {
3456
- this.refCount.set(changeTree, refCount - 1);
3485
+ this.refCount.set(changeTree, refCount);
3457
3486
  }
3458
3487
  changeTree.forEachChild((child, _) => this.remove(child));
3488
+ return refCount;
3459
3489
  }
3460
3490
  clear() {
3461
3491
  this.changes.clear();
@@ -3486,10 +3516,10 @@
3486
3516
  ) {
3487
3517
  const hasView = (view !== undefined);
3488
3518
  const rootChangeTree = this.state[$changes];
3489
- const changeTreesIterator = changeTrees.entries();
3490
- for (const [changeTree, changes] of changeTreesIterator) {
3519
+ const shouldClearChanges = !isEncodeAll && !hasView;
3520
+ for (const [changeTree, changes] of changeTrees.entries()) {
3491
3521
  const ref = changeTree.ref;
3492
- const ctor = ref['constructor'];
3522
+ const ctor = ref.constructor;
3493
3523
  const encoder = ctor[$encoder];
3494
3524
  const filter = ctor[$filter];
3495
3525
  // try { throw new Error(); } catch (e) {
@@ -3513,8 +3543,7 @@
3513
3543
  buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
3514
3544
  number$1(buffer, changeTree.refId, it);
3515
3545
  }
3516
- const changesIterator = changes.entries();
3517
- for (const [fieldIndex, operation] of changesIterator) {
3546
+ for (const [fieldIndex, operation] of changes.entries()) {
3518
3547
  //
3519
3548
  // first pass (encodeAll), identify "filtered" operations without encoding them
3520
3549
  // they will be encoded per client, based on their view.
@@ -3539,6 +3568,14 @@
3539
3568
  // }
3540
3569
  encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView);
3541
3570
  }
3571
+ // if (shouldClearChanges) {
3572
+ // // changeTree.endEncode();
3573
+ // changeTree.changes.clear();
3574
+ // // ArraySchema and MapSchema have a custom "encode end" method
3575
+ // changeTree.ref[$onEncodeEnd]?.();
3576
+ // // Not a new instance anymore
3577
+ // delete changeTree[$isNew];
3578
+ // }
3542
3579
  }
3543
3580
  if (it.offset > buffer.byteLength) {
3544
3581
  const newSize = getNextPowerOf2(buffer.byteLength * 2);
@@ -3561,7 +3598,7 @@
3561
3598
  //
3562
3599
  // only clear changes after making sure buffer resize is not required.
3563
3600
  //
3564
- if (!isEncodeAll && !hasView) {
3601
+ if (shouldClearChanges) {
3565
3602
  //
3566
3603
  // FIXME: avoid iterating over change trees twice.
3567
3604
  //
@@ -3645,6 +3682,11 @@
3645
3682
  const changeTreesIterator = changeTrees.entries();
3646
3683
  for (const [changeTree, _] of changeTreesIterator) {
3647
3684
  changeTree.endEncode();
3685
+ // changeTree.changes.clear();
3686
+ // // ArraySchema and MapSchema have a custom "encode end" method
3687
+ // changeTree.ref[$onEncodeEnd]?.();
3688
+ // // Not a new instance anymore
3689
+ // delete changeTree[$isNew];
3648
3690
  }
3649
3691
  }
3650
3692
  discardChanges() {
@@ -3760,8 +3802,9 @@
3760
3802
  // Ensure child schema instances have their references removed as well.
3761
3803
  //
3762
3804
  if (Metadata.isValidInstance(ref)) {
3763
- const metadata = ref['constructor'][Symbol.metadata];
3764
- for (const field in metadata) {
3805
+ const metadata = ref.constructor[Symbol.metadata];
3806
+ for (const index in metadata) {
3807
+ const field = metadata[index].name;
3765
3808
  const childRefId = typeof (ref[field]) === "object" && this.refIds.get(ref[field]);
3766
3809
  if (childRefId) {
3767
3810
  this.removeRef(childRefId);
@@ -3948,7 +3991,9 @@
3948
3991
  const reflection = new Reflection();
3949
3992
  const encoder = new Encoder(reflection);
3950
3993
  const buildType = (currentType, metadata) => {
3951
- for (const fieldName in metadata) {
3994
+ for (const fieldIndex in metadata) {
3995
+ const index = Number(fieldIndex);
3996
+ const fieldName = metadata[index].name;
3952
3997
  // skip fields from parent classes
3953
3998
  if (!Object.prototype.hasOwnProperty.call(metadata, fieldName)) {
3954
3999
  continue;
@@ -3956,7 +4001,7 @@
3956
4001
  const field = new ReflectionField();
3957
4002
  field.name = fieldName;
3958
4003
  let fieldType;
3959
- const type = metadata[fieldName].type;
4004
+ const type = metadata[index].type;
3960
4005
  if (typeof (type) === "string") {
3961
4006
  fieldType = type;
3962
4007
  }
@@ -4022,18 +4067,7 @@
4022
4067
  reflection.types.forEach((reflectionType) => {
4023
4068
  const schemaType = typeContext.get(reflectionType.id);
4024
4069
  const metadata = schemaType[Symbol.metadata];
4025
- // FIXME: use metadata[-1] to get field count
4026
4070
  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
4071
  reflectionType.fields.forEach((field, i) => {
4038
4072
  const fieldIndex = parentFieldIndex + i;
4039
4073
  if (field.referencedType !== undefined) {
@@ -4198,7 +4232,7 @@
4198
4232
  //
4199
4233
  bindTo: function bindTo(targetObject, properties) {
4200
4234
  if (!properties) {
4201
- properties = Object.keys(metadata);
4235
+ properties = Object.keys(metadata).map((index) => metadata[index].name);
4202
4236
  }
4203
4237
  return $root.addCallback($root.refIds.get(context.instance), exports.OPERATION.REPLACE, () => {
4204
4238
  properties.forEach((prop) => targetObject[prop] = context.instance[prop]);
@@ -4206,7 +4240,8 @@
4206
4240
  }
4207
4241
  }, {
4208
4242
  get(target, prop) {
4209
- if (metadata[prop]) {
4243
+ const metadataField = metadata[metadata[prop]];
4244
+ if (metadataField) {
4210
4245
  const instance = context.instance?.[prop];
4211
4246
  const onInstanceAvailable = ((callback) => {
4212
4247
  const unbind = $(context.instance).listen(prop, (value, _) => {
@@ -4222,7 +4257,7 @@
4222
4257
  callback(instance, true);
4223
4258
  }
4224
4259
  });
4225
- return getProxy(metadata[prop].type, {
4260
+ return getProxy(metadataField.type, {
4226
4261
  // make sure refId is available, otherwise need to wait for the instance to be available.
4227
4262
  instance: ($root.refIds.get(instance) && instance),
4228
4263
  parentInstance: context.instance,
@@ -4335,7 +4370,7 @@
4335
4370
  console.warn("StateView#add(), invalid object:", obj);
4336
4371
  return this;
4337
4372
  }
4338
- // FIXME: ArraySchema/MapSchema does not have metadata
4373
+ // FIXME: ArraySchema/MapSchema do not have metadata
4339
4374
  const metadata = obj.constructor[Symbol.metadata];
4340
4375
  const changeTree = obj[$changes];
4341
4376
  this.items.add(changeTree);
@@ -4381,7 +4416,7 @@
4381
4416
  ? changeTree.allFilteredChanges
4382
4417
  : changeTree.allChanges;
4383
4418
  changeSet.forEach((op, index) => {
4384
- const tagAtIndex = metadata?.[metadata?.[index]].tag;
4419
+ const tagAtIndex = metadata?.[index].tag;
4385
4420
  if ((isInvisible || // if "invisible", include all
4386
4421
  tagAtIndex === undefined || // "all change" with no tag
4387
4422
  tagAtIndex === tag // tagged property
@@ -4394,7 +4429,7 @@
4394
4429
  // Add children of this ChangeTree to this view
4395
4430
  changeTree.forEachChild((change, index) => {
4396
4431
  // Do not ADD children that don't have the same tag
4397
- if (metadata && metadata[metadata[index]].tag !== tag) {
4432
+ if (metadata && metadata[index].tag !== tag) {
4398
4433
  return;
4399
4434
  }
4400
4435
  this.add(change.ref, tag, false);