@jbrowse/mobx-state-tree 6.6.0 → 6.6.1
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.
- package/CHANGELOG.md +10 -0
- package/dist/mobx-state-tree.cjs +144 -68
- package/dist/mobx-state-tree.cjs.map +1 -1
- package/dist/mobx-state-tree.mjs +144 -68
- package/dist/mobx-state-tree.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
## [6.6.1](https://github.com/GMOD/mobx-state-tree/compare/v6.6.0...v6.6.1) (2026-09-25)
|
|
2
|
+
|
|
3
|
+
### Performance Improvements
|
|
4
|
+
|
|
5
|
+
- Reconcile children in linear time ([530e27f](https://github.com/GMOD/mobx-state-tree/commit/530e27f20046ba449d45c2724106fcb5b4133e06))
|
|
6
|
+
|
|
7
|
+
### Tests
|
|
8
|
+
|
|
9
|
+
- Typecheck the reconcile tests; add pnpm typecheck to the verification norm ([a23e4f3](https://github.com/GMOD/mobx-state-tree/commit/a23e4f34f718dfd398b696ca5e36be88bbe52454))
|
|
10
|
+
|
|
1
11
|
## [6.6.0](https://github.com/GMOD/mobx-state-tree/compare/v6.5.2...v6.6.0) (2026-09-24)
|
|
2
12
|
|
|
3
13
|
### Bug Fixes
|
package/dist/mobx-state-tree.cjs
CHANGED
|
@@ -4469,98 +4469,174 @@ function array(subtype) {
|
|
|
4469
4469
|
*/
|
|
4470
4470
|
function reconcileArrayChildren(parent, childType, oldNodes, newValues, firstNewPath) {
|
|
4471
4471
|
let nothingChanged = true;
|
|
4472
|
-
|
|
4473
|
-
//
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
childType instanceof ModelType &&
|
|
4484
|
-
childType.identifierAttribute) {
|
|
4485
|
-
const byId = new Map();
|
|
4486
|
-
for (const n of oldNodes) {
|
|
4487
|
-
if (n instanceof ObjectNode && n.identifier !== null) {
|
|
4488
|
-
byId.set(n.identifier, n);
|
|
4489
|
-
}
|
|
4490
|
-
}
|
|
4491
|
-
idIndex = { attr: childType.identifierAttribute, byId };
|
|
4492
|
-
}
|
|
4493
|
-
for (let i = 0;; i++) {
|
|
4494
|
-
const hasNewNode = i <= newValues.length - 1;
|
|
4495
|
-
const oldNode = oldNodes[i];
|
|
4496
|
-
let newValue = hasNewNode ? newValues[i] : undefined;
|
|
4472
|
+
const result = [];
|
|
4473
|
+
// every old node before `next` is claimed
|
|
4474
|
+
const claimed = new Uint8Array(oldNodes.length);
|
|
4475
|
+
let next = 0;
|
|
4476
|
+
let finder;
|
|
4477
|
+
for (let i = 0; i < newValues.length; i++) {
|
|
4478
|
+
while (claimed[next]) {
|
|
4479
|
+
next++;
|
|
4480
|
+
}
|
|
4481
|
+
const oldNode = oldNodes[next];
|
|
4482
|
+
let newValue = newValues[i];
|
|
4497
4483
|
const newPath = `${firstNewPath + i}`;
|
|
4498
4484
|
// for some reason, instead of newValue we got a node, fallback to the storedValue
|
|
4499
4485
|
// TODO: https://github.com/mobxjs/mobx-state-tree/issues/340#issuecomment-325581681
|
|
4500
4486
|
if (isNode(newValue)) {
|
|
4501
4487
|
newValue = newValue.storedValue;
|
|
4502
4488
|
}
|
|
4503
|
-
if (!oldNode
|
|
4504
|
-
|
|
4505
|
-
|
|
4489
|
+
if (!oldNode) {
|
|
4490
|
+
if (isStateTreeNode(newValue) &&
|
|
4491
|
+
getStateTreeNode(newValue).parent === parent) {
|
|
4492
|
+
// this node is owned by this parent, but not in the reconcilable set, so it must be double
|
|
4493
|
+
throw fail(`Cannot add an object to a state tree if it is already part of the same or another state tree. Tried to assign an object to '${parent.path}/${newPath}', but it lives already at '${getStateTreeNode(newValue).path}'`);
|
|
4494
|
+
}
|
|
4495
|
+
nothingChanged = false;
|
|
4496
|
+
result.push(valueAsNode(childType, parent, newPath, newValue));
|
|
4497
|
+
}
|
|
4498
|
+
else if (areSame(oldNode, newValue)) {
|
|
4499
|
+
claimed[next] = 1;
|
|
4500
|
+
result.push(valueAsNode(childType, parent, newPath, newValue, oldNode));
|
|
4501
|
+
}
|
|
4502
|
+
else {
|
|
4503
|
+
nothingChanged = false;
|
|
4504
|
+
// a single-element write scans one node, cheaper than building an index
|
|
4505
|
+
const j = oldNodes.length > 1
|
|
4506
|
+
? (finder ??= new ReuseFinder(childType, oldNodes, claimed)).find(newValue, next)
|
|
4507
|
+
: scanForSame(oldNodes, claimed, next, newValue);
|
|
4508
|
+
let oldMatch;
|
|
4509
|
+
if (j >= 0) {
|
|
4510
|
+
claimed[j] = 1;
|
|
4511
|
+
oldMatch = oldNodes[j];
|
|
4512
|
+
}
|
|
4513
|
+
result.push(valueAsNode(childType, parent, newPath, newValue, oldMatch));
|
|
4506
4514
|
}
|
|
4507
|
-
|
|
4508
|
-
|
|
4515
|
+
}
|
|
4516
|
+
for (let j = next; j < oldNodes.length; j++) {
|
|
4517
|
+
if (!claimed[j]) {
|
|
4509
4518
|
nothingChanged = false;
|
|
4510
|
-
oldNodes
|
|
4519
|
+
const oldNode = oldNodes[j];
|
|
4511
4520
|
if (oldNode instanceof ObjectNode) {
|
|
4512
4521
|
// since it is going to be returned by pop/splice/shift better create it before killing it
|
|
4513
4522
|
// so it doesn't end up in an undead state
|
|
4514
4523
|
oldNode.createObservableInstanceIfNeeded();
|
|
4515
4524
|
}
|
|
4516
4525
|
oldNode.die();
|
|
4517
|
-
i--;
|
|
4518
4526
|
}
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
}
|
|
4527
|
-
nothingChanged = false;
|
|
4528
|
-
const newNode = valueAsNode(childType, parent, newPath, newValue);
|
|
4529
|
-
oldNodes.splice(i, 0, newNode);
|
|
4527
|
+
}
|
|
4528
|
+
return nothingChanged ? null : result;
|
|
4529
|
+
}
|
|
4530
|
+
function scanForSame(oldNodes, claimed, from, value) {
|
|
4531
|
+
for (let j = from; j < oldNodes.length; j++) {
|
|
4532
|
+
if (!claimed[j] && areSame(oldNodes[j], value)) {
|
|
4533
|
+
return j;
|
|
4530
4534
|
}
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4535
|
+
}
|
|
4536
|
+
return -1;
|
|
4537
|
+
}
|
|
4538
|
+
/**
|
|
4539
|
+
* Picks the old node the reorder branch reuses: the first unclaimed one
|
|
4540
|
+
* `areSame` holds for. `areSame` reduces to an equality — node identity for a
|
|
4541
|
+
* live node, the old node's snapshot for anything else — except where the
|
|
4542
|
+
* value is a plain object and the old node carries an identifier, where it
|
|
4543
|
+
* runs `is()` before matching ids. So only that case scans, and an identified
|
|
4544
|
+
* plain model looks its id up instead, as ADR 0001 records.
|
|
4545
|
+
*/
|
|
4546
|
+
class ReuseFinder {
|
|
4547
|
+
oldNodes;
|
|
4548
|
+
claimed;
|
|
4549
|
+
idAttribute;
|
|
4550
|
+
byId;
|
|
4551
|
+
byNode;
|
|
4552
|
+
bySnapshot;
|
|
4553
|
+
anyIdentified;
|
|
4554
|
+
constructor(childType, oldNodes, claimed) {
|
|
4555
|
+
this.oldNodes = oldNodes;
|
|
4556
|
+
this.claimed = claimed;
|
|
4557
|
+
this.idAttribute =
|
|
4558
|
+
childType instanceof ModelType ? childType.identifierAttribute : undefined;
|
|
4559
|
+
}
|
|
4560
|
+
find(value, next) {
|
|
4561
|
+
let j;
|
|
4562
|
+
if (this.idAttribute && isPlainObject(value)) {
|
|
4563
|
+
j =
|
|
4564
|
+
this.idIndex(next).get(normalizeIdentifier(value[this.idAttribute])) ??
|
|
4565
|
+
-1;
|
|
4566
|
+
}
|
|
4567
|
+
else if (isStateTreeNode(value)) {
|
|
4568
|
+
j = this.nodeIndex(next).get(getStateTreeNode(value)) ?? -1;
|
|
4569
|
+
}
|
|
4570
|
+
else if (!isPlainObject(value) || !this.hasIdentifiedNode(next)) {
|
|
4571
|
+
j = this.firstWithSnapshot(value, next);
|
|
4534
4572
|
}
|
|
4535
4573
|
else {
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4574
|
+
return scanForSame(this.oldNodes, this.claimed, next, value);
|
|
4575
|
+
}
|
|
4576
|
+
return j >= 0 && !this.claimed[j] && areSame(this.oldNodes[j], value)
|
|
4577
|
+
? j
|
|
4578
|
+
: -1;
|
|
4579
|
+
}
|
|
4580
|
+
// last one wins on a duplicate id, even when it is already claimed
|
|
4581
|
+
idIndex(next) {
|
|
4582
|
+
if (!this.byId) {
|
|
4583
|
+
this.byId = new Map();
|
|
4584
|
+
for (let j = next; j < this.oldNodes.length; j++) {
|
|
4585
|
+
const node = this.oldNodes[j];
|
|
4586
|
+
if (node instanceof ObjectNode && node.identifier !== null) {
|
|
4587
|
+
this.byId.set(node.identifier, j);
|
|
4547
4588
|
}
|
|
4548
4589
|
}
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4590
|
+
}
|
|
4591
|
+
return this.byId;
|
|
4592
|
+
}
|
|
4593
|
+
nodeIndex(next) {
|
|
4594
|
+
if (!this.byNode) {
|
|
4595
|
+
this.byNode = new Map();
|
|
4596
|
+
for (let j = next; j < this.oldNodes.length; j++) {
|
|
4597
|
+
this.byNode.set(this.oldNodes[j], j);
|
|
4598
|
+
}
|
|
4599
|
+
}
|
|
4600
|
+
return this.byNode;
|
|
4601
|
+
}
|
|
4602
|
+
hasIdentifiedNode(next) {
|
|
4603
|
+
if (this.anyIdentified === undefined) {
|
|
4604
|
+
this.anyIdentified = this.oldNodes
|
|
4605
|
+
.slice(next)
|
|
4606
|
+
.some(node => node instanceof ObjectNode &&
|
|
4607
|
+
node.identifier !== null &&
|
|
4608
|
+
!!node.identifierAttribute);
|
|
4609
|
+
}
|
|
4610
|
+
return this.anyIdentified;
|
|
4611
|
+
}
|
|
4612
|
+
firstWithSnapshot(value, next) {
|
|
4613
|
+
if (!this.bySnapshot) {
|
|
4614
|
+
const first = new Map();
|
|
4615
|
+
const nextSame = new Int32Array(this.oldNodes.length);
|
|
4616
|
+
for (let j = this.oldNodes.length - 1; j >= next; j--) {
|
|
4617
|
+
const node = this.oldNodes[j];
|
|
4618
|
+
if (!this.claimed[j] && node.isAlive) {
|
|
4619
|
+
const snapshot = node.snapshot;
|
|
4620
|
+
nextSame[j] = first.get(snapshot) ?? -1;
|
|
4621
|
+
first.set(snapshot, j);
|
|
4556
4622
|
}
|
|
4557
4623
|
}
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4624
|
+
this.bySnapshot = { first, nextSame };
|
|
4625
|
+
}
|
|
4626
|
+
const { first, nextSame } = this.bySnapshot;
|
|
4627
|
+
const head = first.get(value);
|
|
4628
|
+
if (head === undefined) {
|
|
4629
|
+
return -1;
|
|
4630
|
+
}
|
|
4631
|
+
let j = head;
|
|
4632
|
+
while (j >= 0 && (this.claimed[j] || !this.oldNodes[j].isAlive)) {
|
|
4633
|
+
j = nextSame[j];
|
|
4634
|
+
}
|
|
4635
|
+
if (j !== head) {
|
|
4636
|
+
first.set(value, j);
|
|
4561
4637
|
}
|
|
4638
|
+
return j;
|
|
4562
4639
|
}
|
|
4563
|
-
return nothingChanged ? null : oldNodes;
|
|
4564
4640
|
}
|
|
4565
4641
|
/**
|
|
4566
4642
|
* Convert a value to a node at given parent and subpath. Attempts to reuse old node if possible and given.
|