@colyseus/schema 3.0.0-alpha.33 → 3.0.0-alpha.35

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 (63) hide show
  1. package/bin/schema-debug +94 -0
  2. package/build/cjs/index.js +465 -303
  3. package/build/cjs/index.js.map +1 -1
  4. package/build/esm/index.mjs +465 -303
  5. package/build/esm/index.mjs.map +1 -1
  6. package/build/umd/index.js +465 -303
  7. package/lib/Metadata.d.ts +5 -5
  8. package/lib/Metadata.js +17 -17
  9. package/lib/Metadata.js.map +1 -1
  10. package/lib/Schema.js +24 -17
  11. package/lib/Schema.js.map +1 -1
  12. package/lib/annotations.js +11 -11
  13. package/lib/annotations.js.map +1 -1
  14. package/lib/bench_encode.js +12 -5
  15. package/lib/bench_encode.js.map +1 -1
  16. package/lib/decoder/Decoder.js +1 -1
  17. package/lib/decoder/Decoder.js.map +1 -1
  18. package/lib/encoder/ChangeTree.d.ts +23 -7
  19. package/lib/encoder/ChangeTree.js +183 -106
  20. package/lib/encoder/ChangeTree.js.map +1 -1
  21. package/lib/encoder/EncodeOperation.d.ts +2 -1
  22. package/lib/encoder/EncodeOperation.js +2 -2
  23. package/lib/encoder/EncodeOperation.js.map +1 -1
  24. package/lib/encoder/Encoder.d.ts +3 -5
  25. package/lib/encoder/Encoder.js +93 -61
  26. package/lib/encoder/Encoder.js.map +1 -1
  27. package/lib/encoder/Root.d.ts +12 -7
  28. package/lib/encoder/Root.js +41 -20
  29. package/lib/encoder/Root.js.map +1 -1
  30. package/lib/encoder/StateView.d.ts +5 -5
  31. package/lib/encoder/StateView.js +29 -23
  32. package/lib/encoder/StateView.js.map +1 -1
  33. package/lib/encoding/encode.js +12 -9
  34. package/lib/encoding/encode.js.map +1 -1
  35. package/lib/types/TypeContext.js +2 -1
  36. package/lib/types/TypeContext.js.map +1 -1
  37. package/lib/types/custom/ArraySchema.js +27 -13
  38. package/lib/types/custom/ArraySchema.js.map +1 -1
  39. package/lib/types/custom/MapSchema.d.ts +3 -1
  40. package/lib/types/custom/MapSchema.js +7 -4
  41. package/lib/types/custom/MapSchema.js.map +1 -1
  42. package/lib/types/symbols.d.ts +8 -6
  43. package/lib/types/symbols.js +9 -7
  44. package/lib/types/symbols.js.map +1 -1
  45. package/lib/utils.js +6 -3
  46. package/lib/utils.js.map +1 -1
  47. package/package.json +3 -2
  48. package/src/Metadata.ts +22 -22
  49. package/src/Schema.ts +33 -25
  50. package/src/annotations.ts +12 -12
  51. package/src/bench_encode.ts +15 -6
  52. package/src/decoder/Decoder.ts +1 -1
  53. package/src/encoder/ChangeTree.ts +220 -115
  54. package/src/encoder/EncodeOperation.ts +5 -1
  55. package/src/encoder/Encoder.ts +110 -68
  56. package/src/encoder/Root.ts +41 -21
  57. package/src/encoder/StateView.ts +32 -28
  58. package/src/encoding/encode.ts +12 -9
  59. package/src/types/TypeContext.ts +2 -1
  60. package/src/types/custom/ArraySchema.ts +39 -17
  61. package/src/types/custom/MapSchema.ts +12 -5
  62. package/src/types/symbols.ts +10 -9
  63. package/src/utils.ts +7 -3
@@ -40,7 +40,6 @@
40
40
  const $filter = Symbol("$filter");
41
41
  const $getByIndex = Symbol("$getByIndex");
42
42
  const $deleteByIndex = Symbol("$deleteByIndex");
43
- const $descriptors = Symbol("$descriptors");
44
43
  /**
45
44
  * Used to hold ChangeTree instances whitin the structures
46
45
  */
@@ -50,11 +49,6 @@
50
49
  * (MapSchema, ArraySchema, etc.)
51
50
  */
52
51
  const $childType = Symbol('$childType');
53
- /**
54
- * Special ChangeTree property to identify new instances
55
- * (Once they're encoded, they're not new anymore)
56
- */
57
- const $isNew = Symbol("$isNew");
58
52
  /**
59
53
  * Optional "discard" method for custom types (ArraySchema)
60
54
  * (Discards changes for next serialization)
@@ -64,6 +58,14 @@
64
58
  * When decoding, this method is called after the instance is fully decoded
65
59
  */
66
60
  const $onDecodeEnd = Symbol("$onDecodeEnd");
61
+ /**
62
+ * Metadata
63
+ */
64
+ const $descriptors = Symbol("$descriptors");
65
+ const $numFields = "$__numFields";
66
+ const $refTypeFieldIndexes = "$__refTypeFieldIndexes";
67
+ const $viewFieldIndexes = "$__viewFieldIndexes";
68
+ const $fieldIndexesByViewTag = "$__fieldIndexesByViewTag";
67
69
 
68
70
  const registeredTypes = {};
69
71
  const identifiers = new Map();
@@ -110,7 +112,7 @@
110
112
  };
111
113
  }
112
114
  // map -1 as last field index
