@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 +96 -44
- 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/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
|
|
@@ -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,
|
|
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
|
-
})
|
|
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
|
|
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)
|
|
804
|
-
enhanced.$.users.all
|
|
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/
|
|
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 (
|
|
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.
|
|
1595
|
-
tree.$.users.updateMany(['1'
|
|
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.
|
|
1616
|
-
appTree.$.app.data.products.
|
|
1617
|
-
appTree.$.admin.data.logs.all
|
|
1618
|
-
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
|
|
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)
|
|
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;
|
|
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)
|
|
2043
|
-
// tree.$.users.all
|
|
2044
|
-
// 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
|
|
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()** -
|
|
2065
|
-
- **devTools()** -
|
|
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) -
|
|
2272
|
+
- **entities()** (+0.97KB gzipped) - Enhanced CRUD operations & entity management
|
|
2221
2273
|
|
|
2222
2274
|
### Development Tools
|
|
2223
2275
|
|
|
2224
|
-
- **devTools()** (+2.49KB gzipped) -
|
|
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
|