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