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 +87 -0
- package/dist/api.d.ts +3 -3
- package/dist/computedProxy.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/model/Model.d.ts +1 -1
- package/dist/model/ModelAdministration.d.ts +3 -2
- package/dist/model/idMap.d.ts +2 -2
- package/dist/observables/array.d.ts +1 -1
- package/dist/observables/collection.d.ts +1 -1
- package/dist/observables/index.d.ts +1 -1
- package/dist/observables/internal/Administration.d.ts +2 -2
- package/dist/observables/internal/NodeMap.d.ts +1 -1
- package/dist/observables/internal/lookup.d.ts +3 -1
- package/dist/observables/internal/utils.d.ts +2 -2
- package/dist/observables/object.d.ts +2 -2
- package/dist/observables/preact.d.ts +12 -4
- package/dist/r-state-tree.cjs +218 -20
- package/dist/r-state-tree.js +220 -19
- package/dist/store/Store.d.ts +1 -1
- package/dist/store/StoreAdministration.d.ts +3 -3
- package/dist/types.d.ts +10 -2
- package/dist/utils.d.ts +1 -1
- package/package.json +57 -58
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>;
|
package/dist/computedProxy.d.ts
CHANGED
|
@@ -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";
|
package/dist/model/Model.d.ts
CHANGED
|
@@ -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
|
|
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> {
|
package/dist/model/idMap.d.ts
CHANGED
|
@@ -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;
|
|
@@ -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,
|
|
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
|
|
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
|
-
|
|
58
|
-
|
|
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;
|