@pyreon/state-tree 0.6.0 → 0.8.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.
@@ -1,104 +1,40 @@
1
- /** Returns true when a value is a model instance (has metadata registered). */
2
- function isModelInstance(value) {
3
- return value != null && typeof value === "object" && instanceMeta.has(value);
4
- }
5
-
6
- //#endregion
7
- //#region src/snapshot.ts
8
- /**
9
- * Serialize a model instance to a plain JS object (no signals, no functions).
10
- * Nested model instances are recursively serialized.
11
- *
12
- * @example
13
- * getSnapshot(counter) // { count: 6 }
14
- * getSnapshot(app) // { profile: { name: "Alice" }, title: "My App" }
15
- */
16
- function getSnapshot(instance) {
17
- const meta = instanceMeta.get(instance);
18
- if (!meta) throw new Error("[@pyreon/state-tree] getSnapshot: not a model instance");
19
- const out = {};
20
- for (const key of meta.stateKeys) {
21
- const sig = instance[key];
22
- if (!sig) continue;
23
- const val = sig.peek();
24
- out[key] = isModelInstance(val) ? getSnapshot(val) : val;
25
- }
26
- return out;
27
- }
28
-
29
- //#endregion
30
- //#region src/devtools.ts
31
- /**
32
- * @pyreon/state-tree devtools introspection API.
33
- * Import: `import { ... } from "@pyreon/state-tree/devtools"`
34
- */
35
-
36
- function _notify() {
37
- for (const listener of _listeners) listener();
38
- }
39
- /**
40
- * Register a model instance for devtools inspection.
41
- * Call this when creating instances you want visible in devtools.
42
- *
43
- * @example
44
- * const counter = Counter.create()
45
- * registerInstance("app-counter", counter)
46
- */
47
- function registerInstance(name, instance) {
48
- _activeModels.set(name, new WeakRef(instance));
49
- _notify();
50
- }
51
- /**
52
- * Unregister a model instance.
53
- */
54
- function unregisterInstance(name) {
55
- _activeModels.delete(name);
56
- _notify();
57
- }
58
- /**
59
- * Get all registered model instance names.
60
- * Automatically cleans up garbage-collected instances.
61
- */
62
- function getActiveModels() {
63
- for (const [name, ref] of _activeModels) if (ref.deref() === void 0) _activeModels.delete(name);
64
- return [..._activeModels.keys()];
65
- }
66
- /**
67
- * Get a model instance by name (or undefined if GC'd or not registered).
68
- */
69
- function getModelInstance(name) {
70
- const ref = _activeModels.get(name);
71
- if (!ref) return void 0;
72
- const instance = ref.deref();
73
- if (!instance) {
74
- _activeModels.delete(name);
75
- return;
76
- }
77
- return instance;
78
- }
79
- /**
80
- * Get a snapshot of a registered model instance.
81
- */
82
- function getModelSnapshot(name) {
83
- const instance = getModelInstance(name);
84
- if (!instance) return void 0;
85
- return getSnapshot(instance);
86
- }
87
- /**
88
- * Subscribe to model registry changes. Returns unsubscribe function.
89
- */
90
- function onModelChange(listener) {
91
- _listeners.add(listener);
92
- return () => {
93
- _listeners.delete(listener);
94
- };
95
- }
1
+ //#region src/devtools.d.ts
2
+ /**
3
+ * @pyreon/state-tree devtools introspection API.
4
+ * Import: `import { ... } from "@pyreon/state-tree/devtools"`
5
+ */
6
+ /**
7
+ * Register a model instance for devtools inspection.
8
+ * Call this when creating instances you want visible in devtools.
9
+ *
10
+ * @example
11
+ * const counter = Counter.create()
12
+ * registerInstance("app-counter", counter)
13
+ */
14
+ declare function registerInstance(name: string, instance: object): void;
15
+ /**
16
+ * Unregister a model instance.
17
+ */
18
+ declare function unregisterInstance(name: string): void;
19
+ /**
20
+ * Get all registered model instance names.
21
+ * Automatically cleans up garbage-collected instances.
22
+ */
23
+ declare function getActiveModels(): string[];
24
+ /**
25
+ * Get a model instance by name (or undefined if GC'd or not registered).
26
+ */
27
+ declare function getModelInstance(name: string): object | undefined;
28
+ /**
29
+ * Get a snapshot of a registered model instance.
30
+ */
31
+ declare function getModelSnapshot(name: string): Record<string, unknown> | undefined;
32
+ /**
33
+ * Subscribe to model registry changes. Returns unsubscribe function.
34
+ */
35
+ declare function onModelChange(listener: () => void): () => void;
96
36
  /** @internal — reset devtools registry (for tests). */
97
- function _resetDevtools() {
98
- _activeModels.clear();
99
- _listeners.clear();
100
- }
101
-
37
+ declare function _resetDevtools(): void;
102
38
  //#endregion
103
39
  export { _resetDevtools, getActiveModels, getModelInstance, getModelSnapshot, onModelChange, registerInstance, unregisterInstance };
