@pyreon/state-tree 0.22.0 → 0.23.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
@@ -1,11 +1,13 @@
1
1
  # @pyreon/state-tree
2
2
 
3
- Structured reactive state trees with signal-backed models, computed views, actions, snapshots, JSON patches, and middleware.
3
+ MobX-State-Tree-inspired structured models on signals `model({ state, views, actions })`.
4
+
5
+ Structured reactive state trees built on `@pyreon/reactivity`: `model({ state, views, actions })` composes signal-backed state, computed views, and the only legal mutation path (actions). Supports nested model composition, typed snapshots, JSON-patch record/replay (replace only), and action interception middleware. Fits when `@pyreon/store` is too flat — use it for tree-shaped domain models with undo/redo, time-travel, or audit-log requirements.
4
6
 
5
7
  ## Install
6
8
 
7
9
  ```bash
8
- bun add @pyreon/state-tree
10
+ bun add @pyreon/state-tree @pyreon/reactivity
9
11
  ```
10
12
 
11
13
  ## Quick Start
@@ -247,3 +249,19 @@ applyPatch(counter, history) // replays to count: 2
247
249
  - `applySnapshot` leaves missing keys unchanged; it does not delete extra state.
248
250
  - `self` inside actions/views is loosely typed for non-state keys to prevent circular type resolution. Use explicit types if needed.
249
251
  - Always call `resetAllHooks()` in test `afterEach` to prevent singleton leakage between tests.