113
- Object.defineProperty(metadata, -1, {
115
+ Object.defineProperty(metadata, $numFields, {
114
116
  value: index,
115
117
  enumerable: false,
116
118
  configurable: true
@@ -123,14 +125,14 @@
123
125
  });
124
126
  // if child Ref/complex type, add to -4
125
127
  if (typeof (metadata[index].type) !== "string") {
126
- if (metadata[-4] === undefined) {
127
- Object.defineProperty(metadata, -4, {
128
+ if (metadata[$refTypeFieldIndexes] === undefined) {
129
+ Object.defineProperty(metadata, $refTypeFieldIndexes, {
128
130
  value: [],
129
131
  enumerable: false,
130
132
  configurable: true,
131
133
  });
132
134
  }
133
- metadata[-4].push(index);
135
+ metadata[$refTypeFieldIndexes].push(index);
134
136
  }
135
137
  },
136
138
  setTag(metadata, fieldName, tag) {
@@ -138,25 +140,25 @@
138
140
  const field = metadata[index];
139
141
  // add 'tag' to the field
140
142
  field.tag = tag;
141
- if (!metadata[-2]) {
143
+ if (!metadata[$viewFieldIndexes]) {
142
144
  // -2: all field indexes with "view" tag
143
- Object.defineProperty(metadata, -2, {
145
+ Object.defineProperty(metadata, $viewFieldIndexes, {
144
146
  value: [],
145
147
  enumerable: false,
146
148
  configurable: true
147
149
  });
148
150
  // -3: field indexes by "view" tag
149
- Object.defineProperty(metadata, -3, {
151
+ Object.defineProperty(metadata, $fieldIndexesByViewTag, {
150
152
  value: {},
151
153
  enumerable: false,
152
154
  configurable: true
153
155
  });
154
156
  }
155
- metadata[-2].push(index);
156
- if (!metadata[-3][tag]) {
157
- metadata[-3][tag] = [];
157
+ metadata[$viewFieldIndexes].push(index);
158
+ if (!metadata[$fieldIndexesByViewTag][tag]) {
159
+ metadata[$fieldIndexesByViewTag][tag] = [];
158
160
  }
159
- metadata[-3][tag].push(index);
161
+ metadata[$fieldIndexesByViewTag][tag].push(index);
160
162
  },
161
163
  setFields(target, fields) {
162
164
  const metadata = (target.prototype.constructor[Symbol.metadata] ??= {});
@@ -181,7 +183,7 @@
181
183
  //
182
184
  const metadata = {};
183
185
  klass[Symbol.metadata] = metadata;
184
- Object.defineProperty(metadata, -1, {
186
+ Object.defineProperty(metadata, $numFields, {
185
187
  value: 0,
186
188
  enumerable: false,
187
189
  configurable: true,
@@ -195,7 +197,7 @@
195
197
  if (parentMetadata) {
196
198
  // assign parent metadata to current
197
199
  Object.assign(metadata, parentMetadata);
198
- for (let i = 0; i <= parentMetadata[-1]; i++) {
200
+ for (let i = 0; i <= parentMetadata[$numFields]; i++) {
199
201
  const fieldName = parentMetadata[i].name;
200
202
  Object.defineProperty(metadata, fieldName, {
201
203
  value: parentMetadata[fieldName],
@@ -203,8 +205,8 @@
203
205
  configurable: true,
204
206
  });
205
207
  }
206
- Object.defineProperty(metadata, -1, {
207
- value: parentMetadata[-1],
208
+ Object.defineProperty(metadata, $numFields, {
209
+ value: parentMetadata[$numFields],
208
210
  enumerable: false,
209
211
  configurable: true,
210
212
  writable: true,
@@ -216,40 +218,69 @@
216
218
  },
217
219
  isValidInstance(klass) {
218
220
  return (klass.constructor[Symbol.metadata] &&
219
- Object.prototype.hasOwnProperty.call(klass.constructor[Symbol.metadata], -1));
221
+ Object.prototype.hasOwnProperty.call(klass.constructor[Symbol.metadata], $numFields));
220
222
  },
221
223
  getFields(klass) {
222
224
  const metadata = klass[Symbol.metadata];
223
225
  const fields = {};
224
- for (let i = 0; i <= metadata[-1]; i++) {
226
+ for (let i = 0; i <= metadata[$numFields]; i++) {
225
227
  fields[metadata[i].name] = metadata[i].type;
226
228
  }
227
229
  return fields;
228
230
  }
229
231
  };
230
232
 
231
- var _a$5;
233
+ function setOperationAtIndex(changeSet, index) {
234
+ const operationsIndex = changeSet.indexes[index];
235
+ if (operationsIndex === undefined) {
236
+ changeSet.indexes[index] = changeSet.operations.push(index) - 1;
237
+ }
238
+ else {
239
+ changeSet.operations[operationsIndex] = index;
240
+ }
241
+ }
242
+ function deleteOperationAtIndex(changeSet, index) {
243
+ const operationsIndex = changeSet.indexes[index];
244
+ if (operationsIndex !== undefined) {
245
+ changeSet.operations[operationsIndex] = undefined;
246
+ }
247
+ delete changeSet.indexes[index];
248
+ }
249
+ function enqueueChangeTree(root, changeTree, changeSet, queueRootIndex = changeTree[changeSet].queueRootIndex) {
250
+ if (root && root[changeSet][queueRootIndex] !== changeTree) {
251
+ changeTree[changeSet].queueRootIndex = root[changeSet].push(changeTree) - 1;
252
+ }
253
+ }
232
254
  class ChangeTree {
233
- static { _a$5 = $isNew; }
234
255
  constructor(ref) {
235
256
  this.isFiltered = false;
236
257
  this.isPartiallyFiltered = false;
237
- this.currentOperationIndex = 0;
238
- this.changes = new Map();
239
- this.allChanges = new Map();
240
- this[_a$5] = true;
258
+ this.indexedOperations = {};
259
+ //
260
+ // TODO:
261
+ // try storing the index + operation per item.
262
+ // example: 1024 & 1025 => ADD, 1026 => DELETE
263
+ //
264
+ // => https://chatgpt.com/share/67107d0c-bc20-8004-8583-83b17dd7c196
265
+ //
266
+ this.changes = { indexes: {}, operations: [] };
267
+ this.allChanges = { indexes: {}, operations: [] };
268
+ /**
269
+ * Is this a new instance? Used on ArraySchema to determine OPERATION.MOVE_AND_ADD operation.
270
+ */
271
+ this.isNew = true;
241
272
  this.ref = ref;
242
273
  //
243
274
  // Does this structure have "filters" declared?
244
275
  //
245
- if (ref.constructor[Symbol.metadata]?.[-2]) {
246
- this.allFilteredChanges = new Map();
247
- this.filteredChanges = new Map();
276
+ if (ref.constructor[Symbol.metadata]?.[$viewFieldIndexes]) {
277
+ this.allFilteredChanges = { indexes: {}, operations: [] };
278
+ this.filteredChanges = { indexes: {}, operations: [] };
248
279
  }
249
280
  }
250
281
  setRoot(root) {
251
282
  this.root = root;
252
- this.root.add(this);
283
+ const isNewChangeTree = this.root.add(this);
253
284
  const metadata = this.ref.constructor[Symbol.metadata];
254
285
  if (this.root.types.hasFilters) {
255
286
  //
@@ -260,22 +291,24 @@
260
291
  //
261
292
  this.checkIsFiltered(metadata, this.parent, this.parentIndex);
262
293
  if (this.isFiltered || this.isPartiallyFiltered) {
263
- this.root.allFilteredChanges.set(this, this.allFilteredChanges);
264
- this.root.filteredChanges.set(this, this.filteredChanges);
294
+ enqueueChangeTree(root, this, 'filteredChanges');
295
+ if (isNewChangeTree) {
296
+ this.root.allFilteredChanges.push(this);
297
+ }
265
298
  }
266
299
  }
267
300
  if (!this.isFiltered) {
268
- this.root.changes.set(this, this.changes);
269
- this.root.allChanges.set(this, this.allChanges);
301
+ enqueueChangeTree(root, this, 'changes');
302
+ if (isNewChangeTree) {
303
+ this.root.allChanges.push(this);
304
+ }
270
305
  }
271
- this.ensureRefId();
306
+ // Recursively set root on child structures
272
307
  if (metadata) {
273
- metadata[-4]?.forEach((index) => {
308
+ metadata[$refTypeFieldIndexes]?.forEach((index) => {
274
309
  const field = metadata[index];
275
310
  const value = this.ref[field.name];
276
- if (value) {
277
- value[$changes].setRoot(root);
278
- }
311
+ value?.[$changes].setRoot(root);
279
312
  });
280
313
  }
281
314
  else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
@@ -292,31 +325,36 @@
292
325
  if (!root) {
293
326
  return;
294
327
  }
295
- root.add(this);
296
328
  const metadata = this.ref.constructor[Symbol.metadata];
297
329
  // skip if parent is already set
298
330
  if (root !== this.root) {
299
331
  this.root = root;
332
+ const isNewChangeTree = root.add(this);
300
333
  if (root.types.hasFilters) {
301
334
  this.checkIsFiltered(metadata, parent, parentIndex);
302
335
  if (this.isFiltered || this.isPartiallyFiltered) {
303
- this.root.filteredChanges.set(this, this.filteredChanges);
304
- this.root.allFilteredChanges.set(this, this.filteredChanges);
336
+ enqueueChangeTree(root, this, 'filteredChanges');
337
+ if (isNewChangeTree) {
338
+ this.root.allFilteredChanges.push(this);
339
+ }
305
340
  }
306
341
  }
307
342
  if (!this.isFiltered) {
308
- this.root.changes.set(this, this.changes);
309
- this.root.allChanges.set(this, this.allChanges);
343
+ enqueueChangeTree(root, this, 'changes');
344
+ if (isNewChangeTree) {
345
+ this.root.allChanges.push(this);
346
+ }
310
347
  }
311
- this.ensureRefId();
348
+ }
349
+ else {
350
+ root.add(this);
312
351
  }
313
352
  // assign same parent on child structures
314
353
  if (metadata) {
315
- metadata[-4]?.forEach((index) => {
354
+ metadata[$refTypeFieldIndexes]?.forEach((index) => {
316
355
  const field = metadata[index];
317
356
  const value = this.ref[field.name];
318
357
  value?.[$changes].setParent(this.ref, root, index);
319
- // console.log(this.ref.constructor.name, field.name, value);
320
358
  // try { throw new Error(); } catch (e) {
321
359
  // console.log(e.stack);
322
360
  // }
@@ -335,7 +373,7 @@
335
373
  //
336
374
  const metadata = this.ref.constructor[Symbol.metadata];
337
375
  if (metadata) {
338
- metadata[-4]?.forEach((index) => {
376
+ metadata[$refTypeFieldIndexes]?.forEach((index) => {
339
377
  const field = metadata[index];
340
378
  const value = this.ref[field.name];
341
379
  if (value) {
@@ -351,8 +389,10 @@
351
389
  }
352
390
  }
353
391
  operation(op) {
354
- this.changes.set(--this.currentOperationIndex, op);
355
- this.root?.changes.set(this, this.changes);
392
+ // operations without index use negative values to represent them
393
+ // this is checked during .encode() time.
394
+ this.changes.operations.push(-op);
395
+ enqueueChangeTree(this.root, this, 'changes');
356
396
  }
357
397
  change(index, operation = exports.OPERATION.ADD) {
358
398
  const metadata = this.ref.constructor[Symbol.metadata];
@@ -360,7 +400,7 @@
360
400
  const changeSet = (isFiltered)
361
401
  ? this.filteredChanges
362
402
  : this.changes;
363
- const previousOperation = changeSet.get(index);
403
+ const previousOperation = this.indexedOperations[index];
364
404
  if (!previousOperation || previousOperation === exports.OPERATION.DELETE) {
365
405
  const op = (!previousOperation)
366
406
  ? operation
@@ -370,16 +410,19 @@
370
410
  //
371
411
  // TODO: are DELETE operations being encoded as ADD here ??
372
412
  //
373
- changeSet.set(index, op);
413
+ this.indexedOperations[index] = op;
374
414
  }
415
+ setOperationAtIndex(changeSet, index);
375
416
  if (isFiltered) {
376
- this.allFilteredChanges.set(index, exports.OPERATION.ADD);
377
- this.root?.filteredChanges.set(this, this.filteredChanges);
378
- this.root?.allFilteredChanges.set(this, this.allFilteredChanges);
417
+ setOperationAtIndex(this.allFilteredChanges, index);
418
+ if (this.root) {
419
+ enqueueChangeTree(this.root, this, 'filteredChanges');
420
+ enqueueChangeTree(this.root, this, 'allFilteredChanges');
421
+ }
379
422
  }
380
423
  else {
381
- this.allChanges.set(index, exports.OPERATION.ADD);
382
- this.root?.changes.set(this, this.changes);
424
+ setOperationAtIndex(this.allChanges, index);
425
+ enqueueChangeTree(this.root, this, 'changes');
383
426
  }
384
427
  }
385
428
  shiftChangeIndexes(shiftIndex) {
@@ -391,12 +434,15 @@
391
434
  const changeSet = (this.isFiltered)
392
435
  ? this.filteredChanges
393
436
  : this.changes;
394
- const changeSetEntries = Array.from(changeSet.entries());
395
- changeSet.clear();
396
- // Re-insert each entry with the shifted index
397
- for (const [index, op] of changeSetEntries) {
398
- changeSet.set(index + shiftIndex, op);
437
+ const newIndexedOperations = {};
438
+ const newIndexes = {};
439
+ for (const index in this.indexedOperations) {
440
+ newIndexedOperations[Number(index) + shiftIndex] = this.indexedOperations[index];
441
+ newIndexes[Number(index) + shiftIndex] = changeSet[index];
399
442
  }
443
+ this.indexedOperations = newIndexedOperations;
444
+ changeSet.indexes = newIndexes;
445
+ changeSet.operations = changeSet.operations.map((index) => index + shiftIndex);
400
446
  }
401
447
  shiftAllChangeIndexes(shiftIndex, startIndex = 0) {
402
448
  //
@@ -412,24 +458,36 @@
412
458
  this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allChanges);
413
459
  }
414
460
  }
415
- _shiftAllChangeIndexes(shiftIndex, startIndex = 0, allChangeSet) {
416
- Array.from(allChangeSet.entries()).forEach(([index, op]) => {
417
- if (index >= startIndex) {
418
- allChangeSet.delete(index);
419
- allChangeSet.set(index + shiftIndex, op);
461
+ _shiftAllChangeIndexes(shiftIndex, startIndex = 0, changeSet) {
462
+ const newIndexes = {};
463
+ for (const key in changeSet.indexes) {
464
+ const index = changeSet.indexes[key];
465
+ if (index > startIndex) {
466
+ newIndexes[Number(key) + shiftIndex] = index;
420
467
  }
421
- });
468
+ else {
469
+ newIndexes[key] = index;
470
+ }
471
+ }
472
+ changeSet.indexes = newIndexes;
473
+ for (let i = 0; i < changeSet.operations.length; i++) {
474
+ const index = changeSet.operations[i];
475
+ if (index > startIndex) {
476
+ changeSet.operations[i] = index + shiftIndex;
477
+ }
478
+ }
422
479
  }
423
480
  indexedOperation(index, operation, allChangesIndex = index) {
424
- if (this.filteredChanges !== undefined) {
425
- this.allFilteredChanges.set(allChangesIndex, exports.OPERATION.ADD);
426
- this.filteredChanges.set(index, operation);
427
- this.root?.filteredChanges.set(this, this.filteredChanges);
481
+ this.indexedOperations[index] = operation;
482
+ if (this.filteredChanges) {
483
+ setOperationAtIndex(this.allFilteredChanges, allChangesIndex);
484
+ setOperationAtIndex(this.filteredChanges, index);
485
+ enqueueChangeTree(this.root, this, 'filteredChanges');
428
486
  }
429
487
  else {
430
- this.allChanges.set(allChangesIndex, exports.OPERATION.ADD);
431
- this.changes.set(index, operation);
432
- this.root?.changes.set(this, this.changes);
488
+ setOperationAtIndex(this.allChanges, allChangesIndex);
489
+ setOperationAtIndex(this.changes, index);
490
+ enqueueChangeTree(this.root, this, 'changes');
433
491
  }
434
492
  }
435
493
  getType(index) {
@@ -448,8 +506,7 @@
448
506
  }
449
507
  }
450
508
  getChange(index) {
451
- // TODO: optimize this. avoid checking against multiple instances
452
- return this.changes.get(index) ?? this.filteredChanges?.get(index);
509
+ return this.indexedOperations[index];
453
510
  }
454
511
  //
455
512
  // used during `.encode()`
@@ -473,8 +530,9 @@
473
530
  const changeSet = (this.filteredChanges)
474
531
  ? this.filteredChanges
475
532
  : this.changes;
533
+ this.indexedOperations[index] = operation ?? exports.OPERATION.DELETE;
534
+ setOperationAtIndex(changeSet, index);
476
535
  const previousValue = this.getValue(index);
477
- changeSet.set(index, operation ?? exports.OPERATION.DELETE);
478
536
  // remove `root` reference
479
537
  if (previousValue && previousValue[$changes]) {
480
538
  //
@@ -490,23 +548,26 @@
490
548
  this.root?.remove(previousValue[$changes]);
491
549
  }
492
550
  //
493
- // FIXME: this is looking a bit ugly (and repeated from `.change()`)
551
+ // FIXME: this is looking a ugly and repeated
494
552
  //
495
553
  if (this.filteredChanges) {
496
- this.root?.filteredChanges.set(this, this.filteredChanges);
497
- this.allFilteredChanges.delete(allChangesIndex);
554
+ deleteOperationAtIndex(this.allFilteredChanges, allChangesIndex);
555
+ enqueueChangeTree(this.root, this, 'filteredChanges');
498
556
  }
499
557
  else {
500
- this.root?.changes.set(this, this.changes);
501
- this.allChanges.delete(allChangesIndex);
558
+ deleteOperationAtIndex(this.allChanges, allChangesIndex);
559
+ enqueueChangeTree(this.root, this, 'changes');
502
560
  }
503
561
  }
504
562
  endEncode() {
505
- this.changes.clear();
563
+ this.indexedOperations = {};
564
+ // // clear changes
565
+ // this.changes.indexes = {};
566
+ // this.changes.operations.length = 0;
506
567
  // ArraySchema and MapSchema have a custom "encode end" method
507
568
  this.ref[$onEncodeEnd]?.();
508
569
  // Not a new instance anymore
509
- delete this[$isNew];
570
+ this.isNew = false;
510
571
  }
511
572
  discard(discardAll = false) {
512
573
  //
@@ -515,13 +576,22 @@
515
576
  // REPLACE in case same key is used on next patches.
516
577
  //
517
578
  this.ref[$onEncodeEnd]?.();
518
- this.changes.clear();
519
- this.filteredChanges?.clear();
520
- // reset operation index
521
- this.currentOperationIndex = 0;
579
+ this.indexedOperations = {};
580
+ this.changes.indexes = {};
581
+ this.changes.operations.length = 0;
582
+ this.changes.queueRootIndex = undefined;
583
+ if (this.filteredChanges !== undefined) {
584
+ this.filteredChanges.indexes = {};
585
+ this.filteredChanges.operations.length = 0;
586
+ this.filteredChanges.queueRootIndex = undefined;
587
+ }
522
588
  if (discardAll) {
523
- this.allChanges.clear();
524
- this.allFilteredChanges?.clear();
589
+ this.allChanges.indexes = {};
590
+ this.allChanges.operations.length = 0;
591
+ if (this.allFilteredChanges !== undefined) {
592
+ this.allFilteredChanges.indexes = {};
593
+ this.allFilteredChanges.operations.length = 0;
594
+ }
525
595
  // remove children references
526
596
  this.forEachChild((changeTree, _) => this.root?.remove(changeTree));
527
597
  }
@@ -530,12 +600,13 @@
530
600
  * Recursively discard all changes from this, and child structures.
531
601
  */
532
602
  discardAll() {
533
- this.changes.forEach((_, fieldIndex) => {
534
- const value = this.getValue(fieldIndex);
603
+ const keys = Object.keys(this.indexedOperations);
604
+ for (let i = 0, len = keys.length; i < len; i++) {
605
+ const value = this.getValue(Number(keys[i]));
535
606
  if (value && value[$changes]) {
536
607
  value[$changes].discardAll();
537
608
  }
538
- });
609
+ }
539
610
  this.discard();
540
611
  }
541
612
  ensureRefId() {
@@ -546,42 +617,48 @@
546
617
  this.refId = this.root.getNextUniqueId();
547
618
  }
548
619
  get changed() {
549
- return this.changes.size > 0;
620
+ return (Object.entries(this.indexedOperations).length > 0);
550
621
  }
551
622
  checkIsFiltered(metadata, parent, parentIndex) {
552
623
  // Detect if current structure has "filters" declared
553
- this.isPartiallyFiltered = metadata?.[-2] !== undefined;
624
+ this.isPartiallyFiltered = metadata?.[$viewFieldIndexes] !== undefined;
554
625
  if (this.isPartiallyFiltered) {
555
- this.filteredChanges = this.filteredChanges || new Map();
556
- this.allFilteredChanges = this.allFilteredChanges || new Map();
626
+ this.filteredChanges = this.filteredChanges || { indexes: {}, operations: [] };
627
+ this.allFilteredChanges = this.allFilteredChanges || { indexes: {}, operations: [] };
557
628
  }
558
- if (parent) {
559
- if (!Metadata.isValidInstance(parent)) {
560
- const parentChangeTree = parent[$changes];
561
- parent = parentChangeTree.parent;
562
- parentIndex = parentChangeTree.parentIndex;
563
- }
564
- const parentMetadata = parent?.constructor?.[Symbol.metadata];
565
- this.isFiltered = (parent && parentMetadata?.[-2]?.includes(parentIndex));
566
- //
567
- // TODO: refactor this!
568
- //
569
- // swapping `changes` and `filteredChanges` is required here
570
- // because "isFiltered" may not be imedialely available on `change()`
571
- //
572
- if (this.isFiltered) {
573
- this.filteredChanges = new Map();
574
- this.allFilteredChanges = new Map();
575
- if (this.changes.size > 0) {
576
- // swap changes reference
577
- const changes = this.changes;
578
- this.changes = this.filteredChanges;
579
- this.filteredChanges = changes;
580
- // swap "all changes" reference
581
- const allFilteredChanges = this.allFilteredChanges;
582
- this.allFilteredChanges = this.allChanges;
583
- this.allChanges = allFilteredChanges;
584
- }
629
+ // skip if parent is not set
630
+ if (!parent) {
631
+ return;
632
+ }
633
+ if (!Metadata.isValidInstance(parent)) {
634
+ const parentChangeTree = parent[$changes];
635
+ parent = parentChangeTree.parent;
636
+ parentIndex = parentChangeTree.parentIndex;
637
+ }
638
+ const parentMetadata = parent.constructor?.[Symbol.metadata];
639
+ this.isFiltered = parentMetadata?.[$viewFieldIndexes]?.includes(parentIndex);
640
+ //
641
+ // TODO: refactor this!
642
+ //
643
+ // swapping `changes` and `filteredChanges` is required here
644
+ // because "isFiltered" may not be imedialely available on `change()`
645
+ //
646
+ if (this.isFiltered) {
647
+ this.filteredChanges = { indexes: {}, operations: [] };
648
+ this.allFilteredChanges = { indexes: {}, operations: [] };
649
+ if (this.changes.operations.length > 0) {
650
+ // swap changes reference
651
+ const changes = this.changes;
652
+ this.changes = this.filteredChanges;
653
+ this.filteredChanges = changes;
654
+ // swap "all changes" reference
655
+ const allFilteredChanges = this.allFilteredChanges;
656
+ this.allFilteredChanges = this.allChanges;
657
+ this.allChanges = allFilteredChanges;
658
+ // console.log("SWAP =>", {
659
+ // "this.allFilteredChanges": this.allFilteredChanges,
660
+ // "this.allChanges": this.allChanges
661
+ // })
585
662
  }
586
663
  }
587
664
  }
@@ -650,21 +727,24 @@
650
727
  view[it.offset++] = c;
651
728
  }
652
729
  else if (c < 0x800) {
653
- view[it.offset++] = 0xc0 | (c >> 6);
654
- view[it.offset++] = 0x80 | (c & 0x3f);
730
+ view[it.offset] = 0xc0 | (c >> 6);
731
+ view[it.offset + 1] = 0x80 | (c & 0x3f);
732
+ it.offset += 2;
655
733
  }
656
734
  else if (c < 0xd800 || c >= 0xe000) {
657
- view[it.offset++] = 0xe0 | (c >> 12);
658
- view[it.offset++] = 0x80 | (c >> 6 & 0x3f);
659
- view[it.offset++] = 0x80 | (c & 0x3f);
735
+ view[it.offset] = 0xe0 | (c >> 12);
736
+ view[it.offset + 1] = 0x80 | (c >> 6 & 0x3f);
737
+ view[it.offset + 2] = 0x80 | (c & 0x3f);
738
+ it.offset += 3;
660
739
  }
661
740
  else {
662
741
  i++;
663
742
  c = 0x10000 + (((c & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff));
664
- view[it.offset++] = 0xf0 | (c >> 18);
665
- view[it.offset++] = 0x80 | (c >> 12 & 0x3f);
666
- view[it.offset++] = 0x80 | (c >> 6 & 0x3f);
667
- view[it.offset++] = 0x80 | (c & 0x3f);
743
+ view[it.offset] = 0xf0 | (c >> 18);
744
+ view[it.offset + 1] = 0x80 | (c >> 12 & 0x3f);
745
+ view[it.offset + 2] = 0x80 | (c >> 6 & 0x3f);
746
+ view[it.offset + 3] = 0x80 | (c & 0x3f);
747
+ it.offset += 4;
668
748
  }
669
749
  }
670
750
  }
@@ -893,7 +973,7 @@
893
973
  * Used for Schema instances.
894
974
  * @private
895
975
  */
896
- const encodeSchemaOperation = function (encoder, bytes, changeTree, index, operation, it) {
976
+ const encodeSchemaOperation = function (encoder, bytes, changeTree, index, operation, it, _, __, metadata) {
897
977
  // "compress" field index + operation
898
978
  bytes[it.offset++] = (index | operation) & 255;
899
979
  // Do not encode value for DELETE operations
@@ -901,7 +981,7 @@
901
981
  return;
902
982
  }
903
983
  const ref = changeTree.ref;
904
- const metadata = ref.constructor[Symbol.metadata];
984
+ // const metadata: Metadata = ref.constructor[Symbol.metadata];
905
985
  const field = metadata[index];
906
986
  // TODO: inline this function call small performance gain
907
987
  encodeValue(encoder, bytes, metadata[index].type, ref[field.name], operation, it);
@@ -1596,6 +1676,7 @@
1596
1676
  const proxy = new Proxy(this, {
1597
1677
  get: (obj, prop) => {
1598
1678
  if (typeof (prop) !== "symbol" &&
1679
+ // FIXME: d8 accuses this as low performance
1599
1680
  !isNaN(prop) // https://stackoverflow.com/a/175787/892698
1600
1681
  ) {
1601
1682
  return this.items[prop];
@@ -1613,7 +1694,7 @@
1613
1694
  if (setValue[$changes]) {
1614
1695
  assertInstanceType(setValue, obj[$childType], obj, key);
1615
1696
  if (obj.items[key] !== undefined) {
1616
- if (setValue[$changes][$isNew]) {
1697
+ if (setValue[$changes].isNew) {
1617
1698
  this[$changes].indexedOperation(Number(key), exports.OPERATION.MOVE_AND_ADD);
1618
1699
  }
1619
1700
  else {
@@ -1625,7 +1706,7 @@
1625
1706
  }
1626
1707
  }
1627
1708
  }
1628
- else if (setValue[$changes][$isNew]) {
1709
+ else if (setValue[$changes].isNew) {
1629
1710
  this[$changes].indexedOperation(Number(key), exports.OPERATION.ADD);
1630
1711
  }
1631
1712
  }
@@ -1659,7 +1740,9 @@
1659
1740
  });
1660
1741
  this[$changes] = new ChangeTree(proxy);
1661
1742
  this[$changes].indexes = {};
1662
- this.push.apply(this, items);
1743
+ if (items.length > 0) {
1744
+ this.push(...items);
1745
+ }
1663
1746
  return proxy;
1664
1747
  }
1665
1748
  set length(newLength) {
@@ -1678,15 +1761,18 @@
1678
1761
  }
1679
1762
  push(...values) {
1680
1763
  let length = this.tmpItems.length;
1681
- values.forEach((value, i) => {
1682
- // skip null values
1764
+ const changeTree = this[$changes];
1765
+ // values.forEach((value, i) => {
1766
+ for (let i = 0, l = values.length; i < values.length; i++, length++) {
1767
+ const value = values[i];
1683
1768
  if (value === undefined || value === null) {
1769
+ // skip null values
1684
1770
  return;
1685
1771
  }
1686
1772
  else if (typeof (value) === "object" && this[$childType]) {
1687
1773
  assertInstanceType(value, this[$childType], this, i);
1774
+ // TODO: move value[$changes]?.setParent() to this block.
1688
1775
  }
1689
- const changeTree = this[$changes];
1690
1776
  changeTree.indexedOperation(length, exports.OPERATION.ADD, this.items.length);
1691
1777
  this.items.push(value);
1692
1778
  this.tmpItems.push(value);
@@ -1695,8 +1781,9 @@
1695
1781
  // (to avoid encoding "refId" operations before parent's "ADD" operation)
1696
1782
  //
1697
1783
  value[$changes]?.setParent(this, changeTree.root, length);
1698
- length++;
1699
- });
1784
+ }
1785
+ // length++;
1786
+ // });
1700
1787
  return length;
1701
1788
  }
1702
1789
  /**
@@ -1717,6 +1804,7 @@
1717
1804
  }
1718
1805
  this[$changes].delete(index, undefined, this.items.length - 1);
1719
1806
  // this.tmpItems[index] = undefined;
1807
+ // this.tmpItems.pop();
1720
1808
  this.deletedIndexes[index] = true;
1721
1809
  return this.items.pop();
1722
1810
  }
@@ -1781,9 +1869,12 @@
1781
1869
  //
1782
1870
  // TODO: do not use [$changes] at decoding time.
1783
1871
  //
1784
- changeTree.root?.changes.delete(changeTree);
1785
- changeTree.root?.allChanges.delete(changeTree);
1786
- changeTree.root?.allFilteredChanges.delete(changeTree);
1872
+ const root = changeTree.root;
1873
+ if (root !== undefined) {
1874
+ root.removeChangeFromChangeSet("changes", changeTree);
1875
+ root.removeChangeFromChangeSet("allChanges", changeTree);
1876
+ root.removeChangeFromChangeSet("allFilteredChanges", changeTree);
1877
+ }
1787
1878
  });
1788
1879
  changeTree.discard(true);
1789
1880
  changeTree.operation(exports.OPERATION.CLEAR);
@@ -1827,6 +1918,7 @@
1827
1918
  const changeTree = this[$changes];
1828
1919
  changeTree.delete(index);
1829
1920
  changeTree.shiftAllChangeIndexes(-1, index);
1921
+ // this.deletedIndexes[index] = true;
1830
1922
  return this.items.shift();
1831
1923
  }
1832
1924
  /**
@@ -1907,10 +1999,12 @@
1907
1999
  changeTree.shiftChangeIndexes(items.length);
1908
2000
  // new index
1909
2001
  if (changeTree.isFiltered) {
1910
- changeTree.filteredChanges.set(this.items.length, exports.OPERATION.ADD);
2002
+ setOperationAtIndex(changeTree.filteredChanges, this.items.length);
2003
+ // changeTree.filteredChanges[this.items.length] = OPERATION.ADD;
1911
2004
  }
1912
2005
  else {
1913
- changeTree.allChanges.set(this.items.length, exports.OPERATION.ADD);
2006
+ setOperationAtIndex(changeTree.allChanges, this.items.length);
2007
+ // changeTree.allChanges[this.items.length] = OPERATION.ADD;
1914
2008
  }
1915
2009
  // FIXME: should we use OPERATION.MOVE here instead?
1916
2010
  items.forEach((_, index) => {
@@ -2251,7 +2345,7 @@
2251
2345
  const isReplace = typeof (changeTree.indexes[key]) !== "undefined";
2252
2346
  const index = (isReplace)
2253
2347
  ? changeTree.indexes[key]
2254
- : changeTree.indexes[-1] ?? 0;
2348
+ : changeTree.indexes[$numFields] ?? 0;
2255
2349
  let operation = (isReplace)
2256
2350
  ? exports.OPERATION.REPLACE
2257
2351
  : exports.OPERATION.ADD;
@@ -2263,7 +2357,7 @@
2263
2357
  if (!isReplace) {
2264
2358
  this.$indexes.set(index, key);
2265
2359
  changeTree.indexes[key] = index;
2266
- changeTree.indexes[-1] = index + 1;
2360
+ changeTree.indexes[$numFields] = index + 1;
2267
2361
  }
2268
2362
  else if (!isRef &&
2269
2363
  this.$items.get(key) === value) {
@@ -2338,8 +2432,11 @@
2338
2432
  }
2339
2433
  [$onEncodeEnd]() {
2340
2434
  const changeTree = this[$changes];
2341
- const changes = changeTree.changes.entries();
2342
- for (const [fieldIndex, operation] of changes) {
2435
+ const keys = Object.keys(changeTree.indexedOperations);
2436
+ for (let i = 0, len = keys.length; i < len; i++) {
2437
+ const key = keys[i];
2438
+ const fieldIndex = Number(key);
2439
+ const operation = changeTree.indexedOperations[key];
2343
2440
  if (operation === exports.OPERATION.DELETE) {
2344
2441
  const index = this[$getByIndex](fieldIndex);
2345
2442
  delete changeTree.indexes[index];
@@ -2442,7 +2539,7 @@
2442
2539
  });
2443
2540
  const metadata = (klass[Symbol.metadata] ??= {});
2444
2541
  // if any schema/field has filters, mark "context" as having filters.
2445
- if (metadata[-2]) {
2542
+ if (metadata[$viewFieldIndexes]) {
2446
2543
  this.hasFilters = true;
2447
2544
  }
2448
2545
  if (parentFieldViewTag !== undefined) {
@@ -2505,8 +2602,8 @@
2505
2602
  // // detect index for this field, considering inheritance
2506
2603
  // //
2507
2604
  // const parent = Object.getPrototypeOf(context.metadata);
2508
- // let fieldIndex: number = context.metadata[-1] // current structure already has fields defined
2509
- // ?? (parent && parent[-1]) // parent structure has fields defined
2605
+ // let fieldIndex: number = context.metadata[$numFields] // current structure already has fields defined
2606
+ // ?? (parent && parent[$numFields]) // parent structure has fields defined
2510
2607
  // ?? -1; // no fields defined
2511
2608
  // fieldIndex++;
2512
2609
  // if (
@@ -2635,8 +2732,8 @@
2635
2732
  // //
2636
2733
  // metadata[fieldIndex] = {
2637
2734
  // type: undefined,
2638
- // index: (metadata[-1] // current structure already has fields defined
2639
- // ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
2735
+ // index: (metadata[$numFields] // current structure already has fields defined
2736
+ // ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
2640
2737
  // ?? -1) + 1 // no fields defined
2641
2738
  // }
2642
2739
  // }
@@ -2679,8 +2776,8 @@
2679
2776
  //
2680
2777
  // detect index for this field, considering inheritance
2681
2778
  //
2682
- fieldIndex = metadata[-1] // current structure already has fields defined
2683
- ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
2779
+ fieldIndex = metadata[$numFields] // current structure already has fields defined
2780
+ ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
2684
2781
  ?? -1; // no fields defined
2685
2782
  fieldIndex++;
2686
2783
  }
@@ -2707,7 +2804,7 @@
2707
2804
  return {
2708
2805
  get: function () { return this[fieldCached]; },
2709
2806
  set: function (value) {
2710
- const previousValue = this[fieldCached] || undefined;
2807
+ const previousValue = this[fieldCached] ?? undefined;
2711
2808
  // skip if value is the same as cached.
2712
2809
  if (value === previousValue) {
2713
2810
  return;
@@ -2779,8 +2876,8 @@
2779
2876
  // //
2780
2877
  // metadata[field] = {
2781
2878
  // type: undefined,
2782
- // index: (metadata[-1] // current structure already has fields defined
2783
- // ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
2879
+ // index: (metadata[$numFields] // current structure already has fields defined
2880
+ // ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
2784
2881
  // ?? -1) + 1 // no fields defined
2785
2882
  // }
2786
2883
  // }
@@ -2818,15 +2915,18 @@
2818
2915
  ops: {},
2819
2916
  refs: []
2820
2917
  };
2821
- $root.changes.forEach((operations, changeTree) => {
2918
+ // for (const refId in $root.changes) {
2919
+ $root.changes.forEach(changeTree => {
2920
+ const changes = changeTree.indexedOperations;
2822
2921
  dump.refs.push(`refId#${changeTree.refId}`);
2823
- operations.forEach((op, index) => {
2922
+ for (const index in changes) {
2923
+ const op = changes[index];
2824
2924
  const opName = exports.OPERATION[op];
2825
2925
  if (!dump.ops[opName]) {
2826
2926
  dump.ops[opName] = 0;
2827
2927
  }
2828
2928
  dump.ops[exports.OPERATION[op]]++;
2829
- });
2929
+ }
2830
2930
  });
2831
2931
  return dump;
2832
2932
  }
@@ -2852,6 +2952,7 @@
2852
2952
  class Schema {
2853
2953
  static { this[_a$2] = encodeSchemaOperation; }
2854
2954
  static { this[_b$2] = decodeSchemaOperation; }
2955
+ // public [$changes]: ChangeTree;
2855
2956
  /**
2856
2957
  * Assign the property descriptors required to track changes on this instance.
2857
2958
  * @param instance
@@ -2911,12 +3012,7 @@
2911
3012
  // inline
2912
3013
  // Schema.initialize(this);
2913
3014
  //
2914
- Object.defineProperty(this, $changes, {
2915
- value: new ChangeTree(this),
2916
- enumerable: false,
2917
- writable: true
2918
- });
2919
- Object.defineProperties(this, this.constructor[Symbol.metadata]?.[$descriptors] || {});
3015
+ Schema.initialize(this);
2920
3016
  //
2921
3017
  // Assign initial values
2922
3018
  //
@@ -2990,7 +3086,7 @@
2990
3086
  const changeTree = ref[$changes];
2991
3087
  const contents = (jsonContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
2992
3088
  let output = "";
2993
- output += `${getIndent(level)}${ref.constructor.name} (${ref[$changes].refId})${contents}\n`;
3089
+ output += `${getIndent(level)}${ref.constructor.name} (refId: ${ref[$changes].refId})${contents}\n`;
2994
3090
  changeTree.forEachChild((childChangeTree) => output += this.debugRefIds(childChangeTree.ref, jsonContents, level + 1));
2995
3091
  return output;
2996
3092
  }
@@ -3008,18 +3104,26 @@
3008
3104
  const changeSetName = (isEncodeAll) ? "allChanges" : "changes";
3009
3105
  let output = `${instance.constructor.name} (${changeTree.refId}) -> .${changeSetName}:\n`;
3010
3106
  function dumpChangeSet(changeSet) {
3011
- Array.from(changeSet)
3012
- .sort((a, b) => a[0] - b[0])
3013
- .forEach(([index, operation]) => output += `- [${index}]: ${exports.OPERATION[operation]} (${JSON.stringify(changeTree.getValue(index, isEncodeAll))})\n`);
3107
+ changeSet.operations
3108
+ .filter(op => op)
3109
+ .forEach((index) => {
3110
+ const operation = changeTree.indexedOperations[index];
3111
+ console.log({ index, operation });
3112
+ output += `- [${index}]: ${exports.OPERATION[operation]} (${JSON.stringify(changeTree.getValue(Number(index), isEncodeAll))})\n`;
3113
+ });
3014
3114
  }
3015
3115
  dumpChangeSet(changeSet);
3016
3116
  // display filtered changes
3017
- if (!isEncodeAll && changeTree.filteredChanges?.size > 0) {
3117
+ if (!isEncodeAll &&
3118
+ changeTree.filteredChanges &&
3119
+ (changeTree.filteredChanges.operations).filter(op => op).length > 0) {
3018
3120
  output += `${instance.constructor.name} (${changeTree.refId}) -> .filteredChanges:\n`;
3019
3121
  dumpChangeSet(changeTree.filteredChanges);
3020
3122
  }
3021
3123
  // display filtered changes
3022
- if (isEncodeAll && changeTree.allFilteredChanges?.size > 0) {
3124
+ if (isEncodeAll &&
3125
+ changeTree.allFilteredChanges &&
3126
+ (changeTree.allFilteredChanges.operations).filter(op => op).length > 0) {
3023
3127
  output += `${instance.constructor.name} (${changeTree.refId}) -> .allFilteredChanges:\n`;
3024
3128
  dumpChangeSet(changeTree.allFilteredChanges);
3025
3129
  }
@@ -3028,10 +3132,12 @@
3028
3132
  static debugChangesDeep(ref, changeSetName = "changes") {
3029
3133
  let output = "";
3030
3134
  const rootChangeTree = ref[$changes];
3135
+ const root = rootChangeTree.root;
3031
3136
  const changeTrees = new Map();
3032
3137
  let totalInstances = 0;
3033
3138
  let totalOperations = 0;
3034
- for (const [changeTree, changes] of (rootChangeTree.root[changeSetName].entries())) {
3139
+ for (const [refId, changes] of Object.entries(root[changeSetName])) {
3140
+ const changeTree = root.changeTrees[refId];
3035
3141
  let includeChangeTree = false;
3036
3142
  let parentChangeTrees = [];
3037
3143
  let parentChangeTree = changeTree.parent?.[$changes];
@@ -3050,7 +3156,7 @@
3050
3156
  }
3051
3157
  if (includeChangeTree) {
3052
3158
  totalInstances += 1;
3053
- totalOperations += changes.size;
3159
+ totalOperations += Object.keys(changes).length;
3054
3160
  changeTrees.set(changeTree, parentChangeTrees.reverse());
3055
3161
  }
3056
3162
  }
@@ -3068,12 +3174,13 @@
3068
3174
  visitedParents.add(parentChangeTree);
3069
3175
  }
3070
3176
  });
3071
- const changes = changeTree.changes;
3177
+ const changes = changeTree.indexedOperations;
3072
3178
  const level = parentChangeTrees.length;
3073
3179
  const indent = getIndent(level);
3074
3180
  const parentIndex = (level > 0) ? `(${changeTree.parentIndex}) ` : "";
3075
- output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.refId}) - changes: ${changes.size}\n`;
3076
- for (const [index, operation] of changes) {
3181
+ output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.refId}) - changes: ${Object.keys(changes).length}\n`;
3182
+ for (const index in changes) {
3183
+ const operation = changes[index];
3077
3184
  output += `${getIndent(level + 1)}${exports.OPERATION[operation]}: ${index}\n`;
3078
3185
  }
3079
3186
  }
@@ -3434,59 +3541,90 @@
3434
3541
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
3435
3542
  };
3436
3543
 
3544
+ function spliceOne(arr, index) {
3545
+ // manually splice an array
3546
+ if (index === -1 || index >= arr.length) {
3547
+ return false;
3548
+ }
3549
+ const len = arr.length - 1;
3550
+ for (let i = index; i < len; i++) {
3551
+ arr[i] = arr[i + 1];
3552
+ }
3553
+ arr.length = len;
3554
+ return true;
3555
+ }
3556
+
3437
3557
  class Root {
3438
3558
  constructor(types) {
3439
3559
  this.types = types;
3440
3560
  this.nextUniqueId = 0;
3441
- this.refCount = new WeakMap();
3561
+ this.refCount = {};
3562
+ this.changeTrees = {};
3442
3563
  // all changes
3443
- this.allChanges = new Map();
3444
- this.allFilteredChanges = new Map();
3564
+ this.allChanges = [];
3565
+ this.allFilteredChanges = []; // TODO: do not initialize it if filters are not used
3445
3566
  // pending changes to be encoded
3446
- this.changes = new Map();
3447
- this.filteredChanges = new Map();
3567
+ this.changes = [];
3568
+ this.filteredChanges = []; // TODO: do not initialize it if filters are not used
3448
3569
  }
3449
3570
  getNextUniqueId() {
3450
3571
  return this.nextUniqueId++;
3451
3572
  }
3452
3573
  add(changeTree) {
3453
- const previousRefCount = this.refCount.get(changeTree);
3574
+ // FIXME: move implementation of `ensureRefId` to `Root` class
3575
+ changeTree.ensureRefId();
3576
+ const isNewChangeTree = (this.changeTrees[changeTree.refId] === undefined);
3577
+ if (isNewChangeTree) {
3578
+ this.changeTrees[changeTree.refId] = changeTree;
3579
+ }
3580
+ const previousRefCount = this.refCount[changeTree.refId];
3454
3581
  if (previousRefCount === 0) {
3455
3582
  //
3456
3583
  // When a ChangeTree is re-added, it means that it was previously removed.
3457
3584
  // We need to re-add all changes to the `changes` map.
3458
3585
  //
3459
- changeTree.allChanges.forEach((operation, index) => {
3460
- changeTree.changes.set(index, operation);
3461
- });
3586
+ const ops = changeTree.allChanges.operations;
3587
+ let len = ops.length;
3588
+ while (len--) {
3589
+ changeTree.indexedOperations[ops[len]] = exports.OPERATION.ADD;
3590
+ setOperationAtIndex(changeTree.changes, len);
3591
+ }
3462
3592
  }
3463
- const refCount = (previousRefCount || 0) + 1;
3464
- this.refCount.set(changeTree, refCount);
3465
- return refCount;
3593
+ this.refCount[changeTree.refId] = (previousRefCount || 0) + 1;
3594
+ return isNewChangeTree;
3466
3595
  }
3467
3596
  remove(changeTree) {
3468
- const refCount = (this.refCount.get(changeTree)) - 1;
3597
+ const refCount = (this.refCount[changeTree.refId]) - 1;
3469
3598
  if (refCount <= 0) {
3470
3599
  //
3471
3600
  // Only remove "root" reference if it's the last reference
3472
3601
  //
3473
3602
  changeTree.root = undefined;
3474
- this.allChanges.delete(changeTree);
3475
- this.changes.delete(changeTree);
3603
+ delete this.changeTrees[changeTree.refId];
3604
+ this.removeChangeFromChangeSet("allChanges", changeTree);
3605
+ this.removeChangeFromChangeSet("changes", changeTree);
3476
3606
  if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
3477
- this.allFilteredChanges.delete(changeTree);
3478
- this.filteredChanges.delete(changeTree);
3607
+ this.removeChangeFromChangeSet("allFilteredChanges", changeTree);
3608
+ this.removeChangeFromChangeSet("filteredChanges", changeTree);
3479
3609
  }
3480
- this.refCount.set(changeTree, 0);
3610
+ this.refCount[changeTree.refId] = 0;
3481
3611
  }
3482
3612
  else {
3483
- this.refCount.set(changeTree, refCount);
3613
+ this.refCount[changeTree.refId] = refCount;
3484
3614
  }
3485
3615
  changeTree.forEachChild((child, _) => this.remove(child));
3486
3616
  return refCount;
3487
3617
  }
3618
+ removeChangeFromChangeSet(changeSetName, changeTree) {
3619
+ const changeSet = this[changeSetName];
3620
+ const index = changeSet.indexOf(changeTree);
3621
+ if (index !== -1) {
3622
+ spliceOne(changeSet, index);
3623
+ // changeSet[index] = undefined;
3624
+ }
3625
+ }
3488
3626
  clear() {
3489
- this.changes.clear();
3627
+ this.changes.length = 0;
3490
3628
  }
3491
3629
  }
3492
3630
 
@@ -3510,20 +3648,26 @@
3510
3648
  this.state = state;
3511
3649
  this.state[$changes].setRoot(this.root);
3512
3650
  }
3513
- encode(it = { offset: 0 }, view, buffer = this.sharedBuffer, changeTrees = this.root.changes, isEncodeAll = this.root.allChanges === changeTrees, initialOffset = it.offset // cache current offset in case we need to resize the buffer
3651
+ encode(it = { offset: 0 }, view, buffer = this.sharedBuffer, changeSetName = "changes", isEncodeAll = changeSetName === "allChanges", initialOffset = it.offset // cache current offset in case we need to resize the buffer
3514
3652
  ) {
3515
3653
  const hasView = (view !== undefined);
3516
3654
  const rootChangeTree = this.state[$changes];
3517
- const shouldClearChanges = !isEncodeAll && !hasView;
3518
- for (const [changeTree, changes] of changeTrees.entries()) {
3655
+ const shouldDiscardChanges = !isEncodeAll && !hasView;
3656
+ const changeTrees = this.root[changeSetName];
3657
+ for (let i = 0, numChangeTrees = changeTrees.length; i < numChangeTrees; i++) {
3658
+ const changeTree = changeTrees[i];
3659
+ // // Root#removeChangeFromChangeSet() is now removing instead of setting to "undefined"
3660
+ // if (changeTree === undefined) { continue; }
3661
+ const operations = changeTree[changeSetName];
3519
3662
  const ref = changeTree.ref;
3520
3663
  const ctor = ref.constructor;
3521
3664
  const encoder = ctor[$encoder];
3522
3665
  const filter = ctor[$filter];
3666
+ const metadata = ctor[Symbol.metadata];
3523
3667
  // try { throw new Error(); } catch (e) {
3524
3668
  // // only print if not coming from Reflection.ts
3525
3669
  // if (!e.stack.includes("src/Reflection.ts")) {
3526
- // console.log("ChangeTree:", { ref: ref.constructor.name, });
3670
+ // console.log("ChangeTree:", { refId: changeTree.refId, ref: ref.constructor.name });
3527
3671
  // }
3528
3672
  // }
3529
3673
  if (hasView) {
@@ -3541,7 +3685,13 @@
3541
3685
  buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
3542
3686
  number$1(buffer, changeTree.refId, it);
3543
3687
  }
3544
- for (const [fieldIndex, operation] of changes.entries()) {
3688
+ for (let j = 0, numChanges = operations.operations.length; j < numChanges; j++) {
3689
+ const fieldIndex = operations.operations[j];
3690
+ const operation = (fieldIndex < 0)
3691
+ ? Math.abs(fieldIndex) // "pure" operation without fieldIndex (e.g. CLEAR, REVERSE, etc.)
3692
+ : (isEncodeAll)
3693
+ ? exports.OPERATION.ADD
3694
+ : changeTree.indexedOperations[fieldIndex];
3545
3695
  //
3546
3696
  // first pass (encodeAll), identify "filtered" operations without encoding them
3547
3697
  // they will be encoded per client, based on their view.
@@ -3549,7 +3699,7 @@
3549
3699
  // TODO: how can we optimize filtering out "encode all" operations?
3550
3700
  // TODO: avoid checking if no view tags were defined
3551
3701
  //
3552
- if (filter && !filter(ref, fieldIndex, view)) {
3702
+ if (fieldIndex === undefined || operation === undefined || (filter && !filter(ref, fieldIndex, view))) {
3553
3703
  // console.log("ADD AS INVISIBLE:", fieldIndex, changeTree.ref.constructor.name)
3554
3704
  // view?.invisible.add(changeTree);
3555
3705
  continue;
@@ -3564,16 +3714,14 @@
3564
3714
  // });
3565
3715
  // }
3566
3716
  // }
3567
- encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView);
3717
+ // console.log("encode...", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, fieldIndex, operation });
3718
+ encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView, metadata);
3719
+ }
3720
+ if (shouldDiscardChanges) {
3721
+ changeTree.discard();
3722
+ // Not a new instance anymore
3723
+ changeTree.isNew = false;
3568
3724
  }
3569
- // if (shouldClearChanges) {
3570
- // // changeTree.endEncode();
3571
- // changeTree.changes.clear();
3572
- // // ArraySchema and MapSchema have a custom "encode end" method
3573
- // changeTree.ref[$onEncodeEnd]?.();
3574
- // // Not a new instance anymore
3575
- // delete changeTree[$isNew];
3576
- // }
3577
3725
  }
3578
3726
  if (it.offset > buffer.byteLength) {
3579
3727
  const newSize = getNextPowerOf2(buffer.byteLength * 2);
@@ -3590,51 +3738,54 @@
3590
3738
  if (buffer === this.sharedBuffer) {
3591
3739
  this.sharedBuffer = buffer;
3592
3740
  }
3593
- return this.encode({ offset: initialOffset }, view, buffer, changeTrees, isEncodeAll);
3741
+ return this.encode({ offset: initialOffset }, view, buffer, changeSetName, isEncodeAll);
3594
3742
  }
3595
3743
  else {
3596
- //
3597
- // only clear changes after making sure buffer resize is not required.
3598
- //
3599
- if (shouldClearChanges) {
3600
- //
3601
- // FIXME: avoid iterating over change trees twice.
3602
- //
3603
- this.onEndEncode(changeTrees);
3604
- }
3744
+ // //
3745
+ // // only clear changes after making sure buffer resize is not required.
3746
+ // //
3747
+ // if (shouldClearChanges) {
3748
+ // //
3749
+ // // FIXME: avoid iterating over change trees twice.
3750
+ // //
3751
+ // this.onEndEncode(changeTrees);
3752
+ // }
3605
3753
  return buffer.subarray(0, it.offset);
3606
3754
  }
3607
3755
  }
3608
3756
  encodeAll(it = { offset: 0 }, buffer = this.sharedBuffer) {
3609
- // console.log(`\nencodeAll(), this.root.allChanges (${this.root.allChanges.size})`);
3757
+ // console.log(`\nencodeAll(), this.root.allChanges (${(Object.keys(this.root.allChanges).length)})`);
3610
3758
  // this.debugChanges("allChanges");
3611
- return this.encode(it, undefined, buffer, this.root.allChanges, true);
3759
+ return this.encode(it, undefined, buffer, "allChanges", true);
3612
3760
  }
3613
3761
  encodeAllView(view, sharedOffset, it, bytes = this.sharedBuffer) {
3614
3762
  const viewOffset = it.offset;
3615
- // console.log(`\nencodeAllView(), this.root.allFilteredChanges (${this.root.allFilteredChanges.size})`);
3763
+ // console.log(`\nencodeAllView(), this.root.allFilteredChanges (${(Object.keys(this.root.allFilteredChanges).length)})`);
3616
3764
  // this.debugChanges("allFilteredChanges");
3765
+ // console.log("\n\nENCODE ALL FOR VIEW...\n\n")
3617
3766
  // try to encode "filtered" changes
3618
- this.encode(it, view, bytes, this.root.allFilteredChanges, true, viewOffset);
3767
+ this.encode(it, view, bytes, "allFilteredChanges", true, viewOffset);
3619
3768
  return Buffer.concat([
3620
3769
  bytes.subarray(0, sharedOffset),
3621
3770
  bytes.subarray(viewOffset, it.offset)
3622
3771
  ]);
3623
3772
  }
3624
3773
  debugChanges(field) {
3625
- const changeSet = (typeof (field) === "string")
3774
+ const rootChangeSet = (typeof (field) === "string")
3626
3775
  ? this.root[field]
3627
3776
  : field;
3628
- Array.from(changeSet.entries()).map((item) => {
3629
- const metadata = item[0].ref.constructor[Symbol.metadata];
3630
- console.log("->", { ref: item[0].ref.constructor.name, refId: item[0].refId, changes: item[1].size });
3631
- item[1].forEach((op, index) => {
3777
+ rootChangeSet.forEach((changeTree) => {
3778
+ const changeSet = changeTree[field];
3779
+ const metadata = changeTree.ref.constructor[Symbol.metadata];
3780
+ console.log("->", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, changes: Object.keys(changeSet).length });
3781
+ for (const index in changeSet) {
3782
+ const op = changeSet[index];
3632
3783
  console.log(" ->", {
3633
3784
  index,
3634
3785
  field: metadata?.[index],
3635
3786
  op: exports.OPERATION[op],
3636
3787
  });
3637
- });
3788
+ }
3638
3789
  });
3639
3790
  }
3640
3791
  encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
@@ -3644,23 +3795,31 @@
3644
3795
  // console.log(`\nencodeView(), this.root.filteredChanges (${this.root.filteredChanges.size})`);
3645
3796
  // this.debugChanges("filteredChanges");
3646
3797
  // encode visibility changes (add/remove for this view)
3647
- const viewChangesIterator = view.changes.entries();
3648
- for (const [changeTree, changes] of viewChangesIterator) {
3649
- if (changes.size === 0) {
3650
- // FIXME: avoid having empty changes if no changes were made
3651
- // console.log("changes.size === 0", changeTree.ref.constructor.name);
3798
+ const refIds = Object.keys(view.changes);
3799
+ // console.log("ENCODE VIEW:", refIds);
3800
+ for (let i = 0, numRefIds = refIds.length; i < numRefIds; i++) {
3801
+ const refId = refIds[i];
3802
+ const changes = view.changes[refId];
3803
+ const changeTree = this.root.changeTrees[refId];
3804
+ if (changeTree === undefined ||
3805
+ Object.keys(changes).length === 0 // FIXME: avoid having empty changes if no changes were made
3806
+ ) {
3807
+ // console.log("changes.size === 0, skip", changeTree.ref.constructor.name);
3652
3808
  continue;
3653
3809
  }
3654
3810
  const ref = changeTree.ref;
3655
- const ctor = ref['constructor'];
3811
+ const ctor = ref.constructor;
3656
3812
  const encoder = ctor[$encoder];
3813
+ const metadata = ctor[Symbol.metadata];
3657
3814
  bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
3658
3815
  number$1(bytes, changeTree.refId, it);
3659
- const changesIterator = changes.entries();
3660
- for (const [fieldIndex, operation] of changesIterator) {
3816
+ const keys = Object.keys(changes);
3817
+ for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
3818
+ const key = keys[i];
3819
+ const operation = changes[key];
3661
3820
  // isEncodeAll = false
3662
3821
  // hasView = true
3663
- encoder(this, bytes, changeTree, fieldIndex, operation, it, false, true);
3822
+ encoder(this, bytes, changeTree, Number(key), operation, it, false, true, metadata);
3664
3823
  }
3665
3824
  }
3666
3825
  //
@@ -3668,35 +3827,46 @@
3668
3827
  // (to allow re-using StateView's for multiple clients)
3669
3828
  //
3670
3829
  // clear "view" changes after encoding
3671
- view.changes.clear();
3830
+ view.changes = {};
3831
+ // console.log("FILTERED CHANGES:", this.root.filteredChanges);
3672
3832
  // try to encode "filtered" changes
3673
- this.encode(it, view, bytes, this.root.filteredChanges, false, viewOffset);
3833
+ this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
3674
3834
  return Buffer.concat([
3675
3835
  bytes.subarray(0, sharedOffset),
3676
3836
  bytes.subarray(viewOffset, it.offset)
3677
3837
  ]);
3678
3838
  }
3679
3839
  onEndEncode(changeTrees = this.root.changes) {
3680
- const changeTreesIterator = changeTrees.entries();
3681
- for (const [changeTree, _] of changeTreesIterator) {
3682
- changeTree.endEncode();
3683
- // changeTree.changes.clear();
3684
- // // ArraySchema and MapSchema have a custom "encode end" method
3685
- // changeTree.ref[$onEncodeEnd]?.();
3686
- // // Not a new instance anymore
3687
- // delete changeTree[$isNew];
3688
- }
3840
+ // changeTrees.forEach(function(changeTree) {
3841
+ // changeTree.endEncode();
3842
+ // });
3843
+ // for (const refId in changeTrees) {
3844
+ // const changeTree = this.root.changeTrees[refId];
3845
+ // changeTree.endEncode();
3846
+ // // changeTree.changes.clear();
3847
+ // // // ArraySchema and MapSchema have a custom "encode end" method
3848
+ // // changeTree.ref[$onEncodeEnd]?.();
3849
+ // // // Not a new instance anymore
3850
+ // // delete changeTree[$isNew];
3851
+ // }
3689
3852
  }
3690
3853
  discardChanges() {
3854
+ // console.log("DISCARD CHANGES!");
3691
3855
  // discard shared changes
3692
- if (this.root.changes.size > 0) {
3693
- this.onEndEncode(this.root.changes);
3694
- this.root.changes.clear();
3856
+ let length = this.root.changes.length;
3857
+ if (length > 0) {
3858
+ while (length--) {
3859
+ this.root.changes[length]?.endEncode();
3860
+ }
3861
+ this.root.changes.length = 0;
3695
3862
  }
3696
3863
  // discard filtered changes
3697
- if (this.root.filteredChanges.size > 0) {
3698
- this.onEndEncode(this.root.filteredChanges);
3699
- this.root.filteredChanges.clear();
3864
+ length = this.root.filteredChanges.length;
3865
+ if (length > 0) {
3866
+ while (length--) {
3867
+ this.root.filteredChanges[length]?.endEncode();
3868
+ }
3869
+ this.root.filteredChanges.length = 0;
3700
3870
  }
3701
3871
  }
3702
3872
  tryEncodeTypeId(bytes, baseType, targetType, it) {
@@ -3709,19 +3879,6 @@
3709
3879
  }
3710
3880
  }
3711
3881
 
3712
- function spliceOne(arr, index) {
3713
- // manually splice an array
3714
- if (index === -1 || index >= arr.length) {
3715
- return false;
3716
- }
3717
- const len = arr.length - 1;
3718
- for (let i = index; i < len; i++) {
3719
- arr[i] = arr[i + 1];
3720
- }
3721
- arr.length = len;
3722
- return true;
3723
- }
3724
-
3725
3882
  class DecodingWarning extends Error {
3726
3883
  constructor(message) {
3727
3884
  super(message);
@@ -3883,7 +4040,7 @@
3883
4040
  }
3884
4041
  ref[$onDecodeEnd]?.();
3885
4042
  ref = nextRef;
3886
- decoder = ref['constructor'][$decoder];
4043
+ decoder = ref.constructor[$decoder];
3887
4044
  continue;
3888
4045
  }
3889
4046
  const result = decoder(this, bytes, it, ref, allChanges);
@@ -4360,7 +4517,7 @@
4360
4517
  * Manual "ADD" operations for changes per ChangeTree, specific to this view.
4361
4518
  * (This is used to force encoding a property, even if it was not changed)
4362
4519
  */
4363
- this.changes = new Map();
4520
+ this.changes = {};
4364
4521
  }
4365
4522
  // TODO: allow to set multiple tags at once
4366
4523
  add(obj, tag = DEFAULT_VIEW_TAG, checkIncludeParent = true) {
@@ -4382,10 +4539,10 @@
4382
4539
  // TODO: when adding an item of a MapSchema, the changes may not
4383
4540
  // be set (only the parent's changes are set)
4384
4541
  //
4385
- let changes = this.changes.get(changeTree);
4542
+ let changes = this.changes[changeTree.refId];
4386
4543
  if (changes === undefined) {
4387
- changes = new Map();
4388
- this.changes.set(changeTree, changes);
4544
+ changes = {};
4545
+ this.changes[changeTree.refId] = changes;
4389
4546
  }
4390
4547
  // set tag
4391
4548
  if (tag !== DEFAULT_VIEW_TAG) {
@@ -4402,9 +4559,9 @@
4402
4559
  }
4403
4560
  tags.add(tag);
4404
4561
  // Ref: add tagged properties
4405
- metadata?.[-3]?.[tag]?.forEach((index) => {
4562
+ metadata?.[$fieldIndexesByViewTag]?.[tag]?.forEach((index) => {
4406
4563
  if (changeTree.getChange(index) !== exports.OPERATION.DELETE) {
4407
- changes.set(index, exports.OPERATION.ADD);
4564
+ changes[index] = exports.OPERATION.ADD;
4408
4565
  }
4409
4566
  });
4410
4567
  }
@@ -4413,16 +4570,21 @@
4413
4570
  const changeSet = (changeTree.isFiltered || changeTree.isPartiallyFiltered)
4414
4571
  ? changeTree.allFilteredChanges
4415
4572
  : changeTree.allChanges;
4416
- changeSet.forEach((op, index) => {
4573
+ for (let i = 0, len = changeSet.operations.length; i < len; i++) {
4574
+ const index = changeSet.operations[i];
4575
+ if (index === undefined) {
4576
+ continue;
4577
+ } // skip "undefined" indexes
4578
+ const op = changeTree.indexedOperations[index];
4417
4579
  const tagAtIndex = metadata?.[index].tag;
4418
4580
  if ((isInvisible || // if "invisible", include all
4419
4581
  tagAtIndex === undefined || // "all change" with no tag
4420
4582
  tagAtIndex === tag // tagged property
4421
4583
  ) &&
4422
4584
  op !== exports.OPERATION.DELETE) {
4423
- changes.set(index, op);
4585
+ changes[index] = op;
4424
4586
  }
4425
- });
4587
+ }
4426
4588
  }
4427
4589
  // Add children of this ChangeTree to this view
4428
4590
  changeTree.forEachChild((change, index) => {
@@ -4448,10 +4610,10 @@
4448
4610
  }
4449
4611
  // add parent's tag properties
4450
4612
  if (changeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {
4451
- let changes = this.changes.get(changeTree);
4613
+ let changes = this.changes[changeTree.refId];
4452
4614
  if (changes === undefined) {
4453
- changes = new Map();
4454
- this.changes.set(changeTree, changes);
4615
+ changes = {};
4616
+ this.changes[changeTree.refId] = changes;
4455
4617
  }
4456
4618
  if (!this.tags) {
4457
4619
  this.tags = new WeakMap();
@@ -4465,7 +4627,7 @@
4465
4627
  tags = this.tags.get(changeTree);
4466
4628
  }
4467
4629
  tags.add(tag);
4468
- changes.set(parentIndex, exports.OPERATION.ADD);
4630
+ changes[parentIndex] = exports.OPERATION.ADD;
4469
4631
  }
4470
4632
  }
4471
4633
  remove(obj, tag = DEFAULT_VIEW_TAG) {
@@ -4477,32 +4639,32 @@
4477
4639
  this.items.delete(changeTree);
4478
4640
  const ref = changeTree.ref;
4479
4641
  const metadata = ref.constructor[Symbol.metadata];
4480
- let changes = this.changes.get(changeTree);
4642
+ let changes = this.changes[changeTree.refId];
4481
4643
  if (changes === undefined) {
4482
- changes = new Map();
4483
- this.changes.set(changeTree, changes);
4644
+ changes = {};
4645
+ this.changes[changeTree.refId] = changes;
4484
4646
  }
4485
4647
  if (tag === DEFAULT_VIEW_TAG) {
4486
4648
  // parent is collection (Map/Array)
4487
4649
  const parent = changeTree.parent;
4488
4650
  if (!Metadata.isValidInstance(parent)) {
4489
4651
  const parentChangeTree = parent[$changes];
4490
- let changes = this.changes.get(parentChangeTree);
4652
+ let changes = this.changes[parentChangeTree.refId];
4491
4653
  if (changes === undefined) {
4492
- changes = new Map();
4493
- this.changes.set(parentChangeTree, changes);
4654
+ changes = {};
4655
+ this.changes[parentChangeTree.refId] = changes;
4494
4656
  }
4495
4657
  // DELETE / DELETE BY REF ID
4496
- changes.set(changeTree.parentIndex, exports.OPERATION.DELETE);
4658
+ changes[changeTree.parentIndex] = exports.OPERATION.DELETE;
4497
4659
  }
4498
4660
  else {
4499
4661
  // delete all "tagged" properties.
4500
- metadata[-2].forEach((index) => changes.set(index, exports.OPERATION.DELETE));
4662
+ metadata[$viewFieldIndexes].forEach((index) => changes[index] = exports.OPERATION.DELETE);
4501
4663
  }
4502
4664
  }
4503
4665
  else {
4504
4666
  // delete only tagged properties
4505
- metadata[-3][tag].forEach((index) => changes.set(index, exports.OPERATION.DELETE));
4667
+ metadata[$fieldIndexesByViewTag][tag].forEach((index) => changes[index] = exports.OPERATION.DELETE);
4506
4668
  }
4507
4669
  // remove tag
4508
4670
  if (this.tags && this.tags.has(changeTree)) {