104
- //# sourceMappingURL=devtools.d.ts.map
40
+ //# sourceMappingURL=devtools2.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"devtools.d.ts","names":[],"sources":["../../src/registry.ts","../../src/snapshot.ts","../../src/devtools.ts"],"mappings":";AASA,SAAgB,eAAA,CAAgB,KAAA,EAAyB;EACvD,OACE,KAAA,IAAS,IAAA,IACT,OAAO,KAAA,KAAU,QAAA,IACjB,YAAA,CAAa,GAAA,CAAI,KAAA,CAAgB;;;;;;;;;;;;;ACErC,SAAgB,WAAA,CACd,QAAA,EACkB;EAClB,MAAM,IAAA,GAAO,YAAA,CAAa,GAAA,CAAI,QAAA,CAAS;EACvC,IAAI,CAAC,IAAA,EACH,MAAM,IAAI,KAAA,CAAM,wDAAA,CAAyD;EAE3E,MAAM,GAAA,GAA+B,CAAA,CAAE;EACvC,KAAK,MAAM,GAAA,IAAO,IAAA,CAAK,SAAA,EAAW;IAChC,MAAM,GAAA,GAAO,QAAA,CAA6C,GAAA,CAAA;IAC1D,IAAI,CAAC,GAAA,EAAK;IACV,MAAM,GAAA,GAAM,GAAA,CAAI,IAAA,CAAA,CAAM;IACtB,GAAA,CAAI,GAAA,CAAA,GAAO,eAAA,CAAgB,GAAA,CAAI,GAAG,WAAA,CAAY,GAAA,CAAc,GAAG,GAAA;;EAEjE,OAAO,GAAA;;;;;;;;;;AClBT,SAAS,OAAA,CAAA,EAAgB;EACvB,KAAK,MAAM,QAAA,IAAY,UAAA,EAAY,QAAA,CAAA,CAAU;;;;;;;;;;AAW/C,SAAgB,gBAAA,CAAiB,IAAA,EAAc,QAAA,EAAwB;EACrE,aAAA,CAAc,GAAA,CAAI,IAAA,EAAM,IAAI,OAAA,CAAQ,QAAA,CAAS,CAAC;EAC9C,OAAA,CAAA,CAAS;;;;;AAMX,SAAgB,kBAAA,CAAmB,IAAA,EAAoB;EACrD,aAAA,CAAc,MAAA,CAAO,IAAA,CAAK;EAC1B,OAAA,CAAA,CAAS;;;;;;AAOX,SAAgB,eAAA,CAAA,EAA4B;EAC1C,KAAK,MAAM,CAAC,IAAA,EAAM,GAAA,CAAA,IAAQ,aAAA,EACxB,IAAI,GAAA,CAAI,KAAA,CAAA,CAAO,KAAK,KAAA,CAAA,EAAW,aAAA,CAAc,MAAA,CAAO,IAAA,CAAK;EAE3D,OAAO,CAAC,GAAG,aAAA,CAAc,IAAA,CAAA,CAAM,CAAC;;;;;AAMlC,SAAgB,gBAAA,CAAiB,IAAA,EAAkC;EACjE,MAAM,GAAA,GAAM,aAAA,CAAc,GAAA,CAAI,IAAA,CAAK;EACnC,IAAI,CAAC,GAAA,EAAK,OAAO,KAAA,CAAA;EACjB,MAAM,QAAA,GAAW,GAAA,CAAI,KAAA,CAAA,CAAO;EAC5B,IAAI,CAAC,QAAA,EAAU;IACb,aAAA,CAAc,MAAA,CAAO,IAAA,CAAK;IAC1B;;EAEF,OAAO,QAAA;;;;;AAMT,SAAgB,gBAAA,CACd,IAAA,EACqC;EACrC,MAAM,QAAA,GAAW,gBAAA,CAAiB,IAAA,CAAK;EACvC,IAAI,CAAC,QAAA,EAAU,OAAO,KAAA,CAAA;EACtB,OAAO,WAAA,CAAY,QAAA,CAAS;;;;;AAM9B,SAAgB,aAAA,CAAc,QAAA,EAAkC;EAC9D,UAAA,CAAW,GAAA,CAAI,QAAA,CAAS;EACxB,OAAA,MAAa;IACX,UAAA,CAAW,MAAA,CAAO,QAAA,CAAS;;;;AAK/B,SAAgB,cAAA,CAAA,EAAuB;EACrC,aAAA,CAAc,KAAA,CAAA,CAAO;EACrB,UAAA,CAAW,KAAA,CAAA,CAAO"}
1
+ {"version":3,"file":"devtools2.d.ts","names":[],"sources":["../../../src/devtools.ts"],"mappings":";;AAuBA;;;;;AAQA;;;;;AASA;iBAjBgB,gBAAA,CAAiB,IAAA,UAAc,QAAA;;;;iBAQ/B,kBAAA,CAAmB,IAAA;;;;;iBASnB,eAAA,CAAA;;;;iBAUA,gBAAA,CAAiB,IAAA;AAyBjC;;;AAAA,iBAXgB,gBAAA,CACd,IAAA,WACC,MAAA;;AAiBH;;iBARgB,aAAA,CAAc,QAAA;;iBAQd,cAAA,CAAA"}
@@ -1,316 +1,198 @@
1
- import { batch, signal } from "@pyreon/reactivity";
1
+ import { Computed, Signal } from "@pyreon/reactivity";
2
2
 
