@colyseus/schema 3.0.0-alpha.3 → 3.0.0-alpha.30

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 (116) hide show
  1. package/README.md +131 -61
  2. package/build/cjs/index.js +644 -283
  3. package/build/cjs/index.js.map +1 -1
  4. package/build/esm/index.mjs +643 -282
  5. package/build/esm/index.mjs.map +1 -1
  6. package/build/umd/index.js +644 -283
  7. package/lib/Metadata.d.ts +2 -0
  8. package/lib/Metadata.js +39 -0
  9. package/lib/Metadata.js.map +1 -1
  10. package/lib/Reflection.d.ts +2 -3
  11. package/lib/Reflection.js +31 -26
  12. package/lib/Reflection.js.map +1 -1
  13. package/lib/Schema.d.ts +2 -2
  14. package/lib/Schema.js +2 -2
  15. package/lib/Schema.js.map +1 -1
  16. package/lib/annotations.d.ts +0 -19
  17. package/lib/annotations.js +15 -101
  18. package/lib/annotations.js.map +1 -1
  19. package/lib/bench_encode.d.ts +1 -0
  20. package/lib/bench_encode.js +120 -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 -7
  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/strategy/RawChanges.js +1 -2
  52. package/lib/decoder/strategy/RawChanges.js.map +1 -1
  53. package/lib/decoder/strategy/StateCallbacks.d.ts +44 -11
  54. package/lib/decoder/strategy/StateCallbacks.js +72 -63
  55. package/lib/decoder/strategy/StateCallbacks.js.map +1 -1
  56. package/lib/encoder/ChangeTree.d.ts +1 -12
  57. package/lib/encoder/ChangeTree.js +29 -58
  58. package/lib/encoder/ChangeTree.js.map +1 -1
  59. package/lib/encoder/EncodeOperation.d.ts +0 -1
  60. package/lib/encoder/EncodeOperation.js +29 -13
  61. package/lib/encoder/EncodeOperation.js.map +1 -1
  62. package/lib/encoder/Encoder.d.ts +10 -8
  63. package/lib/encoder/Encoder.js +78 -53
  64. package/lib/encoder/Encoder.js.map +1 -1
  65. package/lib/encoder/Root.d.ts +17 -0
  66. package/lib/encoder/Root.js +44 -0
  67. package/lib/encoder/Root.js.map +1 -0
  68. package/lib/encoder/StateView.d.ts +2 -2
  69. package/lib/encoder/StateView.js +48 -58
  70. package/lib/encoder/StateView.js.map +1 -1
  71. package/lib/encoding/assert.js +3 -3
  72. package/lib/encoding/assert.js.map +1 -1
  73. package/lib/encoding/decode.js +21 -22
  74. package/lib/encoding/decode.js.map +1 -1
  75. package/lib/encoding/encode.d.ts +2 -2
  76. package/lib/encoding/encode.js +40 -39
  77. package/lib/encoding/encode.js.map +1 -1
  78. package/lib/encoding/spec.d.ts +2 -1
  79. package/lib/encoding/spec.js +1 -0
  80. package/lib/encoding/spec.js.map +1 -1
  81. package/lib/index.d.ts +5 -1
  82. package/lib/index.js +8 -4
  83. package/lib/index.js.map +1 -1
  84. package/lib/types/TypeContext.d.ts +23 -0
  85. package/lib/types/TypeContext.js +109 -0
  86. package/lib/types/TypeContext.js.map +1 -0
  87. package/lib/types/custom/ArraySchema.d.ts +2 -2
  88. package/lib/types/custom/ArraySchema.js +0 -9
  89. package/lib/types/custom/ArraySchema.js.map +1 -1
  90. package/lib/types/registry.js +3 -4
  91. package/lib/types/registry.js.map +1 -1
  92. package/lib/types/utils.js +1 -2
  93. package/lib/types/utils.js.map +1 -1
  94. package/lib/utils.js +3 -4
  95. package/lib/utils.js.map +1 -1
  96. package/package.json +5 -5
  97. package/src/Metadata.ts +47 -0
  98. package/src/Reflection.ts +34 -26
  99. package/src/Schema.ts +2 -2
  100. package/src/annotations.ts +7 -109
  101. package/src/bench_encode.ts +97 -0
  102. package/src/debug.ts +56 -0
  103. package/src/decoder/DecodeOperation.ts +30 -7
  104. package/src/decoder/Decoder.ts +13 -11
  105. package/src/decoder/strategy/StateCallbacks.ts +149 -79
  106. package/src/encoder/ChangeTree.ts +36 -66
  107. package/src/encoder/EncodeOperation.ts +29 -12
  108. package/src/encoder/Encoder.ts +95 -61
  109. package/src/encoder/Root.ts +51 -0
  110. package/src/encoder/StateView.ts +51 -67
  111. package/src/encoding/decode.ts +1 -2
  112. package/src/encoding/encode.ts +25 -22
  113. package/src/encoding/spec.ts +1 -0
  114. package/src/index.ts +8 -11
  115. package/src/types/TypeContext.ts +127 -0
  116. package/src/types/custom/ArraySchema.ts +2 -2
