@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
package/src/Schema.ts CHANGED
@@ -4,7 +4,7 @@ import { DEFAULT_VIEW_TAG, DefinitionType } from "./annotations";
4
4
  import { NonFunctionPropNames, ToJSON } from './types/HelperTypes';
5
5
 
6
6
  import { ChangeTree, Ref } from './encoder/ChangeTree';
7
- import { $changes, $decoder, $deleteByIndex, $encoder, $filter, $getByIndex, $track } from './types/symbols';
7
+ import { $changes, $decoder, $deleteByIndex, $descriptors, $encoder, $filter, $getByIndex, $track } from './types/symbols';
8
8
  import { StateView } from './encoder/StateView';
9
9
 
10
10
  import { encodeSchemaOperation } from './encoder/EncodeOperation';
@@ -16,7 +16,6 @@ import { getIndent } from './utils';
16
16
  * Schema encoder / decoder
17
17
  */
18
18
  export abstract class Schema {
19
-
20
19
  static [$encoder] = encodeSchemaOperation;
21
20
  static [$decoder] = decodeSchemaOperation;
22
21
 
@@ -31,37 +30,7 @@ export abstract class Schema {
31
30
  writable: true
32
31
  });
33
32
 
34
- const metadata = instance.constructor[Symbol.metadata];
35
-
36
- // Define property descriptors
37
- for (const field in metadata) {
38
- if (metadata[field].descriptor) {
39
- // for encoder
40
- Object.defineProperty(instance, `_${field}`, {
41
- value: undefined,
42
- writable: true,
43
- enumerable: false,
44
- configurable: true,
45
- });
46
- Object.defineProperty(instance, field, metadata[field].descriptor);
47
-
48
- } else {
49
- // for decoder
50
- Object.defineProperty(instance, field, {
51
- value: undefined,
52
- writable: true,
53
- enumerable: true,
54
- configurable: true,
55
- });
56
- }
57
-
58
- // Object.defineProperty(instance, field, {
59
- // ...instance.constructor[Symbol.metadata][field].descriptor
60
- // });
61
- // if (args[0]?.hasOwnProperty(field)) {
62
- // instance[field] = args[0][field];
63
- // }
64
- }
33
+ Object.defineProperties(instance, instance.constructor[Symbol.metadata]?.[$descriptors] || {});
65
34
  }
66
35
 
