@colyseus/schema 3.0.0-alpha.9 → 3.0.0

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 (150) hide show
  1. package/README.md +148 -62
  2. package/bin/schema-debug +94 -0
  3. package/build/cjs/index.js +2222 -1513
  4. package/build/cjs/index.js.map +1 -1
  5. package/build/esm/index.mjs +2223 -1516
  6. package/build/esm/index.mjs.map +1 -1
  7. package/build/umd/index.js +2225 -1516
  8. package/lib/Metadata.d.ts +21 -9
  9. package/lib/Metadata.js +169 -32
  10. package/lib/Metadata.js.map +1 -1
  11. package/lib/Reflection.d.ts +19 -4
  12. package/lib/Reflection.js +66 -31
  13. package/lib/Reflection.js.map +1 -1
  14. package/lib/Schema.d.ts +12 -5
  15. package/lib/Schema.js +57 -56
  16. package/lib/Schema.js.map +1 -1
  17. package/lib/annotations.d.ts +31 -34
  18. package/lib/annotations.js +110 -160
  19. package/lib/annotations.js.map +1 -1
  20. package/lib/bench_encode.d.ts +1 -0
  21. package/lib/bench_encode.js +130 -0
  22. package/lib/bench_encode.js.map +1 -0
  23. package/lib/codegen/api.js +1 -2
  24. package/lib/codegen/api.js.map +1 -1
  25. package/lib/codegen/languages/cpp.js +1 -2
  26. package/lib/codegen/languages/cpp.js.map +1 -1
  27. package/lib/codegen/languages/csharp.js +9 -46
  28. package/lib/codegen/languages/csharp.js.map +1 -1
  29. package/lib/codegen/languages/haxe.js +4 -2
  30. package/lib/codegen/languages/haxe.js.map +1 -1
  31. package/lib/codegen/languages/java.js +1 -2
  32. package/lib/codegen/languages/java.js.map +1 -1
  33. package/lib/codegen/languages/js.js +1 -2
  34. package/lib/codegen/languages/js.js.map +1 -1
  35. package/lib/codegen/languages/lua.js +23 -25
  36. package/lib/codegen/languages/lua.js.map +1 -1
  37. package/lib/codegen/languages/ts.js +1 -2
  38. package/lib/codegen/languages/ts.js.map +1 -1
  39. package/lib/codegen/parser.js +85 -3
  40. package/lib/codegen/parser.js.map +1 -1
  41. package/lib/codegen/types.js +6 -3
  42. package/lib/codegen/types.js.map +1 -1
  43. package/lib/debug.d.ts +1 -0
  44. package/lib/debug.js +51 -0
  45. package/lib/debug.js.map +1 -0
  46. package/lib/decoder/DecodeOperation.d.ts +3 -4
  47. package/lib/decoder/DecodeOperation.js +35 -17
  48. package/lib/decoder/DecodeOperation.js.map +1 -1
  49. package/lib/decoder/Decoder.d.ts +5 -6
  50. package/lib/decoder/Decoder.js +10 -10
  51. package/lib/decoder/Decoder.js.map +1 -1
  52. package/lib/decoder/ReferenceTracker.js +4 -2
  53. package/lib/decoder/ReferenceTracker.js.map +1 -1
  54. package/lib/decoder/strategy/RawChanges.js +1 -2
  55. package/lib/decoder/strategy/RawChanges.js.map +1 -1
  56. package/lib/decoder/strategy/StateCallbacks.d.ts +44 -11
  57. package/lib/decoder/strategy/StateCallbacks.js +74 -64
  58. package/lib/decoder/strategy/StateCallbacks.js.map +1 -1
  59. package/lib/encoder/ChangeTree.d.ts +28 -20
  60. package/lib/encoder/ChangeTree.js +242 -188
  61. package/lib/encoder/ChangeTree.js.map +1 -1
  62. package/lib/encoder/EncodeOperation.d.ts +3 -6
  63. package/lib/encoder/EncodeOperation.js +51 -65
  64. package/lib/encoder/EncodeOperation.js.map +1 -1
  65. package/lib/encoder/Encoder.d.ts +8 -7
  66. package/lib/encoder/Encoder.js +128 -79
  67. package/lib/encoder/Encoder.js.map +1 -1
  68. package/lib/encoder/Root.d.ts +22 -0
  69. package/lib/encoder/Root.js +81 -0
  70. package/lib/encoder/Root.js.map +1 -0
  71. package/lib/encoder/StateView.d.ts +7 -7
  72. package/lib/encoder/StateView.js +72 -74
  73. package/lib/encoder/StateView.js.map +1 -1
  74. package/lib/encoding/assert.d.ts +7 -6
  75. package/lib/encoding/assert.js +13 -5
  76. package/lib/encoding/assert.js.map +1 -1
  77. package/lib/encoding/decode.d.ts +36 -19
  78. package/lib/encoding/decode.js +54 -84
  79. package/lib/encoding/decode.js.map +1 -1
  80. package/lib/encoding/encode.d.ts +36 -18
  81. package/lib/encoding/encode.js +61 -48
  82. package/lib/encoding/encode.js.map +1 -1
  83. package/lib/encoding/spec.d.ts +4 -5
  84. package/lib/encoding/spec.js +1 -2
  85. package/lib/encoding/spec.js.map +1 -1
  86. package/lib/index.d.ts +10 -9
  87. package/lib/index.js +24 -17
  88. package/lib/index.js.map +1 -1
  89. package/lib/types/HelperTypes.d.ts +34 -2
  90. package/lib/types/HelperTypes.js.map +1 -1
  91. package/lib/types/TypeContext.d.ts +29 -0
  92. package/lib/types/TypeContext.js +151 -0
  93. package/lib/types/TypeContext.js.map +1 -0
  94. package/lib/types/custom/ArraySchema.d.ts +2 -2
  95. package/lib/types/custom/ArraySchema.js +33 -22
  96. package/lib/types/custom/ArraySchema.js.map +1 -1
  97. package/lib/types/custom/CollectionSchema.d.ts +2 -2
  98. package/lib/types/custom/CollectionSchema.js +1 -0
  99. package/lib/types/custom/CollectionSchema.js.map +1 -1
  100. package/lib/types/custom/MapSchema.d.ts +18 -16
  101. package/lib/types/custom/MapSchema.js +12 -4
  102. package/lib/types/custom/MapSchema.js.map +1 -1
  103. package/lib/types/custom/SetSchema.d.ts +2 -2
  104. package/lib/types/custom/SetSchema.js +1 -0
  105. package/lib/types/custom/SetSchema.js.map +1 -1
  106. package/lib/types/registry.d.ts +8 -1
  107. package/lib/types/registry.js +23 -6
  108. package/lib/types/registry.js.map +1 -1
  109. package/lib/types/symbols.d.ts +8 -5
  110. package/lib/types/symbols.js +9 -6
  111. package/lib/types/symbols.js.map +1 -1
  112. package/lib/types/utils.js +1 -2
  113. package/lib/types/utils.js.map +1 -1
  114. package/lib/utils.js +9 -7
  115. package/lib/utils.js.map +1 -1
  116. package/package.json +19 -18
  117. package/src/Metadata.ts +190 -42
  118. package/src/Reflection.ts +76 -38
  119. package/src/Schema.ts +72 -70
  120. package/src/annotations.ts +156 -202
  121. package/src/bench_encode.ts +108 -0
  122. package/src/codegen/languages/csharp.ts +8 -47
  123. package/src/codegen/languages/haxe.ts +4 -0
  124. package/src/codegen/languages/lua.ts +19 -27
  125. package/src/codegen/parser.ts +107 -0
  126. package/src/codegen/types.ts +1 -0
  127. package/src/debug.ts +55 -0
  128. package/src/decoder/DecodeOperation.ts +43 -15
  129. package/src/decoder/Decoder.ts +12 -10
  130. package/src/decoder/ReferenceTracker.ts +5 -3
  131. package/src/decoder/strategy/StateCallbacks.ts +152 -81
  132. package/src/encoder/ChangeTree.ts +282 -209
  133. package/src/encoder/EncodeOperation.ts +78 -78
  134. package/src/encoder/Encoder.ts +152 -87
  135. package/src/encoder/Root.ts +93 -0
  136. package/src/encoder/StateView.ts +80 -88
  137. package/src/encoding/assert.ts +17 -8
  138. package/src/encoding/decode.ts +73 -93
  139. package/src/encoding/encode.ts +76 -45
  140. package/src/encoding/spec.ts +3 -5
  141. package/src/index.ts +12 -20
  142. package/src/types/HelperTypes.ts +54 -2
  143. package/src/types/TypeContext.ts +175 -0
  144. package/src/types/custom/ArraySchema.ts +49 -19
  145. package/src/types/custom/CollectionSchema.ts +1 -0
  146. package/src/types/custom/MapSchema.ts +30 -17
  147. package/src/types/custom/SetSchema.ts +1 -0
  148. package/src/types/registry.ts +22 -3
  149. package/src/types/symbols.ts +10 -7
  150. package/src/utils.ts +7 -3
