@signaltree/core 8.0.0 → 8.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.
package/README.md CHANGED
@@ -118,7 +118,7 @@ Performance and bundle size vary by app shape, build tooling, device, and runtim
118
118
 
119
119
  ## Best Practices (SignalTree-First)
120
120
 
121
- > 📖 **Full guide**: [Architecture Guide](https://github.com/JBorgia/signaltree/blob/main/docs/architecture/signaltree-architecture-guide.md)
121
+ > 📖 **Full guide**: [Implementation Patterns](https://github.com/JBorgia/signaltree/blob/main/docs/IMPLEMENTATION_PATTERNS.md)
122
122
 
123
123
  Follow these principles for idiomatic SignalTree code:
124
124
 
@@ -164,7 +164,7 @@ export function createUserTree() {
164
164
  // ✅ Correct: Derived from multiple signals
165
165
  const selectedUser = computed(() => {
166
166
  const id = $.selected.userId();
167
- return id ? $.users.byId(id)?.() ?? null : null;
167
+ return id ? $.users.byId(id)() : null;
168
168
  });
169
169
 
170
170
  // ❌ Wrong: Wrapping an existing signal
@@ -175,8 +175,8 @@ const selectedUserId = computed(() => $.selected.userId()); // Unnecessary!
175
175
 
176
176
  ```typescript
177
177
  // ✅ SignalTree-native
178
- const user = $.users.byId(123)?.(); // O(1) lookup
179
- const allUsers = $.users.all(); // Get all
178
+ const user = $.users.byId(123)(); // O(1) lookup
179
+ const allUsers = $.users.all; // Get all
180
180
  $.users.setAll(usersFromApi); // Replace all
181
181
 
182
182
  // ❌ NgRx-style (avoid)
@@ -650,7 +650,7 @@ All enhancers are exported directly from `@signaltree/core`:
650
650
 
651
651
  **Data Management:**
652
652
 
653
- - `entities()` - Legacy helper (not required when using `entityMap()` in v7)
653
+ - `entities()` - Advanced CRUD operations for collections
654
654
  - `createAsyncOperation()` - Async operation management with loading/error states
655
655
  - `trackAsync()` - Track async operations in your state
656
656
  - `serialization()` - State persistence and SSR support
@@ -661,12 +661,6 @@ All enhancers are exported directly from `@signaltree/core`:
661
661
  - `devTools()` - Redux DevTools auto-connect, path actions, and time-travel dispatch
662
662
  - `withTimeTravel()` - Undo/redo functionality
663
663
 
664
- **DevTools connection model**
665
-
666
- `devTools()` connects to the Redux DevTools browser extension via `window.__REDUX_DEVTOOLS_EXTENSION__.connect(...)`. That means **each SignalTree instance you enhance with `devTools()` will show up as its own DevTools “instance”.**
667
-
668
- If you want a single unified DevTools view, prefer a **single global tree** composed from domain slices, and apply `devTools()` once at the root. See the Architecture Guide (Category C: Feature-scoped / composed sub-trees) for patterns that keep code modular without fragmenting runtime state.
669
-
670
664
  **Presets:**
671
665
 
672
666
  - `createDevTree()` - Pre-configured development setup
@@ -697,12 +691,14 @@ const tree = signalTree({ count: 0 }).with(
697
691
  **Performance-Focused Stack:**
698
692
 
699
693
  ```typescript
700
- import { signalTree, batching, memoization, entityMap } from '@signaltree/core';
694
+ import { signalTree, batching, memoization, entities } from '@signaltree/core';
701
695
 
702
696
  const tree = signalTree({
703
697
  products: entityMap<Product>(),
704
698
  ui: { loading: false },
705
- }).with(batching()); // Batch updates for optimal rendering
699
+ })
700
+ .with(entities()) // Efficient CRUD operations (auto-detects entityMap)
701
+ .with(batching()); // Batch updates for optimal rendering
706
702
 
707
703
  // Entity CRUD operations
708
704
  tree.$.products.addOne(newProduct);
@@ -788,20 +784,20 @@ const customTree = signalTree(state, TREE_PRESETS.DASHBOARD);
788
784
  SignalTree Core includes all enhancer functionality built-in. No separate packages needed:
789
785
 
790
786
  ```typescript
791
- import { signalTree, entityMap } from '@signaltree/core';
787
+ import { signalTree, entityMap, entities } from '@signaltree/core';
792
788
 
793
789
  // Without entityMap - use manual array updates
794
790
  const basic = signalTree({ users: [] as User[] });
795
791
  basic.$.users.update((users) => [...users, newUser]);
796
792
 
797
- // With entityMap (v7+ auto-processed) - use entity helpers
793
+ // With entityMap + entities - use entity helpers
798
794
  const enhanced = signalTree({
799
795
  users: entityMap<User>(),
800
- });
796
+ }).with(entities());
801
797
 
802
798
  enhanced.$.users.addOne(newUser); // ✅ Advanced CRUD operations
803
- enhanced.$.users.byId(123)?.(); // ✅ O(1) lookups
804
- enhanced.$.users.all(); // ✅ Get all as array
799
+ enhanced.$.users.byId(123)(); // ✅ O(1) lookups
800
+ enhanced.$.users.all; // ✅ Get all as array
805
801
  ```
806
802
 
807
803
  Core includes several performance optimizations:
@@ -914,7 +910,7 @@ const tree = signalTree({ count: 0 }).with(withLogger());
914
910
  tree.log('Tree created');
915
911
  ```
916
912
 
917
- > 📖 **Full guide**: [Custom Markers & Enhancers](https://github.com/JBorgia/signaltree/blob/main/docs/guides/custom-markers-enhancers.md)
913
+ > 📖 **Full guide**: [Custom Markers & Enhancers](https://github.com/JBorgia/signaltree/blob/main/docs/custom-markers-enhancers.md)
918
914
  >
919
915
  > 📱 **Interactive demo**: [Demo App](/custom-extensions)
920
916
 
@@ -1580,19 +1576,19 @@ const filteredProducts = computed(() => {
1580
1576
  ### Data Management Composition
1581
1577
 
1582
1578
  ```typescript
1583
- import { signalTree, entityMap } from '@signaltree/core';
1579
+ import { signalTree, entityMap, entities } from '@signaltree/core';
1584
1580
 
1585
- // Add data management capabilities (entityMap is auto-processed)
1581
+ // Add data management capabilities (+2.77KB total)
1586
1582
  const tree = signalTree({
1587
1583
  users: entityMap<User>(),
1588
1584
  posts: entityMap<Post>(),
1589
1585
  ui: { loading: false, error: null as string | null },
1590
- });
1586
+ }).with(entities());
1591
1587
 
