@jbrowse/mobx-state-tree 6.6.0 → 6.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/dist/index.d.ts +7 -0
- package/dist/mobx-state-tree.cjs +199 -80
- package/dist/mobx-state-tree.cjs.map +1 -1
- package/dist/mobx-state-tree.mjs +199 -80
- package/dist/mobx-state-tree.mjs.map +1 -1
- package/package.json +1 -1
package/dist/mobx-state-tree.mjs
CHANGED
|
@@ -466,7 +466,7 @@ function resolveIdentifier(type, target, identifier) {
|
|
|
466
466
|
assertIsType(type, 1);
|
|
467
467
|
assertIsStateTreeNode(target, 2);
|
|
468
468
|
assertIsValidIdentifier(identifier, 3);
|
|
469
|
-
const node = getStateTreeNode(target).root.identifierCache
|
|
469
|
+
const node = getStateTreeNode(target).root.identifierCache?.resolve(type, normalizeIdentifier(identifier));
|
|
470
470
|
return node?.value;
|
|
471
471
|
}
|
|
472
472
|
/**
|
|
@@ -1209,7 +1209,7 @@ class ObjectNode extends BaseNode {
|
|
|
1209
1209
|
// addNodeToCache no-ops without an identifier attribute, and reaching the
|
|
1210
1210
|
// root to ask it is a walk up the whole parent chain
|
|
1211
1211
|
if (this.identifierAttribute) {
|
|
1212
|
-
const cache = parent ? parent.
|
|
1212
|
+
const cache = parent ? parent.assertAttachable() : this.identifierCache;
|
|
1213
1213
|
cache.addNodeToCache(this);
|
|
1214
1214
|
}
|
|
1215
1215
|
}
|
|
@@ -1220,8 +1220,11 @@ class ObjectNode extends BaseNode {
|
|
|
1220
1220
|
}
|
|
1221
1221
|
}
|
|
1222
1222
|
createObservableInstance(fireHooks = true) {
|
|
1223
|
+
const diedUncreated = this.state === NodeLifeCycle.DEAD &&
|
|
1224
|
+
this._observableInstanceState ===
|
|
1225
|
+
ObservableInstanceLifecycle.UNINITIALIZED;
|
|
1223
1226
|
if (devMode()) {
|
|
1224
|
-
if (this.state !== NodeLifeCycle.INITIALIZING) {
|
|
1227
|
+
if (!diedUncreated && this.state !== NodeLifeCycle.INITIALIZING) {
|
|
1225
1228
|
// istanbul ignore next
|
|
1226
1229
|
throw fail("assertion failed: the creation of the observable instance must be done on the initializing phase");
|
|
1227
1230
|
}
|
|
@@ -1274,6 +1277,9 @@ class ObjectNode extends BaseNode {
|
|
|
1274
1277
|
// subscribes to never pays for a full-tree snapshot at creation. keepAlive
|
|
1275
1278
|
// on _snapshotComputed preserves snapshot referential stability without it.
|
|
1276
1279
|
this._childNodes = EMPTY_OBJECT;
|
|
1280
|
+
if (diedUncreated) {
|
|
1281
|
+
return;
|
|
1282
|
+
}
|
|
1277
1283
|
this.state = NodeLifeCycle.CREATED;
|
|
1278
1284
|
if (fireHooks) {
|
|
1279
1285
|
this.fireHook(Hook.afterCreate);
|
|
@@ -1328,6 +1334,19 @@ class ObjectNode extends BaseNode {
|
|
|
1328
1334
|
child.notifyDetachOf(subject);
|
|
1329
1335
|
}
|
|
1330
1336
|
}
|
|
1337
|
+
/**
|
|
1338
|
+
* @internal
|
|
1339
|
+
* @hidden
|
|
1340
|
+
* A node that died inside a tree is its own root and has no identifier
|
|
1341
|
+
* cache, so attaching it, or attaching anything to it, cannot work.
|
|
1342
|
+
*/
|
|
1343
|
+
assertAttachable() {
|
|
1344
|
+
const cache = this.root.identifierCache;
|
|
1345
|
+
if (!cache) {
|
|
1346
|
+
throw fail(`'${this}' is no longer part of a state tree, so it cannot be attached to one or have nodes attached to it.`);
|
|
1347
|
+
}
|
|
1348
|
+
return cache;
|
|
1349
|
+
}
|
|
1331
1350
|
setParent(newParent, subpath) {
|
|
1332
1351
|
const parentChanged = newParent !== this.parent;
|
|
1333
1352
|
const subpathChanged = subpath !== this.subpath;
|
|
@@ -1356,9 +1375,9 @@ class ObjectNode extends BaseNode {
|
|
|
1356
1375
|
}
|
|
1357
1376
|
}
|
|
1358
1377
|
if (parentChanged) {
|
|
1359
|
-
|
|
1378
|
+
this.assertAttachable();
|
|
1360
1379
|
this.environment = undefined; // will use root's
|
|
1361
|
-
newParent.
|
|
1380
|
+
newParent.assertAttachable().mergeCache(this);
|
|
1362
1381
|
this.baseSetParent(newParent, subpath);
|
|
1363
1382
|
this.fireHook(Hook.afterAttach);
|
|
1364
1383
|
}
|
|
@@ -1554,13 +1573,33 @@ class ObjectNode extends BaseNode {
|
|
|
1554
1573
|
addHiddenFinalProp(this.storedValue, "toJSON", toJSON);
|
|
1555
1574
|
}
|
|
1556
1575
|
aboutToDie() {
|
|
1576
|
+
const wasUninitialized = this._observableInstanceState ===
|
|
1577
|
+
ObservableInstanceLifecycle.UNINITIALIZED;
|
|
1578
|
+
const children = this.getChildren();
|
|
1579
|
+
if (wasUninitialized &&
|
|
1580
|
+
(this.hasSnapshotPostProcessor ||
|
|
1581
|
+
children.some(child => child instanceof ObjectNode &&
|
|
1582
|
+
child._observableInstanceState !==
|
|
1583
|
+
ObservableInstanceLifecycle.UNINITIALIZED))) {
|
|
1584
|
+
this.createObservableInstanceIfNeeded();
|
|
1585
|
+
}
|
|
1586
|
+
for (const node of children) {
|
|
1587
|
+
if (node.isAlive) {
|
|
1588
|
+
node.aboutToDie();
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1557
1591
|
if (this._observableInstanceState ===
|
|
1558
1592
|
ObservableInstanceLifecycle.UNINITIALIZED) {
|
|
1559
1593
|
return;
|
|
1560
1594
|
}
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1595
|
+
if (wasUninitialized) {
|
|
1596
|
+
const walked = new Set(children);
|
|
1597
|
+
for (const node of this.getChildren()) {
|
|
1598
|
+
if (node.isAlive && !walked.has(node)) {
|
|
1599
|
+
node.aboutToDie();
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1564
1603
|
// beforeDestroy should run before the disposers since else we could end up in a situation where
|
|
1565
1604
|
// a disposer added with addDisposer at this stage (beforeDestroy) is actually never released
|
|
1566
1605
|
this.baseAboutToDie();
|
|
@@ -1857,12 +1896,16 @@ class ComplexType extends BaseType {
|
|
|
1857
1896
|
current.setParent(parent, subpath);
|
|
1858
1897
|
return current;
|
|
1859
1898
|
}
|
|
1899
|
+
const newNode = getStateTreeNodeSafe(newValue);
|
|
1900
|
+
if (newNode) {
|
|
1901
|
+
newNode.assertAttachable();
|
|
1902
|
+
if (!current.isDetaching && newNode.isWithin(current)) {
|
|
1903
|
+
throw fail(`Cannot assign ${newNode} to '${parent.path}/${subpath}': it is part of ${current}, which it would replace.`);
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1860
1906
|
// current node cannot be recycled in any way
|
|
1861
1907
|
current.die(); // noop if detaching
|
|
1862
|
-
|
|
1863
|
-
if (isStateTreeNode(newValue) && this.isAssignableFrom(getType(newValue))) {
|
|
1864
|
-
// newValue is a Node as well, move it here..
|
|
1865
|
-
const newNode = getStateTreeNode(newValue);
|
|
1908
|
+
if (newNode && this.isAssignableFrom(newNode.type)) {
|
|
1866
1909
|
newNode.setParent(parent, subpath);
|
|
1867
1910
|
return newNode;
|
|
1868
1911
|
}
|
|
@@ -4467,98 +4510,174 @@ function array(subtype) {
|
|
|
4467
4510
|
*/
|
|
4468
4511
|
function reconcileArrayChildren(parent, childType, oldNodes, newValues, firstNewPath) {
|
|
4469
4512
|
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;
|
|
4513
|
+
const result = [];
|
|
4514
|
+
// every old node before `next` is claimed
|
|
4515
|
+
const claimed = new Uint8Array(oldNodes.length);
|
|
4516
|
+
let next = 0;
|
|
4517
|
+
let finder;
|
|
4518
|
+
for (let i = 0; i < newValues.length; i++) {
|
|
4519
|
+
while (claimed[next]) {
|
|
4520
|
+
next++;
|
|
4521
|
+
}
|
|
4522
|
+
const oldNode = oldNodes[next];
|
|
4523
|
+
let newValue = newValues[i];
|
|
4495
4524
|
const newPath = `${firstNewPath + i}`;
|
|
4496
4525
|
// for some reason, instead of newValue we got a node, fallback to the storedValue
|
|
4497
4526
|
// TODO: https://github.com/mobxjs/mobx-state-tree/issues/340#issuecomment-325581681
|
|
4498
4527
|
if (isNode(newValue)) {
|
|
4499
4528
|
newValue = newValue.storedValue;
|
|
4500
4529
|
}
|
|
4501
|
-
if (!oldNode
|
|
4502
|
-
|
|
4503
|
-
|
|
4530
|
+
if (!oldNode) {
|
|
4531
|
+
if (isStateTreeNode(newValue) &&
|
|
4532
|
+
getStateTreeNode(newValue).parent === parent) {
|
|
4533
|
+
// this node is owned by this parent, but not in the reconcilable set, so it must be double
|
|
4534
|
+
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}'`);
|
|
4535
|
+
}
|
|
4536
|
+
nothingChanged = false;
|
|
4537
|
+
result.push(valueAsNode(childType, parent, newPath, newValue));
|
|
4538
|
+
}
|
|
4539
|
+
else if (areSame(oldNode, newValue)) {
|
|
4540
|
+
claimed[next] = 1;
|
|
4541
|
+
result.push(valueAsNode(childType, parent, newPath, newValue, oldNode));
|
|
4504
4542
|
}
|
|
4505
|
-
else
|
|
4506
|
-
// new one does not exists
|
|
4543
|
+
else {
|
|
4507
4544
|
nothingChanged = false;
|
|
4508
|
-
|
|
4545
|
+
// a single-element write scans one node, cheaper than building an index
|
|
4546
|
+
const j = oldNodes.length > 1
|
|
4547
|
+
? (finder ??= new ReuseFinder(childType, oldNodes, claimed)).find(newValue, next)
|
|
4548
|
+
: scanForSame(oldNodes, claimed, next, newValue);
|
|
4549
|
+
let oldMatch;
|
|
4550
|
+
if (j >= 0) {
|
|
4551
|
+
claimed[j] = 1;
|
|
4552
|
+
oldMatch = oldNodes[j];
|
|
4553
|
+
}
|
|
4554
|
+
result.push(valueAsNode(childType, parent, newPath, newValue, oldMatch));
|
|
4555
|
+
}
|
|
4556
|
+
}
|
|
4557
|
+
for (let j = next; j < oldNodes.length; j++) {
|
|
4558
|
+
if (!claimed[j]) {
|
|
4559
|
+
nothingChanged = false;
|
|
4560
|
+
const oldNode = oldNodes[j];
|
|
4509
4561
|
if (oldNode instanceof ObjectNode) {
|
|
4510
4562
|
// since it is going to be returned by pop/splice/shift better create it before killing it
|
|
4511
4563
|
// so it doesn't end up in an undead state
|
|
4512
4564
|
oldNode.createObservableInstanceIfNeeded();
|
|
4513
4565
|
}
|
|
4514
4566
|
oldNode.die();
|
|
4515
|
-
i--;
|
|
4516
4567
|
}
|
|
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);
|
|
4568
|
+
}
|
|
4569
|
+
return nothingChanged ? null : result;
|
|
4570
|
+
}
|
|
4571
|
+
function scanForSame(oldNodes, claimed, from, value) {
|
|
4572
|
+
for (let j = from; j < oldNodes.length; j++) {
|
|
4573
|
+
if (!claimed[j] && areSame(oldNodes[j], value)) {
|
|
4574
|
+
return j;
|
|
4528
4575
|
}
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4576
|
+
}
|
|
4577
|
+
return -1;
|
|
4578
|
+
}
|
|
4579
|
+
/**
|
|
4580
|
+
* Picks the old node the reorder branch reuses: the first unclaimed one
|
|
4581
|
+
* `areSame` holds for. `areSame` reduces to an equality — node identity for a
|
|
4582
|
+
* live node, the old node's snapshot for anything else — except where the
|
|
4583
|
+
* value is a plain object and the old node carries an identifier, where it
|
|
4584
|
+
* runs `is()` before matching ids. So only that case scans, and an identified
|
|
4585
|
+
* plain model looks its id up instead, as ADR 0001 records.
|
|
4586
|
+
*/
|
|
4587
|
+
class ReuseFinder {
|
|
4588
|
+
oldNodes;
|
|
4589
|
+
claimed;
|
|
4590
|
+
idAttribute;
|
|
4591
|
+
byId;
|
|
4592
|
+
byNode;
|
|
4593
|
+
bySnapshot;
|
|
4594
|
+
anyIdentified;
|
|
4595
|
+
constructor(childType, oldNodes, claimed) {
|
|
4596
|
+
this.oldNodes = oldNodes;
|
|
4597
|
+
this.claimed = claimed;
|
|
4598
|
+
this.idAttribute =
|
|
4599
|
+
childType instanceof ModelType ? childType.identifierAttribute : undefined;
|
|
4600
|
+
}
|
|
4601
|
+
find(value, next) {
|
|
4602
|
+
let j;
|
|
4603
|
+
if (this.idAttribute && isPlainObject(value)) {
|
|
4604
|
+
j =
|
|
4605
|
+
this.idIndex(next).get(normalizeIdentifier(value[this.idAttribute])) ??
|
|
4606
|
+
-1;
|
|
4607
|
+
}
|
|
4608
|
+
else if (isStateTreeNode(value)) {
|
|
4609
|
+
j = this.nodeIndex(next).get(getStateTreeNode(value)) ?? -1;
|
|
4610
|
+
}
|
|
4611
|
+
else if (!isPlainObject(value) || !this.hasIdentifiedNode(next)) {
|
|
4612
|
+
j = this.firstWithSnapshot(value, next);
|
|
4532
4613
|
}
|
|
4533
4614
|
else {
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4615
|
+
return scanForSame(this.oldNodes, this.claimed, next, value);
|
|
4616
|
+
}
|
|
4617
|
+
return j >= 0 && !this.claimed[j] && areSame(this.oldNodes[j], value)
|
|
4618
|
+
? j
|
|
4619
|
+
: -1;
|
|
4620
|
+
}
|
|
4621
|
+
// last one wins on a duplicate id, even when it is already claimed
|
|
4622
|
+
idIndex(next) {
|
|
4623
|
+
if (!this.byId) {
|
|
4624
|
+
this.byId = new Map();
|
|
4625
|
+
for (let j = next; j < this.oldNodes.length; j++) {
|
|
4626
|
+
const node = this.oldNodes[j];
|
|
4627
|
+
if (node instanceof ObjectNode && node.identifier !== null) {
|
|
4628
|
+
this.byId.set(node.identifier, j);
|
|
4545
4629
|
}
|
|
4546
4630
|
}
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4631
|
+
}
|
|
4632
|
+
return this.byId;
|
|
4633
|
+
}
|
|
4634
|
+
nodeIndex(next) {
|
|
4635
|
+
if (!this.byNode) {
|
|
4636
|
+
this.byNode = new Map();
|
|
4637
|
+
for (let j = next; j < this.oldNodes.length; j++) {
|
|
4638
|
+
this.byNode.set(this.oldNodes[j], j);
|
|
4639
|
+
}
|
|
4640
|
+
}
|
|
4641
|
+
return this.byNode;
|
|
4642
|
+
}
|
|
4643
|
+
hasIdentifiedNode(next) {
|
|
4644
|
+
if (this.anyIdentified === undefined) {
|
|
4645
|
+
this.anyIdentified = this.oldNodes
|
|
4646
|
+
.slice(next)
|
|
4647
|
+
.some(node => node instanceof ObjectNode &&
|
|
4648
|
+
node.identifier !== null &&
|
|
4649
|
+
!!node.identifierAttribute);
|
|
4650
|
+
}
|
|
4651
|
+
return this.anyIdentified;
|
|
4652
|
+
}
|
|
4653
|
+
firstWithSnapshot(value, next) {
|
|
4654
|
+
if (!this.bySnapshot) {
|
|
4655
|
+
const first = new Map();
|
|
4656
|
+
const nextSame = new Int32Array(this.oldNodes.length);
|
|
4657
|
+
for (let j = this.oldNodes.length - 1; j >= next; j--) {
|
|
4658
|
+
const node = this.oldNodes[j];
|
|
4659
|
+
if (!this.claimed[j] && node.isAlive) {
|
|
4660
|
+
const snapshot = node.snapshot;
|
|
4661
|
+
nextSame[j] = first.get(snapshot) ?? -1;
|
|
4662
|
+
first.set(snapshot, j);
|
|
4554
4663
|
}
|
|
4555
4664
|
}
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4665
|
+
this.bySnapshot = { first, nextSame };
|
|
4666
|
+
}
|
|
4667
|
+
const { first, nextSame } = this.bySnapshot;
|
|
4668
|
+
const head = first.get(value);
|
|
4669
|
+
if (head === undefined) {
|
|
4670
|
+
return -1;
|
|
4671
|
+
}
|
|
4672
|
+
let j = head;
|
|
4673
|
+
while (j >= 0 && (this.claimed[j] || !this.oldNodes[j].isAlive)) {
|
|
4674
|
+
j = nextSame[j];
|
|
4675
|
+
}
|
|
4676
|
+
if (j !== head) {
|
|
4677
|
+
first.set(value, j);
|
|
4559
4678
|
}
|
|
4679
|
+
return j;
|
|
4560
4680
|
}
|
|
4561
|
-
return nothingChanged ? null : oldNodes;
|
|
4562
4681
|
}
|
|
4563
4682
|
/**
|
|
4564
4683
|
* Convert a value to a node at given parent and subpath. Attempts to reuse old node if possible and given.
|