@colyseus/schema 3.0.0-alpha.4 → 3.0.0-alpha.41

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