1592
1588
  // Advanced entity operations via tree.$ accessor
1593
1589
  tree.$.users.addOne(newUser);
1594
- tree.$.users.where((u) => u.active); // Filtered signal
1595
- tree.$.users.updateMany(['1'], { status: 'active' });
1590
+ tree.$.users.selectBy((u) => u.active);
1591
+ tree.$.users.updateMany([{ id: '1', changes: { status: 'active' } }]);
1596
1592
 
1597
1593
  // Entity helpers work with nested structures
1598
1594
  // Example: deeply nested entities in a domain-driven design pattern
@@ -1609,13 +1605,13 @@ const appTree = signalTree({
1609
1605
  reports: entityMap<Report>(),
1610
1606
  },
1611
1607
  },
1612
- });
1608
+ }).with(entities());
1613
1609
 
1614
1610
  // Access nested entities using tree.$ accessor
1615
- appTree.$.app.data.users.where((u) => u.isAdmin); // Filtered signal
1616
- appTree.$.app.data.products.count(); // Count
1617
- appTree.$.admin.data.logs.all(); // All items as array
1618
- appTree.$.admin.data.reports.ids(); // ID array
1611
+ appTree.$.app.data.users.selectBy((u) => u.isAdmin); // Filtered signal
1612
+ appTree.$.app.data.products.selectTotal(); // Count signal
1613
+ appTree.$.admin.data.logs.all; // All items as array
1614
+ appTree.$.admin.data.reports.selectIds(); // ID array signal
1619
1615
 
1620
1616
  // For async operations, use manual async or async helpers
