@colyseus/schema 3.0.0-alpha.2 → 3.0.0-alpha.23

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 (103) hide show
  1. package/README.md +131 -61
  2. package/build/cjs/index.js +476 -150
  3. package/build/cjs/index.js.map +1 -1
  4. package/build/esm/index.mjs +475 -149
  5. package/build/esm/index.mjs.map +1 -1
  6. package/build/umd/index.js +476 -150
  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 +1 -2
  11. package/lib/Reflection.js +28 -22
  12. package/lib/Reflection.js.map +1 -1
  13. package/lib/Schema.d.ts +1 -1
  14. package/lib/annotations.js +12 -10
  15. package/lib/annotations.js.map +1 -1
  16. package/lib/bench_encode.d.ts +1 -0
  17. package/lib/bench_encode.js +93 -0
  18. package/lib/bench_encode.js.map +1 -0
  19. package/lib/codegen/api.js +1 -2
  20. package/lib/codegen/api.js.map +1 -1
  21. package/lib/codegen/languages/cpp.js +1 -2
  22. package/lib/codegen/languages/cpp.js.map +1 -1
  23. package/lib/codegen/languages/csharp.js +1 -2
  24. package/lib/codegen/languages/csharp.js.map +1 -1
  25. package/lib/codegen/languages/haxe.js +1 -2
  26. package/lib/codegen/languages/haxe.js.map +1 -1
  27. package/lib/codegen/languages/java.js +1 -2
  28. package/lib/codegen/languages/java.js.map +1 -1
  29. package/lib/codegen/languages/js.js +1 -2
  30. package/lib/codegen/languages/js.js.map +1 -1
  31. package/lib/codegen/languages/lua.js +1 -2
  32. package/lib/codegen/languages/lua.js.map +1 -1
  33. package/lib/codegen/languages/ts.js +1 -2
  34. package/lib/codegen/languages/ts.js.map +1 -1
  35. package/lib/codegen/parser.js +2 -3
  36. package/lib/codegen/parser.js.map +1 -1
  37. package/lib/codegen/types.js +3 -3
  38. package/lib/codegen/types.js.map +1 -1
  39. package/lib/debug.d.ts +1 -0
  40. package/lib/debug.js +52 -0
  41. package/lib/debug.js.map +1 -0
  42. package/lib/decoder/DecodeOperation.d.ts +0 -1
  43. package/lib/decoder/DecodeOperation.js +22 -7
  44. package/lib/decoder/DecodeOperation.js.map +1 -1
  45. package/lib/decoder/Decoder.d.ts +4 -5
  46. package/lib/decoder/Decoder.js +4 -4
  47. package/lib/decoder/Decoder.js.map +1 -1
  48. package/lib/decoder/strategy/RawChanges.js +1 -2
  49. package/lib/decoder/strategy/RawChanges.js.map +1 -1
  50. package/lib/decoder/strategy/StateCallbacks.d.ts +36 -7
  51. package/lib/decoder/strategy/StateCallbacks.js +39 -46
  52. package/lib/decoder/strategy/StateCallbacks.js.map +1 -1
  53. package/lib/encoder/ChangeTree.js +2 -5
  54. package/lib/encoder/ChangeTree.js.map +1 -1
  55. package/lib/encoder/EncodeOperation.d.ts +0 -1
  56. package/lib/encoder/EncodeOperation.js +29 -13
  57. package/lib/encoder/EncodeOperation.js.map +1 -1
  58. package/lib/encoder/Encoder.d.ts +5 -4
  59. package/lib/encoder/Encoder.js +60 -38
  60. package/lib/encoder/Encoder.js.map +1 -1
  61. package/lib/encoder/StateView.js +11 -6
  62. package/lib/encoder/StateView.js.map +1 -1
  63. package/lib/encoding/assert.js +3 -3
  64. package/lib/encoding/assert.js.map +1 -1
  65. package/lib/encoding/decode.d.ts +21 -19
  66. package/lib/encoding/decode.js +24 -25
  67. package/lib/encoding/decode.js.map +1 -1
  68. package/lib/encoding/encode.d.ts +2 -2
  69. package/lib/encoding/encode.js +40 -39
  70. package/lib/encoding/encode.js.map +1 -1
  71. package/lib/encoding/spec.d.ts +2 -1
  72. package/lib/encoding/spec.js +1 -0
  73. package/lib/encoding/spec.js.map +1 -1
  74. package/lib/index.d.ts +3 -0
  75. package/lib/index.js +5 -1
  76. package/lib/index.js.map +1 -1
  77. package/lib/types/custom/ArraySchema.d.ts +2 -2
  78. package/lib/types/custom/ArraySchema.js +0 -8
  79. package/lib/types/custom/ArraySchema.js.map +1 -1
  80. package/lib/types/registry.js +3 -4
  81. package/lib/types/registry.js.map +1 -1
  82. package/lib/types/utils.js +1 -2
  83. package/lib/types/utils.js.map +1 -1
  84. package/lib/utils.js +3 -4
  85. package/lib/utils.js.map +1 -1
  86. package/package.json +5 -5
  87. package/src/Metadata.ts +47 -0
  88. package/src/Reflection.ts +31 -22
  89. package/src/annotations.ts +6 -2
  90. package/src/bench_encode.ts +66 -0
  91. package/src/debug.ts +56 -0
  92. package/src/decoder/DecodeOperation.ts +26 -6
  93. package/src/decoder/Decoder.ts +8 -8
  94. package/src/decoder/strategy/StateCallbacks.ts +93 -53
  95. package/src/encoder/ChangeTree.ts +2 -5
  96. package/src/encoder/EncodeOperation.ts +29 -12
  97. package/src/encoder/Encoder.ts +71 -42
  98. package/src/encoder/StateView.ts +10 -7
  99. package/src/encoding/decode.ts +24 -25
  100. package/src/encoding/encode.ts +25 -22
  101. package/src/encoding/spec.ts +1 -0
  102. package/src/index.ts +5 -0
  103. package/src/types/custom/ArraySchema.ts +2 -1
