@jbrowse/mobx-state-tree 6.6.1 → 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 CHANGED
@@ -1,3 +1,17 @@
1
+ ## [6.7.0](https://github.com/GMOD/mobx-state-tree/compare/v6.6.1...v6.7.0) (2026-09-30)
2
+
3
+ ### Bug Fixes
4
+
5
+ - A dead node's never-read child reads as a dead instance ([a6c430e](https://github.com/GMOD/mobx-state-tree/commit/a6c430e926ec31fe226a10cafff6e2ea0add4012))
6
+ - Exempt only never-created dead nodes; ADR 0008 ([d15fb5b](https://github.com/GMOD/mobx-state-tree/commit/d15fb5b99399d7844af910db689cbb5c403ddbc3))
7
+ - A never-read node dies as if it had been read ([3ffffae](https://github.com/GMOD/mobx-state-tree/commit/3ffffae9f5f44e7c5ea1573364da2ce6224ff288))
8
+ - Fail clearly, before damaging the tree, on impossible attaches ([25e0dff](https://github.com/GMOD/mobx-state-tree/commit/25e0dffd31b02c57ec31195685e3551de7167bb0))
9
+ - Destroy children that hooks add while a node is built at death ([743f887](https://github.com/GMOD/mobx-state-tree/commit/743f887f0a749f5dff727c86429c5ffb7ce4b726))
10
+
11
+ ### Styling
12
+
13
+ - Format ADR 0008 ([4290662](https://github.com/GMOD/mobx-state-tree/commit/42906620c1c6fc58526251310468d0012f446159))
14
+
1
15
  ## [6.6.1](https://github.com/GMOD/mobx-state-tree/compare/v6.6.0...v6.6.1) (2026-09-25)
2
16
 
3
17
  ### Performance Improvements
package/dist/index.d.ts CHANGED
@@ -624,6 +624,13 @@ declare class ObjectNode<C, S, T> extends BaseNode<C, S, T> {
624
624
  clearParent(): void;
625
625
  notifyDetachOf(subject: AnyObjectNode): void;
626
626
  private notifyChildrenOfDetach;
627
+ /**
628
+ * @internal
629
+ * @hidden
630
+ * A node that died inside a tree is its own root and has no identifier
631
+ * cache, so attaching it, or attaching anything to it, cannot work.
632
+ */
633
+ assertAttachable(): IdentifierCache;
627
634
  setParent(newParent: AnyObjectNode, subpath: string): void;
628
635
  protected fireHook(name: Hook): void;
629
636
  private _snapshotUponDeath?;
@@ -468,7 +468,7 @@ function resolveIdentifier(type, target, identifier) {
468
468
  assertIsType(type, 1);
469
469
  assertIsStateTreeNode(target, 2);
470
470
  assertIsValidIdentifier(identifier, 3);
471
- const node = getStateTreeNode(target).root.identifierCache.resolve(type, normalizeIdentifier(identifier));
471
+ const node = getStateTreeNode(target).root.identifierCache?.resolve(type, normalizeIdentifier(identifier));
472
472
  return node?.value;
473
473
  }
474
474
  /**
@@ -1211,7 +1211,7 @@ class ObjectNode extends BaseNode {
1211
1211
  // addNodeToCache no-ops without an identifier attribute, and reaching the
1212
1212
  // root to ask it is a walk up the whole parent chain
1213
1213
  if (this.identifierAttribute) {
1214
- const cache = parent ? parent.root.identifierCache : this.identifierCache;
1214
+ const cache = parent ? parent.assertAttachable() : this.identifierCache;
1215
1215
  cache.addNodeToCache(this);
1216
1216
  }
1217
1217
  }
@@ -1222,8 +1222,11 @@ class ObjectNode extends BaseNode {
1222
1222
  }
1223
1223
  }
1224
1224
  createObservableInstance(fireHooks = true) {
1225
+ const diedUncreated = this.state === NodeLifeCycle.DEAD &&
1226
+ this._observableInstanceState ===
1227
+ ObservableInstanceLifecycle.UNINITIALIZED;
1225
1228
  if (devMode()) {
1226
- if (this.state !== NodeLifeCycle.INITIALIZING) {
1229
+ if (!diedUncreated && this.state !== NodeLifeCycle.INITIALIZING) {
1227
1230
  // istanbul ignore next
1228
1231
  throw fail("assertion failed: the creation of the observable instance must be done on the initializing phase");
1229
1232
  }
@@ -1276,6 +1279,9 @@ class ObjectNode extends BaseNode {
1276
1279
  // subscribes to never pays for a full-tree snapshot at creation. keepAlive
1277
1280
  // on _snapshotComputed preserves snapshot referential stability without it.
1278
1281
  this._childNodes = EMPTY_OBJECT;
1282
+ if (diedUncreated) {
1283
+ return;
1284
+ }
1279
1285
  this.state = NodeLifeCycle.CREATED;
1280
1286
  if (fireHooks) {
1281
1287
  this.fireHook(Hook.afterCreate);
@@ -1330,6 +1336,19 @@ class ObjectNode extends BaseNode {
1330
1336
  child.notifyDetachOf(subject);
1331
1337
  }
1332
1338
  }
1339
+ /**
1340
+ * @internal
1341
+ * @hidden
1342
+ * A node that died inside a tree is its own root and has no identifier
1343
+ * cache, so attaching it, or attaching anything to it, cannot work.
1344
+ */
1345
+ assertAttachable() {
1346
+ const cache = this.root.identifierCache;
1347
+ if (!cache) {
1348
+ throw fail(`'${this}' is no longer part of a state tree, so it cannot be attached to one or have nodes attached to it.`);
1349
+ }
1350
+ return cache;
1351
+ }
1333
1352
  setParent(newParent, subpath) {
1334
1353
  const parentChanged = newParent !== this.parent;
1335
1354
  const subpathChanged = subpath !== this.subpath;
@@ -1358,9 +1377,9 @@ class ObjectNode extends BaseNode {
1358
1377
  }
1359
1378
  }
1360
1379
  if (parentChanged) {
1361
- // attach to new parent
1380
+ this.assertAttachable();
1362
1381
  this.environment = undefined; // will use root's
1363
- newParent.root.identifierCache.mergeCache(this);
1382
+ newParent.assertAttachable().mergeCache(this);
1364
1383
  this.baseSetParent(newParent, subpath);
1365
1384
  this.fireHook(Hook.afterAttach);
1366
1385
  }
@@ -1556,13 +1575,33 @@ class ObjectNode extends BaseNode {
1556
1575
  addHiddenFinalProp(this.storedValue, "toJSON", toJSON);
1557
1576
  }
1558
1577
  aboutToDie() {
1578
+ const wasUninitialized = this._observableInstanceState ===
1579
+ ObservableInstanceLifecycle.UNINITIALIZED;
1580
+ const children = this.getChildren();
1581
+ if (wasUninitialized &&
1582
+ (this.hasSnapshotPostProcessor ||
1583
+ children.some(child => child instanceof ObjectNode &&
1584
+ child._observableInstanceState !==
1585
+ ObservableInstanceLifecycle.UNINITIALIZED))) {
1586
+ this.createObservableInstanceIfNeeded();
1587
+ }
1588
+ for (const node of children) {
1589
+ if (node.isAlive) {
1590
+ node.aboutToDie();
1591
+ }
1592
+ }
1559
1593
  if (this._observableInstanceState ===
1560
1594
  ObservableInstanceLifecycle.UNINITIALIZED) {
1561
1595
  return;
1562
1596
  }
1563
- this.getChildren().forEach(node => {
1564
- node.aboutToDie();
1565
- });
1597
+ if (wasUninitialized) {
1598
+ const walked = new Set(children);
1599
+ for (const node of this.getChildren()) {
1600
+ if (node.isAlive && !walked.has(node)) {
1601
+ node.aboutToDie();
1602
+ }
1603
+ }
1604
+ }
1566
1605
  // beforeDestroy should run before the disposers since else we could end up in a situation where
1567
1606
  // a disposer added with addDisposer at this stage (beforeDestroy) is actually never released
1568
1607
  this.baseAboutToDie();
@@ -1859,12 +1898,16 @@ class ComplexType extends BaseType {
1859
1898
  current.setParent(parent, subpath);
1860
1899
  return current;
1861
1900
  }
1901
+ const newNode = getStateTreeNodeSafe(newValue);
1902
+ if (newNode) {
1903
+ newNode.assertAttachable();
1904
+ if (!current.isDetaching && newNode.isWithin(current)) {
1905
+ throw fail(`Cannot assign ${newNode} to '${parent.path}/${subpath}': it is part of ${current}, which it would replace.`);
1906
+ }
1907
+ }
1862
1908
  // current node cannot be recycled in any way
1863
1909
  current.die(); // noop if detaching
1864
- // attempt to reuse the new one
1865
- if (isStateTreeNode(newValue) && this.isAssignableFrom(getType(newValue))) {
1866
- // newValue is a Node as well, move it here..
1867
- const newNode = getStateTreeNode(newValue);
1910
+ if (newNode && this.isAssignableFrom(newNode.type)) {
1868
1911
  newNode.setParent(parent, subpath);
1869
1912
  return newNode;
1870
1913
  }