@signaltree/core 9.0.0 → 9.1.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 +73 -66
- package/dist/index.js +0 -1
- package/dist/lib/signal-tree.js +49 -4
- package/package.json +1 -6
- package/src/enhancers/types.d.ts +1 -1
- package/src/enhancers/typing/helpers-types.d.ts +1 -1
- package/src/index.d.ts +1 -2
- package/src/lib/types.d.ts +1 -26
- package/dist/enhancers/effects/effects.js +0 -74
- package/dist/enhancers/memoization/memoization.js +0 -325
- package/dist/enhancers/presets/lib/presets.js +0 -27
- package/dist/lib/presets.js +0 -30
- package/dist/presets.js +0 -2
- package/src/enhancers/memoization/index.d.ts +0 -1
- package/src/enhancers/memoization/memoization.d.ts +0 -54
- package/src/enhancers/memoization/memoization.types.d.ts +0 -1
- package/src/enhancers/memoization/test-setup.d.ts +0 -3
- package/src/enhancers/presets/index.d.ts +0 -1
- package/src/enhancers/presets/lib/presets.d.ts +0 -8
- package/src/lib/presets.d.ts +0 -34
- package/src/presets.d.ts +0 -2
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
|
-
> 📖 **
|
|
121
|
+
> 📖 **Production app structure**: For anything beyond a single-component prototype, follow the [Recommended Default Architecture](https://github.com/JBorgia/signaltree/blob/main/docs/architecture/signaltree-architecture-guide.md#recommended-default-architecture) — it wraps the patterns below in an `AppStore` facade with a `$ + ops` split, derived tiers, and an ESLint guard against direct tree mutation from components/services. The snippets in this README are deliberately minimal and use the low-level core API directly so they stay self-contained; in real apps the mutations shown here belong inside `*Ops` classes.
|
|
122
122
|
|
|
123
123
|
Follow these principles for idiomatic SignalTree code:
|
|
124
124
|
|
|
@@ -416,17 +416,18 @@ effect(() => {
|
|
|
416
416
|
// Best Practices:
|
|
417
417
|
// 1. Use computed() for derived state that depends on signals
|
|
418
418
|
// 2. Keep computations pure - no side effects
|
|
419
|
-
// 3.
|
|
419
|
+
// 3. Angular's computed() automatically caches results
|
|
420
420
|
// 4. Chain computed values for complex transformations
|
|
421
421
|
// 5. Use factory functions for parameterized computations
|
|
422
422
|
```
|
|
423
423
|
|
|
424
|
-
### Performance optimization with
|
|
424
|
+
### Performance optimization with computed()
|
|
425
425
|
|
|
426
|
-
|
|
426
|
+
Angular's built-in `computed()` provides automatic memoization — a result is cached until one of the signals it reads from changes. No additional enhancer is required:
|
|
427
427
|
|
|
428
428
|
```typescript
|
|
429
|
-
import {
|
|
429
|
+
import { computed } from '@angular/core';
|
|
430
|
+
import { signalTree } from '@signaltree/core';
|
|
430
431
|
|
|
431
432
|
const tree = signalTree({
|
|
432
433
|
items: Array.from({ length: 10000 }, (_, i) => ({
|
|
@@ -434,9 +435,9 @@ const tree = signalTree({
|
|
|
434
435
|
value: Math.random(),
|
|
435
436
|
category: `cat-${i % 10}`,
|
|
436
437
|
})),
|
|
437
|
-
})
|
|
438
|
+
});
|
|
438
439
|
|
|
439
|
-
// Expensive computation - automatically cached by
|
|
440
|
+
// Expensive computation - automatically cached by Angular's computed()
|
|
440
441
|
const expensiveComputation = computed(() => {
|
|
441
442
|
return tree.$.items()
|
|
442
443
|
.filter((item) => item.value > 0.5)
|
|
@@ -444,9 +445,11 @@ const expensiveComputation = computed(() => {
|
|
|
444
445
|
});
|
|
445
446
|
|
|
446
447
|
// The computation only runs when tree.$.items() actually changes
|
|
447
|
-
// Subsequent calls return cached result
|
|
448
|
+
// Subsequent calls return the cached result
|
|
448
449
|
```
|
|
449
450
|
|
|
451
|
+
> **9.0.1 note:** The `memoization()` enhancer was removed. Angular's `computed()` already memoizes; the enhancer added no value on top of it.
|
|
452
|
+
|
|
450
453
|
### Advanced usage (full state tree)
|
|
451
454
|
|
|
452
455
|
```typescript
|
|
@@ -644,9 +647,8 @@ All enhancers are exported directly from `@signaltree/core`:
|
|
|
644
647
|
**Performance Enhancers:**
|
|
645
648
|
|
|
646
649
|
- `batching()` - Batch updates to reduce recomputation and rendering
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
- `withHighPerformanceMemoization()` - Optimized memoization for large state trees
|
|
650
|
+
|
|
651
|
+
> **9.0.1 note:** The `memoization()` enhancer and all preset variants were removed. Use Angular's built-in `computed()` — it provides equivalent memoization with zero additional runtime cost.
|
|
650
652
|
|
|
651
653
|
**Data Management:**
|
|
652
654
|
|
|
@@ -661,11 +663,6 @@ All enhancers are exported directly from `@signaltree/core`:
|
|
|
661
663
|
- `devTools()` - Redux DevTools auto-connect, path actions, and time-travel dispatch
|
|
662
664
|
- `withTimeTravel()` - Undo/redo functionality
|
|
663
665
|
|
|
664
|
-
**Presets:**
|
|
665
|
-
|
|
666
|
-
- `createDevTree()` - Pre-configured development setup
|
|
667
|
-
- `TREE_PRESETS` - Common configuration patterns
|
|
668
|
-
|
|
669
666
|
#### Additional Packages
|
|
670
667
|
|
|
671
668
|
These are the **only** separate packages in the SignalTree ecosystem:
|
|
@@ -691,7 +688,7 @@ const tree = signalTree({ count: 0 }).with(
|
|
|
691
688
|
**Performance-Focused Stack:**
|
|
692
689
|
|
|
693
690
|
```typescript
|
|
694
|
-
import { signalTree, batching,
|
|
691
|
+
import { signalTree, batching, entities } from '@signaltree/core';
|
|
695
692
|
|
|
696
693
|
const tree = signalTree({
|
|
697
694
|
products: entityMap<Product>(),
|
|
@@ -756,27 +753,9 @@ Enhancers can declare metadata for automatic dependency resolution:
|
|
|
756
753
|
// Enhancers are automatically ordered based on requirements
|
|
757
754
|
const tree = signalTree(state).with(
|
|
758
755
|
devTools(), // Requires: core, provides: debugging
|
|
759
|
-
batching()
|
|
760
|
-
memoization() // Requires: batching, provides: caching
|
|
756
|
+
batching() // Requires: core, provides: batching
|
|
761
757
|
);
|
|
762
|
-
// Automatically ordered: batching ->
|
|
763
|
-
```
|
|
764
|
-
|
|
765
|
-
#### Quick Start with Presets
|
|
766
|
-
|
|
767
|
-
For common patterns, use presets that combine multiple enhancers:
|
|
768
|
-
|
|
769
|
-
```typescript
|
|
770
|
-
import { createDevTree, TREE_PRESETS } from '@signaltree/core';
|
|
771
|
-
|
|
772
|
-
// Development preset includes: batching, memoization, devtools, time-travel
|
|
773
|
-
const devTree = createDevTree({
|
|
774
|
-
products: [] as Product[],
|
|
775
|
-
cart: { items: [], total: 0 },
|
|
776
|
-
});
|
|
777
|
-
|
|
778
|
-
// Or use preset configurations
|
|
779
|
-
const customTree = signalTree(state, TREE_PRESETS.DASHBOARD);
|
|
758
|
+
// Automatically ordered: batching -> devtools
|
|
780
759
|
```
|
|
781
760
|
|
|
782
761
|
#### Core Stubs
|
|
@@ -1548,15 +1527,14 @@ tree.effect(() => console.log('State changed'));
|
|
|
1548
1527
|
### Performance-Enhanced Composition
|
|
1549
1528
|
|
|
1550
1529
|
```typescript
|
|
1551
|
-
import { signalTree, batching
|
|
1530
|
+
import { signalTree, batching } from '@signaltree/core';
|
|
1552
1531
|
|
|
1553
1532
|
// Add performance optimizations
|
|
1554
1533
|
const tree = signalTree({
|
|
1555
1534
|
products: [] as Product[],
|
|
1556
1535
|
filters: { category: '', search: '' },
|
|
1557
1536
|
}).with(
|
|
1558
|
-
batching()
|
|
1559
|
-
memoization() // Cache expensive computations
|
|
1537
|
+
batching() // Batch updates for optimal rendering
|
|
1560
1538
|
);
|
|
1561
1539
|
|
|
1562
1540
|
// Now supports batched updates
|
|
@@ -1565,7 +1543,7 @@ tree.batchUpdate((state) => ({
|
|
|
1565
1543
|
filters: { category: 'electronics', search: '' },
|
|
1566
1544
|
}));
|
|
1567
1545
|
|
|
1568
|
-
//
|
|
1546
|
+
// Angular's computed() automatically caches derived values
|
|
1569
1547
|
const filteredProducts = computed(() => {
|
|
1570
1548
|
return tree.$.products()
|
|
1571
1549
|
.filter((p) => p.category.includes(tree.$.filters.category()))
|
|
@@ -1671,7 +1649,8 @@ tree.save(); // Persistence
|
|
|
1671
1649
|
```
|
|
1672
1650
|
|
|
1673
1651
|
### Aggregated Redux DevTools Instance
|
|
1674
|
-
|
|
1652
|
+
|
|
1653
|
+
````
|
|
1675
1654
|
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
1655
|
|
|
1677
1656
|
```typescript
|
|
@@ -1770,21 +1749,21 @@ const tree = signalTree(state).with(
|
|
|
1770
1749
|
### Preset-Based Composition
|
|
1771
1750
|
|
|
1772
1751
|
```typescript
|
|
1773
|
-
import {
|
|
1752
|
+
import { signalTree, batching, devTools, withTimeTravel } from '@signaltree/core';
|
|
1774
1753
|
|
|
1775
|
-
//
|
|
1776
|
-
const devTree =
|
|
1754
|
+
// Compose the enhancers you actually need
|
|
1755
|
+
const devTree = signalTree({
|
|
1777
1756
|
products: [],
|
|
1778
1757
|
cart: { items: [], total: 0 },
|
|
1779
1758
|
user: null,
|
|
1780
|
-
})
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
const customTree = signalTree(state, TREE_PRESETS.PERFORMANCE);
|
|
1785
|
-
// Includes: batching, memoization optimizations
|
|
1759
|
+
})
|
|
1760
|
+
.with(batching())
|
|
1761
|
+
.with(devTools())
|
|
1762
|
+
.with(withTimeTravel());
|
|
1786
1763
|
```
|
|
1787
1764
|
|
|
1765
|
+
> **9.0.1 note:** Preset factories (`createDevTree`, `TREE_PRESETS`, etc.) were removed. Compose enhancers directly with `.with()`.
|
|
1766
|
+
|
|
1788
1767
|
### Measuring bundle size
|
|
1789
1768
|
|
|
1790
1769
|
Bundle sizes depend on your build, tree-shaking, and which enhancers you include. Use the scripts in `scripts/` to analyze min+gz for your configuration.
|
|
@@ -1818,7 +1797,7 @@ if (isDevelopment) {
|
|
|
1818
1797
|
}
|
|
1819
1798
|
|
|
1820
1799
|
if (needsPerformance) {
|
|
1821
|
-
tree = tree.with(batching()
|
|
1800
|
+
tree = tree.with(batching());
|
|
1822
1801
|
}
|
|
1823
1802
|
|
|
1824
1803
|
if (needsTimeTravel) {
|
|
@@ -2085,6 +2064,7 @@ tree.unwrap(); // Get plain object
|
|
|
2085
2064
|
|
|
2086
2065
|
// Tree operations
|
|
2087
2066
|
tree.update(updater); // Update entire tree
|
|
2067
|
+
tree.updateAndReport(updater); // Update + return changed leaf paths (9.1+)
|
|
2088
2068
|
tree.effect(fn); // Create reactive effects
|
|
2089
2069
|
tree.subscribe(fn); // Manual subscriptions
|
|
2090
2070
|
tree.destroy(); // Cleanup resources
|
|
@@ -2096,15 +2076,35 @@ tree.destroy(); // Cleanup resources
|
|
|
2096
2076
|
// tree.$.users.selectBy(pred); // Filtered signal
|
|
2097
2077
|
```
|
|
2098
2078
|
|
|
2079
|
+
### updateAndReport (9.1+)
|
|
2080
|
+
|
|
2081
|
+
Like `update`, but returns the dot-paths of every leaf signal whose value
|
|
2082
|
+
actually changed. Useful for diff logging, audit trails, optimistic-update
|
|
2083
|
+
rollback, and selective re-syncing to a server.
|
|
2084
|
+
|
|
2085
|
+
```typescript
|
|
2086
|
+
const tree = signalTree({ user: { name: 'Ada', age: 36 }, count: 0 });
|
|
2087
|
+
|
|
2088
|
+
const changed = tree.updateAndReport({
|
|
2089
|
+
user: { name: 'Ada', age: 37 }, // age changes, name is ref-equal
|
|
2090
|
+
count: 0, // ref-equal, skipped
|
|
2091
|
+
});
|
|
2092
|
+
// changed === ['user.age']
|
|
2093
|
+
```
|
|
2094
|
+
|
|
2095
|
+
Reference-equal values are skipped automatically by the underlying
|
|
2096
|
+
`update` path, so passing a server-returned partial that mostly matches
|
|
2097
|
+
local state is cheap (`O(changed)` instead of `O(N)`).
|
|
2098
|
+
|
|
2099
2099
|
## Extending with enhancers
|
|
2100
2100
|
|
|
2101
2101
|
SignalTree Core includes all enhancers built-in:
|
|
2102
2102
|
|
|
2103
2103
|
```typescript
|
|
2104
|
-
import { signalTree, batching,
|
|
2104
|
+
import { signalTree, batching, withTimeTravel } from '@signaltree/core';
|
|
2105
2105
|
|
|
2106
2106
|
// All enhancers available from @signaltree/core
|
|
2107
|
-
const tree = signalTree(initialState).with(batching(),
|
|
2107
|
+
const tree = signalTree(initialState).with(batching(), withTimeTravel());
|
|
2108
2108
|
```
|
|
2109
2109
|
|
|
2110
2110
|
### Available enhancers
|
|
@@ -2112,13 +2112,10 @@ const tree = signalTree(initialState).with(batching(), memoization(), withTimeTr
|
|
|
2112
2112
|
All enhancers are included in `@signaltree/core`:
|
|
2113
2113
|
|
|
2114
2114
|
- **batching()** - Batch multiple updates for better performance
|
|
2115
|
-
- **memoization()** - Intelligent caching & performance optimization
|
|
2116
2115
|
- **entities()** - Advanced entity management & CRUD operations
|
|
2117
2116
|
- **devTools()** - Redux DevTools integration for debugging
|
|
2118
2117
|
- **withTimeTravel()** - Undo/redo functionality & state history
|
|
2119
2118
|
- **serialization()** - State persistence & SSR support
|
|
2120
|
-
- **createDevTree()** - Pre-configured development setup
|
|
2121
|
-
- **TREE_PRESETS** - Common configuration patterns (PERFORMANCE, DASHBOARD, etc.)
|
|
2122
2119
|
|
|
2123
2120
|
## When to use core only
|
|
2124
2121
|
|
|
@@ -2132,7 +2129,7 @@ Perfect for:
|
|
|
2132
2129
|
|
|
2133
2130
|
Consider enhancers when you need:
|
|
2134
2131
|
|
|
2135
|
-
- ⚡ Performance optimization (batching
|
|
2132
|
+
- ⚡ Performance optimization (batching)
|
|
2136
2133
|
- 🐛 Advanced debugging (devTools, withTimeTravel)
|
|
2137
2134
|
- 📦 Entity management (entities)
|
|
2138
2135
|
|
|
@@ -2144,6 +2141,21 @@ Consider separate packages when you need:
|
|
|
2144
2141
|
|
|
2145
2142
|
## Migration from NgRx
|
|
2146
2143
|
|
|
2144
|
+
### Case Study
|
|
2145
|
+
|
|
2146
|
+
Measured from a production Angular mobile application migrating from NgRx Signal Store to SignalTree. Results reflect one team's experience; your mileage will vary depending on app complexity and existing architecture.
|
|
2147
|
+
|
|
2148
|
+
| Metric | NgRx | SignalTree | Change |
|
|
2149
|
+
| --- | --- | --- | --- |
|
|
2150
|
+
| **App state code** | 11,735 lines / 45 files | 2,825 lines / 23 files | **-76%** |
|
|
2151
|
+
| **npm packages** | 4 (@ngrx/\*) | 1 (@signaltree/core) | **-75%** |
|
|
2152
|
+
| **State bundle (gzip)** | ~50KB | ~27KB | **-46%** |
|
|
2153
|
+
| **Boilerplate files** | 17 custom `withX` helpers | 0 (built-in) | **Eliminated** |
|
|
2154
|
+
|
|
2155
|
+
13 separate stores → 1 unified tree. `entityMap()` replaced a 222-line `withEntityCrud` wrapper. Derived tiers replaced scattered `withComputed` blocks.
|
|
2156
|
+
|
|
2157
|
+
### Migration Steps
|
|
2158
|
+
|
|
2147
2159
|
```typescript
|
|
2148
2160
|
// Step 1: Create parallel tree
|
|
2149
2161
|
const tree = signalTree(initialState);
|
|
@@ -2265,7 +2277,6 @@ All enhancers are now consolidated in the core package. The following features a
|
|
|
2265
2277
|
### Performance & Optimization
|
|
2266
2278
|
|
|
2267
2279
|
- **batching()** (+1.27KB gzipped) - Batch multiple updates for better performance
|
|
2268
|
-
- **memoization()** (+2.33KB gzipped) - Intelligent caching & performance optimization
|
|
2269
2280
|
|
|
2270
2281
|
### Advanced Features
|
|
2271
2282
|
|
|
@@ -2279,8 +2290,6 @@ All enhancers are now consolidated in the core package. The following features a
|
|
|
2279
2290
|
### Integration & Convenience
|
|
2280
2291
|
|
|
2281
2292
|
- **serialization()** (+0.84KB gzipped) - State persistence & SSR support
|
|
2282
|
-
- **ecommercePreset()** - Pre-configured setups for e-commerce applications
|
|
2283
|
-
- **dashboardPreset()** - Pre-configured setups for dashboard applications
|
|
2284
2293
|
|
|
2285
2294
|
### Quick Start with Extensions
|
|
2286
2295
|
|
|
@@ -2294,13 +2303,10 @@ npm install @signaltree/core
|
|
|
2294
2303
|
import {
|
|
2295
2304
|
signalTree,
|
|
2296
2305
|
batching,
|
|
2297
|
-
memoization,
|
|
2298
2306
|
entities,
|
|
2299
2307
|
devTools,
|
|
2300
2308
|
withTimeTravel,
|
|
2301
|
-
serialization
|
|
2302
|
-
ecommercePreset,
|
|
2303
|
-
dashboardPreset
|
|
2309
|
+
serialization
|
|
2304
2310
|
} from '@signaltree/core';
|
|
2305
2311
|
```
|
|
2306
2312
|
|
|
@@ -2626,7 +2632,7 @@ export default {
|
|
|
2626
2632
|
|
|
2627
2633
|
**Start with just `@signaltree/core`** - it includes comprehensive enhancers for most applications:
|
|
2628
2634
|
|
|
2629
|
-
- Performance optimization (batching
|
|
2635
|
+
- Performance optimization (batching)
|
|
2630
2636
|
- Data management (entities, async operations)
|
|
2631
2637
|
- Development tools (devtools, time-travel)
|
|
2632
2638
|
- State persistence (serialization)
|
|
@@ -2671,3 +2677,4 @@ MIT License with AI Training Restriction - see the [LICENSE](../../LICENSE) file
|
|
|
2671
2677
|
---
|
|
2672
2678
|
|
|
2673
2679
|
**Ready to get started?** This core package provides everything you need for most applications. Add extensions only when you need them! 🚀
|
|
2680
|
+
````
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,6 @@ export { composeEnhancers, isAnySignal, isNodeAccessor, toWritableSignal } from
|
|
|
10
10
|
export { getPathNotifier } from './lib/path-notifier.js';
|
|
11
11
|
export { createEnhancer, resolveEnhancerOrder } from './enhancers/index.js';
|
|
12
12
|
export { batching } from './enhancers/batching/batching.js';
|
|
13
|
-
export { memoization } from './enhancers/memoization/memoization.js';
|
|
14
13
|
export { timeTravel } from './enhancers/time-travel/time-travel.js';
|
|
15
14
|
export { persistence, serialization } from './enhancers/serialization/serialization.js';
|
|
16
15
|
export { devTools } from './enhancers/devtools/devtools.js';
|
package/dist/lib/signal-tree.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { signal, isSignal } from '@angular/core';
|
|
1
|
+
import { signal, isSignal, untracked } from '@angular/core';
|
|
2
2
|
import { SIGNAL_TREE_MESSAGES, SIGNAL_TREE_CONSTANTS } from './constants.js';
|
|
3
3
|
import { batchScope } from './internals/batch-scope.js';
|
|
4
4
|
import { isRegisteredMarker, materializeMarkers } from './internals/materialize-markers.js';
|
|
@@ -114,19 +114,25 @@ function makeNodeAccessor(store) {
|
|
|
114
114
|
}
|
|
115
115
|
return accessor;
|
|
116
116
|
}
|
|
117
|
-
function recursiveUpdate(target, updates) {
|
|
117
|
+
function recursiveUpdate(target, updates, out, pathPrefix = '') {
|
|
118
118
|
if (!updates || typeof updates !== 'object') return;
|
|
119
119
|
const targetObj = isNodeAccessor(target) ? target : target;
|
|
120
120
|
for (const [key, value] of Object.entries(updates)) {
|
|
121
121
|
const prop = targetObj[key];
|
|
122
122
|
if (prop === undefined) continue;
|
|
123
|
+
const childPath = pathPrefix ? `${pathPrefix}.${key}` : key;
|
|
123
124
|
if (isSignal(prop) && 'set' in prop) {
|
|
124
|
-
prop
|
|
125
|
+
const sig = prop;
|
|
126
|
+
const current = untracked(() => sig());
|
|
127
|
+
if (current === value) continue;
|
|
128
|
+
sig.set(value);
|
|
129
|
+
if (out) out.push(childPath);
|
|
125
130
|
} else if (isNodeAccessor(prop)) {
|
|
126
131
|
if (value && typeof value === 'object') {
|
|
127
|
-
recursiveUpdate(prop, value);
|
|
132
|
+
recursiveUpdate(prop, value, out, childPath);
|
|
128
133
|
} else {
|
|
129
134
|
prop(value);
|
|
135
|
+
if (out) out.push(childPath);
|
|
130
136
|
}
|
|
131
137
|
}
|
|
132
138
|
}
|
|
@@ -333,6 +339,25 @@ function create(initialState, config) {
|
|
|
333
339
|
writable: true,
|
|
334
340
|
configurable: true
|
|
335
341
|
});
|
|
342
|
+
Object.defineProperty(tree, 'updateAndReport', {
|
|
343
|
+
value: function (arg) {
|
|
344
|
+
if (arguments.length === 0) return [];
|
|
345
|
+
const out = [];
|
|
346
|
+
if (typeof arg === 'function') {
|
|
347
|
+
const updater = arg;
|
|
348
|
+
const current = unwrap(signalState);
|
|
349
|
+
batchScope(() => recursiveUpdate(signalState, updater(current), out));
|
|
350
|
+
} else if (typeof arg === 'object' && arg !== null && !Array.isArray(arg)) {
|
|
351
|
+
batchScope(() => recursiveUpdate(signalState, arg, out));
|
|
352
|
+
} else {
|
|
353
|
+
recursiveUpdate(signalState, arg, out);
|
|
354
|
+
}
|
|
355
|
+
return out;
|
|
356
|
+
},
|
|
357
|
+
enumerable: false,
|
|
358
|
+
writable: true,
|
|
359
|
+
configurable: true
|
|
360
|
+
});
|
|
336
361
|
for (const key of Object.keys(signalState)) {
|
|
337
362
|
if (!(key in tree)) {
|
|
338
363
|
Object.defineProperty(tree, key, {
|
|
@@ -453,6 +478,26 @@ function createBuilder(baseTree) {
|
|
|
453
478
|
configurable: true
|
|
454
479
|
});
|
|
455
480
|
}
|
|
481
|
+
Object.defineProperty(builder, 'updateAndReport', {
|
|
482
|
+
value: function (arg) {
|
|
483
|
+
finalize();
|
|
484
|
+
const fn = baseTree['updateAndReport'];
|
|
485
|
+
return fn ? fn.call(baseTree, arg) : [];
|
|
486
|
+
},
|
|
487
|
+
enumerable: false,
|
|
488
|
+
writable: false,
|
|
489
|
+
configurable: true
|
|
490
|
+
});
|
|
491
|
+
Object.defineProperty(builder, 'batchUpdate', {
|
|
492
|
+
value: function (arg) {
|
|
493
|
+
finalize();
|
|
494
|
+
const fn = baseTree['batchUpdate'];
|
|
495
|
+
if (fn) fn.call(baseTree, arg);
|
|
496
|
+
},
|
|
497
|
+
enumerable: false,
|
|
498
|
+
writable: true,
|
|
499
|
+
configurable: true
|
|
500
|
+
});
|
|
456
501
|
Object.defineProperty(builder, 'derived', {
|
|
457
502
|
value: function (factory) {
|
|
458
503
|
if (isFinalized) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signaltree/core",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.1.0",
|
|
4
4
|
"description": "Reactive JSON for Angular. JSON branches, reactive leaves. No actions. No reducers. No selectors.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -14,11 +14,6 @@
|
|
|
14
14
|
"import": "./dist/index.js",
|
|
15
15
|
"default": "./dist/index.js"
|
|
16
16
|
},
|
|
17
|
-
"./presets": {
|
|
18
|
-
"types": "./src/presets.d.ts",
|
|
19
|
-
"import": "./dist/presets.js",
|
|
20
|
-
"default": "./dist/presets.js"
|
|
21
|
-
},
|
|
22
17
|
"./security": {
|
|
23
18
|
"types": "./src/security.d.ts",
|
|
24
19
|
"import": "./dist/security.js",
|
package/src/enhancers/types.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { BatchingMethods, BatchingConfig,
|
|
1
|
+
export { BatchingMethods, BatchingConfig, TimeTravelMethods, TimeTravelConfig, EffectsMethods, DevToolsMethods, DevToolsConfig, EntitiesEnabled, Enhancer, EnhancerWithMeta, EnhancerMeta, } from '../lib/types';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type Equals<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false;
|
|
1
|
+
export type Equals<A, B> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2) ? true : false;
|
|
2
2
|
export type Assert<T extends true> = T;
|
package/src/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { signalTree } from './lib/signal-tree';
|
|
2
|
-
export type { ISignalTree, SignalTree, SignalTreeBase,
|
|
2
|
+
export type { ISignalTree, SignalTree, SignalTreeBase, TreeNode, CallableWritableSignal, AccessibleNode, NodeAccessor, Primitive, NotFn, TreeConfig, Enhancer, EnhancerMeta, EnhancerWithMeta, EntitySignal, EntityMapMarker, EntityConfig, MutationOptions, AddOptions, AddManyOptions, TimeTravelEntry, TimeTravelMethods, EnhancerCleanup, } from './lib/types';
|
|
3
3
|
export { entityMap } from './lib/types';
|
|
4
4
|
export type { ProcessDerived, DeepMergeTree, DerivedFactory, WithDerived, } from './lib/internals/derived-types';
|
|
5
5
|
export { derivedFrom, externalDerived } from './lib/internals/derived-types';
|
|
@@ -15,7 +15,6 @@ export { createEnhancer, resolveEnhancerOrder } from './enhancers/index';
|
|
|
15
15
|
export { ENHANCER_META } from './lib/types';
|
|
16
16
|
export { batching } from './enhancers/batching/batching';
|
|
17
17
|
export type { BatchingConfig, BatchingMethods } from './lib/types';
|
|
18
|
-
export { memoization } from './enhancers/memoization/memoization';
|
|
19
18
|
export { timeTravel } from './enhancers/time-travel/time-travel';
|
|
20
19
|
export { serialization, persistence, } from './enhancers/serialization/serialization';
|
|
21
20
|
export { devTools } from './enhancers/devtools/devtools';
|
package/src/lib/types.d.ts
CHANGED
|
@@ -14,13 +14,6 @@ export interface TimeTravelConfig {
|
|
|
14
14
|
[key: string]: string | undefined;
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
|
-
export interface MemoizationConfig {
|
|
18
|
-
enabled?: boolean;
|
|
19
|
-
maxCacheSize?: number;
|
|
20
|
-
ttl?: number;
|
|
21
|
-
enableLRU?: boolean;
|
|
22
|
-
equality?: 'deep' | 'shallow' | 'reference';
|
|
23
|
-
}
|
|
24
17
|
export type Primitive = string | number | boolean | null | undefined | bigint | symbol;
|
|
25
18
|
export type NotFn<T> = T extends (...args: unknown[]) => unknown ? never : T;
|
|
26
19
|
declare module '@angular/core' {
|
|
@@ -45,6 +38,7 @@ export interface ISignalTree<T> extends NodeAccessor<T> {
|
|
|
45
38
|
destroy(): void;
|
|
46
39
|
readonly destroyed: Signal<boolean>;
|
|
47
40
|
registerCleanup(fn: EnhancerCleanup): void;
|
|
41
|
+
updateAndReport(updates: Partial<T> | ((current: T) => Partial<T>)): string[];
|
|
48
42
|
}
|
|
49
43
|
export type EnhancerCleanup = () => void;
|
|
50
44
|
export interface EffectsMethods<T> {
|
|
@@ -61,20 +55,6 @@ export interface BatchingMethods<T = unknown> {
|
|
|
61
55
|
hasPendingNotifications(): boolean;
|
|
62
56
|
flushNotifications(): void;
|
|
63
57
|
}
|
|
64
|
-
export interface MemoizationMethods<T> {
|
|
65
|
-
memoize<R>(fn: (state: T) => R, cacheKey?: string): Signal<R>;
|
|
66
|
-
memoizedUpdate?: (updater: (current: T) => Partial<T>, cacheKey?: string) => void;
|
|
67
|
-
clearMemoCache(key?: string): void;
|
|
68
|
-
clearCache?: (key?: string) => void;
|
|
69
|
-
getCacheStats(): CacheStats;
|
|
70
|
-
}
|
|
71
|
-
export type CacheStats = {
|
|
72
|
-
size: number;
|
|
73
|
-
hitRate: number;
|
|
74
|
-
totalHits: number;
|
|
75
|
-
totalMisses: number;
|
|
76
|
-
keys: string[];
|
|
77
|
-
};
|
|
78
58
|
export interface TimeTravelMethods<T = unknown> {
|
|
79
59
|
undo(): void;
|
|
80
60
|
redo(): void;
|
|
@@ -125,10 +105,8 @@ export interface TimeTravelEntry<T> {
|
|
|
125
105
|
state: T;
|
|
126
106
|
payload?: unknown;
|
|
127
107
|
}
|
|
128
|
-
export type TreePreset = 'basic' | 'performance' | 'development' | 'production';
|
|
129
108
|
export interface TreeConfig {
|
|
130
109
|
batchUpdates?: boolean;
|
|
131
|
-
useMemoization?: boolean;
|
|
132
110
|
enableTimeTravel?: boolean;
|
|
133
111
|
useLazySignals?: boolean;
|
|
134
112
|
useShallowComparison?: boolean;
|
|
@@ -310,9 +288,6 @@ export interface EnhancerMeta {
|
|
|
310
288
|
provides?: string[];
|
|
311
289
|
description?: string;
|
|
312
290
|
}
|
|
313
|
-
export type FullSignalTree<T> = ISignalTree<T> & EffectsMethods<T> & BatchingMethods<T> & MemoizationMethods<T> & TimeTravelMethods<T> & DevToolsMethods & EntitiesEnabled & OptimizedUpdateMethods<T>;
|
|
314
|
-
export type ProdSignalTree<T> = ISignalTree<T> & EffectsMethods<T> & BatchingMethods<T> & MemoizationMethods<T> & EntitiesEnabled & OptimizedUpdateMethods<T>;
|
|
315
|
-
export type MinimalSignalTree<T> = ISignalTree<T> & EffectsMethods<T>;
|
|
316
291
|
export type SignalTree<T> = ISignalTree<T> & TreeNode<T>;
|
|
317
292
|
export type SignalTreeBase<T> = ISignalTree<T> & TreeNode<T>;
|
|
318
293
|
export declare function isSignalTree<T>(value: unknown): value is ISignalTree<T>;
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { effect, untracked } from '@angular/core';
|
|
2
|
-
import { ENHANCER_META } from '../../lib/types.js';
|
|
3
|
-
|
|
4
|
-
function effects(config = {}) {
|
|
5
|
-
const {
|
|
6
|
-
enabled = true
|
|
7
|
-
} = config;
|
|
8
|
-
const enhancerFn = tree => {
|
|
9
|
-
const cleanupFns = [];
|
|
10
|
-
const methods = {
|
|
11
|
-
effect(effectFn) {
|
|
12
|
-
if (!enabled) {
|
|
13
|
-
return () => {};
|
|
14
|
-
}
|
|
15
|
-
let innerCleanup;
|
|
16
|
-
const effectRef = effect(() => {
|
|
17
|
-
const state = tree();
|
|
18
|
-
if (innerCleanup) {
|
|
19
|
-
untracked(() => innerCleanup());
|
|
20
|
-
}
|
|
21
|
-
innerCleanup = untracked(() => effectFn(state));
|
|
22
|
-
});
|
|
23
|
-
const cleanup = () => {
|
|
24
|
-
if (innerCleanup) {
|
|
25
|
-
innerCleanup();
|
|
26
|
-
}
|
|
27
|
-
effectRef.destroy();
|
|
28
|
-
};
|
|
29
|
-
cleanupFns.push(cleanup);
|
|
30
|
-
return cleanup;
|
|
31
|
-
},
|
|
32
|
-
subscribe(fn) {
|
|
33
|
-
if (!enabled) {
|
|
34
|
-
return () => {};
|
|
35
|
-
}
|
|
36
|
-
const effectRef = effect(() => {
|
|
37
|
-
const state = tree();
|
|
38
|
-
untracked(() => fn(state));
|
|
39
|
-
});
|
|
40
|
-
const cleanup = () => {
|
|
41
|
-
effectRef.destroy();
|
|
42
|
-
};
|
|
43
|
-
cleanupFns.push(cleanup);
|
|
44
|
-
return cleanup;
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
const originalDestroy = tree.destroy?.bind(tree);
|
|
48
|
-
tree.destroy = () => {
|
|
49
|
-
cleanupFns.forEach(fn => fn());
|
|
50
|
-
cleanupFns.length = 0;
|
|
51
|
-
if (originalDestroy) {
|
|
52
|
-
originalDestroy();
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
return Object.assign(tree, methods);
|
|
56
|
-
};
|
|
57
|
-
const meta = {
|
|
58
|
-
name: 'effects',
|
|
59
|
-
provides: ['effects']
|
|
60
|
-
};
|
|
61
|
-
enhancerFn.metadata = meta;
|
|
62
|
-
enhancerFn[ENHANCER_META] = meta;
|
|
63
|
-
return enhancerFn;
|
|
64
|
-
}
|
|
65
|
-
function enableEffects() {
|
|
66
|
-
return effects({
|
|
67
|
-
enabled: true
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
Object.assign((config = {}) => effects(config), {
|
|
71
|
-
enable: enableEffects
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
export { effects, enableEffects };
|
|
@@ -1,325 +0,0 @@
|
|
|
1
|
-
import { computed } from '@angular/core';
|
|
2
|
-
import { isNodeAccessor } from '../../lib/utils.js';
|
|
3
|
-
import { ENHANCER_META } from '../../lib/types.js';
|
|
4
|
-
import { LRUCache } from '../../shared/lib/lru-cache.js';
|
|
5
|
-
import { deepEqual } from '../../shared/lib/deep-equal.js';
|
|
6
|
-
|
|
7
|
-
function isDevMode() {
|
|
8
|
-
if (typeof __DEV__ !== 'undefined') {
|
|
9
|
-
return __DEV__;
|
|
10
|
-
}
|
|
11
|
-
return false;
|
|
12
|
-
}
|
|
13
|
-
const MAX_CACHE_SIZE = 1000;
|
|
14
|
-
const memoizationCache = new Map();
|
|
15
|
-
function createMemoCacheStore(maxSize, enableLRU) {
|
|
16
|
-
if (enableLRU) {
|
|
17
|
-
const cache = new LRUCache(maxSize);
|
|
18
|
-
const shadow = new Map();
|
|
19
|
-
const pruneShadow = () => {
|
|
20
|
-
while (shadow.size > cache.size()) {
|
|
21
|
-
const oldestKey = shadow.keys().next().value;
|
|
22
|
-
if (oldestKey === undefined) {
|
|
23
|
-
break;
|
|
24
|
-
}
|
|
25
|
-
shadow.delete(oldestKey);
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
return {
|
|
29
|
-
get: key => {
|
|
30
|
-
const value = cache.get(key);
|
|
31
|
-
if (value !== undefined) {
|
|
32
|
-
shadow.set(key, value);
|
|
33
|
-
} else if (shadow.has(key)) {
|
|
34
|
-
shadow.delete(key);
|
|
35
|
-
}
|
|
36
|
-
return value;
|
|
37
|
-
},
|
|
38
|
-
set: (key, value) => {
|
|
39
|
-
cache.set(key, value);
|
|
40
|
-
shadow.set(key, value);
|
|
41
|
-
pruneShadow();
|
|
42
|
-
},
|
|
43
|
-
delete: key => {
|
|
44
|
-
cache.delete(key);
|
|
45
|
-
shadow.delete(key);
|
|
46
|
-
},
|
|
47
|
-
clear: () => {
|
|
48
|
-
cache.clear();
|
|
49
|
-
shadow.clear();
|
|
50
|
-
},
|
|
51
|
-
size: () => shadow.size,
|
|
52
|
-
forEach: callback => {
|
|
53
|
-
shadow.forEach((value, key) => callback(value, key));
|
|
54
|
-
},
|
|
55
|
-
keys: () => shadow.keys()
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
const store = new Map();
|
|
59
|
-
return {
|
|
60
|
-
get: key => store.get(key),
|
|
61
|
-
set: (key, value) => {
|
|
62
|
-
store.set(key, value);
|
|
63
|
-
},
|
|
64
|
-
delete: key => store.delete(key),
|
|
65
|
-
clear: () => store.clear(),
|
|
66
|
-
size: () => store.size,
|
|
67
|
-
forEach: callback => {
|
|
68
|
-
store.forEach((value, key) => callback(value, key));
|
|
69
|
-
},
|
|
70
|
-
keys: () => store.keys()
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
function getCleanupInterval(tree) {
|
|
74
|
-
return tree._memoCleanupInterval;
|
|
75
|
-
}
|
|
76
|
-
function setCleanupInterval(tree, interval) {
|
|
77
|
-
if (interval === undefined) {
|
|
78
|
-
delete tree._memoCleanupInterval;
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
tree._memoCleanupInterval = interval;
|
|
82
|
-
}
|
|
83
|
-
function clearCleanupInterval(tree) {
|
|
84
|
-
const interval = getCleanupInterval(tree);
|
|
85
|
-
if (!interval) {
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
try {
|
|
89
|
-
clearInterval(interval);
|
|
90
|
-
} catch {}
|
|
91
|
-
setCleanupInterval(tree);
|
|
92
|
-
}
|
|
93
|
-
function shallowEqual(a, b) {
|
|
94
|
-
if (a === b) return true;
|
|
95
|
-
if (a == null || b == null) return false;
|
|
96
|
-
if (typeof a !== typeof b) return false;
|
|
97
|
-
if (typeof a === 'object' && typeof b === 'object') {
|
|
98
|
-
const objA = a;
|
|
99
|
-
const objB = b;
|
|
100
|
-
let countA = 0;
|
|
101
|
-
for (const key in objA) {
|
|
102
|
-
if (!Object.prototype.hasOwnProperty.call(objA, key)) continue;
|
|
103
|
-
countA++;
|
|
104
|
-
if (!(key in objB) || objA[key] !== objB[key]) return false;
|
|
105
|
-
}
|
|
106
|
-
let countB = 0;
|
|
107
|
-
for (const key in objB) {
|
|
108
|
-
if (Object.prototype.hasOwnProperty.call(objB, key)) countB++;
|
|
109
|
-
}
|
|
110
|
-
return countA === countB;
|
|
111
|
-
}
|
|
112
|
-
return false;
|
|
113
|
-
}
|
|
114
|
-
function generateCacheKey(fn, args) {
|
|
115
|
-
try {
|
|
116
|
-
return `${fn.name || 'anonymous'}_${JSON.stringify(args)}`;
|
|
117
|
-
} catch {
|
|
118
|
-
return `${fn.name || 'anonymous'}_${args.length}`;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
function getEqualityFn(strategy) {
|
|
122
|
-
switch (strategy) {
|
|
123
|
-
case 'shallow':
|
|
124
|
-
return shallowEqual;
|
|
125
|
-
case 'reference':
|
|
126
|
-
return (a, b) => a === b;
|
|
127
|
-
case 'deep':
|
|
128
|
-
default:
|
|
129
|
-
return deepEqual;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
const MEMOIZATION_PRESETS = {
|
|
133
|
-
selector: {
|
|
134
|
-
equality: 'reference',
|
|
135
|
-
maxCacheSize: 10,
|
|
136
|
-
enableLRU: false,
|
|
137
|
-
ttl: undefined
|
|
138
|
-
},
|
|
139
|
-
computed: {
|
|
140
|
-
equality: 'shallow',
|
|
141
|
-
maxCacheSize: 100,
|
|
142
|
-
enableLRU: false,
|
|
143
|
-
ttl: undefined
|
|
144
|
-
},
|
|
145
|
-
deepState: {
|
|
146
|
-
equality: 'deep',
|
|
147
|
-
maxCacheSize: 1000,
|
|
148
|
-
enableLRU: true,
|
|
149
|
-
ttl: 5 * 60 * 1000
|
|
150
|
-
},
|
|
151
|
-
highFrequency: {
|
|
152
|
-
equality: 'reference',
|
|
153
|
-
maxCacheSize: 5,
|
|
154
|
-
enableLRU: false,
|
|
155
|
-
ttl: undefined
|
|
156
|
-
}
|
|
157
|
-
};
|
|
158
|
-
function selectorMemoization() {
|
|
159
|
-
return memoization(MEMOIZATION_PRESETS.selector);
|
|
160
|
-
}
|
|
161
|
-
function computedMemoization() {
|
|
162
|
-
return memoization(MEMOIZATION_PRESETS.computed);
|
|
163
|
-
}
|
|
164
|
-
function deepStateMemoization() {
|
|
165
|
-
return memoization(MEMOIZATION_PRESETS.deepState);
|
|
166
|
-
}
|
|
167
|
-
function highFrequencyMemoization() {
|
|
168
|
-
return memoization(MEMOIZATION_PRESETS.highFrequency);
|
|
169
|
-
}
|
|
170
|
-
Object.assign((config = {}) => memoization(config), {
|
|
171
|
-
selector: selectorMemoization,
|
|
172
|
-
computed: computedMemoization,
|
|
173
|
-
deep: deepStateMemoization,
|
|
174
|
-
fast: highFrequencyMemoization
|
|
175
|
-
});
|
|
176
|
-
function memoization(config = {}) {
|
|
177
|
-
const {
|
|
178
|
-
enabled = true,
|
|
179
|
-
maxCacheSize = 1000,
|
|
180
|
-
ttl,
|
|
181
|
-
equality = 'deep',
|
|
182
|
-
enableLRU = true
|
|
183
|
-
} = config;
|
|
184
|
-
const enhancer = tree => {
|
|
185
|
-
const originalTreeCall = tree.bind(tree);
|
|
186
|
-
const applyUpdateResult = result => {
|
|
187
|
-
Object.entries(result).forEach(([propKey, value]) => {
|
|
188
|
-
const property = tree.state[propKey];
|
|
189
|
-
if (property && 'set' in property) {
|
|
190
|
-
property.set(value);
|
|
191
|
-
} else if (isNodeAccessor(property)) {
|
|
192
|
-
property(value);
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
};
|
|
196
|
-
if (!enabled) {
|
|
197
|
-
const memoTree = tree;
|
|
198
|
-
memoTree.memoize = (fn, _cacheKey) => {
|
|
199
|
-
return computed(() => fn(originalTreeCall()));
|
|
200
|
-
};
|
|
201
|
-
memoTree.memoizedUpdate = updater => {
|
|
202
|
-
const currentState = originalTreeCall();
|
|
203
|
-
const result = updater(currentState);
|
|
204
|
-
applyUpdateResult(result);
|
|
205
|
-
};
|
|
206
|
-
memoTree.clearMemoCache = () => {};
|
|
207
|
-
memoTree.clearCache = memoTree.clearMemoCache;
|
|
208
|
-
memoTree.getCacheStats = () => ({
|
|
209
|
-
size: 0,
|
|
210
|
-
hitRate: 0,
|
|
211
|
-
totalHits: 0,
|
|
212
|
-
totalMisses: 0,
|
|
213
|
-
keys: []
|
|
214
|
-
});
|
|
215
|
-
return memoTree;
|
|
216
|
-
}
|
|
217
|
-
const cache = createMemoCacheStore(maxCacheSize, enableLRU);
|
|
218
|
-
memoizationCache.set(tree, cache);
|
|
219
|
-
const equalityFn = getEqualityFn(equality);
|
|
220
|
-
tree.memoizedUpdate = (updater, cacheKey) => {
|
|
221
|
-
const currentState = originalTreeCall();
|
|
222
|
-
const key = cacheKey || generateCacheKey(updater, [currentState]);
|
|
223
|
-
const cached = cache.get(key);
|
|
224
|
-
if (cached && equalityFn(cached.deps, [currentState])) {
|
|
225
|
-
const cachedUpdate = cached.value;
|
|
226
|
-
applyUpdateResult(cachedUpdate);
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
const result = updater(currentState);
|
|
230
|
-
cache.set(key, {
|
|
231
|
-
value: result,
|
|
232
|
-
deps: [currentState],
|
|
233
|
-
timestamp: Date.now(),
|
|
234
|
-
hitCount: 1
|
|
235
|
-
});
|
|
236
|
-
applyUpdateResult(result);
|
|
237
|
-
};
|
|
238
|
-
const memoizeResultCache = createMemoCacheStore(MAX_CACHE_SIZE, true);
|
|
239
|
-
tree.memoize = (fn, cacheKey) => {
|
|
240
|
-
return computed(() => {
|
|
241
|
-
const currentState = originalTreeCall();
|
|
242
|
-
const key = cacheKey || generateCacheKey(fn, [currentState]);
|
|
243
|
-
const cached = memoizeResultCache.get(key);
|
|
244
|
-
if (cached && equalityFn(cached.deps, [currentState])) {
|
|
245
|
-
return cached.value;
|
|
246
|
-
}
|
|
247
|
-
if (isDevMode() && tree.__devHooks?.onRecompute) {
|
|
248
|
-
try {
|
|
249
|
-
tree.__devHooks.onRecompute(key, 1);
|
|
250
|
-
} catch {}
|
|
251
|
-
}
|
|
252
|
-
const result = fn(currentState);
|
|
253
|
-
memoizeResultCache.set(key, {
|
|
254
|
-
value: result,
|
|
255
|
-
deps: [currentState],
|
|
256
|
-
timestamp: Date.now(),
|
|
257
|
-
hitCount: 1
|
|
258
|
-
});
|
|
259
|
-
return result;
|
|
260
|
-
});
|
|
261
|
-
};
|
|
262
|
-
tree.clearMemoCache = key => {
|
|
263
|
-
if (key) {
|
|
264
|
-
cache.delete(key);
|
|
265
|
-
} else {
|
|
266
|
-
cache.clear();
|
|
267
|
-
}
|
|
268
|
-
};
|
|
269
|
-
tree.clearCache = tree.clearMemoCache;
|
|
270
|
-
tree.getCacheStats = () => {
|
|
271
|
-
let totalHits = 0;
|
|
272
|
-
let totalMisses = 0;
|
|
273
|
-
cache.forEach(entry => {
|
|
274
|
-
totalHits += entry.hitCount || 0;
|
|
275
|
-
totalMisses += Math.floor((entry.hitCount || 0) / 2);
|
|
276
|
-
});
|
|
277
|
-
const hitRate = totalHits + totalMisses > 0 ? totalHits / (totalHits + totalMisses) : 0;
|
|
278
|
-
return {
|
|
279
|
-
size: cache.size(),
|
|
280
|
-
hitRate,
|
|
281
|
-
totalHits,
|
|
282
|
-
totalMisses,
|
|
283
|
-
keys: Array.from(cache.keys())
|
|
284
|
-
};
|
|
285
|
-
};
|
|
286
|
-
const maybeInterval = getCleanupInterval(tree);
|
|
287
|
-
if (maybeInterval) {
|
|
288
|
-
const origClear = tree.clearMemoCache.bind(tree);
|
|
289
|
-
tree.clearMemoCache = key => {
|
|
290
|
-
origClear(key);
|
|
291
|
-
clearCleanupInterval(tree);
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
tree.clearCache = tree.clearMemoCache;
|
|
295
|
-
if (ttl) {
|
|
296
|
-
const cleanup = () => {
|
|
297
|
-
const now = Date.now();
|
|
298
|
-
cache.forEach((entry, key) => {
|
|
299
|
-
if (entry.timestamp && now - entry.timestamp > ttl) {
|
|
300
|
-
cache.delete(key);
|
|
301
|
-
}
|
|
302
|
-
});
|
|
303
|
-
};
|
|
304
|
-
const intervalId = setInterval(cleanup, ttl);
|
|
305
|
-
setCleanupInterval(tree, intervalId);
|
|
306
|
-
}
|
|
307
|
-
if (typeof tree.registerCleanup === 'function') {
|
|
308
|
-
tree.registerCleanup(() => {
|
|
309
|
-
clearCleanupInterval(tree);
|
|
310
|
-
cache.clear();
|
|
311
|
-
memoizationCache.delete(tree);
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
return tree;
|
|
315
|
-
};
|
|
316
|
-
const meta = {
|
|
317
|
-
name: 'memoization',
|
|
318
|
-
provides: ['memoization']
|
|
319
|
-
};
|
|
320
|
-
enhancer.metadata = meta;
|
|
321
|
-
enhancer[ENHANCER_META] = meta;
|
|
322
|
-
return enhancer;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
export { MEMOIZATION_PRESETS, computedMemoization, deepStateMemoization, highFrequencyMemoization, memoization, selectorMemoization };
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
const TREE_PRESETS = {};
|
|
2
|
-
function createPresetConfig(preset, overrides = {}) {
|
|
3
|
-
return {
|
|
4
|
-
...overrides
|
|
5
|
-
};
|
|
6
|
-
}
|
|
7
|
-
function validatePreset(preset) {
|
|
8
|
-
return typeof preset === 'string';
|
|
9
|
-
}
|
|
10
|
-
function getAvailablePresets() {
|
|
11
|
-
return Object.keys(TREE_PRESETS);
|
|
12
|
-
}
|
|
13
|
-
function combinePresets(presets, overrides = {}) {
|
|
14
|
-
let combined = {};
|
|
15
|
-
for (const p of presets) {
|
|
16
|
-
combined = {
|
|
17
|
-
...combined,
|
|
18
|
-
...TREE_PRESETS[p]
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
return {
|
|
22
|
-
...combined,
|
|
23
|
-
...overrides
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export { TREE_PRESETS, combinePresets, createPresetConfig, getAvailablePresets, validatePreset };
|
package/dist/lib/presets.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { signalTree } from './signal-tree.js';
|
|
2
|
-
import { effects } from '../enhancers/effects/effects.js';
|
|
3
|
-
import { batching } from '../enhancers/batching/batching.js';
|
|
4
|
-
import { memoization } from '../enhancers/memoization/memoization.js';
|
|
5
|
-
import { timeTravel } from '../enhancers/time-travel/time-travel.js';
|
|
6
|
-
import { devTools } from '../enhancers/devtools/devtools.js';
|
|
7
|
-
|
|
8
|
-
function createDevTree(initialState, config = {}) {
|
|
9
|
-
if (arguments.length === 0) {
|
|
10
|
-
const enhancer = tree => tree.with(effects()).with(batching()).with(memoization()).with(timeTravel()).with(devTools());
|
|
11
|
-
return {
|
|
12
|
-
enhancer
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
const base = signalTree(initialState, config);
|
|
16
|
-
const enhanced = base.with(effects()).with(batching(config.batching)).with(memoization(config.memoization)).with(timeTravel(config.timeTravel)).with(devTools(config.devTools));
|
|
17
|
-
return enhanced;
|
|
18
|
-
}
|
|
19
|
-
function createProdTree(initialState, config = {}) {
|
|
20
|
-
const base = signalTree(initialState, config);
|
|
21
|
-
const enhanced = base.with(effects()).with(batching(config.batching)).with(memoization(config.memoization));
|
|
22
|
-
return enhanced;
|
|
23
|
-
}
|
|
24
|
-
function createMinimalTree(initialState, config = {}) {
|
|
25
|
-
const base = signalTree(initialState, config);
|
|
26
|
-
const enhanced = base.with(effects());
|
|
27
|
-
return enhanced;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export { createDevTree, createMinimalTree, createProdTree };
|
package/dist/presets.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './memoization';
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import type { ISignalTree } from '../../lib/types';
|
|
2
|
-
export declare function cleanupMemoizationCache(): void;
|
|
3
|
-
import type { MemoizationConfig, MemoizationMethods } from '../../lib/types';
|
|
4
|
-
export declare function memoize<TArgs extends unknown[], TReturn>(fn: (...args: TArgs) => TReturn, keyFn?: (...args: TArgs) => string, config?: MemoizationConfig): (...args: TArgs) => TReturn;
|
|
5
|
-
export declare function memoizeShallow<TArgs extends unknown[], TReturn>(fn: (...args: TArgs) => TReturn, keyFn?: (...args: TArgs) => string): (...args: TArgs) => TReturn;
|
|
6
|
-
export declare function memoizeReference<TArgs extends unknown[], TReturn>(fn: (...args: TArgs) => TReturn, keyFn?: (...args: TArgs) => string): (...args: TArgs) => TReturn;
|
|
7
|
-
export declare const MEMOIZATION_PRESETS: {
|
|
8
|
-
readonly selector: {
|
|
9
|
-
readonly equality: "reference";
|
|
10
|
-
readonly maxCacheSize: 10;
|
|
11
|
-
readonly enableLRU: false;
|
|
12
|
-
readonly ttl: undefined;
|
|
13
|
-
};
|
|
14
|
-
readonly computed: {
|
|
15
|
-
readonly equality: "shallow";
|
|
16
|
-
readonly maxCacheSize: 100;
|
|
17
|
-
readonly enableLRU: false;
|
|
18
|
-
readonly ttl: undefined;
|
|
19
|
-
};
|
|
20
|
-
readonly deepState: {
|
|
21
|
-
readonly equality: "deep";
|
|
22
|
-
readonly maxCacheSize: 1000;
|
|
23
|
-
readonly enableLRU: true;
|
|
24
|
-
readonly ttl: number;
|
|
25
|
-
};
|
|
26
|
-
readonly highFrequency: {
|
|
27
|
-
readonly equality: "reference";
|
|
28
|
-
readonly maxCacheSize: 5;
|
|
29
|
-
readonly enableLRU: false;
|
|
30
|
-
readonly ttl: undefined;
|
|
31
|
-
};
|
|
32
|
-
};
|
|
33
|
-
export declare function selectorMemoization(): <T>(tree: ISignalTree<T>) => ISignalTree<T> & MemoizationMethods<T>;
|
|
34
|
-
export declare function computedMemoization(): <T>(tree: ISignalTree<T>) => ISignalTree<T> & MemoizationMethods<T>;
|
|
35
|
-
export declare function deepStateMemoization(): <T>(tree: ISignalTree<T>) => ISignalTree<T> & MemoizationMethods<T>;
|
|
36
|
-
export declare function highFrequencyMemoization(): <T>(tree: ISignalTree<T>) => ISignalTree<T> & MemoizationMethods<T>;
|
|
37
|
-
export declare const withMemoization: ((config?: MemoizationConfig) => <T>(tree: ISignalTree<T>) => ISignalTree<T> & MemoizationMethods<T>) & {
|
|
38
|
-
selector: typeof selectorMemoization;
|
|
39
|
-
computed: typeof computedMemoization;
|
|
40
|
-
deep: typeof deepStateMemoization;
|
|
41
|
-
fast: typeof highFrequencyMemoization;
|
|
42
|
-
};
|
|
43
|
-
export declare function memoization(config?: MemoizationConfig): <T>(tree: ISignalTree<T>) => ISignalTree<T> & MemoizationMethods<T>;
|
|
44
|
-
export declare function enableMemoization(): <T>(tree: ISignalTree<T>) => ISignalTree<T> & MemoizationMethods<T>;
|
|
45
|
-
export declare function highPerformanceMemoization(): <T>(tree: ISignalTree<T>) => ISignalTree<T> & MemoizationMethods<T>;
|
|
46
|
-
export declare function lightweightMemoization(): <T>(tree: ISignalTree<T>) => ISignalTree<T> & MemoizationMethods<T>;
|
|
47
|
-
export declare function shallowMemoization(): <T>(tree: ISignalTree<T>) => ISignalTree<T> & MemoizationMethods<T>;
|
|
48
|
-
export declare function clearAllCaches(): void;
|
|
49
|
-
export declare function getGlobalCacheStats(): {
|
|
50
|
-
treeCount: number;
|
|
51
|
-
totalSize: number;
|
|
52
|
-
totalHits: number;
|
|
53
|
-
averageCacheSize: number;
|
|
54
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './lib/presets';
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { createDevTree, createMinimalTree, createProdTree } from '../../../lib/presets';
|
|
2
|
-
import type { TreeConfig, TreePreset } from '../../../lib/types';
|
|
3
|
-
export declare const TREE_PRESETS: Record<TreePreset, Partial<TreeConfig>>;
|
|
4
|
-
export declare function createPresetConfig(preset: TreePreset, overrides?: Partial<TreeConfig>): TreeConfig;
|
|
5
|
-
export declare function validatePreset(preset: TreePreset): boolean;
|
|
6
|
-
export declare function getAvailablePresets(): TreePreset[];
|
|
7
|
-
export declare function combinePresets(presets: TreePreset[], overrides?: Partial<TreeConfig>): TreeConfig;
|
|
8
|
-
export { createDevTree, createProdTree, createMinimalTree };
|
package/src/lib/presets.d.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type { ISignalTree, TreeConfig, BatchingConfig, MemoizationConfig, TimeTravelConfig, DevToolsConfig, BatchingMethods, MemoizationMethods, TimeTravelMethods, DevToolsMethods, EffectsMethods, EntitiesEnabled } from './types';
|
|
2
|
-
export interface DevTreeConfig extends TreeConfig {
|
|
3
|
-
effects?: Record<string, never>;
|
|
4
|
-
batching?: BatchingConfig;
|
|
5
|
-
memoization?: MemoizationConfig;
|
|
6
|
-
timeTravel?: TimeTravelConfig;
|
|
7
|
-
devTools?: DevToolsConfig;
|
|
8
|
-
entities?: Record<string, never>;
|
|
9
|
-
}
|
|
10
|
-
export interface ProdTreeConfig extends TreeConfig {
|
|
11
|
-
effects?: Record<string, never>;
|
|
12
|
-
batching?: BatchingConfig;
|
|
13
|
-
memoization?: MemoizationConfig;
|
|
14
|
-
entities?: Record<string, never>;
|
|
15
|
-
}
|
|
16
|
-
export interface MinimalTreeConfig extends TreeConfig {
|
|
17
|
-
effects?: Record<string, never>;
|
|
18
|
-
}
|
|
19
|
-
export type FullSignalTree<T> = ISignalTree<T> & EffectsMethods<T> & BatchingMethods & MemoizationMethods<T> & EntitiesEnabled & TimeTravelMethods & DevToolsMethods;
|
|
20
|
-
export type ProdSignalTree<T> = ISignalTree<T> & EffectsMethods<T> & BatchingMethods & MemoizationMethods<T> & EntitiesEnabled;
|
|
21
|
-
export type MinimalSignalTree<T> = ISignalTree<T> & EffectsMethods<T>;
|
|
22
|
-
export declare function createDevTree<T extends object>(initialState: T, config?: DevTreeConfig): FullSignalTree<T>;
|
|
23
|
-
export declare function createDevTree(): {
|
|
24
|
-
enhancer: <T>(tree: ISignalTree<T>) => ISignalTree<T> & EffectsMethods<T> & BatchingMethods<T> & MemoizationMethods<T> & EntitiesEnabled & TimeTravelMethods<T> & DevToolsMethods;
|
|
25
|
-
};
|
|
26
|
-
export declare function createProdTree<T extends object>(initialState: T, config?: ProdTreeConfig): ProdSignalTree<T>;
|
|
27
|
-
export declare function createMinimalTree<T extends object>(initialState: T, config?: MinimalTreeConfig): MinimalSignalTree<T>;
|
|
28
|
-
export declare const devTree: typeof createDevTree;
|
|
29
|
-
export declare const prodTree: typeof createProdTree;
|
|
30
|
-
export declare const minimalTree: typeof createMinimalTree;
|
|
31
|
-
export declare function buildTree<T extends object>(initialState: T, config?: TreeConfig): {
|
|
32
|
-
add<R>(enhancer: (t: ISignalTree<T>) => R): any;
|
|
33
|
-
done(): ISignalTree<T>;
|
|
34
|
-
};
|
package/src/presets.d.ts
DELETED