package/src/debug.ts ADDED
@@ -0,0 +1,56 @@
1
+ import * as fs from "fs";
2
+ import { Reflection, Decoder } from "./index";
3
+
4
+ const contents = fs.readFileSync("/Users/endel/Projects/colyseus/clients/bubbits/project/@bubbits/backend/schema-debug.txt", { encoding: "utf8" }).toString();
5
+
6
+ let isCommentBlock = false;
7
+ let lastComment = "";
8
+
9
+ let decoder: Decoder;
10
+
11
+ function getBuffer(line: string) {
12
+ const start = line.lastIndexOf(":");
13
+ const buffer = Buffer.from(new Uint8Array(line.substring(start + 1).split(",").map(n => Number(n))));
14
+ console.log(`(${buffer.byteLength}) ${Array.from(buffer).join(",")}`)
15
+ // console.log("");
16
+ // console.log("");
17
+ // console.log("> ", line);
18
+ // console.log("> substring:", line.substring(start + 1))
19
+ return buffer;
20
+ }
21
+
22
+ function decode(buffer: Buffer) {
23
+ try {
24
+ decoder.decode(buffer);
25
+ } catch (e) {
26
+ console.error(e);
27
+ console.log("Last log:\n\n")
28
+ console.log(lastComment);
29
+ }
30
+ }
31
+
32
+ contents.split("\n").forEach((line) => {
33
+ if (line.startsWith("#")) {
34
+ // reset last comment.
35
+ if (isCommentBlock === false) { lastComment = ""; }
36
+
37
+ isCommentBlock = true;
38
+ lastComment += line.substring(line.indexOf(":") + 1) + "\n";
39
+ return;
40
+ }
41
+
42
+ isCommentBlock = false;
43
+
44
+ if (line.startsWith("handshake:") && !decoder) {
45
+ const state = Reflection.decode(getBuffer(line));
46
+ decoder = new Decoder(state);
47
+
48
+ } else if (line.startsWith("state:")) {
49
+ decode(getBuffer(line));
50
+
51
+ } else if (line.startsWith("patch:")) {
52
+ decode(getBuffer(line));
53
+ }
54
+ });
55
+
56
+ console.log(decoder.state.toJSON());
@@ -4,7 +4,7 @@ import { Schema } from "../Schema";
4
4
  import type { Ref } from "../encoder/ChangeTree";
5
5
  import type { Decoder } from "./Decoder";
6
6
  import * as decode from "../encoding/decode";
7
- import { $changes, $childType, $deleteByIndex, $getByIndex } from "../types/symbols";
7
+ import { $childType, $deleteByIndex, $getByIndex } from "../types/symbols";
8
8
 
9
9
  import type { MapSchema } from "../types/custom/MapSchema";
10
10
  import type { ArraySchema } from "../types/custom/ArraySchema";
