jotai-state-tree 1.4.3 → 1.4.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.
@@ -128,6 +128,41 @@ var StateTreeNode = class {
128
128
  this.$parent = parent ?? null;
129
129
  this.$path = parent ? `${parent.$path}/${pathSegment}` : "";
130
130
  this.valueAtom = atom(initialValue);
131
+ this.isAliveAtom = atom(true);
132
+ this.snapshotAtom = atom((get) => {
133
+ const value = get(this.valueAtom);
134
+ if (this.$type._kind === "model") {
135
+ const res = {};
136
+ for (const [key, childNode] of this.children.entries()) {
137
+ res[key] = get(childNode.snapshotAtom);
138
+ }
139
+ if (value && typeof value === "object") {
140
+ for (const key of Object.keys(value)) {
141
+ if (!this.children.has(key)) {
142
+ res[key] = value[key];
143
+ }
144
+ }
145
+ }
146
+ return this.postProcessor ? this.postProcessor(res) : res;
147
+ }
148
+ if (this.$type._kind === "array") {
149
+ const res = [];
150
+ const childrenKeys = Array.from(this.children.keys()).sort((a, b) => Number(a) - Number(b));
151
+ for (const key of childrenKeys) {
152
+ const childNode = this.children.get(key);
153
+ res.push(get(childNode.snapshotAtom));
154
+ }
155
+ return res;
156
+ }
157
+ if (this.$type._kind === "map") {
158
+ const res = {};
159
+ for (const [key, childNode] of this.children.entries()) {
160
+ res[key] = get(childNode.snapshotAtom);
161
+ }
162
+ return res;
163
+ }
164
+ return value;
165
+ });
131
166
  nodeRegistry.set(this.$id, { node: new WeakRef(this), instance: null });
132
167
  nodeFinalizationRegistry.register(this, this.$id, this);
133
168
  }