@@ -6,7 +6,7 @@ import { DataChange } from "../DecodeOperation";
6
6
  import { OPERATION } from "../../encoding/spec";
7
7
  import { DefinitionType } from "../../annotations";
8
8
  import { Schema } from "../../Schema";
9
- import type { ArraySchema } from "../../types/custom/ArraySchema";
9
+ import type { CollectionSchema } from "../../types/custom/CollectionSchema";
10
10
 
11
11
  //
12
12
  // Discussion: https://github.com/colyseus/schema/issues/155
@@ -17,27 +17,74 @@ import type { ArraySchema } from "../../types/custom/ArraySchema";
17
17
  // - Avoid closures by allowing to pass a context. (https://github.com/colyseus/schema/issues/155#issuecomment-1804694081)
18
18
  //
19
19
 
20
- type GetProxyType<T> = unknown extends T // is "any"?
20
+ export type GetCallbackProxy = (<T extends Schema>(instance: T) => CallbackProxy<T>);
21
+
22
+ export type CallbackProxy<T> = unknown extends T // is "any"?
21
23
  ? InstanceCallback<T> & CollectionCallback<any, any>
22
24
  : T extends Collection<infer K, infer V, infer _>
23
25
  ? CollectionCallback<K, V>
24
- : InstanceCallback<T>
26
+ : InstanceCallback<T>;
25
27
 
26
28
  type InstanceCallback<T> = {
29
+ /**
30
+ * Trigger callback when value of a property changes.
31
+ *
32
+ * @param prop name of the property
33
+ * @param callback callback to be triggered on property change
34
+ * @param immediate trigger immediatelly if property has been already set.
35
+ * @return callback to detach the listener
36
+ */
27
37
  listen<K extends NonFunctionPropNames<T>>(
28
38
  prop: K,
29
39
  callback: (value: T[K], previousValue: T[K]) => void,
30
40
  immediate?: boolean,
31
- )
32
- onChange(callback: () => void): void;
41
+ ): () => void;
42
+
43
+ /**
44
+ * Trigger callback whenever any property changed within this instance.
45
+ *
46
+ * @param prop name of the property
47
+ * @param callback callback to be triggered on property change
48
+ * @param immediate trigger immediatelly if property has been already set.
49
+ * @return callback to detach the listener
50
+ */
51
+ onChange(callback: () => void): () => void;
52
+
53
+ /**
54
+ * Bind properties to another object. Changes on the properties will be reflected on the target object.
55
+ *
56
+ * @param targetObject object to bind properties to
57
+ * @param properties list of properties to bind. If not provided, all properties will be bound.
58
+ */
33
59
  bindTo(targetObject: any, properties?: Array<NonFunctionPropNames<T>>): void;
34
60
  } & {
35
- [K in NonFunctionNonPrimitivePropNames<T>]: GetProxyType<T[K]>;
61
+ [K in NonFunctionNonPrimitivePropNames<T>]: CallbackProxy<T[K]>;
36
62
  }
37
63
 
