@colyseus/schema 3.0.0-alpha.3 → 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 (134) hide show
  1. package/README.md +131 -61
  2. package/build/cjs/index.js +966 -563
  3. package/build/cjs/index.js.map +1 -1
  4. package/build/esm/index.mjs +965 -562
  5. package/build/esm/index.mjs.map +1 -1
  6. package/build/umd/index.js +966 -563
  7. package/lib/Metadata.d.ts +15 -4
  8. package/lib/Metadata.js +86 -18
  9. package/lib/Metadata.js.map +1 -1
  10. package/lib/Reflection.d.ts +2 -3
  11. package/lib/Reflection.js +24 -28
  12. package/lib/Reflection.js.map +1 -1
  13. package/lib/Schema.d.ts +2 -2
  14. package/lib/Schema.js +28 -41
  15. package/lib/Schema.js.map +1 -1
  16. package/lib/annotations.d.ts +1 -21
  17. package/lib/annotations.js +73 -153
  18. package/lib/annotations.js.map +1 -1
  19. package/lib/bench_encode.d.ts +1 -0
  20. package/lib/bench_encode.js +142 -0
  21. package/lib/bench_encode.js.map +1 -0
  22. package/lib/codegen/api.js +1 -2
  23. package/lib/codegen/api.js.map +1 -1
  24. package/lib/codegen/languages/cpp.js +1 -2
  25. package/lib/codegen/languages/cpp.js.map +1 -1
  26. package/lib/codegen/languages/csharp.js +1 -2
  27. package/lib/codegen/languages/csharp.js.map +1 -1
  28. package/lib/codegen/languages/haxe.js +1 -2
  29. package/lib/codegen/languages/haxe.js.map +1 -1
  30. package/lib/codegen/languages/java.js +1 -2
  31. package/lib/codegen/languages/java.js.map +1 -1
  32. package/lib/codegen/languages/js.js +1 -2
  33. package/lib/codegen/languages/js.js.map +1 -1
  34. package/lib/codegen/languages/lua.js +1 -2
  35. package/lib/codegen/languages/lua.js.map +1 -1
  36. package/lib/codegen/languages/ts.js +1 -2
  37. package/lib/codegen/languages/ts.js.map +1 -1
  38. package/lib/codegen/parser.js +2 -3
  39. package/lib/codegen/parser.js.map +1 -1
  40. package/lib/codegen/types.js +3 -3
  41. package/lib/codegen/types.js.map +1 -1
  42. package/lib/debug.d.ts +1 -0
  43. package/lib/debug.js +52 -0
  44. package/lib/debug.js.map +1 -0
  45. package/lib/decoder/DecodeOperation.d.ts +0 -1
  46. package/lib/decoder/DecodeOperation.js +23 -11
  47. package/lib/decoder/DecodeOperation.js.map +1 -1
  48. package/lib/decoder/Decoder.d.ts +6 -7
  49. package/lib/decoder/Decoder.js +8 -8
  50. package/lib/decoder/Decoder.js.map +1 -1
  51. package/lib/decoder/ReferenceTracker.js +3 -2
  52. package/lib/decoder/ReferenceTracker.js.map +1 -1
  53. package/lib/decoder/strategy/RawChanges.js +1 -2
  54. package/lib/decoder/strategy/RawChanges.js.map +1 -1
  55. package/lib/decoder/strategy/StateCallbacks.d.ts +44 -11
  56. package/lib/decoder/strategy/StateCallbacks.js +75 -65
  57. package/lib/decoder/strategy/StateCallbacks.js.map +1 -1
  58. package/lib/encoder/ChangeTree.d.ts +9 -19
  59. package/lib/encoder/ChangeTree.js +129 -145
  60. package/lib/encoder/ChangeTree.js.map +1 -1
  61. package/lib/encoder/EncodeOperation.d.ts +1 -5
  62. package/lib/encoder/EncodeOperation.js +74 -58
  63. package/lib/encoder/EncodeOperation.js.map +1 -1
  64. package/lib/encoder/Encoder.d.ts +10 -8
  65. package/lib/encoder/Encoder.js +89 -56
  66. package/lib/encoder/Encoder.js.map +1 -1
  67. package/lib/encoder/Root.d.ts +17 -0
  68. package/lib/encoder/Root.js +44 -0
  69. package/lib/encoder/Root.js.map +1 -0
  70. package/lib/encoder/StateView.d.ts +2 -2
  71. package/lib/encoder/StateView.js +49 -59
  72. package/lib/encoder/StateView.js.map +1 -1
  73. package/lib/encoding/assert.d.ts +2 -1
  74. package/lib/encoding/assert.js +5 -5
  75. package/lib/encoding/assert.js.map +1 -1
  76. package/lib/encoding/decode.js +21 -22
  77. package/lib/encoding/decode.js.map +1 -1
  78. package/lib/encoding/encode.d.ts +2 -2
  79. package/lib/encoding/encode.js +40 -39
  80. package/lib/encoding/encode.js.map +1 -1
  81. package/lib/encoding/spec.d.ts +2 -1
  82. package/lib/encoding/spec.js +1 -0
  83. package/lib/encoding/spec.js.map +1 -1
  84. package/lib/index.d.ts +6 -3
  85. package/lib/index.js +18 -13
  86. package/lib/index.js.map +1 -1
  87. package/lib/types/TypeContext.d.ts +23 -0
  88. package/lib/types/TypeContext.js +102 -0
  89. package/lib/types/TypeContext.js.map +1 -0
  90. package/lib/types/custom/ArraySchema.d.ts +2 -2
  91. package/lib/types/custom/ArraySchema.js +6 -9
  92. package/lib/types/custom/ArraySchema.js.map +1 -1
  93. package/lib/types/custom/CollectionSchema.js +1 -0
  94. package/lib/types/custom/CollectionSchema.js.map +1 -1
  95. package/lib/types/custom/MapSchema.js +5 -0
  96. package/lib/types/custom/MapSchema.js.map +1 -1
  97. package/lib/types/custom/SetSchema.js +1 -0
  98. package/lib/types/custom/SetSchema.js.map +1 -1
  99. package/lib/types/registry.js +3 -4
  100. package/lib/types/registry.js.map +1 -1
  101. package/lib/types/symbols.d.ts +1 -0
  102. package/lib/types/symbols.js +2 -1
  103. package/lib/types/symbols.js.map +1 -1
  104. package/lib/types/utils.js +1 -2
  105. package/lib/types/utils.js.map +1 -1
  106. package/lib/utils.js +3 -4
  107. package/lib/utils.js.map +1 -1
  108. package/package.json +5 -5
  109. package/src/Metadata.ts +104 -26
  110. package/src/Reflection.ts +26 -28
  111. package/src/Schema.ts +35 -47
  112. package/src/annotations.ts +82 -176
  113. package/src/bench_encode.ts +121 -0
  114. package/src/debug.ts +56 -0
  115. package/src/decoder/DecodeOperation.ts +28 -11
  116. package/src/decoder/Decoder.ts +13 -11
  117. package/src/decoder/ReferenceTracker.ts +3 -2
  118. package/src/decoder/strategy/StateCallbacks.ts +152 -81
  119. package/src/encoder/ChangeTree.ts +147 -166
  120. package/src/encoder/EncodeOperation.ts +93 -70
  121. package/src/encoder/Encoder.ts +111 -65
  122. package/src/encoder/Root.ts +51 -0
  123. package/src/encoder/StateView.ts +53 -69
  124. package/src/encoding/assert.ts +4 -3
  125. package/src/encoding/decode.ts +1 -2
  126. package/src/encoding/encode.ts +25 -22
  127. package/src/encoding/spec.ts +1 -0
  128. package/src/index.ts +8 -14
  129. package/src/types/TypeContext.ts +122 -0
  130. package/src/types/custom/ArraySchema.ts +10 -2
  131. package/src/types/custom/CollectionSchema.ts +1 -0
  132. package/src/types/custom/MapSchema.ts +6 -0
  133. package/src/types/custom/SetSchema.ts +1 -0
  134. package/src/types/symbols.ts +2 -0
@@ -23,6 +23,7 @@ var OPERATION;
23
23
  OPERATION[OPERATION["REVERSE"] = 15] = "REVERSE";
24
24
  OPERATION[OPERATION["MOVE"] = 32] = "MOVE";
25
25
  OPERATION[OPERATION["DELETE_BY_REFID"] = 33] = "DELETE_BY_REFID";
26
+ OPERATION[OPERATION["ADD_BY_REFID"] = 129] = "ADD_BY_REFID";
26
27
  })(OPERATION || (OPERATION = {}));
27
28
 
28
29
  Symbol.metadata ??= Symbol.for("Symbol.metadata");
@@ -33,6 +34,7 @@ const $decoder = Symbol("$decoder");
33
34
  const $filter = Symbol("$filter");
34
35
  const $getByIndex = Symbol("$getByIndex");
35
36
  const $deleteByIndex = Symbol("$deleteByIndex");
37
+ const $descriptors = Symbol("$descriptors");
36
38
  /**
37
39
  * Used to hold ChangeTree instances whitin the structures
38
40
  */
@@ -68,34 +70,67 @@ function getType(identifier) {
68
70
  }
69
71
 
