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