@signaltree/core 7.6.1 → 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
@@ -691,12 +691,14 @@ const tree = signalTree({ count: 0 }).with(
691
691
  **Performance-Focused Stack:**
692
692
 
693
693
  ```typescript
694
- import { signalTree, batching, memoization, entityMap } from '@signaltree/core';
694
+ import { signalTree, batching, memoization, entities } from '@signaltree/core';
695
695
 
696
696
  const tree = signalTree({
697
697
  products: entityMap<Product>(),
698
698
  ui: { loading: false },
699
- }).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
700
702
 
701
703
  // Entity CRUD operations
702
704
  tree.$.products.addOne(newProduct);
@@ -782,20 +784,20 @@ const customTree = signalTree(state, TREE_PRESETS.DASHBOARD);
782
784
  SignalTree Core includes all enhancer functionality built-in. No separate packages needed:
783
785
 
784
786
  ```typescript
785
- import { signalTree, entityMap } from '@signaltree/core';
787
+ import { signalTree, entityMap, entities } from '@signaltree/core';
786
788
 
787
789
  // Without entityMap - use manual array updates
788
790
  const basic = signalTree({ users: [] as User[] });
789
791
  basic.$.users.update((users) => [...users, newUser]);
790
792
 
791
- // With entityMap (v7+ auto-processed) - use entity helpers
793
+ // With entityMap + entities - use entity helpers
792
794
  const enhanced = signalTree({
793
795
  users: entityMap<User>(),
794
- });
796
+ }).with(entities());
795
797
 
796
798
  enhanced.$.users.addOne(newUser); // ✅ Advanced CRUD operations
797
- enhanced.$.users.byId(123)?.(); // ✅ O(1) lookups
798
- enhanced.$.users.all(); // ✅ Get all as array
799
+ enhanced.$.users.byId(123)(); // ✅ O(1) lookups
800
+ enhanced.$.users.all; // ✅ Get all as array
799
801
  ```
800
802
 
801
803
  Core includes several performance optimizations:
@@ -908,7 +910,7 @@ const tree = signalTree({ count: 0 }).with(withLogger());
908
910
  tree.log('Tree created');
909
911
  ```
910
912
 
911
- > 📖 **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)
912
914
  >
913
915
  > 📱 **Interactive demo**: [Demo App](/custom-extensions)
914
916
 
