jotai-state-tree 1.4.1 → 1.4.2

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.
@@ -42,9 +42,12 @@ var identifierFinalizationRegistry = new FinalizationRegistry(
42
42
  (info) => {
43
43
  const typeMap = identifierRegistry.get(info.typeName);
44
44
  if (typeMap) {
45
- typeMap.delete(info.identifier);
46
- if (typeMap.size === 0) {
47
- identifierRegistry.delete(info.typeName);
45
+ const ref = typeMap.get(info.identifier);
46
+ if (!ref || ref.deref() === void 0) {
47
+ typeMap.delete(info.identifier);
48
+ if (typeMap.size === 0) {
49
+ identifierRegistry.delete(info.typeName);
50
+ }
48
51
  }
49
52
  }
50
53
  }
@@ -71,6 +74,35 @@ function notifyLifecycleChange(node, isAlive2) {
71
74
  listeners.forEach((listener) => listener(isAlive2));
72
75
  }
73
76
  }
77
+ function cloneAndSerialize(value) {
78
+ if (value === null || value === void 0) {
79
+ return value;
80
+ }
81
+ if (hasStateTreeNode(value)) {
82
+ return getSnapshotFromNode(getStateTreeNode(value));
83
+ }
84
+ if (Array.isArray(value)) {
85
+ return value.map(cloneAndSerialize);
86
+ }
87
+ if (typeof value === "object") {
88
+ if (value instanceof Map) {
89
+ const res = {};
90
+ for (const [k, v] of value.entries()) {
91
+ res[k] = cloneAndSerialize(v);
92
+ }
93
+ return res;
94
+ }
95
+ const proto = Object.getPrototypeOf(value);
96
+ if (proto === null || proto === Object.prototype) {
97
+ const res = {};
98
+ for (const [k, v] of Object.entries(value)) {
99
+ res[k] = cloneAndSerialize(v);
100
+ }
101
+ return res;
102
+ }
103
+ }
104
+ return value;
105
+ }
74
106
  var StateTreeNode = class {
75
107
  constructor(type, initialValue, env, parent, pathSegment) {
76
108
  this.$parent = null;
@@ -88,6 +120,8 @@ var StateTreeNode = class {
88
120
  /** Cached snapshot for structural sharing optimization */
89
121
  this.cachedSnapshot = void 0;
90
122
  this.isSnapshotDirty = true;
123
+ /** Strong reference to the instance proxy to prevent GC of active nodes */
124
+ this.instance = null;
91
125
  this.$id = generateNodeId();
92
126
  this.$type = type;
93
127
  this.$env = env ?? parent?.$env;
@@ -108,6 +142,7 @@ var StateTreeNode = class {
108
142
  }
109
143
  /** Set the instance reference */
110
144
  setInstance(instance) {
145
+ this.instance = instance;
111
146
  const entry = nodeRegistry.get(this.$id);
112
147
  if (entry && instance && typeof instance === "object") {
113
148
  entry.instance = new WeakRef(instance);
@@ -115,8 +150,7 @@ var StateTreeNode = class {
115
150
  }
116
151
  /** Get the instance */
117
152
  getInstance() {
118
- const entry = nodeRegistry.get(this.$id);
119
- return entry?.instance?.deref() ?? null;
153
+ return this.instance;
120
154
  }
121
155
  /** Get current value from atom */
122
156
  getValue() {
@@ -222,9 +256,27 @@ var StateTreeNode = class {
222
256
  }
223
257
  /** Notify patch listeners */
224
258
  notifyPatch(patch, reversePatch) {
225
- this.patchListeners.forEach((listener) => listener(patch, reversePatch));
259
+ const serializedPatch = {
260
+ ...patch,
261
+ value: "value" in patch ? cloneAndSerialize(patch.value) : void 0
262
+ };
263
+ if (!("value" in patch)) {
264
+ delete serializedPatch.value;
265
+ }
266
+ const serializedReversePatch = {
267
+ ...reversePatch,
268
+ value: "value" in reversePatch ? cloneAndSerialize(reversePatch.value) : void 0,
269
+ oldValue: "oldValue" in reversePatch ? cloneAndSerialize(reversePatch.oldValue) : void 0
270
+ };
271
+ if (!("value" in reversePatch)) {
272
+ delete serializedReversePatch.value;
273
+ }
274
+ if (!("oldValue" in reversePatch)) {
275
+ delete serializedReversePatch.oldValue;
276
+ }
277
+ this.patchListeners.forEach((listener) => listener(serializedPatch, serializedReversePatch));
226
278
  if (this.$parent) {
227
- this.$parent.notifyPatch(patch, reversePatch);
279
+ this.$parent.notifyPatch(serializedPatch, serializedReversePatch);
228
280
  }
229
281
  }
230
282
  /** Notify snapshot listeners */
@@ -261,6 +313,7 @@ var StateTreeNode = class {
261
313
  /** Destroy this node and all children */
262
314
  destroy() {
263
315
  if (!this.$isAlive) return;
316
+ this.instance = null;
264
317
  lifecycleHookHandlers.runBeforeDestroy?.(this);
265
318
  this.children.forEach((child) => child.destroy());
266
319
  this.children.clear();
@@ -900,6 +953,8 @@ function unfreeze(target) {
900
953
  }
901
954
 
902
955
  // src/undo.ts
956
+ var undoManagersRegistry = /* @__PURE__ */ new WeakMap();
957
+ var timeTravelManagersRegistry = /* @__PURE__ */ new WeakMap();
903
958
  var UndoManager = class {
904
959
  constructor(target, options = {}) {
905
960
  this.historyEntries = [];
@@ -920,6 +975,7 @@ var UndoManager = class {
920
975
  groupByTime: options.groupByTime ?? false,
921
976
  groupingWindow: options.groupingWindow ?? 200
922
977
  };
978
+ undoManagersRegistry.set(target, this);
923
979
  this.disposer = onPatch(target, (patch, reversePatch) => {
924
980
  this.recordPatch(patch, reversePatch);
925
981
  });
@@ -1013,6 +1069,13 @@ var UndoManager = class {
1013
1069
  applyPatch(this.target, entry.patches[i]);
1014
1070
  }
1015
1071
  this.currentIndex--;
1072
+ const tt = timeTravelManagersRegistry.get(this.target);
1073
+ if (tt) {
1074
+ const N = tt.snapshots.length;
1075
+ const M = this.historyEntries.length;
1076
+ const ttIndex = this.currentIndex + 1 + (N - 1 - M);
1077
+ tt.index = Math.max(0, Math.min(N - 1, ttIndex));
1078
+ }
1016
1079
  } finally {
1017
1080
  this.isUndoing = false;
1018
1081
  rootNode.$isApplyingHistory = wasApplying;
@@ -1033,6 +1096,13 @@ var UndoManager = class {
1033
1096
  for (const patch of entry.inversePatches) {
1034
1097
  applyPatch(this.target, patch);
1035
1098
  }
1099
+ const tt = timeTravelManagersRegistry.get(this.target);
1100
+ if (tt) {
1101
+ const N = tt.snapshots.length;
1102
+ const M = this.historyEntries.length;
1103
+ const ttIndex = this.currentIndex + 1 + (N - 1 - M);
1104
+ tt.index = Math.max(0, Math.min(N - 1, ttIndex));
1105
+ }
1036
1106
  } finally {
1037
1107
  this.isRedoing = false;
1038
1108
  rootNode.$isApplyingHistory = wasApplying;
@@ -1086,6 +1156,7 @@ var UndoManager = class {
1086
1156
  }
1087
1157
  }
1088
1158
  dispose() {
1159
+ undoManagersRegistry.delete(this.target);
1089
1160
  if (this.disposer) {
1090
1161
  this.disposer();
1091
1162
  this.disposer = null;
@@ -1112,6 +1183,7 @@ var TimeTravelManager = class {
1112
1183
  this.target = target;
1113
1184
  this.maxSnapshots = options.maxSnapshots ?? 50;
1114
1185
  this.autoRecord = options.autoRecord ?? false;
1186
+ timeTravelManagersRegistry.set(target, this);
1115
1187
  this.record();
1116
1188
  if (this.autoRecord) {
1117
1189
  this.disposer = onPatch(target, () => {
@@ -1193,6 +1265,13 @@ var TimeTravelManager = class {
1193
1265
  try {
1194
1266
  this.index = index;
1195
1267
  applySnapshot(this.target, this.snapshots[index]);
1268
+ const undo = undoManagersRegistry.get(this.target);
1269
+ if (undo) {
1270
+ const N = this.snapshots.length;
1271
+ const M = undo.historyEntries.length;
1272
+ const undoIndex = this.index - 1 - (N - 1 - M);
1273
+ undo.currentIndex = Math.max(-1, Math.min(M - 1, undoIndex));
1274
+ }
1196
1275
  } finally {
1197
1276
  this.isApplying = false;
1198
1277
  rootNode.$isApplyingHistory = wasApplying;
@@ -1210,6 +1289,7 @@ var TimeTravelManager = class {
1210
1289
  this.record();
1211
1290
  }
1212
1291
  dispose() {
1292
+ timeTravelManagersRegistry.delete(this.target);
1213
1293
  if (this.disposer) {
1214
1294
  this.disposer();
1215
1295
  this.disposer = null;
@@ -1302,6 +1382,7 @@ function createActionRecorder(target) {
1302
1382
  export {
1303
1383
  setLifecycleHookHandlers,
1304
1384
  getIsApplyingSnapshotOrPatch,
1385
+ setIsApplyingSnapshotOrPatch,
1305
1386
  getGlobalStore,
1306
1387
  setGlobalStore,
1307
1388
  resetGlobalStore,
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 } from './undo-BcBI_BQM.mjs';
2
- export { L as CustomTypeOptions, aG as IActionRecorder, aH as IActionRecording, C as IAnyComplexType, D as IAnyMixin, aE as IHistoryEntry, F as IJsonPatch, y as IMSTArray, z as IMSTMap, G as IReversibleJsonPatch, x as IStateTreeNode, aF as ITimeTravelManager, aC as IUndoManager, aD as IUndoManagerOptions, H as IValidationContext, K as IValidationError, J as IValidationResult, A as ModelInstance, E as ModelSelf, B as SnapshotOut, T as applyPatch, O as applySnapshot, aw as cleanupStaleEntries, ax as clearAllRegistries, aa as clone, aq as cloneDeep, aB as createActionRecorder, aA as createTimeTravelManager, az as createUndoManager, 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 './undo-BcBI_BQM.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 } from './undo-DL1pyOkT.mjs';
2
+ export { L as CustomTypeOptions, aG as IActionRecorder, aH as IActionRecording, C as IAnyComplexType, D as IAnyMixin, aE as IHistoryEntry, F as IJsonPatch, y as IMSTArray, z as IMSTMap, G as IReversibleJsonPatch, x as IStateTreeNode, aF as ITimeTravelManager, aC as IUndoManager, aD as IUndoManagerOptions, H as IValidationContext, K as IValidationError, J as IValidationResult, A as ModelInstance, E as ModelSelf, B as SnapshotOut, T as applyPatch, O as applySnapshot, aw as cleanupStaleEntries, ax as clearAllRegistries, aa as clone, aq as cloneDeep, aB as createActionRecorder, aA as createTimeTravelManager, az as createUndoManager, 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 './undo-DL1pyOkT.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 } from './undo-BcBI_BQM.js';
2
- export { L as CustomTypeOptions, aG as IActionRecorder, aH as IActionRecording, C as IAnyComplexType, D as IAnyMixin, aE as IHistoryEntry, F as IJsonPatch, y as IMSTArray, z as IMSTMap, G as IReversibleJsonPatch, x as IStateTreeNode, aF as ITimeTravelManager, aC as IUndoManager, aD as IUndoManagerOptions, H as IValidationContext, K as IValidationError, J as IValidationResult, A as ModelInstance, E as ModelSelf, B as SnapshotOut, T as applyPatch, O as applySnapshot, aw as cleanupStaleEntries, ax as clearAllRegistries, aa as clone, aq as cloneDeep, aB as createActionRecorder, aA as createTimeTravelManager, az as createUndoManager, 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 './undo-BcBI_BQM.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 } from './undo-DL1pyOkT.js';
2
+ export { L as CustomTypeOptions, aG as IActionRecorder, aH as IActionRecording, C as IAnyComplexType, D as IAnyMixin, aE as IHistoryEntry, F as IJsonPatch, y as IMSTArray, z as IMSTMap, G as IReversibleJsonPatch, x as IStateTreeNode, aF as ITimeTravelManager, aC as IUndoManager, aD as IUndoManagerOptions, H as IValidationContext, K as IValidationError, J as IValidationResult, A as ModelInstance, E as ModelSelf, B as SnapshotOut, T as applyPatch, O as applySnapshot, aw as cleanupStaleEntries, ax as clearAllRegistries, aa as clone, aq as cloneDeep, aB as createActionRecorder, aA as createTimeTravelManager, az as createUndoManager, 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 './undo-DL1pyOkT.js';
3
3
  import 'jotai/vanilla/internals';
4
4
  import 'jotai';
5
5
 
package/dist/index.js CHANGED
@@ -473,9 +473,12 @@ var identifierFinalizationRegistry = new FinalizationRegistry(
473
473
  (info) => {
474
474
  const typeMap = identifierRegistry.get(info.typeName);
475
475
  if (typeMap) {
476
- typeMap.delete(info.identifier);
477
- if (typeMap.size === 0) {
478
- identifierRegistry.delete(info.typeName);
476
+ const ref = typeMap.get(info.identifier);
477
+ if (!ref || ref.deref() === void 0) {
478
+ typeMap.delete(info.identifier);
479
+ if (typeMap.size === 0) {
480
+ identifierRegistry.delete(info.typeName);
481
+ }
479
482
  }
480
483
  }
481
484
  }
@@ -502,6 +505,35 @@ function notifyLifecycleChange(node, isAlive2) {
502
505
  listeners.forEach((listener) => listener(isAlive2));
503
506
  }
504
507
  }
508
+ function cloneAndSerialize(value) {
509
+ if (value === null || value === void 0) {
510
+ return value;
511
+ }
512
+ if (hasStateTreeNode(value)) {
513
+ return getSnapshotFromNode(getStateTreeNode(value));
514
+ }
515
+ if (Array.isArray(value)) {
516
+ return value.map(cloneAndSerialize);
517
+ }
518
+ if (typeof value === "object") {
519
+ if (value instanceof Map) {
520
+ const res = {};
521
+ for (const [k, v] of value.entries()) {
522
+ res[k] = cloneAndSerialize(v);
523
+ }
524
+ return res;
525
+ }
526
+ const proto = Object.getPrototypeOf(value);
527
+ if (proto === null || proto === Object.prototype) {
528
+ const res = {};
529
+ for (const [k, v] of Object.entries(value)) {
530
+ res[k] = cloneAndSerialize(v);
531
+ }
532
+ return res;
533
+ }
534
+ }
535
+ return value;
536
+ }
505
537
  var StateTreeNode = class {
506
538
  constructor(type, initialValue, env, parent, pathSegment) {
507
539
  this.$parent = null;
@@ -519,6 +551,8 @@ var StateTreeNode = class {
519
551
  /** Cached snapshot for structural sharing optimization */
520
552
  this.cachedSnapshot = void 0;
521
553
  this.isSnapshotDirty = true;
554
+ /** Strong reference to the instance proxy to prevent GC of active nodes */
555
+ this.instance = null;
522
556
  this.$id = generateNodeId();
523
557
  this.$type = type;
524
558
  this.$env = env ?? parent?.$env;
@@ -539,6 +573,7 @@ var StateTreeNode = class {
539
573
  }
540
574
  /** Set the instance reference */
541
575
  setInstance(instance) {
576
+ this.instance = instance;
542
577
  const entry = nodeRegistry.get(this.$id);
543
578
  if (entry && instance && typeof instance === "object") {
544
579
  entry.instance = new WeakRef(instance);
@@ -546,8 +581,7 @@ var StateTreeNode = class {
546
581
  }
547
582
  /** Get the instance */
548
583
  getInstance() {
549
- const entry = nodeRegistry.get(this.$id);
550
- return entry?.instance?.deref() ?? null;
584
+ return this.instance;
551
585
  }
552
586
  /** Get current value from atom */
553
587
  getValue() {
@@ -653,9 +687,27 @@ var StateTreeNode = class {
653
687
  }
654
688
  /** Notify patch listeners */
655
689
  notifyPatch(patch, reversePatch) {
656
- this.patchListeners.forEach((listener) => listener(patch, reversePatch));
690
+ const serializedPatch = {
691
+ ...patch,
692
+ value: "value" in patch ? cloneAndSerialize(patch.value) : void 0
693
+ };
694
+ if (!("value" in patch)) {
695
+ delete serializedPatch.value;
696
+ }
697
+ const serializedReversePatch = {
698
+ ...reversePatch,
699
+ value: "value" in reversePatch ? cloneAndSerialize(reversePatch.value) : void 0,
700
+ oldValue: "oldValue" in reversePatch ? cloneAndSerialize(reversePatch.oldValue) : void 0
701
+ };
702
+ if (!("value" in reversePatch)) {
703
+ delete serializedReversePatch.value;
704
+ }
705
+ if (!("oldValue" in reversePatch)) {
706
+ delete serializedReversePatch.oldValue;
707
+ }
708
+ this.patchListeners.forEach((listener) => listener(serializedPatch, serializedReversePatch));
657
709
  if (this.$parent) {
658
- this.$parent.notifyPatch(patch, reversePatch);
710
+ this.$parent.notifyPatch(serializedPatch, serializedReversePatch);
659
711
  }
660
712
  }
661
713
  /** Notify snapshot listeners */
@@ -692,6 +744,7 @@ var StateTreeNode = class {
692
744
  /** Destroy this node and all children */
693
745
  destroy() {
694
746
  if (!this.$isAlive) return;
747
+ this.instance = null;
695
748
  lifecycleHookHandlers.runBeforeDestroy?.(this);
696
749
  this.children.forEach((child) => child.destroy());
697
750
  this.children.clear();
@@ -3716,6 +3769,8 @@ function createWithDefaults(type, snapshot = {}, env) {
3716
3769
  }
3717
3770
 
3718
3771
  // src/undo.ts
3772
+ var undoManagersRegistry = /* @__PURE__ */ new WeakMap();
3773
+ var timeTravelManagersRegistry = /* @__PURE__ */ new WeakMap();
3719
3774
  var UndoManager = class {
3720
3775
  constructor(target, options = {}) {
3721
3776
  this.historyEntries = [];
@@ -3736,6 +3791,7 @@ var UndoManager = class {
3736
3791
  groupByTime: options.groupByTime ?? false,
3737
3792
  groupingWindow: options.groupingWindow ?? 200
3738
3793
  };
3794
+ undoManagersRegistry.set(target, this);
3739
3795
  this.disposer = onPatch(target, (patch, reversePatch) => {
3740
3796
  this.recordPatch(patch, reversePatch);
3741
3797
  });
@@ -3829,6 +3885,13 @@ var UndoManager = class {
3829
3885
  applyPatch(this.target, entry.patches[i]);
3830
3886
  }
3831
3887
  this.currentIndex--;
3888
+ const tt = timeTravelManagersRegistry.get(this.target);
3889
+ if (tt) {
3890
+ const N = tt.snapshots.length;
3891
+ const M = this.historyEntries.length;
3892
+ const ttIndex = this.currentIndex + 1 + (N - 1 - M);
3893
+ tt.index = Math.max(0, Math.min(N - 1, ttIndex));
3894
+ }
3832
3895
  } finally {
3833
3896
  this.isUndoing = false;
3834
3897
  rootNode.$isApplyingHistory = wasApplying;
@@ -3849,6 +3912,13 @@ var UndoManager = class {
3849
3912
  for (const patch of entry.inversePatches) {
3850
3913
  applyPatch(this.target, patch);
3851
3914
  }
3915
+ const tt = timeTravelManagersRegistry.get(this.target);
3916
+ if (tt) {
3917
+ const N = tt.snapshots.length;
3918
+ const M = this.historyEntries.length;
3919
+ const ttIndex = this.currentIndex + 1 + (N - 1 - M);
3920
+ tt.index = Math.max(0, Math.min(N - 1, ttIndex));
3921
+ }
3852
3922
  } finally {
3853
3923
  this.isRedoing = false;
3854
3924
  rootNode.$isApplyingHistory = wasApplying;
@@ -3902,6 +3972,7 @@ var UndoManager = class {
3902
3972
  }
3903
3973
  }
3904
3974
  dispose() {
3975
+ undoManagersRegistry.delete(this.target);
3905
3976
  if (this.disposer) {
3906
3977
  this.disposer();
3907
3978
  this.disposer = null;
@@ -3928,6 +3999,7 @@ var TimeTravelManager = class {
3928
3999
  this.target = target;
3929
4000
  this.maxSnapshots = options.maxSnapshots ?? 50;
3930
4001
  this.autoRecord = options.autoRecord ?? false;
4002
+ timeTravelManagersRegistry.set(target, this);
3931
4003
  this.record();
3932
4004
  if (this.autoRecord) {
3933
4005
  this.disposer = onPatch(target, () => {
@@ -4009,6 +4081,13 @@ var TimeTravelManager = class {
4009
4081
  try {
4010
4082
  this.index = index;
4011
4083
  applySnapshot(this.target, this.snapshots[index]);
4084
+ const undo = undoManagersRegistry.get(this.target);
4085
+ if (undo) {
4086
+ const N = this.snapshots.length;
4087
+ const M = undo.historyEntries.length;
4088
+ const undoIndex = this.index - 1 - (N - 1 - M);
4089
+ undo.currentIndex = Math.max(-1, Math.min(M - 1, undoIndex));
4090
+ }
4012
4091
  } finally {
4013
4092
  this.isApplying = false;
4014
4093
  rootNode.$isApplyingHistory = wasApplying;
@@ -4026,6 +4105,7 @@ var TimeTravelManager = class {
4026
4105
  this.record();
4027
4106
  }
4028
4107
  dispose() {
4108
+ timeTravelManagersRegistry.delete(this.target);
4029
4109
  if (this.disposer) {
4030
4110
  this.disposer();
4031
4111
  this.disposer = null;
package/dist/index.mjs CHANGED
@@ -62,7 +62,7 @@ import {
62
62
  tryResolve,
63
63
  unfreeze,
64
64
  walk
65
- } from "./chunk-MSLAD5CJ.mjs";
65
+ } from "./chunk-Q6QPBXHH.mjs";
66
66
 
67
67
  // src/primitives.ts
68
68
  function createSimpleType(name, validator, defaultValue) {
package/dist/react.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import React, { ComponentType, FC, ReactNode } from 'react';
2
- import { ag as getGlobalStore, aD as IUndoManagerOptions, aC as IUndoManager, aF as ITimeTravelManager } from './undo-BcBI_BQM.mjs';
3
- export { aI as hasStateTreeNode } from './undo-BcBI_BQM.mjs';
2
+ import { ag as getGlobalStore, aD as IUndoManagerOptions, aC as IUndoManager, aF as ITimeTravelManager } from './undo-DL1pyOkT.mjs';
3
+ export { aI as hasStateTreeNode } from './undo-DL1pyOkT.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, aD as IUndoManagerOptions, aC as IUndoManager, aF as ITimeTravelManager } from './undo-BcBI_BQM.js';
3
- export { aI as hasStateTreeNode } from './undo-BcBI_BQM.js';
2
+ import { ag as getGlobalStore, aD as IUndoManagerOptions, aC as IUndoManager, aF as ITimeTravelManager } from './undo-DL1pyOkT.js';
3
+ export { aI as hasStateTreeNode } from './undo-DL1pyOkT.js';
4
4
  import 'jotai/vanilla/internals';
5
5
  import 'jotai';
6
6
 
package/dist/react.js CHANGED
@@ -93,9 +93,12 @@ var identifierFinalizationRegistry = new FinalizationRegistry(
93
93
  (info) => {
94
94
  const typeMap = identifierRegistry.get(info.typeName);
95
95
  if (typeMap) {
96
- typeMap.delete(info.identifier);
97
- if (typeMap.size === 0) {
98
- identifierRegistry.delete(info.typeName);
96
+ const ref = typeMap.get(info.identifier);
97
+ if (!ref || ref.deref() === void 0) {
98
+ typeMap.delete(info.identifier);
99
+ if (typeMap.size === 0) {
100
+ identifierRegistry.delete(info.typeName);
101
+ }
99
102
  }
100
103
  }
101
104
  }
@@ -122,6 +125,35 @@ function notifyLifecycleChange(node, isAlive) {
122
125
  listeners.forEach((listener) => listener(isAlive));
123
126
  }
124
127
  }
128
+ function cloneAndSerialize(value) {
129
+ if (value === null || value === void 0) {
130
+ return value;
131
+ }
132
+ if (hasStateTreeNode(value)) {
133
+ return getSnapshotFromNode(getStateTreeNode(value));
134
+ }
135
+ if (Array.isArray(value)) {
136
+ return value.map(cloneAndSerialize);
137
+ }
138
+ if (typeof value === "object") {
139
+ if (value instanceof Map) {
140
+ const res = {};
141
+ for (const [k, v] of value.entries()) {
142
+ res[k] = cloneAndSerialize(v);
143
+ }
144
+ return res;
145
+ }
146
+ const proto = Object.getPrototypeOf(value);
147
+ if (proto === null || proto === Object.prototype) {
148
+ const res = {};
149
+ for (const [k, v] of Object.entries(value)) {
150
+ res[k] = cloneAndSerialize(v);
151
+ }
152
+ return res;
153
+ }
154
+ }
155
+ return value;
156
+ }
125
157
  var StateTreeNode = class {
126
158
  constructor(type, initialValue, env, parent, pathSegment) {
127
159
  this.$parent = null;
@@ -139,6 +171,8 @@ var StateTreeNode = class {
139
171
  /** Cached snapshot for structural sharing optimization */
140
172
  this.cachedSnapshot = void 0;
141
173
  this.isSnapshotDirty = true;
174
+ /** Strong reference to the instance proxy to prevent GC of active nodes */
175
+ this.instance = null;
142
176
  this.$id = generateNodeId();
143
177
  this.$type = type;
144
178
  this.$env = env ?? parent?.$env;
@@ -159,6 +193,7 @@ var StateTreeNode = class {
159
193
  }
160
194
  /** Set the instance reference */
161
195
  setInstance(instance) {
196
+ this.instance = instance;
162
197
  const entry = nodeRegistry.get(this.$id);
163
198
  if (entry && instance && typeof instance === "object") {
164
199
  entry.instance = new WeakRef(instance);
@@ -166,8 +201,7 @@ var StateTreeNode = class {
166
201
  }
167
202
  /** Get the instance */
168
203
  getInstance() {
169
- const entry = nodeRegistry.get(this.$id);
170
- return entry?.instance?.deref() ?? null;
204
+ return this.instance;
171
205
  }
172
206
  /** Get current value from atom */
173
207
  getValue() {
@@ -273,9 +307,27 @@ var StateTreeNode = class {
273
307
  }
274
308
  /** Notify patch listeners */
275
309
  notifyPatch(patch, reversePatch) {
276
- this.patchListeners.forEach((listener) => listener(patch, reversePatch));
310
+ const serializedPatch = {
311
+ ...patch,
312
+ value: "value" in patch ? cloneAndSerialize(patch.value) : void 0
313
+ };
314
+ if (!("value" in patch)) {
315
+ delete serializedPatch.value;
316
+ }
317
+ const serializedReversePatch = {
318
+ ...reversePatch,
319
+ value: "value" in reversePatch ? cloneAndSerialize(reversePatch.value) : void 0,
320
+ oldValue: "oldValue" in reversePatch ? cloneAndSerialize(reversePatch.oldValue) : void 0
321
+ };
322
+ if (!("value" in reversePatch)) {
323
+ delete serializedReversePatch.value;
324
+ }
325
+ if (!("oldValue" in reversePatch)) {
326
+ delete serializedReversePatch.oldValue;
327
+ }
328
+ this.patchListeners.forEach((listener) => listener(serializedPatch, serializedReversePatch));
277
329
  if (this.$parent) {
278
- this.$parent.notifyPatch(patch, reversePatch);
330
+ this.$parent.notifyPatch(serializedPatch, serializedReversePatch);
279
331
  }
280
332
  }
281
333
  /** Notify snapshot listeners */
@@ -312,6 +364,7 @@ var StateTreeNode = class {
312
364
  /** Destroy this node and all children */
313
365
  destroy() {
314
366
  if (!this.$isAlive) return;
367
+ this.instance = null;
315
368
  lifecycleHookHandlers.runBeforeDestroy?.(this);
316
369
  this.children.forEach((child) => child.destroy());
317
370
  this.children.clear();
@@ -560,6 +613,8 @@ function onAction(target, listener) {
560
613
  }
561
614
 
562
615
  // src/undo.ts
616
+ var undoManagersRegistry = /* @__PURE__ */ new WeakMap();
617
+ var timeTravelManagersRegistry = /* @__PURE__ */ new WeakMap();
563
618
  var UndoManager = class {
564
619
  constructor(target, options = {}) {
565
620
  this.historyEntries = [];
@@ -580,6 +635,7 @@ var UndoManager = class {
580
635
  groupByTime: options.groupByTime ?? false,
581
636
  groupingWindow: options.groupingWindow ?? 200
582
637
  };
638
+ undoManagersRegistry.set(target, this);
583
639
  this.disposer = onPatch(target, (patch, reversePatch) => {
584
640
  this.recordPatch(patch, reversePatch);
585
641
  });
@@ -673,6 +729,13 @@ var UndoManager = class {
673
729
  applyPatch(this.target, entry.patches[i]);
674
730
  }
675
731
  this.currentIndex--;
732
+ const tt = timeTravelManagersRegistry.get(this.target);
733
+ if (tt) {
734
+ const N = tt.snapshots.length;
735
+ const M = this.historyEntries.length;
736
+ const ttIndex = this.currentIndex + 1 + (N - 1 - M);
737
+ tt.index = Math.max(0, Math.min(N - 1, ttIndex));
738
+ }
676
739
  } finally {
677
740
  this.isUndoing = false;
678
741
  rootNode.$isApplyingHistory = wasApplying;
@@ -693,6 +756,13 @@ var UndoManager = class {
693
756
  for (const patch of entry.inversePatches) {
694
757
  applyPatch(this.target, patch);
695
758
  }
759
+ const tt = timeTravelManagersRegistry.get(this.target);
760
+ if (tt) {
761
+ const N = tt.snapshots.length;
762
+ const M = this.historyEntries.length;
763
+ const ttIndex = this.currentIndex + 1 + (N - 1 - M);
764
+ tt.index = Math.max(0, Math.min(N - 1, ttIndex));
765
+ }
696
766
  } finally {
697
767
  this.isRedoing = false;
698
768
  rootNode.$isApplyingHistory = wasApplying;
@@ -746,6 +816,7 @@ var UndoManager = class {
746
816
  }
747
817
  }
748
818
  dispose() {
819
+ undoManagersRegistry.delete(this.target);
749
820
  if (this.disposer) {
750
821
  this.disposer();
751
822
  this.disposer = null;
@@ -772,6 +843,7 @@ var TimeTravelManager = class {
772
843
  this.target = target;
773
844
  this.maxSnapshots = options.maxSnapshots ?? 50;
774
845
  this.autoRecord = options.autoRecord ?? false;
846
+ timeTravelManagersRegistry.set(target, this);
775
847
  this.record();
776
848
  if (this.autoRecord) {
777
849
  this.disposer = onPatch(target, () => {
@@ -853,6 +925,13 @@ var TimeTravelManager = class {
853
925
  try {
854
926
  this.index = index;
855
927
  applySnapshot(this.target, this.snapshots[index]);
928
+ const undo = undoManagersRegistry.get(this.target);
929
+ if (undo) {
930
+ const N = this.snapshots.length;
931
+ const M = undo.historyEntries.length;
932
+ const undoIndex = this.index - 1 - (N - 1 - M);
933
+ undo.currentIndex = Math.max(-1, Math.min(M - 1, undoIndex));
934
+ }
856
935
  } finally {
857
936
  this.isApplying = false;
858
937
  rootNode.$isApplyingHistory = wasApplying;
@@ -870,6 +949,7 @@ var TimeTravelManager = class {
870
949
  this.record();
871
950
  }
872
951
  dispose() {
952
+ timeTravelManagersRegistry.delete(this.target);
873
953
  if (this.disposer) {
874
954
  this.disposer();
875
955
  this.disposer = null;
@@ -1229,6 +1309,15 @@ function useHydrateStore(target, snapshot, options) {
1229
1309
  return;
1230
1310
  }
1231
1311
  const node = getStateTreeNode(target);
1312
+ (0, import_react.useMemo)(() => {
1313
+ const wasApplying = getIsApplyingSnapshotOrPatch();
1314
+ setIsApplyingSnapshotOrPatch(true);
1315
+ try {
1316
+ applySnapshotToNode(node, snapshot);
1317
+ } finally {
1318
+ setIsApplyingSnapshotOrPatch(wasApplying);
1319
+ }
1320
+ }, [node, snapshot]);
1232
1321
  const pairs = (0, import_react.useMemo)(() => {
1233
1322
  const collectedPairs = [];
1234
1323
  function collect(n) {