r-state-tree 0.8.1 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -205,7 +205,7 @@ class ItemsStore extends Store {
205
205
 
206
206
  ### Store lifetime and owned effects
207
207
 
208
- Mounted stores expose reactive `isMounted` state and implement `Disposable`:
208
+ Stores implement `Disposable`, and their owned effects run only during the mounted lifetime:
209
209
 
210
210
  ```ts
211
211
  class TodoStore extends Store {
@@ -242,9 +242,9 @@ class TodoStore extends Store {
242
242
  }
243
243
  ```
244
244
 
245
- `Store.effect()` and `Store.reaction()` register mount-scoped behavior. Mounting first links the complete store tree and marks it mounted bottom-up in one transaction. Registrations then activate bottom-up, so even a grandchild's initial effect observes every ancestor and descendant as mounted. A reaction establishes its initial value at activation and still skips its initial callback. Calling the disposer returned during construction cancels the pending registration; after mount, it disposes the live subscription. All registrations are disposed automatically with the store.
245
+ `Store.effect()` and `Store.reaction()` register mount-scoped behavior. Mounting first links the complete store tree and commits its private mounted state bottom-up in one transaction. Registrations then activate bottom-up, so even a grandchild's initial effect sees a complete tree. A reaction establishes its initial value at activation and still skips its initial callback. Calling the disposer returned during construction cancels the pending registration; after mount, it disposes the live subscription. All registrations are disposed automatically with the store.
246
246
 
247
- Use the exported standalone `effect()` or `reaction()` when observing a store from outside its owned lifetime—for example, to observe the public reactive `isMounted` transition to `false`.
247
+ Registration activation is non-reentrant. If an active effect or reaction lazily materializes another child store, the new child's registrations are queued until the current callback and child getter stack have returned. This prevents initialization behavior from running inside the getter that created the store.
248
248
 
249
249
  ### Context
250
250
 
@@ -878,23 +878,19 @@ This keeps snapshot hydration predictable: `create(snapshot)` and `applySnapshot
878
878
 
879
879
  ### Model attachment and disposal
880
880
 
881
- The public `parent` relationship is reactive. Register a model-owned reaction in the constructor to observe attachment and detachment:
881
+ Models are inert state trees: creating or attaching one does not start reactions or effects. The public `parent` relationship remains reactive, so an external owner may observe it explicitly when needed:
882
882
 
883
883
  ```ts
884
- class TodoModel extends Model {
885
- @child tags: TagModel[] = [];
886
-
887
- constructor() {
888
- super();
889
- this.reaction(() => this.parent, (parent, previousParent) => {
890
- if (previousParent) console.log("detached", previousParent);
891
- if (parent) console.log("attached", parent);
892
- });
884
+ const stop = reaction(
885
+ () => todo.parent,
886
+ (parent, previousParent) => {
887
+ if (previousParent) console.log("detached", previousParent);
888
+ if (parent) console.log("attached", parent);
893
889
  }
894
- }
890
+ );
895
891
  ```
896
892
 
897
- Detachment is reversible; model-owned effects survive reattachment. `model[Symbol.dispose]()` is terminal and recursively disposes owned child models, but never model refs.
893
+ The external caller owns `stop`. Detachment is reversible. `model[Symbol.dispose]()` is terminal and recursively disposes owned child models, but never model refs.
898
894
 
899
895
  ### Model configuration
900
896
 
@@ -1207,15 +1203,9 @@ class ListModel extends Model {
1207
1203
  }
1208
1204
  ```
1209
1205
 
1210
- Lifecycle registration:
1206
+ Store lifecycle registration:
1211
1207
 
1212
1208
  ```ts
1213
- class M extends Model {
1214
- constructor() {
1215
- super();
1216
- this.reaction(() => this.parent, (parent, previousParent) => {});
1217
- }
1218
- }
1219
1209
  class S extends Store {
1220
1210
  constructor(props) {
1221
1211
  super(props);
@@ -1284,7 +1274,7 @@ When child stores are created during mount with `models` that point back into th
1284
1274
  - Decorators (Stores): `@child`, `@model`
1285
1275
  - Core: `createStore`, `mount`, `Symbol.dispose`, `updateStore`
1286
1276
  - Snapshots: `onSnapshot`, `toSnapshot`, `applySnapshot`, `onSnapshotDiff`
1287
- - Lifecycle: reactive `Store.isMounted`, reactive `Model.parent`, and owned `reaction`/`effect`
1277
+ - Lifecycle: Store-owned `reaction`/`effect`, reactive `Model.parent`, and `Symbol.dispose`
1288
1278
  - Best practices: domain in Models; delegate from Stores; stable keys for `@child`; return cleanup from store effects; don’t shadow `props`.
1289
1279
 
1290
1280
  ## Testing
@@ -7,7 +7,5 @@ export default class Model implements Disposable {
7
7
  }, snapshot?: Snapshot<T>): T;
8
8
  constructor();
9
9
  get parent(): Model | null;
10
- reaction<T>(track: () => T, callback: (value: T, previousValue: T) => void): () => void;
11
- effect(callback: () => void | (() => void)): () => void;
12
10
  [Symbol.dispose](): void;
13
11
  }
@@ -20,16 +20,12 @@ export declare class ModelAdministration extends PreactObjectAdministration<any>
20
20
  private computedSnapshot;
21
21
  private snapshotMap;
22
22
  private contextCache;
23
- private ownedDisposers;
24
23
  private disposed;
25
24
  parentName: PropertyKey | null;
26
25
  get parent(): ModelAdministration | null;
27
26
  set parent(value: ModelAdministration | null);
28
27
  setConfiguration(configurationGetter: () => ModelConfiguration<any>): void;
29
28
  assertUsable(): void;
30
- private own;
31
- reaction<T>(track: () => T, callback: (value: T, previousValue: T) => void): () => void;
32
- effect(callback: () => void | (() => void)): () => void;
33
29
  private get configuration();
34
30
  private getReferencedAtom;
35
31
  private setState;
@@ -1993,6 +1993,32 @@ function validateStoreChildValue(value, propertyName) {
1993
1993
  );
1994
1994
  }
1995
1995
  class StoreAdministration extends PreactObjectAdministration {
1996
+ static pendingActivations = [];
1997
+ static isFlushingActivations = false;
1998
+ static currentActivationDepth = 0;
1999
+ static flushReactiveRegistrations(stores) {
2000
+ const depth = this.isFlushingActivations ? this.currentActivationDepth + 1 : 0;
2001
+ this.pendingActivations.push(...stores.map((store) => ({ store, depth })));
2002
+ if (this.isFlushingActivations) return;
2003
+ this.isFlushingActivations = true;
2004
+ try {
2005
+ let entry;
2006
+ while (entry = this.pendingActivations.shift()) {
2007
+ this.currentActivationDepth = entry.depth;
2008
+ if (entry.depth > MAX_MOUNT_DEPTH) {
2009
+ throw createCircularMountError({
2010
+ storeName: entry.store.proxy.constructor.name || "Store",
2011
+ modelsKeys: Object.keys(entry.store.proxy.props?.models ?? {})
2012
+ });
2013
+ }
2014
+ entry.store.startReactiveRegistrations();
2015
+ }
2016
+ } finally {
2017
+ this.pendingActivations.length = 0;
2018
+ this.currentActivationDepth = 0;
2019
+ this.isFlushingActivations = false;
2020
+ }
2021
+ }
1996
2022
  static proxyTraps = Object.assign(
1997
2023
  {},
1998
2024
  PreactObjectAdministration.proxyTraps,
@@ -2032,7 +2058,7 @@ class StoreAdministration extends PreactObjectAdministration {
2032
2058
  }
2033
2059
  );
2034
2060
  parent = null;
2035
- mountedSignal = createSignal(false);
2061
+ mounted = false;
2036
2062
  disposed = false;
2037
2063
  contextCache = /* @__PURE__ */ new Map();
2038
2064
  childStoreDataMap = /* @__PURE__ */ new Map();
@@ -2194,7 +2220,7 @@ class StoreAdministration extends PreactObjectAdministration {
2194
2220
  return !this.parent;
2195
2221
  }
2196
2222
  get isMounted() {
2197
- return this.mountedSignal.get();
2223
+ return this.mounted;
2198
2224
  }
2199
2225
  getContextValue(contextId, provideSymbol, defaultValue, hasDefault) {
2200
2226
  let computed2 = this.contextCache.get(contextId);
@@ -2290,13 +2316,11 @@ class StoreAdministration extends PreactObjectAdministration {
2290
2316
  getStoreAdm(stores)?.mount(this, name, registrationsToStart);
2291
2317
  }
2292
2318
  });
2293
- this.mountedSignal.set(true);
2319
+ this.mounted = true;
2294
2320
  registrationsToStart.push(this);
2295
2321
  });
2296
2322
  if (isOutermostMount) {
2297
- registrationsToStart.forEach(
2298
- (store) => store.startReactiveRegistrations()
2299
- );
2323
+ StoreAdministration.flushReactiveRegistrations(registrationsToStart);
2300
2324
  }
2301
2325
  } catch (error) {
2302
2326
  if (error instanceof Error && (error instanceof RangeError && /call stack/i.test(error.message) || /cycle detected/i.test(error.message))) {
@@ -2336,7 +2360,7 @@ class StoreAdministration extends PreactObjectAdministration {
2336
2360
  registration.stop = void 0;
2337
2361
  });
2338
2362
  this.reactiveRegistrations.clear();
2339
- this.mountedSignal.set(false);
2363
+ this.mounted = false;
2340
2364
  }
2341
2365
  }
2342
2366
  let initEnabled$1 = false;
@@ -2382,9 +2406,6 @@ class Store {
2382
2406
  get key() {
2383
2407
  return this.props.key;
2384
2408
  }
2385
- get isMounted() {
2386
- return getStoreAdm(this).isMounted;
2387
- }
2388
2409
  reaction(track, callback) {
2389
2410
  return getStoreAdm(this).reaction(track, callback);
2390
2411
  }
@@ -2738,7 +2759,6 @@ class ModelAdministration extends PreactObjectAdministration {
2738
2759
  computedSnapshot;
2739
2760
  snapshotMap = /* @__PURE__ */ new Map();
2740
2761
  contextCache = /* @__PURE__ */ new Map();
2741
- ownedDisposers = /* @__PURE__ */ new Set();
2742
2762
  disposed = false;
2743
2763
  parentName = null;
2744
2764
  get parent() {
@@ -2759,24 +2779,6 @@ class ModelAdministration extends PreactObjectAdministration {
2759
2779
  throw new Error("r-state-tree: cannot use a disposed model");
2760
2780
  }
2761
2781
  }
2762
- own(disposer) {
2763
- this.assertUsable();
2764
- let active = true;
2765
- const wrapped = () => {
2766
- if (!active) return;
2767
- active = false;
2768
- this.ownedDisposers.delete(wrapped);
2769
- disposer();
2770
- };
2771
- this.ownedDisposers.add(wrapped);
2772
- return wrapped;
2773
- }
2774
- reaction(track, callback) {
2775
- return this.own(reaction(track, callback));
2776
- }
2777
- effect(callback) {
2778
- return this.own(signalsCore.effect(callback));
2779
- }
2780
2782
  get configuration() {
2781
2783
  return this.configurationGetter?.() ?? {};
2782
2784
  }
@@ -3068,7 +3070,6 @@ class ModelAdministration extends PreactObjectAdministration {
3068
3070
  this.modelsTraceUnsub.clear();
3069
3071
  this.contextCache.forEach((computed2) => computed2.clear());
3070
3072
  this.contextCache.clear();
3071
- Array.from(this.ownedDisposers).forEach((dispose) => dispose());
3072
3073
  }
3073
3074
  getContextValue(contextId, provideSymbol, defaultValue, hasDefault) {
3074
3075
  let computed2 = this.contextCache.get(contextId);
@@ -3318,12 +3319,6 @@ class Model {
3318
3319
  get parent() {
3319
3320
  return getModelAdm(this).parent?.proxy ?? null;
3320
3321
  }
3321
- reaction(track, callback) {
3322
- return getModelAdm(this).reaction(track, callback);
3323
- }
3324
- effect(callback) {
3325
- return getModelAdm(this).effect(callback);
3326
- }
3327
3322
  [Symbol.dispose]() {
3328
3323
  getModelAdm(this).dispose();
3329
3324
  }
@@ -1992,6 +1992,32 @@ function validateStoreChildValue(value, propertyName) {
1992
1992
  );
1993
1993
  }
1994
1994
  class StoreAdministration extends PreactObjectAdministration {
1995
+ static pendingActivations = [];
1996
+ static isFlushingActivations = false;
1997
+ static currentActivationDepth = 0;
1998
+ static flushReactiveRegistrations(stores) {
1999
+ const depth = this.isFlushingActivations ? this.currentActivationDepth + 1 : 0;
2000
+ this.pendingActivations.push(...stores.map((store) => ({ store, depth })));
2001
+ if (this.isFlushingActivations) return;
2002
+ this.isFlushingActivations = true;
2003
+ try {
2004
+ let entry;
2005
+ while (entry = this.pendingActivations.shift()) {
2006
+ this.currentActivationDepth = entry.depth;
2007
+ if (entry.depth > MAX_MOUNT_DEPTH) {
2008
+ throw createCircularMountError({
2009
+ storeName: entry.store.proxy.constructor.name || "Store",
2010
+ modelsKeys: Object.keys(entry.store.proxy.props?.models ?? {})
2011
+ });
2012
+ }
2013
+ entry.store.startReactiveRegistrations();
2014
+ }
2015
+ } finally {
2016
+ this.pendingActivations.length = 0;
2017
+ this.currentActivationDepth = 0;
2018
+ this.isFlushingActivations = false;
2019
+ }
2020
+ }
1995
2021
  static proxyTraps = Object.assign(
1996
2022
  {},
1997
2023
  PreactObjectAdministration.proxyTraps,
@@ -2031,7 +2057,7 @@ class StoreAdministration extends PreactObjectAdministration {
2031
2057
  }
2032
2058
  );
2033
2059
  parent = null;
2034
- mountedSignal = createSignal(false);
2060
+ mounted = false;
2035
2061
  disposed = false;
2036
2062
  contextCache = /* @__PURE__ */ new Map();
2037
2063
  childStoreDataMap = /* @__PURE__ */ new Map();
@@ -2193,7 +2219,7 @@ class StoreAdministration extends PreactObjectAdministration {
2193
2219
  return !this.parent;
2194
2220
  }
2195
2221
  get isMounted() {
2196
- return this.mountedSignal.get();
2222
+ return this.mounted;
2197
2223
  }
2198
2224
  getContextValue(contextId, provideSymbol, defaultValue, hasDefault) {
2199
2225
  let computed2 = this.contextCache.get(contextId);
@@ -2289,13 +2315,11 @@ class StoreAdministration extends PreactObjectAdministration {
2289
2315
  getStoreAdm(stores)?.mount(this, name, registrationsToStart);
2290
2316
  }
2291
2317
  });
2292
- this.mountedSignal.set(true);
2318
+ this.mounted = true;
2293
2319
  registrationsToStart.push(this);
2294
2320
  });
2295
2321
  if (isOutermostMount) {
2296
- registrationsToStart.forEach(
2297
- (store) => store.startReactiveRegistrations()
2298
- );
2322
+ StoreAdministration.flushReactiveRegistrations(registrationsToStart);
2299
2323
  }
2300
2324
  } catch (error) {
2301
2325
  if (error instanceof Error && (error instanceof RangeError && /call stack/i.test(error.message) || /cycle detected/i.test(error.message))) {
@@ -2335,7 +2359,7 @@ class StoreAdministration extends PreactObjectAdministration {
2335
2359
  registration.stop = void 0;
2336
2360
  });
2337
2361
  this.reactiveRegistrations.clear();
2338
- this.mountedSignal.set(false);
2362
+ this.mounted = false;
2339
2363
  }
2340
2364
  }
2341
2365
  let initEnabled$1 = false;
@@ -2381,9 +2405,6 @@ class Store {
2381
2405
  get key() {
2382
2406
  return this.props.key;
2383
2407
  }
2384
- get isMounted() {
2385
- return getStoreAdm(this).isMounted;
2386
- }
2387
2408
  reaction(track, callback) {
2388
2409
  return getStoreAdm(this).reaction(track, callback);
2389
2410
  }
@@ -2737,7 +2758,6 @@ class ModelAdministration extends PreactObjectAdministration {
2737
2758
  computedSnapshot;
2738
2759
  snapshotMap = /* @__PURE__ */ new Map();
2739
2760
  contextCache = /* @__PURE__ */ new Map();
2740
- ownedDisposers = /* @__PURE__ */ new Set();
2741
2761
  disposed = false;
2742
2762
  parentName = null;
2743
2763
  get parent() {
@@ -2758,24 +2778,6 @@ class ModelAdministration extends PreactObjectAdministration {
2758
2778
  throw new Error("r-state-tree: cannot use a disposed model");
2759
2779
  }
2760
2780
  }
2761
- own(disposer) {
2762
- this.assertUsable();
2763
- let active = true;
2764
- const wrapped = () => {
2765
- if (!active) return;
2766
- active = false;
2767
- this.ownedDisposers.delete(wrapped);
2768
- disposer();
2769
- };
2770
- this.ownedDisposers.add(wrapped);
2771
- return wrapped;
2772
- }
2773
- reaction(track, callback) {
2774
- return this.own(reaction(track, callback));
2775
- }
2776
- effect(callback) {
2777
- return this.own(effect(callback));
2778
- }
2779
2781
  get configuration() {
2780
2782
  return this.configurationGetter?.() ?? {};
2781
2783
  }
@@ -3067,7 +3069,6 @@ class ModelAdministration extends PreactObjectAdministration {
3067
3069
  this.modelsTraceUnsub.clear();
3068
3070
  this.contextCache.forEach((computed2) => computed2.clear());
3069
3071
  this.contextCache.clear();
3070
- Array.from(this.ownedDisposers).forEach((dispose) => dispose());
3071
3072
  }
3072
3073
  getContextValue(contextId, provideSymbol, defaultValue, hasDefault) {
3073
3074
  let computed2 = this.contextCache.get(contextId);
@@ -3317,12 +3318,6 @@ class Model {
3317
3318
  get parent() {
3318
3319
  return getModelAdm(this).parent?.proxy ?? null;
3319
3320
  }
3320
- reaction(track, callback) {
3321
- return getModelAdm(this).reaction(track, callback);
3322
- }
3323
- effect(callback) {
3324
- return getModelAdm(this).effect(callback);
3325
- }
3326
3321
  [Symbol.dispose]() {
3327
3322
  getModelAdm(this).dispose();
3328
3323
  }
@@ -13,7 +13,6 @@ export default class Store<PropsType extends Record<string, any> = StoreProps<Pr
13
13
  props: StoreProps<PropsType>;
14
14
  constructor(props: StoreProps<PropsType>);
15
15
  get key(): string | number | undefined;
16
- get isMounted(): boolean;
17
16
  reaction<T>(track: () => T, callback: (value: T, previousValue: T) => void): () => void;
18
17
  effect(callback: () => void | (() => void)): () => void;
19
18
  [Symbol.dispose](): void;
@@ -4,9 +4,13 @@ import type { StoreConfiguration, Props } from "../types";
4
4
  export declare function updateProps(props: Props, newProps: Props): void;
5
5
  export declare function getStoreAdm(store: Store): StoreAdministration;
6
6
  export declare class StoreAdministration<StoreType extends Store = Store> extends ObjectAdministration<Store> {
7
+ private static pendingActivations;
8
+ private static isFlushingActivations;
9
+ private static currentActivationDepth;
10
+ private static flushReactiveRegistrations;
7
11
  static proxyTraps: ProxyHandler<object>;
8
12
  parent: StoreAdministration | null;
9
- private mountedSignal;
13
+ private mounted;
10
14
  private disposed;
11
15
  private contextCache;
12
16
  private childStoreDataMap;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "r-state-tree",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "description": "reactive state management library",
5
5
  "main": "dist/r-state-tree.cjs",
6
6
  "module": "dist/r-state-tree.js",