@signaltree/core 7.6.0 β 8.0.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 +50 -43
- package/dist/enhancers/devtools/devtools.js +24 -10
- package/dist/lib/utils.js +22 -0
- package/package.json +1 -1
- package/src/lib/utils.d.ts +20 -1
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**: [Architecture Guide](https://github.com/JBorgia/signaltree/blob/main/docs/architecture/signaltree-architecture-guide.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;
|
|
167
|
+
return id ? $.users.byId(id)?.() ?? null : 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()` -
|
|
653
|
+
- `entities()` - Legacy helper (not required when using `entityMap()` in v7)
|
|
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
|
|
@@ -658,9 +658,15 @@ All enhancers are exported directly from `@signaltree/core`:
|
|
|
658
658
|
|
|
659
659
|
**Development Tools:**
|
|
660
660
|
|
|
661
|
-
- `devTools()` - Redux DevTools
|
|
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
|
+
|
|
664
670
|
**Presets:**
|
|
665
671
|
|
|
666
672
|
- `createDevTree()` - Pre-configured development setup
|
|
@@ -691,14 +697,12 @@ const tree = signalTree({ count: 0 }).with(
|
|
|
691
697
|
**Performance-Focused Stack:**
|
|
692
698
|
|
|
693
699
|
```typescript
|
|
694
|
-
import { signalTree, batching, memoization,
|
|
700
|
+
import { signalTree, batching, memoization, entityMap } from '@signaltree/core';
|
|
695
701
|
|
|
696
702
|
const tree = signalTree({
|
|
697
703
|
products: entityMap<Product>(),
|
|
698
704
|
ui: { loading: false },
|
|
699
|
-
})
|
|
700
|
-
.with(entities()) // Efficient CRUD operations (auto-detects entityMap)
|
|
701
|
-
.with(batching()); // Batch updates for optimal rendering
|
|
705
|
+
}).with(batching()); // Batch updates for optimal rendering
|
|
702
706
|
|
|
703
707
|
// Entity CRUD operations
|
|
704
708
|
tree.$.products.addOne(newProduct);
|
|
@@ -748,6 +752,8 @@ tree.undo(); // Revert changes
|
|
|
748
752
|
|
|
749
753
|
#### Enhancer Metadata & Ordering
|
|
750
754
|
|
|
755
|
+
Derived computed signals are preserved across `.with()` chaining, so enhancer composition does not recreate signal identities.
|
|
756
|
+
|
|
751
757
|
Enhancers can declare metadata for automatic dependency resolution:
|
|
752
758
|
|
|
753
759
|
```typescript
|
|
@@ -782,20 +788,20 @@ const customTree = signalTree(state, TREE_PRESETS.DASHBOARD);
|
|
|
782
788
|
SignalTree Core includes all enhancer functionality built-in. No separate packages needed:
|
|
783
789
|
|
|
784
790
|
```typescript
|
|
785
|
-
import { signalTree, entityMap
|
|
791
|
+
import { signalTree, entityMap } from '@signaltree/core';
|
|
786
792
|
|
|
787
793
|
// Without entityMap - use manual array updates
|
|
788
794
|
const basic = signalTree({ users: [] as User[] });
|
|
789
795
|
basic.$.users.update((users) => [...users, newUser]);
|
|
790
796
|
|
|
791
|
-
// With entityMap +
|
|
797
|
+
// With entityMap (v7+ auto-processed) - use entity helpers
|
|
792
798
|
const enhanced = signalTree({
|
|
793
799
|
users: entityMap<User>(),
|
|
794
|
-
})
|
|
800
|
+
});
|
|
795
801
|
|
|
796
802
|
enhanced.$.users.addOne(newUser); // β
Advanced CRUD operations
|
|
797
|
-
enhanced.$.users.byId(123)(); // β
O(1) lookups
|
|
798
|
-
enhanced.$.users.all; // β
Get all as array
|
|
803
|
+
enhanced.$.users.byId(123)?.(); // β
O(1) lookups
|
|
804
|
+
enhanced.$.users.all(); // β
Get all as array
|
|
799
805
|
```
|
|
800
806
|
|
|
801
807
|
Core includes several performance optimizations:
|
|
@@ -908,7 +914,7 @@ const tree = signalTree({ count: 0 }).with(withLogger());
|
|
|
908
914
|
tree.log('Tree created');
|
|
909
915
|
```
|
|
910
916
|
|
|
911
|
-
> π **Full guide**: [Custom Markers & Enhancers](https://github.com/JBorgia/signaltree/blob/main/docs/custom-markers-enhancers.md)
|
|
917
|
+
> π **Full guide**: [Custom Markers & Enhancers](https://github.com/JBorgia/signaltree/blob/main/docs/guides/custom-markers-enhancers.md)
|
|
912
918
|
>
|
|
913
919
|
> π± **Interactive demo**: [Demo App](/custom-extensions)
|
|
914
920
|
|
|
@@ -1574,19 +1580,19 @@ const filteredProducts = computed(() => {
|
|
|
1574
1580
|
### Data Management Composition
|
|
1575
1581
|
|
|
1576
1582
|
```typescript
|
|
1577
|
-
import { signalTree, entityMap
|
|
1583
|
+
import { signalTree, entityMap } from '@signaltree/core';
|
|
1578
1584
|
|
|
1579
|
-
// Add data management capabilities (
|
|
1585
|
+
// Add data management capabilities (entityMap is auto-processed)
|
|
1580
1586
|
const tree = signalTree({
|
|
1581
1587
|
users: entityMap<User>(),
|
|
1582
1588
|
posts: entityMap<Post>(),
|
|
1583
1589
|
ui: { loading: false, error: null as string | null },
|
|
1584
|
-
})
|
|
1590
|
+
});
|
|
1585
1591
|
|
|
1586
1592
|
// Advanced entity operations via tree.$ accessor
|
|
1587
1593
|
tree.$.users.addOne(newUser);
|
|
1588
|
-
tree.$.users.
|
|
1589
|
-
tree.$.users.updateMany([
|
|
1594
|
+
tree.$.users.where((u) => u.active); // Filtered signal
|
|
1595
|
+
tree.$.users.updateMany(['1'], { status: 'active' });
|
|
1590
1596
|
|
|
1591
1597
|
// Entity helpers work with nested structures
|
|
1592
1598
|
// Example: deeply nested entities in a domain-driven design pattern
|
|
@@ -1603,13 +1609,13 @@ const appTree = signalTree({
|
|
|
1603
1609
|
reports: entityMap<Report>(),
|
|
1604
1610
|
},
|
|
1605
1611
|
},
|
|
1606
|
-
})
|
|
1612
|
+
});
|
|
1607
1613
|
|
|
1608
1614
|
// Access nested entities using tree.$ accessor
|
|
1609
|
-
appTree.$.app.data.users.
|
|
1610
|
-
appTree.$.app.data.products.
|
|
1611
|
-
appTree.$.admin.data.logs.all; // All items as array
|
|
1612
|
-
appTree.$.admin.data.reports.
|
|
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
|
|
1613
1619
|
|
|
1614
1620
|
// For async operations, use manual async or async helpers
|
|
1615
1621
|
async function fetchUsers() {
|
|
@@ -1628,7 +1634,7 @@ async function fetchUsers() {
|
|
|
1628
1634
|
### Full-Featured Development Composition
|
|
1629
1635
|
|
|
1630
1636
|
```typescript
|
|
1631
|
-
import { signalTree, batching,
|
|
1637
|
+
import { signalTree, batching, serialization, withTimeTravel, devTools } from '@signaltree/core';
|
|
1632
1638
|
|
|
1633
1639
|
// Full development stack (example)
|
|
1634
1640
|
const tree = signalTree({
|
|
@@ -1639,7 +1645,6 @@ const tree = signalTree({
|
|
|
1639
1645
|
},
|
|
1640
1646
|
}).with(
|
|
1641
1647
|
batching(), // Performance
|
|
1642
|
-
entities(), // Data management
|
|
1643
1648
|
// withAsync removed β use async helpers for API integration
|
|
1644
1649
|
serialization({
|
|
1645
1650
|
// State persistence
|
|
@@ -1653,7 +1658,9 @@ const tree = signalTree({
|
|
|
1653
1658
|
devTools({
|
|
1654
1659
|
// Debug tools (dev only)
|
|
1655
1660
|
name: 'MyApp',
|
|
1656
|
-
|
|
1661
|
+
enableTimeTravel: true,
|
|
1662
|
+
includePaths: ['app.*', 'ui.*'],
|
|
1663
|
+
formatPath: (path) => path.replace(/\.(\d+)/g, '[$1]'),
|
|
1657
1664
|
})
|
|
1658
1665
|
);
|
|
1659
1666
|
|
|
@@ -1661,7 +1668,7 @@ const tree = signalTree({
|
|
|
1661
1668
|
async function fetchUser(id: string) {
|
|
1662
1669
|
return await api.getUser(id);
|
|
1663
1670
|
}
|
|
1664
|
-
tree.$.app.data.users.byId(userId)(); // O(1) lookup
|
|
1671
|
+
tree.$.app.data.users.byId(userId)?.(); // O(1) lookup
|
|
1665
1672
|
tree.undo(); // Time travel
|
|
1666
1673
|
tree.save(); // Persistence
|
|
1667
1674
|
```
|
|
@@ -1669,12 +1676,11 @@ tree.save(); // Persistence
|
|
|
1669
1676
|
### Production-Ready Composition
|
|
1670
1677
|
|
|
1671
1678
|
```typescript
|
|
1672
|
-
import { signalTree, batching,
|
|
1679
|
+
import { signalTree, batching, serialization } from '@signaltree/core';
|
|
1673
1680
|
|
|
1674
1681
|
// Production build (no dev tools)
|
|
1675
1682
|
const tree = signalTree(initialState).with(
|
|
1676
1683
|
batching(), // Performance optimization
|
|
1677
|
-
entities(), // Data management
|
|
1678
1684
|
// withAsync removed β use async helpers for API integration
|
|
1679
1685
|
serialization({
|
|
1680
1686
|
// User preferences
|
|
@@ -1690,14 +1696,13 @@ const tree = signalTree(initialState).with(
|
|
|
1690
1696
|
### Conditional Enhancement
|
|
1691
1697
|
|
|
1692
1698
|
```typescript
|
|
1693
|
-
import { signalTree, batching,
|
|
1699
|
+
import { signalTree, batching, devTools, withTimeTravel } from '@signaltree/core';
|
|
1694
1700
|
|
|
1695
1701
|
const isDevelopment = process.env['NODE_ENV'] === 'development';
|
|
1696
1702
|
|
|
1697
1703
|
// Conditional enhancement based on environment
|
|
1698
1704
|
const tree = signalTree(state).with(
|
|
1699
1705
|
batching(), // Always include performance
|
|
1700
|
-
entities(), // Always include data management
|
|
1701
1706
|
...(isDevelopment
|
|
1702
1707
|
? [
|
|
1703
1708
|
// Development-only features
|
|
@@ -1742,7 +1747,7 @@ const tree = signalTree(state);
|
|
|
1742
1747
|
const tree2 = tree.with(batching());
|
|
1743
1748
|
|
|
1744
1749
|
// Phase 3: Add data management for collections
|
|
1745
|
-
const tree3 = tree2
|
|
1750
|
+
const tree3 = tree2; // entityMap is auto-processed in v7
|
|
1746
1751
|
|
|
1747
1752
|
// Phase 4: Add async for API integration
|
|
1748
1753
|
// withAsync removed β no explicit async enhancer; use async helpers instead
|
|
@@ -1820,7 +1825,7 @@ For fair, reproducible measurements that reflect your app and hardware, use the
|
|
|
1820
1825
|
{{ userTree.$.error() }}
|
|
1821
1826
|
<button (click)="loadUsers()">Retry</button>
|
|
1822
1827
|
</div>
|
|
1823
|
-
} @else { @for (user of users
|
|
1828
|
+
} @else { @for (user of users(); track user.id) {
|
|
1824
1829
|
<div class="user-card">
|
|
1825
1830
|
<h3>{{ user.name }}</h3>
|
|
1826
1831
|
<p>{{ user.email }}</p>
|
|
@@ -1848,6 +1853,8 @@ class UserManagerComponent implements OnInit {
|
|
|
1848
1853
|
form: { id: '', name: '', email: '' },
|
|
1849
1854
|
});
|
|
1850
1855
|
|
|
1856
|
+
readonly users = this.userTree.$.users;
|
|
1857
|
+
|
|
1851
1858
|
constructor(private userService: UserService) {}
|
|
1852
1859
|
|
|
1853
1860
|
ngOnInit() {
|
|
@@ -2032,9 +2039,9 @@ tree.destroy(); // Cleanup resources
|
|
|
2032
2039
|
|
|
2033
2040
|
// Entity helpers (when using entityMap + entities)
|
|
2034
2041
|
// tree.$.users.addOne(user); // Add single entity
|
|
2035
|
-
// tree.$.users.byId(id)(); // O(1) lookup by ID
|
|
2036
|
-
// tree.$.users.all;
|
|
2037
|
-
// tree.$.users.
|
|
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
|
|
2038
2045
|
```
|
|
2039
2046
|
|
|
2040
2047
|
## Extending with enhancers
|
|
@@ -2054,8 +2061,8 @@ All enhancers are included in `@signaltree/core`:
|
|
|
2054
2061
|
|
|
2055
2062
|
- **batching()** - Batch multiple updates for better performance
|
|
2056
2063
|
- **memoization()** - Intelligent caching & performance optimization
|
|
2057
|
-
- **entities()** -
|
|
2058
|
-
- **devTools()** -
|
|
2064
|
+
- **entities()** - Legacy helper (not required when using `entityMap()` in v7)
|
|
2065
|
+
- **devTools()** - Auto-connect, path actions, and time-travel dispatch
|
|
2059
2066
|
- **withTimeTravel()** - Undo/redo functionality & state history
|
|
2060
2067
|
- **serialization()** - State persistence & SSR support
|
|
2061
2068
|
- **createDevTree()** - Pre-configured development setup
|
|
@@ -2210,11 +2217,11 @@ All enhancers are now consolidated in the core package. The following features a
|
|
|
2210
2217
|
|
|
2211
2218
|
### Advanced Features
|
|
2212
2219
|
|
|
2213
|
-
- **entities()** (+0.97KB gzipped) -
|
|
2220
|
+
- **entities()** (+0.97KB gzipped) - Legacy helper (not required when using `entityMap()` in v7)
|
|
2214
2221
|
|
|
2215
2222
|
### Development Tools
|
|
2216
2223
|
|
|
2217
|
-
- **devTools()** (+2.49KB gzipped) -
|
|
2224
|
+
- **devTools()** (+2.49KB gzipped) - Auto-connect, path actions, and time-travel dispatch
|
|
2218
2225
|
- **withTimeTravel()** (+1.75KB gzipped) - Undo/redo functionality & state history
|
|
2219
2226
|
|
|
2220
2227
|
### Integration & Convenience
|
|
@@ -216,7 +216,7 @@ function sanitizeState(value, options, depth = 0, seen = new WeakSet()) {
|
|
|
216
216
|
if (typeof value === 'number' || typeof value === 'boolean') return value;
|
|
217
217
|
if (typeof value === 'bigint') return `${value.toString()}n`;
|
|
218
218
|
if (typeof value === 'symbol') return String(value);
|
|
219
|
-
if (typeof value === 'function') return
|
|
219
|
+
if (typeof value === 'function') return undefined;
|
|
220
220
|
if (depth >= maxDepth) return '[MaxDepth]';
|
|
221
221
|
if (value instanceof Date) return value.toISOString();
|
|
222
222
|
if (value instanceof RegExp) return value.toString();
|
|
@@ -245,6 +245,7 @@ function sanitizeState(value, options, depth = 0, seen = new WeakSet()) {
|
|
|
245
245
|
}
|
|
246
246
|
const result = {};
|
|
247
247
|
for (const [key, val] of Object.entries(obj)) {
|
|
248
|
+
if (typeof val === 'function') continue;
|
|
248
249
|
result[key] = sanitizeState(val, options, depth + 1, seen);
|
|
249
250
|
}
|
|
250
251
|
return result;
|
|
@@ -299,6 +300,7 @@ function devTools(config = {}) {
|
|
|
299
300
|
logger.logComposition(modules, 'with');
|
|
300
301
|
};
|
|
301
302
|
const activeProfiles = new Map();
|
|
303
|
+
let browserDevToolsConnection = null;
|
|
302
304
|
let browserDevTools = null;
|
|
303
305
|
let isConnected = false;
|
|
304
306
|
let isApplyingExternalState = false;
|
|
@@ -374,7 +376,7 @@ function devTools(config = {}) {
|
|
|
374
376
|
lastSnapshot = currentSnapshot;
|
|
375
377
|
return;
|
|
376
378
|
}
|
|
377
|
-
const effectiveAction = pendingExplicitAction ? pendingAction : formattedPaths.length === 1 ? buildAction(`SignalTree/${formattedPaths[0]}`, formattedPaths[0]) : formattedPaths.length > 1 ? buildAction('SignalTree/
|
|
379
|
+
const effectiveAction = pendingExplicitAction ? pendingAction : formattedPaths.length === 1 ? buildAction(`SignalTree/${formattedPaths[0]}`, formattedPaths[0]) : formattedPaths.length > 1 ? buildAction('SignalTree/update', formattedPaths) : buildAction('SignalTree/update');
|
|
378
380
|
const actionMeta = {
|
|
379
381
|
timestamp: Date.now(),
|
|
380
382
|
...(pendingSource && {
|
|
@@ -521,15 +523,20 @@ function devTools(config = {}) {
|
|
|
521
523
|
skip: true
|
|
522
524
|
}
|
|
523
525
|
});
|
|
526
|
+
browserDevToolsConnection = connection;
|
|
524
527
|
browserDevTools = {
|
|
525
528
|
send: connection.send,
|
|
526
529
|
subscribe: connection.subscribe
|
|
527
530
|
};
|
|
528
531
|
if (browserDevTools.subscribe && !unsubscribeDevTools) {
|
|
529
|
-
browserDevTools.subscribe(handleDevToolsMessage);
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
}
|
|
532
|
+
const maybeUnsubscribe = browserDevTools.subscribe(handleDevToolsMessage);
|
|
533
|
+
if (typeof maybeUnsubscribe === 'function') {
|
|
534
|
+
unsubscribeDevTools = maybeUnsubscribe;
|
|
535
|
+
} else {
|
|
536
|
+
unsubscribeDevTools = () => {
|
|
537
|
+
browserDevTools?.subscribe?.(() => void 0);
|
|
538
|
+
};
|
|
539
|
+
}
|
|
533
540
|
}
|
|
534
541
|
const rawSnapshot = readSnapshot();
|
|
535
542
|
const sanitized = buildSerializedState(rawSnapshot);
|
|
@@ -652,7 +659,17 @@ function devTools(config = {}) {
|
|
|
652
659
|
initBrowserDevTools();
|
|
653
660
|
},
|
|
654
661
|
disconnectDevTools() {
|
|
662
|
+
try {
|
|
663
|
+
unsubscribeDevTools?.();
|
|
664
|
+
} catch {}
|
|
665
|
+
try {
|
|
666
|
+
browserDevToolsConnection?.unsubscribe?.();
|
|
667
|
+
} catch {}
|
|
668
|
+
try {
|
|
669
|
+
browserDevToolsConnection?.disconnect?.();
|
|
670
|
+
} catch {}
|
|
655
671
|
browserDevTools = null;
|
|
672
|
+
browserDevToolsConnection = null;
|
|
656
673
|
isConnected = false;
|
|
657
674
|
if (unsubscribeNotifier) {
|
|
658
675
|
unsubscribeNotifier();
|
|
@@ -662,10 +679,7 @@ function devTools(config = {}) {
|
|
|
662
679
|
unsubscribeFlush();
|
|
663
680
|
unsubscribeFlush = null;
|
|
664
681
|
}
|
|
665
|
-
|
|
666
|
-
unsubscribeDevTools();
|
|
667
|
-
unsubscribeDevTools = null;
|
|
668
|
-
}
|
|
682
|
+
unsubscribeDevTools = null;
|
|
669
683
|
if (effectRef) {
|
|
670
684
|
effectRef.destroy();
|
|
671
685
|
effectRef = null;
|
package/dist/lib/utils.js
CHANGED
|
@@ -180,6 +180,8 @@ function unwrap(node) {
|
|
|
180
180
|
} else {
|
|
181
181
|
result[key] = unwrappedValue;
|
|
182
182
|
}
|
|
183
|
+
} else if (typeof value === 'function') {
|
|
184
|
+
continue;
|
|
183
185
|
} else if (typeof value === 'object' && value !== null && !Array.isArray(value) && !isBuiltInObject(value)) {
|
|
184
186
|
result[key] = unwrap(value);
|
|
185
187
|
} else {
|
|
@@ -212,6 +214,9 @@ function unwrap(node) {
|
|
|
212
214
|
if (typeof v === 'function') continue;
|
|
213
215
|
}
|
|
214
216
|
const value = node[key];
|
|
217
|
+
if (typeof value === 'function' && !isNodeAccessor(value) && !isSignal(value)) {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
215
220
|
if (isNodeAccessor(value)) {
|
|
216
221
|
const unwrappedValue = value();
|
|
217
222
|
if (typeof unwrappedValue === 'object' && unwrappedValue !== null && !Array.isArray(unwrappedValue) && !isBuiltInObject(unwrappedValue)) {
|
|
@@ -235,6 +240,9 @@ function unwrap(node) {
|
|
|
235
240
|
const symbols = Object.getOwnPropertySymbols(node);
|
|
236
241
|
for (const sym of symbols) {
|
|
237
242
|
const value = node[sym];
|
|
243
|
+
if (typeof value === 'function' && !isNodeAccessor(value) && !isSignal(value)) {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
238
246
|
if (isNodeAccessor(value)) {
|
|
239
247
|
const unwrappedValue = value();
|
|
240
248
|
if (typeof unwrappedValue === 'object' && unwrappedValue !== null && !Array.isArray(unwrappedValue) && !isBuiltInObject(unwrappedValue)) {
|
|
@@ -263,6 +271,12 @@ function snapshotState(state) {
|
|
|
263
271
|
function applyState(stateNode, snapshot) {
|
|
264
272
|
if (snapshot === null || snapshot === undefined) return;
|
|
265
273
|
if (typeof snapshot !== 'object') return;
|
|
274
|
+
if (stateNode && typeof stateNode === 'object' && typeof stateNode.setAll === 'function' && snapshot && typeof snapshot === 'object' && Array.isArray(snapshot.all)) {
|
|
275
|
+
try {
|
|
276
|
+
stateNode.setAll(snapshot.all);
|
|
277
|
+
return;
|
|
278
|
+
} catch {}
|
|
279
|
+
}
|
|
266
280
|
for (const key of Object.keys(snapshot)) {
|
|
267
281
|
const val = snapshot[key];
|
|
268
282
|
const target = stateNode[key];
|
|
@@ -288,6 +302,14 @@ function applyState(stateNode, snapshot) {
|
|
|
288
302
|
target(val);
|
|
289
303
|
} catch {}
|
|
290
304
|
}
|
|
305
|
+
} else if (target && typeof target === 'object' && val && typeof val === 'object' && !Array.isArray(target) && !Array.isArray(val)) {
|
|
306
|
+
try {
|
|
307
|
+
applyState(target, val);
|
|
308
|
+
} catch {
|
|
309
|
+
try {
|
|
310
|
+
stateNode[key] = val;
|
|
311
|
+
} catch {}
|
|
312
|
+
}
|
|
291
313
|
} else {
|
|
292
314
|
try {
|
|
293
315
|
stateNode[key] = val;
|
package/package.json
CHANGED
package/src/lib/utils.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { WritableSignal } from '@angular/core';
|
|
2
|
-
import { deepEqual, isBuiltInObject, parsePath } from '@signaltree/shared';
|
|
3
2
|
export { deepEqual };
|
|
4
3
|
export { deepEqual as equal };
|
|
5
4
|
export { isBuiltInObject };
|
|
@@ -11,6 +10,26 @@ export interface MemoryManager {
|
|
|
11
10
|
dispose(): void;
|
|
12
11
|
}
|
|
13
12
|
import type { NodeAccessor, TreeNode } from './types';
|
|
13
|
+
|
|
14
|
+
// Inlined from @signaltree/shared (internal package)
|
|
15
|
+
declare function deepEqual<T>(a: T, b: T): boolean;
|
|
16
|
+
declare function deepClone<T>(obj: T): T;
|
|
17
|
+
declare function isBuiltInObject(value: unknown): boolean;
|
|
18
|
+
declare function parsePath(path: string): string[];
|
|
19
|
+
declare function matchPath(path: string[], pattern: string[]): boolean;
|
|
20
|
+
declare function mergeDeep<T>(target: T, source: unknown): T;
|
|
21
|
+
declare function snapshotsEqual<T>(a: T, b: T): boolean;
|
|
22
|
+
declare function getChanges<T>(prev: T, next: T): unknown;
|
|
23
|
+
declare class LRUCache<K, V> {
|
|
24
|
+
constructor(maxSize: number);
|
|
25
|
+
get(key: K): V | undefined;
|
|
26
|
+
set(key: K, value: V): void;
|
|
27
|
+
has(key: K): boolean;
|
|
28
|
+
delete(key: K): boolean;
|
|
29
|
+
clear(): void;
|
|
30
|
+
get size(): number;
|
|
31
|
+
}
|
|
32
|
+
declare const DEFAULT_PATH_CACHE_SIZE: number;
|
|
14
33
|
export declare function isNodeAccessor(value: unknown): value is NodeAccessor<unknown>;
|
|
15
34
|
export declare function isAnySignal(value: unknown): boolean;
|
|
16
35
|
export declare function toWritableSignal<T>(node: NodeAccessor<T>, injector?: unknown): WritableSignal<T>;
|