@@ -43,7 +43,7 @@ export function decodeValue(
43
43
  it: decode.Iterator,
44
44
  allChanges: DataChange[],
45
45
  ) {
46
- const $root = decoder.$root;
46
+ const $root = decoder.root;
47
47
  const previousValue = ref[$getByIndex](index);
48
48
 
49
49
  let value: any;
@@ -251,6 +251,7 @@ export const decodeKeyValueOperation: DecodeOperation = function (
251
251
  dynamicIndex = ref['getIndex'](index);
252
252
  }
253
253
 
254
+
254
255
  const { value, previousValue } = decodeValue(
255
256
  decoder,
256
257
  operation,
@@ -303,7 +304,10 @@ export const decodeArray: DecodeOperation = function (
303
304
  allChanges: DataChange[]
304
305
  ) {
305
306
  // "uncompressed" index + operation (array/map items)
306
- const operation = bytes[it.offset++];
307
+ let operation = bytes[it.offset++];
308
+
309
+ let isSchemaChild: boolean;
310
+ let index: number;
307
311
 
308
312
  if (operation === OPERATION.CLEAR) {
309
313
  //
@@ -318,8 +322,8 @@ export const decodeArray: DecodeOperation = function (
318
322
  } else if (operation === OPERATION.DELETE_BY_REFID) {
319
323
  // TODO: refactor here, try to follow same flow as below
320
324
  const refId = decode.number(bytes, it);
321
- const previousValue = decoder.$root.refs.get(refId);
322
- const index = ref.findIndex((value) => value === previousValue);
325
+ const previousValue = decoder.root.refs.get(refId);
326
+ index = ref.findIndex((value) => value === previousValue);
323
327
  ref[$deleteByIndex](index);
324
328
  allChanges.push({
325
329
  ref,
@@ -330,10 +334,26 @@ export const decodeArray: DecodeOperation = function (
330
334
  value: undefined,
331
335
  previousValue,
332
336
  });
337
+
333
338
  return;
339
+
340
+ } else if (operation === OPERATION.ADD_BY_REFID) {
341
+ isSchemaChild = true;
342
+ // operation = OPERATION.ADD;
343
+
344
+ const refId = decode.number(bytes, it);
345
+ const itemByRefId = decoder.root.refs.get(refId);
346
+
347
+ // use existing index, or push new value
348
+ index = (itemByRefId)
349
+ ? ref.findIndex((value) => value === itemByRefId)
350
+ : ref.length;
351
+
352
+ } else {
353
+ isSchemaChild = false;
354
+ index = decode.number(bytes, it);
334
355
  }
335
356
 
336
- const index = decode.number(bytes, it);
337
357
  const type = ref[$childType];
338
358
 
339
359
  let dynamicIndex: number | string = index;
@@ -4,17 +4,17 @@ import { Schema } from "../Schema";
4
4
 
5
5
  import * as decode from "../encoding/decode";
6
6
  import { OPERATION, SWITCH_TO_STRUCTURE, TYPE_ID } from '../encoding/spec';
7
- import { Ref } from "../encoder/ChangeTree";
8
- import { Iterator } from "../encoding/decode";
7
+ import type { Ref } from "../encoder/ChangeTree";
8
+ import type { Iterator } from "../encoding/decode";
9
9
  import { ReferenceTracker } from "./ReferenceTracker";
10
- import { DEFINITION_MISMATCH, DataChange, DecodeOperation } from "./DecodeOperation";
10
+ import { DEFINITION_MISMATCH, type DataChange, type DecodeOperation } from "./DecodeOperation";
11
11
  import { Collection } from "../types/HelperTypes";
12
12
 
13
13
  export class Decoder<T extends Schema = any> {
14
14
  context: TypeContext;
15
15
 
16
16
  state: T;
17
- $root: ReferenceTracker;
17
+ root: ReferenceTracker;
18
18
 
19
19
  currentRefId: number = 0;
20
20
 
@@ -32,8 +32,8 @@ export class Decoder<T extends Schema = any> {
32
32
 
33
33
  protected setRoot(root: T) {
34
34
  this.state = root;
35
- this.$root = new ReferenceTracker();
36
- this.$root.addRef(0, root);
35
+ this.root = new ReferenceTracker();
36
+ this.root.addRef(0, root);
37
37
  }
38
38
 
39
39
  decode(
@@ -43,7 +43,7 @@ export class Decoder<T extends Schema = any> {
43
43
  ) {
44
44
  const allChanges: DataChange[] = [];
45
45
 
46
- const $root = this.$root;
46
+ const $root = this.root;
47
47
  const totalBytes = bytes.byteLength;
48
48
 
49
49
  let decoder: DecodeOperation = ref['constructor'][$decoder];
@@ -146,7 +146,7 @@ export class Decoder<T extends Schema = any> {
146
146
  });
147
147
 
148
148
  if (needRemoveRef) {
149
- this.$root.removeRef(this.$root.refIds.get(value));
149
+ this.root.removeRef(this.root.refIds.get(value));
150
150
  }
151
151
  });
152
152
  }
@@ -17,27 +17,69 @@ 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
+ */
27
36
  listen<K extends NonFunctionPropNames<T>>(
28
37
  prop: K,
29
38
  callback: (value: T[K], previousValue: T[K]) => void,
30
39
  immediate?: boolean,
31
40
  )
41
+ /**
42
+ * Trigger callback whenever any property changed within this instance.
43
+ *
44
+ * @param prop name of the property
45
+ * @param callback callback to be triggered on property change
46
+ * @param immediate trigger immediatelly if property has been already set.
47
+ */
32
48
  onChange(callback: () => void): void;
49
+
50
+ /**
51
+ * Bind properties to another object. Changes on the properties will be reflected on the target object.
52
+ *
53
+ * @param targetObject object to bind properties to
54
+ * @param properties list of properties to bind. If not provided, all properties will be bound.
55
+ */
33
56
  bindTo(targetObject: any, properties?: Array<NonFunctionPropNames<T>>): void;
34
57
  } & {
35
- [K in NonFunctionNonPrimitivePropNames<T>]: GetProxyType<T[K]>;
58
+ [K in NonFunctionNonPrimitivePropNames<T>]: CallbackProxy<T[K]>;
36
59
  }
37
60
 
38
61
  type CollectionCallback<K, V> = {
62
+ /**
63
+ * Trigger callback when an item has been added to the collection.
64
+ *
65
+ * @param callback
66
+ * @param immediate
67
+ */
39
68
  onAdd(callback: (item: V, index: K) => void, immediate?: boolean): void;
69
+
70
+ /**
71
+ * Trigger callback when an item has been removed to the collection.
72
+ *
73
+ * @param callback
74
+ */
40
75
  onRemove(callback: (item: V, index: K) => void): void;
76
+
77
+ // /**
78
+ // * Trigger callback when an item has been removed to the collection.
79
+ // *
80
+ // * @param callback
81
+ // */
82
+ // onChange(callback: (item: V, index: K) => void): void;
41
83
  };
42
84
 
43
85
  type OnInstanceAvailableCallback = (callback: (ref: Ref, existing: boolean) => void) => void;
@@ -48,8 +90,9 @@ type CallContext = {
48
90
  onInstanceAvailable?: OnInstanceAvailableCallback,
49
91
  }
50
92
 
51
- export function getStateCallbacks(decoder: Decoder) {
52
- const $root = decoder.$root;
93
+
94
+ export function getDecoderStateCallbacks<T extends Schema>(decoder: Decoder<T>): GetCallbackProxy {
95
+ const $root = decoder.root;
53
96
  const callbacks = $root.callbacks;
54
97
 
55
98
  let isTriggeringOnAdd = false;
@@ -76,8 +119,6 @@ export function getStateCallbacks(decoder: Decoder) {
76
119
  for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
77
120
  deleteCallbacks[i]();
78
121
  }
79
- // callbacks[$root.refIds.get(change.previousValue)]?.[OPERATION.DELETE]?.forEach(callback =>
80
- // callback());
81
122
  }
82
123
 
83
124
  if (ref instanceof Schema) {
@@ -86,47 +127,35 @@ export function getStateCallbacks(decoder: Decoder) {
86
127
  //
87
128
 
88
129
  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);
130
+ // trigger onChange
131
+ const replaceCallbacks = $callbacks?.[OPERATION.REPLACE];
132
+ for (let i = replaceCallbacks?.length - 1; i >= 0; i--) {
133
+ replaceCallbacks[i]();
134
+ // try {
135
+ // } catch (e) {
136
+ // console.error(e);
137
+ // }
98
138
  }
99
139
  }
100
140
 
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
- }
141
+ if ($callbacks.hasOwnProperty(change.field)) {
142
+ const fieldCallbacks = $callbacks[change.field];
143
+ for (let i = fieldCallbacks?.length - 1; i >= 0; i--) {
144
+ fieldCallbacks[i](change.value, change.previousValue);
145
+ // try {
146
+ // } catch (e) {
147
+ // console.error(e);
148
+ // }
107
149
  }
108
-
109
- } catch (e) {
110
- //
111
- console.error(e);
112
150
  }
113
151
 
152
+
114
153
  } else {
115
154
  //
116
155
  // Handle collection of items
117
156
  //
118
157
 
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) {
158
+ if ((change.op & OPERATION.DELETE) === OPERATION.DELETE) {
130
159
  //
131
160
  // FIXME: `previousValue` should always be available.
132
161
  //
@@ -146,6 +175,16 @@ export function getStateCallbacks(decoder: Decoder) {
146
175
  addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
147
176
  }
148
177
  }
178
+
179
+ } else if ((change.op & OPERATION.ADD) === OPERATION.ADD && change.previousValue === undefined) {
180
+ // triger onAdd
181
+
182
+ isTriggeringOnAdd = true;
183
+ const addCallbacks = $callbacks[OPERATION.ADD];
184
+ for (let i = addCallbacks?.length - 1; i >= 0; i--) {
185
+ addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
186
+ }
187
+ isTriggeringOnAdd = false;
149
188
  }
150
189
 
151
190
  // trigger onChange
@@ -206,10 +245,22 @@ export function getStateCallbacks(decoder: Decoder) {
206
245
  OPERATION.REPLACE,
207
246
  callback
208
247
  );
209
-
210
248
  },
211
249
  bindTo: function bindTo(targetObject: any, properties?: string[]) {
212
- console.log("bindTo", targetObject, properties);
250
+ //
251
+ // TODO: refactor this implementation. There is room for improvement here.
252
+ //
253
+ if (!properties) {
254
+ properties = Object.keys(metadata);
255
+ }
256
+ return $root.addCallback(
257
+ $root.refIds.get(context.instance),
258
+ OPERATION.REPLACE,
259
+ () => {
260
+ properties.forEach((prop) =>
261
+ targetObject[prop] = context.instance[prop])
262
+ }
263
+ );
213
264
  }
214
265
  }, {
215
266
  get(target, prop: string) {
@@ -307,20 +358,9 @@ export function getStateCallbacks(decoder: Decoder) {
307
358
  }
308
359
  }
309
360
 
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
- }
361
+ function $<T>(instance: T): CallbackProxy<T> {
362
+ return getProxy(undefined, { instance }) as CallbackProxy<T>;
324
363
  }
325
364
 
365
+ return $;
326
366
  }
