jotai-state-tree 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 snapshot = {};
313
+ const modelSnapshot = {};
290
314
  const children = node.getChildren();
291
315
  for (const [key, childNode] of children) {
292
- snapshot[key] = getSnapshotFromNode(childNode);
316
+ modelSnapshot[key] = getSnapshotFromNode(childNode);
293
317
  }
294
318
  if (node.postProcessor) {
295
- return node.postProcessor(snapshot);
319
+ snapshot = node.postProcessor(modelSnapshot);
320
+ } else {
321
+ snapshot = modelSnapshot;
296
322
  }
297
- return snapshot;
298
- }
299
- if (type._kind === "array") {
323
+ } else if (type._kind === "array") {
300
324
  const arr = value;
301
- return arr.map((_, index) => {
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
- if (type._kind === "map") {
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
- snapshot[key] = getSnapshotFromNode(childNode);
333
+ mapSnapshot[key] = getSnapshotFromNode(childNode);
311
334
  }
312
- return snapshot;
313
- }
314
- if (type._kind === "reference") {
315
- return node.identifierValue ?? value;
335
+ snapshot = mapSnapshot;
336
+ } else if (type._kind === "reference") {
337
+ snapshot = node.identifierValue ?? value;
338
+ } else {
339
+ snapshot = value;
316
340
  }
317
- return value;
341
+ node.cachedSnapshot = snapshot;
342
+ node.isSnapshotDirty = false;
343
+ return snapshot;
318
344
  }
319
345
  function applySnapshotToNode(node, snapshot) {
320
346
  if (!node.$isAlive) {
@@ -591,6 +617,9 @@ function recordPatches(target) {
591
617
  };
592
618
  }
593
619
  var currentAction = null;
620
+ function getCurrentAction() {
621
+ return currentAction;
622
+ }
594
623
  var actionListeners = /* @__PURE__ */ new Set();
595
624
  var actionRecorderHooks = [];
596
625
  function registerActionRecorderHook(hook) {
@@ -856,6 +885,7 @@ export {
856
885
  onPatch,
857
886
  applyPatch,
858
887
  recordPatches,
888
+ getCurrentAction,
859
889
  registerActionRecorderHook,
860
890
  isActionRunning,
861
891
  trackAction,
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-B2tSEN1S.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-B2tSEN1S.mjs';
1
+ import { I as ISimpleType, a as IType, b as IIdentifierType, c as IIdentifierNumberType, d as ILiteralType, e as IEnumerationType, f as IFrozenType, M as ModelProperties, g as IModelType, h as MixinConfig, i as IMixin, j as IAnyType, k as IArrayType, l as IMapType, m as IOptionalType, n as IMaybeType, o as IMaybeNullType, p as IUnionType, U as UnionOptions, q as ILateType, r as IAnyModelType, R as ReferenceOptions, s as IReferenceType, t as ISafeReferenceType, u as IRefinementType, v as IDisposer, S as SnapshotIn, w as Instance, x as IReversibleJsonPatch } from './tree-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
 
@@ -362,9 +362,14 @@ interface IMiddlewareHandler {
362
362
  (call: IMiddlewareEvent, next: (call: IMiddlewareEvent, callback?: (value: unknown) => unknown) => unknown, abort: (value: unknown) => unknown): unknown;
363
363
  }
364
364
  /**
365
- * Add middleware to the global stack
365
+ * Add middleware to the stack/node
366
366
  */
367
367
  declare function addMiddleware(target: unknown, handler: IMiddlewareHandler, includeHooks?: boolean): IDisposer;
368
+ /**
369
+ * Creates an async action (generator function) that can be yielded.
370
+ * Compatible with MST's flow().
371
+ */
372
+ declare function flow<Args extends unknown[], R>(generator: (...args: Args) => Generator<Promise<unknown>, R, unknown>): (...args: Args) => Promise<R>;
368
373
  interface ISerializedActionCall {
369
374
  name: string;
370
375
  path: string;
@@ -646,11 +651,6 @@ declare const types: {
646
651
  safeDynamicReference: typeof safeDynamicReference;
647
652
  };
648
653
 
649
- /**
650
- * Creates an async action (generator function) that can be yielded.
651
- * Compatible with MST's flow().
652
- */
653
- declare function flow<Args extends unknown[], R>(generator: (...args: Args) => Generator<Promise<unknown>, R, unknown>): (...args: Args) => Promise<R>;
654
654
  /**
655
655
  * Cast a value to a different type.
656
656
  * Useful for working around TypeScript limitations.
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-B2tSEN1S.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-B2tSEN1S.js';
1
+ import { I as ISimpleType, a as IType, b as IIdentifierType, c as IIdentifierNumberType, d as ILiteralType, e as IEnumerationType, f as IFrozenType, M as ModelProperties, g as IModelType, h as MixinConfig, i as IMixin, j as IAnyType, k as IArrayType, l as IMapType, m as IOptionalType, n as IMaybeType, o as IMaybeNullType, p as IUnionType, U as UnionOptions, q as ILateType, r as IAnyModelType, R as ReferenceOptions, s as IReferenceType, t as ISafeReferenceType, u as IRefinementType, v as IDisposer, S as SnapshotIn, w as Instance, x as IReversibleJsonPatch } from './tree-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
 
@@ -362,9 +362,14 @@ interface IMiddlewareHandler {
362
362
  (call: IMiddlewareEvent, next: (call: IMiddlewareEvent, callback?: (value: unknown) => unknown) => unknown, abort: (value: unknown) => unknown): unknown;
363
363
  }
364
364
  /**
365
- * Add middleware to the global stack
365
+ * Add middleware to the stack/node
366
366
  */
367
367
  declare function addMiddleware(target: unknown, handler: IMiddlewareHandler, includeHooks?: boolean): IDisposer;
368
+ /**
369
+ * Creates an async action (generator function) that can be yielded.
370
+ * Compatible with MST's flow().
371
+ */
372
+ declare function flow<Args extends unknown[], R>(generator: (...args: Args) => Generator<Promise<unknown>, R, unknown>): (...args: Args) => Promise<R>;
368
373
  interface ISerializedActionCall {
369
374
  name: string;
370
375
  path: string;
@@ -646,11 +651,6 @@ declare const types: {
646
651
  safeDynamicReference: typeof safeDynamicReference;
647
652
  };
648
653
 
649
- /**
650
- * Creates an async action (generator function) that can be yielded.
651
- * Compatible with MST's flow().
652
- */
653
- declare function flow<Args extends unknown[], R>(generator: (...args: Args) => Generator<Promise<unknown>, R, unknown>): (...args: Args) => Promise<R>;
654
654
  /**
655
655
  * Cast a value to a different type.
656
656
  * Useful for working around TypeScript limitations.