38
64
  type CollectionCallback<K, V> = {
39
- onAdd(callback: (item: V, index: K) => void, immediate?: boolean): void;
40
- onRemove(callback: (item: V, index: K) => void): void;
65
+ /**
66
+ * Trigger callback when an item has been added to the collection.
67
+ *
68
+ * @param callback
69
+ * @param immediate
70
+ * @return callback to detach the onAdd listener
71
+ */
72
+ onAdd(callback: (item: V, index: K) => void, immediate?: boolean): () => void;
73
+
74
+ /**
75
+ * Trigger callback when an item has been removed to the collection.
76
+ *
77
+ * @param callback
78
+ * @return callback to detach the onRemove listener
79
+ */
80
+ onRemove(callback: (item: V, index: K) => void): () => void;
81
+
82
+ // /**
83
+ // * Trigger callback when an item has been removed to the collection.
84
+ // *
85
+ // * @param callback
86
+ // */
87
+ // onChange(callback: (item: V, index: K) => void): void;
41
88
  };
42
89
 
43
90
  type OnInstanceAvailableCallback = (callback: (ref: Ref, existing: boolean) => void) => void;
@@ -48,11 +95,13 @@ type CallContext = {
48
95
  onInstanceAvailable?: OnInstanceAvailableCallback,
49
96
  }
50
97
 
