jotai-state-tree 1.3.2 → 1.3.4

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.
@@ -858,6 +858,7 @@ export {
858
858
  $treenode,
859
859
  getStateTreeNode,
860
860
  hasStateTreeNode,
861
+ getSnapshotFromNode,
861
862
  applySnapshotToNode,
862
863
  resolveIdentifier,
863
864
  getRegistryStats,
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { I as ISimpleType, a as IType, b as IIdentifierType, c as IIdentifierNumberType, d as ILiteralType, e as IEnumerationType, f as IFrozenType, M as ModelProperties, g as IModelType, h as MixinConfig, i as IMixin, j as IAnyType, k as IArrayType, l as IMapType, m as IOptionalType, n as IMaybeType, o as IMaybeNullType, p as IUnionType, U as UnionOptions, q as ILateType, r as IAnyModelType, R as ReferenceOptions, s as IReferenceType, t as ISafeReferenceType, u as IRefinementType, v as IDisposer, S as SnapshotIn, w as Instance, x as IReversibleJsonPatch } from './tree-BV2K9utF.mjs';
2
- export { L as CustomTypeOptions, D as IAnyComplexType, E as IAnyMixin, G as IJsonPatch, z as IMSTArray, A as IMSTMap, y as IStateTreeNode, H as IValidationContext, K as IValidationError, J as IValidationResult, B as ModelInstance, F as ModelSelf, C as SnapshotOut, T as applyPatch, O as applySnapshot, aw as cleanupStaleEntries, ax as clearAllRegistries, aa as clone, aq as cloneDeep, a8 as destroy, a9 as detach, am as findAll, an as findFirst, as as freeze, a2 as getEnv, ag as getGlobalStore, a4 as getIdentifier, af as getMembers, ar as getOrCreatePath, Y as getParent, $ as getParentOfType, a0 as getPath, a1 as getPathParts, av as getRegistryStats, aj as getRelativePath, X as getRoot, N as getSnapshot, ap as getTreeStats, a3 as getType, _ as hasParent, al as haveSameRoot, a5 as isAlive, ak as isAncestor, at as isFrozen, a6 as isRoot, a7 as isStateTreeNode, ao as isValidReference, W as onAction, ay as onLifecycleChange, Q as onPatch, P as onSnapshot, V as recordPatches, ai as resetGlobalStore, ae as resolveIdentifier, ac as resolvePath, ah as setGlobalStore, Z as tryGetParent, ad as tryResolve, au as unfreeze, ab as walk } from './tree-BV2K9utF.mjs';
1
+ import { I as ISimpleType, a as IType, b as IIdentifierType, c as IIdentifierNumberType, d as ILiteralType, e as IEnumerationType, f as IFrozenType, M as ModelProperties, g as IModelType, h as MixinConfig, i as IMixin, j as IAnyType, k as IArrayType, l as IMapType, m as IOptionalType, n as IMaybeType, o as IMaybeNullType, p as IUnionType, U as UnionOptions, q as ILateType, r as IAnyModelType, R as ReferenceOptions, s as IReferenceType, t as ISafeReferenceType, u as IRefinementType, v as IDisposer, S as SnapshotIn, w as Instance, x as IReversibleJsonPatch } from './tree-S8buyIlj.mjs';
2
+ export { L as CustomTypeOptions, D as IAnyComplexType, E as IAnyMixin, G as IJsonPatch, z as IMSTArray, A as IMSTMap, y as IStateTreeNode, H as IValidationContext, K as IValidationError, J as IValidationResult, B as ModelInstance, F as ModelSelf, C as SnapshotOut, T as applyPatch, O as applySnapshot, aw as cleanupStaleEntries, ax as clearAllRegistries, aa as clone, aq as cloneDeep, a8 as destroy, a9 as detach, am as findAll, an as findFirst, as as freeze, a2 as getEnv, ag as getGlobalStore, a4 as getIdentifier, af as getMembers, ar as getOrCreatePath, Y as getParent, $ as getParentOfType, a0 as getPath, a1 as getPathParts, av as getRegistryStats, aj as getRelativePath, X as getRoot, N as getSnapshot, ap as getTreeStats, a3 as getType, _ as hasParent, al as haveSameRoot, a5 as isAlive, ak as isAncestor, at as isFrozen, a6 as isRoot, a7 as isStateTreeNode, ao as isValidReference, W as onAction, ay as onLifecycleChange, Q as onPatch, P as onSnapshot, V as recordPatches, ai as resetGlobalStore, ae as resolveIdentifier, ac as resolvePath, ah as setGlobalStore, Z as tryGetParent, ad as tryResolve, au as unfreeze, ab as walk } from './tree-S8buyIlj.mjs';
3
3
  import 'jotai/vanilla/internals';
4
4
  import 'jotai';
5
5
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { I as ISimpleType, a as IType, b as IIdentifierType, c as IIdentifierNumberType, d as ILiteralType, e as IEnumerationType, f as IFrozenType, M as ModelProperties, g as IModelType, h as MixinConfig, i as IMixin, j as IAnyType, k as IArrayType, l as IMapType, m as IOptionalType, n as IMaybeType, o as IMaybeNullType, p as IUnionType, U as UnionOptions, q as ILateType, r as IAnyModelType, R as ReferenceOptions, s as IReferenceType, t as ISafeReferenceType, u as IRefinementType, v as IDisposer, S as SnapshotIn, w as Instance, x as IReversibleJsonPatch } from './tree-BV2K9utF.js';
2
- export { L as CustomTypeOptions, D as IAnyComplexType, E as IAnyMixin, G as IJsonPatch, z as IMSTArray, A as IMSTMap, y as IStateTreeNode, H as IValidationContext, K as IValidationError, J as IValidationResult, B as ModelInstance, F as ModelSelf, C as SnapshotOut, T as applyPatch, O as applySnapshot, aw as cleanupStaleEntries, ax as clearAllRegistries, aa as clone, aq as cloneDeep, a8 as destroy, a9 as detach, am as findAll, an as findFirst, as as freeze, a2 as getEnv, ag as getGlobalStore, a4 as getIdentifier, af as getMembers, ar as getOrCreatePath, Y as getParent, $ as getParentOfType, a0 as getPath, a1 as getPathParts, av as getRegistryStats, aj as getRelativePath, X as getRoot, N as getSnapshot, ap as getTreeStats, a3 as getType, _ as hasParent, al as haveSameRoot, a5 as isAlive, ak as isAncestor, at as isFrozen, a6 as isRoot, a7 as isStateTreeNode, ao as isValidReference, W as onAction, ay as onLifecycleChange, Q as onPatch, P as onSnapshot, V as recordPatches, ai as resetGlobalStore, ae as resolveIdentifier, ac as resolvePath, ah as setGlobalStore, Z as tryGetParent, ad as tryResolve, au as unfreeze, ab as walk } from './tree-BV2K9utF.js';
1
+ import { I as ISimpleType, a as IType, b as IIdentifierType, c as IIdentifierNumberType, d as ILiteralType, e as IEnumerationType, f as IFrozenType, M as ModelProperties, g as IModelType, h as MixinConfig, i as IMixin, j as IAnyType, k as IArrayType, l as IMapType, m as IOptionalType, n as IMaybeType, o as IMaybeNullType, p as IUnionType, U as UnionOptions, q as ILateType, r as IAnyModelType, R as ReferenceOptions, s as IReferenceType, t as ISafeReferenceType, u as IRefinementType, v as IDisposer, S as SnapshotIn, w as Instance, x as IReversibleJsonPatch } from './tree-S8buyIlj.js';
2
+ export { L as CustomTypeOptions, D as IAnyComplexType, E as IAnyMixin, G as IJsonPatch, z as IMSTArray, A as IMSTMap, y as IStateTreeNode, H as IValidationContext, K as IValidationError, J as IValidationResult, B as ModelInstance, F as ModelSelf, C as SnapshotOut, T as applyPatch, O as applySnapshot, aw as cleanupStaleEntries, ax as clearAllRegistries, aa as clone, aq as cloneDeep, a8 as destroy, a9 as detach, am as findAll, an as findFirst, as as freeze, a2 as getEnv, ag as getGlobalStore, a4 as getIdentifier, af as getMembers, ar as getOrCreatePath, Y as getParent, $ as getParentOfType, a0 as getPath, a1 as getPathParts, av as getRegistryStats, aj as getRelativePath, X as getRoot, N as getSnapshot, ap as getTreeStats, a3 as getType, _ as hasParent, al as haveSameRoot, a5 as isAlive, ak as isAncestor, at as isFrozen, a6 as isRoot, a7 as isStateTreeNode, ao as isValidReference, W as onAction, ay as onLifecycleChange, Q as onPatch, P as onSnapshot, V as recordPatches, ai as resetGlobalStore, ae as resolveIdentifier, ac as resolvePath, ah as setGlobalStore, Z as tryGetParent, ad as tryResolve, au as unfreeze, ab as walk } from './tree-S8buyIlj.js';
3
3
  import 'jotai/vanilla/internals';
4
4
  import 'jotai';
5
5
 
package/dist/index.js CHANGED
@@ -2418,6 +2418,8 @@ var MSTArray = class _MSTArray extends Array {
2418
2418
  return [...this];
2419
2419
  }
2420
2420
  syncToNode() {
2421
+ const oldArray = this.node.getValue() || [];
2422
+ const newArray = [...this];
2421
2423
  const existingChildNodes = /* @__PURE__ */ new Set();
2422
2424
  for (const [, child] of this.node.getChildren()) {
2423
2425
  existingChildNodes.add(child);
@@ -2469,7 +2471,63 @@ var MSTArray = class _MSTArray extends Array {
2469
2471
  for (const [key, childNode] of newChildren) {
2470
2472
  this.node.addChild(key, childNode);
2471
2473
  }
2472
- this.node.setValue([...this]);
2474
+ const patches = [];
2475
+ const reversePatches = [];
2476
+ if (newArray.length > oldArray.length && oldArray.every((val, idx) => val === newArray[idx])) {
2477
+ for (let i = oldArray.length; i < newArray.length; i++) {
2478
+ const childNode = this.node.getChild(String(i));
2479
+ const valSnap = childNode ? getSnapshotFromNode(childNode) : newArray[i];
2480
+ patches.push({
2481
+ op: "add",
2482
+ path: `${this.node.$path}/${i}`,
2483
+ value: valSnap
2484
+ });
2485
+ reversePatches.push({
2486
+ op: "remove",
2487
+ path: `${this.node.$path}/${i}`
2488
+ });
2489
+ }
2490
+ } else if (newArray.length < oldArray.length && newArray.every((val, idx) => val === oldArray[idx])) {
2491
+ for (let i = oldArray.length - 1; i >= newArray.length; i--) {
2492
+ const childNode = this.node.getChild(String(i));
2493
+ const oldValSnap = childNode ? getSnapshotFromNode(childNode) : oldArray[i];
2494
+ patches.push({
2495
+ op: "remove",
2496
+ path: `${this.node.$path}/${i}`
2497
+ });
2498
+ reversePatches.push({
2499
+ op: "add",
2500
+ path: `${this.node.$path}/${i}`,
2501
+ value: oldValSnap
2502
+ });
2503
+ }
2504
+ } else {
2505
+ const oldSnap = oldArray.map((_, idx) => {
2506
+ const childNode = this.node.getChild(String(idx));
2507
+ return childNode ? getSnapshotFromNode(childNode) : oldArray[idx];
2508
+ });
2509
+ const newSnap = newArray.map((_, idx) => {
2510
+ const childNode = this.node.getChild(String(idx));
2511
+ return childNode ? getSnapshotFromNode(childNode) : newArray[idx];
2512
+ });
2513
+ patches.push({
2514
+ op: "replace",
2515
+ path: this.node.$path,
2516
+ value: newSnap
2517
+ });
2518
+ reversePatches.push({
2519
+ op: "replace",
2520
+ path: this.node.$path,
2521
+ value: oldSnap
2522
+ });
2523
+ }
2524
+ const store = getGlobalStore();
2525
+ store.set(this.node.valueAtom, newArray);
2526
+ this.node.invalidateSnapshot();
2527
+ patches.forEach((patch, idx) => {
2528
+ this.node.notifyPatch(patch, reversePatches[idx]);
2529
+ });
2530
+ this.node.notifyVolatileChange();
2473
2531
  }
2474
2532
  };
2475
2533
  var ArrayType = class {
package/dist/index.mjs CHANGED
@@ -27,6 +27,7 @@ import {
27
27
  getRelativePath,
28
28
  getRoot,
29
29
  getSnapshot,
30
+ getSnapshotFromNode,
30
31
  getStateTreeNode,
31
32
  getTreeStats,
32
33
  getType,
@@ -57,7 +58,7 @@ import {
57
58
  tryResolve,
58
59
  unfreeze,
59
60
  walk
60
- } from "./chunk-OAXK4KFM.mjs";
61
+ } from "./chunk-NFJRKXCI.mjs";
61
62
 
62
63
  // src/primitives.ts
63
64
  function createSimpleType(name, validator, defaultValue) {
@@ -1479,6 +1480,8 @@ var MSTArray = class _MSTArray extends Array {
1479
1480
  return [...this];
1480
1481
  }
1481
1482
  syncToNode() {
1483
+ const oldArray = this.node.getValue() || [];
1484
+ const newArray = [...this];
1482
1485
  const existingChildNodes = /* @__PURE__ */ new Set();
1483
1486
  for (const [, child] of this.node.getChildren()) {
1484
1487
  existingChildNodes.add(child);
@@ -1530,7 +1533,63 @@ var MSTArray = class _MSTArray extends Array {
1530
1533
  for (const [key, childNode] of newChildren) {
1531
1534
  this.node.addChild(key, childNode);
1532
1535
  }
1533
- this.node.setValue([...this]);
1536
+ const patches = [];
1537
+ const reversePatches = [];
1538
+ if (newArray.length > oldArray.length && oldArray.every((val, idx) => val === newArray[idx])) {
1539
+ for (let i = oldArray.length; i < newArray.length; i++) {
1540
+ const childNode = this.node.getChild(String(i));
1541
+ const valSnap = childNode ? getSnapshotFromNode(childNode) : newArray[i];
1542
+ patches.push({
1543
+ op: "add",
1544
+ path: `${this.node.$path}/${i}`,
1545
+ value: valSnap
1546
+ });
1547
+ reversePatches.push({
1548
+ op: "remove",
1549
+ path: `${this.node.$path}/${i}`
1550
+ });
1551
+ }
1552
+ } else if (newArray.length < oldArray.length && newArray.every((val, idx) => val === oldArray[idx])) {
1553
+ for (let i = oldArray.length - 1; i >= newArray.length; i--) {
1554
+ const childNode = this.node.getChild(String(i));
1555
+ const oldValSnap = childNode ? getSnapshotFromNode(childNode) : oldArray[i];
1556
+ patches.push({
1557
+ op: "remove",
1558
+ path: `${this.node.$path}/${i}`
1559
+ });
1560
+ reversePatches.push({
1561
+ op: "add",
1562
+ path: `${this.node.$path}/${i}`,
1563
+ value: oldValSnap
1564
+ });
1565
+ }
1566
+ } else {
1567
+ const oldSnap = oldArray.map((_, idx) => {
1568
+ const childNode = this.node.getChild(String(idx));
1569
+ return childNode ? getSnapshotFromNode(childNode) : oldArray[idx];
1570
+ });
1571
+ const newSnap = newArray.map((_, idx) => {
1572
+ const childNode = this.node.getChild(String(idx));
1573
+ return childNode ? getSnapshotFromNode(childNode) : newArray[idx];
1574
+ });
1575
+ patches.push({
1576
+ op: "replace",
1577
+ path: this.node.$path,
1578
+ value: newSnap
1579
+ });
1580
+ reversePatches.push({
1581
+ op: "replace",
1582
+ path: this.node.$path,
1583
+ value: oldSnap
1584
+ });
1585
+ }
1586
+ const store = getGlobalStore();
1587
+ store.set(this.node.valueAtom, newArray);
1588
+ this.node.invalidateSnapshot();
1589
+ patches.forEach((patch, idx) => {
1590
+ this.node.notifyPatch(patch, reversePatches[idx]);
1591
+ });
1592
+ this.node.notifyVolatileChange();
1534
1593
  }
1535
1594
  };
1536
1595
  var ArrayType = class {
package/dist/react.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import React, { ComponentType, FC, ReactNode } from 'react';
2
- import { ag as getGlobalStore } from './tree-BV2K9utF.mjs';
3
- export { az as hasStateTreeNode } from './tree-BV2K9utF.mjs';
2
+ import { ag as getGlobalStore } from './tree-S8buyIlj.mjs';
3
+ export { az as hasStateTreeNode } from './tree-S8buyIlj.mjs';
4
4
  import 'jotai/vanilla/internals';
5
5
  import 'jotai';
6
6
 
package/dist/react.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import React, { ComponentType, FC, ReactNode } from 'react';
2
- import { ag as getGlobalStore } from './tree-BV2K9utF.js';
3
- export { az as hasStateTreeNode } from './tree-BV2K9utF.js';
2
+ import { ag as getGlobalStore } from './tree-S8buyIlj.js';
3
+ export { az as hasStateTreeNode } from './tree-S8buyIlj.js';
4
4
  import 'jotai/vanilla/internals';
5
5
  import 'jotai';
6
6
 
package/dist/react.mjs CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  onPatch,
10
10
  onSnapshot,
11
11
  setActiveTrackingFn
12
- } from "./chunk-OAXK4KFM.mjs";
12
+ } from "./chunk-NFJRKXCI.mjs";
13
13
 
14
14
  // src/react.ts
15
15
  import React, {
@@ -375,7 +375,7 @@ declare class StateTreeNode implements IStateTreeNode {
375
375
  /** Subscribe to patches */
376
376
  onPatch(listener: (patch: IJsonPatch, reversePatch: IReversibleJsonPatch) => void): IDisposer;
377
377
  /** Notify patch listeners */
378
- private notifyPatch;
378
+ notifyPatch(patch: IJsonPatch, reversePatch: IReversibleJsonPatch): void;
379
379
  /** Notify snapshot listeners */
380
380
  private notifySnapshotChange;
381
381
  /** Notify about a property change (for use by model proxy) */
@@ -375,7 +375,7 @@ declare class StateTreeNode implements IStateTreeNode {
375
375
  /** Subscribe to patches */
376
376
  onPatch(listener: (patch: IJsonPatch, reversePatch: IReversibleJsonPatch) => void): IDisposer;
377
377
  /** Notify patch listeners */
378
- private notifyPatch;
378
+ notifyPatch(patch: IJsonPatch, reversePatch: IReversibleJsonPatch): void;
379
379
  /** Notify snapshot listeners */
380
380
  private notifySnapshotChange;
381
381
  /** Notify about a property change (for use by model proxy) */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jotai-state-tree",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "description": "MobX-State-Tree API compatible library powered by Jotai",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -687,6 +687,51 @@ describe("Patches", () => {
687
687
  disposer();
688
688
  });
689
689
 
690
+ it("should listen to complex array patches", () => {
691
+ const Todo = types.model("Todo", {
692
+ id: types.identifier,
693
+ title: types.string,
694
+ done: types.optional(types.boolean, false),
695
+ });
696
+
697
+ const TodoStore = types
698
+ .model("TodoStore", {
699
+ todos: types.optional(types.array(Todo), []),
700
+ })
701
+ .actions((self) => ({
702
+ addTodo(title: string) {
703
+ self.todos.push({
704
+ id: "1",
705
+ title,
706
+ done: false,
707
+ });
708
+ },
709
+ }));
710
+
711
+ const store = TodoStore.create({
712
+ todos: [],
713
+ });
714
+
715
+ const patches: any[] = [];
716
+ onPatch(store, (patch) => {
717
+ patches.push(patch);
718
+ });
719
+
720
+ store.addTodo("Learn jotai-state-tree");
721
+
722
+ expect(patches).toEqual([
723
+ {
724
+ op: "add",
725
+ path: "/todos/0",
726
+ value: {
727
+ id: "1",
728
+ title: "Learn jotai-state-tree",
729
+ done: false,
730
+ },
731
+ },
732
+ ]);
733
+ });
734
+
690
735
  it("should apply patches", () => {
691
736
  const Model = types.model("Model", {
692
737
  value: types.number,
package/src/array.ts CHANGED
@@ -10,12 +10,15 @@ import type {
10
10
  IValidationContext,
11
11
  IValidationResult,
12
12
  IAnyType,
13
+ IJsonPatch,
14
+ IReversibleJsonPatch,
13
15
  } from "./types";
14
16
  import {
15
17
  StateTreeNode,
16
18
  $treenode,
17
19
  getStateTreeNode,
18
20
  getGlobalStore,
21
+ getSnapshotFromNode,
19
22
  } from "./tree";
20
23
  import { canWrite } from "./lifecycle";
21
24
 
@@ -235,6 +238,9 @@ class MSTArray<T> extends Array<T> implements IMSTArray<T> {
235
238
  }
236
239
 
237
240
  private syncToNode(): void {
241
+ const oldArray = (this.node.getValue() as unknown[]) || [];
242
+ const newArray = [...this];
243
+
238
244
  // Collect existing child nodes for cleanup comparison
239
245
  const existingChildNodes = new Set<StateTreeNode>();
240
246
  for (const [, child] of this.node.getChildren()) {
@@ -303,8 +309,77 @@ class MSTArray<T> extends Array<T> implements IMSTArray<T> {
303
309
  this.node.addChild(key, childNode);
304
310
  }
305
311
 
306
- // Update the node's value
307
- this.node.setValue([...this]);
312
+ // Determine diff and generate granular patches
313
+ const patches: IJsonPatch[] = [];
314
+ const reversePatches: IReversibleJsonPatch[] = [];
315
+
316
+ // Case 1: Simple push (items added at the end)
317
+ if (newArray.length > oldArray.length && oldArray.every((val, idx) => val === newArray[idx])) {
318
+ for (let i = oldArray.length; i < newArray.length; i++) {
319
+ const childNode = this.node.getChild(String(i));
320
+ const valSnap = childNode ? getSnapshotFromNode(childNode) : newArray[i];
321
+ patches.push({
322
+ op: "add",
323
+ path: `${this.node.$path}/${i}`,
324
+ value: valSnap,
325
+ });
326
+ reversePatches.push({
327
+ op: "remove",
328
+ path: `${this.node.$path}/${i}`,
329
+ });
330
+ }
331
+ }
332
+ // Case 2: Simple pop (items removed from the end)
333
+ else if (newArray.length < oldArray.length && newArray.every((val, idx) => val === oldArray[idx])) {
334
+ for (let i = oldArray.length - 1; i >= newArray.length; i--) {
335
+ const childNode = this.node.getChild(String(i));
336
+ const oldValSnap = childNode ? getSnapshotFromNode(childNode) : oldArray[i];
337
+ patches.push({
338
+ op: "remove",
339
+ path: `${this.node.$path}/${i}`,
340
+ });
341
+ reversePatches.push({
342
+ op: "add",
343
+ path: `${this.node.$path}/${i}`,
344
+ value: oldValSnap,
345
+ });
346
+ }
347
+ }
348
+ // Case 3: Other mutations (fallback to replace array)
349
+ else {
350
+ const oldSnap = oldArray.map((_, idx) => {
351
+ const childNode = this.node.getChild(String(idx));
352
+ return childNode ? getSnapshotFromNode(childNode) : oldArray[idx];
353
+ });
354
+ const newSnap = newArray.map((_, idx) => {
355
+ const childNode = this.node.getChild(String(idx));
356
+ return childNode ? getSnapshotFromNode(childNode) : newArray[idx];
357
+ });
358
+
359
+ patches.push({
360
+ op: "replace",
361
+ path: this.node.$path,
362
+ value: newSnap,
363
+ });
364
+ reversePatches.push({
365
+ op: "replace",
366
+ path: this.node.$path,
367
+ value: oldSnap,
368
+ });
369
+ }
370
+
371
+ // Update the node's value silently
372
+ const store = getGlobalStore();
373
+ store.set(this.node.valueAtom, newArray);
374
+ this.node.invalidateSnapshot();
375
+
376
+ // Notify patch listeners
377
+ patches.forEach((patch, idx) => {
378
+ this.node.notifyPatch(patch, reversePatches[idx]);
379
+ });
380
+
381
+ // Notify snapshot changes
382
+ this.node.notifyVolatileChange();
308
383
  }
309
384
  }
310
385
 
package/src/tree.ts CHANGED
@@ -385,7 +385,7 @@ export class StateTreeNode implements IStateTreeNode {
385
385
  }
386
386
 
387
387
  /** Notify patch listeners */
388
- private notifyPatch(patch: IJsonPatch, reversePatch: IReversibleJsonPatch) {
388
+ notifyPatch(patch: IJsonPatch, reversePatch: IReversibleJsonPatch) {
389
389
  this.patchListeners.forEach((listener) => listener(patch, reversePatch));
390
390
  // Bubble up to parent
391
391
  if (this.$parent) {