@@ -10,7 +10,6 @@ import type { SetSchema } from "../types/custom/SetSchema";
10
10
  import { Metadata } from "../Metadata";
11
11
  import type { EncodeOperation } from "./EncodeOperation";
12
12
  import type { DecodeOperation } from "../decoder/DecodeOperation";
13
- import type { StateView } from "./StateView";
14
13
 
15
14
  declare global {
16
15
  interface Object {
@@ -170,13 +169,12 @@ export class ChangeTree<T extends Ref=any> {
170
169
 
171
170
  if (!this.isFiltered) {
172
171
  this.root.changes.set(this, this.changes);
172
+ this.root.allChanges.set(this, this.allChanges);
173
173
  }
174
174
 
175
175
  if (this.isFiltered || this.isPartiallyFiltered) {
176
176
  this.root.filteredChanges.set(this, this.filteredChanges);
177
177
  this.root.allFilteredChanges.set(this, this.filteredChanges);
178
- } else {
179
- this.root.allChanges.set(this, this.allChanges);
180
178
  }
181
179
 
182
180
  this.ensureRefId();
@@ -285,7 +283,6 @@ export class ChangeTree<T extends Ref=any> {
285
283
 
286
284
  private _shiftAllChangeIndexes(shiftIndex: number, startIndex: number = 0, allChangeSet: Map<number, OPERATION>) {
287
285
  Array.from(allChangeSet.entries()).forEach(([index, op]) => {
288
- // console.log('shiftAllChangeIndexes', index >= startIndex, { index, op, shiftIndex, startIndex })
289
286
  if (index >= startIndex) {
290
287
  allChangeSet.delete(index);
291
288
  allChangeSet.set(index + shiftIndex, op);
@@ -452,7 +449,7 @@ export class ChangeTree<T extends Ref=any> {
452
449
 
453
450
  protected checkIsFiltered(parent: Ref, parentIndex: number) {
454
451
  // Detect if current structure has "filters" declared
455
- this.isPartiallyFiltered = this.ref['constructor']?.[Symbol.metadata]?.[-2];
452
+ this.isPartiallyFiltered = (this.ref['constructor']?.[Symbol.metadata]?.[-2] !== undefined);
456
453
 
457
454
  // TODO: support "partially filtered", where the instance is visible, but only a field is not.
458
455
 
@@ -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) {