51
- export function getStateCallbacks(decoder: Decoder) {
52
- const $root = decoder.$root;
98
+
99
+ export function getDecoderStateCallbacks<T extends Schema>(decoder: Decoder<T>): GetCallbackProxy {
100
+ const $root = decoder.root;
53
101
  const callbacks = $root.callbacks;
54
102
 
55
- let isTriggeringOnAdd = false;
103
+ const onAddCalls: WeakMap<Function, boolean> = new WeakMap();
104
+ let currentOnAddCallback: Function | undefined;
56
105
 
57
106
  decoder.triggerChanges = function (allChanges: DataChange[]) {
58
107
  const uniqueRefIds = new Set<number>();
@@ -76,8 +125,6 @@ export function getStateCallbacks(decoder: Decoder) {
76
125
  for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
77
126
  deleteCallbacks[i]();
78
127
  }
79
- // callbacks[$root.refIds.get(change.previousValue)]?.[OPERATION.DELETE]?.forEach(callback =>
80
- // callback());
81
128
  }
82
129
 
83
130
  if (ref instanceof Schema) {
@@ -86,47 +133,35 @@ export function getStateCallbacks(decoder: Decoder) {
86
133
  //
87
134
 
88
135
  if (!uniqueRefIds.has(refId)) {
89
- try {
90
- // trigger onChange
91
- const replaceCallbacks = $callbacks?.[OPERATION.REPLACE];
92
- for (let i = replaceCallbacks?.length - 1; i >= 0; i--) {
93
- replaceCallbacks[i]();
94
- }
95
-
96
- } catch (e) {
97
- console.error(e);
136
+ // trigger onChange
137
+ const replaceCallbacks = $callbacks?.[OPERATION.REPLACE];
138
+ for (let i = replaceCallbacks?.length - 1; i >= 0; i--) {
139
+ replaceCallbacks[i]();
140
+ // try {
141
+ // } catch (e) {
142
+ // console.error(e);
143
+ // }
98
144
  }
99
145
  }
100
146
 
101
- try {
102
- if ($callbacks.hasOwnProperty(change.field)) {
103
- const fieldCallbacks = $callbacks[change.field];
104
- for (let i = fieldCallbacks?.length - 1; i >= 0; i--) {
105
- fieldCallbacks[i](change.value, change.previousValue);
106
- }
147
+ if ($callbacks.hasOwnProperty(change.field)) {
148
+ const fieldCallbacks = $callbacks[change.field];
149
+ for (let i = fieldCallbacks?.length - 1; i >= 0; i--) {
150
+ fieldCallbacks[i](change.value, change.previousValue);
151
+ // try {
152
+ // } catch (e) {
153
+ // console.error(e);
154
+ // }
107
155
  }
108
-
109
- } catch (e) {
110
- //
111
- console.error(e);
112
156
  }
113
157
 
158
+
114
159
  } else {
115
160
  //
116
161
  // Handle collection of items
117
162
  //
118
163
 
119
- if (change.op === OPERATION.ADD && change.previousValue === undefined) {
120
- // triger onAdd
121
-
122
- isTriggeringOnAdd = true;
123
- const addCallbacks = $callbacks[OPERATION.ADD];
124
- for (let i = addCallbacks?.length - 1; i >= 0; i--) {
125
- addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
126
- }
127
- isTriggeringOnAdd = false;
128
-
129
- } else if ((change.op & OPERATION.DELETE) === OPERATION.DELETE) {
164
+ if ((change.op & OPERATION.DELETE) === OPERATION.DELETE) {
130
165
  //
131
166
  // FIXME: `previousValue` should always be available.
132
167
  //
@@ -139,13 +174,19 @@ export function getStateCallbacks(decoder: Decoder) {
139
174
  }
140
175
 
141
176
  // Handle DELETE_AND_ADD operations
142
- // FIXME: should we set "isTriggeringOnAdd" here?
143
177
  if ((change.op & OPERATION.ADD) === OPERATION.ADD) {
144
178
  const addCallbacks = $callbacks[OPERATION.ADD];
145
179
  for (let i = addCallbacks?.length - 1; i >= 0; i--) {
146
180
  addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
147
181
  }
148
182
  }
183
+
184
+ } else if ((change.op & OPERATION.ADD) === OPERATION.ADD && change.previousValue === undefined) {
185
+ // triger onAdd
186
+ const addCallbacks = $callbacks[OPERATION.ADD];
187
+ for (let i = addCallbacks?.length - 1; i >= 0; i--) {
188
+ addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
189
+ }
149
190
  }
150
191
 
151
192
  // trigger onChange
@@ -161,7 +202,10 @@ export function getStateCallbacks(decoder: Decoder) {
161
202
  }
162
203
  };
163
204
 
164
- function getProxy(metadataOrType: Metadata | DefinitionType, context: CallContext) {
205
+ function getProxy(
206
+ metadataOrType: Metadata | DefinitionType,
207
+ context: CallContext
208
+ ) {
165
209
  let metadata: Metadata = context.instance?.constructor[Symbol.metadata] || metadataOrType;
166
210
  let isCollection = (
167
211
  (context.instance && typeof (context.instance['forEach']) === "function") ||
@@ -170,7 +214,7 @@ export function getStateCallbacks(decoder: Decoder) {
170
214
 
171
215
  if (metadata && !isCollection) {
172
216
 
173
- const onAdd = function (
217
+ const onAddListen = function (
174
218
  ref: Ref,
175
219
  prop: string,
176
220
  callback: (value: any, previousValue: any) => void, immediate: boolean
@@ -179,7 +223,7 @@ export function getStateCallbacks(decoder: Decoder) {
179
223
  if (
180
224
  immediate &&
181
225
  context.instance[prop] !== undefined &&
182
- !isTriggeringOnAdd // FIXME: This is a workaround (https://github.com/colyseus/schema/issues/147)
226
+ !onAddCalls.has(currentOnAddCallback) // Workaround for https://github.com/colyseus/schema/issues/147
183
227
  ) {
184
228
  callback(context.instance[prop], undefined);
185
229
  }
@@ -192,24 +236,44 @@ export function getStateCallbacks(decoder: Decoder) {
192
236
  return new Proxy({
193
237
  listen: function listen(prop: string, callback: (value: any, previousValue: any) => void, immediate: boolean = true) {
194
238
  if (context.instance) {
195
- return onAdd(context.instance, prop, callback, immediate);
239
+ return onAddListen(context.instance, prop, callback, immediate);
196
240
 
197
241
  } else {
198
242
  // collection instance not received yet
199
- context.onInstanceAvailable((ref: Ref, existing: boolean) =>
200
- onAdd(ref, prop, callback, immediate && existing));
243
+ let detachCallback = () => {};
244
+
245
+ context.onInstanceAvailable((ref: Ref, existing: boolean) => {
246
+ detachCallback = onAddListen(ref, prop, callback, immediate && existing && !onAddCalls.has(currentOnAddCallback))
247
+ });
248
+
249
+ return () => detachCallback();
201
250
  }
202
251
  },
252
+
203
253
  onChange: function onChange(callback: () => void) {
204
254
  return $root.addCallback(
205
255
  $root.refIds.get(context.instance),
206
256
  OPERATION.REPLACE,
207
257
  callback
208
258
  );
209
-
210
259
  },
260
+
261
+ //
262
+ // TODO: refactor `bindTo()` implementation.
263
+ // There is room for improvement.
264
+ //
211
265
  bindTo: function bindTo(targetObject: any, properties?: string[]) {
212
- console.log("bindTo", targetObject, properties);
266
+ if (!properties) {
267
+ properties = Object.keys(metadata);
268
+ }
269
+ return $root.addCallback(
270
+ $root.refIds.get(context.instance),
271
+ OPERATION.REPLACE,
272
+ () => {
273
+ properties.forEach((prop) =>
274
+ targetObject[prop] = context.instance[prop])
275
+ }
276
+ );
213
277
  }
214
278
  }, {
215
279
  get(target, prop: string) {
@@ -234,7 +298,8 @@ export function getStateCallbacks(decoder: Decoder) {
234
298
  }
235
299
  );
236
300
  return getProxy(metadata[prop].type, {
237
- instance,
301
+ // make sure refId is available, otherwise need to wait for the instance to be available.
302
+ instance: ($root.refIds.get(instance) && instance),
238
303
  parentInstance: context.instance,
239
304
  onInstanceAvailable,
240
305
  });
@@ -257,9 +322,16 @@ export function getStateCallbacks(decoder: Decoder) {
257
322
  const onAdd = function (ref: Ref, callback: (value: any, key: any) => void, immediate: boolean) {
258
323
  // Trigger callback on existing items
259
324
  if (immediate) {
260
- (ref as ArraySchema).forEach((v, k) => callback(v, k));
325
+ (ref as CollectionSchema).forEach((v, k) => callback(v, k));
261
326
  }
262
- return $root.addCallback($root.refIds.get(ref), OPERATION.ADD, callback);
327
+
328
+ return $root.addCallback($root.refIds.get(ref), OPERATION.ADD, (value, key) => {
329
+ onAddCalls.set(callback, true);
330
+ currentOnAddCallback = callback;
331
+ callback(value, key);
332
+ onAddCalls.delete(callback)
333
+ currentOnAddCallback = undefined;
334
+ });
263
335
  };
264
336
 
265
337
  const onRemove = function (ref: Ref, callback: (value: any, key: any) => void) {
@@ -272,25 +344,34 @@ export function getStateCallbacks(decoder: Decoder) {
272
344
  // https://github.com/colyseus/schema/issues/147
273
345
  // If parent instance has "onAdd" registered, avoid triggering immediate callback.
274
346
  //
275
- // FIXME: "isTriggeringOnAdd" is a workaround. We should find a better way to handle this.
276
- //
277
- if (context.onInstanceAvailable) {
347
+
348
+ if (context.instance) {
349
+ return onAdd(context.instance, callback, immediate && !onAddCalls.has(currentOnAddCallback));
350
+
351
+ } else if (context.onInstanceAvailable) {
278
352
  // collection instance not received yet
279
- context.onInstanceAvailable((ref: Ref, existing: boolean) =>
280
- onAdd(ref, callback, immediate && existing && !isTriggeringOnAdd));
353
+ let detachCallback = () => {};
281
354
 
282
- } else if (context.instance) {
283
- onAdd(context.instance, callback, immediate && !isTriggeringOnAdd);
355
+ context.onInstanceAvailable((ref: Ref, existing: boolean) => {
356
+ detachCallback = onAdd(ref, callback, immediate && existing && !onAddCalls.has(currentOnAddCallback));
357
+ });
358
+
359
+ return () => detachCallback();
284
360
  }
285
361
  },
286
362
  onRemove: function(callback: (value, key) => void) {
287
363
  if (context.onInstanceAvailable) {
288
364
  // collection instance not received yet
289
- context.onInstanceAvailable((ref: Ref) =>
290
- onRemove(ref, callback));
365
+ let detachCallback = () => {};
366
+
367
+ context.onInstanceAvailable((ref: Ref) => {
368
+ detachCallback = onRemove(ref, callback)
369
+ });
370
+
371
+ return () => detachCallback();
291
372
 
292
373
  } else if (context.instance) {
293
- onRemove(context.instance, callback);
374
+ return onRemove(context.instance, callback);
294
375
  }
295
376
  },
296
377
  }, {
@@ -307,20 +388,9 @@ export function getStateCallbacks(decoder: Decoder) {
307
388
  }
308
389
  }
309
390
 
310
- function $<T>(instance: T): GetProxyType<T> {
311
- return getProxy(undefined, { instance }) as GetProxyType<T>;
312
- }
313
-
314
- return {
315
- $,
316
- trigger: function trigger(changes: DataChange[]) {
317
- for (let i = 0, l = changes.length; i < l; i++) {
318
- const change = changes[i];
319
-
320
- change.op
321
- change.ref
322
- }
323
- }
391
+ function $<T>(instance: T): CallbackProxy<T> {
392
+ return getProxy(undefined, { instance }) as CallbackProxy<T>;
324
393
  }
325
394
 
395
+ return $;
326
396
  }
@@ -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,53 +27,6 @@ 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;
@@ -123,9 +76,6 @@ export class ChangeTree<T extends Ref=any> {
123
76
  if (this.isFiltered || this.isPartiallyFiltered) {
124
77
  this.root.allFilteredChanges.set(this, this.allFilteredChanges);
125
78
  this.root.filteredChanges.set(this, this.filteredChanges);
126
-
127
- // } else {
128
- // this.root.allChanges.set(this, this.allChanges);
129
79
  }
130
80
 
131
81
  if (!this.isFiltered) {
@@ -170,13 +120,12 @@ export class ChangeTree<T extends Ref=any> {
170
120
 
171
121
  if (!this.isFiltered) {
172
122
  this.root.changes.set(this, this.changes);
123
+ this.root.allChanges.set(this, this.allChanges);
173
124
  }
174
125
 
175
126
  if (this.isFiltered || this.isPartiallyFiltered) {
176
127
  this.root.filteredChanges.set(this, this.filteredChanges);
177
128
  this.root.allFilteredChanges.set(this, this.filteredChanges);
178
- } else {
179
- this.root.allChanges.set(this, this.allChanges);
180
129
  }
181
130
 
182
131
  this.ensureRefId();
@@ -206,7 +155,7 @@ export class ChangeTree<T extends Ref=any> {
206
155
  // MapSchema / ArraySchema, etc.
207
156
  (this.ref as MapSchema).forEach((value, key) => {
208
157
  if (Metadata.isValidInstance(value)) {
209
- callback(value[$changes], this.ref[$changes].indexes[key]);
158
+ callback(value[$changes], this.ref[$changes].indexes[key] ?? key);
210
159
  }
211
160
  });
212
161
  }
@@ -240,9 +189,11 @@ export class ChangeTree<T extends Ref=any> {
240
189
  //
241
190
 
242
191
  if (isFiltered) {
243
- this.allFilteredChanges.set(index, OPERATION.ADD);
244
192
  this.root?.filteredChanges.set(this, this.filteredChanges);
245
193
 
194
+ this.allFilteredChanges.set(index, OPERATION.ADD);
195
+ this.root?.allFilteredChanges.set(this, this.allFilteredChanges);
196
+
246
197
  } else {
247
198
  this.allChanges.set(index, OPERATION.ADD);
248
199
  this.root?.changes.set(this, this.changes);
@@ -285,7 +236,6 @@ export class ChangeTree<T extends Ref=any> {
285
236
 
286
237
  private _shiftAllChangeIndexes(shiftIndex: number, startIndex: number = 0, allChangeSet: Map<number, OPERATION>) {
287
238
  Array.from(allChangeSet.entries()).forEach(([index, op]) => {
288
- // console.log('shiftAllChangeIndexes', index >= startIndex, { index, op, shiftIndex, startIndex })
289
239
  if (index >= startIndex) {
290
240
  allChangeSet.delete(index);
291
241
  allChangeSet.set(index + shiftIndex, op);
@@ -452,21 +402,41 @@ export class ChangeTree<T extends Ref=any> {
452
402
 
453
403
  protected checkIsFiltered(parent: Ref, parentIndex: number) {
454
404
  // Detect if current structure has "filters" declared
455
- this.isPartiallyFiltered = this.ref['constructor']?.[Symbol.metadata]?.[-2];
405
+ this.isPartiallyFiltered = (this.ref['constructor']?.[Symbol.metadata]?.[-2] !== undefined);
406
+
407
+ if (parent && !Metadata.isValidInstance(parent)) {
408
+ const parentChangeTree = parent[$changes];
409
+ parent = parentChangeTree.parent;
410
+ parentIndex = parentChangeTree.parentIndex;
411
+ }
412
+
413
+ const parentMetadata = parent?.['constructor']?.[Symbol.metadata];
456
414
 
457
- // TODO: support "partially filtered", where the instance is visible, but only a field is not.
415
+ this.isFiltered = (
416
+ parent &&
417
+ parentMetadata?.[-2]?.includes(parentIndex)
418
+ );
458
419
 
459
- // Detect if parent has "filters" declared
460
- while (parent && !this.isFiltered) {
461
- const metadata: Metadata = parent['constructor'][Symbol.metadata];
420
+ // this.isFiltered = this.ref['constructor']?.[Symbol.metadata]?.[-4];
462
421
 
463
- const fieldName = metadata?.[parentIndex];
464
- const isParentOwned = metadata?.[fieldName]?.tag !== undefined;
422
+ // // Detect if parent has "filters" declared
423
+ // while (parent && !this.isFiltered) {
424
+ // const metadata: Metadata = parent['constructor'][Symbol.metadata];
425
+ // // this.isFiltered = metadata?.[-4];
465
426
 
466
- this.isFiltered = isParentOwned || parent[$changes].isFiltered; // metadata?.[-2]
427
+ // const fieldName = metadata?.[parentIndex];
428
+ // const isParentOwned = metadata?.[fieldName]?.tag !== undefined;
429
+ // this.isFiltered = isParentOwned || parent[$changes].isFiltered; // metadata?.[-2]
467
430
 
468
- parent = parent[$changes].parent;
469
- };
431
+ // parent = parent[$changes].parent;
432
+ // };
433
+
434
+ // console.log("ChangeTree.checkIsFiltered", {
435
+ // parent: parent?.constructor.name,
436
+ // ref: this.ref.constructor.name,
437
+ // isFiltered: this.isFiltered,
438
+ // isPartiallyFiltered: this.isPartiallyFiltered,
439
+ // });
470
440
 
471
441
  //
472
442
  // TODO: refactor this!
@@ -172,6 +172,19 @@ export const encodeKeyValueOperation: EncodeOperation = function (
172
172
  const type = changeTree.getType(field);
173
173
  const value = changeTree.getValue(field);
174
174
 
175
+ // try { throw new Error(); } catch (e) {
176
+ // // only print if not coming from Reflection.ts
177
+ // if (!e.stack.includes("src/Reflection.ts")) {
178
+ // console.log("encodeKeyValueOperation -> ", {
179
+ // ref: changeTree.ref.constructor.name,
180
+ // field,
181
+ // operation: OPERATION[operation],
182
+ // value: value?.toJSON(),
183
+ // items: ref.toJSON(),
184
+ // });
185
+ // }
186
+ // }
187
+
175
188
  // TODO: inline this function call small performance gain
176
189
  encodeValue(encoder, bytes, ref, type, value, field, operation, it);
177
190
  }
@@ -191,18 +204,22 @@ export const encodeArray: EncodeOperation = function (
191
204
  hasView: boolean,
192
205
  ) {
193
206
  const ref = changeTree.ref;
207
+ const useOperationByRefId = hasView && changeTree.isFiltered && (typeof (changeTree.getType(field)) !== "string");
194
208
 
195
- if (
196
- hasView &&
197
- operation === OPERATION.DELETE &&
198
- typeof (changeTree.getType(field)) !== "string"
199
- ) {
200
- // encode delete by refId (array of schemas)
201
- bytes[it.offset++] = OPERATION.DELETE_BY_REFID;
202
- const value = ref['tmpItems'][field];
203
- const refId = value[$changes].refId;
204
- encode.number(bytes, refId, it);
205
- return;
209
+ let refOrIndex: number;
210
+
211
+ if (useOperationByRefId) {
212
+ refOrIndex = ref['tmpItems'][field][$changes].refId;
213
+
214
+ if (operation === OPERATION.DELETE) {
215
+ operation = OPERATION.DELETE_BY_REFID;
216
+
217
+ } else if (operation === OPERATION.ADD) {
218
+ operation = OPERATION.ADD_BY_REFID;
219
+ }
220
+
221
+ } else {
222
+ refOrIndex = field;
206
223
  }
207
224
 
208
225
  // encode operation
@@ -214,7 +231,7 @@ export const encodeArray: EncodeOperation = function (
214
231
  }
215
232
 
216
233
  // encode index
217
- encode.number(bytes, field, it);
234
+ encode.number(bytes, refOrIndex, it);
218
235
 
219
236
  // Do not encode value for DELETE operations
220
237
  if (operation === OPERATION.DELETE) {