@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
@@ -7,10 +7,10 @@ import type { ArraySchema } from "../types/custom/ArraySchema";
7
7
  import type { CollectionSchema } from "../types/custom/CollectionSchema";
8
8
  import type { SetSchema } from "../types/custom/SetSchema";
9
9
 
10
+ import { Root } from "./Root";
10
11
  import { Metadata } from "../Metadata";
11
12
  import type { EncodeOperation } from "./EncodeOperation";
12
13
  import type { DecodeOperation } from "../decoder/DecodeOperation";
13
- import type { StateView } from "./StateView";
14
14
 
15
15
  declare global {
16
16
  interface Object {
@@ -27,121 +27,85 @@ export type Ref = Schema
27
27
  | CollectionSchema
28
28
  | SetSchema;
29
29
 
30
- export class Root {
31
- protected nextUniqueId: number = 0;
32
- refCount = new WeakMap<ChangeTree, number>();
33
-
34
- // all changes
35
- allChanges = new Map<ChangeTree, Map<number, OPERATION>>();
36
- allFilteredChanges = new Map<ChangeTree, Map<number, OPERATION>>();
37
-
38
- // pending changes to be encoded
39
- changes = new Map<ChangeTree, Map<number, OPERATION>>();
40
- filteredChanges = new Map<ChangeTree, Map<number, OPERATION>>();
41
-
42
- getNextUniqueId() {
43
- return this.nextUniqueId++;
44
- }
45
-
46
- add (changeTree: ChangeTree) {
47
- const refCount = this.refCount.get(changeTree) || 0;
48
- this.refCount.set(changeTree, refCount + 1);
49
- }
50
-
51
- remove(changeTree: ChangeTree) {
52
- const refCount = this.refCount.get(changeTree);
53
- if (refCount <= 1) {
54
- this.allChanges.delete(changeTree);
55
- this.changes.delete(changeTree);
56
-
57
- if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
58
- this.allFilteredChanges.delete(changeTree);
59
- this.filteredChanges.delete(changeTree);
60
- }
61
-
62
- this.refCount.delete(changeTree);
63
-
64
- } else {
65
- this.refCount.set(changeTree, refCount - 1);
66
- }
67
-
68
- changeTree.forEachChild((child, _) =>
69
- this.remove(child));
70
- }
71
-
72
- clear() {
73
- this.changes.clear();
74
- }
75
- }
76
-
77
30
  export class ChangeTree<T extends Ref=any> {
78
31
  ref: T;
79
32
  refId: number;
80
33
 
81
34
  root?: Root;
82
-
83
- isFiltered?: boolean;
84
- isPartiallyFiltered?: boolean;
85
-
86
35
  parent?: Ref;
87
36
  parentIndex?: number;
88
37
 
89
- indexes: {[index: string]: any} = {}; // TODO: remove this, only used by MapSchema/SetSchema/CollectionSchema (`encodeKeyValueOperation`)
38
+ isFiltered: boolean = false;
39
+ isPartiallyFiltered: boolean = false;
40
+
90
41
  currentOperationIndex: number = 0;
91
42
 
43
+ changes = new Map<number, OPERATION>();
92
44
  allChanges = new Map<number, OPERATION>();
93
- allFilteredChanges = new Map<number, OPERATION>();
94
45
 
95
- changes = new Map<number, OPERATION>();
96
- filteredChanges = new Map<number, OPERATION>();;
46
+ allFilteredChanges: Map<number, OPERATION>;
47
+ filteredChanges: Map<number, OPERATION>;
48
+
49
+ indexes: {[index: string]: any}; // TODO: remove this, only used by MapSchema/SetSchema/CollectionSchema (`encodeKeyValueOperation`)
97
50
 
98
51
  [$isNew] = true;
99
52
 
100
53
  constructor(ref: T) {
101
54
  this.ref = ref;
55
+
56
+ //
57
+ // Does this structure have "filters" declared?
58
+ //
59
+ if (ref.constructor[Symbol.metadata]?.[-2]) {
60
+ this.allFilteredChanges = new Map<number, OPERATION>();
61
+ this.filteredChanges = new Map<number, OPERATION>();
62
+ }
102
63
  }
103
64
 
104
65
  setRoot(root: Root) {
105
66
  this.root = root;
106
67
  this.root.add(this);
107
68
 
108
- //
109
- // At Schema initialization, the "root" structure might not be available
110
- // yet, as it only does once the "Encoder" has been set up.
111
- //
112
- // So the "parent" may be already set without a "root".
113
- //
114
- this.checkIsFiltered(this.parent, this.parentIndex);
69
+ const metadata: Metadata = this.ref.constructor[Symbol.metadata];
115
70
 
116
- // unique refId for the ChangeTree.
117
- this.ensureRefId();
71
+ if (this.root.types.hasFilters) {
72
+ //
73
+ // At Schema initialization, the "root" structure might not be available
74
+ // yet, as it only does once the "Encoder" has been set up.
75
+ //
76
+ // So the "parent" may be already set without a "root".
77
+ //
78
+ this.checkIsFiltered(metadata, this.parent, this.parentIndex);
79
+
80
+ if (this.isFiltered || this.isPartiallyFiltered) {
81
+ this.root.allFilteredChanges.set(this, this.allFilteredChanges);
82
+ this.root.filteredChanges.set(this, this.filteredChanges);
83
+ }
84
+ }
118
85
 
119
86
  if (!this.isFiltered) {
120
87
  this.root.changes.set(this, this.changes);
88
+ this.root.allChanges.set(this, this.allChanges);
121
89
  }
122
90
 
123
- if (this.isFiltered || this.isPartiallyFiltered) {
124
- this.root.allFilteredChanges.set(this, this.allFilteredChanges);
125
- this.root.filteredChanges.set(this, this.filteredChanges);
91
+ this.ensureRefId();
126
92
 
127
- // } else {
128
- // this.root.allChanges.set(this, this.allChanges);
129
- }
93
+ if (metadata) {
94
+ metadata[-4]?.forEach((index) => {
95
+ const field = metadata[index as any as number];
96
+ const value = this.ref[field.name];
97
+ if (value) {
98
+ value[$changes].setRoot(root);
99
+ }
100
+ });
130
101
 
131
- if (!this.isFiltered) {
132
- this.root.allChanges.set(this, this.allChanges);
102
+ } else if (this.ref[$childType] && typeof(this.ref[$childType]) !== "string") {
103
+ // MapSchema / ArraySchema, etc.
104
+ (this.ref as MapSchema).forEach((value, key) => {
105
+ value[$changes].setRoot(root);
106
+ });
133
107
  }
134
108
 
135
- this.forEachChild((changeTree, _) => {
136
- changeTree.setRoot(root);
137
- });
138
-
139
- // this.allChanges.forEach((_, index) => {
140
- // const childRef = this.ref[$getByIndex](index);
141
- // if (childRef && childRef[$changes]) {
142
- // childRef[$changes].setRoot(root);
143
- // }
144
- // });
145
109
  }
146
110
 
147
111
  setParent(
@@ -157,57 +121,71 @@ export class ChangeTree<T extends Ref=any> {
157
121
 
158
122
  root.add(this);
159
123
 
124
+ const metadata: Metadata = this.ref.constructor[Symbol.metadata];
125
+
160
126
  // skip if parent is already set
161
- if (root === this.root) {
162
- this.forEachChild((changeTree, atIndex) => {
163
- changeTree.setParent(this.ref, root, atIndex);
164
- });
165
- return;
166
- }
127
+ if (root !== this.root) {
128
+ this.root = root;
167
129
 
168
- this.root = root;
169
- this.checkIsFiltered(parent, parentIndex);
130
+ if (root.types.hasFilters) {
131
+ this.checkIsFiltered(metadata, parent, parentIndex);
170
132
 
171
- if (!this.isFiltered) {
172
- this.root.changes.set(this, this.changes);
173
- }
133
+ if (this.isFiltered || this.isPartiallyFiltered) {
134
+ this.root.filteredChanges.set(this, this.filteredChanges);
135
+ this.root.allFilteredChanges.set(this, this.filteredChanges);
136
+ }
137
+ }
174
138
 
175
- if (this.isFiltered || this.isPartiallyFiltered) {
176
- this.root.filteredChanges.set(this, this.filteredChanges);
177
- this.root.allFilteredChanges.set(this, this.filteredChanges);
178
- } else {
179
- this.root.allChanges.set(this, this.allChanges);
139
+ if (!this.isFiltered) {
140
+ this.root.changes.set(this, this.changes);
141
+ this.root.allChanges.set(this, this.allChanges);
142
+ }
143
+
144
+ this.ensureRefId();
180
145
  }
181
146
 
182
- this.ensureRefId();
147
+ // assign same parent on child structures
148
+ if (metadata) {
149
+ metadata[-4]?.forEach((index) => {
150
+ const field = metadata[index as any as number];
151
+ const value = this.ref[field.name];
152
+ value?.[$changes].setParent(this.ref, root, index);
153
+
154
+ // console.log(this.ref.constructor.name, field.name, value);
155
+
156
+ // try { throw new Error(); } catch (e) {
157
+ // console.log(e.stack);
158
+ // }
159
+
160
+ });
161
+
162
+ } else if (this.ref[$childType] && typeof(this.ref[$childType]) !== "string") {
163
+ // MapSchema / ArraySchema, etc.
164
+ (this.ref as MapSchema).forEach((value, key) => {
165
+ value[$changes].setParent(this.ref, root, this.indexes[key] ?? key);
166
+ });
167
+ }
183
168
 
184
- this.forEachChild((changeTree, atIndex) => {
185
- changeTree.setParent(this.ref, root, atIndex);
186
- });
187
169
  }
188
170
 
189
171
  forEachChild(callback: (change: ChangeTree, atIndex: number) => void) {
190
172
  //
191
173
  // assign same parent on child structures
192
174
  //
193
- if (Metadata.isValidInstance(this.ref)) {
194
- const metadata: Metadata = this.ref['constructor'][Symbol.metadata];
195
-
196
- // FIXME: need to iterate over parent metadata instead.
197
- for (const field in metadata) {
198
- const value = this.ref[field];
199
-
200
- if (value && value[$changes]) {
201
- callback(value[$changes], metadata[field].index);
175
+ const metadata: Metadata = this.ref.constructor[Symbol.metadata];
176
+ if (metadata) {
177
+ metadata[-4]?.forEach((index) => {
178
+ const field = metadata[index as any as number];
179
+ const value = this.ref[field.name];
180
+ if (value) {
181
+ callback(value[$changes], index);
202
182
  }
203
- }
183
+ });
204
184
 
205
- } else if (typeof (this.ref) === "object") {
185
+ } else if (this.ref[$childType] && typeof(this.ref[$childType]) !== "string") {
206
186
  // MapSchema / ArraySchema, etc.
207
187
  (this.ref as MapSchema).forEach((value, key) => {
208
- if (Metadata.isValidInstance(value)) {
209
- callback(value[$changes], this.ref[$changes].indexes[key]);
210
- }
188
+ callback(value[$changes], this.indexes[key] ?? key);
211
189
  });
212
190
  }
213
191
  }
@@ -218,9 +196,9 @@ export class ChangeTree<T extends Ref=any> {
218
196
  }
219
197
 
220
198
  change(index: number, operation: OPERATION = OPERATION.ADD) {
221
- const metadata = this.ref['constructor'][Symbol.metadata] as Metadata;
199
+ const metadata = this.ref.constructor[Symbol.metadata] as Metadata;
222
200
 
223
- const isFiltered = this.isFiltered || (metadata && metadata[metadata[index]].tag !== undefined);
201
+ const isFiltered = this.isFiltered || (metadata?.[index]?.tag !== undefined);
224
202
  const changeSet = (isFiltered)
225
203
  ? this.filteredChanges
226
204
  : this.changes;
@@ -232,16 +210,16 @@ export class ChangeTree<T extends Ref=any> {
232
210
  : (previousOperation === OPERATION.DELETE)
233
211
  ? OPERATION.DELETE_AND_ADD
234
212
  : operation
213
+ //
214
+ // TODO: are DELETE operations being encoded as ADD here ??
215
+ //
235
216
  changeSet.set(index, op);
236
217
  }
237
218
 
238
- //
239
- // TODO: are DELETE operations being encoded as ADD here ??
240
- //
241
-
242
219
  if (isFiltered) {
243
220
  this.allFilteredChanges.set(index, OPERATION.ADD);
244
221
  this.root?.filteredChanges.set(this, this.filteredChanges);
222
+ this.root?.allFilteredChanges.set(this, this.allFilteredChanges);
245
223
 
246
224
  } else {
247
225
  this.allChanges.set(index, OPERATION.ADD);
@@ -285,7 +263,6 @@ export class ChangeTree<T extends Ref=any> {
285
263
 
286
264
  private _shiftAllChangeIndexes(shiftIndex: number, startIndex: number = 0, allChangeSet: Map<number, OPERATION>) {
287
265
  Array.from(allChangeSet.entries()).forEach(([index, op]) => {
288
- // console.log('shiftAllChangeIndexes', index >= startIndex, { index, op, shiftIndex, startIndex })
289
266
  if (index >= startIndex) {
290
267
  allChangeSet.delete(index);
291
268
  allChangeSet.set(index + shiftIndex, op);
@@ -294,10 +271,7 @@ export class ChangeTree<T extends Ref=any> {
294
271
  }
295
272
 
296
273
  indexedOperation(index: number, operation: OPERATION, allChangesIndex = index) {
297
- const metadata = this.ref['constructor'][Symbol.metadata] as Metadata;
298
- const isFiltered = this.isFiltered || (metadata && metadata[metadata[index]].tag !== undefined);
299
-
300
- if (isFiltered) {
274
+ if (this.filteredChanges !== undefined) {
301
275
  this.allFilteredChanges.set(allChangesIndex, OPERATION.ADD);
302
276
  this.filteredChanges.set(index, operation);
303
277
  this.root?.filteredChanges.set(this, this.filteredChanges);
@@ -311,8 +285,8 @@ export class ChangeTree<T extends Ref=any> {
311
285
 
312
286
  getType(index?: number) {
313
287
  if (Metadata.isValidInstance(this.ref)) {
314
- const metadata = this.ref['constructor'][Symbol.metadata] as Metadata;
315
- return metadata[metadata[index]].type;
288
+ const metadata = this.ref.constructor[Symbol.metadata] as Metadata;
289
+ return metadata[index].type;
316
290
 
317
291
  } else {
318
292
  //
@@ -327,7 +301,7 @@ export class ChangeTree<T extends Ref=any> {
327
301
 
328
302
  getChange(index: number) {
329
303
  // TODO: optimize this. avoid checking against multiple instances
330
- return this.changes.get(index) ?? this.filteredChanges.get(index);
304
+ return this.changes.get(index) ?? this.filteredChanges?.get(index);
331
305
  }
332
306
 
333
307
  //
@@ -350,9 +324,7 @@ export class ChangeTree<T extends Ref=any> {
350
324
  return;
351
325
  }
352
326
 
353
- const metadata = this.ref['constructor'][Symbol.metadata] as Metadata;
354
- const isFiltered = this.isFiltered || (metadata && metadata[metadata[index]].tag !== undefined);
355
- const changeSet = (isFiltered)
327
+ const changeSet = (this.filteredChanges)
356
328
  ? this.filteredChanges
357
329
  : this.changes;
358
330
 
@@ -380,7 +352,7 @@ export class ChangeTree<T extends Ref=any> {
380
352
  //
381
353
  // FIXME: this is looking a bit ugly (and repeated from `.change()`)
382
354
  //
383
- if (isFiltered) {
355
+ if (this.filteredChanges) {
384
356
  this.root?.filteredChanges.set(this, this.filteredChanges);
385
357
  this.allFilteredChanges.delete(allChangesIndex);
386
358
 
@@ -392,6 +364,8 @@ export class ChangeTree<T extends Ref=any> {
392
364
 
393
365
  endEncode() {
394
366
  this.changes.clear();
367
+
368
+ // ArraySchema and MapSchema have a custom "encode end" method
395
369
  this.ref[$onEncodeEnd]?.();
396
370
 
397
371
  // Not a new instance anymore
@@ -407,14 +381,14 @@ export class ChangeTree<T extends Ref=any> {
407
381
  this.ref[$onEncodeEnd]?.();
408
382
 
409
383
  this.changes.clear();
410
- this.filteredChanges.clear();
384
+ this.filteredChanges?.clear();
411
385
 
412
386
  // reset operation index
413
387
  this.currentOperationIndex = 0;
414
388
 
415
389
  if (discardAll) {
416
390
  this.allChanges.clear();
417
- this.allFilteredChanges.clear();
391
+ this.allFilteredChanges?.clear();
418
392
 
419
393
  // remove children references
420
394
  this.forEachChild((changeTree, _) =>
@@ -450,40 +424,47 @@ export class ChangeTree<T extends Ref=any> {
450
424
  return this.changes.size > 0;
451
425
  }
452
426
 
453
- protected checkIsFiltered(parent: Ref, parentIndex: number) {
427
+ protected checkIsFiltered(metadata: Metadata, parent: Ref, parentIndex: number) {
454
428
  // Detect if current structure has "filters" declared
455
- this.isPartiallyFiltered = this.ref['constructor']?.[Symbol.metadata]?.[-2];
456
-
457
- // TODO: support "partially filtered", where the instance is visible, but only a field is not.
429
+ this.isPartiallyFiltered = metadata?.[-2] !== undefined;
458
430
 
459
- // Detect if parent has "filters" declared
460
- while (parent && !this.isFiltered) {
461
- const metadata: Metadata = parent['constructor'][Symbol.metadata];
462
-
463
- const fieldName = metadata?.[parentIndex];
464
- const isParentOwned = metadata?.[fieldName]?.tag !== undefined;
431
+ if (this.isPartiallyFiltered) {
432
+ this.filteredChanges = this.filteredChanges || new Map<number, OPERATION>();
433
+ this.allFilteredChanges = this.allFilteredChanges || new Map<number, OPERATION>();
434
+ }
465
435
 
466
- this.isFiltered = isParentOwned || parent[$changes].isFiltered; // metadata?.[-2]
436
+ if (parent) {
437
+ if (!Metadata.isValidInstance(parent)) {
438
+ const parentChangeTree = parent[$changes];
439
+ parent = parentChangeTree.parent;
440
+ parentIndex = parentChangeTree.parentIndex;
441
+ }
467
442
 
468
- parent = parent[$changes].parent;
469
- };
443
+ const parentMetadata = parent?.constructor?.[Symbol.metadata];
444
+ this.isFiltered = (parent && parentMetadata?.[-2]?.includes(parentIndex));
470
445
 
471
- //
472
- // TODO: refactor this!
473
- //
474
- // swapping `changes` and `filteredChanges` is required here
475
- // because "isFiltered" may not be imedialely available on `change()`
476
- //
477
- if (this.isFiltered && this.changes.size > 0) {
478
- // swap changes reference
479
- const changes = this.changes;
480
- this.changes = this.filteredChanges;
481
- this.filteredChanges = changes;
482
-
483
- // swap "all changes" reference
484
- const allFilteredChanges = this.allFilteredChanges;
485
- this.allFilteredChanges = this.allChanges;
486
- this.allChanges = allFilteredChanges;
446
+ //
447
+ // TODO: refactor this!
448
+ //
449
+ // swapping `changes` and `filteredChanges` is required here
450
+ // because "isFiltered" may not be imedialely available on `change()`
451
+ //
452
+ if (this.isFiltered) {
453
+ this.filteredChanges = new Map<number, OPERATION>();
454
+ this.allFilteredChanges = new Map<number, OPERATION>();
455
+
456
+ if (this.changes.size > 0) {
457
+ // swap changes reference
458
+ const changes = this.changes;
459
+ this.changes = this.filteredChanges;
460
+ this.filteredChanges = changes;
461
+
462
+ // swap "all changes" reference
463
+ const allFilteredChanges = this.allFilteredChanges;
464
+ this.allFilteredChanges = this.allChanges;
465
+ this.allChanges = allFilteredChanges;
466
+ }
467
+ }
487
468
  }
488
469
  }
489
470