3
- //#region src/registry.ts
4
- /**
5
- * WeakMap from every model instance object → its internal metadata.
6
- * Shared across patch, middleware, and snapshot modules.
7
- */
8
-
9
- /** Returns true when a value is a model instance (has metadata registered). */
10
- function isModelInstance(value) {
11
- return value != null && typeof value === "object" && instanceMeta.has(value);
12
- }
13
-
14
- //#endregion
15
- //#region src/middleware.ts
3
+ //#region src/types.d.ts
4
+ /** Property key stamped on every ModelDefinition to distinguish it from plain objects. */
5
+ declare const MODEL_BRAND: "__pyreonMod";
6
+ type StateShape = Record<string, unknown>;
16
7
  /**
17
- * Run an action through the middleware chain registered on `meta`.
18
- * Each middleware receives the call descriptor and a `next` function.
19
- * If no middlewares, the action runs directly.
20
- */
21
- function runAction(meta, name, fn, args) {
22
- const call = {
23
- name,
24
- args,
25
- path: `/${name}`
26
- };
27
- const dispatch = (idx, c) => {
28
- if (idx >= meta.middlewares.length) return fn(...c.args);
29
- const mw = meta.middlewares[idx];
30
- if (!mw) return fn(...c.args);
31
- return mw(c, nextCall => dispatch(idx + 1, nextCall));
32
- };
33
- return dispatch(0, call);
34
- }
8
+ * Resolve a state field type:
9
+ * - ModelDefinition the instance type it produces
10
+ * - Anything else as-is
11
+ */
12
+ type ResolveField<T> = T extends {
13
+ readonly __pyreonMod: true;
14
+ create(initial?: any): infer I;
15
+ } ? I : T;
16
+ /** Map state shape to per-field signals. */
17
+ type StateSignals<TState extends StateShape> = { readonly [K in keyof TState]: Signal<ResolveField<TState[K]>> };
35
18
  /**
36
- * Intercept every action call on `instance`.
37
- * Middlewares run in registration order — call `next(call)` to continue.
38
- *
39
- * Returns an unsubscribe function.
40
- *
41
- * @example
42
- * const unsub = addMiddleware(counter, (call, next) => {
43
- * console.log(`> ${call.name}(${call.args})`)
44
- * const result = next(call)
45
- * console.log(`< ${call.name}`)
46
- * return result
47
- * })
48
- */
49
- function addMiddleware(instance, middleware) {
50
- const meta = instanceMeta.get(instance);
51
- if (!meta) throw new Error("[@pyreon/state-tree] addMiddleware: not a model instance");
52
- meta.middlewares.push(middleware);
53
- return () => {
54
- const idx = meta.middlewares.indexOf(middleware);
55
- if (idx !== -1) meta.middlewares.splice(idx, 1);
56
- };
57
- }
58
-
59
- //#endregion
60
- //#region src/patch.ts
61
- /** Property names that must never be used as patch path segments. */
62
-
19
+ * `self` type inside actions / views:
20
+ * strongly typed for state signals, `any` for actions and views so that
21
+ * actions can call each other without circular type issues.
22
+ */
23
+ type ModelSelf<TState extends StateShape> = StateSignals<TState> & Record<string, any>;
24
+ /** The public instance type returned by `.create()` and hooks. */
25
+ type ModelInstance<TState extends StateShape, TActions extends Record<string, (...args: any[]) => any>, TViews extends Record<string, Signal<any> | Computed<any>>> = StateSignals<TState> & TActions & TViews;
63
26
  /**
64
- * Wraps a signal so that every write emits a JSON patch via `emitPatch`.
65
- * Reads are pass-through no overhead on hot reactive paths.
66
- *
67
- * @param hasListeners Optional predicate — when provided, patch object allocation
68
- * and snapshotting are skipped entirely when no listeners are registered.
69
- */
70
- function trackedSignal(inner, path, emitPatch, hasListeners) {
71
- const read = () => inner();
72
- read.peek = () => inner.peek();
73
- read.subscribe = listener => inner.subscribe(listener);
74
- read.set = newValue => {
75
- const prev = inner.peek();
76
- inner.set(newValue);
77
- if (!Object.is(prev, newValue) && (!hasListeners || hasListeners())) emitPatch({
78
- op: "replace",
79
- path,
80
- value: isModelInstance(newValue) ? snapshotValue(newValue) : newValue
81
- });
82
- };
83
- read.update = fn => {
84
- read.set(fn(inner.peek()));
27
+ * Extract the state type from a ModelDefinition.
28
+ * Used by Snapshot to recursively resolve nested model types.
29
+ */
30
+ type ExtractModelState<T> = T extends {
31
+ readonly __pyreonMod: true;
32
+ readonly _config: {
33
+ state: infer S extends StateShape;
85
34
  };
86
- return read;
87
- }
88
- /** Shallow snapshot helper (avoids importing snapshot.ts to prevent circular deps). */
89
- function snapshotValue(instance) {
90
- const meta = instanceMeta.get(instance);
91
- if (!meta) return instance;
92
- const out = {};
93
- for (const key of meta.stateKeys) {
94
- const sig = instance[key];
95
- if (!sig) continue;
96
- const val = sig.peek();
97
- out[key] = isModelInstance(val) ? snapshotValue(val) : val;
98
- }
99
- return out;
100
- }
35
+ } ? S : never;
101
36
  /**
102
- * Subscribe to every state mutation in `instance` as a JSON patch.
103
- * Also captures mutations in nested model instances (path is prefixed).
104
- *
105
- * Returns an unsubscribe function.
106
- *
107
- * @example
108
- * const unsub = onPatch(counter, patch => {
109
- * // { op: "replace", path: "/count", value: 6 }
110
- * })
111
- */
112
- function onPatch(instance, listener) {
113
- const meta = instanceMeta.get(instance);
114
- if (!meta) throw new Error("[@pyreon/state-tree] onPatch: not a model instance");
115
- meta.patchListeners.add(listener);
116
- return () => meta.patchListeners.delete(listener);
37
+ * Snapshot type: plain JS values (no signals, no model instances).
38
+ * Nested model fields recursively produce their own typed snapshot.
39
+ */
40
+ type Snapshot<TState extends StateShape> = { [K in keyof TState]: TState[K] extends {
41
+ readonly __pyreonMod: true;
42
+ } ? Snapshot<ExtractModelState<TState[K]>> : TState[K] };
43
+ interface Patch {
44
+ op: 'replace';
45
+ path: string;
46
+ value: unknown;
117
47
  }
118
- /**
119
- * Apply a JSON patch (or array of patches) to a model instance.
120
- * Only "replace" operations are supported (matching the patches emitted by `onPatch`).
121
- *
122
- * Paths use JSON pointer format: `"/count"` for top-level, `"/profile/name"` for nested.
123
- * Nested model instances are resolved automatically.
124
- *
125
- * @example
126
- * applyPatch(counter, { op: "replace", path: "/count", value: 10 })
127
- *
128
- * @example
129
- * // Replay patches recorded from onPatch (undo/redo, time-travel)
130
- * applyPatch(counter, [
131
- * { op: "replace", path: "/count", value: 1 },
132
- * { op: "replace", path: "/count", value: 2 },
133
- * ])
134
- */
135
- function applyPatch(instance, patch) {
136
- const patches = Array.isArray(patch) ? patch : [patch];
137
- batch(() => {
138
- for (const p of patches) {
139
- if (p.op !== "replace") throw new Error(`[@pyreon/state-tree] applyPatch: unsupported op "${p.op}"`);
140
- const segments = p.path.split("/").filter(Boolean);
141
- if (segments.length === 0) throw new Error("[@pyreon/state-tree] applyPatch: empty path");
142
- let target = instance;
143
- for (let i = 0; i < segments.length - 1; i++) {
144
- const segment = segments[i];
145
- if (RESERVED_KEYS.has(segment)) throw new Error(`[@pyreon/state-tree] applyPatch: reserved property name "${segment}"`);
146
- if (!instanceMeta.get(target)) throw new Error(`[@pyreon/state-tree] applyPatch: not a model instance at "${segment}"`);
147
- const sig = target[segment];
148
- if (!sig || typeof sig.peek !== "function") throw new Error(`[@pyreon/state-tree] applyPatch: unknown state key "${segment}"`);
149
- const nested = sig.peek();
150
- if (!nested || typeof nested !== "object" || !isModelInstance(nested)) throw new Error(`[@pyreon/state-tree] applyPatch: "${segment}" is not a nested model instance`);
151
- target = nested;
152
- }
153
- const lastKey = segments[segments.length - 1];
154
- if (RESERVED_KEYS.has(lastKey)) throw new Error(`[@pyreon/state-tree] applyPatch: reserved property name "${lastKey}"`);
155
- const meta = instanceMeta.get(target);
156
- if (!meta) throw new Error("[@pyreon/state-tree] applyPatch: not a model instance");
157
- if (!meta.stateKeys.includes(lastKey)) throw new Error(`[@pyreon/state-tree] applyPatch: unknown state key "${lastKey}"`);
158
- const sig = target[lastKey];
159
- if (sig && typeof sig.set === "function") sig.set(p.value);
160
- }
161
- });
48
+ type PatchListener = (patch: Patch) => void;
49
+ interface ActionCall {
50
+ /** Action name. */
51
+ name: string;
52
+ /** Arguments passed to the action. */
53
+ args: unknown[];
54
+ /** JSON-pointer-style path, e.g. `"/inc"`. */
55
+ path: string;
162
56
  }
163
-
57
+ type MiddlewareFn = (call: ActionCall, next: (nextCall: ActionCall) => unknown) => unknown;
164
58
  //#endregion
165
- //#region src/types.ts
166
- /** Property key stamped on every ModelDefinition to distinguish it from plain objects. */
167
-
168
- //#endregion
169
- //#region src/instance.ts
170
- function isModelDef(v) {
171
- if (v == null || typeof v !== "object") return false;
172
- return v[MODEL_BRAND] === true;
173
- }
174
- /**
175
- * Create a live model instance from a config + optional initial snapshot.
176
- * Called by `ModelDefinition.create()`.
177
- */
178
- function createInstance(config, initial) {
179
- const instance = {};
180
- const meta = {
181
- stateKeys: [],
182
- patchListeners: /* @__PURE__ */new Set(),
183
- middlewares: [],
184
- emitPatch(patch) {
185
- if (this.patchListeners.size === 0) return;
186
- for (const listener of this.patchListeners) listener(patch);
187
- }
188
- };
189
- instanceMeta.set(instance, meta);
190
- const self = new Proxy(instance, {
191
- get(_, k) {
192
- return instance[k];
193
- }
194
- });
195
- for (const [key, defaultValue] of Object.entries(config.state)) {
196
- meta.stateKeys.push(key);
197
- const path = `/${key}`;
198
- const initValue = key in initial ? initial[key] : void 0;
199
- let rawSig;
200
- if (isModelDef(defaultValue)) {
201
- const nestedInstance = createInstance(defaultValue._config, initValue ?? {});
202
- rawSig = signal(nestedInstance);
203
- onPatch(nestedInstance, patch => {
204
- meta.emitPatch({
205
- ...patch,
206
- path: path + patch.path
207
- });
208
- });
209
- } else rawSig = signal(initValue !== void 0 ? initValue : defaultValue);
210
- instance[key] = trackedSignal(rawSig, path, p => meta.emitPatch(p), () => meta.patchListeners.size > 0);
211
- }
212
- if (config.views) {
213
- const views = config.views(self);
214
- for (const [key, view] of Object.entries(views)) instance[key] = view;
215
- }
216
- if (config.actions) {
217
- const rawActions = config.actions(self);
218
- for (const [key, actionFn] of Object.entries(rawActions)) instance[key] = (...args) => runAction(meta, key, actionFn, args);
219
- }
220
- return instance;
59
+ //#region src/instance.d.ts
60
+ interface ModelConfig<TState extends StateShape, TActions, TViews> {
61
+ state: TState;
62
+ views?: (self: any) => TViews;
63
+ actions?: (self: any) => TActions;
221
64
  }
222
-
223
65
  //#endregion
224
- //#region src/model.ts
225
-
66
+ //#region src/model.d.ts
226
67
  /** Destroy a hook singleton by id so next call re-creates the instance. */
227
- function resetHook(id) {
228
- _hookRegistry.delete(id);
229
- }
68
+ declare function resetHook(id: string): void;
230
69
  /** Destroy all hook singletons. */
231
- function resetAllHooks() {
232
- _hookRegistry.clear();
70
+ declare function resetAllHooks(): void;
71
+ /**
72
+ * Returned by `model()`. Call `.create()` for instances or `.asHook(id)` for
73
+ * a Zustand-style singleton hook.
74
+ */
75
+ declare class ModelDefinition<TState extends StateShape, TActions extends Record<string, (...args: any[]) => any>, TViews extends Record<string, Signal<any> | Computed<any>>> {
76
+ /** Brand used to identify ModelDefinition objects at runtime (without instanceof). */
77
+ readonly [MODEL_BRAND]: true;
78
+ /** @internal — exposed so nested instance creation can read it. */
79
+ readonly _config: ModelConfig<TState, TActions, TViews>;
80
+ constructor(config: ModelConfig<TState, TActions, TViews>);
81
+ /**
82
+ * Create a new independent model instance.
83
+ * Pass a partial snapshot to override defaults.
84
+ *
85
+ * @example
86
+ * const counter = Counter.create({ count: 5 })
87
+ */
88
+ create(initial?: Partial<Snapshot<TState>>): ModelInstance<TState, TActions, TViews>;
89
+ /**
90
+ * Returns a hook function that always returns the same singleton instance
91
+ * for the given `id` — Zustand / Pinia style.
92
+ *
93
+ * @example
94
+ * const useCounter = Counter.asHook("app-counter")
95
+ * // Any call to useCounter() returns the same instance.
96
+ * const store = useCounter()
97
+ */
98
+ asHook(id: string): () => ModelInstance<TState, TActions, TViews>;
233
99
  }
234
100
  /**
235
- * Returned by `model()`. Call `.create()` for instances or `.asHook(id)` for
236
- * a Zustand-style singleton hook.
237
- */
238
-
101
+ * Define a reactive model with state, views, and actions.
102
+ *
103
+ * - **state** — plain JS object; each key becomes a `Signal<T>` on the instance.
104
+ * - **views** — factory receiving `self`; return computed signals for derived state.
105
+ * - **actions** — factory receiving `self`; return functions that mutate state.
106
+ *
107
+ * Use nested `ModelDefinition` values in `state` to compose models.
108
+ *
109
+ * @example
110
+ * const Counter = model({
111
+ * state: { count: 0 },
112
+ * views: (self) => ({
113
+ * doubled: computed(() => self.count() * 2),
114
+ * }),
115
+ * actions: (self) => ({
116
+ * inc: () => self.count.update(c => c + 1),
117
+ * reset: () => self.count.set(0),
118
+ * }),
119
+ * })
120
+ *
121
+ * const c = Counter.create({ count: 5 })
122
+ * c.count() // 5
123
+ * c.inc()
124
+ * c.doubled() // 12
125
+ */
126
+ declare function model<TState extends StateShape, TActions extends Record<string, (...args: any[]) => any> = Record<never, never>, TViews extends Record<string, Signal<any> | Computed<any>> = Record<never, never>>(config: ModelConfig<TState, TActions, TViews>): ModelDefinition<TState, TActions, TViews>;
127
+ //#endregion
128
+ //#region src/snapshot.d.ts
239
129
  /**
240
- * Define a reactive model with state, views, and actions.
241
- *
242
- * - **state** — plain JS object; each key becomes a `Signal<T>` on the instance.
243
- * - **views** — factory receiving `self`; return computed signals for derived state.
244
- * - **actions** factory receiving `self`; return functions that mutate state.
245
- *
246
- * Use nested `ModelDefinition` values in `state` to compose models.
247
- *
248
- * @example
249
- * const Counter = model({
250
- * state: { count: 0 },
251
- * views: (self) => ({
252
- * doubled: computed(() => self.count() * 2),
253
- * }),
254
- * actions: (self) => ({
255
- * inc: () => self.count.update(c => c + 1),
256
- * reset: () => self.count.set(0),
257
- * }),
258
- * })
259
- *
260
- * const c = Counter.create({ count: 5 })
261
- * c.count() // 5
262
- * c.inc()
263
- * c.doubled() // 12
264
- */
265
- function model(config) {
266
- return new ModelDefinition(config);
267
- }
268
-
130
+ * Serialize a model instance to a plain JS object (no signals, no functions).
131
+ * Nested model instances are recursively serialized.
132
+ *
133
+ * @example
134
+ * getSnapshot(counter) // { count: 6 }
135
+ * getSnapshot(app) // { profile: { name: "Alice" }, title: "My App" }
136
+ */
137
+ declare function getSnapshot<TState extends StateShape>(instance: object): Snapshot<TState>;
138
+ /**
139
+ * Restore a model instance from a plain-object snapshot.
140
+ * All signal writes are coalesced via `batch()` for a single reactive flush.
141
+ * Keys absent from the snapshot are left unchanged.
142
+ *
143
+ * @example
144
+ * applySnapshot(counter, { count: 0 })
145
+ */
146
+ declare function applySnapshot<TState extends StateShape>(instance: object, snapshot: Partial<Snapshot<TState>>): void;
269
147
  //#endregion
270
- //#region src/snapshot.ts
148
+ //#region src/patch.d.ts
271
149
  /**
272
- * Serialize a model instance to a plain JS object (no signals, no functions).
273
- * Nested model instances are recursively serialized.
274
- *
275
- * @example
276
- * getSnapshot(counter) // { count: 6 }
277
- * getSnapshot(app) // { profile: { name: "Alice" }, title: "My App" }
278
- */
279
- function getSnapshot(instance) {
280
- const meta = instanceMeta.get(instance);
281
- if (!meta) throw new Error("[@pyreon/state-tree] getSnapshot: not a model instance");
282
- const out = {};
283
- for (const key of meta.stateKeys) {
284
- const sig = instance[key];
285
- if (!sig) continue;
286
- const val = sig.peek();
287
- out[key] = isModelInstance(val) ? getSnapshot(val) : val;
288
- }
289
- return out;
290
- }
150
+ * Subscribe to every state mutation in `instance` as a JSON patch.
151
+ * Also captures mutations in nested model instances (path is prefixed).
152
+ *
153
+ * Returns an unsubscribe function.
154
+ *
155
+ * @example
156
+ * const unsub = onPatch(counter, patch => {
157
+ * // { op: "replace", path: "/count", value: 6 }
158
+ * })
159
+ */
160
+ declare function onPatch(instance: object, listener: PatchListener): () => void;
291
161
  /**
292
- * Restore a model instance from a plain-object snapshot.
293
- * All signal writes are coalesced via `batch()` for a single reactive flush.
294
- * Keys absent from the snapshot are left unchanged.
295
- *
296
- * @example
297
- * applySnapshot(counter, { count: 0 })
298
- */
299
- function applySnapshot(instance, snapshot) {
300
- const meta = instanceMeta.get(instance);
301
- if (!meta) throw new Error("[@pyreon/state-tree] applySnapshot: not a model instance");
302
- batch(() => {
303
- for (const key of meta.stateKeys) {
304
- if (!(key in snapshot)) continue;
305
- const sig = instance[key];
306
- if (!sig) continue;
307
- const val = snapshot[key];
308
- const current = sig.peek();
309
- if (isModelInstance(current)) applySnapshot(current, val);else sig.set(val);
310
- }
311
- });
312
- }
313
-
162
+ * Apply a JSON patch (or array of patches) to a model instance.
163
+ * Only "replace" operations are supported (matching the patches emitted by `onPatch`).
164
+ *
165
+ * Paths use JSON pointer format: `"/count"` for top-level, `"/profile/name"` for nested.
166
+ * Nested model instances are resolved automatically.
167
+ *
168
+ * @example
169
+ * applyPatch(counter, { op: "replace", path: "/count", value: 10 })
170
+ *
171
+ * @example
172
+ * // Replay patches recorded from onPatch (undo/redo, time-travel)
173
+ * applyPatch(counter, [
174
+ * { op: "replace", path: "/count", value: 1 },
175
+ * { op: "replace", path: "/count", value: 2 },
176
+ * ])
177
+ */
178
+ declare function applyPatch(instance: object, patch: Patch | Patch[]): void;
179
+ //#endregion
180
+ //#region src/middleware.d.ts
181
+ /**
182
+ * Intercept every action call on `instance`.
183
+ * Middlewares run in registration order — call `next(call)` to continue.
184
+ *
185
+ * Returns an unsubscribe function.
186
+ *
187
+ * @example
188
+ * const unsub = addMiddleware(counter, (call, next) => {
189
+ * console.log(`> ${call.name}(${call.args})`)
190
+ * const result = next(call)
191
+ * console.log(`< ${call.name}`)
192
+ * return result
193
+ * })
194
+ */
195
+ declare function addMiddleware(instance: object, middleware: MiddlewareFn): () => void;
314
196
  //#endregion
315
- export { addMiddleware, applyPatch, applySnapshot, getSnapshot, model, onPatch, resetAllHooks, resetHook };
316
- //# sourceMappingURL=index.d.ts.map
197
+ export { type ActionCall, type MiddlewareFn, type ModelDefinition, type ModelInstance, type ModelSelf, type Patch, type PatchListener, type Snapshot, type StateShape, addMiddleware, applyPatch, applySnapshot, getSnapshot, model, onPatch, resetAllHooks, resetHook };
198
+ //# sourceMappingURL=index2.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/registry.ts","../../src/middleware.ts","../../src/patch.ts","../../src/types.ts","../../src/instance.ts","../../src/model.ts","../../src/snapshot.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index2.d.ts","names":[],"sources":["../../../src/types.ts","../../../src/instance.ts","../../../src/model.ts","../../../src/snapshot.ts","../../../src/patch.ts","../../../src/middleware.ts"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyreon/state-tree",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "Structured reactive state tree — composable models with snapshots, patches, and middleware",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -45,6 +45,6 @@
45
45
  "typecheck": "tsc --noEmit"
46
46
  },
47
47
  "peerDependencies": {
48
- "@pyreon/reactivity": ">=0.5.0 <1.0.0"
48
+ "@pyreon/reactivity": ">=0.7.0 <0.8.0"
49
49
  }
50
50
  }
@@ -1,40 +0,0 @@
1
- //#region src/devtools.d.ts
2
- /**
3
- * @pyreon/state-tree devtools introspection API.
4
- * Import: `import { ... } from "@pyreon/state-tree/devtools"`
5
- */
6
- /**
7
- * Register a model instance for devtools inspection.
8
- * Call this when creating instances you want visible in devtools.
9
- *
10
- * @example
11
- * const counter = Counter.create()
12
- * registerInstance("app-counter", counter)
13
- */
14
- declare function registerInstance(name: string, instance: object): void;
15
- /**
16
- * Unregister a model instance.
17
- */
18
- declare function unregisterInstance(name: string): void;
19
- /**
20
- * Get all registered model instance names.
21
- * Automatically cleans up garbage-collected instances.
22
- */
23
- declare function getActiveModels(): string[];
24
- /**
25
- * Get a model instance by name (or undefined if GC'd or not registered).
26
- */
27
- declare function getModelInstance(name: string): object | undefined;
28
- /**
29
- * Get a snapshot of a registered model instance.
30
- */
31
- declare function getModelSnapshot(name: string): Record<string, unknown> | undefined;
32
- /**
33
- * Subscribe to model registry changes. Returns unsubscribe function.
34
- */
35
- declare function onModelChange(listener: () => void): () => void;
36
- /** @internal — reset devtools registry (for tests). */
37
- declare function _resetDevtools(): void;
38
- //#endregion
39
- export { _resetDevtools, getActiveModels, getModelInstance, getModelSnapshot, onModelChange, registerInstance, unregisterInstance };
40
- //# sourceMappingURL=devtools2.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"devtools2.d.ts","names":[],"sources":["../../src/devtools.ts"],"mappings":";;AAuBA;;;;;AAQA;;;;;AASA;iBAjBgB,gBAAA,CAAiB,IAAA,UAAc,QAAA;;;;iBAQ/B,kBAAA,CAAmB,IAAA;;;;;iBASnB,eAAA,CAAA;;;;iBAUA,gBAAA,CAAiB,IAAA;AAyBjC;;;AAAA,iBAXgB,gBAAA,CACd,IAAA,WACC,MAAA;;AAiBH;;iBARgB,aAAA,CAAc,QAAA;;iBAQd,cAAA,CAAA"}
@@ -1,198 +0,0 @@
1
- import { Computed, Signal } from "@pyreon/reactivity";
2
-
3
- //#region src/types.d.ts
4
- /** Property key stamped on every ModelDefinition to distinguish it from plain objects. */
5
- declare const MODEL_BRAND: "__pyreonMod";
6
- type StateShape = Record<string, unknown>;
7
- /**
8
- * Resolve a state field type:
9
- * - ModelDefinition → the instance type it produces
10
- * - Anything else → as-is
11
- */
12
- type ResolveField<T> = T extends {
13
- readonly __pyreonMod: true;
14
- create(initial?: any): infer I;
15
- } ? I : T;
16
- /** Map state shape to per-field signals. */
17
- type StateSignals<TState extends StateShape> = { readonly [K in keyof TState]: Signal<ResolveField<TState[K]>> };
18
- /**
19
- * `self` type inside actions / views:
20
- * strongly typed for state signals, `any` for actions and views so that
21
- * actions can call each other without circular type issues.
22
- */
23
- type ModelSelf<TState extends StateShape> = StateSignals<TState> & Record<string, any>;
24
- /** The public instance type returned by `.create()` and hooks. */
25
- type ModelInstance<TState extends StateShape, TActions extends Record<string, (...args: any[]) => any>, TViews extends Record<string, Signal<any> | Computed<any>>> = StateSignals<TState> & TActions & TViews;
26
- /**
27
- * Extract the state type from a ModelDefinition.
28
- * Used by Snapshot to recursively resolve nested model types.
29
- */
30
- type ExtractModelState<T> = T extends {
31
- readonly __pyreonMod: true;
32
- readonly _config: {
33
- state: infer S extends StateShape;
34
- };
35
- } ? S : never;
36
- /**
37
- * Snapshot type: plain JS values (no signals, no model instances).
38
- * Nested model fields recursively produce their own typed snapshot.
39
- */
40
- type Snapshot<TState extends StateShape> = { [K in keyof TState]: TState[K] extends {
41
- readonly __pyreonMod: true;
42
- } ? Snapshot<ExtractModelState<TState[K]>> : TState[K] };
43
- interface Patch {
44
- op: 'replace';
45
- path: string;
46
- value: unknown;
47
- }
48
- type PatchListener = (patch: Patch) => void;
49
- interface ActionCall {
50
- /** Action name. */
51
- name: string;
52
- /** Arguments passed to the action. */
53
- args: unknown[];
54
- /** JSON-pointer-style path, e.g. `"/inc"`. */
55
- path: string;
56
- }
57
- type MiddlewareFn = (call: ActionCall, next: (nextCall: ActionCall) => unknown) => unknown;
58
- //#endregion
59
- //#region src/instance.d.ts
60
- interface ModelConfig<TState extends StateShape, TActions, TViews> {
61
- state: TState;
62
- views?: (self: any) => TViews;
63
- actions?: (self: any) => TActions;
64
- }
65
- //#endregion
66
- //#region src/model.d.ts
67
- /** Destroy a hook singleton by id so next call re-creates the instance. */
68
- declare function resetHook(id: string): void;
69
- /** Destroy all hook singletons. */
70
- declare function resetAllHooks(): void;
71
- /**
72
- * Returned by `model()`. Call `.create()` for instances or `.asHook(id)` for
73
- * a Zustand-style singleton hook.
74
- */
75
- declare class ModelDefinition<TState extends StateShape, TActions extends Record<string, (...args: any[]) => any>, TViews extends Record<string, Signal<any> | Computed<any>>> {
76
- /** Brand used to identify ModelDefinition objects at runtime (without instanceof). */
77
- readonly [MODEL_BRAND]: true;
78
- /** @internal — exposed so nested instance creation can read it. */
79
- readonly _config: ModelConfig<TState, TActions, TViews>;
80
- constructor(config: ModelConfig<TState, TActions, TViews>);
81
- /**
82
- * Create a new independent model instance.
83
- * Pass a partial snapshot to override defaults.
84
- *
85
- * @example
86
- * const counter = Counter.create({ count: 5 })
87
- */
88
- create(initial?: Partial<Snapshot<TState>>): ModelInstance<TState, TActions, TViews>;
89
- /**
90
- * Returns a hook function that always returns the same singleton instance
91
- * for the given `id` — Zustand / Pinia style.
92
- *
93
- * @example
94
- * const useCounter = Counter.asHook("app-counter")
95
- * // Any call to useCounter() returns the same instance.
96
- * const store = useCounter()
97
- */
98
- asHook(id: string): () => ModelInstance<TState, TActions, TViews>;
99
- }
100
- /**
101
- * Define a reactive model with state, views, and actions.
102
- *
103
- * - **state** — plain JS object; each key becomes a `Signal<T>` on the instance.
104
- * - **views** — factory receiving `self`; return computed signals for derived state.
105
- * - **actions** — factory receiving `self`; return functions that mutate state.
106
- *
107
- * Use nested `ModelDefinition` values in `state` to compose models.
108
- *
109
- * @example
110
- * const Counter = model({
111
- * state: { count: 0 },
112
- * views: (self) => ({
113
- * doubled: computed(() => self.count() * 2),
114
- * }),
115
- * actions: (self) => ({
116
- * inc: () => self.count.update(c => c + 1),
117
- * reset: () => self.count.set(0),
118
- * }),
119
- * })
120
- *
121
- * const c = Counter.create({ count: 5 })
122
- * c.count() // 5
123
- * c.inc()
124
- * c.doubled() // 12
125
- */
126
- declare function model<TState extends StateShape, TActions extends Record<string, (...args: any[]) => any> = Record<never, never>, TViews extends Record<string, Signal<any> | Computed<any>> = Record<never, never>>(config: ModelConfig<TState, TActions, TViews>): ModelDefinition<TState, TActions, TViews>;
127
- //#endregion
128
- //#region src/snapshot.d.ts
129
- /**
130
- * Serialize a model instance to a plain JS object (no signals, no functions).
131
- * Nested model instances are recursively serialized.
132
- *
133
- * @example
134
- * getSnapshot(counter) // { count: 6 }
135
- * getSnapshot(app) // { profile: { name: "Alice" }, title: "My App" }
136
- */
137
- declare function getSnapshot<TState extends StateShape>(instance: object): Snapshot<TState>;
138
- /**
139
- * Restore a model instance from a plain-object snapshot.
140
- * All signal writes are coalesced via `batch()` for a single reactive flush.
141
- * Keys absent from the snapshot are left unchanged.
142
- *
143
- * @example
144
- * applySnapshot(counter, { count: 0 })
145
- */
146
- declare function applySnapshot<TState extends StateShape>(instance: object, snapshot: Partial<Snapshot<TState>>): void;
147
- //#endregion
148
- //#region src/patch.d.ts
149
- /**
150
- * Subscribe to every state mutation in `instance` as a JSON patch.
151
- * Also captures mutations in nested model instances (path is prefixed).
152
- *
153
- * Returns an unsubscribe function.
154
- *
155
- * @example
156
- * const unsub = onPatch(counter, patch => {
157
- * // { op: "replace", path: "/count", value: 6 }
158
- * })
159
- */
160
- declare function onPatch(instance: object, listener: PatchListener): () => void;
161
- /**
162
- * Apply a JSON patch (or array of patches) to a model instance.
163
- * Only "replace" operations are supported (matching the patches emitted by `onPatch`).
164
- *
165
- * Paths use JSON pointer format: `"/count"` for top-level, `"/profile/name"` for nested.
166
- * Nested model instances are resolved automatically.
167
- *
168
- * @example
169
- * applyPatch(counter, { op: "replace", path: "/count", value: 10 })
170
- *
171
- * @example
172
- * // Replay patches recorded from onPatch (undo/redo, time-travel)
173
- * applyPatch(counter, [
174
- * { op: "replace", path: "/count", value: 1 },
175
- * { op: "replace", path: "/count", value: 2 },
176
- * ])
177
- */
178
- declare function applyPatch(instance: object, patch: Patch | Patch[]): void;
179
- //#endregion
180
- //#region src/middleware.d.ts
181
- /**
182
- * Intercept every action call on `instance`.
183
- * Middlewares run in registration order — call `next(call)` to continue.
184
- *
185
- * Returns an unsubscribe function.
186
- *
187
- * @example
188
- * const unsub = addMiddleware(counter, (call, next) => {
189
- * console.log(`> ${call.name}(${call.args})`)
190
- * const result = next(call)
191
- * console.log(`< ${call.name}`)
192
- * return result
193
- * })
194
- */
195
- declare function addMiddleware(instance: object, middleware: MiddlewareFn): () => void;
196
- //#endregion
197
- export { type ActionCall, type MiddlewareFn, type ModelDefinition, type ModelInstance, type ModelSelf, type Patch, type PatchListener, type Snapshot, type StateShape, addMiddleware, applyPatch, applySnapshot, getSnapshot, model, onPatch, resetAllHooks, resetHook };
198
- //# sourceMappingURL=index2.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index2.d.ts","names":[],"sources":["../../src/types.ts","../../src/instance.ts","../../src/model.ts","../../src/snapshot.ts","../../src/patch.ts","../../src/middleware.ts"],"mappings":"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"}