@@ -1,95 +1,80 @@
1
1
  "use strict";
2
- var _a;
3
2
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.ChangeTree = exports.Root = void 0;
3
+ exports.ChangeTree = void 0;
4
+ exports.setOperationAtIndex = setOperationAtIndex;
5
+ exports.deleteOperationAtIndex = deleteOperationAtIndex;
5
6
  const spec_1 = require("../encoding/spec");
6
7
  const symbols_1 = require("../types/symbols");
7
8
  const Metadata_1 = require("../Metadata");
8
- class Root {
9
- constructor() {
10
- this.nextUniqueId = 0;
11
- this.refCount = new WeakMap();
12
- // all changes
13
- this.allChanges = new Map();
14
- this.allFilteredChanges = new Map();
15
- // pending changes to be encoded
16
- this.changes = new Map();
17
- this.filteredChanges = new Map();
9
+ function setOperationAtIndex(changeSet, index) {
10
+ const operationsIndex = changeSet.indexes[index];
11
+ if (operationsIndex === undefined) {
12
+ changeSet.indexes[index] = changeSet.operations.push(index) - 1;
18
13
  }
19
- getNextUniqueId() {
20
- return this.nextUniqueId++;
14
+ else {
15
+ changeSet.operations[operationsIndex] = index;
21
16
  }
22
- add(changeTree) {
23
- const refCount = this.refCount.get(changeTree) || 0;
24
- this.refCount.set(changeTree, refCount + 1);
25
- }
26
- remove(changeTree) {
27
- const refCount = this.refCount.get(changeTree);
28
- if (refCount <= 1) {
29
- this.allChanges.delete(changeTree);
30
- this.changes.delete(changeTree);
31
- if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
32
- this.allFilteredChanges.delete(changeTree);
33
- this.filteredChanges.delete(changeTree);
34
- }
35
- this.refCount.delete(changeTree);
36
- }
37
- else {
38
- this.refCount.set(changeTree, refCount - 1);
39
- }
40
- changeTree.forEachChild((child, _) => this.remove(child));
17
+ }
18
+ function deleteOperationAtIndex(changeSet, index) {
19
+ const operationsIndex = changeSet.indexes[index];
20
+ if (operationsIndex !== undefined) {
21
+ changeSet.operations[operationsIndex] = undefined;
41
22
  }
42
- clear() {
43
- this.changes.clear();
23
+ delete changeSet.indexes[index];
24
+ }
25
+ function enqueueChangeTree(root, changeTree, changeSet, queueRootIndex = changeTree[changeSet].queueRootIndex) {
26
+ if (root && root[changeSet][queueRootIndex] !== changeTree) {
27
+ changeTree[changeSet].queueRootIndex = root[changeSet].push(changeTree) - 1;
44
28
  }
45
29
  }
46
- exports.Root = Root;
47
30
  class ChangeTree {
48
- static { _a = symbols_1.$isNew; }
49
- ;
50
31
  constructor(ref) {
51
- this.indexes = {}; // TODO: remove this, only used by MapSchema/SetSchema/CollectionSchema (`encodeKeyValueOperation`)
52
- this.currentOperationIndex = 0;
53
- this.allChanges = new Map();
54
- this.allFilteredChanges = new Map();
55
- this.changes = new Map();
56
- this.filteredChanges = new Map();
57
- this[_a] = true;
32
+ /**
33
+ * Whether this structure is parent of a filtered structure.
34
+ */
35
+ this.isFiltered = false;
36
+ this.indexedOperations = {};
37
+ //
38
+ // TODO:
39
+ // try storing the index + operation per item.
40
+ // example: 1024 & 1025 => ADD, 1026 => DELETE
41
+ //
42
+ // => https://chatgpt.com/share/67107d0c-bc20-8004-8583-83b17dd7c196
43
+ //
44
+ this.changes = { indexes: {}, operations: [] };
45
+ this.allChanges = { indexes: {}, operations: [] };
46
+ /**
47
+ * Is this a new instance? Used on ArraySchema to determine OPERATION.MOVE_AND_ADD operation.
48
+ */
49
+ this.isNew = true;
58
50
  this.ref = ref;
51
+ //
52
+ // Does this structure have "filters" declared?
53
+ //
54
+ const metadata = ref.constructor[Symbol.metadata];
55
+ if (metadata?.[symbols_1.$viewFieldIndexes]) {
56
+ this.allFilteredChanges = { indexes: {}, operations: [] };
57
+ this.filteredChanges = { indexes: {}, operations: [] };
58
+ }
59
59
  }
60
60
  setRoot(root) {
61
61
  this.root = root;
62
- this.root.add(this);
63
- //
64
- // At Schema initialization, the "root" structure might not be available
65
- // yet, as it only does once the "Encoder" has been set up.
66
- //
67
- // So the "parent" may be already set without a "root".
68
- //
69
62
  this.checkIsFiltered(this.parent, this.parentIndex);
70
- // unique refId for the ChangeTree.
71
- this.ensureRefId();
72
- if (!this.isFiltered) {
73
- this.root.changes.set(this, this.changes);
63
+ // Recursively set root on child structures
64
+ const metadata = this.ref.constructor[Symbol.metadata];
65
+ if (metadata) {
66
+ metadata[symbols_1.$refTypeFieldIndexes]?.forEach((index) => {
67
+ const field = metadata[index];
68
+ const value = this.ref[field.name];
69
+ value?.[symbols_1.$changes].setRoot(root);
70
+ });
74
71
  }
75
- if (this.isFiltered || this.isPartiallyFiltered) {
76
- this.root.allFilteredChanges.set(this, this.allFilteredChanges);
77
- this.root.filteredChanges.set(this, this.filteredChanges);
78
- // } else {
79
- // this.root.allChanges.set(this, this.allChanges);
72
+ else if (this.ref[symbols_1.$childType] && typeof (this.ref[symbols_1.$childType]) !== "string") {
73
+ // MapSchema / ArraySchema, etc.
74
+ this.ref.forEach((value, key) => {
75
+ value[symbols_1.$changes].setRoot(root);
76
+ });
80
77
  }
81
- if (!this.isFiltered) {
82
- this.root.allChanges.set(this, this.allChanges);
83
- }
84
- this.forEachChild((changeTree, _) => {
85
- changeTree.setRoot(root);
86
- });
87
- // this.allChanges.forEach((_, index) => {
88
- // const childRef = this.ref[$getByIndex](index);
89
- // if (childRef && childRef[$changes]) {
90
- // childRef[$changes].setRoot(root);
91
- // }
92
- // });
93
78
  }
94
79
  setParent(parent, root, parentIndex) {
95
80
  this.parent = parent;
@@ -98,83 +83,86 @@ class ChangeTree {
98
83
  if (!root) {
99
84
  return;
100
85
  }
101
- root.add(this);
102
86
  // skip if parent is already set
103
- if (root === this.root) {
104
- this.forEachChild((changeTree, atIndex) => {
105
- changeTree.setParent(this.ref, root, atIndex);
106
- });
107
- return;
87
+ if (root !== this.root) {
88
+ this.root = root;
89
+ this.checkIsFiltered(parent, parentIndex);
108
90
  }
109
- this.root = root;
110
- this.checkIsFiltered(parent, parentIndex);
111
- if (!this.isFiltered) {
112
- this.root.changes.set(this, this.changes);
91
+ else {
92
+ root.add(this);
113
93
  }
114
- if (this.isFiltered || this.isPartiallyFiltered) {
115
- this.root.filteredChanges.set(this, this.filteredChanges);
116
- this.root.allFilteredChanges.set(this, this.filteredChanges);
94
+ // assign same parent on child structures
95
+ const metadata = this.ref.constructor[Symbol.metadata];
96
+ if (metadata) {
97
+ metadata[symbols_1.$refTypeFieldIndexes]?.forEach((index) => {
98
+ const field = metadata[index];
99
+ const value = this.ref[field.name];
100
+ value?.[symbols_1.$changes].setParent(this.ref, root, index);
101
+ });
117
102
  }
118
- else {
119
- this.root.allChanges.set(this, this.allChanges);
103
+ else if (this.ref[symbols_1.$childType] && typeof (this.ref[symbols_1.$childType]) !== "string") {
104
+ // MapSchema / ArraySchema, etc.
105
+ this.ref.forEach((value, key) => {
106
+ value[symbols_1.$changes].setParent(this.ref, root, this.indexes[key] ?? key);
107
+ });
120
108
  }
121
- this.ensureRefId();
122
- this.forEachChild((changeTree, atIndex) => {
123
- changeTree.setParent(this.ref, root, atIndex);
124
- });
125
109
  }
126
110
  forEachChild(callback) {
127
111
  //
128
112
  // assign same parent on child structures
129
113
  //
130
- if (Metadata_1.Metadata.isValidInstance(this.ref)) {
131
- const metadata = this.ref['constructor'][Symbol.metadata];
132
- // FIXME: need to iterate over parent metadata instead.
133
- for (const field in metadata) {
134
- const value = this.ref[field];
135
- if (value && value[symbols_1.$changes]) {
136
- callback(value[symbols_1.$changes], metadata[field].index);
114
+ const metadata = this.ref.constructor[Symbol.metadata];
115
+ if (metadata) {
116
+ metadata[symbols_1.$refTypeFieldIndexes]?.forEach((index) => {
117
+ const field = metadata[index];
118
+ const value = this.ref[field.name];
119
+ if (value) {
120
+ callback(value[symbols_1.$changes], index);
137
121
  }
138
- }
122
+ });
139
123
  }
140
- else if (typeof (this.ref) === "object") {
124
+ else if (this.ref[symbols_1.$childType] && typeof (this.ref[symbols_1.$childType]) !== "string") {
141
125
  // MapSchema / ArraySchema, etc.
142
126
  this.ref.forEach((value, key) => {
143
- if (Metadata_1.Metadata.isValidInstance(value)) {
144
- callback(value[symbols_1.$changes], this.ref[symbols_1.$changes].indexes[key]);
145
- }
127
+ callback(value[symbols_1.$changes], this.indexes[key] ?? key);
146
128
  });
147
129
  }
148
130
  }
149
131
  operation(op) {
150
- this.changes.set(--this.currentOperationIndex, op);
151
- this.root?.changes.set(this, this.changes);
132
+ // operations without index use negative values to represent them
133
+ // this is checked during .encode() time.
134
+ this.changes.operations.push(-op);
135
+ enqueueChangeTree(this.root, this, 'changes');
152
136
  }
153
137
  change(index, operation = spec_1.OPERATION.ADD) {
154
- const metadata = this.ref['constructor'][Symbol.metadata];
155
- const isFiltered = this.isFiltered || (metadata && metadata[metadata[index]].tag !== undefined);
138
+ const metadata = this.ref.constructor[Symbol.metadata];
139
+ const isFiltered = this.isFiltered || (metadata?.[index]?.tag !== undefined);
156
140
  const changeSet = (isFiltered)
157
141
  ? this.filteredChanges
158
142
  : this.changes;
159
- const previousOperation = changeSet.get(index);
143
+ const previousOperation = this.indexedOperations[index];
160
144
  if (!previousOperation || previousOperation === spec_1.OPERATION.DELETE) {
161
145
  const op = (!previousOperation)
162
146
  ? operation
163
147
  : (previousOperation === spec_1.OPERATION.DELETE)
164
148
  ? spec_1.OPERATION.DELETE_AND_ADD
165
149
  : operation;
166
- changeSet.set(index, op);
150
+ //
151
+ // TODO: are DELETE operations being encoded as ADD here ??
152
+ //
153
+ this.indexedOperations[index] = op;
167
154
  }
168
- //
169
- // TODO: are DELETE operations being encoded as ADD here ??
170
- //
155
+ setOperationAtIndex(changeSet, index);
171
156
  if (isFiltered) {
172
- this.allFilteredChanges.set(index, spec_1.OPERATION.ADD);
173
- this.root?.filteredChanges.set(this, this.filteredChanges);
157
+ setOperationAtIndex(this.allFilteredChanges, index);
158
+ if (this.root) {
159
+ enqueueChangeTree(this.root, this, 'filteredChanges');
160
+ enqueueChangeTree(this.root, this, 'allFilteredChanges');
161
+ }
174
162
  }
175
163
  else {
176
- this.allChanges.set(index, spec_1.OPERATION.ADD);
177
- this.root?.changes.set(this, this.changes);
164
+ setOperationAtIndex(this.allChanges, index);
165
+ enqueueChangeTree(this.root, this, 'changes');
178
166
  }
179
167
  }
180
168
  shiftChangeIndexes(shiftIndex) {
@@ -186,12 +174,15 @@ class ChangeTree {
186
174
  const changeSet = (this.isFiltered)
187
175
  ? this.filteredChanges
188
176
  : this.changes;
189
- const changeSetEntries = Array.from(changeSet.entries());
190
- changeSet.clear();
191
- // Re-insert each entry with the shifted index
192
- for (const [index, op] of changeSetEntries) {
193
- changeSet.set(index + shiftIndex, op);
177
+ const newIndexedOperations = {};
178
+ const newIndexes = {};
179
+ for (const index in this.indexedOperations) {
180
+ newIndexedOperations[Number(index) + shiftIndex] = this.indexedOperations[index];
181
+ newIndexes[Number(index) + shiftIndex] = changeSet[index];
194
182
  }
183
+ this.indexedOperations = newIndexedOperations;
184
+ changeSet.indexes = newIndexes;
185
+ changeSet.operations = changeSet.operations.map((index) => index + shiftIndex);
195
186
  }
196
187
  shiftAllChangeIndexes(shiftIndex, startIndex = 0) {
197
188
  //
@@ -199,7 +190,7 @@ class ChangeTree {
199
190
  //
200
191
  // - ArraySchema#splice()
201
192
  //
202
- if (this.isFiltered || this.isPartiallyFiltered) {
193
+ if (this.filteredChanges !== undefined) {
203
194
  this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allFilteredChanges);
204
195
  this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allChanges);
205
196
  }
@@ -207,33 +198,42 @@ class ChangeTree {
207
198
  this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allChanges);
208
199
  }
209
200
  }
210
- _shiftAllChangeIndexes(shiftIndex, startIndex = 0, allChangeSet) {
211
- Array.from(allChangeSet.entries()).forEach(([index, op]) => {
212
- // console.log('shiftAllChangeIndexes', index >= startIndex, { index, op, shiftIndex, startIndex })
213
- if (index >= startIndex) {
214
- allChangeSet.delete(index);
215
- allChangeSet.set(index + shiftIndex, op);
201
+ _shiftAllChangeIndexes(shiftIndex, startIndex = 0, changeSet) {
202
+ const newIndexes = {};
203
+ for (const key in changeSet.indexes) {
204
+ const index = changeSet.indexes[key];
205
+ if (index > startIndex) {
206
+ newIndexes[Number(key) + shiftIndex] = index;
216
207
  }
217
- });
208
+ else {
209
+ newIndexes[key] = index;
210
+ }
211
+ }
212
+ changeSet.indexes = newIndexes;
213
+ for (let i = 0; i < changeSet.operations.length; i++) {
214
+ const index = changeSet.operations[i];
215
+ if (index > startIndex) {
216
+ changeSet.operations[i] = index + shiftIndex;
217
+ }
218
+ }
218
219
  }
219
220
  indexedOperation(index, operation, allChangesIndex = index) {
220
- const metadata = this.ref['constructor'][Symbol.metadata];
221
- const isFiltered = this.isFiltered || (metadata && metadata[metadata[index]].tag !== undefined);
222
- if (isFiltered) {
223
- this.allFilteredChanges.set(allChangesIndex, spec_1.OPERATION.ADD);
224
- this.filteredChanges.set(index, operation);
225
- this.root?.filteredChanges.set(this, this.filteredChanges);
221
+ this.indexedOperations[index] = operation;
222
+ if (this.filteredChanges !== undefined) {
223
+ setOperationAtIndex(this.allFilteredChanges, allChangesIndex);
224
+ setOperationAtIndex(this.filteredChanges, index);
225
+ enqueueChangeTree(this.root, this, 'filteredChanges');
226
226
  }
227
227
  else {
228
- this.allChanges.set(allChangesIndex, spec_1.OPERATION.ADD);
229
- this.changes.set(index, operation);
230
- this.root?.changes.set(this, this.changes);
228
+ setOperationAtIndex(this.allChanges, allChangesIndex);
229
+ setOperationAtIndex(this.changes, index);
230
+ enqueueChangeTree(this.root, this, 'changes');
231
231
  }
232
232
  }
233
233
  getType(index) {
234
234
  if (Metadata_1.Metadata.isValidInstance(this.ref)) {
235
- const metadata = this.ref['constructor'][Symbol.metadata];
236
- return metadata[metadata[index]].type;
235
+ const metadata = this.ref.constructor[Symbol.metadata];
236
+ return metadata[index].type;
237
237
  }
238
238
  else {
239
239
  //
@@ -246,8 +246,7 @@ class ChangeTree {
246
246
  }
247
247
  }
248
248
  getChange(index) {
249
- // TODO: optimize this. avoid checking against multiple instances
250
- return this.changes.get(index) ?? this.filteredChanges.get(index);
249
+ return this.indexedOperations[index];
251
250
  }
252
251
  //
253
252
  // used during `.encode()`
@@ -268,16 +267,14 @@ class ChangeTree {
268
267
  }
269
268
  return;
270
269
  }
271
- const metadata = this.ref['constructor'][Symbol.metadata];
272
- const isFiltered = this.isFiltered || (metadata && metadata[metadata[index]].tag !== undefined);
273
- const changeSet = (isFiltered)
270
+ const changeSet = (this.filteredChanges !== undefined)
274
271
  ? this.filteredChanges
275
272
  : this.changes;
273
+ this.indexedOperations[index] = operation ?? spec_1.OPERATION.DELETE;
274
+ setOperationAtIndex(changeSet, index);
276
275
  const previousValue = this.getValue(index);
277
- changeSet.set(index, operation ?? spec_1.OPERATION.DELETE);
278
276
  // remove `root` reference
279
277
  if (previousValue && previousValue[symbols_1.$changes]) {
280
- previousValue[symbols_1.$changes].root = undefined;
281
278
  //
282
279
  // FIXME: this.root is "undefined"
283
280
  //
@@ -286,27 +283,31 @@ class ChangeTree {
286
283
  // - This is due to using the concrete Schema class at decoding time.
287
284
  // - "Reflected" structures do not have this problem.
288
285
  //
289
- // (the property descriptors should NOT be used at decoding time. only at encoding time.)
286
+ // (The property descriptors should NOT be used at decoding time. only at encoding time.)
290
287
  //
291
288
  this.root?.remove(previousValue[symbols_1.$changes]);
292
289
  }
293
290
  //
294
- // FIXME: this is looking a bit ugly (and repeated from `.change()`)
291
+ // FIXME: this is looking a ugly and repeated
295
292
  //
296
- if (isFiltered) {
297
- this.root?.filteredChanges.set(this, this.filteredChanges);
298
- this.allFilteredChanges.delete(allChangesIndex);
293
+ if (this.filteredChanges !== undefined) {
294
+ deleteOperationAtIndex(this.allFilteredChanges, allChangesIndex);
295
+ enqueueChangeTree(this.root, this, 'filteredChanges');
299
296
  }
300
297
  else {
301
- this.root?.changes.set(this, this.changes);
302
- this.allChanges.delete(allChangesIndex);
298
+ deleteOperationAtIndex(this.allChanges, allChangesIndex);
299
+ enqueueChangeTree(this.root, this, 'changes');
303
300
  }
304
301
  }
305
302
  endEncode() {
306
- this.changes.clear();
303
+ this.indexedOperations = {};
304
+ // // clear changes
305
+ // this.changes.indexes = {};
306
+ // this.changes.operations.length = 0;
307
+ // ArraySchema and MapSchema have a custom "encode end" method
307
308
  this.ref[symbols_1.$onEncodeEnd]?.();
308
309
  // Not a new instance anymore
309
- delete this[symbols_1.$isNew];
310
+ this.isNew = false;
310
311
  }
311
312
  discard(discardAll = false) {
312
313
  //
@@ -315,13 +316,22 @@ class ChangeTree {
315
316
  // REPLACE in case same key is used on next patches.
316
317
  //
317
318
  this.ref[symbols_1.$onEncodeEnd]?.();
318
- this.changes.clear();
319
- this.filteredChanges.clear();
320
- // reset operation index
321
- this.currentOperationIndex = 0;
319
+ this.indexedOperations = {};
320
+ this.changes.indexes = {};
321
+ this.changes.operations.length = 0;
322
+ this.changes.queueRootIndex = undefined;
323
+ if (this.filteredChanges !== undefined) {
324
+ this.filteredChanges.indexes = {};
325
+ this.filteredChanges.operations.length = 0;
326
+ this.filteredChanges.queueRootIndex = undefined;
327
+ }
322
328
  if (discardAll) {
323
- this.allChanges.clear();
324
- this.allFilteredChanges.clear();
329
+ this.allChanges.indexes = {};
330
+ this.allChanges.operations.length = 0;
331
+ if (this.allFilteredChanges !== undefined) {
332
+ this.allFilteredChanges.indexes = {};
333
+ this.allFilteredChanges.operations.length = 0;
334
+ }
325
335
  // remove children references
326
336
  this.forEachChild((changeTree, _) => this.root?.remove(changeTree));
327
337
  }
@@ -330,12 +340,13 @@ class ChangeTree {
330
340
  * Recursively discard all changes from this, and child structures.
331
341
  */
332
342
  discardAll() {
333
- this.changes.forEach((_, fieldIndex) => {
334
- const value = this.getValue(fieldIndex);
343
+ const keys = Object.keys(this.indexedOperations);
344
+ for (let i = 0, len = keys.length; i < len; i++) {
345
+ const value = this.getValue(Number(keys[i]));
335
346
  if (value && value[symbols_1.$changes]) {
336
347
  value[symbols_1.$changes].discardAll();
337
348
  }
338
- });
349
+ }
339
350
  this.discard();
340
351
  }
341
352
  ensureRefId() {
@@ -346,36 +357,79 @@ class ChangeTree {
346
357
  this.refId = this.root.getNextUniqueId();
347
358
  }
348
359
  get changed() {
349
- return this.changes.size > 0;
360
+ return (Object.entries(this.indexedOperations).length > 0);
350
361
  }
351
362
  checkIsFiltered(parent, parentIndex) {
352
- // Detect if current structure has "filters" declared
353
- this.isPartiallyFiltered = this.ref['constructor']?.[Symbol.metadata]?.[-2];
354
- // TODO: support "partially filtered", where the instance is visible, but only a field is not.
355
- // Detect if parent has "filters" declared
356
- while (parent && !this.isFiltered) {
357
- const metadata = parent['constructor'][Symbol.metadata];
358
- const fieldName = metadata?.[parentIndex];
359
- const isParentOwned = metadata?.[fieldName]?.tag !== undefined;
360
- this.isFiltered = isParentOwned || parent[symbols_1.$changes].isFiltered; // metadata?.[-2]
361
- parent = parent[symbols_1.$changes].parent;
362
- }
363
- ;
363
+ const isNewChangeTree = this.root.add(this);
364
+ if (this.root.types.hasFilters) {
365
+ //
366
+ // At Schema initialization, the "root" structure might not be available
367
+ // yet, as it only does once the "Encoder" has been set up.
368
+ //
369
+ // So the "parent" may be already set without a "root".
370
+ //
371
+ this._checkFilteredByParent(parent, parentIndex);
372
+ if (this.filteredChanges !== undefined) {
373
+ enqueueChangeTree(this.root, this, 'filteredChanges');
374
+ if (isNewChangeTree) {
375
+ this.root.allFilteredChanges.push(this);
376
+ }
377
+ }
378
+ }
379
+ if (!this.isFiltered) {
380
+ enqueueChangeTree(this.root, this, 'changes');
381
+ if (isNewChangeTree) {
382
+ this.root.allChanges.push(this);
383
+ }
384
+ }
385
+ }
386
+ _checkFilteredByParent(parent, parentIndex) {
387
+ // skip if parent is not set
388
+ if (!parent) {
389
+ return;
390
+ }
391
+ //
392
+ // ArraySchema | MapSchema - get the child type
393
+ // (if refType is typeof string, the parentFiltered[key] below will always be invalid)
394
+ //
395
+ const refType = Metadata_1.Metadata.isValidInstance(this.ref)
396
+ ? this.ref.constructor
397
+ : this.ref[symbols_1.$childType];
398
+ if (!Metadata_1.Metadata.isValidInstance(parent)) {
399
+ const parentChangeTree = parent[symbols_1.$changes];
400
+ parent = parentChangeTree.parent;
401
+ parentIndex = parentChangeTree.parentIndex;
402
+ }
403
+ const parentConstructor = parent.constructor;
404
+ let key = `${this.root.types.getTypeId(refType)}`;
405
+ if (parentConstructor) {
406
+ key += `-${this.root.types.schemas.get(parentConstructor)}`;
407
+ }
408
+ key += `-${parentIndex}`;
409
+ this.isFiltered = parent[symbols_1.$changes].isFiltered // in case parent is already filtered
410
+ || this.root.types.parentFiltered[key];
411
+ // const parentMetadata = parentConstructor?.[Symbol.metadata];
412
+ // this.isFiltered = parentMetadata?.[$viewFieldIndexes]?.includes(parentIndex) || this.root.types.parentFiltered[key];
364
413
  //
365
414
  // TODO: refactor this!
366
415
  //
367
416
  // swapping `changes` and `filteredChanges` is required here
368
417
  // because "isFiltered" may not be imedialely available on `change()`
418
+ // (this happens when instance is detached from root or parent)
369
419
  //
370
- if (this.isFiltered && this.changes.size > 0) {
371
- // swap changes reference
372
- const changes = this.changes;
373
- this.changes = this.filteredChanges;
374
- this.filteredChanges = changes;
375
- // swap "all changes" reference
376
- const allFilteredChanges = this.allFilteredChanges;
377
- this.allFilteredChanges = this.allChanges;
378
- this.allChanges = allFilteredChanges;
420
+ if (this.isFiltered) {
421
+ this.filteredChanges = { indexes: {}, operations: [] };
422
+ this.allFilteredChanges = { indexes: {}, operations: [] };
423
+ if (this.changes.operations.length > 0) {
424
+ // swap changes reference
425
+ const changes = this.changes;
426
+ this.changes = this.filteredChanges;
427
+ this.filteredChanges = changes;
428
+ // swap "all changes" reference
429
+ const allFilteredChanges = this.allFilteredChanges;
430
+ this.allFilteredChanges = this.allChanges;
431
+ this.allChanges = allFilteredChanges;
432
+ }
379
433
  }
380
434
  }
381
435
  }