@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
@@ -36,6 +36,7 @@ const $decoder = Symbol("$decoder");
36
36
  const $filter = Symbol("$filter");
37
37
  const $getByIndex = Symbol("$getByIndex");
38
38
  const $deleteByIndex = Symbol("$deleteByIndex");
39
+ const $descriptors = Symbol("$descriptors");
39
40
  /**
40
41
  * Used to hold ChangeTree instances whitin the structures
41
42
  */
@@ -71,34 +72,67 @@ function getType(identifier) {
71
72
  }
72
73
 
73
74
  const Metadata = {
74
- addField(metadata, index, field, type, descriptor) {
75
+ addField(metadata, index, name, type, descriptor) {
75
76
  if (index > 64) {
76
- throw new Error(`Can't define field '${field}'.\nSchema instances may only have up to 64 fields.`);
77
+ throw new Error(`Can't define field '${name}'.\nSchema instances may only have up to 64 fields.`);
77
78
  }
78
- metadata[field] = Object.assign(metadata[field] || {}, // avoid overwriting previous field metadata (@owned / @deprecated)
79
+ metadata[index] = Object.assign(metadata[index] || {}, // avoid overwriting previous field metadata (@owned / @deprecated)
79
80
  {
80
81
  type: (Array.isArray(type))
81
82
  ? { array: type[0] }
82
83
  : type,
83
84
  index,
84
- descriptor,
85
+ name,
85
86
  });
87
+ // create "descriptors" map
88
+ metadata[$descriptors] ??= {};
89
+ if (descriptor) {
90
+ // for encoder
91
+ metadata[$descriptors][name] = descriptor;
92
+ metadata[$descriptors][`_${name}`] = {
93
+ value: undefined,
94
+ writable: true,
95
+ enumerable: false,
96
+ configurable: true,
97
+ };
98
+ }
99
+ else {
100
+ // for decoder
101
+ metadata[$descriptors][name] = {
102
+ value: undefined,
103
+ writable: true,
104
+ enumerable: true,
105
+ configurable: true,
106
+ };
107
+ }
86
108
  // map -1 as last field index
87
109
  Object.defineProperty(metadata, -1, {
88
110
  value: index,
89
111
  enumerable: false,
90
112
  configurable: true
91
113
  });
92
- // map index => field name (non enumerable)
93
- Object.defineProperty(metadata, index, {
94
- value: field,
114
+ // map field name => index (non enumerable)
115
+ Object.defineProperty(metadata, name, {
116
+ value: index,
95
117
  enumerable: false,
96
118
  configurable: true,
97
119
  });
120
+ // if child Ref/complex type, add to -4
121
+ if (typeof (metadata[index].type) !== "string") {
122
+ if (metadata[-4] === undefined) {
123
+ Object.defineProperty(metadata, -4, {
124
+ value: [],
125
+ enumerable: false,
126
+ configurable: true,
127
+ });
128
+ }
129
+ metadata[-4].push(index);
130
+ }
98
131
  },
99
132
  setTag(metadata, fieldName, tag) {
133
+ const index = metadata[fieldName];
134
+ const field = metadata[index];
100
135
  // add 'tag' to the field
101
- const field = metadata[fieldName];
102
136
  field.tag = tag;
103
137
  if (!metadata[-2]) {
104
138
  // -2: all field indexes with "view" tag
@@ -114,20 +148,14 @@ const Metadata = {
114
148
  configurable: true
115
149
  });
116
150
  }
117
- metadata[-2].push(field.index);
151
+ metadata[-2].push(index);
118
152
  if (!metadata[-3][tag]) {
119
153
  metadata[-3][tag] = [];
120
154
  }
121
- metadata[-3][tag].push(field.index);
155
+ metadata[-3][tag].push(index);
122
156
  },
123
157
  setFields(target, fields) {
124
158
  const metadata = (target.prototype.constructor[Symbol.metadata] ??= {});
125
- // target[$track] = function (changeTree, index: number, operation: OPERATION = OPERATION.ADD) {
126
- // changeTree.change(index, operation, encodeSchemaOperation);
127
- // };
128
- // target[$encoder] = encodeSchemaOperation;
129
- // target[$decoder] = decodeSchemaOperation;
130
- // if (!target.prototype.toJSON) { target.prototype.toJSON = Schema.prototype.toJSON; }
131
159
  let index = 0;
132
160
  for (const field in fields) {
133
161
  const type = fields[field];
@@ -135,7 +163,7 @@ const Metadata = {
135
163
  const complexTypeKlass = (Array.isArray(type))
136
164
  ? getType("array")
137
165
  : (typeof (Object.keys(type)[0]) === "string") && getType(Object.keys(type)[0]);
138
- Metadata.addField(metadata, index, field, type, getPropertyDescriptor(`_${field}`, index, type, complexTypeKlass, metadata, field));
166
+ Metadata.addField(metadata, index, field, type, getPropertyDescriptor(`_${field}`, index, type, complexTypeKlass));
139
167
  index++;
140
168
  }
141
169
  },
@@ -164,8 +192,9 @@ const Metadata = {
164
192
  // assign parent metadata to current
165
193
  Object.assign(metadata, parentMetadata);
166
194
  for (let i = 0; i <= parentMetadata[-1]; i++) {
167
- Object.defineProperty(metadata, i, {
168
- value: parentMetadata[i],
195
+ const fieldName = parentMetadata[i].name;
196
+ Object.defineProperty(metadata, fieldName, {
197
+ value: parentMetadata[fieldName],
169
198
  enumerable: false,
170
199
  configurable: true,
171
200
  });
@@ -189,7 +218,7 @@ const Metadata = {
189
218
  const metadata = klass[Symbol.metadata];
190
219
  const fields = {};
191
220
  for (let i = 0; i <= metadata[-1]; i++) {
192
- fields[metadata[i]] = metadata[metadata[i]].type;
221
+ fields[metadata[i].name] = metadata[i].type;
193
222
  }
194
223
  return fields;
195
224
  }
@@ -198,48 +227,59 @@ const Metadata = {
198
227
  var _a$5;
199
228
  class ChangeTree {
200
229
  static { _a$5 = $isNew; }
201
- ;
202
230
  constructor(ref) {
203
- this.indexes = {}; // TODO: remove this, only used by MapSchema/SetSchema/CollectionSchema (`encodeKeyValueOperation`)
231
+ this.isFiltered = false;
232
+ this.isPartiallyFiltered = false;
204
233
  this.currentOperationIndex = 0;
205
- this.allChanges = new Map();
206
- this.allFilteredChanges = new Map();
207
234
  this.changes = new Map();
208
- this.filteredChanges = new Map();
235
+ this.allChanges = new Map();
209
236
  this[_a$5] = true;
210
237
  this.ref = ref;
238
+ //
239
+ // Does this structure have "filters" declared?
240
+ //
241
+ if (ref.constructor[Symbol.metadata]?.[-2]) {
242
+ this.allFilteredChanges = new Map();
243
+ this.filteredChanges = new Map();
244
+ }
211
245
  }
212
246
  setRoot(root) {
213
247
  this.root = root;
214
248
  this.root.add(this);
215
- //
216
- // At Schema initialization, the "root" structure might not be available
217
- // yet, as it only does once the "Encoder" has been set up.
218
- //
219
- // So the "parent" may be already set without a "root".
220
- //
221
- this.checkIsFiltered(this.parent, this.parentIndex);
222
- // unique refId for the ChangeTree.
223
- this.ensureRefId();
249
+ const metadata = this.ref.constructor[Symbol.metadata];
250
+ if (this.root.types.hasFilters) {
251
+ //
252
+ // At Schema initialization, the "root" structure might not be available
253
+ // yet, as it only does once the "Encoder" has been set up.
254
+ //
255
+ // So the "parent" may be already set without a "root".
256
+ //
257
+ this.checkIsFiltered(metadata, this.parent, this.parentIndex);
258
+ if (this.isFiltered || this.isPartiallyFiltered) {
259
+ this.root.allFilteredChanges.set(this, this.allFilteredChanges);
260
+ this.root.filteredChanges.set(this, this.filteredChanges);
261
+ }
262
+ }
224
263
  if (!this.isFiltered) {
225
264
  this.root.changes.set(this, this.changes);
265
+ this.root.allChanges.set(this, this.allChanges);
226
266
  }
227
- if (this.isFiltered || this.isPartiallyFiltered) {
228
- this.root.allFilteredChanges.set(this, this.allFilteredChanges);
229
- this.root.filteredChanges.set(this, this.filteredChanges);
267
+ this.ensureRefId();
268
+ if (metadata) {
269
+ metadata[-4]?.forEach((index) => {
270
+ const field = metadata[index];
271
+ const value = this.ref[field.name];
272
+ if (value) {
273
+ value[$changes].setRoot(root);
274
+ }
275
+ });
230
276
  }
231
- if (!this.isFiltered) {
232
- this.root.allChanges.set(this, this.allChanges);
277
+ else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
278
+ // MapSchema / ArraySchema, etc.
279
+ this.ref.forEach((value, key) => {
280
+ value[$changes].setRoot(root);
281
+ });
233
282
  }
234
- this.forEachChild((changeTree, _) => {
235
- changeTree.setRoot(root);
236
- });
237
- // this.allChanges.forEach((_, index) => {
238
- // const childRef = this.ref[$getByIndex](index);
239
- // if (childRef && childRef[$changes]) {
240
- // childRef[$changes].setRoot(root);
241
- // }
242
- // });
243
283
  }
244
284
  setParent(parent, root, parentIndex) {
245
285
  this.parent = parent;
@@ -249,48 +289,60 @@ class ChangeTree {
249
289
  return;
250
290
  }
251
291
  root.add(this);
292
+ const metadata = this.ref.constructor[Symbol.metadata];
252
293
  // skip if parent is already set
253
- if (root === this.root) {
254
- this.forEachChild((changeTree, atIndex) => {
255
- changeTree.setParent(this.ref, root, atIndex);
256
- });
257
- return;
294
+ if (root !== this.root) {
295
+ this.root = root;
296
+ if (root.types.hasFilters) {
297
+ this.checkIsFiltered(metadata, parent, parentIndex);
298
+ if (this.isFiltered || this.isPartiallyFiltered) {
299
+ this.root.filteredChanges.set(this, this.filteredChanges);
300
+ this.root.allFilteredChanges.set(this, this.filteredChanges);
301
+ }
302
+ }
303
+ if (!this.isFiltered) {
304
+ this.root.changes.set(this, this.changes);
305
+ this.root.allChanges.set(this, this.allChanges);
306
+ }
307
+ this.ensureRefId();
258
308
  }
259
- this.root = root;
260
- this.checkIsFiltered(parent, parentIndex);
261
- if (!this.isFiltered) {
262
- this.root.changes.set(this, this.changes);
263
- this.root.allChanges.set(this, this.allChanges);
309
+ // assign same parent on child structures
310
+ if (metadata) {
311
+ metadata[-4]?.forEach((index) => {
312
+ const field = metadata[index];
313
+ const value = this.ref[field.name];
314
+ value?.[$changes].setParent(this.ref, root, index);
315
+ // console.log(this.ref.constructor.name, field.name, value);
316
+ // try { throw new Error(); } catch (e) {
317
+ // console.log(e.stack);
318
+ // }
319
+ });
264
320
  }
265
- if (this.isFiltered || this.isPartiallyFiltered) {
266
- this.root.filteredChanges.set(this, this.filteredChanges);
267
- this.root.allFilteredChanges.set(this, this.filteredChanges);
321
+ else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
322
+ // MapSchema / ArraySchema, etc.
323
+ this.ref.forEach((value, key) => {
324
+ value[$changes].setParent(this.ref, root, this.indexes[key] ?? key);
325
+ });
268
326
  }
269
- this.ensureRefId();
270
- this.forEachChild((changeTree, atIndex) => {
271
- changeTree.setParent(this.ref, root, atIndex);
272
- });
273
327
  }
274
328
  forEachChild(callback) {
275
329
  //
276
330
  // assign same parent on child structures
277
331
  //
278
- if (Metadata.isValidInstance(this.ref)) {
279
- const metadata = this.ref['constructor'][Symbol.metadata];
280
- // FIXME: need to iterate over parent metadata instead.
281
- for (const field in metadata) {
282
- const value = this.ref[field];
283
- if (value && value[$changes]) {
284
- callback(value[$changes], metadata[field].index);
332
+ const metadata = this.ref.constructor[Symbol.metadata];
333
+ if (metadata) {
334
+ metadata[-4]?.forEach((index) => {
335
+ const field = metadata[index];
336
+ const value = this.ref[field.name];
337
+ if (value) {
338
+ callback(value[$changes], index);
285
339
  }
286
- }
340
+ });
287
341
  }
288
- else if (typeof (this.ref) === "object") {
342
+ else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
289
343
  // MapSchema / ArraySchema, etc.
290
344
  this.ref.forEach((value, key) => {
291
- if (Metadata.isValidInstance(value)) {
292
- callback(value[$changes], this.ref[$changes].indexes[key] ?? key);
293
- }
345
+ callback(value[$changes], this.indexes[key] ?? key);
294
346
  });
295
347
  }
296
348
  }
@@ -299,8 +351,8 @@ class ChangeTree {
299
351
  this.root?.changes.set(this, this.changes);
300
352
  }
301
353
  change(index, operation = exports.OPERATION.ADD) {
302
- const metadata = this.ref['constructor'][Symbol.metadata];
303
- const isFiltered = this.isFiltered || (metadata && metadata[metadata[index]].tag !== undefined);
354
+ const metadata = this.ref.constructor[Symbol.metadata];
355
+ const isFiltered = this.isFiltered || (metadata?.[index]?.tag !== undefined);
304
356
  const changeSet = (isFiltered)
305
357
  ? this.filteredChanges
306
358
  : this.changes;
@@ -311,14 +363,14 @@ class ChangeTree {
311
363
  : (previousOperation === exports.OPERATION.DELETE)
312
364
  ? exports.OPERATION.DELETE_AND_ADD
313
365
  : operation;
366
+ //
367
+ // TODO: are DELETE operations being encoded as ADD here ??
368
+ //
314
369
  changeSet.set(index, op);
315
370
  }
316
- //
317
- // TODO: are DELETE operations being encoded as ADD here ??
318
- //
319
371
  if (isFiltered) {
320
- this.root?.filteredChanges.set(this, this.filteredChanges);
321
372
  this.allFilteredChanges.set(index, exports.OPERATION.ADD);
373
+ this.root?.filteredChanges.set(this, this.filteredChanges);
322
374
  this.root?.allFilteredChanges.set(this, this.allFilteredChanges);
323
375
  }
324
376
  else {
@@ -365,9 +417,7 @@ class ChangeTree {
365
417
  });
366
418
  }
367
419
  indexedOperation(index, operation, allChangesIndex = index) {
368
- const metadata = this.ref['constructor'][Symbol.metadata];
369
- const isFiltered = this.isFiltered || (metadata && metadata[metadata[index]].tag !== undefined);
370
- if (isFiltered) {
420
+ if (this.filteredChanges !== undefined) {
371
421
  this.allFilteredChanges.set(allChangesIndex, exports.OPERATION.ADD);
372
422
  this.filteredChanges.set(index, operation);
373
423
  this.root?.filteredChanges.set(this, this.filteredChanges);
@@ -380,8 +430,8 @@ class ChangeTree {
380
430
  }
381
431
  getType(index) {
382
432
  if (Metadata.isValidInstance(this.ref)) {
383
- const metadata = this.ref['constructor'][Symbol.metadata];
384
- return metadata[metadata[index]].type;
433
+ const metadata = this.ref.constructor[Symbol.metadata];
434
+ return metadata[index].type;
385
435
  }
386
436
  else {
387
437
  //
@@ -395,7 +445,7 @@ class ChangeTree {
395
445
  }
396
446
  getChange(index) {
397
447
  // TODO: optimize this. avoid checking against multiple instances
398
- return this.changes.get(index) ?? this.filteredChanges.get(index);
448
+ return this.changes.get(index) ?? this.filteredChanges?.get(index);
399
449
  }
400
450
  //
401
451
  // used during `.encode()`
@@ -416,9 +466,7 @@ class ChangeTree {
416
466
  }
417
467
  return;
418
468
  }
419
- const metadata = this.ref['constructor'][Symbol.metadata];
420
- const isFiltered = this.isFiltered || (metadata && metadata[metadata[index]].tag !== undefined);
421
- const changeSet = (isFiltered)
469
+ const changeSet = (this.filteredChanges)
422
470
  ? this.filteredChanges
423
471
  : this.changes;
424
472
  const previousValue = this.getValue(index);
@@ -441,7 +489,7 @@ class ChangeTree {
441
489
  //
442
490
  // FIXME: this is looking a bit ugly (and repeated from `.change()`)
443
491
  //
444
- if (isFiltered) {
492
+ if (this.filteredChanges) {
445
493
  this.root?.filteredChanges.set(this, this.filteredChanges);
446
494
  this.allFilteredChanges.delete(allChangesIndex);
447
495
  }
@@ -452,6 +500,7 @@ class ChangeTree {
452
500
  }
453
501
  endEncode() {
454
502
  this.changes.clear();
503
+ // ArraySchema and MapSchema have a custom "encode end" method
455
504
  this.ref[$onEncodeEnd]?.();
456
505
  // Not a new instance anymore
457
506
  delete this[$isNew];
@@ -464,12 +513,12 @@ class ChangeTree {
464
513
  //
465
514
  this.ref[$onEncodeEnd]?.();
466
515
  this.changes.clear();
467
- this.filteredChanges.clear();
516
+ this.filteredChanges?.clear();
468
517
  // reset operation index
469
518
  this.currentOperationIndex = 0;
470
519
  if (discardAll) {
471
520
  this.allChanges.clear();
472
- this.allFilteredChanges.clear();
521
+ this.allFilteredChanges?.clear();
473
522
  // remove children references
474
523
  this.forEachChild((changeTree, _) => this.root?.remove(changeTree));
475
524
  }
@@ -496,48 +545,41 @@ class ChangeTree {
496
545
  get changed() {
497
546
  return this.changes.size > 0;
498
547
  }
499
- checkIsFiltered(parent, parentIndex) {
548
+ checkIsFiltered(metadata, parent, parentIndex) {
500
549
  // Detect if current structure has "filters" declared
501
- this.isPartiallyFiltered = (this.ref['constructor']?.[Symbol.metadata]?.[-2] !== undefined);
502
- if (parent && !Metadata.isValidInstance(parent)) {
503
- const parentChangeTree = parent[$changes];
504
- parent = parentChangeTree.parent;
505
- parentIndex = parentChangeTree.parentIndex;
506
- }
507
- const parentMetadata = parent?.['constructor']?.[Symbol.metadata];
508
- this.isFiltered = (parent &&
509
- parentMetadata?.[-2]?.includes(parentIndex));
510
- // this.isFiltered = this.ref['constructor']?.[Symbol.metadata]?.[-4];
511
- // // Detect if parent has "filters" declared
512
- // while (parent && !this.isFiltered) {
513
- // const metadata: Metadata = parent['constructor'][Symbol.metadata];
514
- // // this.isFiltered = metadata?.[-4];
515
- // const fieldName = metadata?.[parentIndex];
516
- // const isParentOwned = metadata?.[fieldName]?.tag !== undefined;
517
- // this.isFiltered = isParentOwned || parent[$changes].isFiltered; // metadata?.[-2]
518
- // parent = parent[$changes].parent;
519
- // };
520
- // console.log("ChangeTree.checkIsFiltered", {
521
- // parent: parent?.constructor.name,
522
- // ref: this.ref.constructor.name,
523
- // isFiltered: this.isFiltered,
524
- // isPartiallyFiltered: this.isPartiallyFiltered,
525
- // });
526
- //
527
- // TODO: refactor this!
528
- //
529
- // swapping `changes` and `filteredChanges` is required here
530
- // because "isFiltered" may not be imedialely available on `change()`
531
- //
532
- if (this.isFiltered && this.changes.size > 0) {
533
- // swap changes reference
534
- const changes = this.changes;
535
- this.changes = this.filteredChanges;
536
- this.filteredChanges = changes;
537
- // swap "all changes" reference
538
- const allFilteredChanges = this.allFilteredChanges;
539
- this.allFilteredChanges = this.allChanges;
540
- this.allChanges = allFilteredChanges;
550
+ this.isPartiallyFiltered = metadata?.[-2] !== undefined;
551
+ if (this.isPartiallyFiltered) {
552
+ this.filteredChanges = this.filteredChanges || new Map();
553
+ this.allFilteredChanges = this.allFilteredChanges || new Map();
554
+ }
555
+ if (parent) {
556
+ if (!Metadata.isValidInstance(parent)) {
557
+ const parentChangeTree = parent[$changes];
558
+ parent = parentChangeTree.parent;
559
+ parentIndex = parentChangeTree.parentIndex;
560
+ }
561
+ const parentMetadata = parent?.constructor?.[Symbol.metadata];
562
+ this.isFiltered = (parent && parentMetadata?.[-2]?.includes(parentIndex));
563
+ //
564
+ // TODO: refactor this!
565
+ //
566
+ // swapping `changes` and `filteredChanges` is required here
567
+ // because "isFiltered" may not be imedialely available on `change()`
568
+ //
569
+ if (this.isFiltered) {
570
+ this.filteredChanges = new Map();
571
+ this.allFilteredChanges = new Map();
572
+ if (this.changes.size > 0) {
573
+ // swap changes reference
574
+ const changes = this.changes;
575
+ this.changes = this.filteredChanges;
576
+ this.filteredChanges = changes;
577
+ // swap "all changes" reference
578
+ const allFilteredChanges = this.allFilteredChanges;
579
+ this.allFilteredChanges = this.allChanges;
580
+ this.allChanges = allFilteredChanges;
581
+ }
582
+ }
541
583
  }
542
584
  }
543
585
  }
@@ -821,62 +863,21 @@ var encode = /*#__PURE__*/Object.freeze({
821
863
  writeFloat64: writeFloat64
822
864
  });
823
865
 
824
- class EncodeSchemaError extends Error {
825
- }
826
- function assertType(value, type, klass, field) {
827
- let typeofTarget;
828
- let allowNull = false;
829
- switch (type) {
830
- case "number":
831
- case "int8":
832
- case "uint8":
833
- case "int16":
834
- case "uint16":
835
- case "int32":
836
- case "uint32":
837
- case "int64":
838
- case "uint64":
839
- case "float32":
840
- case "float64":
841
- typeofTarget = "number";
842
- if (isNaN(value)) {
843
- console.log(`trying to encode "NaN" in ${klass.constructor.name}#${field}`);
844
- }
845
- break;
846
- case "string":
847
- typeofTarget = "string";
848
- allowNull = true;
849
- break;
850
- case "boolean":
851
- // boolean is always encoded as true/false based on truthiness
852
- return;
853
- }
854
- if (typeof (value) !== typeofTarget && (!allowNull || (allowNull && value !== null))) {
855
- let foundValue = `'${JSON.stringify(value)}'${(value && value.constructor && ` (${value.constructor.name})`) || ''}`;
856
- throw new EncodeSchemaError(`a '${typeofTarget}' was expected, but ${foundValue} was provided in ${klass.constructor.name}#${field}`);
857
- }
858
- }
859
- function assertInstanceType(value, type, klass, field) {
860
- if (!(value instanceof type)) {
861
- throw new EncodeSchemaError(`a '${type.name}' was expected, but '${value && value.constructor.name}' was provided in ${klass.constructor.name}#${field}`);
862
- }
863
- }
864
-
865
- function encodePrimitiveType(type, bytes, value, klass, field, it) {
866
- assertType(value, type, klass, field);
867
- const encodeFunc = encode[type];
868
- if (encodeFunc) {
869
- encodeFunc(bytes, value, it);
870
- // encodeFunc(bytes, value);
871
- }
872
- else {
873
- throw new EncodeSchemaError(`a '${type}' was expected, but ${value} was provided in ${klass.constructor.name}#${field}`);
866
+ function encodeValue(encoder, bytes,
867
+ // ref: Ref,
868
+ type, value,
869
+ // field: string | number,
870
+ operation, it) {
871
+ if (typeof (type) === "string") {
872
+ //
873
+ // Primitive values
874
+ //
875
+ // assertType(value, type as string, ref as Schema, field);
876
+ encode[type]?.(bytes, value, it);
874
877
  }
875
- }
876
- function encodeValue(encoder, bytes, ref, type, value, field, operation, it) {
877
- if (type[Symbol.metadata] !== undefined) {
878
- // TODO: move this to the `@type()` annotation
879
- assertInstanceType(value, type, ref, field);
878
+ else if (type[Symbol.metadata] !== undefined) {
879
+ // // TODO: move this to the `@type()` annotation
880
+ // assertInstanceType(value, type as typeof Schema, ref as Schema, field);
880
881
  //
881
882
  // Encode refId for this instance.
882
883
  // The actual instance is going to be encoded on next `changeTree` iteration.
@@ -887,21 +888,15 @@ function encodeValue(encoder, bytes, ref, type, value, field, operation, it) {
887
888
  encoder.tryEncodeTypeId(bytes, type, value.constructor, it);
888
889
  }
889
890
  }
890
- else if (typeof (type) === "string") {
891
- //
892
- // Primitive values
893
- //
894
- encodePrimitiveType(type, bytes, value, ref, field, it);
895
- }
896
891
  else {
897
- //
898
- // Custom type (MapSchema, ArraySchema, etc)
899
- //
900
- const definition = getType(Object.keys(type)[0]);
901
- //
902
- // ensure a ArraySchema has been provided
903
- //
904
- assertInstanceType(ref[field], definition.constructor, ref, field);
892
+ // //
893
+ // // Custom type (MapSchema, ArraySchema, etc)
894
+ // //
895
+ // const definition = getType(Object.keys(type)[0]);
896
+ // //
897
+ // // ensure a ArraySchema has been provided
898
+ // //
899
+ // assertInstanceType(ref[field], definition.constructor, ref as Schema, field);
905
900
  //
906
901
  // Encode refId for this instance.
907
902
  // The actual instance is going to be encoded on next `changeTree` iteration.
@@ -914,26 +909,27 @@ function encodeValue(encoder, bytes, ref, type, value, field, operation, it) {
914
909
  * @private
915
910
  */
916
911
  const encodeSchemaOperation = function (encoder, bytes, changeTree, index, operation, it) {
917
- const ref = changeTree.ref;
918
- const metadata = ref['constructor'][Symbol.metadata];
919
- const field = metadata[index];
920
- const type = metadata[field].type;
921
- const value = ref[field];
922
912
  // "compress" field index + operation
923
913
  bytes[it.offset++] = (index | operation) & 255;
924
914
  // Do not encode value for DELETE operations
925
915
  if (operation === exports.OPERATION.DELETE) {
926
916
  return;
927
917
  }
918
+ const ref = changeTree.ref;
919
+ const metadata = ref['constructor'][Symbol.metadata];
920
+ const field = metadata[index];
928
921
  // TODO: inline this function call small performance gain
929
- encodeValue(encoder, bytes, ref, type, value, field, operation, it);
922
+ encodeValue(encoder, bytes,
923
+ // ref,
924
+ metadata[index].type, ref[field.name],
925
+ // index,
926
+ operation, it);
930
927
  };
931
928
  /**
932
929
  * Used for collections (MapSchema, CollectionSchema, SetSchema)
933
930
  * @private
934
931
  */
935
- const encodeKeyValueOperation = function (encoder, bytes, changeTree, field, operation, it) {
936
- const ref = changeTree.ref;
932
+ const encodeKeyValueOperation = function (encoder, bytes, changeTree, index, operation, it) {
937
933
  // encode operation
938
934
  bytes[it.offset++] = operation & 255;
939
935
  // custom operations
@@ -941,11 +937,12 @@ const encodeKeyValueOperation = function (encoder, bytes, changeTree, field, ope
941
937
  return;
942
938
  }
943
939
  // encode index
944
- number$1(bytes, field, it);
940
+ number$1(bytes, index, it);
945
941
  // Do not encode value for DELETE operations
946
942
  if (operation === exports.OPERATION.DELETE) {
947
943
  return;
948
944
  }
945
+ const ref = changeTree.ref;
949
946
  //
950
947
  // encode "alias" for dynamic fields (maps)
951
948
  //
@@ -954,12 +951,12 @@ const encodeKeyValueOperation = function (encoder, bytes, changeTree, field, ope
954
951
  //
955
952
  // MapSchema dynamic key
956
953
  //
957
- const dynamicIndex = changeTree.ref['$indexes'].get(field);
954
+ const dynamicIndex = changeTree.ref['$indexes'].get(index);
958
955
  string$1(bytes, dynamicIndex, it);
959
956
  }
960
957
  }
961
- const type = changeTree.getType(field);
962
- const value = changeTree.getValue(field);
958
+ const type = changeTree.getType(index);
959
+ const value = changeTree.getValue(index);
963
960
  // try { throw new Error(); } catch (e) {
964
961
  // // only print if not coming from Reflection.ts
965
962
  // if (!e.stack.includes("src/Reflection.ts")) {
@@ -973,7 +970,11 @@ const encodeKeyValueOperation = function (encoder, bytes, changeTree, field, ope
973
970
  // }
974
971
  // }
975
972
  // TODO: inline this function call small performance gain
976
- encodeValue(encoder, bytes, ref, type, value, field, operation, it);
973
+ encodeValue(encoder, bytes,
974
+ // ref,
975
+ type, value,
976
+ // index,
977
+ operation, it);
977
978
  };
978
979
  /**
979
980
  * Used for collections (MapSchema, ArraySchema, etc.)
@@ -1017,7 +1018,11 @@ const encodeArray = function (encoder, bytes, changeTree, field, operation, it,
1017
1018
  // items: ref.toJSON(),
1018
1019
  // });
1019
1020
  // TODO: inline this function call small performance gain
1020
- encodeValue(encoder, bytes, ref, type, value, field, operation, it);
1021
+ encodeValue(encoder, bytes,
1022
+ // ref,
1023
+ type, value,
1024
+ // field,
1025
+ operation, it);
1021
1026
  };
1022
1027
 
1023
1028
  /**
@@ -1374,7 +1379,7 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
1374
1379
  }
1375
1380
  const decodeSchemaOperation = function (decoder, bytes, it, ref, allChanges) {
1376
1381
  const first_byte = bytes[it.offset++];
1377
- const metadata = ref['constructor'][Symbol.metadata];
1382
+ const metadata = ref.constructor[Symbol.metadata];
1378
1383
  // "compressed" index + operation
1379
1384
  const operation = (first_byte >> 6) << 6;
1380
1385
  const index = first_byte % (operation || 255);
@@ -1384,9 +1389,9 @@ const decodeSchemaOperation = function (decoder, bytes, it, ref, allChanges) {
1384
1389
  console.warn("@colyseus/schema: field not defined at", { index, ref: ref.constructor.name, metadata });
1385
1390
  return DEFINITION_MISMATCH;
1386
1391
  }
1387
- const { value, previousValue } = decodeValue(decoder, operation, ref, index, metadata[field].type, bytes, it, allChanges);
1392
+ const { value, previousValue } = decodeValue(decoder, operation, ref, index, field.type, bytes, it, allChanges);
1388
1393
  if (value !== null && value !== undefined) {
1389
- ref[field] = value;
1394
+ ref[field.name] = value;
1390
1395
  }
1391
1396
  // add change
1392
1397
  if (previousValue !== value) {
@@ -1394,7 +1399,7 @@ const decodeSchemaOperation = function (decoder, bytes, it, ref, allChanges) {
1394
1399
  ref,
1395
1400
  refId: decoder.currentRefId,
1396
1401
  op: operation,
1397
- field: field,
1402
+ field: field.name,
1398
1403
  value,
1399
1404
  previousValue,
1400
1405
  });
@@ -1492,7 +1497,6 @@ const decodeArray = function (decoder, bytes, it, ref, allChanges) {
1492
1497
  return;
1493
1498
  }
1494
1499
  else if (operation === exports.OPERATION.ADD_BY_REFID) {
1495
- // operation = OPERATION.ADD;
1496
1500
  const refId = number(bytes, it);
1497
1501
  const itemByRefId = decoder.root.refs.get(refId);
1498
1502
  // use existing index, or push new value
@@ -1526,6 +1530,47 @@ const decodeArray = function (decoder, bytes, it, ref, allChanges) {
1526
1530
  }
1527
1531
  };
1528
1532
 
1533
+ class EncodeSchemaError extends Error {
1534
+ }
1535
+ function assertType(value, type, klass, field) {
1536
+ let typeofTarget;
1537
+ let allowNull = false;
1538
+ switch (type) {
1539
+ case "number":
1540
+ case "int8":
1541
+ case "uint8":
1542
+ case "int16":
1543
+ case "uint16":
1544
+ case "int32":
1545
+ case "uint32":
1546
+ case "int64":
1547
+ case "uint64":
1548
+ case "float32":
1549
+ case "float64":
1550
+ typeofTarget = "number";
1551
+ if (isNaN(value)) {
1552
+ console.log(`trying to encode "NaN" in ${klass.constructor.name}#${field}`);
1553
+ }
1554
+ break;
1555
+ case "string":
1556
+ typeofTarget = "string";
1557
+ allowNull = true;
1558
+ break;
1559
+ case "boolean":
1560
+ // boolean is always encoded as true/false based on truthiness
1561
+ return;
1562
+ }
1563
+ if (typeof (value) !== typeofTarget && (!allowNull || (allowNull && value !== null))) {
1564
+ let foundValue = `'${JSON.stringify(value)}'${(value && value.constructor && ` (${value.constructor.name})`) || ''}`;
1565
+ throw new EncodeSchemaError(`a '${typeofTarget}' was expected, but ${foundValue} was provided in ${klass.constructor.name}#${field}`);
1566
+ }
1567
+ }
1568
+ function assertInstanceType(value, type, instance, field) {
1569
+ if (!(value instanceof type)) {
1570
+ throw new EncodeSchemaError(`a '${type.name}' was expected, but '${value && value.constructor.name}' was provided in ${instance.constructor.name}#${field}`);
1571
+ }
1572
+ }
1573
+
1529
1574
  var _a$4, _b$4;
1530
1575
  const DEFAULT_SORT = (a, b) => {
1531
1576
  const A = a.toString();
@@ -1591,6 +1636,7 @@ class ArraySchema {
1591
1636
  }
1592
1637
  else {
1593
1638
  if (setValue[$changes]) {
1639
+ assertInstanceType(setValue, obj[$childType], obj, key);
1594
1640
  if (obj.items[key] !== undefined) {
1595
1641
  if (setValue[$changes][$isNew]) {
1596
1642
  this[$changes].indexedOperation(Number(key), exports.OPERATION.MOVE_AND_ADD);
@@ -1637,6 +1683,7 @@ class ArraySchema {
1637
1683
  }
1638
1684
  });
1639
1685
  this[$changes] = new ChangeTree(proxy);
1686
+ this[$changes].indexes = {};
1640
1687
  this.push.apply(this, items);
1641
1688
  return proxy;
1642
1689
  }
@@ -1661,6 +1708,9 @@ class ArraySchema {
1661
1708
  if (value === undefined || value === null) {
1662
1709
  return;
1663
1710
  }
1711
+ else if (typeof (value) === "object" && this[$childType]) {
1712
+ assertInstanceType(value, this[$childType], this, i);
1713
+ }
1664
1714
  const changeTree = this[$changes];
1665
1715
  changeTree.indexedOperation(length, exports.OPERATION.ADD, this.items.length);
1666
1716
  this.items.push(value);
@@ -2188,6 +2238,7 @@ class MapSchema {
2188
2238
  this.$items = new Map();
2189
2239
  this.$indexes = new Map();
2190
2240
  this[$changes] = new ChangeTree(this);
2241
+ this[$changes].indexes = {};
2191
2242
  if (initialValues) {
2192
2243
  if (initialValues instanceof Map ||
2193
2244
  initialValues instanceof MapSchema) {
@@ -2214,6 +2265,9 @@ class MapSchema {
2214
2265
  if (value === undefined || value === null) {
2215
2266
  throw new Error(`MapSchema#set('${key}', ${value}): trying to set ${value} value on '${key}'.`);
2216
2267
  }
2268
+ else if (typeof (value) === "object" && this[$childType]) {
2269
+ assertInstanceType(value, this[$childType], this, key);
2270
+ }
2217
2271
  // Force "key" as string
2218
2272
  // See: https://github.com/colyseus/colyseus/issues/561#issuecomment-1646733468
2219
2273
  key = key.toString();
@@ -2419,27 +2473,20 @@ class TypeContext {
2419
2473
  if (parentFieldViewTag !== undefined) {
2420
2474
  this.parentFiltered[`${this.schemas.get(klass)}-${parentIndex}`] = true;
2421
2475
  }
2422
- for (const field in metadata) {
2423
- // //
2424
- // // Modify the field's metadata to include the parent field's view tag
2425
- // //
2426
- // if (
2427
- // parentFieldViewTag !== undefined &&
2428
- // metadata[field].tag === undefined
2429
- // ) {
2430
- // metadata[field].tag = parentFieldViewTag;
2431
- // }
2432
- const fieldType = metadata[field].type;
2433
- const viewTag = metadata[field].tag;
2476
+ for (const fieldIndex in metadata) {
2477
+ const index = fieldIndex;
2478
+ const fieldType = metadata[index].type;
2479
+ const viewTag = metadata[index].tag;
2434
2480
  if (typeof (fieldType) === "string") {
2435
2481
  continue;
2436
2482
  }
2437
2483
  if (Array.isArray(fieldType)) {
2438
2484
  const type = fieldType[0];
2485
+ // skip primitive types
2439
2486
  if (type === "string") {
2440
2487
  continue;
2441
2488
  }
2442
- this.discoverTypes(type, metadata[field].index, viewTag);
2489
+ this.discoverTypes(type, index, viewTag);
2443
2490
  }
2444
2491
  else if (typeof (fieldType) === "function") {
2445
2492
  this.discoverTypes(fieldType, viewTag);
@@ -2450,7 +2497,7 @@ class TypeContext {
2450
2497
  if (typeof (type) === "string") {
2451
2498
  continue;
2452
2499
  }
2453
- this.discoverTypes(type, metadata[field].index, viewTag);
2500
+ this.discoverTypes(type, index, viewTag);
2454
2501
  }
2455
2502
  }
2456
2503
  }
@@ -2606,17 +2653,18 @@ function view(tag = DEFAULT_VIEW_TAG) {
2606
2653
  const parentMetadata = parentClass[Symbol.metadata];
2607
2654
  // TODO: use Metadata.initialize()
2608
2655
  const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
2609
- if (!metadata[fieldName]) {
2610
- //
2611
- // detect index for this field, considering inheritance
2612
- //
2613
- metadata[fieldName] = {
2614
- type: undefined,
2615
- index: (metadata[-1] // current structure already has fields defined
2616
- ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
2617
- ?? -1) + 1 // no fields defined
2618
- };
2619
- }
2656
+ // const fieldIndex = metadata[fieldName];
2657
+ // if (!metadata[fieldIndex]) {
2658
+ // //
2659
+ // // detect index for this field, considering inheritance
2660
+ // //
2661
+ // metadata[fieldIndex] = {
2662
+ // type: undefined,
2663
+ // index: (metadata[-1] // current structure already has fields defined
2664
+ // ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
2665
+ // ?? -1) + 1 // no fields defined
2666
+ // }
2667
+ // }
2620
2668
  Metadata.setTag(metadata, fieldName, tag);
2621
2669
  };
2622
2670
  }
@@ -2631,16 +2679,16 @@ function type(type, options) {
2631
2679
  const parentClass = Object.getPrototypeOf(constructor);
2632
2680
  const parentMetadata = parentClass && parentClass[Symbol.metadata];
2633
2681
  const metadata = Metadata.initialize(constructor, parentMetadata);
2634
- let fieldIndex;
2682
+ let fieldIndex = metadata[field];
2635
2683
  /**
2636
2684
  * skip if descriptor already exists for this field (`@deprecated()`)
2637
2685
  */
2638
- if (metadata[field]) {
2639
- if (metadata[field].deprecated) {
2686
+ if (metadata[fieldIndex]) {
2687
+ if (metadata[fieldIndex].deprecated) {
2640
2688
  // do not create accessors for deprecated properties.
2641
2689
  return;
2642
2690
  }
2643
- else if (metadata[field].descriptor !== undefined) {
2691
+ else if (metadata[fieldIndex].type !== undefined) {
2644
2692
  // trying to define same property multiple times across inheritance.
2645
2693
  // https://github.com/colyseus/colyseus-unity3d/issues/131#issuecomment-814308572
2646
2694
  try {
@@ -2651,9 +2699,6 @@ function type(type, options) {
2651
2699
  throw new Error(`${e.message} ${definitionAtLine}`);
2652
2700
  }
2653
2701
  }
2654
- else {
2655
- fieldIndex = metadata[field].index;
2656
- }
2657
2702
  }
2658
2703
  else {
2659
2704
  //
@@ -2679,11 +2724,11 @@ function type(type, options) {
2679
2724
  const childType = (complexTypeKlass)
2680
2725
  ? Object.values(type)[0]
2681
2726
  : type;
2682
- Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass, metadata, field));
2727
+ Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass));
2683
2728
  }
2684
2729
  };
2685
2730
  }
2686
- function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass, metadata, field) {
2731
+ function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass) {
2687
2732
  return {
2688
2733
  get: function () { return this[fieldCached]; },
2689
2734
  set: function (value) {
@@ -2705,22 +2750,27 @@ function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass,
2705
2750
  }
2706
2751
  value[$childType] = type;
2707
2752
  }
2753
+ else if (typeof (type) !== "string") {
2754
+ assertInstanceType(value, type, this, fieldCached.substring(1));
2755
+ }
2756
+ else {
2757
+ assertType(value, type, this, fieldCached.substring(1));
2758
+ }
2759
+ const changeTree = this[$changes];
2708
2760
  //
2709
2761
  // Replacing existing "ref", remove it from root.
2710
2762
  // TODO: if there are other references to this instance, we should not remove it from root.
2711
2763
  //
2712
2764
  if (previousValue !== undefined && previousValue[$changes]) {
2713
- this[$changes].root?.remove(previousValue[$changes]);
2765
+ changeTree.root?.remove(previousValue[$changes]);
2714
2766
  }
2715
2767
  // flag the change for encoding.
2716
- this.constructor[$track](this[$changes], fieldIndex, exports.OPERATION.ADD);
2768
+ this.constructor[$track](changeTree, fieldIndex, exports.OPERATION.ADD);
2717
2769
  //
2718
2770
  // call setParent() recursively for this and its child
2719
2771
  // structures.
2720
2772
  //
2721
- if (value[$changes]) {
2722
- value[$changes].setParent(this, this[$changes].root, metadata[field].index);
2723
- }
2773
+ value[$changes]?.setParent(this, changeTree.root, fieldIndex);
2724
2774
  }
2725
2775
  else if (previousValue !== undefined) {
2726
2776
  //
@@ -2747,20 +2797,22 @@ function deprecated(throws = true) {
2747
2797
  const parentClass = Object.getPrototypeOf(constructor);
2748
2798
  const parentMetadata = parentClass[Symbol.metadata];
2749
2799
  const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
2750
- if (!metadata[field]) {
2751
- //
2752
- // detect index for this field, considering inheritance
2753
- //
2754
- metadata[field] = {
2755
- type: undefined,
2756
- index: (metadata[-1] // current structure already has fields defined
2757
- ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
2758
- ?? -1) + 1 // no fields defined
2759
- };
2760
- }
2761
- metadata[field].deprecated = true;
2800
+ const fieldIndex = metadata[field];
2801
+ // if (!metadata[field]) {
2802
+ // //
2803
+ // // detect index for this field, considering inheritance
2804
+ // //
2805
+ // metadata[field] = {
2806
+ // type: undefined,
2807
+ // index: (metadata[-1] // current structure already has fields defined
2808
+ // ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
2809
+ // ?? -1) + 1 // no fields defined
2810
+ // }
2811
+ // }
2812
+ metadata[fieldIndex].deprecated = true;
2762
2813
  if (throws) {
2763
- metadata[field].descriptor = {
2814
+ metadata[$descriptors] ??= {};
2815
+ metadata[$descriptors][field] = {
2764
2816
  get: function () { throw new Error(`${field} is deprecated.`); },
2765
2817
  set: function (value) { },
2766
2818
  enumerable: false,
@@ -2768,8 +2820,8 @@ function deprecated(throws = true) {
2768
2820
  };
2769
2821
  }
2770
2822
  // flag metadata[field] as non-enumerable
2771
- Object.defineProperty(metadata, field, {
2772
- value: metadata[field],
2823
+ Object.defineProperty(metadata, fieldIndex, {
2824
+ value: metadata[fieldIndex],
2773
2825
  enumerable: false,
2774
2826
  configurable: true
2775
2827
  });
@@ -2835,35 +2887,7 @@ class Schema {
2835
2887
  enumerable: false,
2836
2888
  writable: true
2837
2889
  });
2838
- const metadata = instance.constructor[Symbol.metadata];
2839
- // Define property descriptors
2840
- for (const field in metadata) {
2841
- if (metadata[field].descriptor) {
2842
- // for encoder
2843
- Object.defineProperty(instance, `_${field}`, {
2844
- value: undefined,
2845
- writable: true,
2846
- enumerable: false,
2847
- configurable: true,
2848
- });
2849
- Object.defineProperty(instance, field, metadata[field].descriptor);
2850
- }
2851
- else {
2852
- // for decoder
2853
- Object.defineProperty(instance, field, {
2854
- value: undefined,
2855
- writable: true,
2856
- enumerable: true,
2857
- configurable: true,
2858
- });
2859
- }
2860
- // Object.defineProperty(instance, field, {
2861
- // ...instance.constructor[Symbol.metadata][field].descriptor
2862
- // });
2863
- // if (args[0]?.hasOwnProperty(field)) {
2864
- // instance[field] = args[0][field];
2865
- // }
2866
- }
2890
+ Object.defineProperties(instance, instance.constructor[Symbol.metadata]?.[$descriptors] || {});
2867
2891
  }
2868
2892
  static is(type) {
2869
2893
  return typeof (type[Symbol.metadata]) === "object";
@@ -2887,7 +2911,7 @@ class Schema {
2887
2911
  */
2888
2912
  static [$filter](ref, index, view) {
2889
2913
  const metadata = ref.constructor[Symbol.metadata];
2890
- const tag = metadata[metadata[index]].tag;
2914
+ const tag = metadata[index]?.tag;
2891
2915
  if (view === undefined) {
2892
2916
  // shared pass/encode: encode if doesn't have a tag
2893
2917
  return tag === undefined;
@@ -2908,12 +2932,21 @@ class Schema {
2908
2932
  }
2909
2933
  // allow inherited classes to have a constructor
2910
2934
  constructor(...args) {
2911
- Schema.initialize(this);
2935
+ //
2936
+ // inline
2937
+ // Schema.initialize(this);
2938
+ //
2939
+ Object.defineProperty(this, $changes, {
2940
+ value: new ChangeTree(this),
2941
+ enumerable: false,
2942
+ writable: true
2943
+ });
2944
+ Object.defineProperties(this, this.constructor[Symbol.metadata]?.[$descriptors] || {});
2912
2945
  //
2913
2946
  // Assign initial values
2914
2947
  //
2915
2948
  if (args[0]) {
2916
- this.assign(args[0]);
2949
+ Object.assign(this, args[0]);
2917
2950
  }
2918
2951
  }
2919
2952
  assign(props) {
@@ -2927,7 +2960,8 @@ class Schema {
2927
2960
  * @param operation OPERATION to perform (detected automatically)
2928
2961
  */
2929
2962
  setDirty(property, operation) {
2930
- this[$changes].change(this.constructor[Symbol.metadata][property].index, operation);
2963
+ const metadata = this.constructor[Symbol.metadata];
2964
+ this[$changes].change(metadata[metadata[property]].index, operation);
2931
2965
  }
2932
2966
  clone() {
2933
2967
  const cloned = new (this.constructor);
@@ -2936,7 +2970,9 @@ class Schema {
2936
2970
  // TODO: clone all properties, not only annotated ones
2937
2971
  //
2938
2972
  // for (const field in this) {
2939
- for (const field in metadata) {
2973
+ for (const fieldIndex in metadata) {
2974
+ // const field = metadata[metadata[fieldIndex]].name;
2975
+ const field = metadata[fieldIndex].name;
2940
2976
  if (typeof (this[field]) === "object" &&
2941
2977
  typeof (this[field]?.clone) === "function") {
2942
2978
  // deep clone
@@ -2950,10 +2986,11 @@ class Schema {
2950
2986
  return cloned;
2951
2987
  }
2952
2988
  toJSON() {
2953
- const metadata = this.constructor[Symbol.metadata];
2954
2989
  const obj = {};
2955
- for (const fieldName in metadata) {
2956
- const field = metadata[fieldName];
2990
+ const metadata = this.constructor[Symbol.metadata];
2991
+ for (const index in metadata) {
2992
+ const field = metadata[index];
2993
+ const fieldName = field.name;
2957
2994
  if (!field.deprecated && this[fieldName] !== null && typeof (this[fieldName]) !== "undefined") {
2958
2995
  obj[fieldName] = (typeof (this[fieldName]['toJSON']) === "function")
2959
2996
  ? this[fieldName]['toJSON']()
@@ -2966,10 +3003,12 @@ class Schema {
2966
3003
  this[$changes].discardAll();
2967
3004
  }
2968
3005
  [$getByIndex](index) {
2969
- return this[this.constructor[Symbol.metadata][index]];
3006
+ const metadata = this.constructor[Symbol.metadata];
3007
+ return this[metadata[index].name];
2970
3008
  }
2971
3009
  [$deleteByIndex](index) {
2972
- this[this.constructor[Symbol.metadata][index]] = undefined;
3010
+ const metadata = this.constructor[Symbol.metadata];
3011
+ this[metadata[index].name] = undefined;
2973
3012
  }
2974
3013
  static debugRefIds(instance, jsonContents = true, level = 0) {
2975
3014
  const ref = instance;
@@ -3093,6 +3132,7 @@ class CollectionSchema {
3093
3132
  this.$indexes = new Map();
3094
3133
  this.$refId = 0;
3095
3134
  this[$changes] = new ChangeTree(this);
3135
+ this[$changes].indexes = {};
3096
3136
  if (initialValues) {
3097
3137
  initialValues.forEach((v) => this.add(v));
3098
3138
  }
@@ -3248,6 +3288,7 @@ class SetSchema {
3248
3288
  this.$indexes = new Map();
3249
3289
  this.$refId = 0;
3250
3290
  this[$changes] = new ChangeTree(this);
3291
+ this[$changes].indexes = {};
3251
3292
  if (initialValues) {
3252
3293
  initialValues.forEach((v) => this.add(v));
3253
3294
  }
@@ -3482,8 +3523,8 @@ class Encoder {
3482
3523
  ) {
3483
3524
  const hasView = (view !== undefined);
3484
3525
  const rootChangeTree = this.state[$changes];
3485
- const changeTreesIterator = changeTrees.entries();
3486
- for (const [changeTree, changes] of changeTreesIterator) {
3526
+ const shouldClearChanges = !isEncodeAll && !hasView;
3527
+ for (const [changeTree, changes] of changeTrees.entries()) {
3487
3528
  const ref = changeTree.ref;
3488
3529
  const ctor = ref['constructor'];
3489
3530
  const encoder = ctor[$encoder];
@@ -3535,6 +3576,9 @@ class Encoder {
3535
3576
  // }
3536
3577
  encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView);
3537
3578
  }
3579
+ // if (shouldClearChanges) {
3580
+ // changeTree.endEncode();
3581
+ // }
3538
3582
  }
3539
3583
  if (it.offset > buffer.byteLength) {
3540
3584
  const newSize = getNextPowerOf2(buffer.byteLength * 2);
@@ -3557,7 +3601,7 @@ class Encoder {
3557
3601
  //
3558
3602
  // only clear changes after making sure buffer resize is not required.
3559
3603
  //
3560
- if (!isEncodeAll && !hasView) {
3604
+ if (shouldClearChanges) {
3561
3605
  //
3562
3606
  // FIXME: avoid iterating over change trees twice.
3563
3607
  //
@@ -3641,6 +3685,11 @@ class Encoder {
3641
3685
  const changeTreesIterator = changeTrees.entries();
3642
3686
  for (const [changeTree, _] of changeTreesIterator) {
3643
3687
  changeTree.endEncode();
3688
+ // changeTree.changes.clear();
3689
+ // // ArraySchema and MapSchema have a custom "encode end" method
3690
+ // changeTree.ref[$onEncodeEnd]?.();
3691
+ // // Not a new instance anymore
3692
+ // delete changeTree[$isNew];
3644
3693
  }
3645
3694
  }
3646
3695
  discardChanges() {
@@ -3756,8 +3805,9 @@ class ReferenceTracker {
3756
3805
  // Ensure child schema instances have their references removed as well.
3757
3806
  //
3758
3807
  if (Metadata.isValidInstance(ref)) {
3759
- const metadata = ref['constructor'][Symbol.metadata];
3760
- for (const field in metadata) {
3808
+ const metadata = ref.constructor[Symbol.metadata];
3809
+ for (const index in metadata) {
3810
+ const field = metadata[index].name;
3761
3811
  const childRefId = typeof (ref[field]) === "object" && this.refIds.get(ref[field]);
3762
3812
  if (childRefId) {
3763
3813
  this.removeRef(childRefId);
@@ -3944,7 +3994,9 @@ class Reflection extends Schema {
3944
3994
  const reflection = new Reflection();
3945
3995
  const encoder = new Encoder(reflection);
3946
3996
  const buildType = (currentType, metadata) => {
3947
- for (const fieldName in metadata) {
3997
+ for (const fieldIndex in metadata) {
3998
+ const index = Number(fieldIndex);
3999
+ const fieldName = metadata[index].name;
3948
4000
  // skip fields from parent classes
3949
4001
  if (!Object.prototype.hasOwnProperty.call(metadata, fieldName)) {
3950
4002
  continue;
@@ -3952,7 +4004,7 @@ class Reflection extends Schema {
3952
4004
  const field = new ReflectionField();
3953
4005
  field.name = fieldName;
3954
4006
  let fieldType;
3955
- const type = metadata[fieldName].type;
4007
+ const type = metadata[index].type;
3956
4008
  if (typeof (type) === "string") {
3957
4009
  fieldType = type;
3958
4010
  }
@@ -4018,18 +4070,7 @@ class Reflection extends Schema {
4018
4070
  reflection.types.forEach((reflectionType) => {
4019
4071
  const schemaType = typeContext.get(reflectionType.id);
4020
4072
  const metadata = schemaType[Symbol.metadata];
4021
- // FIXME: use metadata[-1] to get field count
4022
4073
  const parentFieldIndex = 0;
4023
- // console.log("--------------------");
4024
- // // console.log("reflectionType", reflectionType.toJSON());
4025
- // console.log("reflectionType.fields", reflectionType.fields.toJSON());
4026
- // console.log("parentFieldIndex", parentFieldIndex);
4027
- //
4028
- // FIXME: set fields using parentKlass as well
4029
- // currently the fields are duplicated on inherited classes
4030
- //
4031
- // // const parentKlass = reflection.types[reflectionType.extendsId];
4032
- // // parentKlass.fields
4033
4074
  reflectionType.fields.forEach((field, i) => {
4034
4075
  const fieldIndex = parentFieldIndex + i;
4035
4076
  if (field.referencedType !== undefined) {
@@ -4194,7 +4235,7 @@ function getDecoderStateCallbacks(decoder) {
4194
4235
  //
4195
4236
  bindTo: function bindTo(targetObject, properties) {
4196
4237
  if (!properties) {
4197
- properties = Object.keys(metadata);
4238
+ properties = Object.keys(metadata).map((index) => metadata[index].name);
4198
4239
  }
4199
4240
  return $root.addCallback($root.refIds.get(context.instance), exports.OPERATION.REPLACE, () => {
4200
4241
  properties.forEach((prop) => targetObject[prop] = context.instance[prop]);
@@ -4202,7 +4243,8 @@ function getDecoderStateCallbacks(decoder) {
4202
4243
  }
4203
4244
  }, {
4204
4245
  get(target, prop) {
4205
- if (metadata[prop]) {
4246
+ const metadataField = metadata[metadata[prop]];
4247
+ if (metadataField) {
4206
4248
  const instance = context.instance?.[prop];
4207
4249
  const onInstanceAvailable = ((callback) => {
4208
4250
  const unbind = $(context.instance).listen(prop, (value, _) => {
@@ -4218,7 +4260,7 @@ function getDecoderStateCallbacks(decoder) {
4218
4260
  callback(instance, true);
4219
4261
  }
4220
4262
  });
4221
- return getProxy(metadata[prop].type, {
4263
+ return getProxy(metadataField.type, {
4222
4264
  // make sure refId is available, otherwise need to wait for the instance to be available.
4223
4265
  instance: ($root.refIds.get(instance) && instance),
4224
4266
  parentInstance: context.instance,
@@ -4331,7 +4373,7 @@ class StateView {
4331
4373
  console.warn("StateView#add(), invalid object:", obj);
4332
4374
  return this;
4333
4375
  }
4334
- // FIXME: ArraySchema/MapSchema does not have metadata
4376
+ // FIXME: ArraySchema/MapSchema do not have metadata
4335
4377
  const metadata = obj.constructor[Symbol.metadata];
4336
4378
  const changeTree = obj[$changes];
4337
4379
  this.items.add(changeTree);
@@ -4377,7 +4419,7 @@ class StateView {
4377
4419
  ? changeTree.allFilteredChanges
4378
4420
  : changeTree.allChanges;
4379
4421
  changeSet.forEach((op, index) => {
4380
- const tagAtIndex = metadata?.[metadata?.[index]].tag;
4422
+ const tagAtIndex = metadata?.[index].tag;
4381
4423
  if ((isInvisible || // if "invisible", include all
4382
4424
  tagAtIndex === undefined || // "all change" with no tag
4383
4425
  tagAtIndex === tag // tagged property
@@ -4390,7 +4432,7 @@ class StateView {
4390
4432
  // Add children of this ChangeTree to this view
4391
4433
  changeTree.forEachChild((change, index) => {
4392
4434
  // Do not ADD children that don't have the same tag
4393
- if (metadata && metadata[metadata[index]].tag !== tag) {
4435
+ if (metadata && metadata[index].tag !== tag) {
4394
4436
  return;
4395
4437
  }
4396
4438
  this.add(change.ref, tag, false);