@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 +96 -38
- package/dist/enhancers/devtools/devtools.js +717 -135
- package/dist/enhancers/memoization/memoization.js +2 -2
- package/dist/index.js +3 -3
- package/dist/lib/constants.js +1 -1
- package/dist/lib/edit-session.js +1 -1
- package/dist/lib/signal-tree.js +2 -2
- package/dist/lib/utils.js +1 -1
- package/package.json +2 -6
- package/src/enhancers/devtools/devtools.d.ts +1 -1
- package/src/lib/types.d.ts +5 -1
- package/src/lib/utils.d.ts +20 -1
- /package/dist/{constants.js → shared/lib/constants.js} +0 -0
- /package/dist/{deep-equal.js → shared/lib/deep-equal.js} +0 -0
- /package/dist/{is-built-in-object.js → shared/lib/is-built-in-object.js} +0 -0
- /package/dist/{lru-cache.js → shared/lib/lru-cache.js} +0 -0
- /package/dist/{parse-path.js → shared/lib/parse-path.js} +0 -0
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**: [
|
|
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)
|
|
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)
|
|
179
|
-
const allUsers = $.users.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()` -
|
|
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,
|
|
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
|
-
})
|
|
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
|
|
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)
|
|
798
|
-
enhanced.$.users.all
|
|
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/
|
|
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 (
|
|
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.
|
|
1589
|
-
tree.$.users.updateMany(['1'
|
|
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.
|
|
1610
|
-
appTree.$.app.data.products.
|
|
1611
|
-
appTree.$.admin.data.logs.all
|
|
1612
|
-
appTree.$.admin.data.reports.
|
|
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)
|
|
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;
|
|
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)
|
|
2037
|
-
// tree.$.users.all
|
|
2038
|
-
// tree.$.users.
|
|
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()** -
|
|
2059
|
-
- **devTools()** -
|
|
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) -
|
|
2272
|
+
- **entities()** (+0.97KB gzipped) - Enhanced CRUD operations & entity management
|
|
2215
2273
|
|
|
2216
2274
|
### Development Tools
|
|
2217
2275
|
|
|
2218
|
-
- **devTools()** (+2.49KB gzipped) -
|
|
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
|