@@ -319,6 +354,10 @@ var StateTreeNode = class {
319
354
  this.children.clear();
320
355
  this.unregisterIdentifier();
321
356
  this.$isAlive = false;
357
+ try {
358
+ globalStore.set(this.isAliveAtom, false);
359
+ } catch (e) {
360
+ }
322
361
  notifyLifecycleChange(this, false);
323
362
  nodeRegistry.delete(this.$id);
324
363
  nodeFinalizationRegistry.unregister(this);
@@ -495,6 +534,10 @@ function clearAllRegistries() {
495
534
  const node = entry.node.deref();
496
535
  if (node) {
497
536
  node.$isAlive = false;
537
+ try {
538
+ globalStore.set(node.isAliveAtom, false);
539
+ } catch (e) {
540
+ }
498
541
  }
499
542
  }
500
543
  nodeRegistry.clear();
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-quCDYz_6.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-quCDYz_6.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-BSq-Pomp.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-BSq-Pomp.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-quCDYz_6.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-quCDYz_6.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-BSq-Pomp.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-BSq-Pomp.js';
3
3
  import 'jotai/vanilla/internals';
4
4
  import 'jotai';
5
5
 
package/dist/index.js CHANGED
@@ -559,6 +559,41 @@ var StateTreeNode = class {
559
559
  this.$parent = parent ?? null;
560
560
  this.$path = parent ? `${parent.$path}/${pathSegment}` : "";
561
561
  this.valueAtom = (0, import_jotai.atom)(initialValue);
562
+ this.isAliveAtom = (0, import_jotai.atom)(true);
563
+ this.snapshotAtom = (0, import_jotai.atom)((get) => {
564
+ const value = get(this.valueAtom);
565
+ if (this.$type._kind === "model") {
566
+ const res = {};
567
+ for (const [key, childNode] of this.children.entries()) {
568
+ res[key] = get(childNode.snapshotAtom);
569
+ }
570
+ if (value && typeof value === "object") {
571
+ for (const key of Object.keys(value)) {
572
+ if (!this.children.has(key)) {
573
+ res[key] = value[key];
574
+ }
575
+ }
576
+ }
577
+ return this.postProcessor ? this.postProcessor(res) : res;
578
+ }
579
+ if (this.$type._kind === "array") {
580
+ const res = [];
581
+ const childrenKeys = Array.from(this.children.keys()).sort((a, b) => Number(a) - Number(b));
582
+ for (const key of childrenKeys) {
583
+ const childNode = this.children.get(key);
584
+ res.push(get(childNode.snapshotAtom));
585
+ }
586
+ return res;
587
+ }
588
+ if (this.$type._kind === "map") {
589
+ const res = {};
590
+ for (const [key, childNode] of this.children.entries()) {
591
+ res[key] = get(childNode.snapshotAtom);
592
+ }
593
+ return res;
594
+ }
595
+ return value;
596
+ });
562
597
  nodeRegistry.set(this.$id, { node: new WeakRef(this), instance: null });
563
598
  nodeFinalizationRegistry.register(this, this.$id, this);
564
599
  }
@@ -750,6 +785,10 @@ var StateTreeNode = class {
750
785
  this.children.clear();
751
786
  this.unregisterIdentifier();
752
787
  this.$isAlive = false;
788
+ try {
789
+ globalStore.set(this.isAliveAtom, false);
790
+ } catch (e) {
791
+ }
753
792
  notifyLifecycleChange(this, false);
754
793
  nodeRegistry.delete(this.$id);
755
794
  nodeFinalizationRegistry.unregister(this);
@@ -926,6 +965,10 @@ function clearAllRegistries() {
926
965
  const node = entry.node.deref();
927
966
  if (node) {
928
967
  node.$isAlive = false;
968
+ try {
969
+ globalStore.set(node.isAliveAtom, false);
970
+ } catch (e) {
971
+ }
929
972
  }
930
973
  }
931
974
  nodeRegistry.clear();
package/dist/index.mjs CHANGED
@@ -62,7 +62,7 @@ import {
62
62
  tryResolve,
63
63
  unfreeze,
64
64
  walk
65
- } from "./chunk-5OCZ6YLH.mjs";
65
+ } from "./chunk-XA4ACZVI.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-quCDYz_6.mjs';
3
- export { aI as hasStateTreeNode } from './undo-quCDYz_6.mjs';
2
+ import { ag as getGlobalStore, aD as IUndoManagerOptions, aC as IUndoManager, aF as ITimeTravelManager } from './undo-BSq-Pomp.mjs';
3
+ export { aI as hasStateTreeNode } from './undo-BSq-Pomp.mjs';
4
4
  import 'jotai/vanilla/internals';
5
5
  import 'jotai';
6
6
 
@@ -108,9 +108,6 @@ declare function createStoreContext<T>(): {
108
108
  useIsAlive: () => boolean;
109
109
  Context: React.Context<T | null>;
110
110
  };
111
- /**
112
- * Hook that returns the current snapshot and re-renders on changes.
113
- */
114
111
  declare function useSnapshot<T>(target: unknown): T;
115
112
  /**
116
113
  * Hook to watch specific paths in a state tree
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-quCDYz_6.js';
3
- export { aI as hasStateTreeNode } from './undo-quCDYz_6.js';
2
+ import { ag as getGlobalStore, aD as IUndoManagerOptions, aC as IUndoManager, aF as ITimeTravelManager } from './undo-BSq-Pomp.js';
3
+ export { aI as hasStateTreeNode } from './undo-BSq-Pomp.js';
4
4
  import 'jotai/vanilla/internals';
5
5
  import 'jotai';
6
6
 
@@ -108,9 +108,6 @@ declare function createStoreContext<T>(): {
108
108
  useIsAlive: () => boolean;
109
109
  Context: React.Context<T | null>;
110
110
  };
111
- /**
112
- * Hook that returns the current snapshot and re-renders on changes.
113
- */
114
111
  declare function useSnapshot<T>(target: unknown): T;
115
112
  /**
116
113
  * Hook to watch specific paths in a state tree
package/dist/react.js CHANGED
@@ -109,17 +109,6 @@ function generateNodeId() {
109
109
  return `node_${++nodeIdCounter}_${Date.now().toString(36)}`;
110
110
  }
111
111
  var lifecycleListeners = /* @__PURE__ */ new WeakMap();
112
- function onLifecycleChange(node, listener) {
113
- let listeners = lifecycleListeners.get(node);
114
- if (!listeners) {
115
- listeners = /* @__PURE__ */ new Set();
116
- lifecycleListeners.set(node, listeners);
117
- }
118
- listeners.add(listener);
119
- return () => {
120
- listeners?.delete(listener);
121
- };
122
- }
123
112
  function notifyLifecycleChange(node, isAlive) {
124
113
  const listeners = lifecycleListeners.get(node);
125
114
  if (listeners) {
@@ -180,6 +169,41 @@ var StateTreeNode = class {
180
169
  this.$parent = parent ?? null;
181
170
  this.$path = parent ? `${parent.$path}/${pathSegment}` : "";
182
171
  this.valueAtom = (0, import_jotai.atom)(initialValue);
172
+ this.isAliveAtom = (0, import_jotai.atom)(true);
173
+ this.snapshotAtom = (0, import_jotai.atom)((get) => {
174
+ const value = get(this.valueAtom);
175
+ if (this.$type._kind === "model") {
176
+ const res = {};
177
+ for (const [key, childNode] of this.children.entries()) {
178
+ res[key] = get(childNode.snapshotAtom);
179
+ }
180
+ if (value && typeof value === "object") {
181
+ for (const key of Object.keys(value)) {
182
+ if (!this.children.has(key)) {
183
+ res[key] = value[key];
184
+ }
185
+ }
186
+ }
187
+ return this.postProcessor ? this.postProcessor(res) : res;
188
+ }
189
+ if (this.$type._kind === "array") {
190
+ const res = [];
191
+ const childrenKeys = Array.from(this.children.keys()).sort((a, b) => Number(a) - Number(b));
192
+ for (const key of childrenKeys) {
193
+ const childNode = this.children.get(key);
194
+ res.push(get(childNode.snapshotAtom));
195
+ }
196
+ return res;
197
+ }
198
+ if (this.$type._kind === "map") {
199
+ const res = {};
200
+ for (const [key, childNode] of this.children.entries()) {
201
+ res[key] = get(childNode.snapshotAtom);
202
+ }
203
+ return res;
204
+ }
205
+ return value;
206
+ });
183
207
  nodeRegistry.set(this.$id, { node: new WeakRef(this), instance: null });
184
208
  nodeFinalizationRegistry.register(this, this.$id, this);
185
209
  }
@@ -371,6 +395,10 @@ var StateTreeNode = class {
371
395
  this.children.clear();
372
396
  this.unregisterIdentifier();
373
397
  this.$isAlive = false;
398
+ try {
399
+ globalStore.set(this.isAliveAtom, false);
400
+ } catch (e) {
401
+ }
374
402
  notifyLifecycleChange(this, false);
375
403
  nodeRegistry.delete(this.$id);
376
404
  nodeFinalizationRegistry.unregister(this);
@@ -504,10 +532,6 @@ function applySnapshot(target, snapshot) {
504
532
  setIsApplyingSnapshotOrPatch(wasApplying);
505
533
  }
506
534
  }
507
- function onSnapshot(target, listener) {
508
- const node = getStateTreeNode(target);
509
- return node.onSnapshot(listener);
510
- }
511
535
  function onPatch(target, listener) {
512
536
  const node = getStateTreeNode(target);
513
537
  return node.onPatch(listener);
@@ -1007,60 +1031,19 @@ function useObserver(fn) {
1007
1031
  }
1008
1032
  }
1009
1033
  function useLocalObservable(initializer, dependencies = []) {
1010
- const [, forceUpdate] = (0, import_react.useState)({});
1011
- const storeRef = (0, import_react.useRef)(null);
1012
- const disposerRef = (0, import_react.useRef)(null);
1013
- if (storeRef.current === null) {
1014
- storeRef.current = initializer();
1015
- if (hasStateTreeNode(storeRef.current)) {
1016
- disposerRef.current = onSnapshot(storeRef.current, () => {
1017
- forceUpdate({});
1018
- });
1019
- }
1034
+ const store = (0, import_react.useMemo)(initializer, dependencies);
1035
+ if (hasStateTreeNode(store)) {
1036
+ const node = getStateTreeNode(store);
1037
+ (0, import_jotai3.useAtomValue)(node.snapshotAtom, { store: getGlobalStore() });
1020
1038
  }
1021
- (0, import_react.useEffect)(() => {
1022
- return () => {
1023
- disposerRef.current?.();
1024
- };
1025
- }, []);
1026
- (0, import_react.useEffect)(() => {
1027
- if (dependencies.length > 0) {
1028
- disposerRef.current?.();
1029
- storeRef.current = initializer();
1030
- if (hasStateTreeNode(storeRef.current)) {
1031
- disposerRef.current = onSnapshot(storeRef.current, () => {
1032
- forceUpdate({});
1033
- });
1034
- }
1035
- }
1036
- }, dependencies);
1037
- return storeRef.current;
1039
+ return store;
1038
1040
  }
1039
1041
  function useSyncedStore(store) {
1040
- const snapshotRef = (0, import_react.useRef)(null);
1041
- const subscribe = (0, import_react.useCallback)(
1042
- (callback) => {
1043
- if (!hasStateTreeNode(store)) {
1044
- return () => {
1045
- };
1046
- }
1047
- return onSnapshot(store, () => {
1048
- snapshotRef.current = getSnapshot(store);
1049
- callback();
1050
- });
1051
- },
1052
- [store]
1053
- );
1054
- const getSnapshotValue = (0, import_react.useCallback)(() => {
1055
- if (!hasStateTreeNode(store)) {
1056
- return null;
1057
- }
1058
- if (snapshotRef.current === null) {
1059
- snapshotRef.current = getSnapshot(store);
1060
- }
1061
- return snapshotRef.current;
1062
- }, [store]);
1063
- (0, import_react.useSyncExternalStore)(subscribe, getSnapshotValue, getSnapshotValue);
1042
+ if (!hasStateTreeNode(store)) {
1043
+ return store;
1044
+ }
1045
+ const node = getStateTreeNode(store);
1046
+ (0, import_jotai3.useAtomValue)(node.snapshotAtom, { store: getGlobalStore() });
1064
1047
  return store;
1065
1048
  }
1066
1049
  var StoreContext = import_react.default.createContext(
@@ -1084,20 +1067,12 @@ function useStore() {
1084
1067
  }
1085
1068
  function useStoreSnapshot(selector) {
1086
1069
  const store = useStore();
1087
- const [, forceUpdate] = (0, import_react.useState)({});
1088
- (0, import_react.useEffect)(() => {
1089
- if (hasStateTreeNode(store)) {
1090
- return onSnapshot(store, () => {
1091
- forceUpdate({});
1092
- });
1093
- }
1094
- return () => {
1095
- };
1096
- }, [store]);
1097
- if (selector) {
1098
- return selector(store);
1070
+ if (!hasStateTreeNode(store)) {
1071
+ return store;
1099
1072
  }
1100
- return store;
1073
+ const node = getStateTreeNode(store);
1074
+ (0, import_jotai3.useAtomValue)(node.snapshotAtom, { store: getGlobalStore() });
1075
+ return selector ? selector(store) : store;
1101
1076
  }
1102
1077
  function createStoreContext() {
1103
1078
  const Context = import_react.default.createContext(null);
@@ -1118,20 +1093,12 @@ function createStoreContext() {
1118
1093
  }
1119
1094
  function useTypedStoreSnapshot(selector) {
1120
1095
  const store = useTypedStore();
1121
- const [, forceUpdate] = (0, import_react.useState)({});
1122
- (0, import_react.useEffect)(() => {
1123
- if (hasStateTreeNode(store)) {
1124
- return onSnapshot(store, () => {
1125
- forceUpdate({});
1126
- });
1127
- }
1128
- return () => {
1129
- };
1130
- }, [store]);
1131
- if (selector) {
1132
- return selector(store);
1096
+ if (!hasStateTreeNode(store)) {
1097
+ return store;
1133
1098
  }
1134
- return store;
1099
+ const node = getStateTreeNode(store);
1100
+ (0, import_jotai3.useAtomValue)(node.snapshotAtom, { store: getGlobalStore() });
1101
+ return selector ? selector(store) : store;
1135
1102
  }
1136
1103
  function useTypedIsAlive() {
1137
1104
  const store = useTypedStore();
@@ -1146,47 +1113,33 @@ function createStoreContext() {
1146
1113
  };
1147
1114
  }
1148
1115
  function useSnapshot(target) {
1149
- const [snapshot, setSnapshot] = (0, import_react.useState)(() => getSnapshot(target));
1150
- (0, import_react.useEffect)(() => {
1151
- const disposer = onSnapshot(target, (newSnapshot) => {
1152
- setSnapshot(newSnapshot);
1153
- });
1154
- return disposer;
1155
- }, [target]);
1156
- return snapshot;
1116
+ if (!hasStateTreeNode(target)) {
1117
+ return target;
1118
+ }
1119
+ const node = getStateTreeNode(target);
1120
+ return (0, import_jotai3.useAtomValue)(node.snapshotAtom, { store: getGlobalStore() });
1157
1121
  }
1158
1122
  function useWatchPath(target, path, defaultValue) {
1159
- const [value, setValue] = (0, import_react.useState)(() => {
1160
- const snapshot = getSnapshot(target);
1161
- const parts = path.split(".");
1162
- let current = snapshot;
1163
- for (const part of parts) {
1164
- if (current && typeof current === "object" && part in current) {
1165
- current = current[part];
1166
- } else {
1167
- return defaultValue;
1168
- }
1169
- }
1170
- return current;
1171
- });
1172
- (0, import_react.useEffect)(() => {
1173
- const disposer = onSnapshot(target, (newSnapshot) => {
1174
- const snapshot = newSnapshot;
1123
+ if (!hasStateTreeNode(target)) {
1124
+ return defaultValue;
1125
+ }
1126
+ const node = getStateTreeNode(target);
1127
+ const pathAtom = (0, import_react.useMemo)(() => {
1128
+ return (0, import_jotai3.atom)((get) => {
1129
+ const snapshot = get(node.snapshotAtom);
1175
1130
  const parts = path.split(".");
1176
1131
  let current = snapshot;
1177
1132
  for (const part of parts) {
1178
1133
  if (current && typeof current === "object" && part in current) {
1179
1134
  current = current[part];
1180
1135
  } else {
1181
- setValue(defaultValue);
1182
- return;
1136
+ return defaultValue;
1183
1137
  }
1184
1138
  }
1185
- setValue(current);
1139
+ return current;
1186
1140
  });
1187
- return disposer;
1188
- }, [target, path, defaultValue]);
1189
- return value;
1141
+ }, [node, path, defaultValue]);
1142
+ return (0, import_jotai3.useAtomValue)(pathAtom, { store: getGlobalStore() });
1190
1143
  }
1191
1144
  function usePatches(target, callback) {
1192
1145
  (0, import_react.useEffect)(() => {
@@ -1223,20 +1176,9 @@ function scheduleUpdate(update) {
1223
1176
  }
1224
1177
  }
1225
1178
  function useIsAlive(target) {
1226
- const [isAlive, setIsAlive] = (0, import_react.useState)(() => {
1227
- if (!hasStateTreeNode(target)) return false;
1228
- return getStateTreeNode(target).$isAlive;
1229
- });
1230
- (0, import_react.useEffect)(() => {
1231
- if (!hasStateTreeNode(target)) return;
1232
- const node = getStateTreeNode(target);
1233
- setIsAlive(node.$isAlive);
1234
- const disposer = onLifecycleChange(node, (alive) => {
1235
- setIsAlive(alive);
1236
- });
1237
- return disposer;
1238
- }, [target]);
1239
- return isAlive;
1179
+ if (!hasStateTreeNode(target)) return false;
1180
+ const node = getStateTreeNode(target);
1181
+ return (0, import_jotai3.useAtomValue)(node.isAliveAtom, { store: getGlobalStore() });
1240
1182
  }
1241
1183
  function useCleanup(cleanupFn) {
1242
1184
  const cleanupRef = (0, import_react.useRef)(cleanupFn);
package/dist/react.mjs CHANGED
@@ -5,15 +5,12 @@ import {
5
5
  getGlobalStore,
6
6
  getIsApplyingSnapshotOrPatch,
7
7
  getOrCreateHistoryTracker,
8
- getSnapshot,
9
8
  getStateTreeNode,
10
9
  hasStateTreeNode,
11
- onLifecycleChange,
12
10
  onPatch,
13
- onSnapshot,
14
11
  setActiveTrackingFn,
15
12
  setIsApplyingSnapshotOrPatch
16
- } from "./chunk-5OCZ6YLH.mjs";
13
+ } from "./chunk-XA4ACZVI.mjs";
17
14
 
18
15
  // src/react.ts
19
16
  import React, {
@@ -22,11 +19,10 @@ import React, {
22
19
  useMemo,
23
20
  useRef,
24
21
  forwardRef,
25
- memo,
26
- useCallback,
27
- useSyncExternalStore
22
+ memo
28
23
  } from "react";
29
24
  import {
25
+ atom,
30
26
  useAtomValue
31
27
  } from "jotai";
32
28
  import { useHydrateAtoms } from "jotai/utils";
@@ -116,60 +112,19 @@ function useObserver(fn) {
116
112
  }
117
113
  }
118
114
  function useLocalObservable(initializer, dependencies = []) {
119
- const [, forceUpdate] = useState({});
120
- const storeRef = useRef(null);
121
- const disposerRef = useRef(null);
122
- if (storeRef.current === null) {
123
- storeRef.current = initializer();
124
- if (hasStateTreeNode(storeRef.current)) {
125
- disposerRef.current = onSnapshot(storeRef.current, () => {
126
- forceUpdate({});
127
- });
128
- }
115
+ const store = useMemo(initializer, dependencies);
116
+ if (hasStateTreeNode(store)) {
117
+ const node = getStateTreeNode(store);
118
+ useAtomValue(node.snapshotAtom, { store: getGlobalStore() });
129
119
  }
130
- useEffect(() => {
131
- return () => {
132
- disposerRef.current?.();
133
- };
134
- }, []);
135
- useEffect(() => {
136
- if (dependencies.length > 0) {
137
- disposerRef.current?.();
138
- storeRef.current = initializer();
139
- if (hasStateTreeNode(storeRef.current)) {
140
- disposerRef.current = onSnapshot(storeRef.current, () => {
141
- forceUpdate({});
142
- });
143
- }
144
- }
145
- }, dependencies);
146
- return storeRef.current;
120
+ return store;
147
121
  }
148
122
  function useSyncedStore(store) {
149
- const snapshotRef = useRef(null);
150
- const subscribe = useCallback(
151
- (callback) => {
152
- if (!hasStateTreeNode(store)) {
153
- return () => {
154
- };
155
- }
156
- return onSnapshot(store, () => {
157
- snapshotRef.current = getSnapshot(store);
158
- callback();
159
- });
160
- },
161
- [store]
162
- );
163
- const getSnapshotValue = useCallback(() => {
164
- if (!hasStateTreeNode(store)) {
165
- return null;
166
- }
167
- if (snapshotRef.current === null) {
168
- snapshotRef.current = getSnapshot(store);
169
- }
170
- return snapshotRef.current;
171
- }, [store]);
172
- useSyncExternalStore(subscribe, getSnapshotValue, getSnapshotValue);
123
+ if (!hasStateTreeNode(store)) {
124
+ return store;
125
+ }
126
+ const node = getStateTreeNode(store);
127
+ useAtomValue(node.snapshotAtom, { store: getGlobalStore() });
173
128
  return store;
174
129
  }
175
130
  var StoreContext = React.createContext(
@@ -193,20 +148,12 @@ function useStore() {
193
148
  }
194
149
  function useStoreSnapshot(selector) {
195
150
  const store = useStore();
196
- const [, forceUpdate] = useState({});
197
- useEffect(() => {
198
- if (hasStateTreeNode(store)) {
199
- return onSnapshot(store, () => {
200
- forceUpdate({});
201
- });
202
- }
203
- return () => {
204
- };
205
- }, [store]);
206
- if (selector) {
207
- return selector(store);
151
+ if (!hasStateTreeNode(store)) {
152
+ return store;
208
153
  }
209
- return store;
154
+ const node = getStateTreeNode(store);
155
+ useAtomValue(node.snapshotAtom, { store: getGlobalStore() });
156
+ return selector ? selector(store) : store;
210
157
  }
211
158
  function createStoreContext() {
212
159
  const Context = React.createContext(null);
@@ -227,20 +174,12 @@ function createStoreContext() {
227
174
  }
228
175
  function useTypedStoreSnapshot(selector) {
229
176
  const store = useTypedStore();
230
- const [, forceUpdate] = useState({});
231
- useEffect(() => {
232
- if (hasStateTreeNode(store)) {
233
- return onSnapshot(store, () => {
234
- forceUpdate({});
235
- });
236
- }
237
- return () => {
238
- };
239
- }, [store]);
240
- if (selector) {
241
- return selector(store);
177
+ if (!hasStateTreeNode(store)) {
178
+ return store;
242
179
  }
243
- return store;
180
+ const node = getStateTreeNode(store);
181
+ useAtomValue(node.snapshotAtom, { store: getGlobalStore() });
182
+ return selector ? selector(store) : store;
244
183
  }
245
184
  function useTypedIsAlive() {
246
185
  const store = useTypedStore();
@@ -255,47 +194,33 @@ function createStoreContext() {
255
194
  };
256
195
  }
257
196
  function useSnapshot(target) {
258
- const [snapshot, setSnapshot] = useState(() => getSnapshot(target));
259
- useEffect(() => {
260
- const disposer = onSnapshot(target, (newSnapshot) => {
261
- setSnapshot(newSnapshot);
262
- });
263
- return disposer;
264
- }, [target]);
265
- return snapshot;
197
+ if (!hasStateTreeNode(target)) {
198
+ return target;
199
+ }
200
+ const node = getStateTreeNode(target);
201
+ return useAtomValue(node.snapshotAtom, { store: getGlobalStore() });
266
202
  }
267
203
  function useWatchPath(target, path, defaultValue) {
268
- const [value, setValue] = useState(() => {
269
- const snapshot = getSnapshot(target);
270
- const parts = path.split(".");
271
- let current = snapshot;
272
- for (const part of parts) {
273
- if (current && typeof current === "object" && part in current) {
274
- current = current[part];
275
- } else {
276
- return defaultValue;
277
- }
278
- }
279
- return current;
280
- });
281
- useEffect(() => {
282
- const disposer = onSnapshot(target, (newSnapshot) => {
283
- const snapshot = newSnapshot;
204
+ if (!hasStateTreeNode(target)) {
205
+ return defaultValue;
206
+ }
207
+ const node = getStateTreeNode(target);
208
+ const pathAtom = useMemo(() => {
209
+ return atom((get) => {
210
+ const snapshot = get(node.snapshotAtom);
284
211
  const parts = path.split(".");
285
212
  let current = snapshot;
286
213
  for (const part of parts) {
287
214
  if (current && typeof current === "object" && part in current) {
288
215
  current = current[part];
289
216
  } else {
290
- setValue(defaultValue);
291
- return;
217
+ return defaultValue;
292
218
  }
293
219
  }
294
- setValue(current);
220
+ return current;
295
221
  });
296
- return disposer;
297
- }, [target, path, defaultValue]);
298
- return value;
222
+ }, [node, path, defaultValue]);
223
+ return useAtomValue(pathAtom, { store: getGlobalStore() });
299
224
  }
300
225
  function usePatches(target, callback) {
301
226
  useEffect(() => {
@@ -332,20 +257,9 @@ function scheduleUpdate(update) {
332
257
  }
333
258
  }
334
259
  function useIsAlive(target) {
335
- const [isAlive, setIsAlive] = useState(() => {
336
- if (!hasStateTreeNode(target)) return false;
337
- return getStateTreeNode(target).$isAlive;
338
- });
339
- useEffect(() => {
340
- if (!hasStateTreeNode(target)) return;
341
- const node = getStateTreeNode(target);
342
- setIsAlive(node.$isAlive);
343
- const disposer = onLifecycleChange(node, (alive) => {
344
- setIsAlive(alive);
345
- });
346
- return disposer;
347
- }, [target]);
348
- return isAlive;
260
+ if (!hasStateTreeNode(target)) return false;
261
+ const node = getStateTreeNode(target);
262
+ return useAtomValue(node.isAliveAtom, { store: getGlobalStore() });
349
263
  }
350
264
  function useCleanup(cleanupFn) {
351
265
  const cleanupRef = useRef(cleanupFn);
@@ -1,5 +1,5 @@
1
1
  import * as jotai_vanilla_internals from 'jotai/vanilla/internals';
2
- import { WritableAtom, createStore } from 'jotai';
2
+ import { WritableAtom, Atom, createStore } from 'jotai';
3
3
 
4
4
  /**
5
5
  * Core types for jotai-state-tree
@@ -332,6 +332,10 @@ declare class StateTreeNode implements IStateTreeNode {
332
332
  private children;
333
333
  /** Atom storing the raw value/snapshot */
334
334
  valueAtom: WritableAtom<unknown, [unknown], void>;
335
+ /** Derived atom representing the snapshot of the entire node subtree */
336
+ snapshotAtom: Atom<unknown>;
337
+ /** Atom tracking the isAlive status */
338
+ isAliveAtom: WritableAtom<boolean, [boolean], void>;
335
339
  /** Snapshot listeners */
336
340
  private snapshotListeners;
337
341
  /** Patch listeners */
@@ -1,5 +1,5 @@
1
1
  import * as jotai_vanilla_internals from 'jotai/vanilla/internals';
2
- import { WritableAtom, createStore } from 'jotai';
2
+ import { WritableAtom, Atom, createStore } from 'jotai';
3
3
 
4
4
  /**
5
5
  * Core types for jotai-state-tree
@@ -332,6 +332,10 @@ declare class StateTreeNode implements IStateTreeNode {
332
332
  private children;
333
333
  /** Atom storing the raw value/snapshot */
334
334
  valueAtom: WritableAtom<unknown, [unknown], void>;
335
+ /** Derived atom representing the snapshot of the entire node subtree */
336
+ snapshotAtom: Atom<unknown>;
337
+ /** Atom tracking the isAlive status */
338
+ isAliveAtom: WritableAtom<boolean, [boolean], void>;
335
339
  /** Snapshot listeners */
336
340
  private snapshotListeners;
337
341
  /** Patch listeners */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jotai-state-tree",
3
- "version": "1.4.3",
3
+ "version": "1.4.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",
package/src/react.ts CHANGED
@@ -18,6 +18,7 @@ import React, {
18
18
  type FC,
19
19
  } from "react";
20
20
  import {
21
+ atom,
21
22
  useAtom,
22
23
  useAtomValue,
23
24
  useSetAtom,
@@ -218,43 +219,12 @@ export function useLocalObservable<T>(
218
219
  initializer: () => T,
219
220
  dependencies: unknown[] = [],
220
221
  ): T {
221
- const [, forceUpdate] = useState({});
222
- const storeRef = useRef<T | null>(null);
223
- const disposerRef = useRef<IDisposer | null>(null);
224
-
225
- // Initialize store
226
- if (storeRef.current === null) {
227
- storeRef.current = initializer();
228
-
229
- // Subscribe to changes
230
- if (hasStateTreeNode(storeRef.current)) {
231
- disposerRef.current = onSnapshot(storeRef.current, () => {
232
- forceUpdate({});
233
- });
234
- }
222
+ const store = useMemo(initializer, dependencies);
223
+ if (hasStateTreeNode(store)) {
224
+ const node = getStateTreeNode(store);
225
+ useAtomValue(node.snapshotAtom, { store: getGlobalStore() });
235
226
  }
236
-
237
- // Cleanup on unmount
238
- useEffect(() => {
239
- return () => {
240
- disposerRef.current?.();
241
- };
242
- }, []);
243
-
244
- // Reinitialize if dependencies change
245
- useEffect(() => {
246
- if (dependencies.length > 0) {
247
- disposerRef.current?.();
248
- storeRef.current = initializer();
249
- if (hasStateTreeNode(storeRef.current)) {
250
- disposerRef.current = onSnapshot(storeRef.current, () => {
251
- forceUpdate({});
252
- });
253
- }
254
- }
255
- }, dependencies);
256
-
257
- return storeRef.current;
227
+ return store;
258
228
  }
259
229
 
260
230
  // ============================================================================
@@ -266,36 +236,11 @@ export function useLocalObservable<T>(
266
236
  * This provides better concurrent mode support.
267
237
  */
268
238
  export function useSyncedStore<T>(store: T): T {
269
- // Cache the snapshot to provide stable reference for useSyncExternalStore
270
- const snapshotRef = useRef<unknown>(null);
271
-
272
- const subscribe = useCallback(
273
- (callback: () => void) => {
274
- if (!hasStateTreeNode(store)) {
275
- return () => {};
276
- }
277
- return onSnapshot(store, () => {
278
- // Update cached snapshot on change
279
- snapshotRef.current = getSnapshot(store);
280
- callback();
281
- });
282
- },
283
- [store],
284
- );
285
-
286
- const getSnapshotValue = useCallback(() => {
287
- if (!hasStateTreeNode(store)) {
288
- return null;
289
- }
290
- // Return cached snapshot for stable reference comparison
291
- if (snapshotRef.current === null) {
292
- snapshotRef.current = getSnapshot(store);
293
- }
294
- return snapshotRef.current;
295
- }, [store]);
296
-
297
- useSyncExternalStore(subscribe, getSnapshotValue, getSnapshotValue);
298
-
239
+ if (!hasStateTreeNode(store)) {
240
+ return store;
241
+ }
242
+ const node = getStateTreeNode(store);
243
+ useAtomValue(node.snapshotAtom, { store: getGlobalStore() });
299
244
  return store;
300
245
  }
301
246
 
@@ -350,22 +295,12 @@ export function useStoreSnapshot<T>(): T;
350
295
  export function useStoreSnapshot<T, S>(selector: (store: T) => S): S;
351
296
  export function useStoreSnapshot<T, S>(selector?: (store: T) => S): T | S {
352
297
  const store = useStore<T>();
353
- const [, forceUpdate] = useState({});
354
-
355
- useEffect(() => {
356
- if (hasStateTreeNode(store)) {
357
- return onSnapshot(store, () => {
358
- forceUpdate({});
359
- });
360
- }
361
- return () => {};
362
- }, [store]);
363
-
364
- if (selector) {
365
- return selector(store);
298
+ if (!hasStateTreeNode(store)) {
299
+ return store;
366
300
  }
367
-
368
- return store;
301
+ const node = getStateTreeNode(store);
302
+ useAtomValue(node.snapshotAtom, { store: getGlobalStore() });
303
+ return selector ? selector(store) : store;
369
304
  }
370
305
 
371
306
  // ============================================================================
@@ -424,22 +359,12 @@ export function createStoreContext<T>() {
424
359
  function useTypedStoreSnapshot<S>(selector: (store: T) => S): S;
425
360
  function useTypedStoreSnapshot<S>(selector?: (store: T) => S): T | S {
426
361
  const store = useTypedStore();
427
- const [, forceUpdate] = useState({});
428
-
429
- useEffect(() => {
430
- if (hasStateTreeNode(store)) {
431
- return onSnapshot(store, () => {
432
- forceUpdate({});
433
- });
434
- }
435
- return () => {};
436
- }, [store]);
437
-
438
- if (selector) {
439
- return selector(store);
362
+ if (!hasStateTreeNode(store)) {
363
+ return store;
440
364
  }
441
-
442
- return store;
365
+ const node = getStateTreeNode(store);
366
+ useAtomValue(node.snapshotAtom, { store: getGlobalStore() });
367
+ return selector ? selector(store) : store;
443
368
  }
444
369
 
445
370
  /**
@@ -463,20 +388,12 @@ export function createStoreContext<T>() {
463
388
  // Snapshot Hooks
464
389
  // ============================================================================
465
390
 
466
- /**
467
- * Hook that returns the current snapshot and re-renders on changes.
468
- */
469
391
  export function useSnapshot<T>(target: unknown): T {
470
- const [snapshot, setSnapshot] = useState<T>(() => getSnapshot(target));
471
-
472
- useEffect(() => {
473
- const disposer = onSnapshot(target, (newSnapshot) => {
474
- setSnapshot(newSnapshot as T);
475
- });
476
- return disposer;
477
- }, [target]);
478
-
479
- return snapshot;
392
+ if (!hasStateTreeNode(target)) {
393
+ return target as T;
394
+ }
395
+ const node = getStateTreeNode(target);
396
+ return useAtomValue(node.snapshotAtom, { store: getGlobalStore() }) as T;
480
397
  }
481
398
 
482
399
  /**
@@ -487,39 +404,27 @@ export function useWatchPath<T>(
487
404
  path: string,
488
405
  defaultValue?: T,
489
406
  ): T {
490
- const [value, setValue] = useState<T>(() => {
491
- const snapshot = getSnapshot(target) as Record<string, unknown>;
492
- const parts = path.split(".");
493
- let current: unknown = snapshot;
494
- for (const part of parts) {
495
- if (current && typeof current === "object" && part in current) {
496
- current = (current as Record<string, unknown>)[part];
497
- } else {
498
- return defaultValue as T;
499
- }
500
- }
501
- return current as T;
502
- });
503
-
504
- useEffect(() => {
505
- const disposer = onSnapshot(target, (newSnapshot) => {
506
- const snapshot = newSnapshot as Record<string, unknown>;
407
+ if (!hasStateTreeNode(target)) {
408
+ return defaultValue as T;
409
+ }
410
+ const node = getStateTreeNode(target);
411
+ const pathAtom = useMemo(() => {
412
+ return atom((get) => {
413
+ const snapshot = get(node.snapshotAtom) as Record<string, unknown>;
507
414
  const parts = path.split(".");
508
415
  let current: unknown = snapshot;
509
416
  for (const part of parts) {
510
417
  if (current && typeof current === "object" && part in current) {
511
418
  current = (current as Record<string, unknown>)[part];
512
419
  } else {
513
- setValue(defaultValue as T);
514
- return;
420
+ return defaultValue as T;
515
421
  }
516
422
  }
517
- setValue(current as T);
423
+ return current as T;
518
424
  });
519
- return disposer;
520
- }, [target, path, defaultValue]);
425
+ }, [node, path, defaultValue]);
521
426
 
522
- return value;
427
+ return useAtomValue(pathAtom, { store: getGlobalStore() });
523
428
  }
524
429
 
525
430
  /**
@@ -602,27 +507,9 @@ export function scheduleUpdate(update: () => void): void {
602
507
  * Uses proper subscription instead of polling for better performance.
603
508
  */
604
509
  export function useIsAlive(target: unknown): boolean {
605
- const [isAlive, setIsAlive] = useState(() => {
606
- if (!hasStateTreeNode(target)) return false;
607
- return getStateTreeNode(target).$isAlive;
608
- });
609
-
610
- useEffect(() => {
611
- if (!hasStateTreeNode(target)) return;
612
-
613
- const node = getStateTreeNode(target);
614
- setIsAlive(node.$isAlive);
615
-
616
- // Subscribe to lifecycle changes using proper event system
617
- // This is much more efficient than polling
618
- const disposer = onLifecycleChange(node, (alive) => {
619
- setIsAlive(alive);
620
- });
621
-
622
- return disposer;
623
- }, [target]);
624
-
625
- return isAlive;
510
+ if (!hasStateTreeNode(target)) return false;
511
+ const node = getStateTreeNode(target);
512
+ return useAtomValue(node.isAliveAtom, { store: getGlobalStore() });
626
513
  }
627
514
 
628
515
  /**
package/src/tree.ts CHANGED
@@ -9,7 +9,7 @@
9
9
  * - Properly cleans up all registries on node destruction
10
10
  */
11
11
 
12
- import { atom, createStore, type WritableAtom } from "jotai";
12
+ import { atom, createStore, type WritableAtom, type Atom } from "jotai";
13
13
  import type {
14
14
  IStateTreeNode,
15
15
  IType,
@@ -226,6 +226,12 @@ export class StateTreeNode implements IStateTreeNode {
226
226
  /** Atom storing the raw value/snapshot */
227
227
  valueAtom: WritableAtom<unknown, [unknown], void>;
228
228
 
229
+ /** Derived atom representing the snapshot of the entire node subtree */
230
+ snapshotAtom: Atom<unknown>;
231
+
232
+ /** Atom tracking the isAlive status */
233
+ isAliveAtom: WritableAtom<boolean, [boolean], void>;
234
+
229
235
  /** Snapshot listeners */
230
236
  private snapshotListeners = new Set<(snapshot: unknown) => void>();
231
237
 
@@ -280,6 +286,47 @@ export class StateTreeNode implements IStateTreeNode {
280
286
  // Create the value atom
281
287
  this.valueAtom = atom(initialValue);
282
288
 
289
+ this.isAliveAtom = atom(true);
290
+
291
+ this.snapshotAtom = atom((get) => {
292
+ const value = get(this.valueAtom);
293
+
294
+ if (this.$type._kind === "model") {
295
+ const res: Record<string, unknown> = {};
296
+ for (const [key, childNode] of this.children.entries()) {
297
+ res[key] = get(childNode.snapshotAtom);
298
+ }
299
+ if (value && typeof value === "object") {
300
+ for (const key of Object.keys(value)) {
301
+ if (!this.children.has(key)) {
302
+ res[key] = (value as Record<string, unknown>)[key];
303
+ }
304
+ }
305
+ }
306
+ return this.postProcessor ? this.postProcessor(res) : res;
307
+ }
308
+
309
+ if (this.$type._kind === "array") {
310
+ const res: unknown[] = [];
311
+ const childrenKeys = Array.from(this.children.keys()).sort((a, b) => Number(a) - Number(b));
312
+ for (const key of childrenKeys) {
313
+ const childNode = this.children.get(key)!;
314
+ res.push(get(childNode.snapshotAtom));
315
+ }
316
+ return res;
317
+ }
318
+
319
+ if (this.$type._kind === "map") {
320
+ const res: Record<string, unknown> = {};
321
+ for (const [key, childNode] of this.children.entries()) {
322
+ res[key] = get(childNode.snapshotAtom);
323
+ }
324
+ return res;
325
+ }
326
+
327
+ return value;
328
+ });
329
+
283
330
  // Register this node with WeakRef
284
331
  nodeRegistry.set(this.$id, { node: new WeakRef(this), instance: null });
285
332
 
@@ -515,6 +562,11 @@ export class StateTreeNode implements IStateTreeNode {
515
562
 
516
563
  // Mark as dead
517
564
  this.$isAlive = false;
565
+ try {
566
+ globalStore.set(this.isAliveAtom, false);
567
+ } catch (e) {
568
+ // Ignore store errors during teardown
569
+ }
518
570
 
519
571
  // Notify lifecycle listeners
520
572
  notifyLifecycleChange(this, false);
@@ -800,6 +852,11 @@ export function clearAllRegistries(): void {
800
852
  const node = entry.node.deref();
801
853
  if (node) {
802
854
  node.$isAlive = false;
855
+ try {
856
+ globalStore.set(node.isAliveAtom, false);
857
+ } catch (e) {
858
+ // Ignore store errors during teardown
859
+ }
803
860
  }
804
861
  }
805
862
  nodeRegistry.clear();