1621
1617
  async function fetchUsers() {
@@ -1634,7 +1630,7 @@ async function fetchUsers() {
1634
1630
  ### Full-Featured Development Composition
1635
1631
 
1636
1632
  ```typescript
1637
- import { signalTree, batching, serialization, withTimeTravel, devTools } from '@signaltree/core';
1633
+ import { signalTree, batching, entities, serialization, withTimeTravel, devTools } from '@signaltree/core';
1638
1634
 
1639
1635
  // Full development stack (example)
1640
1636
  const tree = signalTree({
@@ -1645,6 +1641,7 @@ const tree = signalTree({
1645
1641
  },
1646
1642
  }).with(
1647
1643
  batching(), // Performance
1644
+ entities(), // Data management
1648
1645
  // withAsync removed — use async helpers for API integration
1649
1646
  serialization({
1650
1647
  // State persistence
@@ -1668,19 +1665,75 @@ const tree = signalTree({
1668
1665
  async function fetchUser(id: string) {
1669
1666
  return await api.getUser(id);
1670
1667
  }
1671
- tree.$.app.data.users.byId(userId)?.(); // O(1) lookup
1668
+ tree.$.app.data.users.byId(userId)(); // O(1) lookup
1672
1669
  tree.undo(); // Time travel
1673
1670
  tree.save(); // Persistence
1674
1671
  ```
1675
1672
 
1673
+ ### Aggregated Redux DevTools Instance
1674
+ ~~~~
1675
+ When using multiple independent trees (e.g. per lazy-loaded feature module), each `devTools()` call creates a separate Redux DevTools instance by default. Use `aggregatedReduxInstance` to group multiple trees under a **single Redux DevTools instance**:
1676
+
1677
+ ```typescript
1678
+ import { signalTree, batching, entityMap, devTools } from '@signaltree/core';
1679
+
1680
+ const DEVTOOLS_GROUP_ID = 'my-app';
1681
+ const DEVTOOLS_GROUP_NAME = 'MyApp SignalTree';
1682
+
1683
+ // Feature A tree
1684
+ const ordersTree = signalTree({ orders: entityMap<Order>() })
1685
+ .with(batching())
1686
+ .with(devTools({
1687
+ treeName: 'orders-store',
1688
+ aggregatedReduxInstance: {
1689
+ id: DEVTOOLS_GROUP_ID,
1690
+ name: DEVTOOLS_GROUP_NAME,
1691
+ },
1692
+ }));
1693
+
1694
+ // Feature B tree (same group)
1695
+ const productsTree = signalTree({ products: entityMap<Product>() })
1696
+ .with(batching())
1697
+ .with(devTools({
1698
+ treeName: 'products-store',
1699
+ aggregatedReduxInstance: {
1700
+ id: DEVTOOLS_GROUP_ID,
1701
+ name: DEVTOOLS_GROUP_NAME,
1702
+ },
1703
+ }));
1704
+ ```
1705
+
1706
+ In Redux DevTools you will see a single instance named `"MyApp SignalTree"` with state:
1707
+
1708
+ ```json
1709
+ {
1710
+ "orders-store": { "orders": { ... } },
1711
+ "products-store": { "products": { ... } }
1712
+ }
1713
+ ```
1714
+
1715
+ **Key behaviors:**
1716
+
1717
+ - Trees sharing the same `aggregatedReduxInstance.id` are grouped together.
1718
+ - The Redux DevTools `instanceId` is based on `aggregatedReduxInstance.id` to avoid collisions.
1719
+ - Trees can be dynamically registered/unregistered as lazy-loaded modules come and go.
1720
+ - The shared connection is created on first registration and cleaned up when the last tree disconnects.
1721
+ - Trees that omit `aggregatedReduxInstance` work as standalone DevTools instances as before.
1722
+ - You can mix aggregated and standalone trees in the same application.
1723
+
1724
+ **Logging:** DevTools internal logging is quiet by default. Set `enableLogging: true` on `devTools({ ... })` if you need console diagnostics.
1725
+
1726
+ **Cleanup:** Call `tree.disconnectDevTools()` when destroying a tree (e.g. in `DestroyRef.onDestroy`) to unregister it from the group and clean up its state in DevTools.
1727
+
1676
1728
  ### Production-Ready Composition
1677
1729
 
1678
1730
  ```typescript
1679
- import { signalTree, batching, serialization } from '@signaltree/core';
1731
+ import { signalTree, batching, entities, serialization } from '@signaltree/core';
1680
1732
 
1681
1733
  // Production build (no dev tools)
1682
1734
  const tree = signalTree(initialState).with(
1683
1735
  batching(), // Performance optimization
1736
+ entities(), // Data management
1684
1737
  // withAsync removed — use async helpers for API integration
1685
1738
  serialization({
1686
1739
  // User preferences
@@ -1696,13 +1749,14 @@ const tree = signalTree(initialState).with(
1696
1749
  ### Conditional Enhancement
1697
1750
 
1698
1751
  ```typescript
1699
- import { signalTree, batching, devTools, withTimeTravel } from '@signaltree/core';
1752
+ import { signalTree, batching, entities, devTools, withTimeTravel } from '@signaltree/core';
1700
1753
 
1701
1754
  const isDevelopment = process.env['NODE_ENV'] === 'development';
1702
1755
 
1703
1756
  // Conditional enhancement based on environment
1704
1757
  const tree = signalTree(state).with(
1705
1758
  batching(), // Always include performance
1759
+ entities(), // Always include data management
1706
1760
  ...(isDevelopment
1707
1761
  ? [
1708
1762
  // Development-only features
@@ -1747,7 +1801,7 @@ const tree = signalTree(state);
1747
1801
  const tree2 = tree.with(batching());
1748
1802
 
1749
1803
  // Phase 3: Add data management for collections
1750
- const tree3 = tree2; // entityMap is auto-processed in v7
1804
+ const tree3 = tree2.with(entities());
1751
1805
 
1752
1806
  // Phase 4: Add async for API integration
1753
1807
  // withAsync removed — no explicit async enhancer; use async helpers instead
@@ -1825,7 +1879,7 @@ For fair, reproducible measurements that reflect your app and hardware, use the
1825
1879
  {{ userTree.$.error() }}
1826
1880
  <button (click)="loadUsers()">Retry</button>
1827
1881
  </div>
1828
- } @else { @for (user of users(); track user.id) {
1882
+ } @else { @for (user of users.selectAll()(); track user.id) {
1829
1883
  <div class="user-card">
1830
1884
  <h3>{{ user.name }}</h3>
1831
1885
  <p>{{ user.email }}</p>
@@ -1853,8 +1907,6 @@ class UserManagerComponent implements OnInit {
1853
1907
  form: { id: '', name: '', email: '' },
1854
1908
  });
1855
1909
 
1856
- readonly users = this.userTree.$.users;
1857
-
1858
1910
  constructor(private userService: UserService) {}
1859
1911
 
1860
1912
  ngOnInit() {
@@ -2039,9 +2091,9 @@ tree.destroy(); // Cleanup resources
2039
2091
 
2040
2092
  // Entity helpers (when using entityMap + entities)
2041
2093
  // tree.$.users.addOne(user); // Add single entity
2042
- // tree.$.users.byId(id)?.(); // O(1) lookup by ID
2043
- // tree.$.users.all(); // Get all as array
2044
- // tree.$.users.where(pred)(); // Filtered array
2094
+ // tree.$.users.byId(id)(); // O(1) lookup by ID
2095
+ // tree.$.users.all; // Get all as array
2096
+ // tree.$.users.selectBy(pred); // Filtered signal
2045
2097
  ```
2046
2098
 
2047
2099
  ## Extending with enhancers
@@ -2061,8 +2113,8 @@ All enhancers are included in `@signaltree/core`:
2061
2113
 
2062
2114
  - **batching()** - Batch multiple updates for better performance
2063
2115
  - **memoization()** - Intelligent caching & performance optimization
2064
- - **entities()** - Legacy helper (not required when using `entityMap()` in v7)
2065
- - **devTools()** - Auto-connect, path actions, and time-travel dispatch
2116
+ - **entities()** - Advanced entity management & CRUD operations
2117
+ - **devTools()** - Redux DevTools integration for debugging
2066
2118
  - **withTimeTravel()** - Undo/redo functionality & state history
2067
2119
  - **serialization()** - State persistence & SSR support
2068
2120
  - **createDevTree()** - Pre-configured development setup
@@ -2217,11 +2269,11 @@ All enhancers are now consolidated in the core package. The following features a
2217
2269
 
2218
2270
  ### Advanced Features
2219
2271
 
2220
- - **entities()** (+0.97KB gzipped) - Legacy helper (not required when using `entityMap()` in v7)
2272
+ - **entities()** (+0.97KB gzipped) - Enhanced CRUD operations & entity management
2221
2273
 
2222
2274
  ### Development Tools
2223
2275
 
2224
- - **devTools()** (+2.49KB gzipped) - Auto-connect, path actions, and time-travel dispatch
2276
+ - **devTools()** (+2.49KB gzipped) - Development tools & Redux DevTools integration
2225
2277
  - **withTimeTravel()** (+1.75KB gzipped) - Undo/redo functionality & state history
2226
2278
 
2227
2279
  ### Integration & Convenience