252
+
253
+ ## Devtools
254
+
255
+ ```ts
256
+ import { stateTreeRegistry } from '@pyreon/state-tree/devtools'
257
+ ```
258
+
259
+ WeakRef-based registry of live model instances — tree-shakeable. Used by the Pyreon devtools panel.
260
+
261
+ ## Documentation
262
+
263
+ Full docs: [docs.pyreon.dev/docs/state-tree](https://docs.pyreon.dev/docs/state-tree) (or `docs/docs/state-tree.md` in this repo).
264
+
265
+ ## License
266
+
267
+ MIT
@@ -0,0 +1,62 @@
1
+ import { batch } 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
+ const instanceMeta = /* @__PURE__ */ new WeakMap();
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/snapshot.ts
16
+ /**
17
+ * Serialize a model instance to a plain JS object (no signals, no functions).
18
+ * Nested model instances are recursively serialized.
19
+ *
20
+ * @example
21
+ * getSnapshot(counter) // { count: 6 }
22
+ * getSnapshot(app) // { profile: { name: "Alice" }, title: "My App" }
23
+ */
24
+ function getSnapshot(instance) {
25
+ const meta = instanceMeta.get(instance);
26
+ if (!meta) throw new Error("[@pyreon/state-tree] getSnapshot: not a model instance");
27
+ const out = {};
28
+ for (const key of meta.stateKeys) {
29
+ const sig = instance[key];
30
+ if (!sig) continue;
31
+ const val = sig.peek();
32
+ out[key] = isModelInstance(val) ? getSnapshot(val) : val;
33
+ }
34
+ return out;
35
+ }
36
+ /**
37
+ * Restore a model instance from a plain-object snapshot.
38
+ * All signal writes are coalesced via `batch()` for a single reactive flush.
39
+ * Keys absent from the snapshot are left unchanged.
40
+ *
41
+ * @example
42
+ * applySnapshot(counter, { count: 0 })
43
+ */
44
+ function applySnapshot(instance, snapshot) {
45
+ const meta = instanceMeta.get(instance);
46
+ if (!meta) throw new Error("[@pyreon/state-tree] applySnapshot: not a model instance");
47
+ batch(() => {
48
+ for (const key of meta.stateKeys) {
49
+ if (!(key in snapshot)) continue;
50
+ const sig = instance[key];
51
+ if (!sig) continue;
52
+ const val = snapshot[key];
53
+ const current = sig.peek();
54
+ if (isModelInstance(current)) applySnapshot(current, val);
55
+ else sig.set(val);
56
+ }
57
+ });
58
+ }
59
+
60
+ //#endregion
61
+ export { isModelInstance as i, getSnapshot as n, instanceMeta as r, applySnapshot as t };
62
+ //# sourceMappingURL=snapshot-dNsO8P8p.js.map
@@ -5386,7 +5386,7 @@ var drawChart = (function (exports) {
5386
5386
  </script>
5387
5387
  <script>
5388
5388
  /*<!--*/
5389
- const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"02da3037-1","name":"registry.ts"},{"uid":"02da3037-3","name":"middleware.ts"},{"uid":"02da3037-5","name":"patch.ts"},{"uid":"02da3037-7","name":"types.ts"},{"uid":"02da3037-9","name":"instance.ts"},{"uid":"02da3037-11","name":"model.ts"},{"uid":"02da3037-13","name":"snapshot.ts"},{"uid":"02da3037-15","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"02da3037-1":{"renderedLength":420,"gzipLength":292,"brotliLength":0,"metaUid":"02da3037-0"},"02da3037-3":{"renderedLength":1291,"gzipLength":627,"brotliLength":0,"metaUid":"02da3037-2"},"02da3037-5":{"renderedLength":4252,"gzipLength":1554,"brotliLength":0,"metaUid":"02da3037-4"},"02da3037-7":{"renderedLength":162,"gzipLength":155,"brotliLength":0,"metaUid":"02da3037-6"},"02da3037-9":{"renderedLength":1691,"gzipLength":758,"brotliLength":0,"metaUid":"02da3037-8"},"02da3037-11":{"renderedLength":2187,"gzipLength":1001,"brotliLength":0,"metaUid":"02da3037-10"},"02da3037-13":{"renderedLength":1396,"gzipLength":602,"brotliLength":0,"metaUid":"02da3037-12"},"02da3037-15":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"02da3037-14"}},"nodeMetas":{"02da3037-0":{"id":"/src/registry.ts","moduleParts":{"index.js":"02da3037-1"},"imported":[],"importedBy":[{"uid":"02da3037-12"},{"uid":"02da3037-4"},{"uid":"02da3037-2"},{"uid":"02da3037-8"}]},"02da3037-2":{"id":"/src/middleware.ts","moduleParts":{"index.js":"02da3037-3"},"imported":[{"uid":"02da3037-0"}],"importedBy":[{"uid":"02da3037-14"},{"uid":"02da3037-8"}]},"02da3037-4":{"id":"/src/patch.ts","moduleParts":{"index.js":"02da3037-5"},"imported":[{"uid":"02da3037-16"},{"uid":"02da3037-0"}],"importedBy":[{"uid":"02da3037-14"},{"uid":"02da3037-8"}]},"02da3037-6":{"id":"/src/types.ts","moduleParts":{"index.js":"02da3037-7"},"imported":[],"importedBy":[{"uid":"02da3037-10"},{"uid":"02da3037-8"}]},"02da3037-8":{"id":"/src/instance.ts","moduleParts":{"index.js":"02da3037-9"},"imported":[{"uid":"02da3037-16"},{"uid":"02da3037-2"},{"uid":"02da3037-4"},{"uid":"02da3037-0"},{"uid":"02da3037-6"}],"importedBy":[{"uid":"02da3037-10"}]},"02da3037-10":{"id":"/src/model.ts","moduleParts":{"index.js":"02da3037-11"},"imported":[{"uid":"02da3037-8"},{"uid":"02da3037-6"}],"importedBy":[{"uid":"02da3037-14"}]},"02da3037-12":{"id":"/src/snapshot.ts","moduleParts":{"index.js":"02da3037-13"},"imported":[{"uid":"02da3037-16"},{"uid":"02da3037-0"}],"importedBy":[{"uid":"02da3037-14"}]},"02da3037-14":{"id":"/src/index.ts","moduleParts":{"index.js":"02da3037-15"},"imported":[{"uid":"02da3037-10"},{"uid":"02da3037-12"},{"uid":"02da3037-4"},{"uid":"02da3037-2"}],"importedBy":[],"isEntry":true},"02da3037-16":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"02da3037-12"},{"uid":"02da3037-4"},{"uid":"02da3037-8"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5389
+ const data = {"version":2,"tree":{"name":"root","children":[{"name":"devtools.js","children":[{"name":"src/devtools.ts","uid":"33598a70-1"}]},{"name":"index.js","children":[{"name":"src","children":[{"uid":"33598a70-3","name":"middleware.ts"},{"uid":"33598a70-5","name":"patch.ts"},{"uid":"33598a70-7","name":"types.ts"},{"uid":"33598a70-9","name":"instance.ts"},{"uid":"33598a70-11","name":"model.ts"},{"uid":"33598a70-13","name":"index.ts"}]}]},{"name":"_chunks/snapshot-dNsO8P8p.js","children":[{"name":"src","children":[{"uid":"33598a70-15","name":"registry.ts"},{"uid":"33598a70-17","name":"snapshot.ts"}]}]}],"isRoot":true},"nodeParts":{"33598a70-1":{"renderedLength":1843,"gzipLength":729,"brotliLength":0,"metaUid":"33598a70-0"},"33598a70-3":{"renderedLength":1291,"gzipLength":627,"brotliLength":0,"metaUid":"33598a70-2"},"33598a70-5":{"renderedLength":4252,"gzipLength":1554,"brotliLength":0,"metaUid":"33598a70-4"},"33598a70-7":{"renderedLength":162,"gzipLength":155,"brotliLength":0,"metaUid":"33598a70-6"},"33598a70-9":{"renderedLength":1691,"gzipLength":758,"brotliLength":0,"metaUid":"33598a70-8"},"33598a70-11":{"renderedLength":2187,"gzipLength":1001,"brotliLength":0,"metaUid":"33598a70-10"},"33598a70-13":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"33598a70-12"},"33598a70-15":{"renderedLength":420,"gzipLength":292,"brotliLength":0,"metaUid":"33598a70-14"},"33598a70-17":{"renderedLength":1396,"gzipLength":602,"brotliLength":0,"metaUid":"33598a70-16"}},"nodeMetas":{"33598a70-0":{"id":"/src/devtools.ts","moduleParts":{"devtools.js":"33598a70-1"},"imported":[{"uid":"33598a70-16"}],"importedBy":[],"isEntry":true},"33598a70-2":{"id":"/src/middleware.ts","moduleParts":{"index.js":"33598a70-3"},"imported":[{"uid":"33598a70-14"}],"importedBy":[{"uid":"33598a70-12"},{"uid":"33598a70-8"}]},"33598a70-4":{"id":"/src/patch.ts","moduleParts":{"index.js":"33598a70-5"},"imported":[{"uid":"33598a70-18"},{"uid":"33598a70-14"}],"importedBy":[{"uid":"33598a70-12"},{"uid":"33598a70-8"}]},"33598a70-6":{"id":"/src/types.ts","moduleParts":{"index.js":"33598a70-7"},"imported":[],"importedBy":[{"uid":"33598a70-10"},{"uid":"33598a70-8"}]},"33598a70-8":{"id":"/src/instance.ts","moduleParts":{"index.js":"33598a70-9"},"imported":[{"uid":"33598a70-18"},{"uid":"33598a70-2"},{"uid":"33598a70-4"},{"uid":"33598a70-14"},{"uid":"33598a70-6"}],"importedBy":[{"uid":"33598a70-10"}]},"33598a70-10":{"id":"/src/model.ts","moduleParts":{"index.js":"33598a70-11"},"imported":[{"uid":"33598a70-8"},{"uid":"33598a70-6"}],"importedBy":[{"uid":"33598a70-12"}]},"33598a70-12":{"id":"/src/index.ts","moduleParts":{"index.js":"33598a70-13"},"imported":[{"uid":"33598a70-10"},{"uid":"33598a70-16"},{"uid":"33598a70-4"},{"uid":"33598a70-2"}],"importedBy":[],"isEntry":true},"33598a70-14":{"id":"/src/registry.ts","moduleParts":{"_chunks/snapshot-dNsO8P8p.js":"33598a70-15"},"imported":[],"importedBy":[{"uid":"33598a70-16"},{"uid":"33598a70-4"},{"uid":"33598a70-2"},{"uid":"33598a70-8"}]},"33598a70-16":{"id":"/src/snapshot.ts","moduleParts":{"_chunks/snapshot-dNsO8P8p.js":"33598a70-17"},"imported":[{"uid":"33598a70-18"},{"uid":"33598a70-14"}],"importedBy":[{"uid":"33598a70-0"},{"uid":"33598a70-12"}]},"33598a70-18":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"33598a70-16"},{"uid":"33598a70-4"},{"uid":"33598a70-8"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5390
5390
 
5391
5391
  const run = () => {
5392
5392
  const width = window.innerWidth;
package/lib/devtools.js CHANGED
@@ -1,38 +1,5 @@
1
- //#region src/registry.ts
2
- /**
3
- * WeakMap from every model instance object → its internal metadata.
4
- * Shared across patch, middleware, and snapshot modules.
5
- */
6
- const instanceMeta = /* @__PURE__ */ new WeakMap();
7
- /** Returns true when a value is a model instance (has metadata registered). */
8
- function isModelInstance(value) {
9
- return value != null && typeof value === "object" && instanceMeta.has(value);
10
- }
11
-
12
- //#endregion
13
- //#region src/snapshot.ts
14
- /**
15
- * Serialize a model instance to a plain JS object (no signals, no functions).
16
- * Nested model instances are recursively serialized.
17
- *
18
- * @example
19
- * getSnapshot(counter) // { count: 6 }
20
- * getSnapshot(app) // { profile: { name: "Alice" }, title: "My App" }
21
- */
22
- function getSnapshot(instance) {
23
- const meta = instanceMeta.get(instance);
24
- if (!meta) throw new Error("[@pyreon/state-tree] getSnapshot: not a model instance");
25
- const out = {};
26
- for (const key of meta.stateKeys) {
27
- const sig = instance[key];
28
- if (!sig) continue;
29
- const val = sig.peek();
30
- out[key] = isModelInstance(val) ? getSnapshot(val) : val;
31
- }
32
- return out;
33
- }
1
+ import { n as getSnapshot } from "./_chunks/snapshot-dNsO8P8p.js";
34
2
 
35
- //#endregion
36
3
  //#region src/devtools.ts
37
4
  /**
38
5
  * @pyreon/state-tree devtools introspection API.
package/lib/index.js CHANGED
@@ -1,17 +1,6 @@
1
+ import { i as isModelInstance, n as getSnapshot, r as instanceMeta, t as applySnapshot } from "./_chunks/snapshot-dNsO8P8p.js";
1
2
  import { batch, signal } from "@pyreon/reactivity";
2
3
 
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
- const instanceMeta = /* @__PURE__ */ new WeakMap();
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
4
  //#region src/middleware.ts
16
5
  /**
17
6
  * Run an action through the middleware chain registered on `meta`.
@@ -302,52 +291,6 @@ function model(config) {
302
291
  return new ModelDefinition(config);
303
292
  }
304
293
 
305
- //#endregion
306
- //#region src/snapshot.ts
307
- /**
308
- * Serialize a model instance to a plain JS object (no signals, no functions).
309
- * Nested model instances are recursively serialized.
310
- *
311
- * @example
312
- * getSnapshot(counter) // { count: 6 }
313
- * getSnapshot(app) // { profile: { name: "Alice" }, title: "My App" }
314
- */
315
- function getSnapshot(instance) {
316
- const meta = instanceMeta.get(instance);
317
- if (!meta) throw new Error("[@pyreon/state-tree] getSnapshot: not a model instance");
318
- const out = {};
319
- for (const key of meta.stateKeys) {
320
- const sig = instance[key];
321
- if (!sig) continue;
322
- const val = sig.peek();
323
- out[key] = isModelInstance(val) ? getSnapshot(val) : val;
324
- }
325
- return out;
326
- }
327
- /**
328
- * Restore a model instance from a plain-object snapshot.
329
- * All signal writes are coalesced via `batch()` for a single reactive flush.
330
- * Keys absent from the snapshot are left unchanged.
331
- *
332
- * @example
333
- * applySnapshot(counter, { count: 0 })
334
- */
335
- function applySnapshot(instance, snapshot) {
336
- const meta = instanceMeta.get(instance);
337
- if (!meta) throw new Error("[@pyreon/state-tree] applySnapshot: not a model instance");
338
- batch(() => {
339
- for (const key of meta.stateKeys) {
340
- if (!(key in snapshot)) continue;
341
- const sig = instance[key];
342
- if (!sig) continue;
343
- const val = snapshot[key];
344
- const current = sig.peek();
345
- if (isModelInstance(current)) applySnapshot(current, val);
346
- else sig.set(val);
347
- }
348
- });
349
- }
350
-
351
294
  //#endregion
352
295
  export { addMiddleware, applyPatch, applySnapshot, getSnapshot, model, onPatch, resetAllHooks, resetHook };
353
296
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyreon/state-tree",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "description": "Structured reactive state tree — composable models with snapshots, patches, and middleware",
5
5
  "homepage": "https://github.com/pyreon/pyreon/tree/main/packages/state-tree#readme",
6
6
  "bugs": {
@@ -49,10 +49,10 @@
49
49
  "devDependencies": {
50
50
  "@happy-dom/global-registrator": "^20.8.9",
51
51
  "@pyreon/manifest": "0.13.1",
52
- "@pyreon/reactivity": "^0.22.0",
52
+ "@pyreon/reactivity": "^0.23.0",
53
53
  "bun-types": "^1.3.12"
54
54
  },
55
55
  "dependencies": {
56
- "@pyreon/reactivity": "^0.22.0"
56
+ "@pyreon/reactivity": "^0.23.0"
57
57
  }
58
58
  }