jotai-state-tree 1.2.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-3TQNT4MR.mjs → chunk-UOYV4J7H.mjs} +42 -16
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +42 -16
- package/dist/index.mjs +1 -1
- package/dist/react.d.mts +2 -2
- package/dist/react.d.ts +2 -2
- package/dist/react.js +42 -16
- package/dist/react.mjs +1 -1
- package/dist/{tree-B2tSEN1S.d.mts → tree-BV2K9utF.d.mts} +4 -0
- package/dist/{tree-B2tSEN1S.d.ts → tree-BV2K9utF.d.ts} +4 -0
- package/package.json +1 -1
- package/src/__tests__/performance.test.ts +2 -2
- package/src/__tests__/snapshot-sharing.test.ts +179 -0
- package/src/tree.ts +47 -21
|
@@ -77,6 +77,9 @@ var StateTreeNode = class {
|
|
|
77
77
|
this.patchListeners = /* @__PURE__ */ new Set();
|
|
78
78
|
/** Volatile state (non-serialized) */
|
|
79
79
|
this.volatileState = {};
|
|
80
|
+
/** Cached snapshot for structural sharing optimization */
|
|
81
|
+
this.cachedSnapshot = void 0;
|
|
82
|
+
this.isSnapshotDirty = true;
|
|
80
83
|
this.$id = generateNodeId();
|
|
81
84
|
this.$type = type;
|
|
82
85
|
this.$env = env ?? parent?.$env;
|
|
@@ -86,6 +89,15 @@ var StateTreeNode = class {
|
|
|
86
89
|
nodeRegistry.set(this.$id, { node: new WeakRef(this), instance: null });
|
|
87
90
|
nodeFinalizationRegistry.register(this, this.$id, this);
|
|
88
91
|
}
|
|
92
|
+
invalidateSnapshot() {
|
|
93
|
+
if (!this.isSnapshotDirty) {
|
|
94
|
+
this.isSnapshotDirty = true;
|
|
95
|
+
this.cachedSnapshot = void 0;
|
|
96
|
+
if (this.$parent) {
|
|
97
|
+
this.$parent.invalidateSnapshot();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
89
101
|
/** Set the instance reference */
|
|
90
102
|
setInstance(instance) {
|
|
91
103
|
const entry = nodeRegistry.get(this.$id);
|
|
@@ -110,6 +122,9 @@ var StateTreeNode = class {
|
|
|
110
122
|
);
|
|
111
123
|
}
|
|
112
124
|
const oldValue = this.getValue();
|
|
125
|
+
if (oldValue === value) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
113
128
|
globalStore.set(this.valueAtom, value);
|
|
114
129
|
this.notifyPatch(
|
|
115
130
|
{ op: "replace", path: this.$path, value },
|
|
@@ -127,6 +142,7 @@ var StateTreeNode = class {
|
|
|
127
142
|
child.$env = child.$env ?? this.$env;
|
|
128
143
|
this.children.set(key, child);
|
|
129
144
|
lifecycleHookHandlers.runAfterAttach?.(child);
|
|
145
|
+
this.invalidateSnapshot();
|
|
130
146
|
}
|
|
131
147
|
/** Recursively update the path of a node and all its children */
|
|
132
148
|
updatePathRecursively(node, newPath) {
|
|
@@ -142,6 +158,7 @@ var StateTreeNode = class {
|
|
|
142
158
|
if (child) {
|
|
143
159
|
child.destroy();
|
|
144
160
|
this.children.delete(key);
|
|
161
|
+
this.invalidateSnapshot();
|
|
145
162
|
}
|
|
146
163
|
}
|
|
147
164
|
/** Get a child node */
|
|
@@ -204,6 +221,7 @@ var StateTreeNode = class {
|
|
|
204
221
|
}
|
|
205
222
|
/** Notify snapshot listeners */
|
|
206
223
|
notifySnapshotChange() {
|
|
224
|
+
this.invalidateSnapshot();
|
|
207
225
|
let current = this;
|
|
208
226
|
while (current) {
|
|
209
227
|
const snapshot = getSnapshotFromNode(current);
|
|
@@ -261,6 +279,7 @@ var StateTreeNode = class {
|
|
|
261
279
|
detach() {
|
|
262
280
|
if (this.$parent) {
|
|
263
281
|
lifecycleHookHandlers.runBeforeDetach?.(this);
|
|
282
|
+
const parent = this.$parent;
|
|
264
283
|
for (const [key, child] of this.$parent.children) {
|
|
265
284
|
if (child === this) {
|
|
266
285
|
this.$parent.children.delete(key);
|
|
@@ -269,6 +288,7 @@ var StateTreeNode = class {
|
|
|
269
288
|
}
|
|
270
289
|
this.$parent = null;
|
|
271
290
|
this.$path = "";
|
|
291
|
+
parent.invalidateSnapshot();
|
|
272
292
|
}
|
|
273
293
|
}
|
|
274
294
|
};
|
|
@@ -283,38 +303,44 @@ function hasStateTreeNode(instance) {
|
|
|
283
303
|
return instance !== null && typeof instance === "object" && $treenode in instance;
|
|
284
304
|
}
|
|
285
305
|
function getSnapshotFromNode(node) {
|
|
306
|
+
if (!node.isSnapshotDirty && node.cachedSnapshot !== void 0) {
|
|
307
|
+
return node.cachedSnapshot;
|
|
308
|
+
}
|
|
286
309
|
const type = node.$type;
|
|
287
310
|
const value = node.getValue();
|
|
311
|
+
let snapshot;
|
|
288
312
|
if (type._kind === "model") {
|
|
289
|
-
const
|
|
313
|
+
const modelSnapshot = {};
|
|
290
314
|
const children = node.getChildren();
|
|
291
315
|
for (const [key, childNode] of children) {
|
|
292
|
-
|
|
316
|
+
modelSnapshot[key] = getSnapshotFromNode(childNode);
|
|
293
317
|
}
|
|
294
318
|
if (node.postProcessor) {
|
|
295
|
-
|
|
319
|
+
snapshot = node.postProcessor(modelSnapshot);
|
|
320
|
+
} else {
|
|
321
|
+
snapshot = modelSnapshot;
|
|
296
322
|
}
|
|
297
|
-
|
|
298
|
-
}
|
|
299
|
-
if (type._kind === "array") {
|
|
323
|
+
} else if (type._kind === "array") {
|
|
300
324
|
const arr = value;
|
|
301
|
-
|
|
325
|
+
snapshot = arr.map((_, index) => {
|
|
302
326
|
const childNode = node.getChild(String(index));
|
|
303
327
|
return childNode ? getSnapshotFromNode(childNode) : arr[index];
|
|
304
328
|
});
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
const snapshot = {};
|
|
329
|
+
} else if (type._kind === "map") {
|
|
330
|
+
const mapSnapshot = {};
|
|
308
331
|
const children = node.getChildren();
|
|
309
332
|
for (const [key, childNode] of children) {
|
|
310
|
-
|
|
333
|
+
mapSnapshot[key] = getSnapshotFromNode(childNode);
|
|
311
334
|
}
|
|
312
|
-
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
|
|
335
|
+
snapshot = mapSnapshot;
|
|
336
|
+
} else if (type._kind === "reference") {
|
|
337
|
+
snapshot = node.identifierValue ?? value;
|
|
338
|
+
} else {
|
|
339
|
+
snapshot = value;
|
|
316
340
|
}
|
|
317
|
-
|
|
341
|
+
node.cachedSnapshot = snapshot;
|
|
342
|
+
node.isSnapshotDirty = false;
|
|
343
|
+
return snapshot;
|
|
318
344
|
}
|
|
319
345
|
function applySnapshotToNode(node, snapshot) {
|
|
320
346
|
if (!node.$isAlive) {
|
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-
|
|
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-
|
|
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';
|
|
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-
|
|
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-
|
|
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';
|
|
3
3
|
import 'jotai/vanilla/internals';
|
|
4
4
|
import 'jotai';
|
|
5
5
|
|
package/dist/index.js
CHANGED
|
@@ -508,6 +508,9 @@ var StateTreeNode = class {
|
|
|
508
508
|
this.patchListeners = /* @__PURE__ */ new Set();
|
|
509
509
|
/** Volatile state (non-serialized) */
|
|
510
510
|
this.volatileState = {};
|
|
511
|
+
/** Cached snapshot for structural sharing optimization */
|
|
512
|
+
this.cachedSnapshot = void 0;
|
|
513
|
+
this.isSnapshotDirty = true;
|
|
511
514
|
this.$id = generateNodeId();
|
|
512
515
|
this.$type = type;
|
|
513
516
|
this.$env = env ?? parent?.$env;
|
|
@@ -517,6 +520,15 @@ var StateTreeNode = class {
|
|
|
517
520
|
nodeRegistry.set(this.$id, { node: new WeakRef(this), instance: null });
|
|
518
521
|
nodeFinalizationRegistry.register(this, this.$id, this);
|
|
519
522
|
}
|
|
523
|
+
invalidateSnapshot() {
|
|
524
|
+
if (!this.isSnapshotDirty) {
|
|
525
|
+
this.isSnapshotDirty = true;
|
|
526
|
+
this.cachedSnapshot = void 0;
|
|
527
|
+
if (this.$parent) {
|
|
528
|
+
this.$parent.invalidateSnapshot();
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
520
532
|
/** Set the instance reference */
|
|
521
533
|
setInstance(instance) {
|
|
522
534
|
const entry = nodeRegistry.get(this.$id);
|
|
@@ -541,6 +553,9 @@ var StateTreeNode = class {
|
|
|
541
553
|
);
|
|
542
554
|
}
|
|
543
555
|
const oldValue = this.getValue();
|
|
556
|
+
if (oldValue === value) {
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
544
559
|
globalStore.set(this.valueAtom, value);
|
|
545
560
|
this.notifyPatch(
|
|
546
561
|
{ op: "replace", path: this.$path, value },
|
|
@@ -558,6 +573,7 @@ var StateTreeNode = class {
|
|
|
558
573
|
child.$env = child.$env ?? this.$env;
|
|
559
574
|
this.children.set(key, child);
|
|
560
575
|
lifecycleHookHandlers.runAfterAttach?.(child);
|
|
576
|
+
this.invalidateSnapshot();
|
|
561
577
|
}
|
|
562
578
|
/** Recursively update the path of a node and all its children */
|
|
563
579
|
updatePathRecursively(node, newPath) {
|
|
@@ -573,6 +589,7 @@ var StateTreeNode = class {
|
|
|
573
589
|
if (child) {
|
|
574
590
|
child.destroy();
|
|
575
591
|
this.children.delete(key);
|
|
592
|
+
this.invalidateSnapshot();
|
|
576
593
|
}
|
|
577
594
|
}
|
|
578
595
|
/** Get a child node */
|
|
@@ -635,6 +652,7 @@ var StateTreeNode = class {
|
|
|
635
652
|
}
|
|
636
653
|
/** Notify snapshot listeners */
|
|
637
654
|
notifySnapshotChange() {
|
|
655
|
+
this.invalidateSnapshot();
|
|
638
656
|
let current = this;
|
|
639
657
|
while (current) {
|
|
640
658
|
const snapshot = getSnapshotFromNode(current);
|
|
@@ -692,6 +710,7 @@ var StateTreeNode = class {
|
|
|
692
710
|
detach() {
|
|
693
711
|
if (this.$parent) {
|
|
694
712
|
lifecycleHookHandlers.runBeforeDetach?.(this);
|
|
713
|
+
const parent = this.$parent;
|
|
695
714
|
for (const [key, child] of this.$parent.children) {
|
|
696
715
|
if (child === this) {
|
|
697
716
|
this.$parent.children.delete(key);
|
|
@@ -700,6 +719,7 @@ var StateTreeNode = class {
|
|
|
700
719
|
}
|
|
701
720
|
this.$parent = null;
|
|
702
721
|
this.$path = "";
|
|
722
|
+
parent.invalidateSnapshot();
|
|
703
723
|
}
|
|
704
724
|
}
|
|
705
725
|
};
|
|
@@ -714,38 +734,44 @@ function hasStateTreeNode(instance) {
|
|
|
714
734
|
return instance !== null && typeof instance === "object" && $treenode in instance;
|
|
715
735
|
}
|
|
716
736
|
function getSnapshotFromNode(node) {
|
|
737
|
+
if (!node.isSnapshotDirty && node.cachedSnapshot !== void 0) {
|
|
738
|
+
return node.cachedSnapshot;
|
|
739
|
+
}
|
|
717
740
|
const type = node.$type;
|
|
718
741
|
const value = node.getValue();
|
|
742
|
+
let snapshot;
|
|
719
743
|
if (type._kind === "model") {
|
|
720
|
-
const
|
|
744
|
+
const modelSnapshot = {};
|
|
721
745
|
const children = node.getChildren();
|
|
722
746
|
for (const [key, childNode] of children) {
|
|
723
|
-
|
|
747
|
+
modelSnapshot[key] = getSnapshotFromNode(childNode);
|
|
724
748
|
}
|
|
725
749
|
if (node.postProcessor) {
|
|
726
|
-
|
|
750
|
+
snapshot = node.postProcessor(modelSnapshot);
|
|
751
|
+
} else {
|
|
752
|
+
snapshot = modelSnapshot;
|
|
727
753
|
}
|
|
728
|
-
|
|
729
|
-
}
|
|
730
|
-
if (type._kind === "array") {
|
|
754
|
+
} else if (type._kind === "array") {
|
|
731
755
|
const arr = value;
|
|
732
|
-
|
|
756
|
+
snapshot = arr.map((_, index) => {
|
|
733
757
|
const childNode = node.getChild(String(index));
|
|
734
758
|
return childNode ? getSnapshotFromNode(childNode) : arr[index];
|
|
735
759
|
});
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
const snapshot = {};
|
|
760
|
+
} else if (type._kind === "map") {
|
|
761
|
+
const mapSnapshot = {};
|
|
739
762
|
const children = node.getChildren();
|
|
740
763
|
for (const [key, childNode] of children) {
|
|
741
|
-
|
|
764
|
+
mapSnapshot[key] = getSnapshotFromNode(childNode);
|
|
742
765
|
}
|
|
743
|
-
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
|
|
766
|
+
snapshot = mapSnapshot;
|
|
767
|
+
} else if (type._kind === "reference") {
|
|
768
|
+
snapshot = node.identifierValue ?? value;
|
|
769
|
+
} else {
|
|
770
|
+
snapshot = value;
|
|
747
771
|
}
|
|
748
|
-
|
|
772
|
+
node.cachedSnapshot = snapshot;
|
|
773
|
+
node.isSnapshotDirty = false;
|
|
774
|
+
return snapshot;
|
|
749
775
|
}
|
|
750
776
|
function applySnapshotToNode(node, snapshot) {
|
|
751
777
|
if (!node.$isAlive) {
|
package/dist/index.mjs
CHANGED
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-
|
|
3
|
-
export { az as hasStateTreeNode } from './tree-
|
|
2
|
+
import { ag as getGlobalStore } from './tree-BV2K9utF.mjs';
|
|
3
|
+
export { az as hasStateTreeNode } from './tree-BV2K9utF.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-
|
|
3
|
-
export { az as hasStateTreeNode } from './tree-
|
|
2
|
+
import { ag as getGlobalStore } from './tree-BV2K9utF.js';
|
|
3
|
+
export { az as hasStateTreeNode } from './tree-BV2K9utF.js';
|
|
4
4
|
import 'jotai/vanilla/internals';
|
|
5
5
|
import 'jotai';
|
|
6
6
|
|
package/dist/react.js
CHANGED
|
@@ -126,6 +126,9 @@ var StateTreeNode = class {
|
|
|
126
126
|
this.patchListeners = /* @__PURE__ */ new Set();
|
|
127
127
|
/** Volatile state (non-serialized) */
|
|
128
128
|
this.volatileState = {};
|
|
129
|
+
/** Cached snapshot for structural sharing optimization */
|
|
130
|
+
this.cachedSnapshot = void 0;
|
|
131
|
+
this.isSnapshotDirty = true;
|
|
129
132
|
this.$id = generateNodeId();
|
|
130
133
|
this.$type = type;
|
|
131
134
|
this.$env = env ?? parent?.$env;
|
|
@@ -135,6 +138,15 @@ var StateTreeNode = class {
|
|
|
135
138
|
nodeRegistry.set(this.$id, { node: new WeakRef(this), instance: null });
|
|
136
139
|
nodeFinalizationRegistry.register(this, this.$id, this);
|
|
137
140
|
}
|
|
141
|
+
invalidateSnapshot() {
|
|
142
|
+
if (!this.isSnapshotDirty) {
|
|
143
|
+
this.isSnapshotDirty = true;
|
|
144
|
+
this.cachedSnapshot = void 0;
|
|
145
|
+
if (this.$parent) {
|
|
146
|
+
this.$parent.invalidateSnapshot();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
138
150
|
/** Set the instance reference */
|
|
139
151
|
setInstance(instance) {
|
|
140
152
|
const entry = nodeRegistry.get(this.$id);
|
|
@@ -159,6 +171,9 @@ var StateTreeNode = class {
|
|
|
159
171
|
);
|
|
160
172
|
}
|
|
161
173
|
const oldValue = this.getValue();
|
|
174
|
+
if (oldValue === value) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
162
177
|
globalStore.set(this.valueAtom, value);
|
|
163
178
|
this.notifyPatch(
|
|
164
179
|
{ op: "replace", path: this.$path, value },
|
|
@@ -176,6 +191,7 @@ var StateTreeNode = class {
|
|
|
176
191
|
child.$env = child.$env ?? this.$env;
|
|
177
192
|
this.children.set(key, child);
|
|
178
193
|
lifecycleHookHandlers.runAfterAttach?.(child);
|
|
194
|
+
this.invalidateSnapshot();
|
|
179
195
|
}
|
|
180
196
|
/** Recursively update the path of a node and all its children */
|
|
181
197
|
updatePathRecursively(node, newPath) {
|
|
@@ -191,6 +207,7 @@ var StateTreeNode = class {
|
|
|
191
207
|
if (child) {
|
|
192
208
|
child.destroy();
|
|
193
209
|
this.children.delete(key);
|
|
210
|
+
this.invalidateSnapshot();
|
|
194
211
|
}
|
|
195
212
|
}
|
|
196
213
|
/** Get a child node */
|
|
@@ -253,6 +270,7 @@ var StateTreeNode = class {
|
|
|
253
270
|
}
|
|
254
271
|
/** Notify snapshot listeners */
|
|
255
272
|
notifySnapshotChange() {
|
|
273
|
+
this.invalidateSnapshot();
|
|
256
274
|
let current = this;
|
|
257
275
|
while (current) {
|
|
258
276
|
const snapshot = getSnapshotFromNode(current);
|
|
@@ -310,6 +328,7 @@ var StateTreeNode = class {
|
|
|
310
328
|
detach() {
|
|
311
329
|
if (this.$parent) {
|
|
312
330
|
lifecycleHookHandlers.runBeforeDetach?.(this);
|
|
331
|
+
const parent = this.$parent;
|
|
313
332
|
for (const [key, child] of this.$parent.children) {
|
|
314
333
|
if (child === this) {
|
|
315
334
|
this.$parent.children.delete(key);
|
|
@@ -318,6 +337,7 @@ var StateTreeNode = class {
|
|
|
318
337
|
}
|
|
319
338
|
this.$parent = null;
|
|
320
339
|
this.$path = "";
|
|
340
|
+
parent.invalidateSnapshot();
|
|
321
341
|
}
|
|
322
342
|
}
|
|
323
343
|
};
|
|
@@ -332,38 +352,44 @@ function hasStateTreeNode(instance) {
|
|
|
332
352
|
return instance !== null && typeof instance === "object" && $treenode in instance;
|
|
333
353
|
}
|
|
334
354
|
function getSnapshotFromNode(node) {
|
|
355
|
+
if (!node.isSnapshotDirty && node.cachedSnapshot !== void 0) {
|
|
356
|
+
return node.cachedSnapshot;
|
|
357
|
+
}
|
|
335
358
|
const type = node.$type;
|
|
336
359
|
const value = node.getValue();
|
|
360
|
+
let snapshot;
|
|
337
361
|
if (type._kind === "model") {
|
|
338
|
-
const
|
|
362
|
+
const modelSnapshot = {};
|
|
339
363
|
const children = node.getChildren();
|
|
340
364
|
for (const [key, childNode] of children) {
|
|
341
|
-
|
|
365
|
+
modelSnapshot[key] = getSnapshotFromNode(childNode);
|
|
342
366
|
}
|
|
343
367
|
if (node.postProcessor) {
|
|
344
|
-
|
|
368
|
+
snapshot = node.postProcessor(modelSnapshot);
|
|
369
|
+
} else {
|
|
370
|
+
snapshot = modelSnapshot;
|
|
345
371
|
}
|
|
346
|
-
|
|
347
|
-
}
|
|
348
|
-
if (type._kind === "array") {
|
|
372
|
+
} else if (type._kind === "array") {
|
|
349
373
|
const arr = value;
|
|
350
|
-
|
|
374
|
+
snapshot = arr.map((_, index) => {
|
|
351
375
|
const childNode = node.getChild(String(index));
|
|
352
376
|
return childNode ? getSnapshotFromNode(childNode) : arr[index];
|
|
353
377
|
});
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
const snapshot = {};
|
|
378
|
+
} else if (type._kind === "map") {
|
|
379
|
+
const mapSnapshot = {};
|
|
357
380
|
const children = node.getChildren();
|
|
358
381
|
for (const [key, childNode] of children) {
|
|
359
|
-
|
|
382
|
+
mapSnapshot[key] = getSnapshotFromNode(childNode);
|
|
360
383
|
}
|
|
361
|
-
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
|
|
384
|
+
snapshot = mapSnapshot;
|
|
385
|
+
} else if (type._kind === "reference") {
|
|
386
|
+
snapshot = node.identifierValue ?? value;
|
|
387
|
+
} else {
|
|
388
|
+
snapshot = value;
|
|
365
389
|
}
|
|
366
|
-
|
|
390
|
+
node.cachedSnapshot = snapshot;
|
|
391
|
+
node.isSnapshotDirty = false;
|
|
392
|
+
return snapshot;
|
|
367
393
|
}
|
|
368
394
|
function getSnapshot(target) {
|
|
369
395
|
const node = getStateTreeNode(target);
|
package/dist/react.mjs
CHANGED
|
@@ -343,6 +343,10 @@ declare class StateTreeNode implements IStateTreeNode {
|
|
|
343
343
|
identifierValue?: string | number;
|
|
344
344
|
/** Type name for identifier registry */
|
|
345
345
|
identifierTypeName?: string;
|
|
346
|
+
/** Cached snapshot for structural sharing optimization */
|
|
347
|
+
cachedSnapshot: unknown;
|
|
348
|
+
isSnapshotDirty: boolean;
|
|
349
|
+
invalidateSnapshot(): void;
|
|
346
350
|
constructor(type: IAnyType, initialValue: unknown, env?: unknown, parent?: StateTreeNode, pathSegment?: string);
|
|
347
351
|
/** Set the instance reference */
|
|
348
352
|
setInstance(instance: unknown): void;
|
|
@@ -343,6 +343,10 @@ declare class StateTreeNode implements IStateTreeNode {
|
|
|
343
343
|
identifierValue?: string | number;
|
|
344
344
|
/** Type name for identifier registry */
|
|
345
345
|
identifierTypeName?: string;
|
|
346
|
+
/** Cached snapshot for structural sharing optimization */
|
|
347
|
+
cachedSnapshot: unknown;
|
|
348
|
+
isSnapshotDirty: boolean;
|
|
349
|
+
invalidateSnapshot(): void;
|
|
346
350
|
constructor(type: IAnyType, initialValue: unknown, env?: unknown, parent?: StateTreeNode, pathSegment?: string);
|
|
347
351
|
/** Set the instance reference */
|
|
348
352
|
setInstance(instance: unknown): void;
|
package/package.json
CHANGED
|
@@ -270,7 +270,7 @@ describe("Performance", () => {
|
|
|
270
270
|
},
|
|
271
271
|
}));
|
|
272
272
|
|
|
273
|
-
const instance = Model.create({ value:
|
|
273
|
+
const instance = Model.create({ value: -1 });
|
|
274
274
|
|
|
275
275
|
// Add many listeners
|
|
276
276
|
const disposers: (() => void)[] = [];
|
|
@@ -312,7 +312,7 @@ describe("Performance", () => {
|
|
|
312
312
|
},
|
|
313
313
|
}));
|
|
314
314
|
|
|
315
|
-
const instance = Model.create({ value:
|
|
315
|
+
const instance = Model.create({ value: -1 });
|
|
316
316
|
|
|
317
317
|
const disposers: (() => void)[] = [];
|
|
318
318
|
let patchCount = 0;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { types, getSnapshot, applySnapshot } from "../index";
|
|
3
|
+
|
|
4
|
+
describe("Snapshot Structural Sharing", () => {
|
|
5
|
+
it("should reuse snapshot references if nothing changes", () => {
|
|
6
|
+
const Child = types.model("Child", {
|
|
7
|
+
id: types.identifier,
|
|
8
|
+
value: types.string,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const Root = types.model("Root", {
|
|
12
|
+
child1: Child,
|
|
13
|
+
child2: Child,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const root = Root.create({
|
|
17
|
+
child1: { id: "1", value: "A" },
|
|
18
|
+
child2: { id: "2", value: "B" },
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const snap1 = getSnapshot<any>(root);
|
|
22
|
+
const snap2 = getSnapshot<any>(root);
|
|
23
|
+
|
|
24
|
+
// If no changes occurred, snapshot references should be identical
|
|
25
|
+
expect(snap1).toBe(snap2);
|
|
26
|
+
expect(snap1.child1).toBe(snap2.child1);
|
|
27
|
+
expect(snap1.child2).toBe(snap2.child2);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("should share unmodified branch references when a sub-branch changes", () => {
|
|
31
|
+
const Child = types
|
|
32
|
+
.model("Child", {
|
|
33
|
+
id: types.identifier,
|
|
34
|
+
value: types.string,
|
|
35
|
+
})
|
|
36
|
+
.actions((self) => ({
|
|
37
|
+
setValue(val: string) {
|
|
38
|
+
self.value = val;
|
|
39
|
+
},
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
const Root = types.model("Root", {
|
|
43
|
+
child1: Child,
|
|
44
|
+
child2: Child,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const root = Root.create({
|
|
48
|
+
child1: { id: "1", value: "A" },
|
|
49
|
+
child2: { id: "2", value: "B" },
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const snap1 = getSnapshot<any>(root);
|
|
53
|
+
|
|
54
|
+
// Modify child1
|
|
55
|
+
root.child1.setValue("A-modified");
|
|
56
|
+
|
|
57
|
+
const snap2 = getSnapshot<any>(root);
|
|
58
|
+
|
|
59
|
+
// The root and child1 snapshots should be new objects
|
|
60
|
+
expect(snap2).not.toBe(snap1);
|
|
61
|
+
expect(snap2.child1).not.toBe(snap1.child1);
|
|
62
|
+
expect(snap2.child1.value).toBe("A-modified");
|
|
63
|
+
|
|
64
|
+
// The child2 snapshot should be exactly the same object reference!
|
|
65
|
+
expect(snap2.child2).toBe(snap1.child2);
|
|
66
|
+
expect(snap2.child2.value).toBe("B");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("should perform structural sharing with arrays", () => {
|
|
70
|
+
const Item = types
|
|
71
|
+
.model("Item", {
|
|
72
|
+
id: types.identifier,
|
|
73
|
+
name: types.string,
|
|
74
|
+
})
|
|
75
|
+
.actions((self) => ({
|
|
76
|
+
setName(name: string) {
|
|
77
|
+
self.name = name;
|
|
78
|
+
},
|
|
79
|
+
}));
|
|
80
|
+
|
|
81
|
+
const Root = types.model("Root", {
|
|
82
|
+
items: types.array(Item),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const root = Root.create({
|
|
86
|
+
items: [
|
|
87
|
+
{ id: "1", name: "Item 1" },
|
|
88
|
+
{ id: "2", name: "Item 2" },
|
|
89
|
+
],
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const snap1 = getSnapshot<any>(root);
|
|
93
|
+
|
|
94
|
+
// Modify Item 2
|
|
95
|
+
root.items[1].setName("Item 2-modified");
|
|
96
|
+
|
|
97
|
+
const snap2 = getSnapshot<any>(root);
|
|
98
|
+
|
|
99
|
+
expect(snap2).not.toBe(snap1);
|
|
100
|
+
expect(snap2.items).not.toBe(snap1.items);
|
|
101
|
+
|
|
102
|
+
// Unmodified Item 1 snapshot should have identical reference
|
|
103
|
+
expect(snap2.items[0]).toBe(snap1.items[0]);
|
|
104
|
+
// Modified Item 2 snapshot should have a new reference
|
|
105
|
+
expect(snap2.items[1]).not.toBe(snap1.items[1]);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("should perform structural sharing with maps", () => {
|
|
109
|
+
const Item = types
|
|
110
|
+
.model("Item", {
|
|
111
|
+
id: types.identifier,
|
|
112
|
+
name: types.string,
|
|
113
|
+
})
|
|
114
|
+
.actions((self) => ({
|
|
115
|
+
setName(name: string) {
|
|
116
|
+
self.name = name;
|
|
117
|
+
},
|
|
118
|
+
}));
|
|
119
|
+
|
|
120
|
+
const Root = types.model("Root", {
|
|
121
|
+
items: types.map(Item),
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const root = Root.create({
|
|
125
|
+
items: {
|
|
126
|
+
a: { id: "1", name: "Item A" },
|
|
127
|
+
b: { id: "2", name: "Item B" },
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const snap1 = getSnapshot<any>(root);
|
|
132
|
+
|
|
133
|
+
// Modify Item B
|
|
134
|
+
root.items.get("b")!.setName("Item B-modified");
|
|
135
|
+
|
|
136
|
+
const snap2 = getSnapshot<any>(root);
|
|
137
|
+
|
|
138
|
+
expect(snap2).not.toBe(snap1);
|
|
139
|
+
expect(snap2.items).not.toBe(snap1.items);
|
|
140
|
+
|
|
141
|
+
// Unmodified Item A snapshot should have identical reference
|
|
142
|
+
expect(snap2.items.a).toBe(snap1.items.a);
|
|
143
|
+
// Modified Item B snapshot should have a new reference
|
|
144
|
+
expect(snap2.items.b).not.toBe(snap1.items.b);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("should handle applySnapshot and correctly update caching", () => {
|
|
148
|
+
const Child = types.model("Child", {
|
|
149
|
+
value: types.string,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const Root = types.model("Root", {
|
|
153
|
+
child1: Child,
|
|
154
|
+
child2: Child,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const root = Root.create({
|
|
158
|
+
child1: { value: "A" },
|
|
159
|
+
child2: { value: "B" },
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const snap1 = getSnapshot<any>(root);
|
|
163
|
+
|
|
164
|
+
// Apply snapshot updating only child1
|
|
165
|
+
applySnapshot(root, {
|
|
166
|
+
child1: { value: "A-updated" },
|
|
167
|
+
child2: { value: "B" },
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const snap2 = getSnapshot<any>(root);
|
|
171
|
+
|
|
172
|
+
expect(snap2).not.toBe(snap1);
|
|
173
|
+
expect(snap2.child1).not.toBe(snap1.child1);
|
|
174
|
+
expect(snap2.child1.value).toBe("A-updated");
|
|
175
|
+
|
|
176
|
+
// child2 was not modified, so it should keep the same reference
|
|
177
|
+
expect(snap2.child2).toBe(snap1.child2);
|
|
178
|
+
});
|
|
179
|
+
});
|
package/src/tree.ts
CHANGED
|
@@ -202,6 +202,20 @@ export class StateTreeNode implements IStateTreeNode {
|
|
|
202
202
|
/** Type name for identifier registry */
|
|
203
203
|
identifierTypeName?: string;
|
|
204
204
|
|
|
205
|
+
/** Cached snapshot for structural sharing optimization */
|
|
206
|
+
cachedSnapshot: unknown = undefined;
|
|
207
|
+
isSnapshotDirty = true;
|
|
208
|
+
|
|
209
|
+
invalidateSnapshot() {
|
|
210
|
+
if (!this.isSnapshotDirty) {
|
|
211
|
+
this.isSnapshotDirty = true;
|
|
212
|
+
this.cachedSnapshot = undefined;
|
|
213
|
+
if (this.$parent) {
|
|
214
|
+
this.$parent.invalidateSnapshot();
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
205
219
|
constructor(
|
|
206
220
|
type: IAnyType,
|
|
207
221
|
initialValue: unknown,
|
|
@@ -254,6 +268,9 @@ export class StateTreeNode implements IStateTreeNode {
|
|
|
254
268
|
}
|
|
255
269
|
|
|
256
270
|
const oldValue = this.getValue();
|
|
271
|
+
if (oldValue === value) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
257
274
|
globalStore.set(this.valueAtom, value);
|
|
258
275
|
|
|
259
276
|
// Notify patch listeners
|
|
@@ -276,6 +293,7 @@ export class StateTreeNode implements IStateTreeNode {
|
|
|
276
293
|
child.$env = child.$env ?? this.$env;
|
|
277
294
|
this.children.set(key, child);
|
|
278
295
|
lifecycleHookHandlers.runAfterAttach?.(child);
|
|
296
|
+
this.invalidateSnapshot();
|
|
279
297
|
}
|
|
280
298
|
|
|
281
299
|
/** Recursively update the path of a node and all its children */
|
|
@@ -295,6 +313,7 @@ export class StateTreeNode implements IStateTreeNode {
|
|
|
295
313
|
if (child) {
|
|
296
314
|
child.destroy();
|
|
297
315
|
this.children.delete(key);
|
|
316
|
+
this.invalidateSnapshot();
|
|
298
317
|
}
|
|
299
318
|
}
|
|
300
319
|
|
|
@@ -376,6 +395,7 @@ export class StateTreeNode implements IStateTreeNode {
|
|
|
376
395
|
|
|
377
396
|
/** Notify snapshot listeners */
|
|
378
397
|
private notifySnapshotChange() {
|
|
398
|
+
this.invalidateSnapshot();
|
|
379
399
|
let current: StateTreeNode | null = this;
|
|
380
400
|
while (current) {
|
|
381
401
|
const snapshot = getSnapshotFromNode(current);
|
|
@@ -464,6 +484,7 @@ export class StateTreeNode implements IStateTreeNode {
|
|
|
464
484
|
// Run beforeDetach hook
|
|
465
485
|
lifecycleHookHandlers.runBeforeDetach?.(this);
|
|
466
486
|
|
|
487
|
+
const parent = this.$parent;
|
|
467
488
|
// Find our key in parent's children
|
|
468
489
|
for (const [key, child] of this.$parent.children) {
|
|
469
490
|
if (child === this) {
|
|
@@ -473,6 +494,7 @@ export class StateTreeNode implements IStateTreeNode {
|
|
|
473
494
|
}
|
|
474
495
|
this.$parent = null;
|
|
475
496
|
this.$path = "";
|
|
497
|
+
parent.invalidateSnapshot();
|
|
476
498
|
}
|
|
477
499
|
}
|
|
478
500
|
}
|
|
@@ -501,50 +523,54 @@ export function hasStateTreeNode(instance: unknown): boolean {
|
|
|
501
523
|
|
|
502
524
|
/** Get snapshot from a node */
|
|
503
525
|
export function getSnapshotFromNode(node: StateTreeNode): unknown {
|
|
526
|
+
if (!node.isSnapshotDirty && node.cachedSnapshot !== undefined) {
|
|
527
|
+
return node.cachedSnapshot;
|
|
528
|
+
}
|
|
529
|
+
|
|
504
530
|
const type = node.$type;
|
|
505
531
|
const value = node.getValue();
|
|
532
|
+
let snapshot: unknown;
|
|
506
533
|
|
|
507
534
|
// Handle based on type kind
|
|
508
535
|
if (type._kind === "model") {
|
|
509
|
-
const
|
|
536
|
+
const modelSnapshot: Record<string, unknown> = {};
|
|
510
537
|
const children = node.getChildren();
|
|
511
538
|
|
|
512
539
|
for (const [key, childNode] of children) {
|
|
513
|
-
|
|
540
|
+
modelSnapshot[key] = getSnapshotFromNode(childNode);
|
|
514
541
|
}
|
|
515
542
|
|
|
516
543
|
// Apply post processor if exists
|
|
517
544
|
if (node.postProcessor) {
|
|
518
|
-
|
|
545
|
+
snapshot = node.postProcessor(modelSnapshot);
|
|
546
|
+
} else {
|
|
547
|
+
snapshot = modelSnapshot;
|
|
519
548
|
}
|
|
520
|
-
|
|
521
|
-
return snapshot;
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
if (type._kind === "array") {
|
|
549
|
+
} else if (type._kind === "array") {
|
|
525
550
|
const arr = value as unknown[];
|
|
526
|
-
|
|
551
|
+
snapshot = arr.map((_, index) => {
|
|
527
552
|
const childNode = node.getChild(String(index));
|
|
528
553
|
return childNode ? getSnapshotFromNode(childNode) : arr[index];
|
|
529
554
|
});
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
if (type._kind === "map") {
|
|
533
|
-
const snapshot: Record<string, unknown> = {};
|
|
555
|
+
} else if (type._kind === "map") {
|
|
556
|
+
const mapSnapshot: Record<string, unknown> = {};
|
|
534
557
|
const children = node.getChildren();
|
|
535
558
|
for (const [key, childNode] of children) {
|
|
536
|
-
|
|
559
|
+
mapSnapshot[key] = getSnapshotFromNode(childNode);
|
|
537
560
|
}
|
|
538
|
-
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
if (type._kind === "reference") {
|
|
561
|
+
snapshot = mapSnapshot;
|
|
562
|
+
} else if (type._kind === "reference") {
|
|
542
563
|
// Return the identifier, not the resolved value
|
|
543
|
-
|
|
564
|
+
snapshot = node.identifierValue ?? value;
|
|
565
|
+
} else {
|
|
566
|
+
// For primitives and frozen, return the value directly
|
|
567
|
+
snapshot = value;
|
|
544
568
|
}
|
|
545
569
|
|
|
546
|
-
|
|
547
|
-
|
|
570
|
+
node.cachedSnapshot = snapshot;
|
|
571
|
+
node.isSnapshotDirty = false;
|
|
572
|
+
|
|
573
|
+
return snapshot;
|
|
548
574
|
}
|
|
549
575
|
|
|
550
576
|
/** Apply snapshot to a node */
|