@pyreon/state-tree 0.0.1

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.
@@ -0,0 +1,40 @@
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
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,316 @@
1
+ import { batch, signal } from "@pyreon/reactivity";
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
16
+ /**
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
+ }
35
+ /**
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
+
63
+ /**
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()));
85
+ };
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
+ }
101
+ /**
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);
117
+ }
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
+ });
162
+ }
163
+
164
+ //#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;
221
+ }
222
+
223
+ //#endregion
224
+ //#region src/model.ts
225
+
226
+ /** Destroy a hook singleton by id so next call re-creates the instance. */
227
+ function resetHook(id) {
228
+ _hookRegistry.delete(id);
229
+ }
230
+ /** Destroy all hook singletons. */
231
+ function resetAllHooks() {
232
+ _hookRegistry.clear();
233
+ }
234
+ /**
235
+ * Returned by `model()`. Call `.create()` for instances or `.asHook(id)` for
236
+ * a Zustand-style singleton hook.
237
+ */
238
+
239
+ /**
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
+
269
+ //#endregion
270
+ //#region src/snapshot.ts
271
+ /**
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
+ }
291
+ /**
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
+
314
+ //#endregion
315
+ export { addMiddleware, applyPatch, applySnapshot, getSnapshot, model, onPatch, resetAllHooks, resetHook };
316
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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":";;;;;;;;;AASA,SAAgB,eAAA,CAAgB,KAAA,EAAyB;EACvD,OACE,KAAA,IAAS,IAAA,IACT,OAAO,KAAA,KAAU,QAAA,IACjB,YAAA,CAAa,GAAA,CAAI,KAAA,CAAgB;;;;;;;;;;ACHrC,SAAgB,SAAA,CACd,IAAA,EACA,IAAA,EACA,EAAA,EACA,IAAA,EACS;EACT,MAAM,IAAA,GAAmB;IAAE,IAAA;IAAM,IAAA;IAAM,IAAA,EAAM,IAAI,IAAA;GAAQ;EAEzD,MAAM,QAAA,GAAA,CAAY,GAAA,EAAa,CAAA,KAA2B;IACxD,IAAI,GAAA,IAAO,IAAA,CAAK,WAAA,CAAY,MAAA,EAAQ,OAAO,EAAA,CAAG,GAAG,CAAA,CAAE,IAAA,CAAK;IACxD,MAAM,EAAA,GAAK,IAAA,CAAK,WAAA,CAAY,GAAA,CAAA;IAC5B,IAAI,CAAC,EAAA,EAAI,OAAO,EAAA,CAAG,GAAG,CAAA,CAAE,IAAA,CAAK;IAC7B,OAAO,EAAA,CAAG,CAAA,EAAI,QAAA,IAAa,QAAA,CAAS,GAAA,GAAM,CAAA,EAAG,QAAA,CAAS,CAAC;;EAGzD,OAAO,QAAA,CAAS,CAAA,EAAG,IAAA,CAAK;;;;;;;;;;;;;;;;AAmB1B,SAAgB,aAAA,CACd,QAAA,EACA,UAAA,EACY;EACZ,MAAM,IAAA,GAAO,YAAA,CAAa,GAAA,CAAI,QAAA,CAAS;EACvC,IAAI,CAAC,IAAA,EACH,MAAM,IAAI,KAAA,CAAM,0DAAA,CAA2D;EAC7E,IAAA,CAAK,WAAA,CAAY,IAAA,CAAK,UAAA,CAAW;EACjC,OAAA,MAAa;IACX,MAAM,GAAA,GAAM,IAAA,CAAK,WAAA,CAAY,OAAA,CAAQ,UAAA,CAAW;IAChD,IAAI,GAAA,KAAQ,CAAA,CAAA,EAAI,IAAA,CAAK,WAAA,CAAY,MAAA,CAAO,GAAA,EAAK,CAAA,CAAE;;;;;;;;;;;;;;;ACrCnD,SAAgB,aAAA,CACd,KAAA,EACA,IAAA,EACA,SAAA,EACA,YAAA,EACW;EACX,MAAM,IAAA,GAAA,CAAA,KAAgB,KAAA,CAAA,CAAO;EAE7B,IAAA,CAAK,IAAA,GAAA,MAAgB,KAAA,CAAM,IAAA,CAAA,CAAM;EAEjC,IAAA,CAAK,SAAA,GAAa,QAAA,IAChB,KAAA,CAAM,SAAA,CAAU,QAAA,CAAS;EAE3B,IAAA,CAAK,GAAA,GAAO,QAAA,IAAsB;IAChC,MAAM,IAAA,GAAO,KAAA,CAAM,IAAA,CAAA,CAAM;IACzB,KAAA,CAAM,GAAA,CAAI,QAAA,CAAS;IAGnB,IAAI,CAAC,MAAA,CAAO,EAAA,CAAG,IAAA,EAAM,QAAA,CAAS,KAAK,CAAC,YAAA,IAAgB,YAAA,CAAA,CAAc,CAAA,EAMhE,SAAA,CAAU;MAAE,EAAA,EAAI,SAAA;MAAW,IAAA;MAAM,KAAA,EAHd,eAAA,CAAgB,QAAA,CAAS,GACxC,aAAA,CAAc,QAAA,CAAmB,GACjC;KACgD,CAAC;;EAIzD,IAAA,CAAK,MAAA,GAAU,EAAA,IAAgC;IAC7C,IAAA,CAAK,GAAA,CAAI,EAAA,CAAG,KAAA,CAAM,IAAA,CAAA,CAAM,CAAC,CAAC;;EAG5B,OAAO,IAAA;;;AAIT,SAAS,aAAA,CAAc,QAAA,EAA2C;EAChE,MAAM,IAAA,GAAO,YAAA,CAAa,GAAA,CAAI,QAAA,CAAS;EACvC,IAAI,CAAC,IAAA,EAAM,OAAO,QAAA;EAClB,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,aAAA,CAAc,GAAA,CAAc,GAAG,GAAA;;EAEnE,OAAO,GAAA;;;;;;;;;;;;;AAgBT,SAAgB,OAAA,CAAQ,QAAA,EAAkB,QAAA,EAAqC;EAC7E,MAAM,IAAA,GAAO,YAAA,CAAa,GAAA,CAAI,QAAA,CAAS;EACvC,IAAI,CAAC,IAAA,EACH,MAAM,IAAI,KAAA,CAAM,oDAAA,CAAqD;EACvE,IAAA,CAAK,cAAA,CAAe,GAAA,CAAI,QAAA,CAAS;EACjC,OAAA,MAAa,IAAA,CAAK,cAAA,CAAe,MAAA,CAAO,QAAA,CAAS;;;;;;;;;;;;;;;;;;;AAsBnD,SAAgB,UAAA,CAAW,QAAA,EAAkB,KAAA,EAA8B;EACzE,MAAM,OAAA,GAAU,KAAA,CAAM,OAAA,CAAQ,KAAA,CAAM,GAAG,KAAA,GAAQ,CAAC,KAAA,CAAM;EAEtD,KAAA,CAAA,MAAY;IACV,KAAK,MAAM,CAAA,IAAK,OAAA,EAAS;MACvB,IAAI,CAAA,CAAE,EAAA,KAAO,SAAA,EACX,MAAM,IAAI,KAAA,CACR,oDAAoD,CAAA,CAAE,EAAA,GAAG,CAC1D;MAGH,MAAM,QAAA,GAAW,CAAA,CAAE,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,CAAC,MAAA,CAAO,OAAA,CAAQ;MAClD,IAAI,QAAA,CAAS,MAAA,KAAW,CAAA,EACtB,MAAM,IAAI,KAAA,CAAM,6CAAA,CAA8C;MAIhE,IAAI,MAAA,GAAiB,QAAA;MACrB,KAAK,IAAI,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,QAAA,CAAS,MAAA,GAAS,CAAA,EAAG,CAAA,EAAA,EAAK;QAC5C,MAAM,OAAA,GAAU,QAAA,CAAS,CAAA,CAAA;QACzB,IAAI,aAAA,CAAc,GAAA,CAAI,OAAA,CAAQ,EAC5B,MAAM,IAAI,KAAA,CACR,4DAA4D,OAAA,GAAQ,CACrE;QAGH,IAAI,CADS,YAAA,CAAa,GAAA,CAAI,MAAA,CAAO,EAEnC,MAAM,IAAI,KAAA,CACR,6DAA6D,OAAA,GAAQ,CACtE;QACH,MAAM,GAAA,GAAO,MAAA,CAA2C,OAAA,CAAA;QACxD,IAAI,CAAC,GAAA,IAAO,OAAO,GAAA,CAAI,IAAA,KAAS,UAAA,EAC9B,MAAM,IAAI,KAAA,CACR,uDAAuD,OAAA,GAAQ,CAChE;QAEH,MAAM,MAAA,GAAS,GAAA,CAAI,IAAA,CAAA,CAAM;QACzB,IAAI,CAAC,MAAA,IAAU,OAAO,MAAA,KAAW,QAAA,IAAY,CAAC,eAAA,CAAgB,MAAA,CAAO,EACnE,MAAM,IAAI,KAAA,CACR,qCAAqC,OAAA,kCAAQ,CAC9C;QAEH,MAAA,GAAS,MAAA;;MAGX,MAAM,OAAA,GAAU,QAAA,CAAS,QAAA,CAAS,MAAA,GAAS,CAAA,CAAA;MAC3C,IAAI,aAAA,CAAc,GAAA,CAAI,OAAA,CAAQ,EAC5B,MAAM,IAAI,KAAA,CACR,4DAA4D,OAAA,GAAQ,CACrE;MAEH,MAAM,IAAA,GAAO,YAAA,CAAa,GAAA,CAAI,MAAA,CAAO;MACrC,IAAI,CAAC,IAAA,EACH,MAAM,IAAI,KAAA,CAAM,uDAAA,CAAwD;MAC1E,IAAI,CAAC,IAAA,CAAK,SAAA,CAAU,QAAA,CAAS,OAAA,CAAQ,EACnC,MAAM,IAAI,KAAA,CACR,uDAAuD,OAAA,GAAQ,CAChE;MAGH,MAAM,GAAA,GAAO,MAAA,CAA2C,OAAA,CAAA;MACxD,IAAI,GAAA,IAAO,OAAO,GAAA,CAAI,GAAA,KAAQ,UAAA,EAC5B,GAAA,CAAI,GAAA,CAAI,CAAA,CAAE,KAAA,CAAM;;IAGpB;;;;;;;;;AExJJ,SAAS,UAAA,CAAW,CAAA,EAA8B;EAChD,IAAI,CAAA,IAAK,IAAA,IAAQ,OAAO,CAAA,KAAM,QAAA,EAAU,OAAO,KAAA;EAC/C,OAAQ,CAAA,CAA8B,WAAA,CAAA,KAAiB,IAAA;;;;;;AAiBzD,SAAgB,cAAA,CAKd,MAAA,EACA,OAAA,EACyC;EAEzC,MAAM,QAAA,GAAoC,CAAA,CAAE;EAG5C,MAAM,IAAA,GAAqB;IACzB,SAAA,EAAW,EAAE;IACb,cAAA,EAAA,eAAgB,IAAI,GAAA,CAAA,CAAK;IACzB,WAAA,EAAa,EAAE;IACf,SAAA,CAAU,KAAA,EAAO;MAEf,IAAI,IAAA,CAAK,cAAA,CAAe,IAAA,KAAS,CAAA,EAAG;MACpC,KAAK,MAAM,QAAA,IAAY,IAAA,CAAK,cAAA,EAAgB,QAAA,CAAS,KAAA,CAAM;;GAE9D;EACD,YAAA,CAAa,GAAA,CAAI,QAAA,EAAU,IAAA,CAAK;EAIhC,MAAM,IAAA,GAAO,IAAI,KAAA,CAAM,QAAA,EAAU;IAC/B,GAAA,CAAI,CAAA,EAAG,CAAA,EAAG;MACR,OAAO,QAAA,CAAS,CAAA,CAAA;;GAEnB,CAAC;EAGF,KAAK,MAAM,CAAC,GAAA,EAAK,YAAA,CAAA,IAAiB,MAAA,CAAO,OAAA,CAAQ,MAAA,CAAO,KAAA,CAAM,EAAE;IAC9D,IAAA,CAAK,SAAA,CAAU,IAAA,CAAK,GAAA,CAAI;IACxB,MAAM,IAAA,GAAO,IAAI,GAAA,EAAA;IACjB,MAAM,SAAA,GACJ,GAAA,IAAO,OAAA,GAAW,OAAA,CAAoC,GAAA,CAAA,GAAO,KAAA,CAAA;IAE/D,IAAI,MAAA;IAEJ,IAAI,UAAA,CAAW,YAAA,CAAa,EAAE;MAE5B,MAAM,cAAA,GAAiB,cAAA,CACrB,YAAA,CAAa,OAAA,EACZ,SAAA,IAAyC,CAAA,CAAE,CAC7C;MACD,MAAA,GAAS,MAAA,CAAO,cAAA,CAAe;MAG/B,OAAA,CAAQ,cAAA,EAAiB,KAAA,IAAU;QACjC,IAAA,CAAK,SAAA,CAAU;UAAE,GAAG,KAAA;UAAO,IAAA,EAAM,IAAA,GAAO,KAAA,CAAM;SAAM,CAAC;QACrD;WAEF,MAAA,GAAS,MAAA,CAAO,SAAA,KAAc,KAAA,CAAA,GAAY,SAAA,GAAY,YAAA,CAAa;IASrE,QAAA,CAAS,GAAA,CAAA,GANO,aAAA,CACd,MAAA,EACA,IAAA,EACC,CAAA,IAAM,IAAA,CAAK,SAAA,CAAU,CAAA,CAAE,EAAA,MAClB,IAAA,CAAK,cAAA,CAAe,IAAA,GAAO,CAAA,CAClC;;EAKH,IAAI,MAAA,CAAO,KAAA,EAAO;IAChB,MAAM,KAAA,GAAQ,MAAA,CAAO,KAAA,CAAM,IAAA,CAAK;IAChC,KAAK,MAAM,CAAC,GAAA,EAAK,IAAA,CAAA,IAAS,MAAA,CAAO,OAAA,CAC/B,KAAA,CACD,EACC,QAAA,CAAS,GAAA,CAAA,GAAO,IAAA;;EAKpB,IAAI,MAAA,CAAO,OAAA,EAAS;IAClB,MAAM,UAAA,GAAa,MAAA,CAAO,OAAA,CAAQ,IAAA,CAAK;IAIvC,KAAK,MAAM,CAAC,GAAA,EAAK,QAAA,CAAA,IAAa,MAAA,CAAO,OAAA,CAAQ,UAAA,CAAW,EACtD,QAAA,CAAS,GAAA,CAAA,GAAA,CAAQ,GAAG,IAAA,KAClB,SAAA,CAAU,IAAA,EAAM,GAAA,EAAK,QAAA,EAAU,IAAA,CAAK;;EAI1C,OAAO,QAAA;;;;;;;AClHT,SAAgB,SAAA,CAAU,EAAA,EAAkB;EAC1C,aAAA,CAAc,MAAA,CAAO,EAAA,CAAG;;;AAI1B,SAAgB,aAAA,CAAA,EAAsB;EACpC,aAAA,CAAc,KAAA,CAAA,CAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoFvB,SAAgB,KAAA,CAWd,MAAA,EAC2C;EAC3C,OAAO,IAAI,eAAA,CAAgB,MAAA,CAAO;;;;;;;;;;;;;ACpGpC,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;;;;;;;;;;AAaT,SAAgB,aAAA,CACd,QAAA,EACA,QAAA,EACM;EACN,MAAM,IAAA,GAAO,YAAA,CAAa,GAAA,CAAI,QAAA,CAAS;EACvC,IAAI,CAAC,IAAA,EACH,MAAM,IAAI,KAAA,CAAM,0DAAA,CAA2D;EAE7E,KAAA,CAAA,MAAY;IACV,KAAK,MAAM,GAAA,IAAO,IAAA,CAAK,SAAA,EAAW;MAChC,IAAI,EAAE,GAAA,IAAO,QAAA,CAAA,EAAW;MACxB,MAAM,GAAA,GAAO,QAAA,CAA6C,GAAA,CAAA;MAC1D,IAAI,CAAC,GAAA,EAAK;MACV,MAAM,GAAA,GAAO,QAAA,CAAqC,GAAA,CAAA;MAClD,MAAM,OAAA,GAAU,GAAA,CAAI,IAAA,CAAA,CAAM;MAC1B,IAAI,eAAA,CAAgB,OAAA,CAAQ,EAE1B,aAAA,CAAc,OAAA,EAAmB,GAAA,CAA+B,CAAA,KAEhE,GAAA,CAAI,GAAA,CAAI,GAAA,CAAI;;IAGhB"}
@@ -0,0 +1,198 @@
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
@@ -0,0 +1 @@
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 ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@pyreon/state-tree",
3
+ "version": "0.0.1",
4
+ "description": "Structured reactive state tree — composable models with snapshots, patches, and middleware",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/pyreon/fundamentals.git",
9
+ "directory": "packages/state-tree"
10
+ },
11
+ "homepage": "https://github.com/pyreon/fundamentals/tree/main/packages/state-tree#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/pyreon/fundamentals/issues"
14
+ },
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "files": [
19
+ "lib",
20
+ "src",
21
+ "README.md",
22
+ "LICENSE"
23
+ ],
24
+ "type": "module",
25
+ "main": "./lib/index.js",
26
+ "module": "./lib/index.js",
27
+ "types": "./lib/types/index.d.ts",
28
+ "exports": {
29
+ ".": {
30
+ "bun": "./src/index.ts",
31
+ "import": "./lib/index.js",
32
+ "types": "./lib/types/index.d.ts"
33
+ },
34
+ "./devtools": {
35
+ "bun": "./src/devtools.ts",
36
+ "import": "./lib/devtools.js",
37
+ "types": "./lib/types/devtools.d.ts"
38
+ }
39
+ },
40
+ "sideEffects": false,
41
+ "scripts": {
42
+ "build": "vl_rolldown_build",
43
+ "dev": "vl_rolldown_build-watch",
44
+ "test": "vitest run",
45
+ "typecheck": "tsc --noEmit"
46
+ },
47
+ "peerDependencies": {
48
+ "@pyreon/reactivity": "^0.2.1"
49
+ },
50
+ "devDependencies": {
51
+ "@happy-dom/global-registrator": "^20.8.3",
52
+ "@pyreon/reactivity": "^0.2.1"
53
+ }
54
+ }