70
72
  const Metadata = {
71
- addField(metadata, index, field, type, descriptor) {
73
+ addField(metadata, index, name, type, descriptor) {
72
74
  if (index > 64) {
73
- throw new Error(`Can't define field '${field}'.\nSchema instances may only have up to 64 fields.`);
75
+ throw new Error(`Can't define field '${name}'.\nSchema instances may only have up to 64 fields.`);
74
76
  }
75
- metadata[field] = Object.assign(metadata[field] || {}, // avoid overwriting previous field metadata (@owned / @deprecated)
77
+ metadata[index] = Object.assign(metadata[index] || {}, // avoid overwriting previous field metadata (@owned / @deprecated)
76
78
  {
77
79
  type: (Array.isArray(type))
78
80
  ? { array: type[0] }
79
81
  : type,
80
82
  index,
81
- descriptor,
83
+ name,
82
84
  });
85
+ // create "descriptors" map
86
+ metadata[$descriptors] ??= {};
87
+ if (descriptor) {
88
+ // for encoder
89
+ metadata[$descriptors][name] = descriptor;
90
+ metadata[$descriptors][`_${name}`] = {
91
+ value: undefined,
92
+ writable: true,
93
+ enumerable: false,
94
+ configurable: true,
95
+ };
96
+ }
97
+ else {
98
+ // for decoder
99
+ metadata[$descriptors][name] = {
100
+ value: undefined,
101
+ writable: true,
102
+ enumerable: true,
103
+ configurable: true,
104
+ };
105
+ }
83
106
  // map -1 as last field index
84
107
  Object.defineProperty(metadata, -1, {
85
108
  value: index,
86
109
  enumerable: false,
87
110
  configurable: true
88
111
  });
89
- // map index => field name (non enumerable)
90
- Object.defineProperty(metadata, index, {
91
- value: field,
112
+ // map field name => index (non enumerable)
113
+ Object.defineProperty(metadata, name, {
114
+ value: index,
92
115
  enumerable: false,
93
116
  configurable: true,
94
117
  });
118
+ // if child Ref/complex type, add to -4
119
+ if (typeof (metadata[index].type) !== "string") {
120
+ if (metadata[-4] === undefined) {
121
+ Object.defineProperty(metadata, -4, {
122
+ value: [],
123
+ enumerable: false,
124
+ configurable: true,
125
+ });
126
+ }
127
+ metadata[-4].push(index);
128
+ }
95
129
  },
96
130
  setTag(metadata, fieldName, tag) {
131
+ const index = metadata[fieldName];
132
+ const field = metadata[index];
97
133
  // add 'tag' to the field
98
- const field = metadata[fieldName];
99
134
  field.tag = tag;
100
135
  if (!metadata[-2]) {
101
136
  // -2: all field indexes with "view" tag
@@ -111,20 +146,14 @@ const Metadata = {
111
146
  configurable: true
112
147
  });
113
148
  }
114
- metadata[-2].push(field.index);
149
+ metadata[-2].push(index);
115
150
  if (!metadata[-3][tag]) {
116
151
  metadata[-3][tag] = [];
117
152
  }
118
- metadata[-3][tag].push(field.index);
153
+ metadata[-3][tag].push(index);
119
154
  },
120
155
  setFields(target, fields) {
121
156
  const metadata = (target.prototype.constructor[Symbol.metadata] ??= {});
122
- // target[$track] = function (changeTree, index: number, operation: OPERATION = OPERATION.ADD) {
123
- // changeTree.change(index, operation, encodeSchemaOperation);
124
- // };
125
- // target[$encoder] = encodeSchemaOperation;
126
- // target[$decoder] = decodeSchemaOperation;
127
- // if (!target.prototype.toJSON) { target.prototype.toJSON = Schema.prototype.toJSON; }
128
157
  let index = 0;
129
158
  for (const field in fields) {
130
159
  const type = fields[field];
@@ -132,13 +161,53 @@ const Metadata = {
132
161
  const complexTypeKlass = (Array.isArray(type))
133
162
  ? getType("array")
134
163
  : (typeof (Object.keys(type)[0]) === "string") && getType(Object.keys(type)[0]);
135
- Metadata.addField(metadata, index, field, type, getPropertyDescriptor(`_${field}`, index, type, complexTypeKlass, metadata, field));
164
+ Metadata.addField(metadata, index, field, type, getPropertyDescriptor(`_${field}`, index, type, complexTypeKlass));
136
165
  index++;
137
166
  }
138
167
  },
139
168
  isDeprecated(metadata, field) {
140
169
  return metadata[field].deprecated === true;
141
170
  },
171
+ init(klass) {
172
+ //
173
+ // Used only to initialize an empty Schema (Encoder#constructor)
174
+ // TODO: remove/refactor this...
175
+ //
176
+ const metadata = {};
177
+ klass[Symbol.metadata] = metadata;
178
+ Object.defineProperty(metadata, -1, {
179
+ value: 0,
180
+ enumerable: false,
181
+ configurable: true,
182
+ });
183
+ },
184
+ initialize(constructor, parentMetadata) {
185
+ let metadata = constructor[Symbol.metadata] ?? Object.create(null);
186
+ // make sure inherited classes have their own metadata object.
187
+ if (constructor[Symbol.metadata] === parentMetadata) {
188
+ metadata = Object.create(null);
189
+ if (parentMetadata) {
190
+ // assign parent metadata to current
191
+ Object.assign(metadata, parentMetadata);
192
+ for (let i = 0; i <= parentMetadata[-1]; i++) {
193
+ const fieldName = parentMetadata[i].name;
194
+ Object.defineProperty(metadata, fieldName, {
195
+ value: parentMetadata[fieldName],
196
+ enumerable: false,
197
+ configurable: true,
198
+ });
199
+ }
200
+ Object.defineProperty(metadata, -1, {
201
+ value: parentMetadata[-1],
202
+ enumerable: false,
203
+ configurable: true,
204
+ writable: true,
205
+ });
206
+ }
207
+ }
208
+ constructor[Symbol.metadata] = metadata;
209
+ return metadata;
210
+ },
142
211
  isValidInstance(klass) {
143
212
  return (klass.constructor[Symbol.metadata] &&
144
213
  Object.prototype.hasOwnProperty.call(klass.constructor[Symbol.metadata], -1));
@@ -147,97 +216,68 @@ const Metadata = {
147
216
  const metadata = klass[Symbol.metadata];
148
217
  const fields = {};
149
218
  for (let i = 0; i <= metadata[-1]; i++) {
150
- fields[metadata[i]] = metadata[metadata[i]].type;
219
+ fields[metadata[i].name] = metadata[i].type;
151
220
  }
152
221
  return fields;
153
222
  }
154
223
  };
155
224
 
156
225
  var _a$5;
157
- class Root {
158
- constructor() {
159
- this.nextUniqueId = 0;
160
- this.refCount = new WeakMap();
161
- // all changes
162
- this.allChanges = new Map();
163
- this.allFilteredChanges = new Map();
164
- // pending changes to be encoded
165
- this.changes = new Map();
166
- this.filteredChanges = new Map();
167
- }
168
- getNextUniqueId() {
169
- return this.nextUniqueId++;
170
- }
171
- add(changeTree) {
172
- const refCount = this.refCount.get(changeTree) || 0;
173
- this.refCount.set(changeTree, refCount + 1);
174
- }
175
- remove(changeTree) {
176
- const refCount = this.refCount.get(changeTree);
177
- if (refCount <= 1) {
178
- this.allChanges.delete(changeTree);
179
- this.changes.delete(changeTree);
180
- if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
181
- this.allFilteredChanges.delete(changeTree);
182
- this.filteredChanges.delete(changeTree);
183
- }
184
- this.refCount.delete(changeTree);
185
- }
186
- else {
187
- this.refCount.set(changeTree, refCount - 1);
188
- }
189
- changeTree.forEachChild((child, _) => this.remove(child));
190
- }
191
- clear() {
192
- this.changes.clear();
193
- }
194
- }
195
226
  class ChangeTree {
196
227
  static { _a$5 = $isNew; }
197
- ;
198
228
  constructor(ref) {
199
- this.indexes = {}; // TODO: remove this, only used by MapSchema/SetSchema/CollectionSchema (`encodeKeyValueOperation`)
229
+ this.isFiltered = false;
230
+ this.isPartiallyFiltered = false;
200
231
  this.currentOperationIndex = 0;
201
- this.allChanges = new Map();
202
- this.allFilteredChanges = new Map();
203
232
  this.changes = new Map();
204
- this.filteredChanges = new Map();
233
+ this.allChanges = new Map();
205
234
  this[_a$5] = true;
206
235
  this.ref = ref;
236
+ //
237
+ // Does this structure have "filters" declared?
238
+ //
239
+ if (ref.constructor[Symbol.metadata]?.[-2]) {
240
+ this.allFilteredChanges = new Map();
241
+ this.filteredChanges = new Map();
242
+ }
207
243
  }
208
244
  setRoot(root) {
209
245
  this.root = root;
210
246
  this.root.add(this);
211
- //
212
- // At Schema initialization, the "root" structure might not be available
213
- // yet, as it only does once the "Encoder" has been set up.
214
- //
215
- // So the "parent" may be already set without a "root".
216
- //
217
- this.checkIsFiltered(this.parent, this.parentIndex);
218
- // unique refId for the ChangeTree.
219
- this.ensureRefId();
247
+ const metadata = this.ref.constructor[Symbol.metadata];
248
+ if (this.root.types.hasFilters) {
249
+ //
250
+ // At Schema initialization, the "root" structure might not be available
251
+ // yet, as it only does once the "Encoder" has been set up.
252
+ //
253
+ // So the "parent" may be already set without a "root".
254
+ //
255
+ this.checkIsFiltered(metadata, this.parent, this.parentIndex);
256
+ if (this.isFiltered || this.isPartiallyFiltered) {
257
+ this.root.allFilteredChanges.set(this, this.allFilteredChanges);
258
+ this.root.filteredChanges.set(this, this.filteredChanges);
259
+ }
260
+ }
220
261
  if (!this.isFiltered) {
221
262
  this.root.changes.set(this, this.changes);
263
+ this.root.allChanges.set(this, this.allChanges);
222
264
  }
223
- if (this.isFiltered || this.isPartiallyFiltered) {
224
- this.root.allFilteredChanges.set(this, this.allFilteredChanges);
225
- this.root.filteredChanges.set(this, this.filteredChanges);
226
- // } else {
227
- // this.root.allChanges.set(this, this.allChanges);
265
+ this.ensureRefId();
266
+ if (metadata) {
267
+ metadata[-4]?.forEach((index) => {
268
+ const field = metadata[index];
269
+ const value = this.ref[field.name];
270
+ if (value) {
271
+ value[$changes].setRoot(root);
272
+ }
273
+ });
228
274
  }
229
- if (!this.isFiltered) {
230
- this.root.allChanges.set(this, this.allChanges);
275
+ else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
276
+ // MapSchema / ArraySchema, etc.
277
+ this.ref.forEach((value, key) => {
278
+ value[$changes].setRoot(root);
279
+ });
231
280
  }
232
- this.forEachChild((changeTree, _) => {
233
- changeTree.setRoot(root);
234
- });
235
- // this.allChanges.forEach((_, index) => {
236
- // const childRef = this.ref[$getByIndex](index);
237
- // if (childRef && childRef[$changes]) {
238
- // childRef[$changes].setRoot(root);
239
- // }
240
- // });
241
281
  }
242
282
  setParent(parent, root, parentIndex) {
243
283
  this.parent = parent;
@@ -247,50 +287,60 @@ class ChangeTree {
247
287
  return;
248
288
  }
249
289
  root.add(this);
290
+ const metadata = this.ref.constructor[Symbol.metadata];
250
291
  // skip if parent is already set
251
- if (root === this.root) {
252
- this.forEachChild((changeTree, atIndex) => {
253
- changeTree.setParent(this.ref, root, atIndex);
254
- });
255
- return;
256
- }
257
- this.root = root;
258
- this.checkIsFiltered(parent, parentIndex);
259
- if (!this.isFiltered) {
260
- this.root.changes.set(this, this.changes);
292
+ if (root !== this.root) {
293
+ this.root = root;
294
+ if (root.types.hasFilters) {
295
+ this.checkIsFiltered(metadata, parent, parentIndex);
296
+ if (this.isFiltered || this.isPartiallyFiltered) {
297
+ this.root.filteredChanges.set(this, this.filteredChanges);
298
+ this.root.allFilteredChanges.set(this, this.filteredChanges);
299
+ }
300
+ }
301
+ if (!this.isFiltered) {
302
+ this.root.changes.set(this, this.changes);
303
+ this.root.allChanges.set(this, this.allChanges);
304
+ }
305
+ this.ensureRefId();
261
306
  }
262
- if (this.isFiltered || this.isPartiallyFiltered) {
263
- this.root.filteredChanges.set(this, this.filteredChanges);
264
- this.root.allFilteredChanges.set(this, this.filteredChanges);
307
+ // assign same parent on child structures
308
+ if (metadata) {
309
+ metadata[-4]?.forEach((index) => {
310
+ const field = metadata[index];
311
+ const value = this.ref[field.name];
312
+ value?.[$changes].setParent(this.ref, root, index);
313
+ // console.log(this.ref.constructor.name, field.name, value);
314
+ // try { throw new Error(); } catch (e) {
315
+ // console.log(e.stack);
316
+ // }
317
+ });
265
318
  }
266
- else {
267
- this.root.allChanges.set(this, this.allChanges);
319
+ else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
320
+ // MapSchema / ArraySchema, etc.
321
+ this.ref.forEach((value, key) => {
322
+ value[$changes].setParent(this.ref, root, this.indexes[key] ?? key);
323
+ });
268
324
  }
269
- this.ensureRefId();
270
- this.forEachChild((changeTree, atIndex) => {
271
- changeTree.setParent(this.ref, root, atIndex);
272
- });
273
325
  }
274
326
  forEachChild(callback) {
275
327
  //
276
328
  // assign same parent on child structures
277
329
  //
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);
330
+ const metadata = this.ref.constructor[Symbol.metadata];
331
+ if (metadata) {
332
+ metadata[-4]?.forEach((index) => {
333
+ const field = metadata[index];
334
+ const value = this.ref[field.name];
335
+ if (value) {
336
+ callback(value[$changes], index);
285
337
  }
286
- }
338
+ });
287
339
  }
288
- else if (typeof (this.ref) === "object") {
340
+ else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
289
341
  // MapSchema / ArraySchema, etc.
290
342
  this.ref.forEach((value, key) => {
291
- if (Metadata.isValidInstance(value)) {
292
- callback(value[$changes], this.ref[$changes].indexes[key]);
293
- }
343
+ callback(value[$changes], this.indexes[key] ?? key);
294
344
  });
295
345
  }
296
346
  }
@@ -299,8 +349,8 @@ class ChangeTree {
299
349
  this.root?.changes.set(this, this.changes);
300
350
  }
301
351
  change(index, operation = OPERATION.ADD) {
302
- const metadata = this.ref['constructor'][Symbol.metadata];
303
- const isFiltered = this.isFiltered || (metadata && metadata[metadata[index]].tag !== undefined);
352
+ const metadata = this.ref.constructor[Symbol.metadata];
353
+ const isFiltered = this.isFiltered || (metadata?.[index]?.tag !== undefined);
304
354
  const changeSet = (isFiltered)
305
355
  ? this.filteredChanges
306
356
  : this.changes;
@@ -311,14 +361,15 @@ class ChangeTree {
311
361
  : (previousOperation === OPERATION.DELETE)
312
362
  ? OPERATION.DELETE_AND_ADD
313
363
  : operation;
364
+ //
365
+ // TODO: are DELETE operations being encoded as ADD here ??
366
+ //
314
367
  changeSet.set(index, op);
315
368
  }
316
- //
317
- // TODO: are DELETE operations being encoded as ADD here ??
318
- //
319
369
  if (isFiltered) {
320
370
  this.allFilteredChanges.set(index, OPERATION.ADD);
321
371
  this.root?.filteredChanges.set(this, this.filteredChanges);
372
+ this.root?.allFilteredChanges.set(this, this.allFilteredChanges);
322
373
  }
323
374
  else {
324
375
  this.allChanges.set(index, OPERATION.ADD);
@@ -357,7 +408,6 @@ class ChangeTree {
357
408
  }
358
409
  _shiftAllChangeIndexes(shiftIndex, startIndex = 0, allChangeSet) {
359
410
  Array.from(allChangeSet.entries()).forEach(([index, op]) => {
360
- // console.log('shiftAllChangeIndexes', index >= startIndex, { index, op, shiftIndex, startIndex })
361
411
  if (index >= startIndex) {
362
412
  allChangeSet.delete(index);
363
413
  allChangeSet.set(index + shiftIndex, op);
@@ -365,9 +415,7 @@ class ChangeTree {
365
415
  });
366
416
  }
367
417
  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) {
418
+ if (this.filteredChanges !== undefined) {
371
419
  this.allFilteredChanges.set(allChangesIndex, OPERATION.ADD);
372
420
  this.filteredChanges.set(index, operation);
373
421
  this.root?.filteredChanges.set(this, this.filteredChanges);
@@ -380,8 +428,8 @@ class ChangeTree {
380
428
  }
381
429
  getType(index) {
382
430
  if (Metadata.isValidInstance(this.ref)) {
383
- const metadata = this.ref['constructor'][Symbol.metadata];
384
- return metadata[metadata[index]].type;
431
+ const metadata = this.ref.constructor[Symbol.metadata];
432
+ return metadata[index].type;
385
433
  }
386
434
  else {
387
435
  //
@@ -395,7 +443,7 @@ class ChangeTree {
395
443
  }
396
444
  getChange(index) {
397
445
  // TODO: optimize this. avoid checking against multiple instances
398
- return this.changes.get(index) ?? this.filteredChanges.get(index);
446
+ return this.changes.get(index) ?? this.filteredChanges?.get(index);
399
447
  }
400
448
  //
401
449
  // used during `.encode()`
@@ -416,9 +464,7 @@ class ChangeTree {
416
464
  }
417
465
  return;
418
466
  }
419
- const metadata = this.ref['constructor'][Symbol.metadata];
420
- const isFiltered = this.isFiltered || (metadata && metadata[metadata[index]].tag !== undefined);
421
- const changeSet = (isFiltered)
467
+ const changeSet = (this.filteredChanges)
422
468
  ? this.filteredChanges
423
469
  : this.changes;
424
470
  const previousValue = this.getValue(index);
@@ -441,7 +487,7 @@ class ChangeTree {
441
487
  //
442
488
  // FIXME: this is looking a bit ugly (and repeated from `.change()`)
443
489
  //
444
- if (isFiltered) {
490
+ if (this.filteredChanges) {
445
491
  this.root?.filteredChanges.set(this, this.filteredChanges);
446
492
  this.allFilteredChanges.delete(allChangesIndex);
447
493
  }
@@ -452,6 +498,7 @@ class ChangeTree {
452
498
  }
453
499
  endEncode() {
454
500
  this.changes.clear();
501
+ // ArraySchema and MapSchema have a custom "encode end" method
455
502
  this.ref[$onEncodeEnd]?.();
456
503
  // Not a new instance anymore
457
504
  delete this[$isNew];
@@ -464,12 +511,12 @@ class ChangeTree {
464
511
  //
465
512
  this.ref[$onEncodeEnd]?.();
466
513
  this.changes.clear();
467
- this.filteredChanges.clear();
514
+ this.filteredChanges?.clear();
468
515
  // reset operation index
469
516
  this.currentOperationIndex = 0;
470
517
  if (discardAll) {
471
518
  this.allChanges.clear();
472
- this.allFilteredChanges.clear();
519
+ this.allFilteredChanges?.clear();
473
520
  // remove children references
474
521
  this.forEachChild((changeTree, _) => this.root?.remove(changeTree));
475
522
  }
@@ -496,33 +543,41 @@ class ChangeTree {
496
543
  get changed() {
497
544
  return this.changes.size > 0;
498
545
  }
499
- checkIsFiltered(parent, parentIndex) {
546
+ checkIsFiltered(metadata, parent, parentIndex) {
500
547
  // Detect if current structure has "filters" declared
501
- this.isPartiallyFiltered = this.ref['constructor']?.[Symbol.metadata]?.[-2];
502
- // TODO: support "partially filtered", where the instance is visible, but only a field is not.
503
- // Detect if parent has "filters" declared
504
- while (parent && !this.isFiltered) {
505
- const metadata = parent['constructor'][Symbol.metadata];
506
- const fieldName = metadata?.[parentIndex];
507
- const isParentOwned = metadata?.[fieldName]?.tag !== undefined;
508
- this.isFiltered = isParentOwned || parent[$changes].isFiltered; // metadata?.[-2]
509
- parent = parent[$changes].parent;
548
+ this.isPartiallyFiltered = metadata?.[-2] !== undefined;
549
+ if (this.isPartiallyFiltered) {
550
+ this.filteredChanges = this.filteredChanges || new Map();
551
+ this.allFilteredChanges = this.allFilteredChanges || new Map();
510
552
  }
511
- //
512
- // TODO: refactor this!
513
- //
514
- // swapping `changes` and `filteredChanges` is required here
515
- // because "isFiltered" may not be imedialely available on `change()`
516
- //
517
- if (this.isFiltered && this.changes.size > 0) {
518
- // swap changes reference
519
- const changes = this.changes;
520
- this.changes = this.filteredChanges;
521
- this.filteredChanges = changes;
522
- // swap "all changes" reference
523
- const allFilteredChanges = this.allFilteredChanges;
524
- this.allFilteredChanges = this.allChanges;
525
- this.allChanges = allFilteredChanges;
553
+ if (parent) {
554
+ if (!Metadata.isValidInstance(parent)) {
555
+ const parentChangeTree = parent[$changes];
556
+ parent = parentChangeTree.parent;
557
+ parentIndex = parentChangeTree.parentIndex;
558
+ }
559
+ const parentMetadata = parent?.constructor?.[Symbol.metadata];
560
+ this.isFiltered = (parent && parentMetadata?.[-2]?.includes(parentIndex));
561
+ //
562
+ // TODO: refactor this!
563
+ //
564
+ // swapping `changes` and `filteredChanges` is required here
565
+ // because "isFiltered" may not be imedialely available on `change()`
566
+ //
567
+ if (this.isFiltered) {
568
+ this.filteredChanges = new Map();
569
+ this.allFilteredChanges = new Map();
570
+ if (this.changes.size > 0) {
571
+ // swap changes reference
572
+ const changes = this.changes;
573
+ this.changes = this.filteredChanges;
574
+ this.filteredChanges = changes;
575
+ // swap "all changes" reference
576
+ const allFilteredChanges = this.allFilteredChanges;
577
+ this.allFilteredChanges = this.allChanges;
578
+ this.allChanges = allFilteredChanges;
579
+ }
580
+ }
526
581
  }
527
582
  }
528
583
  }
@@ -559,26 +614,29 @@ try {
559
614
  textEncoder = new TextEncoder();
560
615
  }
561
616
  catch (e) { }
562
- function utf8Length(str) {
563
- var c = 0, length = 0;
564
- for (var i = 0, l = str.length; i < l; i++) {
565
- c = str.charCodeAt(i);
566
- if (c < 0x80) {
567
- length += 1;
568
- }
569
- else if (c < 0x800) {
570
- length += 2;
571
- }
572
- else if (c < 0xd800 || c >= 0xe000) {
573
- length += 3;
574
- }
575
- else {
576
- i++;
577
- length += 4;
617
+ const hasBufferByteLength = (typeof Buffer !== 'undefined' && Buffer.byteLength);
618
+ const utf8Length = (hasBufferByteLength)
619
+ ? Buffer.byteLength // node
620
+ : function (str, _) {
621
+ var c = 0, length = 0;
622
+ for (var i = 0, l = str.length; i < l; i++) {
623
+ c = str.charCodeAt(i);
624
+ if (c < 0x80) {
625
+ length += 1;
626
+ }
627
+ else if (c < 0x800) {
628
+ length += 2;
629
+ }
630
+ else if (c < 0xd800 || c >= 0xe000) {
631
+ length += 3;
632
+ }
633
+ else {
634
+ i++;
635
+ length += 4;
636
+ }
578
637
  }
579
- }
580
- return length;
581
- }
638
+ return length;
639
+ };
582
640
  function utf8Write(view, str, it) {
583
641
  var c = 0;
584
642
  for (var i = 0, l = str.length; i < l; i++) {
@@ -673,8 +731,7 @@ function string$1(bytes, value, it) {
673
731
  if (!value) {
674
732
  value = "";
675
733
  }
676
- // let length = utf8Length(value);
677
- let length = Buffer.byteLength(value, "utf8");
734
+ let length = utf8Length(value, "utf8");
678
735
  let size = 0;
679
736
  // fixstr
680
737
  if (length < 0x20) {
@@ -785,81 +842,40 @@ function number$1(bytes, value, it) {
785
842
 
786
843
  var encode = /*#__PURE__*/Object.freeze({
787
844
  __proto__: null,
788
- utf8Length: utf8Length,
789
- utf8Write: utf8Write,
790
- int8: int8$1,
791
- uint8: uint8$1,
845
+ boolean: boolean$1,
846
+ float32: float32$1,
847
+ float64: float64$1,
792
848
  int16: int16$1,
793
- uint16: uint16$1,
794
849
  int32: int32$1,
795
- uint32: uint32$1,
796
850
  int64: int64$1,
851
+ int8: int8$1,
852
+ number: number$1,
853
+ string: string$1,
854
+ uint16: uint16$1,
855
+ uint32: uint32$1,
797
856
  uint64: uint64$1,
798
- float32: float32$1,
799
- float64: float64$1,
857
+ uint8: uint8$1,
858
+ utf8Length: utf8Length,
859
+ utf8Write: utf8Write,
800
860
  writeFloat32: writeFloat32,
801
- writeFloat64: writeFloat64,
802
- boolean: boolean$1,
803
- string: string$1,
804
- number: number$1
861
+ writeFloat64: writeFloat64
805
862
  });
806
863
 
807
- class EncodeSchemaError extends Error {
808
- }
809
- function assertType(value, type, klass, field) {
810
- let typeofTarget;
811
- let allowNull = false;
812
- switch (type) {
813
- case "number":
814
- case "int8":
815
- case "uint8":
816
- case "int16":
817
- case "uint16":
818
- case "int32":
819
- case "uint32":
820
- case "int64":
821
- case "uint64":
822
- case "float32":
823
- case "float64":
824
- typeofTarget = "number";
825
- if (isNaN(value)) {
826
- console.log(`trying to encode "NaN" in ${klass.constructor.name}#${field}`);
827
- }
828
- break;
829
- case "string":
830
- typeofTarget = "string";
831
- allowNull = true;
832
- break;
833
- case "boolean":
834
- // boolean is always encoded as true/false based on truthiness
835
- return;
836
- }
837
- if (typeof (value) !== typeofTarget && (!allowNull || (allowNull && value !== null))) {
838
- let foundValue = `'${JSON.stringify(value)}'${(value && value.constructor && ` (${value.constructor.name})`) || ''}`;
839
- throw new EncodeSchemaError(`a '${typeofTarget}' was expected, but ${foundValue} was provided in ${klass.constructor.name}#${field}`);
840
- }
841
- }
842
- function assertInstanceType(value, type, klass, field) {
843
- if (!(value instanceof type)) {
844
- throw new EncodeSchemaError(`a '${type.name}' was expected, but '${value && value.constructor.name}' was provided in ${klass.constructor.name}#${field}`);
845
- }
846
- }
847
-
848
- function encodePrimitiveType(type, bytes, value, klass, field, it) {
849
- assertType(value, type, klass, field);
850
- const encodeFunc = encode[type];
851
- if (encodeFunc) {
852
- encodeFunc(bytes, value, it);
853
- // encodeFunc(bytes, value);
854
- }
855
- else {
856
- throw new EncodeSchemaError(`a '${type}' was expected, but ${value} was provided in ${klass.constructor.name}#${field}`);
864
+ function encodeValue(encoder, bytes,
865
+ // ref: Ref,
866
+ type, value,
867
+ // field: string | number,
868
+ operation, it) {
869
+ if (typeof (type) === "string") {
870
+ //
871
+ // Primitive values
872
+ //
873
+ // assertType(value, type as string, ref as Schema, field);
874
+ encode[type]?.(bytes, value, it);
857
875
  }
858
- }
859
- function encodeValue(encoder, bytes, ref, type, value, field, operation, it) {
860
- if (type[Symbol.metadata] !== undefined) {
861
- // TODO: move this to the `@type()` annotation
862
- assertInstanceType(value, type, ref, field);
876
+ else if (type[Symbol.metadata] !== undefined) {
877
+ // // TODO: move this to the `@type()` annotation
878
+ // assertInstanceType(value, type as typeof Schema, ref as Schema, field);
863
879
  //
864
880
  // Encode refId for this instance.
865
881
  // The actual instance is going to be encoded on next `changeTree` iteration.
@@ -870,21 +886,15 @@ function encodeValue(encoder, bytes, ref, type, value, field, operation, it) {
870
886
  encoder.tryEncodeTypeId(bytes, type, value.constructor, it);
871
887
  }
872
888
  }
873
- else if (typeof (type) === "string") {
874
- //
875
- // Primitive values
876
- //
877
- encodePrimitiveType(type, bytes, value, ref, field, it);
878
- }
879
889
  else {
880
- //
881
- // Custom type (MapSchema, ArraySchema, etc)
882
- //
883
- const definition = getType(Object.keys(type)[0]);
884
- //
885
- // ensure a ArraySchema has been provided
886
- //
887
- assertInstanceType(ref[field], definition.constructor, ref, field);
890
+ // //
891
+ // // Custom type (MapSchema, ArraySchema, etc)
892
+ // //
893
+ // const definition = getType(Object.keys(type)[0]);
894
+ // //
895
+ // // ensure a ArraySchema has been provided
896
+ // //
897
+ // assertInstanceType(ref[field], definition.constructor, ref as Schema, field);
888
898
  //
889
899
  // Encode refId for this instance.
890
900
  // The actual instance is going to be encoded on next `changeTree` iteration.
@@ -897,26 +907,27 @@ function encodeValue(encoder, bytes, ref, type, value, field, operation, it) {
897
907
  * @private
898
908
  */
899
909
  const encodeSchemaOperation = function (encoder, bytes, changeTree, index, operation, it) {
900
- const ref = changeTree.ref;
901
- const metadata = ref['constructor'][Symbol.metadata];
902
- const field = metadata[index];
903
- const type = metadata[field].type;
904
- const value = ref[field];
905
910
  // "compress" field index + operation
906
911
  bytes[it.offset++] = (index | operation) & 255;
907
912
  // Do not encode value for DELETE operations
908
913
  if (operation === OPERATION.DELETE) {
909
914
  return;
910
915
  }
916
+ const ref = changeTree.ref;
917
+ const metadata = ref['constructor'][Symbol.metadata];
918
+ const field = metadata[index];
911
919
  // TODO: inline this function call small performance gain
912
- encodeValue(encoder, bytes, ref, type, value, field, operation, it);
920
+ encodeValue(encoder, bytes,
921
+ // ref,
922
+ metadata[index].type, ref[field.name],
923
+ // index,
924
+ operation, it);
913
925
  };
914
926
  /**
915
927
  * Used for collections (MapSchema, CollectionSchema, SetSchema)
916
928
  * @private
917
929
  */
918
- const encodeKeyValueOperation = function (encoder, bytes, changeTree, field, operation, it) {
919
- const ref = changeTree.ref;
930
+ const encodeKeyValueOperation = function (encoder, bytes, changeTree, index, operation, it) {
920
931
  // encode operation
921
932
  bytes[it.offset++] = operation & 255;
922
933
  // custom operations
@@ -924,11 +935,12 @@ const encodeKeyValueOperation = function (encoder, bytes, changeTree, field, ope
924
935
  return;
925
936
  }
926
937
  // encode index
927
- number$1(bytes, field, it);
938
+ number$1(bytes, index, it);
928
939
  // Do not encode value for DELETE operations
929
940
  if (operation === OPERATION.DELETE) {
930
941
  return;
931
942
  }
943
+ const ref = changeTree.ref;
932
944
  //
933
945
  // encode "alias" for dynamic fields (maps)
934
946
  //
@@ -937,14 +949,30 @@ const encodeKeyValueOperation = function (encoder, bytes, changeTree, field, ope
937
949
  //
938
950
  // MapSchema dynamic key
939
951
  //
940
- const dynamicIndex = changeTree.ref['$indexes'].get(field);
952
+ const dynamicIndex = changeTree.ref['$indexes'].get(index);
941
953
  string$1(bytes, dynamicIndex, it);
942
954
  }
943
955
  }
944
- const type = changeTree.getType(field);
945
- const value = changeTree.getValue(field);
956
+ const type = changeTree.getType(index);
957
+ const value = changeTree.getValue(index);
958
+ // try { throw new Error(); } catch (e) {
959
+ // // only print if not coming from Reflection.ts
960
+ // if (!e.stack.includes("src/Reflection.ts")) {
961
+ // console.log("encodeKeyValueOperation -> ", {
962
+ // ref: changeTree.ref.constructor.name,
963
+ // field,
964
+ // operation: OPERATION[operation],
965
+ // value: value?.toJSON(),
966
+ // items: ref.toJSON(),
967
+ // });
968
+ // }
969
+ // }
946
970
  // TODO: inline this function call small performance gain
947
- encodeValue(encoder, bytes, ref, type, value, field, operation, it);
971
+ encodeValue(encoder, bytes,
972
+ // ref,
973
+ type, value,
974
+ // index,
975
+ operation, it);
948
976
  };
949
977
  /**
950
978
  * Used for collections (MapSchema, ArraySchema, etc.)
@@ -952,15 +980,19 @@ const encodeKeyValueOperation = function (encoder, bytes, changeTree, field, ope
952
980
  */
953
981
  const encodeArray = function (encoder, bytes, changeTree, field, operation, it, isEncodeAll, hasView) {
954
982
  const ref = changeTree.ref;
955
- if (hasView &&
956
- operation === OPERATION.DELETE &&
957
- typeof (changeTree.getType(field)) !== "string") {
958
- // encode delete by refId (array of schemas)
959
- bytes[it.offset++] = OPERATION.DELETE_BY_REFID;
960
- const value = ref['tmpItems'][field];
961
- const refId = value[$changes].refId;
962
- number$1(bytes, refId, it);
963
- return;
983
+ const useOperationByRefId = hasView && changeTree.isFiltered && (typeof (changeTree.getType(field)) !== "string");
984
+ let refOrIndex;
985
+ if (useOperationByRefId) {
986
+ refOrIndex = ref['tmpItems'][field][$changes].refId;
987
+ if (operation === OPERATION.DELETE) {
988
+ operation = OPERATION.DELETE_BY_REFID;
989
+ }
990
+ else if (operation === OPERATION.ADD) {
991
+ operation = OPERATION.ADD_BY_REFID;
992
+ }
993
+ }
994
+ else {
995
+ refOrIndex = field;
964
996
  }
965
997
  // encode operation
966
998
  bytes[it.offset++] = operation & 255;
@@ -969,7 +1001,7 @@ const encodeArray = function (encoder, bytes, changeTree, field, operation, it,
969
1001
  return;
970
1002
  }
971
1003
  // encode index
972
- number$1(bytes, field, it);
1004
+ number$1(bytes, refOrIndex, it);
973
1005
  // Do not encode value for DELETE operations
974
1006
  if (operation === OPERATION.DELETE) {
975
1007
  return;
@@ -984,7 +1016,11 @@ const encodeArray = function (encoder, bytes, changeTree, field, operation, it,
984
1016
  // items: ref.toJSON(),
985
1017
  // });
986
1018
  // TODO: inline this function call small performance gain
987
- encodeValue(encoder, bytes, ref, type, value, field, operation, it);
1019
+ encodeValue(encoder, bytes,
1020
+ // ref,
1021
+ type, value,
1022
+ // field,
1023
+ operation, it);
988
1024
  };
989
1025
 
990
1026
  /**
@@ -1010,7 +1046,6 @@ const encodeArray = function (encoder, bytes, changeTree, field, operation, it,
1010
1046
  * SOFTWARE
1011
1047
  */
1012
1048
  function utf8Read(bytes, it, length) {
1013
- it.offset += length;
1014
1049
  var string = '', chr = 0;
1015
1050
  for (var i = it.offset, end = it.offset + length; i < end; i++) {
1016
1051
  var byte = bytes[i];
@@ -1047,6 +1082,7 @@ function utf8Read(bytes, it, length) {
1047
1082
  // (do not throw error to avoid server/client from crashing due to hack attemps)
1048
1083
  // throw new Error('Invalid byte ' + byte.toString(16));
1049
1084
  }
1085
+ it.offset += length;
1050
1086
  return string;
1051
1087
  }
1052
1088
  function int8(bytes, it) {
@@ -1218,31 +1254,31 @@ function switchStructureCheck(bytes, it) {
1218
1254
 
1219
1255
  var decode = /*#__PURE__*/Object.freeze({
1220
1256
  __proto__: null,
1221
- utf8Read: utf8Read,
1222
- int8: int8,
1223
- uint8: uint8,
1224
- int16: int16,
1225
- uint16: uint16,
1226
- int32: int32,
1227
- uint32: uint32,
1257
+ arrayCheck: arrayCheck,
1258
+ boolean: boolean,
1228
1259
  float32: float32,
1229
1260
  float64: float64,
1261
+ int16: int16,
1262
+ int32: int32,
1230
1263
  int64: int64,
1231
- uint64: uint64,
1264
+ int8: int8,
1265
+ number: number,
1266
+ numberCheck: numberCheck,
1232
1267
  readFloat32: readFloat32,
1233
1268
  readFloat64: readFloat64,
1234
- boolean: boolean,
1235
1269
  string: string,
1236
1270
  stringCheck: stringCheck,
1237
- number: number,
1238
- numberCheck: numberCheck,
1239
- arrayCheck: arrayCheck,
1240
- switchStructureCheck: switchStructureCheck
1271
+ switchStructureCheck: switchStructureCheck,
1272
+ uint16: uint16,
1273
+ uint32: uint32,
1274
+ uint64: uint64,
1275
+ uint8: uint8,
1276
+ utf8Read: utf8Read
1241
1277
  });
1242
1278
 
1243
1279
  const DEFINITION_MISMATCH = -1;
1244
1280
  function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges) {
1245
- const $root = decoder.$root;
1281
+ const $root = decoder.root;
1246
1282
  const previousValue = ref[$getByIndex](index);
1247
1283
  let value;
1248
1284
  if ((operation & OPERATION.DELETE) === OPERATION.DELETE) {
@@ -1341,18 +1377,19 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
1341
1377
  }
1342
1378
  const decodeSchemaOperation = function (decoder, bytes, it, ref, allChanges) {
1343
1379
  const first_byte = bytes[it.offset++];
1344
- const metadata = ref['constructor'][Symbol.metadata];
1380
+ const metadata = ref.constructor[Symbol.metadata];
1345
1381
  // "compressed" index + operation
1346
1382
  const operation = (first_byte >> 6) << 6;
1347
1383
  const index = first_byte % (operation || 255);
1348
1384
  // skip early if field is not defined
1349
1385
  const field = metadata[index];
1350
1386
  if (field === undefined) {
1387
+ console.warn("@colyseus/schema: field not defined at", { index, ref: ref.constructor.name, metadata });
1351
1388
  return DEFINITION_MISMATCH;
1352
1389
  }
1353
- const { value, previousValue } = decodeValue(decoder, operation, ref, index, metadata[field].type, bytes, it, allChanges);
1390
+ const { value, previousValue } = decodeValue(decoder, operation, ref, index, field.type, bytes, it, allChanges);
1354
1391
  if (value !== null && value !== undefined) {
1355
- ref[field] = value;
1392
+ ref[field.name] = value;
1356
1393
  }
1357
1394
  // add change
1358
1395
  if (previousValue !== value) {
@@ -1360,7 +1397,7 @@ const decodeSchemaOperation = function (decoder, bytes, it, ref, allChanges) {
1360
1397
  ref,
1361
1398
  refId: decoder.currentRefId,
1362
1399
  op: operation,
1363
- field: field,
1400
+ field: field.name,
1364
1401
  value,
1365
1402
  previousValue,
1366
1403
  });
@@ -1428,7 +1465,8 @@ const decodeKeyValueOperation = function (decoder, bytes, it, ref, allChanges) {
1428
1465
  };
1429
1466
  const decodeArray = function (decoder, bytes, it, ref, allChanges) {
1430
1467
  // "uncompressed" index + operation (array/map items)
1431
- const operation = bytes[it.offset++];
1468
+ let operation = bytes[it.offset++];
1469
+ let index;
1432
1470
  if (operation === OPERATION.CLEAR) {
1433
1471
  //
1434
1472
  // When decoding:
@@ -1442,8 +1480,8 @@ const decodeArray = function (decoder, bytes, it, ref, allChanges) {
1442
1480
  else if (operation === OPERATION.DELETE_BY_REFID) {
1443
1481
  // TODO: refactor here, try to follow same flow as below
1444
1482
  const refId = number(bytes, it);
1445
- const previousValue = decoder.$root.refs.get(refId);
1446
- const index = ref.findIndex((value) => value === previousValue);
1483
+ const previousValue = decoder.root.refs.get(refId);
1484
+ index = ref.findIndex((value) => value === previousValue);
1447
1485
  ref[$deleteByIndex](index);
1448
1486
  allChanges.push({
1449
1487
  ref,
@@ -1456,7 +1494,17 @@ const decodeArray = function (decoder, bytes, it, ref, allChanges) {
1456
1494
  });
1457
1495
  return;
1458
1496
  }
1459
- const index = number(bytes, it);
1497
+ else if (operation === OPERATION.ADD_BY_REFID) {
1498
+ const refId = number(bytes, it);
1499
+ const itemByRefId = decoder.root.refs.get(refId);
1500
+ // use existing index, or push new value
1501
+ index = (itemByRefId)
1502
+ ? ref.findIndex((value) => value === itemByRefId)
1503
+ : ref.length;
1504
+ }
1505
+ else {
1506
+ index = number(bytes, it);
1507
+ }
1460
1508
  const type = ref[$childType];
1461
1509
  let dynamicIndex = index;
1462
1510
  const { value, previousValue } = decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges);
@@ -1480,6 +1528,47 @@ const decodeArray = function (decoder, bytes, it, ref, allChanges) {
1480
1528
  }
1481
1529
  };
1482
1530
 
1531
+ class EncodeSchemaError extends Error {
1532
+ }
1533
+ function assertType(value, type, klass, field) {
1534
+ let typeofTarget;
1535
+ let allowNull = false;
1536
+ switch (type) {
1537
+ case "number":
1538
+ case "int8":
1539
+ case "uint8":
1540
+ case "int16":
1541
+ case "uint16":
1542
+ case "int32":
1543
+ case "uint32":
1544
+ case "int64":
1545
+ case "uint64":
1546
+ case "float32":
1547
+ case "float64":
1548
+ typeofTarget = "number";
1549
+ if (isNaN(value)) {
1550
+ console.log(`trying to encode "NaN" in ${klass.constructor.name}#${field}`);
1551
+ }
1552
+ break;
1553
+ case "string":
1554
+ typeofTarget = "string";
1555
+ allowNull = true;
1556
+ break;
1557
+ case "boolean":
1558
+ // boolean is always encoded as true/false based on truthiness
1559
+ return;
1560
+ }
1561
+ if (typeof (value) !== typeofTarget && (!allowNull || (allowNull && value !== null))) {
1562
+ let foundValue = `'${JSON.stringify(value)}'${(value && value.constructor && ` (${value.constructor.name})`) || ''}`;
1563
+ throw new EncodeSchemaError(`a '${typeofTarget}' was expected, but ${foundValue} was provided in ${klass.constructor.name}#${field}`);
1564
+ }
1565
+ }
1566
+ function assertInstanceType(value, type, instance, field) {
1567
+ if (!(value instanceof type)) {
1568
+ throw new EncodeSchemaError(`a '${type.name}' was expected, but '${value && value.constructor.name}' was provided in ${instance.constructor.name}#${field}`);
1569
+ }
1570
+ }
1571
+
1483
1572
  var _a$4, _b$4;
1484
1573
  const DEFAULT_SORT = (a, b) => {
1485
1574
  const A = a.toString();
@@ -1545,6 +1634,7 @@ class ArraySchema {
1545
1634
  }
1546
1635
  else {
1547
1636
  if (setValue[$changes]) {
1637
+ assertInstanceType(setValue, obj[$childType], obj, key);
1548
1638
  if (obj.items[key] !== undefined) {
1549
1639
  if (setValue[$changes][$isNew]) {
1550
1640
  this[$changes].indexedOperation(Number(key), OPERATION.MOVE_AND_ADD);
@@ -1591,6 +1681,7 @@ class ArraySchema {
1591
1681
  }
1592
1682
  });
1593
1683
  this[$changes] = new ChangeTree(proxy);
1684
+ this[$changes].indexes = {};
1594
1685
  this.push.apply(this, items);
1595
1686
  return proxy;
1596
1687
  }
@@ -1615,9 +1706,11 @@ class ArraySchema {
1615
1706
  if (value === undefined || value === null) {
1616
1707
  return;
1617
1708
  }
1709
+ else if (typeof (value) === "object" && this[$childType]) {
1710
+ assertInstanceType(value, this[$childType], this, i);
1711
+ }
1618
1712
  const changeTree = this[$changes];
1619
1713
  changeTree.indexedOperation(length, OPERATION.ADD, this.items.length);
1620
- // changeTree.indexes[length] = length;
1621
1714
  this.items.push(value);
1622
1715
  this.tmpItems.push(value);
1623
1716
  //
@@ -1865,14 +1958,6 @@ class ArraySchema {
1865
1958
  lastIndexOf(searchElement, fromIndex = this.length - 1) {
1866
1959
  return this.items.lastIndexOf(searchElement, fromIndex);
1867
1960
  }
1868
- /**
1869
- * Determines whether all the members of an array satisfy the specified test.
1870
- * @param callbackfn A function that accepts up to three arguments. The every method calls
1871
- * the callbackfn function for each element in the array until the callbackfn returns a value
1872
- * which is coercible to the Boolean value false, or until the end of the array.
1873
- * @param thisArg An object to which the this keyword can refer in the callbackfn function.
1874
- * If thisArg is omitted, undefined is used as the this value.
1875
- */
1876
1961
  every(callbackfn, thisArg) {
1877
1962
  return this.items.every(callbackfn, thisArg);
1878
1963
  }
@@ -2151,6 +2236,7 @@ class MapSchema {
2151
2236
  this.$items = new Map();
2152
2237
  this.$indexes = new Map();
2153
2238
  this[$changes] = new ChangeTree(this);
2239
+ this[$changes].indexes = {};
2154
2240
  if (initialValues) {
2155
2241
  if (initialValues instanceof Map ||
2156
2242
  initialValues instanceof MapSchema) {
@@ -2177,6 +2263,9 @@ class MapSchema {
2177
2263
  if (value === undefined || value === null) {
2178
2264
  throw new Error(`MapSchema#set('${key}', ${value}): trying to set ${value} value on '${key}'.`);
2179
2265
  }
2266
+ else if (typeof (value) === "object" && this[$childType]) {
2267
+ assertInstanceType(value, this[$childType], this, key);
2268
+ }
2180
2269
  // Force "key" as string
2181
2270
  // See: https://github.com/colyseus/colyseus/issues/561#issuecomment-1646733468
2182
2271
  key = key.toString();
@@ -2316,7 +2405,6 @@ class MapSchema {
2316
2405
  }
2317
2406
  registerType("map", { constructor: MapSchema });
2318
2407
 
2319
- const DEFAULT_VIEW_TAG = -1;
2320
2408
  class TypeContext {
2321
2409
  /**
2322
2410
  * For inheritance support
@@ -2338,6 +2426,7 @@ class TypeContext {
2338
2426
  this.types = {};
2339
2427
  this.schemas = new Map();
2340
2428
  this.hasFilters = false;
2429
+ this.parentFiltered = {};
2341
2430
  if (rootClass) {
2342
2431
  this.discoverTypes(rootClass);
2343
2432
  }
@@ -2354,44 +2443,51 @@ class TypeContext {
2354
2443
  return false;
2355
2444
  }
2356
2445
  this.types[typeid] = schema;
2446
+ //
2447
+ // Workaround to allow using an empty Schema (with no `@type()` fields)
2448
+ //
2449
+ if (schema[Symbol.metadata] === undefined) {
2450
+ Metadata.init(schema);
2451
+ }
2357
2452
  this.schemas.set(schema, typeid);
2358
2453
  return true;
2359
2454
  }
2360
2455
  getTypeId(klass) {
2361
2456
  return this.schemas.get(klass);
2362
2457
  }
2363
- discoverTypes(klass) {
2458
+ discoverTypes(klass, parentIndex, parentFieldViewTag) {
2364
2459
  if (!this.add(klass)) {
2365
2460
  return;
2366
2461
  }
2367
2462
  // add classes inherited from this base class
2368
2463
  TypeContext.inheritedTypes.get(klass)?.forEach((child) => {
2369
- this.discoverTypes(child);
2464
+ this.discoverTypes(child, parentIndex, parentFieldViewTag);
2370
2465
  });
2371
- // skip if no fields are defined for this class.
2372
- if (klass[Symbol.metadata] === undefined) {
2373
- klass[Symbol.metadata] = {};
2374
- }
2375
- // const metadata = Metadata.getFor(klass);
2376
- const metadata = klass[Symbol.metadata];
2466
+ const metadata = (klass[Symbol.metadata] ??= {});
2377
2467
  // if any schema/field has filters, mark "context" as having filters.
2378
2468
  if (metadata[-2]) {
2379
2469
  this.hasFilters = true;
2380
2470
  }
2381
- for (const field in metadata) {
2382
- const fieldType = metadata[field].type;
2471
+ if (parentFieldViewTag !== undefined) {
2472
+ this.parentFiltered[`${this.schemas.get(klass)}-${parentIndex}`] = true;
2473
+ }
2474
+ for (const fieldIndex in metadata) {
2475
+ const index = fieldIndex;
2476
+ const fieldType = metadata[index].type;
2477
+ const viewTag = metadata[index].tag;
2383
2478
  if (typeof (fieldType) === "string") {
2384
2479
  continue;
2385
2480
  }
2386
2481
  if (Array.isArray(fieldType)) {
2387
2482
  const type = fieldType[0];
2483
+ // skip primitive types
2388
2484
  if (type === "string") {
2389
2485
  continue;
2390
2486
  }
2391
- this.discoverTypes(type);
2487
+ this.discoverTypes(type, index, viewTag);
2392
2488
  }
2393
2489
  else if (typeof (fieldType) === "function") {
2394
- this.discoverTypes(fieldType);
2490
+ this.discoverTypes(fieldType, viewTag);
2395
2491
  }
2396
2492
  else {
2397
2493
  const type = Object.values(fieldType)[0];
@@ -2399,11 +2495,13 @@ class TypeContext {
2399
2495
  if (typeof (type) === "string") {
2400
2496
  continue;
2401
2497
  }
2402
- this.discoverTypes(type);
2498
+ this.discoverTypes(type, index, viewTag);
2403
2499
  }
2404
2500
  }
2405
2501
  }
2406
2502
  }
2503
+
2504
+ const DEFAULT_VIEW_TAG = -1;
2407
2505
  /**
2408
2506
  * [See documentation](https://docs.colyseus.io/state/schema/)
2409
2507
  *
@@ -2551,18 +2649,20 @@ function view(tag = DEFAULT_VIEW_TAG) {
2551
2649
  const constructor = target.constructor;
2552
2650
  const parentClass = Object.getPrototypeOf(constructor);
2553
2651
  const parentMetadata = parentClass[Symbol.metadata];
2652
+ // TODO: use Metadata.initialize()
2554
2653
  const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
2555
- if (!metadata[fieldName]) {
2556
- //
2557
- // detect index for this field, considering inheritance
2558
- //
2559
- metadata[fieldName] = {
2560
- type: undefined,
2561
- index: (metadata[-1] // current structure already has fields defined
2562
- ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
2563
- ?? -1) + 1 // no fields defined
2564
- };
2565
- }
2654
+ // const fieldIndex = metadata[fieldName];
2655
+ // if (!metadata[fieldIndex]) {
2656
+ // //
2657
+ // // detect index for this field, considering inheritance
2658
+ // //
2659
+ // metadata[fieldIndex] = {
2660
+ // type: undefined,
2661
+ // index: (metadata[-1] // current structure already has fields defined
2662
+ // ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
2663
+ // ?? -1) + 1 // no fields defined
2664
+ // }
2665
+ // }
2566
2666
  Metadata.setTag(metadata, fieldName, tag);
2567
2667
  };
2568
2668
  }
@@ -2575,18 +2675,18 @@ function type(type, options) {
2575
2675
  // for inheritance support
2576
2676
  TypeContext.register(constructor);
2577
2677
  const parentClass = Object.getPrototypeOf(constructor);
2578
- const parentMetadata = parentClass[Symbol.metadata];
2579
- const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
2580
- let fieldIndex;
2678
+ const parentMetadata = parentClass && parentClass[Symbol.metadata];
2679
+ const metadata = Metadata.initialize(constructor, parentMetadata);
2680
+ let fieldIndex = metadata[field];
2581
2681
  /**
2582
2682
  * skip if descriptor already exists for this field (`@deprecated()`)
2583
2683
  */
2584
- if (metadata[field]) {
2585
- if (metadata[field].deprecated) {
2684
+ if (metadata[fieldIndex]) {
2685
+ if (metadata[fieldIndex].deprecated) {
2586
2686
  // do not create accessors for deprecated properties.
2587
2687
  return;
2588
2688
  }
2589
- else if (metadata[field].descriptor !== undefined) {
2689
+ else if (metadata[fieldIndex].type !== undefined) {
2590
2690
  // trying to define same property multiple times across inheritance.
2591
2691
  // https://github.com/colyseus/colyseus-unity3d/issues/131#issuecomment-814308572
2592
2692
  try {
@@ -2597,9 +2697,6 @@ function type(type, options) {
2597
2697
  throw new Error(`${e.message} ${definitionAtLine}`);
2598
2698
  }
2599
2699
  }
2600
- else {
2601
- fieldIndex = metadata[field].index;
2602
- }
2603
2700
  }
2604
2701
  else {
2605
2702
  //
@@ -2625,11 +2722,11 @@ function type(type, options) {
2625
2722
  const childType = (complexTypeKlass)
2626
2723
  ? Object.values(type)[0]
2627
2724
  : type;
2628
- Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass, metadata, field));
2725
+ Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass));
2629
2726
  }
2630
2727
  };
2631
2728
  }
2632
- function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass, metadata, field) {
2729
+ function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass) {
2633
2730
  return {
2634
2731
  get: function () { return this[fieldCached]; },
2635
2732
  set: function (value) {
@@ -2651,22 +2748,27 @@ function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass,
2651
2748
  }
2652
2749
  value[$childType] = type;
2653
2750
  }
2751
+ else if (typeof (type) !== "string") {
2752
+ assertInstanceType(value, type, this, fieldCached.substring(1));
2753
+ }
2754
+ else {
2755
+ assertType(value, type, this, fieldCached.substring(1));
2756
+ }
2757
+ const changeTree = this[$changes];
2654
2758
  //
2655
2759
  // Replacing existing "ref", remove it from root.
2656
2760
  // TODO: if there are other references to this instance, we should not remove it from root.
2657
2761
  //
2658
2762
  if (previousValue !== undefined && previousValue[$changes]) {
2659
- this[$changes].root?.remove(previousValue[$changes]);
2763
+ changeTree.root?.remove(previousValue[$changes]);
2660
2764
  }
2661
2765
  // flag the change for encoding.
2662
- this.constructor[$track](this[$changes], fieldIndex, OPERATION.ADD);
2766
+ this.constructor[$track](changeTree, fieldIndex, OPERATION.ADD);
2663
2767
  //
2664
2768
  // call setParent() recursively for this and its child
2665
2769
  // structures.
2666
2770
  //
2667
- if (value[$changes]) {
2668
- value[$changes].setParent(this, this[$changes].root, metadata[field].index);
2669
- }
2771
+ value[$changes]?.setParent(this, changeTree.root, fieldIndex);
2670
2772
  }
2671
2773
  else if (previousValue !== undefined) {
2672
2774
  //
@@ -2693,20 +2795,22 @@ function deprecated(throws = true) {
2693
2795
  const parentClass = Object.getPrototypeOf(constructor);
2694
2796
  const parentMetadata = parentClass[Symbol.metadata];
2695
2797
  const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
2696
- if (!metadata[field]) {
2697
- //
2698
- // detect index for this field, considering inheritance
2699
- //
2700
- metadata[field] = {
2701
- type: undefined,
2702
- index: (metadata[-1] // current structure already has fields defined
2703
- ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
2704
- ?? -1) + 1 // no fields defined
2705
- };
2706
- }
2707
- metadata[field].deprecated = true;
2798
+ const fieldIndex = metadata[field];
2799
+ // if (!metadata[field]) {
2800
+ // //
2801
+ // // detect index for this field, considering inheritance
2802
+ // //
2803
+ // metadata[field] = {
2804
+ // type: undefined,
2805
+ // index: (metadata[-1] // current structure already has fields defined
2806
+ // ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
2807
+ // ?? -1) + 1 // no fields defined
2808
+ // }
2809
+ // }
2810
+ metadata[fieldIndex].deprecated = true;
2708
2811
  if (throws) {
2709
- metadata[field].descriptor = {
2812
+ metadata[$descriptors] ??= {};
2813
+ metadata[$descriptors][field] = {
2710
2814
  get: function () { throw new Error(`${field} is deprecated.`); },
2711
2815
  set: function (value) { },
2712
2816
  enumerable: false,
@@ -2714,8 +2818,8 @@ function deprecated(throws = true) {
2714
2818
  };
2715
2819
  }
2716
2820
  // flag metadata[field] as non-enumerable
2717
- Object.defineProperty(metadata, field, {
2718
- value: metadata[field],
2821
+ Object.defineProperty(metadata, fieldIndex, {
2822
+ value: metadata[fieldIndex],
2719
2823
  enumerable: false,
2720
2824
  configurable: true
2721
2825
  });
@@ -2781,35 +2885,7 @@ class Schema {
2781
2885
  enumerable: false,
2782
2886
  writable: true
2783
2887
  });
2784
- const metadata = instance.constructor[Symbol.metadata];
2785
- // Define property descriptors
2786
- for (const field in metadata) {
2787
- if (metadata[field].descriptor) {
2788
- // for encoder
2789
- Object.defineProperty(instance, `_${field}`, {
2790
- value: undefined,
2791
- writable: true,
2792
- enumerable: false,
2793
- configurable: true,
2794
- });
2795
- Object.defineProperty(instance, field, metadata[field].descriptor);
2796
- }
2797
- else {
2798
- // for decoder
2799
- Object.defineProperty(instance, field, {
2800
- value: undefined,
2801
- writable: true,
2802
- enumerable: true,
2803
- configurable: true,
2804
- });
2805
- }
2806
- // Object.defineProperty(instance, field, {
2807
- // ...instance.constructor[Symbol.metadata][field].descriptor
2808
- // });
2809
- // if (args[0]?.hasOwnProperty(field)) {
2810
- // instance[field] = args[0][field];
2811
- // }
2812
- }
2888
+ Object.defineProperties(instance, instance.constructor[Symbol.metadata]?.[$descriptors] || {});
2813
2889
  }
2814
2890
  static is(type) {
2815
2891
  return typeof (type[Symbol.metadata]) === "object";
@@ -2833,7 +2909,7 @@ class Schema {
2833
2909
  */
2834
2910
  static [$filter](ref, index, view) {
2835
2911
  const metadata = ref.constructor[Symbol.metadata];
2836
- const tag = metadata[metadata[index]].tag;
2912
+ const tag = metadata[index]?.tag;
2837
2913
  if (view === undefined) {
2838
2914
  // shared pass/encode: encode if doesn't have a tag
2839
2915
  return tag === undefined;
@@ -2854,12 +2930,21 @@ class Schema {
2854
2930
  }
2855
2931
  // allow inherited classes to have a constructor
2856
2932
  constructor(...args) {
2857
- Schema.initialize(this);
2933
+ //
2934
+ // inline
2935
+ // Schema.initialize(this);
2936
+ //
2937
+ Object.defineProperty(this, $changes, {
2938
+ value: new ChangeTree(this),
2939
+ enumerable: false,
2940
+ writable: true
2941
+ });
2942
+ Object.defineProperties(this, this.constructor[Symbol.metadata]?.[$descriptors] || {});
2858
2943
  //
2859
2944
  // Assign initial values
2860
2945
  //
2861
2946
  if (args[0]) {
2862
- this.assign(args[0]);
2947
+ Object.assign(this, args[0]);
2863
2948
  }
2864
2949
  }
2865
2950
  assign(props) {
@@ -2873,7 +2958,8 @@ class Schema {
2873
2958
  * @param operation OPERATION to perform (detected automatically)
2874
2959
  */
2875
2960
  setDirty(property, operation) {
2876
- this[$changes].change(this.constructor[Symbol.metadata][property].index, operation);
2961
+ const metadata = this.constructor[Symbol.metadata];
2962
+ this[$changes].change(metadata[metadata[property]].index, operation);
2877
2963
  }
2878
2964
  clone() {
2879
2965
  const cloned = new (this.constructor);
@@ -2882,7 +2968,9 @@ class Schema {
2882
2968
  // TODO: clone all properties, not only annotated ones
2883
2969
  //
2884
2970
  // for (const field in this) {
2885
- for (const field in metadata) {
2971
+ for (const fieldIndex in metadata) {
2972
+ // const field = metadata[metadata[fieldIndex]].name;
2973
+ const field = metadata[fieldIndex].name;
2886
2974
  if (typeof (this[field]) === "object" &&
2887
2975
  typeof (this[field]?.clone) === "function") {
2888
2976
  // deep clone
@@ -2896,10 +2984,11 @@ class Schema {
2896
2984
  return cloned;
2897
2985
  }
2898
2986
  toJSON() {
2899
- const metadata = this.constructor[Symbol.metadata];
2900
2987
  const obj = {};
2901
- for (const fieldName in metadata) {
2902
- const field = metadata[fieldName];
2988
+ const metadata = this.constructor[Symbol.metadata];
2989
+ for (const index in metadata) {
2990
+ const field = metadata[index];
2991
+ const fieldName = field.name;
2903
2992
  if (!field.deprecated && this[fieldName] !== null && typeof (this[fieldName]) !== "undefined") {
2904
2993
  obj[fieldName] = (typeof (this[fieldName]['toJSON']) === "function")
2905
2994
  ? this[fieldName]['toJSON']()
@@ -2912,10 +3001,12 @@ class Schema {
2912
3001
  this[$changes].discardAll();
2913
3002
  }
2914
3003
  [$getByIndex](index) {
2915
- return this[this.constructor[Symbol.metadata][index]];
3004
+ const metadata = this.constructor[Symbol.metadata];
3005
+ return this[metadata[index].name];
2916
3006
  }
2917
3007
  [$deleteByIndex](index) {
2918
- this[this.constructor[Symbol.metadata][index]] = undefined;
3008
+ const metadata = this.constructor[Symbol.metadata];
3009
+ this[metadata[index].name] = undefined;
2919
3010
  }
2920
3011
  static debugRefIds(instance, jsonContents = true, level = 0) {
2921
3012
  const ref = instance;
@@ -2957,13 +3048,13 @@ class Schema {
2957
3048
  }
2958
3049
  return output;
2959
3050
  }
2960
- static debugChangesDeep(ref) {
3051
+ static debugChangesDeep(ref, changeSetName = "changes") {
2961
3052
  let output = "";
2962
3053
  const rootChangeTree = ref[$changes];
2963
3054
  const changeTrees = new Map();
2964
3055
  let totalInstances = 0;
2965
3056
  let totalOperations = 0;
2966
- for (const [changeTree, changes] of (rootChangeTree.root.changes.entries())) {
3057
+ for (const [changeTree, changes] of (rootChangeTree.root[changeSetName].entries())) {
2967
3058
  let includeChangeTree = false;
2968
3059
  let parentChangeTrees = [];
2969
3060
  let parentChangeTree = changeTree.parent?.[$changes];
@@ -3039,6 +3130,7 @@ class CollectionSchema {
3039
3130
  this.$indexes = new Map();
3040
3131
  this.$refId = 0;
3041
3132
  this[$changes] = new ChangeTree(this);
3133
+ this[$changes].indexes = {};
3042
3134
  if (initialValues) {
3043
3135
  initialValues.forEach((v) => this.add(v));
3044
3136
  }
@@ -3194,6 +3286,7 @@ class SetSchema {
3194
3286
  this.$indexes = new Map();
3195
3287
  this.$refId = 0;
3196
3288
  this[$changes] = new ChangeTree(this);
3289
+ this[$changes].indexes = {};
3197
3290
  if (initialValues) {
3198
3291
  initialValues.forEach((v) => this.add(v));
3199
3292
  }
@@ -3349,6 +3442,8 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
3349
3442
  OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
3350
3443
  PERFORMANCE OF THIS SOFTWARE.
3351
3444
  ***************************************************************************** */
3445
+ /* global Reflect, Promise, SuppressedError, Symbol */
3446
+
3352
3447
 
3353
3448
  function __decorate(decorators, target, key, desc) {
3354
3449
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -3362,37 +3457,82 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
3362
3457
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
3363
3458
  };
3364
3459
 
3460
+ class Root {
3461
+ constructor(types) {
3462
+ this.types = types;
3463
+ this.nextUniqueId = 0;
3464
+ this.refCount = new WeakMap();
3465
+ // all changes
3466
+ this.allChanges = new Map();
3467
+ this.allFilteredChanges = new Map();
3468
+ // pending changes to be encoded
3469
+ this.changes = new Map();
3470
+ this.filteredChanges = new Map();
3471
+ }
3472
+ getNextUniqueId() {
3473
+ return this.nextUniqueId++;
3474
+ }
3475
+ add(changeTree) {
3476
+ const refCount = this.refCount.get(changeTree) || 0;
3477
+ this.refCount.set(changeTree, refCount + 1);
3478
+ }
3479
+ remove(changeTree) {
3480
+ const refCount = this.refCount.get(changeTree);
3481
+ if (refCount <= 1) {
3482
+ this.allChanges.delete(changeTree);
3483
+ this.changes.delete(changeTree);
3484
+ if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
3485
+ this.allFilteredChanges.delete(changeTree);
3486
+ this.filteredChanges.delete(changeTree);
3487
+ }
3488
+ this.refCount.delete(changeTree);
3489
+ }
3490
+ else {
3491
+ this.refCount.set(changeTree, refCount - 1);
3492
+ }
3493
+ changeTree.forEachChild((child, _) => this.remove(child));
3494
+ }
3495
+ clear() {
3496
+ this.changes.clear();
3497
+ }
3498
+ }
3499
+
3365
3500
  class Encoder {
3366
3501
  static { this.BUFFER_SIZE = 8 * 1024; } // 8KB
3367
- constructor(root) {
3502
+ constructor(state) {
3368
3503
  this.sharedBuffer = Buffer.allocUnsafeSlow(Encoder.BUFFER_SIZE);
3369
- this.setRoot(root);
3370
3504
  //
3371
3505
  // TODO: cache and restore "Context" based on root schema
3372
3506
  // (to avoid creating a new context for every new room)
3373
3507
  //
3374
- this.context = new TypeContext(root.constructor);
3508
+ this.context = new TypeContext(state.constructor);
3509
+ this.root = new Root(this.context);
3510
+ this.setState(state);
3375
3511
  // console.log(">>>>>>>>>>>>>>>> Encoder types");
3376
3512
  // this.context.schemas.forEach((id, schema) => {
3377
3513
  // console.log("type:", id, schema.name, Object.keys(schema[Symbol.metadata]));
3378
3514
  // });
3379
3515
  }
3380
- setRoot(state) {
3381
- this.root = new Root();
3516
+ setState(state) {
3382
3517
  this.state = state;
3383
- state[$changes].setRoot(this.root);
3518
+ this.state[$changes].setRoot(this.root);
3384
3519
  }
3385
- encode(it = { offset: 0 }, view, bytes = this.sharedBuffer, changeTrees = this.root.changes) {
3386
- const initialOffset = it.offset; // cache current offset in case we need to resize the buffer
3387
- const isEncodeAll = this.root.allChanges === changeTrees;
3520
+ encode(it = { offset: 0 }, view, buffer = this.sharedBuffer, changeTrees = this.root.changes, isEncodeAll = this.root.allChanges === changeTrees, initialOffset = it.offset // cache current offset in case we need to resize the buffer
3521
+ ) {
3388
3522
  const hasView = (view !== undefined);
3389
3523
  const rootChangeTree = this.state[$changes];
3390
- const changeTreesIterator = changeTrees.entries();
3391
- for (const [changeTree, changes] of changeTreesIterator) {
3524
+ const shouldClearChanges = !isEncodeAll && !hasView;
3525
+ for (const [changeTree, changes] of changeTrees.entries()) {
3392
3526
  const ref = changeTree.ref;
3393
3527
  const ctor = ref['constructor'];
3394
3528
  const encoder = ctor[$encoder];
3395
3529
  const filter = ctor[$filter];
3530
+ // try { throw new Error(); } catch (e) {
3531
+ // // only print if not coming from Reflection.ts
3532
+ // if (!e.stack.includes("src/Reflection.ts")) {
3533
+ // console.log("ChangeTree:", { ref: ref.constructor.name, });
3534
+ // }
3535
+ // }
3396
3536
  if (hasView) {
3397
3537
  if (!view.items.has(changeTree)) {
3398
3538
  view.invisible.add(changeTree);
@@ -3403,9 +3543,10 @@ class Encoder {
3403
3543
  }
3404
3544
  }
3405
3545
  // skip root `refId` if it's the first change tree
3406
- if (it.offset !== initialOffset || changeTree !== rootChangeTree) {
3407
- bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
3408
- number$1(bytes, changeTree.refId, it);
3546
+ // (unless it "hasView", which will need to revisit the root)
3547
+ if (hasView || it.offset > initialOffset || changeTree !== rootChangeTree) {
3548
+ buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
3549
+ number$1(buffer, changeTree.refId, it);
3409
3550
  }
3410
3551
  const changesIterator = changes.entries();
3411
3552
  for (const [fieldIndex, operation] of changesIterator) {
@@ -3417,74 +3558,94 @@ class Encoder {
3417
3558
  // TODO: avoid checking if no view tags were defined
3418
3559
  //
3419
3560
  if (filter && !filter(ref, fieldIndex, view)) {
3420
- // console.log("SKIP FIELD:", { ref: changeTree.ref.constructor.name, fieldIndex, })
3421
3561
  // console.log("ADD AS INVISIBLE:", fieldIndex, changeTree.ref.constructor.name)
3422
3562
  // view?.invisible.add(changeTree);
3423
3563
  continue;
3424
3564
  }
3425
- // console.log("WILL ENCODE", {
3426
- // ref: changeTree.ref.constructor.name,
3427
- // fieldIndex,
3428
- // operation: OPERATION[operation],
3429
- // });
3430
- encoder(this, bytes, changeTree, fieldIndex, operation, it, isEncodeAll, hasView);
3565
+ // try { throw new Error(); } catch (e) {
3566
+ // // only print if not coming from Reflection.ts
3567
+ // if (!e.stack.includes("src/Reflection.ts")) {
3568
+ // console.log("WILL ENCODE", {
3569
+ // ref: changeTree.ref.constructor.name,
3570
+ // fieldIndex,
3571
+ // operation: OPERATION[operation],
3572
+ // });
3573
+ // }
3574
+ // }
3575
+ encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView);
3431
3576
  }
3577
+ // if (shouldClearChanges) {
3578
+ // changeTree.endEncode();
3579
+ // }
3432
3580
  }
3433
- if (it.offset > bytes.byteLength) {
3434
- const newSize = getNextPowerOf2(this.sharedBuffer.byteLength * 2);
3435
- console.warn("@colyseus/schema encode buffer overflow. Current buffer size: " + bytes.byteLength + ", encoding offset: " + it.offset + ", new size: " + newSize);
3581
+ if (it.offset > buffer.byteLength) {
3582
+ const newSize = getNextPowerOf2(buffer.byteLength * 2);
3583
+ console.warn(`@colyseus/schema buffer overflow. Encoded state is higher than default BUFFER_SIZE. Use the following to increase default BUFFER_SIZE:
3584
+
3585
+ import { Encoder } from "@colyseus/schema";
3586
+ Encoder.BUFFER_SIZE = ${Math.round(newSize / 1024)} * 1024; // ${Math.round(newSize / 1024)} KB
3587
+ `);
3436
3588
  //
3437
3589
  // resize buffer and re-encode (TODO: can we avoid re-encoding here?)
3438
3590
  //
3439
- this.sharedBuffer = Buffer.allocUnsafeSlow(newSize);
3440
- return this.encode({ offset: initialOffset }, view);
3591
+ buffer = Buffer.allocUnsafeSlow(newSize);
3592
+ // assign resized buffer to local sharedBuffer
3593
+ if (buffer === this.sharedBuffer) {
3594
+ this.sharedBuffer = buffer;
3595
+ }
3596
+ return this.encode({ offset: initialOffset }, view, buffer, changeTrees, isEncodeAll);
3441
3597
  }
3442
3598
  else {
3443
3599
  //
3444
3600
  // only clear changes after making sure buffer resize is not required.
3445
3601
  //
3446
- if (!isEncodeAll && !hasView) {
3602
+ if (shouldClearChanges) {
3447
3603
  //
3448
3604
  // FIXME: avoid iterating over change trees twice.
3449
3605
  //
3450
3606
  this.onEndEncode(changeTrees);
3451
3607
  }
3452
- // return bytes;
3453
- return bytes.slice(0, it.offset);
3608
+ return buffer.subarray(0, it.offset);
3454
3609
  }
3455
3610
  }
3456
- encodeAll(it = { offset: 0 }) {
3457
- // console.log(`encodeAll(), this.$root.allChanges (${this.$root.allChanges.size})`);
3458
- // Array.from(this.$root.allChanges.entries()).map((item) => {
3459
- // console.log("->", item[0].refId, item[0].ref.toJSON());
3460
- // });
3461
- return this.encode(it, undefined, this.sharedBuffer, this.root.allChanges);
3611
+ encodeAll(it = { offset: 0 }, buffer = this.sharedBuffer) {
3612
+ // console.log(`\nencodeAll(), this.root.allChanges (${this.root.allChanges.size})`);
3613
+ // this.debugChanges("allChanges");
3614
+ return this.encode(it, undefined, buffer, this.root.allChanges, true);
3462
3615
  }
3463
3616
  encodeAllView(view, sharedOffset, it, bytes = this.sharedBuffer) {
3464
3617
  const viewOffset = it.offset;
3465
- // console.log(`encodeAllView(), this.$root.allFilteredChanges (${this.$root.allFilteredChanges.size})`);
3466
- // this.debugAllFilteredChanges();
3618
+ // console.log(`\nencodeAllView(), this.root.allFilteredChanges (${this.root.allFilteredChanges.size})`);
3619
+ // this.debugChanges("allFilteredChanges");
3467
3620
  // try to encode "filtered" changes
3468
- this.encode(it, view, bytes, this.root.allFilteredChanges);
3621
+ this.encode(it, view, bytes, this.root.allFilteredChanges, true, viewOffset);
3469
3622
  return Buffer.concat([
3470
- bytes.slice(0, sharedOffset),
3471
- bytes.slice(viewOffset, it.offset)
3623
+ bytes.subarray(0, sharedOffset),
3624
+ bytes.subarray(viewOffset, it.offset)
3472
3625
  ]);
3473
3626
  }
3474
- // debugAllFilteredChanges() {
3475
- // Array.from(this.$root.allFilteredChanges.entries()).map((item) => {
3476
- // console.log("->", { refId: item[0].refId }, item[0].ref.toJSON());
3477
- // if (Array.isArray(item[0].ref.toJSON())) {
3478
- // item[1].forEach((op, key) => {
3479
- // console.log(" ->", { key, op: OPERATION[op] });
3480
- // })
3481
- // }
3482
- // });
3483
- // }
3627
+ debugChanges(field) {
3628
+ const changeSet = (typeof (field) === "string")
3629
+ ? this.root[field]
3630
+ : field;
3631
+ Array.from(changeSet.entries()).map((item) => {
3632
+ const metadata = item[0].ref.constructor[Symbol.metadata];
3633
+ console.log("->", { ref: item[0].ref.constructor.name, refId: item[0].refId, changes: item[1].size });
3634
+ item[1].forEach((op, index) => {
3635
+ console.log(" ->", {
3636
+ index,
3637
+ field: metadata?.[index],
3638
+ op: OPERATION[op],
3639
+ });
3640
+ });
3641
+ });
3642
+ }
3484
3643
  encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
3485
3644
  const viewOffset = it.offset;
3486
- // try to encode "filtered" changes
3487
- this.encode(it, view, bytes, this.root.filteredChanges);
3645
+ // console.log(`\nencodeView(), view.changes (${view.changes.size})`);
3646
+ // this.debugChanges(view.changes);
3647
+ // console.log(`\nencodeView(), this.root.filteredChanges (${this.root.filteredChanges.size})`);
3648
+ // this.debugChanges("filteredChanges");
3488
3649
  // encode visibility changes (add/remove for this view)
3489
3650
  const viewChangesIterator = view.changes.entries();
3490
3651
  for (const [changeTree, changes] of viewChangesIterator) {
@@ -3511,15 +3672,22 @@ class Encoder {
3511
3672
  //
3512
3673
  // clear "view" changes after encoding
3513
3674
  view.changes.clear();
3675
+ // try to encode "filtered" changes
3676
+ this.encode(it, view, bytes, this.root.filteredChanges, false, viewOffset);
3514
3677
  return Buffer.concat([
3515
- bytes.slice(0, sharedOffset),
3516
- bytes.slice(viewOffset, it.offset)
3678
+ bytes.subarray(0, sharedOffset),
3679
+ bytes.subarray(viewOffset, it.offset)
3517
3680
  ]);
3518
3681
  }
3519
3682
  onEndEncode(changeTrees = this.root.changes) {
3520
3683
  const changeTreesIterator = changeTrees.entries();
3521
3684
  for (const [changeTree, _] of changeTreesIterator) {
3522
3685
  changeTree.endEncode();
3686
+ // changeTree.changes.clear();
3687
+ // // ArraySchema and MapSchema have a custom "encode end" method
3688
+ // changeTree.ref[$onEncodeEnd]?.();
3689
+ // // Not a new instance anymore
3690
+ // delete changeTree[$isNew];
3523
3691
  }
3524
3692
  }
3525
3693
  discardChanges() {
@@ -3635,8 +3803,9 @@ class ReferenceTracker {
3635
3803
  // Ensure child schema instances have their references removed as well.
3636
3804
  //
3637
3805
  if (Metadata.isValidInstance(ref)) {
3638
- const metadata = ref['constructor'][Symbol.metadata];
3639
- for (const field in metadata) {
3806
+ const metadata = ref.constructor[Symbol.metadata];
3807
+ for (const index in metadata) {
3808
+ const field = metadata[index].name;
3640
3809
  const childRefId = typeof (ref[field]) === "object" && this.refIds.get(ref[field]);
3641
3810
  if (childRefId) {
3642
3811
  this.removeRef(childRefId);
@@ -3683,21 +3852,21 @@ class ReferenceTracker {
3683
3852
  class Decoder {
3684
3853
  constructor(root, context) {
3685
3854
  this.currentRefId = 0;
3686
- this.setRoot(root);
3855
+ this.setState(root);
3687
3856
  this.context = context || new TypeContext(root.constructor);
3688
3857
  // console.log(">>>>>>>>>>>>>>>> Decoder types");
3689
3858
  // this.context.schemas.forEach((id, schema) => {
3690
3859
  // console.log("type:", id, schema.name, Object.keys(schema[Symbol.metadata]));
3691
3860
  // });
3692
3861
  }
3693
- setRoot(root) {
3862
+ setState(root) {
3694
3863
  this.state = root;
3695
- this.$root = new ReferenceTracker();
3696
- this.$root.addRef(0, root);
3864
+ this.root = new ReferenceTracker();
3865
+ this.root.addRef(0, root);
3697
3866
  }
3698
3867
  decode(bytes, it = { offset: 0 }, ref = this.state) {
3699
3868
  const allChanges = [];
3700
- const $root = this.$root;
3869
+ const $root = this.root;
3701
3870
  const totalBytes = bytes.byteLength;
3702
3871
  let decoder = ref['constructor'][$decoder];
3703
3872
  this.currentRefId = 0;
@@ -3778,7 +3947,7 @@ class Decoder {
3778
3947
  previousValue: value
3779
3948
  });
3780
3949
  if (needRemoveRef) {
3781
- this.$root.removeRef(this.$root.refIds.get(value));
3950
+ this.root.removeRef(this.root.refIds.get(value));
3782
3951
  }
3783
3952
  });
3784
3953
  }
@@ -3818,14 +3987,14 @@ class Reflection extends Schema {
3818
3987
  super(...arguments);
3819
3988
  this.types = new ArraySchema();
3820
3989
  }
3821
- static encode(instance, context) {
3822
- if (!context) {
3823
- context = new TypeContext(instance.constructor);
3824
- }
3990
+ static encode(instance, context, it = { offset: 0 }) {
3991
+ context ??= new TypeContext(instance.constructor);
3825
3992
  const reflection = new Reflection();
3826
3993
  const encoder = new Encoder(reflection);
3827
3994
  const buildType = (currentType, metadata) => {
3828
- for (const fieldName in metadata) {
3995
+ for (const fieldIndex in metadata) {
3996
+ const index = Number(fieldIndex);
3997
+ const fieldName = metadata[index].name;
3829
3998
  // skip fields from parent classes
3830
3999
  if (!Object.prototype.hasOwnProperty.call(metadata, fieldName)) {
3831
4000
  continue;
@@ -3833,7 +4002,7 @@ class Reflection extends Schema {
3833
4002
  const field = new ReflectionField();
3834
4003
  field.name = fieldName;
3835
4004
  let fieldType;
3836
- const type = metadata[fieldName].type;
4005
+ const type = metadata[index].type;
3837
4006
  if (typeof (type) === "string") {
3838
4007
  fieldType = type;
3839
4008
  }
@@ -3875,7 +4044,6 @@ class Reflection extends Schema {
3875
4044
  }
3876
4045
  buildType(type, klass[Symbol.metadata]);
3877
4046
  }
3878
- const it = { offset: 0 };
3879
4047
  const buf = encoder.encodeAll(it);
3880
4048
  return Buffer.from(buf, 0, it.offset);
3881
4049
  }
@@ -3883,59 +4051,304 @@ class Reflection extends Schema {
3883
4051
  const reflection = new Reflection();
3884
4052
  const reflectionDecoder = new Decoder(reflection);
3885
4053
  reflectionDecoder.decode(bytes, it);
3886
- const context = new TypeContext();
3887
- const schemaTypes = reflection.types.reduce((types, reflectionType) => {
3888
- const parentKlass = types[reflectionType.extendsId] || Schema;
3889
- const schema = class _ extends parentKlass {
4054
+ const typeContext = new TypeContext();
4055
+ // 1st pass, initialize metadata + inheritance
4056
+ reflection.types.forEach((reflectionType) => {
4057
+ const parentClass = typeContext.get(reflectionType.extendsId) ?? Schema;
4058
+ const schema = class _ extends parentClass {
3890
4059
  };
3891
- // const _metadata = Object.create(_classSuper[Symbol.metadata] ?? null);
3892
- const _metadata = parentKlass && parentKlass[Symbol.metadata] || Object.create(null);
3893
- Object.defineProperty(schema, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
4060
+ const parentMetadata = parentClass[Symbol.metadata];
3894
4061
  // register for inheritance support
3895
4062
  TypeContext.register(schema);
3896
- const typeid = reflectionType.id;
3897
- types[typeid] = schema;
3898
- context.add(schema, typeid);
3899
- return types;
4063
+ // for inheritance support
4064
+ Metadata.initialize(schema, parentMetadata);
4065
+ typeContext.add(schema, reflectionType.id);
3900
4066
  }, {});
4067
+ // 2nd pass, set fields
3901
4068
  reflection.types.forEach((reflectionType) => {
3902
- const schemaType = schemaTypes[reflectionType.id];
4069
+ const schemaType = typeContext.get(reflectionType.id);
3903
4070
  const metadata = schemaType[Symbol.metadata];
3904
- const parentKlass = reflection.types[reflectionType.extendsId];
3905
- const parentFieldIndex = parentKlass && parentKlass.fields.length || 0;
4071
+ const parentFieldIndex = 0;
3906
4072
  reflectionType.fields.forEach((field, i) => {
3907
4073
  const fieldIndex = parentFieldIndex + i;
3908
4074
  if (field.referencedType !== undefined) {
3909
4075
  let fieldType = field.type;
3910
- let refType = schemaTypes[field.referencedType];
4076
+ let refType = typeContext.get(field.referencedType);
3911
4077
  // map or array of primitive type (-1)
3912
4078
  if (!refType) {
3913
4079
  const typeInfo = field.type.split(":");
3914
4080
  fieldType = typeInfo[0];
3915
- refType = typeInfo[1];
4081
+ refType = typeInfo[1]; // string
3916
4082
  }
3917
4083
  if (fieldType === "ref") {
3918
- // type(refType)(schemaType.prototype, field.name);
3919
4084
  Metadata.addField(metadata, fieldIndex, field.name, refType);
3920
4085
  }
3921
4086
  else {
3922
- // type({ [fieldType]: refType } as DefinitionType)(schemaType.prototype, field.name);
3923
4087
  Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType });
3924
4088
  }
3925
4089
  }
3926
4090
  else {
3927
- // type(field.type as PrimitiveType)(schemaType.prototype, field.name);
3928
4091
  Metadata.addField(metadata, fieldIndex, field.name, field.type);
3929
4092
  }
3930
4093
  });
3931
4094
  });
3932
- return new (schemaTypes[0])();
4095
+ // @ts-ignore
4096
+ return new (typeContext.get(0))();
3933
4097
  }
3934
4098
  }
3935
4099
  __decorate([
3936
4100
  type([ReflectionType])
3937
4101
  ], Reflection.prototype, "types", void 0);
3938
4102
 
4103
+ function getDecoderStateCallbacks(decoder) {
4104
+ const $root = decoder.root;
4105
+ const callbacks = $root.callbacks;
4106
+ const onAddCalls = new WeakMap();
4107
+ let currentOnAddCallback;
4108
+ decoder.triggerChanges = function (allChanges) {
4109
+ const uniqueRefIds = new Set();
4110
+ for (let i = 0, l = allChanges.length; i < l; i++) {
4111
+ const change = allChanges[i];
4112
+ const refId = change.refId;
4113
+ const ref = change.ref;
4114
+ const $callbacks = callbacks[refId];
4115
+ if (!$callbacks) {
4116
+ continue;
4117
+ }
4118
+ //
4119
+ // trigger onRemove on child structure.
4120
+ //
4121
+ if ((change.op & OPERATION.DELETE) === OPERATION.DELETE &&
4122
+ change.previousValue instanceof Schema) {
4123
+ const deleteCallbacks = callbacks[$root.refIds.get(change.previousValue)]?.[OPERATION.DELETE];
4124
+ for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
4125
+ deleteCallbacks[i]();
4126
+ }
4127
+ }
4128
+ if (ref instanceof Schema) {
4129
+ //
4130
+ // Handle schema instance
4131
+ //
4132
+ if (!uniqueRefIds.has(refId)) {
4133
+ // trigger onChange
4134
+ const replaceCallbacks = $callbacks?.[OPERATION.REPLACE];
4135
+ for (let i = replaceCallbacks?.length - 1; i >= 0; i--) {
4136
+ replaceCallbacks[i]();
4137
+ // try {
4138
+ // } catch (e) {
4139
+ // console.error(e);
4140
+ // }
4141
+ }
4142
+ }
4143
+ if ($callbacks.hasOwnProperty(change.field)) {
4144
+ const fieldCallbacks = $callbacks[change.field];
4145
+ for (let i = fieldCallbacks?.length - 1; i >= 0; i--) {
4146
+ fieldCallbacks[i](change.value, change.previousValue);
4147
+ // try {
4148
+ // } catch (e) {
4149
+ // console.error(e);
4150
+ // }
4151
+ }
4152
+ }
4153
+ }
4154
+ else {
4155
+ //
4156
+ // Handle collection of items
4157
+ //
4158
+ if ((change.op & OPERATION.DELETE) === OPERATION.DELETE) {
4159
+ //
4160
+ // FIXME: `previousValue` should always be available.
4161
+ //
4162
+ if (change.previousValue !== undefined) {
4163
+ // triger onRemove
4164
+ const deleteCallbacks = $callbacks[OPERATION.DELETE];
4165
+ for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
4166
+ deleteCallbacks[i](change.previousValue, change.dynamicIndex ?? change.field);
4167
+ }
4168
+ }
4169
+ // Handle DELETE_AND_ADD operations
4170
+ if ((change.op & OPERATION.ADD) === OPERATION.ADD) {
4171
+ const addCallbacks = $callbacks[OPERATION.ADD];
4172
+ for (let i = addCallbacks?.length - 1; i >= 0; i--) {
4173
+ addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
4174
+ }
4175
+ }
4176
+ }
4177
+ else if ((change.op & OPERATION.ADD) === OPERATION.ADD && change.previousValue === undefined) {
4178
+ // triger onAdd
4179
+ const addCallbacks = $callbacks[OPERATION.ADD];
4180
+ for (let i = addCallbacks?.length - 1; i >= 0; i--) {
4181
+ addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
4182
+ }
4183
+ }
4184
+ // trigger onChange
4185
+ if (change.value !== change.previousValue) {
4186
+ const replaceCallbacks = $callbacks[OPERATION.REPLACE];
4187
+ for (let i = replaceCallbacks?.length - 1; i >= 0; i--) {
4188
+ replaceCallbacks[i](change.value, change.dynamicIndex ?? change.field);
4189
+ }
4190
+ }
4191
+ }
4192
+ uniqueRefIds.add(refId);
4193
+ }
4194
+ };
4195
+ function getProxy(metadataOrType, context) {
4196
+ let metadata = context.instance?.constructor[Symbol.metadata] || metadataOrType;
4197
+ let isCollection = ((context.instance && typeof (context.instance['forEach']) === "function") ||
4198
+ (metadataOrType && typeof (metadataOrType[Symbol.metadata]) === "undefined"));
4199
+ if (metadata && !isCollection) {
4200
+ const onAddListen = function (ref, prop, callback, immediate) {
4201
+ // immediate trigger
4202
+ if (immediate &&
4203
+ context.instance[prop] !== undefined &&
4204
+ !onAddCalls.has(currentOnAddCallback) // Workaround for https://github.com/colyseus/schema/issues/147
4205
+ ) {
4206
+ callback(context.instance[prop], undefined);
4207
+ }
4208
+ return $root.addCallback($root.refIds.get(ref), prop, callback);
4209
+ };
4210
+ /**
4211
+ * Schema instances
4212
+ */
4213
+ return new Proxy({
4214
+ listen: function listen(prop, callback, immediate = true) {
4215
+ if (context.instance) {
4216
+ return onAddListen(context.instance, prop, callback, immediate);
4217
+ }
4218
+ else {
4219
+ // collection instance not received yet
4220
+ let detachCallback = () => { };
4221
+ context.onInstanceAvailable((ref, existing) => {
4222
+ detachCallback = onAddListen(ref, prop, callback, immediate && existing && !onAddCalls.has(currentOnAddCallback));
4223
+ });
4224
+ return () => detachCallback();
4225
+ }
4226
+ },
4227
+ onChange: function onChange(callback) {
4228
+ return $root.addCallback($root.refIds.get(context.instance), OPERATION.REPLACE, callback);
4229
+ },
4230
+ //
4231
+ // TODO: refactor `bindTo()` implementation.
4232
+ // There is room for improvement.
4233
+ //
4234
+ bindTo: function bindTo(targetObject, properties) {
4235
+ if (!properties) {
4236
+ properties = Object.keys(metadata).map((index) => metadata[index].name);
4237
+ }
4238
+ return $root.addCallback($root.refIds.get(context.instance), OPERATION.REPLACE, () => {
4239
+ properties.forEach((prop) => targetObject[prop] = context.instance[prop]);
4240
+ });
4241
+ }
4242
+ }, {
4243
+ get(target, prop) {
4244
+ const metadataField = metadata[metadata[prop]];
4245
+ if (metadataField) {
4246
+ const instance = context.instance?.[prop];
4247
+ const onInstanceAvailable = ((callback) => {
4248
+ const unbind = $(context.instance).listen(prop, (value, _) => {
4249
+ callback(value, false);
4250
+ // FIXME: by "unbinding" the callback here,
4251
+ // it will not support when the server
4252
+ // re-instantiates the instance.
4253
+ //
4254
+ unbind?.();
4255
+ }, false);
4256
+ // has existing value
4257
+ if ($root.refIds.get(instance) !== undefined) {
4258
+ callback(instance, true);
4259
+ }
4260
+ });
4261
+ return getProxy(metadataField.type, {
4262
+ // make sure refId is available, otherwise need to wait for the instance to be available.
4263
+ instance: ($root.refIds.get(instance) && instance),
4264
+ parentInstance: context.instance,
4265
+ onInstanceAvailable,
4266
+ });
4267
+ }
4268
+ else {
4269
+ // accessing the function
4270
+ return target[prop];
4271
+ }
4272
+ },
4273
+ has(target, prop) { return metadata[prop] !== undefined; },
4274
+ set(_, _1, _2) { throw new Error("not allowed"); },
4275
+ deleteProperty(_, _1) { throw new Error("not allowed"); },
4276
+ });
4277
+ }
4278
+ else {
4279
+ /**
4280
+ * Collection instances
4281
+ */
4282
+ const onAdd = function (ref, callback, immediate) {
4283
+ // Trigger callback on existing items
4284
+ if (immediate) {
4285
+ ref.forEach((v, k) => callback(v, k));
4286
+ }
4287
+ return $root.addCallback($root.refIds.get(ref), OPERATION.ADD, (value, key) => {
4288
+ onAddCalls.set(callback, true);
4289
+ currentOnAddCallback = callback;
4290
+ callback(value, key);
4291
+ onAddCalls.delete(callback);
4292
+ currentOnAddCallback = undefined;
4293
+ });
4294
+ };
4295
+ const onRemove = function (ref, callback) {
4296
+ return $root.addCallback($root.refIds.get(ref), OPERATION.DELETE, callback);
4297
+ };
4298
+ return new Proxy({
4299
+ onAdd: function (callback, immediate = true) {
4300
+ //
4301
+ // https://github.com/colyseus/schema/issues/147
4302
+ // If parent instance has "onAdd" registered, avoid triggering immediate callback.
4303
+ //
4304
+ if (context.instance) {
4305
+ return onAdd(context.instance, callback, immediate && !onAddCalls.has(currentOnAddCallback));
4306
+ }
4307
+ else if (context.onInstanceAvailable) {
4308
+ // collection instance not received yet
4309
+ let detachCallback = () => { };
4310
+ context.onInstanceAvailable((ref, existing) => {
4311
+ detachCallback = onAdd(ref, callback, immediate && existing && !onAddCalls.has(currentOnAddCallback));
4312
+ });
4313
+ return () => detachCallback();
4314
+ }
4315
+ },
4316
+ onRemove: function (callback) {
4317
+ if (context.onInstanceAvailable) {
4318
+ // collection instance not received yet
4319
+ let detachCallback = () => { };
4320
+ context.onInstanceAvailable((ref) => {
4321
+ detachCallback = onRemove(ref, callback);
4322
+ });
4323
+ return () => detachCallback();
4324
+ }
4325
+ else if (context.instance) {
4326
+ return onRemove(context.instance, callback);
4327
+ }
4328
+ },
4329
+ }, {
4330
+ get(target, prop) {
4331
+ if (!target[prop]) {
4332
+ throw new Error(`Can't access '${prop}' through callback proxy. access the instance directly.`);
4333
+ }
4334
+ return target[prop];
4335
+ },
4336
+ has(target, prop) { return target[prop] !== undefined; },
4337
+ set(_, _1, _2) { throw new Error("not allowed"); },
4338
+ deleteProperty(_, _1) { throw new Error("not allowed"); },
4339
+ });
4340
+ }
4341
+ }
4342
+ function $(instance) {
4343
+ return getProxy(undefined, { instance });
4344
+ }
4345
+ return $;
4346
+ }
4347
+
4348
+ function getRawChangesCallback(decoder, callback) {
4349
+ decoder.triggerChanges = callback;
4350
+ }
4351
+
3939
4352
  class StateView {
3940
4353
  constructor() {
3941
4354
  /**
@@ -3953,20 +4366,21 @@ class StateView {
3953
4366
  this.changes = new Map();
3954
4367
  }
3955
4368
  // TODO: allow to set multiple tags at once
3956
- add(obj, tag = DEFAULT_VIEW_TAG) {
4369
+ add(obj, tag = DEFAULT_VIEW_TAG, checkIncludeParent = true) {
3957
4370
  if (!obj[$changes]) {
3958
4371
  console.warn("StateView#add(), invalid object:", obj);
3959
4372
  return this;
3960
4373
  }
3961
- let changeTree = obj[$changes];
3962
- this.items.add(changeTree);
3963
- // Add children of this ChangeTree to this view
3964
- changeTree.forEachChild((change, _) => this.add(change.ref, tag));
3965
- // FIXME: ArraySchema/MapSchema does not have metadata
4374
+ // FIXME: ArraySchema/MapSchema do not have metadata
3966
4375
  const metadata = obj.constructor[Symbol.metadata];
3967
- // add parent ChangeTree's, if they are invisible to this view
3968
- // TODO: REFACTOR addParent()
3969
- this.addParent(changeTree, tag);
4376
+ const changeTree = obj[$changes];
4377
+ this.items.add(changeTree);
4378
+ // add parent ChangeTree's
4379
+ // - if it was invisible to this view
4380
+ // - if it were previously filtered out
4381
+ if (checkIncludeParent && changeTree.parent) {
4382
+ this.addParent(changeTree.parent[$changes], changeTree.parentIndex, tag);
4383
+ }
3970
4384
  //
3971
4385
  // TODO: when adding an item of a MapSchema, the changes may not
3972
4386
  // be set (only the parent's changes are set)
@@ -3990,7 +4404,6 @@ class StateView {
3990
4404
  tags = this.tags.get(changeTree);
3991
4405
  }
3992
4406
  tags.add(tag);
3993
- // console.log("BY TAG:", tag);
3994
4407
  // Ref: add tagged properties
3995
4408
  metadata?.[-3]?.[tag]?.forEach((index) => {
3996
4409
  if (changeTree.getChange(index) !== OPERATION.DELETE) {
@@ -3999,73 +4412,63 @@ class StateView {
3999
4412
  });
4000
4413
  }
4001
4414
  else {
4002
- // console.log("DEFAULT TAG", changeTree.allChanges);
4003
- // // add default tag properties
4004
- // metadata?.[-3]?.[DEFAULT_VIEW_TAG]?.forEach((index) => {
4005
- // if (changeTree.getChange(index) !== OPERATION.DELETE) {
4006
- // changes.set(index, OPERATION.ADD);
4007
- // }
4008
- // });
4009
- const allChangesSet = (changeTree.isFiltered || changeTree.isPartiallyFiltered)
4415
+ const isInvisible = this.invisible.has(changeTree);
4416
+ const changeSet = (changeTree.isFiltered || changeTree.isPartiallyFiltered)
4010
4417
  ? changeTree.allFilteredChanges
4011
4418
  : changeTree.allChanges;
4012
- const it = allChangesSet.keys();
4013
- const isInvisible = this.invisible.has(changeTree);
4014
- for (const index of it) {
4015
- if ((isInvisible || metadata?.[metadata?.[index]].tag === tag) &&
4016
- changeTree.getChange(index) !== OPERATION.DELETE) {
4017
- changes.set(index, OPERATION.ADD);
4419
+ changeSet.forEach((op, index) => {
4420
+ const tagAtIndex = metadata?.[index].tag;
4421
+ if ((isInvisible || // if "invisible", include all
4422
+ tagAtIndex === undefined || // "all change" with no tag
4423
+ tagAtIndex === tag // tagged property
4424
+ ) &&
4425
+ op !== OPERATION.DELETE) {
4426
+ changes.set(index, op);
4018
4427
  }
4019
- }
4020
- }
4021
- // TODO: avoid unnecessary iteration here
4022
- while (changeTree.parent &&
4023
- (changeTree = changeTree.parent[$changes]) &&
4024
- (changeTree.isFiltered || changeTree.isPartiallyFiltered)) {
4025
- this.items.add(changeTree);
4428
+ });
4026
4429
  }
4430
+ // Add children of this ChangeTree to this view
4431
+ changeTree.forEachChild((change, index) => {
4432
+ // Do not ADD children that don't have the same tag
4433
+ if (metadata && metadata[index].tag !== tag) {
4434
+ return;
4435
+ }
4436
+ this.add(change.ref, tag, false);
4437
+ });
4027
4438
  return this;
4028
4439
  }
4029
- addParent(changeTree, tag) {
4030
- const parentRef = changeTree.parent;
4031
- if (!parentRef) {
4032
- return;
4440
+ addParent(changeTree, parentIndex, tag) {
4441
+ // view must have all "changeTree" parent tree
4442
+ this.items.add(changeTree);
4443
+ // add parent's parent
4444
+ const parentChangeTree = changeTree.parent?.[$changes];
4445
+ if (parentChangeTree && (parentChangeTree.isFiltered || parentChangeTree.isPartiallyFiltered)) {
4446
+ this.addParent(parentChangeTree, changeTree.parentIndex, tag);
4033
4447
  }
4034
- const parentChangeTree = parentRef[$changes];
4035
- const parentIndex = changeTree.parentIndex;
4036
- if (!this.invisible.has(parentChangeTree)) {
4037
- // parent is already available, no need to add it!
4448
+ // parent is already available, no need to add it!
4449
+ if (!this.invisible.has(changeTree)) {
4038
4450
  return;
4039
4451
  }
4040
- this.addParent(parentChangeTree, tag);
4041
4452
  // add parent's tag properties
4042
- if (parentChangeTree.getChange(parentIndex) !== OPERATION.DELETE) {
4043
- let parentChanges = this.changes.get(parentChangeTree);
4044
- if (parentChanges === undefined) {
4045
- parentChanges = new Map();
4046
- this.changes.set(parentChangeTree, parentChanges);
4453
+ if (changeTree.getChange(parentIndex) !== OPERATION.DELETE) {
4454
+ let changes = this.changes.get(changeTree);
4455
+ if (changes === undefined) {
4456
+ changes = new Map();
4457
+ this.changes.set(changeTree, changes);
4047
4458
  }
4048
- // console.log("add parent change", {
4049
- // parentIndex,
4050
- // parentChanges,
4051
- // parentChange: (
4052
- // parentChangeTree.getChange(parentIndex) &&
4053
- // OPERATION[parentChangeTree.getChange(parentIndex)]
4054
- // ),
4055
- // })
4056
4459
  if (!this.tags) {
4057
4460
  this.tags = new WeakMap();
4058
4461
  }
4059
4462
  let tags;
4060
- if (!this.tags.has(parentChangeTree)) {
4463
+ if (!this.tags.has(changeTree)) {
4061
4464
  tags = new Set();
4062
- this.tags.set(parentChangeTree, tags);
4465
+ this.tags.set(changeTree, tags);
4063
4466
  }
4064
4467
  else {
4065
- tags = this.tags.get(parentChangeTree);
4468
+ tags = this.tags.get(changeTree);
4066
4469
  }
4067
4470
  tags.add(tag);
4068
- parentChanges.set(parentIndex, OPERATION.ADD);
4471
+ changes.set(parentIndex, OPERATION.ADD);
4069
4472
  }
4070
4473
  }
4071
4474
  remove(obj, tag = DEFAULT_VIEW_TAG) {
@@ -4129,5 +4532,5 @@ registerType("array", { constructor: ArraySchema });
4129
4532
  registerType("set", { constructor: SetSchema });
4130
4533
  registerType("collection", { constructor: CollectionSchema, });
4131
4534
 
4132
- export { $changes, $childType, $decoder, $deleteByIndex, $encoder, $filter, $getByIndex, $track, ArraySchema, ChangeTree, CollectionSchema, Decoder, Encoder, MapSchema, Metadata, OPERATION, Reflection, ReflectionField, ReflectionType, Schema, SetSchema, StateView, TypeContext, decode, decodeKeyValueOperation, decodeSchemaOperation, defineTypes, deprecated, dumpChanges, encode, encodeArray as encodeKeyValueOperation, encodeSchemaOperation, registerType, type, view };
4535
+ export { $changes, $childType, $decoder, $deleteByIndex, $encoder, $filter, $getByIndex, $track, ArraySchema, ChangeTree, CollectionSchema, Decoder, Encoder, MapSchema, Metadata, OPERATION, Reflection, ReflectionField, ReflectionType, Schema, SetSchema, StateView, TypeContext, decode, decodeKeyValueOperation, decodeSchemaOperation, defineTypes, deprecated, dumpChanges, encode, encodeArray as encodeKeyValueOperation, encodeSchemaOperation, getDecoderStateCallbacks, getRawChangesCallback, registerType, type, view };
4133
4536
  //# sourceMappingURL=index.mjs.map