@@ -1574,19 +1576,19 @@ const filteredProducts = computed(() => {
1574
1576
  ### Data Management Composition
1575
1577
 
1576
1578
  ```typescript
1577
- import { signalTree, entityMap } from '@signaltree/core';
1579
+ import { signalTree, entityMap, entities } from '@signaltree/core';
1578
1580
 
1579
- // Add data management capabilities (entityMap is auto-processed)
1581
+ // Add data management capabilities (+2.77KB total)
1580
1582
  const tree = signalTree({
1581
1583
  users: entityMap<User>(),
1582
1584
  posts: entityMap<Post>(),
1583
1585
  ui: { loading: false, error: null as string | null },
1584
- });
1586
+ }).with(entities());
1585
1587
 
1586
1588
  // Advanced entity operations via tree.$ accessor
1587
1589
  tree.$.users.addOne(newUser);
1588
- tree.$.users.where((u) => u.active); // Filtered signal
1589
- tree.$.users.updateMany(['1'], { status: 'active' });
1590
+ tree.$.users.selectBy((u) => u.active);
1591
+ tree.$.users.updateMany([{ id: '1', changes: { status: 'active' } }]);
1590
1592
 
1591
1593
  // Entity helpers work with nested structures
1592
1594
  // Example: deeply nested entities in a domain-driven design pattern
@@ -1603,13 +1605,13 @@ const appTree = signalTree({
1603
1605
  reports: entityMap<Report>(),
1604
1606
  },
1605
1607
  },
1606
- });
1608
+ }).with(entities());
1607
1609
 
1608
1610
  // Access nested entities using tree.$ accessor
1609
- appTree.$.app.data.users.where((u) => u.isAdmin); // Filtered signal
1610
- appTree.$.app.data.products.count(); // Count
1611
- appTree.$.admin.data.logs.all(); // All items as array
1612
- 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
1613
1615
 
1614
1616
  // For async operations, use manual async or async helpers
1615
1617
  async function fetchUsers() {
@@ -1628,7 +1630,7 @@ async function fetchUsers() {
1628
1630
  ### Full-Featured Development Composition
1629
1631
 
1630
1632
  ```typescript
1631
- import { signalTree, batching, serialization, withTimeTravel, devTools } from '@signaltree/core';
1633
+ import { signalTree, batching, entities, serialization, withTimeTravel, devTools } from '@signaltree/core';
1632
1634
 
1633
1635
  // Full development stack (example)
1634
1636
  const tree = signalTree({
@@ -1639,6 +1641,7 @@ const tree = signalTree({
1639
1641
  },
1640
1642
  }).with(
1641
1643
  batching(), // Performance
1644
+ entities(), // Data management
1642
1645
  // withAsync removed — use async helpers for API integration
1643
1646
  serialization({
1644
1647
  // State persistence
@@ -1662,19 +1665,75 @@ const tree = signalTree({
1662
1665
  async function fetchUser(id: string) {
1663
1666
  return await api.getUser(id);
1664
1667
  }
1665
- tree.$.app.data.users.byId(userId)?.(); // O(1) lookup
1668
+ tree.$.app.data.users.byId(userId)(); // O(1) lookup
1666
1669
  tree.undo(); // Time travel
1667
1670
  tree.save(); // Persistence
1668
1671
  ```
1669
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
+
1670
1728
  ### Production-Ready Composition
1671
1729
 
1672
1730
  ```typescript
1673
- import { signalTree, batching, serialization } from '@signaltree/core';
1731
+ import { signalTree, batching, entities, serialization } from '@signaltree/core';
1674
1732
 
1675
1733
  // Production build (no dev tools)
1676
1734
  const tree = signalTree(initialState).with(
1677
1735
  batching(), // Performance optimization
1736
+ entities(), // Data management
1678
1737
  // withAsync removed — use async helpers for API integration
1679
1738
  serialization({
1680
1739
  // User preferences
@@ -1690,13 +1749,14 @@ const tree = signalTree(initialState).with(
1690
1749
  ### Conditional Enhancement
1691
1750
 
1692
1751
  ```typescript
1693
- import { signalTree, batching, devTools, withTimeTravel } from '@signaltree/core';
1752
+ import { signalTree, batching, entities, devTools, withTimeTravel } from '@signaltree/core';
1694
1753
 
1695
1754
  const isDevelopment = process.env['NODE_ENV'] === 'development';
1696
1755
 
1697
1756
  // Conditional enhancement based on environment
1698
1757
  const tree = signalTree(state).with(
1699
1758
  batching(), // Always include performance
1759
+ entities(), // Always include data management
1700
1760
  ...(isDevelopment
1701
1761
  ? [
1702
1762
  // Development-only features
@@ -1741,7 +1801,7 @@ const tree = signalTree(state);
1741
1801
  const tree2 = tree.with(batching());
1742
1802
 
1743
1803
  // Phase 3: Add data management for collections
1744
- const tree3 = tree2; // entityMap is auto-processed in v7
1804
+ const tree3 = tree2.with(entities());
1745
1805
 
1746
1806
  // Phase 4: Add async for API integration
1747
1807
  // withAsync removed — no explicit async enhancer; use async helpers instead
@@ -1819,7 +1879,7 @@ For fair, reproducible measurements that reflect your app and hardware, use the
1819
1879
  {{ userTree.$.error() }}
1820
1880
  <button (click)="loadUsers()">Retry</button>
1821
1881
  </div>
1822
- } @else { @for (user of users(); track user.id) {
1882
+ } @else { @for (user of users.selectAll()(); track user.id) {
1823
1883
  <div class="user-card">
1824
1884
  <h3>{{ user.name }}</h3>
1825
1885
  <p>{{ user.email }}</p>
@@ -1847,8 +1907,6 @@ class UserManagerComponent implements OnInit {
1847
1907
  form: { id: '', name: '', email: '' },
1848
1908
  });
1849
1909
 
1850
- readonly users = this.userTree.$.users;
1851
-
1852
1910
  constructor(private userService: UserService) {}
1853
1911
 
1854
1912
  ngOnInit() {
@@ -2033,9 +2091,9 @@ tree.destroy(); // Cleanup resources
2033
2091
 
2034
2092
  // Entity helpers (when using entityMap + entities)
2035
2093
  // tree.$.users.addOne(user); // Add single entity
2036
- // tree.$.users.byId(id)?.(); // O(1) lookup by ID
2037
- // tree.$.users.all(); // Get all as array
2038
- // 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
2039
2097
  ```
2040
2098
 
2041
2099
  ## Extending with enhancers
@@ -2055,8 +2113,8 @@ All enhancers are included in `@signaltree/core`:
2055
2113
 
2056
2114
  - **batching()** - Batch multiple updates for better performance
2057
2115
  - **memoization()** - Intelligent caching & performance optimization
2058
- - **entities()** - Legacy helper (not required when using `entityMap()` in v7)
2059
- - **devTools()** - Auto-connect, path actions, and time-travel dispatch
2116
+ - **entities()** - Advanced entity management & CRUD operations
2117
+ - **devTools()** - Redux DevTools integration for debugging
2060
2118
  - **withTimeTravel()** - Undo/redo functionality & state history
2061
2119
  - **serialization()** - State persistence & SSR support
2062
2120
  - **createDevTree()** - Pre-configured development setup
@@ -2211,11 +2269,11 @@ All enhancers are now consolidated in the core package. The following features a
2211
2269
 
2212
2270
  ### Advanced Features
2213
2271
 
2214
- - **entities()** (+0.97KB gzipped) - Legacy helper (not required when using `entityMap()` in v7)
2272
+ - **entities()** (+0.97KB gzipped) - Enhanced CRUD operations & entity management
2215
2273
 
2216
2274
  ### Development Tools
2217
2275
 
2218
- - **devTools()** (+2.49KB gzipped) - Auto-connect, path actions, and time-travel dispatch
2276
+ - **devTools()** (+2.49KB gzipped) - Development tools & Redux DevTools integration
2219
2277
  - **withTimeTravel()** (+1.75KB gzipped) - Undo/redo functionality & state history
2220
2278
 
2221
2279
  ### Integration & Convenience