67
36
  static is(type: DefinitionType) {
@@ -88,7 +57,7 @@ export abstract class Schema {
88
57
  */
89
58
  static [$filter] (ref: Schema, index: number, view: StateView) {
90
59
  const metadata: Metadata = ref.constructor[Symbol.metadata];
91
- const tag = metadata[metadata[index]].tag;
60
+ const tag = metadata[index]?.tag;
92
61
 
93
62
  if (view === undefined) {
94
63
  // shared pass/encode: encode if doesn't have a tag
@@ -111,13 +80,24 @@ export abstract class Schema {
111
80
 
112
81
  // allow inherited classes to have a constructor
113
82
  constructor(...args: any[]) {
114
- Schema.initialize(this);
83
+ //
84
+ // inline
85
+ // Schema.initialize(this);
86
+ //
87
+
88
+ Object.defineProperty(this, $changes, {
89
+ value: new ChangeTree(this),
90
+ enumerable: false,
91
+ writable: true
92
+ });
93
+
94
+ Object.defineProperties(this, this.constructor[Symbol.metadata]?.[$descriptors] || {});
115
95
 
116
96
  //
117
97
  // Assign initial values
118
98
  //
119
99
  if (args[0]) {
120
- this.assign(args[0]);
100
+ Object.assign(this, args[0]);
121
101
  }
122
102
  }
123
103
 
@@ -135,21 +115,25 @@ export abstract class Schema {
135
115
  * @param operation OPERATION to perform (detected automatically)
136
116
  */
137
117
  public setDirty<K extends NonFunctionPropNames<this>>(property: K | number, operation?: OPERATION) {
118
+ const metadata: Metadata = this.constructor[Symbol.metadata];
138
119
  this[$changes].change(
139
- this.constructor[Symbol.metadata][property as string].index,
120
+ metadata[metadata[property as string]].index,
140
121
  operation
141
122
  );
142
123
  }
143
124
 
144
125
  clone (): this {
145
126
  const cloned = new ((this as any).constructor);
146
- const metadata = this.constructor[Symbol.metadata];
127
+ const metadata: Metadata = this.constructor[Symbol.metadata];
147
128
 
148
129
  //
149
130
  // TODO: clone all properties, not only annotated ones
150
131
  //
151
132
  // for (const field in this) {
152
- for (const field in metadata) {
133
+ for (const fieldIndex in metadata) {
134
+ // const field = metadata[metadata[fieldIndex]].name;
135
+ const field = metadata[fieldIndex as any as number].name;
136
+
153
137
  if (
154
138
  typeof (this[field]) === "object" &&
155
139
  typeof (this[field]?.clone) === "function"
@@ -162,15 +146,17 @@ export abstract class Schema {
162
146
  cloned[field] = this[field];
163
147
  }
164
148
  }
149
+
165
150
  return cloned;
166
151
  }
167
152
 
168
153
  toJSON () {
169
- const metadata = this.constructor[Symbol.metadata];
170
-
171
154
  const obj: unknown = {};
172
- for (const fieldName in metadata) {
173
- const field = metadata[fieldName];
155
+
156
+ const metadata = this.constructor[Symbol.metadata];
157
+ for (const index in metadata) {
158
+ const field = metadata[index];
159
+ const fieldName = field.name;
174
160
  if (!field.deprecated && this[fieldName] !== null && typeof (this[fieldName]) !== "undefined") {
175
161
  obj[fieldName] = (typeof (this[fieldName]['toJSON']) === "function")
176
162
  ? this[fieldName]['toJSON']()
@@ -185,11 +171,13 @@ export abstract class Schema {
185
171
  }
186
172
 
187
173
  protected [$getByIndex](index: number) {
188
- return this[this.constructor[Symbol.metadata][index]];
174
+ const metadata: Metadata = this.constructor[Symbol.metadata];
175
+ return this[metadata[index].name];
189
176
  }
190
177
 
191
178
  protected [$deleteByIndex](index: number) {
192
- this[this.constructor[Symbol.metadata][index]] = undefined;
179
+ const metadata: Metadata = this.constructor[Symbol.metadata];
180
+ this[metadata[index].name] = undefined;
193
181
  }
194
182
 
195
183
  static debugRefIds(instance: Ref, jsonContents: boolean = true, level: number = 0) {
@@ -3,10 +3,12 @@ import { Schema } from './Schema';
3
3
  import { ArraySchema } from './types/custom/ArraySchema';
4
4
  import { MapSchema } from './types/custom/MapSchema';
5
5
  import { Metadata } from "./Metadata";
6
- import { $changes, $childType, $track } from "./types/symbols";
6
+ import { $changes, $childType, $descriptors, $track } from "./types/symbols";
7
7
  import { TypeDefinition, getType } from "./types/registry";
8
8
  import { OPERATION } from "./encoding/spec";
9
9
  import { TypeContext } from "./types/TypeContext";
10
+ import { assertInstanceType, assertType } from "./encoding/assert";
11
+ import type { Ref } from "./encoder/ChangeTree";
10
12
 
11
13
  /**
12
14
  * Data types
@@ -227,18 +229,19 @@ export function view<T> (tag: number = DEFAULT_VIEW_TAG) {
227
229
 
228
230
  // TODO: use Metadata.initialize()
229
231
  const metadata: Metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
230
-
231
- if (!metadata[fieldName]) {
232
- //
233
- // detect index for this field, considering inheritance
234
- //
235
- metadata[fieldName] = {
236
- type: undefined,
237
- index: (metadata[-1] // current structure already has fields defined
238
- ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
239
- ?? -1) + 1 // no fields defined
240
- }
241
- }
232
+ // const fieldIndex = metadata[fieldName];
233
+
234
+ // if (!metadata[fieldIndex]) {
235
+ // //
236
+ // // detect index for this field, considering inheritance
237
+ // //
238
+ // metadata[fieldIndex] = {
239
+ // type: undefined,
240
+ // index: (metadata[-1] // current structure already has fields defined
241
+ // ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
242
+ // ?? -1) + 1 // no fields defined
243
+ // }
244
+ // }
242
245
 
243
246
  Metadata.setTag(metadata, fieldName, tag);
244
247
  }
@@ -256,20 +259,20 @@ export function unreliable<T> (target: T, field: string) {
256
259
  // TODO: use Metadata.initialize()
257
260
  const metadata: Metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
258
261
 
259
- if (!metadata[field]) {
260
- //
261
- // detect index for this field, considering inheritance
262
- //
263
- metadata[field] = {
264
- type: undefined,
265
- index: (metadata[-1] // current structure already has fields defined
266
- ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
267
- ?? -1) + 1 // no fields defined
268
- }
269
- }
262
+ // if (!metadata[field]) {
263
+ // //
264
+ // // detect index for this field, considering inheritance
265
+ // //
266
+ // metadata[field] = {
267
+ // type: undefined,
268
+ // index: (metadata[-1] // current structure already has fields defined
269
+ // ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
270
+ // ?? -1) + 1 // no fields defined
271
+ // }
272
+ // }
270
273
 
271
274
  // add owned flag to the field
272
- metadata[field].unreliable = true;
275
+ metadata[metadata[field]].unreliable = true;
273
276
  }
274
277
 
275
278
  export function type (
@@ -290,17 +293,17 @@ export function type (
290
293
  const parentMetadata = parentClass && parentClass[Symbol.metadata];
291
294
  const metadata = Metadata.initialize(constructor, parentMetadata);
292
295
 
293
- let fieldIndex: number;
296
+ let fieldIndex: number = metadata[field];
294
297
 
295
298
  /**
296
299
  * skip if descriptor already exists for this field (`@deprecated()`)
297
300
  */
298
- if (metadata[field]) {
299
- if (metadata[field].deprecated) {
301
+ if (metadata[fieldIndex]) {
302
+ if (metadata[fieldIndex].deprecated) {
300
303
  // do not create accessors for deprecated properties.
301
304
  return;
302
305
 
303
- } else if (metadata[field].descriptor !== undefined) {
306
+ } else if (metadata[fieldIndex].type !== undefined) {
304
307
  // trying to define same property multiple times across inheritance.
305
308
  // https://github.com/colyseus/colyseus-unity3d/issues/131#issuecomment-814308572
306
309
  try {
@@ -310,9 +313,6 @@ export function type (
310
313
  const definitionAtLine = e.stack.split("\n")[4].trim();
311
314
  throw new Error(`${e.message} ${definitionAtLine}`);
312
315
  }
313
-
314
- } else {
315
- fieldIndex = metadata[field].index;
316
316
  }
317
317
 
318
318
  } else {
@@ -326,12 +326,18 @@ export function type (
326
326
  }
327
327
 
328
328
  if (options && options.manual) {
329
- Metadata.addField(metadata, fieldIndex, field, type, {
330
- // do not declare getter/setter descriptor
331
- enumerable: true,
332
- configurable: true,
333
- writable: true,
334
- });
329
+ Metadata.addField(
330
+ metadata,
331
+ fieldIndex,
332
+ field,
333
+ type,
334
+ {
335
+ // do not declare getter/setter descriptor
336
+ enumerable: true,
337
+ configurable: true,
338
+ writable: true,
339
+ }
340
+ );
335
341
 
336
342
  } else {
337
343
  const complexTypeKlass = (Array.isArray(type))
@@ -347,7 +353,7 @@ export function type (
347
353
  fieldIndex,
348
354
  field,
349
355
  type,
350
- getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass, metadata, field)
356
+ getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass)
351
357
  );
352
358
  }
353
359
  }
@@ -358,8 +364,6 @@ export function getPropertyDescriptor(
358
364
  fieldIndex: number,
359
365
  type: DefinitionType,
360
366
  complexTypeKlass: TypeDefinition,
361
- metadata: Metadata,
362
- field: string,
363
367
  ) {
364
368
  return {
365
369
  get: function () { return this[fieldCached]; },
@@ -385,30 +389,32 @@ export function getPropertyDescriptor(
385
389
  }
386
390
 
387
391
  value[$childType] = type;
392
+
393
+ } else if (typeof (type) !== "string") {
394
+ assertInstanceType(value, type as typeof Schema, this, fieldCached.substring(1));
395
+
396
+ } else {
397
+ assertType(value, type, this, fieldCached.substring(1));
388
398
  }
389
399
 
400
+ const changeTree = this[$changes];
401
+
390
402
  //
391
403
  // Replacing existing "ref", remove it from root.
392
404
  // TODO: if there are other references to this instance, we should not remove it from root.
393
405
  //
394
406
  if (previousValue !== undefined && previousValue[$changes]) {
395
- this[$changes].root?.remove(previousValue[$changes]);
407
+ changeTree.root?.remove(previousValue[$changes]);
396
408
  }
397
409
 
398
410
  // flag the change for encoding.
399
- this.constructor[$track](this[$changes], fieldIndex, OPERATION.ADD);
411
+ this.constructor[$track](changeTree, fieldIndex, OPERATION.ADD);
400
412
 
401
413
  //
402
414
  // call setParent() recursively for this and its child
403
415
  // structures.
404
416
  //
405
- if (value[$changes]) {
406
- value[$changes].setParent(
407
- this,
408
- this[$changes].root,
409
- metadata[field].index,
410
- );
411
- }
417
+ (value as Ref)[$changes]?.setParent(this, changeTree.root, fieldIndex);
412
418
 
413
419
  } else if (previousValue !== undefined) {
414
420
  //
@@ -440,23 +446,25 @@ export function deprecated(throws: boolean = true): PropertyDecorator {
440
446
  const parentClass = Object.getPrototypeOf(constructor);
441
447
  const parentMetadata = parentClass[Symbol.metadata];
442
448
  const metadata: Metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
443
-
444
- if (!metadata[field]) {
445
- //
446
- // detect index for this field, considering inheritance
447
- //
448
- metadata[field] = {
449
- type: undefined,
450
- index: (metadata[-1] // current structure already has fields defined
451
- ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
452
- ?? -1) + 1 // no fields defined
453
- }
454
- }
455
-
456
- metadata[field].deprecated = true;
449
+ const fieldIndex = metadata[field];
450
+
451
+ // if (!metadata[field]) {
452
+ // //
453
+ // // detect index for this field, considering inheritance
454
+ // //
455
+ // metadata[field] = {
456
+ // type: undefined,
457
+ // index: (metadata[-1] // current structure already has fields defined
458
+ // ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
459
+ // ?? -1) + 1 // no fields defined
460
+ // }
461
+ // }
462
+
463
+ metadata[fieldIndex].deprecated = true;
457
464
 
458
465
  if (throws) {
459
- metadata[field].descriptor = {
466
+ metadata[$descriptors] ??= {};
467
+ metadata[$descriptors][field] = {
460
468
  get: function () { throw new Error(`${field} is deprecated.`); },
461
469
  set: function (this: Schema, value: any) { /* throw new Error(`${field} is deprecated.`); */ },
462
470
  enumerable: false,
@@ -465,8 +473,8 @@ export function deprecated(throws: boolean = true): PropertyDecorator {
465
473
  }
466
474
 
467
475
  // flag metadata[field] as non-enumerable
468
- Object.defineProperty(metadata, field, {
469
- value: metadata[field],
476
+ Object.defineProperty(metadata, fieldIndex, {
477
+ value: metadata[fieldIndex],
470
478
  enumerable: false,
471
479
  configurable: true
472
480
  });
@@ -31,37 +31,40 @@ class State extends Schema {
31
31
 
32
32
  const state = new State();
33
33
 
34
- for (let i = 0; i < 50; i++) {
35
- const player = new Player();
36
- state.players.set(`p-${nanoid()}`, player);
37
-
38
- player.position.x = (i + 1) * 100;
39
- player.position.y = (i + 1) * 100;
40
- for (let j = 0; j < 10; j++) {
41
- const item = new Item();
42
- item.price = (i + 1) * 50;
43
- for (let k = 0; k < 5; k++) {
44
- const attr = new Attribute();
45
- attr.name = `Attribute ${k}`;
46
- attr.value = k;
47
- item.attributes.push(attr);
34
+ Encoder.BUFFER_SIZE = 4096 * 4096;
35
+ const encoder = new Encoder(state);
48
36
 
49
- }
50
- player.items.set(`item-${j}`, item);
51
- }
52
- }
53
37
 
38
+ let now = Date.now();
39
+
40
+ // for (let i = 0; i < 10000; i++) {
41
+ // const player = new Player();
42
+ // state.players.set(`p-${nanoid()}`, player);
43
+ //
44
+ // player.position.x = (i + 1) * 100;
45
+ // player.position.y = (i + 1) * 100;
46
+ // for (let j = 0; j < 10; j++) {
47
+ // const item = new Item();
48
+ // player.items.set(`item-${j}`, item);
49
+ // item.price = (i + 1) * 50;
50
+ // for (let k = 0; k < 5; k++) {
51
+ // const attr = new Attribute();
52
+ // attr.name = `Attribute ${k}`;
53
+ // attr.value = k;
54
+ // item.attributes.push(attr);
55
+ // }
56
+ // }
57
+ // }
58
+ // console.log("time to make changes:", Date.now() - now);
54
59
 
55
- Encoder.BUFFER_SIZE = 4096 * 4096;
56
- const encoder = new Encoder(state);
57
60
 
58
61
  // measure time to .encodeAll()
59
62
 
60
- let now = Date.now();
61
- for (let i = 0; i < 1000; i++) {
62
- encoder.encodeAll();
63
- }
64
- console.log(Date.now() - now);
63
+ now = Date.now();
64
+ // for (let i = 0; i < 1000; i++) {
65
+ // encoder.encodeAll();
66
+ // }
67
+ // console.log(Date.now() - now);
65
68
 
66
69
  const allEncodes = Date.now();
67
70
  for (let i = 0; i < 100; i++) {
@@ -80,7 +83,6 @@ for (let i = 0; i < 100; i++) {
80
83
  attr.name = `Attribute ${l}`;
81
84
  attr.value = l;
82
85
  item.attributes.push(attr);
83
-
84
86
  }
85
87
  player.items.set(`item-${k}`, item);
86
88
  }
@@ -94,4 +96,4 @@ for (let i = 0; i < 100; i++) {
94
96
  }
95
97
  console.log("time for all encodes:", Date.now() - allEncodes);
96
98
 
97
- console.log(Array.from(encoder.encodeAll()).length, "bytes");
99
+ console.log(Array.from(encoder.encodeAll()).length, "bytes");
@@ -105,7 +105,14 @@ export function decodeValue(
105
105
  value = decoder.createInstanceOfType(childType);
106
106
  }
107
107
 
108
- $root.addRef(refId, value, (value !== previousValue));
108
+ $root.addRef(
109
+ refId,
110
+ value,
111
+ (
112
+ value !== previousValue || // increment ref count if value has changed
113
+ (operation === OPERATION.DELETE_AND_ADD && value === previousValue) // increment ref count if the same instance is being added again
114
+ )
115
+ );
109
116
  }
110
117
 
111
118
 
@@ -174,7 +181,7 @@ export const decodeSchemaOperation: DecodeOperation = function (
174
181
  allChanges: DataChange[],
175
182
  ) {
176
183
  const first_byte = bytes[it.offset++];
177
- const metadata: Metadata = ref['constructor'][Symbol.metadata];
184
+ const metadata: Metadata = ref.constructor[Symbol.metadata];
178
185
 
179
186
  // "compressed" index + operation
180
187
  const operation = (first_byte >> 6) << 6
@@ -192,14 +199,14 @@ export const decodeSchemaOperation: DecodeOperation = function (
192
199
  operation,
193
200
  ref,
194
201
  index,
195
- metadata[field].type,
202
+ field.type,
196
203
  bytes,
197
204
  it,
198
205
  allChanges,
199
206
  );
200
207
 
201
208
  if (value !== null && value !== undefined) {
202
- ref[field] = value;
209
+ ref[field.name] = value;
203
210
  }
204
211
 
205
212
  // add change
@@ -208,7 +215,7 @@ export const decodeSchemaOperation: DecodeOperation = function (
208
215
  ref,
209
216
  refId: decoder.currentRefId,
210
217
  op: operation,
211
- field: field,
218
+ field: field.name,
212
219
  value,
213
220
  previousValue,
214
221
  });
@@ -308,8 +315,6 @@ export const decodeArray: DecodeOperation = function (
308
315
  ) {
309
316
  // "uncompressed" index + operation (array/map items)
310
317
  let operation = bytes[it.offset++];
311
-
312
- let isSchemaChild: boolean;
313
318
  let index: number;
314
319
 
315
320
  if (operation === OPERATION.CLEAR) {
@@ -341,9 +346,6 @@ export const decodeArray: DecodeOperation = function (
341
346
  return;
342
347
 
343
348
  } else if (operation === OPERATION.ADD_BY_REFID) {
344
- isSchemaChild = true;
345
- // operation = OPERATION.ADD;
346
-
347
349
  const refId = decode.number(bytes, it);
348
350
  const itemByRefId = decoder.root.refs.get(refId);
349
351
 
@@ -353,7 +355,6 @@ export const decodeArray: DecodeOperation = function (
353
355
  : ref.length;
354
356
 
355
357
  } else {
356
- isSchemaChild = false;
357
358
  index = decode.number(bytes, it);
358
359
  }
359
360
 
@@ -98,8 +98,9 @@ export class ReferenceTracker {
98
98
  // Ensure child schema instances have their references removed as well.
99
99
  //
100
100
  if (Metadata.isValidInstance(ref)) {
101
- const metadata: Metadata = ref['constructor'][Symbol.metadata];
102
- for (const field in metadata) {
101
+ const metadata: Metadata = ref.constructor[Symbol.metadata];
102
+ for (const index in metadata) {
103
+ const field = metadata[index as any as number].name;
103
104
  const childRefId = typeof(ref[field]) === "object" && this.refIds.get(ref[field]);
104
105
  if (childRefId) {
105
106
  this.removeRef(childRefId);
@@ -264,7 +264,7 @@ export function getDecoderStateCallbacks<T extends Schema>(decoder: Decoder<T>):
264
264
  //
265
265
  bindTo: function bindTo(targetObject: any, properties?: string[]) {
266
266
  if (!properties) {
267
- properties = Object.keys(metadata);
267
+ properties = Object.keys(metadata).map((index) => metadata[index as any as number].name);
268
268
  }
269
269
  return $root.addCallback(
270
270
  $root.refIds.get(context.instance),
@@ -277,7 +277,8 @@ export function getDecoderStateCallbacks<T extends Schema>(decoder: Decoder<T>):
277
277
  }
278
278
  }, {
279
279
  get(target, prop: string) {
280
- if (metadata[prop]) {
280
+ const metadataField = metadata[metadata[prop]];
281
+ if (metadataField) {
281
282
  const instance = context.instance?.[prop];
282
283
  const onInstanceAvailable: OnInstanceAvailableCallback = (
283
284
  (callback: (ref: Ref, existing: boolean) => void) => {
@@ -297,7 +298,7 @@ export function getDecoderStateCallbacks<T extends Schema>(decoder: Decoder<T>):
297
298
  }
298
299
  }
299
300
  );
300
- return getProxy(metadata[prop].type, {
301
+ return getProxy(metadataField.type, {
301
302
  // make sure refId is available, otherwise need to wait for the instance to be available.
302
303
  instance: ($root.refIds.get(instance) && instance),
303
304
  parentInstance: context.instance,