r-state-tree 0.4.6 → 0.5.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.
package/README.md CHANGED
@@ -42,6 +42,19 @@ TypeScript 5+ supports TC39 Stage 3 decorators.
42
42
  - `useDefineForClassFields: true` is recommended with modern toolchains targeting ES2022.
43
43
  - The library includes a `Symbol.metadata` polyfill via `@tsmetadata/polyfill`.
44
44
 
45
+ ### Vite / esbuild config (SSR)
46
+
47
+ When using Vite or esbuild for SSR, ensure the target is set to `es2022` to support Stage 3 decorators:
48
+
49
+ ```ts
50
+ // vite.config.ts
51
+ export default defineConfig({
52
+ esbuild: {
53
+ target: "es2022",
54
+ },
55
+ });
56
+ ```
57
+
45
58
  ## Core concepts
46
59
 
47
60
  - Stores: application/view state containers. Create with `createStore()`, attach with `mount()`. Compose with `@child` (single or arrays, stable via `{ key }`). React to changes with `effect`/`reaction` and store lifecycles (`storeDidMount`/`storeWillUnmount`). Update reactive `props` via `updateStore()`.
@@ -389,6 +402,55 @@ effect(() => {
389
402
  state.count++; // Triggers the effect
390
403
  ```
391
404
 
405
+ ### Shallow and signal observables
406
+
407
+ Control the depth of reactivity with `@observable.shallow` and `@observable.signal`:
408
+
409
+ | Decorator | Container Observable? | Values Observable? | Triggers on... |
410
+ |-----------|----------------------|-------------------|----------------|
411
+ | `@observable` | ✅ Yes | ✅ Yes (deep) | mutations + assignment |
412
+ | `@observable.shallow` | ✅ Yes | ❌ No | mutations + assignment |
413
+ | `@observable.signal` | ❌ No | ❌ No | assignment only |
414
+
415
+ ```ts
416
+ import { Observable, observable, effect, isObservable } from "r-state-tree";
417
+
418
+ class DataStore extends Observable {
419
+ // Deep observable - items pushed are also observable
420
+ @observable deepItems: { value: number }[] = [];
421
+
422
+ // Shallow - container is observable, but pushed items are NOT
423
+ @observable.shallow shallowItems: { value: number }[] = [];
424
+
425
+ // Signal - only assignment triggers, mutations do not
426
+ @observable.signal signalItems: number[] = [];
427
+ }
428
+
429
+ const store = new DataStore();
430
+
431
+ // Shallow: items pushed are NOT observable (safe for structuredClone)
432
+ store.shallowItems.push({ value: 1 });
433
+ console.log(isObservable(store.shallowItems[0])); // false
434
+
435
+ // Signal: mutations don't trigger effects
436
+ effect(() => store.signalItems.length);
437
+ store.signalItems.push(1); // Does NOT trigger effect
438
+ store.signalItems = [1, 2]; // DOES trigger effect
439
+ ```
440
+
441
+ **Use cases:**
442
+ - `@observable.shallow`: Store external data that may need `structuredClone()`, or when you want change detection on the container but not deep reactivity
443
+ - `@observable.signal`: Maximum performance when you're replacing values rather than mutating them
444
+
445
+ Models support the same modifiers with `@state`:
446
+
447
+ ```ts
448
+ class M extends Model {
449
+ @state.shallow items: { value: number }[] = []; // Container tracked, items not
450
+ @state.signal data: SomeType = null; // Only assignment tracked
451
+ }
452
+ ```
453
+
392
454
  ## Observables (low‑level)
393
455
 
394
456
  Create reactive structures outside Stores/Models. Supported: Objects, Arrays, Map, Set, WeakMap, WeakSet.
@@ -824,6 +886,31 @@ const off = onSnapshot(m, (snap) =>
824
886
  - Forgetting stable keys for `@child` arrays causes identity churn.
825
887
  - Assuming deep reactivity on undecorated fields of plain classes; use `@observable` for `Observable` classes, or use Stores/Models.
826
888
  - Creating child stores in constructors: `@child` must be on getters so identity and lifecycle can be managed by the framework.
889
+ - Passing `models` into child stores during mount can create a recursive mount loop. If a child needs parent models, create the child store/model inside `storeDidMount` instead of wiring it through `models` during the mount cycle.
890
+
891
+ ### Circular store/model creation
892
+
893
+ When child stores are created during mount with `models` that point back into the parent, it is easy to trigger an endless mount loop. The runtime now guards this by throwing a descriptive error (for example, `detected circular store/model creation while mounting ParentStore -> ChildStore.loop -> ...`). If you see this, move child creation into `storeDidMount` or break the cycle so that models are produced after the parent finishes mounting.
894
+
895
+ ## LLM implementation checklist
896
+
897
+ - Do not thread context data via props. Provide contexts at the parent and consume them in children. Passing `getX` callbacks for resource/page/video/skill is a red flag—consume contexts instead.
898
+ - Avoid aggregating contexts into a single `ctx` object; consume where needed or expose small, focused getters.
899
+ - Only create stores in three places: root, `@child` getters, or immediately before mounting. Avoid standalone factory helpers; inline `createStore` in `@child` getters.
900
+ - Do not wrap `createStore` calls with `as Record<string, unknown>`; fix typing instead.
901
+ - Domain/persistent state belongs in Models; UI/app orchestration in Stores. Keep UI terms out of domain models—name domain concepts (e.g., `ChatThreadsModel`).
902
+ - If state is persistent/rehydratable, model it and derive stores from the model; keep purely view/ephemeral state in stores.
903
+ - Use r-state-tree snapshots (`toSnapshot`/`applySnapshot`) instead of hand-rolled `serialize`/`rehydrate` unless a different shape is required.
904
+ - Keep `onPersist` only when syncing store state into a backing model; otherwise prefer snapshot listeners.
905
+ - Pure, stateless helpers belong in utility modules, not as store methods. If a method does not touch `this`, extract it; keep coupled helpers in the store.
906
+ - Provide stable, rarely changing resource/view/video/page data via context (resourceId, resourceType, totalPages, current page/display mode, page offsets, document title, skill/detail, video info). Children should consume context directly.
907
+ - Use `@child` getters to create child stores with stable keys; avoid constructor creation. Pass only what the child needs; avoid prop drilling context.
908
+ - Eliminate blanket casts; fix types and let inference work. Avoid `any`.
909
+ - For `createStore` props, extend `Record<string, unknown>` only if needed; otherwise rely on proper prop types and context.
910
+ - Avoid barrel files if they cause import confusion; prefer direct imports.
911
+ - Keep file boundaries clean: one model per file; avoid piling multiple models together.
912
+ - Do not shadow `props` or use constructors for work better suited to `storeDidMount`.
913
+ - Use `@model` for injected models; `@child` for child stores; stable keys for arrays.
827
914
 
828
915
  ## Typing recipes
829
916
 
package/dist/api.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { Snapshot, SnapshotDiff } from "./types";
2
- import Store from "./store/Store";
3
- import Model from "./model/Model";
1
+ import type { Snapshot, SnapshotDiff } from "./types";
2
+ import type Store from "./store/Store";
3
+ import type Model from "./model/Model";
4
4
  export declare function mount<T extends Store>(container: T): T;
5
5
  export declare function unmount<S extends Store>(container: S): void;
6
6
  export declare function toSnapshot<T extends Model>(model: T): Snapshot<T>;
@@ -1,2 +1,2 @@
1
- import { ComputedNode } from "./observables";
1
+ import type { ComputedNode } from "./observables";
2
2
  export default function <T extends Record<PropertyKey, any>>(computed: ComputedNode<T>): T;
package/dist/index.d.ts CHANGED
@@ -4,7 +4,7 @@ import Model from "./model/Model";
4
4
  import { mount, unmount, onSnapshot, toSnapshot, applySnapshot, onSnapshotDiff } from "./api";
5
5
  import { createContext } from "./context";
6
6
  export { createStore, Store, Model, mount, unmount, updateStore, onSnapshot, onSnapshotDiff, toSnapshot, applySnapshot, createContext, };
7
- export { observable, computed, source, reportChanged, reportObserved, getSignal, isObservable, reaction, Signal, ReadonlySignal, batch, untracked, effect, Observable, signal, } from "./observables";
7
+ export { observable, computed, source, reportChanged, reportObserved, getSignal, isObservable, reaction, Signal, type ReadonlySignal, batch, untracked, effect, Observable, signal, } from "./observables";
8
8
  export * from "./decorators";
9
9
  export type { Configuration, Snapshot, IdType, SnapshotDiff } from "./types";
10
10
  export type { Context } from "./context";
@@ -1,4 +1,4 @@
1
- import { ModelConfiguration, Snapshot } from "../types";
1
+ import type { ModelConfiguration, Snapshot } from "../types";
2
2
  type ExtractModelDidInitArgs<T extends Model> = T extends {
3
3
  modelDidInit(...args: infer Args): unknown;
4
4
  } ? Args extends [infer Snapshot, ...infer Rest] ? Rest : [] : [];
@@ -1,6 +1,7 @@
1
- import { PreactObjectAdministration, ComputedNode, AtomNode } from "../observables";
1
+ import { PreactObjectAdministration } from "../observables";
2
+ import type { ComputedNode, AtomNode } from "../observables";
2
3
  import Model from "../model/Model";
3
- import { ModelConfiguration, Snapshot, SnapshotChange } from "../types";
4
+ import type { ModelConfiguration, Snapshot, SnapshotChange } from "../types";
4
5
  export declare function getConfigurationFromSnapshot(snapshot: object): ModelConfiguration<unknown> | undefined;
5
6
  export declare function getModelAdm<T extends Model>(model: T): ModelAdministration;
6
7
  export declare class ModelAdministration extends PreactObjectAdministration<any> {
@@ -1,5 +1,5 @@
1
- import { IdType } from "../types";
2
- import Model from "./Model";
1
+ import type { IdType } from "../types";
2
+ import type Model from "./Model";
3
3
  export declare function onSnapshotLoad<T>(fn: () => T): T;
4
4
  export declare function setIdentifier(model: Model, id: string | number): void;
5
5
  export declare function getIdentifier(model: Model): IdType | undefined;
@@ -1,4 +1,4 @@
1
- import { AtomNode } from "./preact";
1
+ import type { AtomNode } from "./preact";
2
2
  import { Administration } from "./internal/Administration";
3
3
  import { SignalMap } from "./internal/NodeMap";
4
4
  export declare class ArrayAdministration<T> extends Administration<T[]> {
@@ -1,4 +1,4 @@
1
- import { AtomNode } from "./preact";
1
+ import type { AtomNode } from "./preact";
2
2
  import { Administration } from "./internal/Administration";
3
3
  import { AtomMap, SignalMap } from "./internal/NodeMap";
4
4
  type Collection<K, V> = Set<K> | Map<K, V>;
@@ -3,4 +3,4 @@ export { ArrayAdministration } from "./array";
3
3
  export { CollectionAdministration } from "./collection";
4
4
  export { DateAdministration } from "./date";
5
5
  export { ObjectAdministration } from "./object";
6
- export { AtomNode, reaction, SignalNode, createAtom, batch, createComputed, createSignal, untracked, ListenerNode, createListener, ComputedNode, PreactObjectAdministration, getSignal, source, reportChanged, reportObserved, observable, computed, Signal, ReadonlySignal, effect, signal, Observable, } from "./preact";
6
+ export { type AtomNode, reaction, type SignalNode, createAtom, batch, createComputed, createSignal, untracked, type ListenerNode, createListener, type ComputedNode, PreactObjectAdministration, getSignal, source, reportChanged, reportObserved, observable, computed, Signal, type ReadonlySignal, effect, signal, Observable, } from "./preact";
@@ -1,5 +1,5 @@
1
- import { ObservedAtomNode } from "../preact";
2
- import { SignalMap } from "./NodeMap";
1
+ import type { ObservedAtomNode } from "../preact";
2
+ import type { SignalMap } from "./NodeMap";
3
3
  export declare class Administration<T extends object = any> {
4
4
  static readonly proxyTraps: ProxyHandler<object>;
5
5
  readonly proxy: T;
@@ -1,4 +1,4 @@
1
- import { AtomNode, ObservedAtomNode, SignalNode } from "../preact";
1
+ import type { AtomNode, ObservedAtomNode, SignalNode } from "../preact";
2
2
  declare class NodeMap<K = unknown, GraphNode extends AtomNode | SignalNode<unknown> = AtomNode | SignalNode<unknown>> {
3
3
  private map;
4
4
  private weakMap;
@@ -2,7 +2,7 @@ import { CollectionAdministration } from "../collection";
2
2
  import { PreactObjectAdministration } from "../preact";
3
3
  import { ArrayAdministration } from "../array";
4
4
  import { DateAdministration } from "../date";
5
- import { Administration } from "./Administration";
5
+ import type { Administration } from "./Administration";
6
6
  export declare function getAdministration<T extends object>(obj: T): T extends Set<infer S> ? CollectionAdministration<S> : T extends Map<infer K, infer V> ? CollectionAdministration<K, V> : T extends Array<infer R> ? ArrayAdministration<R> : T extends Date ? DateAdministration : PreactObjectAdministration<any>;
7
7
  export declare function getSource<T>(obj: T): T;
8
8
  export declare function getAction<T extends Function>(fn: T): T;
@@ -10,5 +10,7 @@ export declare function getObservableClassInstance<T extends object>(value: T):
10
10
  export declare function getObservableIfExists<T>(value: T): T | undefined;
11
11
  export declare function createObservableWithCustomAdministration<T>(value: T, Adm: new (obj: any) => Administration): T;
12
12
  export declare function getObservable<T>(value: T): T;
13
+ export declare function isShallowObservable(obj: unknown): boolean;
14
+ export declare function getShallowObservable<T>(value: T): T;
13
15
  export declare function isObservable(obj: unknown): boolean;
14
16
  export declare function getInternalNode(obj: object, key?: PropertyKey): any;
@@ -1,8 +1,8 @@
1
- import { AtomNode, ComputedNode, SignalNode } from "../preact";
1
+ import type { AtomNode, ComputedNode, SignalNode } from "../preact";
2
2
  export declare function defaultEquals<T>(a: T, b: T): boolean;
3
3
  export declare function isNonPrimitive(val: unknown): val is object;
4
4
  export declare function isPropertyKey(val: unknown): val is string | number | symbol;
5
- export type PropertyType = "action" | "computed" | "observable";
5
+ export type PropertyType = "action" | "computed" | "observable" | "observableShallow" | "observableSignal";
6
6
  export declare function getPropertyType(key: PropertyKey, obj: object): PropertyType | null;
7
7
  export declare function getPropertyDescriptor(obj: object, key: PropertyKey): PropertyDescriptor | undefined;
8
8
  export declare function isPlainObject(value: unknown): value is object;
@@ -1,5 +1,5 @@
1
- import { AtomNode, ComputedNode } from "./preact";
2
- import { PropertyType } from "./internal/utils";
1
+ import type { AtomNode, ComputedNode } from "./preact";
2
+ import type { PropertyType } from "./internal/utils";
3
3
  import { Administration } from "./internal/Administration";
4
4
  import { AtomMap, SignalMap } from "./internal/NodeMap";
5
5
  export declare class ObjectAdministration<T extends object> extends Administration<T> {
@@ -1,4 +1,5 @@
1
- import { signal, Signal, ReadonlySignal, batch, effect, untracked } from "@preact/signals-core";
1
+ import { signal, Signal, batch, effect, untracked } from "@preact/signals-core";
2
+ import type { ReadonlySignal } from "@preact/signals-core";
2
3
  import { ObjectAdministration } from "./object";
3
4
  export { isObservable } from "./index";
4
5
  export declare class PreactObjectAdministration<T extends object> extends ObjectAdministration<T> {
@@ -39,7 +40,8 @@ export declare function createObservedAtom(): {
39
40
  };
40
41
  export declare function createSignal<T>(initialValue: T): SignalNode<T>;
41
42
  export declare function createAtom(): AtomNode;
42
- export { effect, signal, batch, untracked, Signal, ReadonlySignal };
43
+ export { effect, signal, batch, untracked, Signal };
44
+ export type { ReadonlySignal };
43
45
  export declare function reaction<T>(fn: () => T, callback: (value: T) => void): () => void;
44
46
  export type Listener = {
45
47
  dispose: () => void;
@@ -54,8 +56,14 @@ export type PreactObservable<T> = T extends Function ? T : T extends Map<infer K
54
56
  } & {
55
57
  readonly [key in keyof T as T[key] extends object ? never : `$${string & key}`]?: Signal<T[key]>;
56
58
  };
57
- export declare function observable<T>(obj: T): PreactObservable<T>;
58
- export declare function observable(value: undefined, context: ClassFieldDecoratorContext): void;
59
+ declare function observableImpl<T>(obj: T): PreactObservable<T>;
60
+ declare function observableImpl(value: undefined, context: ClassFieldDecoratorContext): void;
61
+ declare function shallowDecorator(value: any, context: ClassFieldDecoratorContext): any;
62
+ declare function signalDecorator(value: any, context: ClassFieldDecoratorContext): any;
63
+ export declare const observable: typeof observableImpl & {
64
+ shallow: typeof shallowDecorator;
65
+ signal: typeof signalDecorator;
66
+ };
59
67
  export declare function computed<T>(value: () => T, context: ClassGetterDecoratorContext): void;
60
68
  export declare function computed<T>(fn: () => T): ReadonlySignal<T>;
61
69
  export declare function source<T>(obj: PreactObservable<T> | T): T;