@signaltree/core 9.0.0 → 9.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -118,7 +118,7 @@ Performance and bundle size vary by app shape, build tooling, device, and runtim
118
118
 
119
119
  ## Best Practices (SignalTree-First)
120
120
 
121
- > 📖 **Full guide**: [Implementation Patterns](https://github.com/JBorgia/signaltree/blob/main/docs/IMPLEMENTATION_PATTERNS.md)
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. Leverage automatic memoization for expensive operations
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 memoization
424
+ ### Performance optimization with computed()
425
425
 
426
- Computed values become even more powerful with the built-in memoization enhancer:
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 { signalTree, memoization } from '@signaltree/core';
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
- }).with(memoization());
438
+ });
438
439
 
439
- // Expensive computation - automatically cached by memoization enhancer
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
- - `memoization()` - Intelligent caching for expensive computations
648
- - `highPerformanceBatching()` - Advanced batching for high-frequency updates
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, memoization, entities } from '@signaltree/core';
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(), // Requires: core, provides: batching
760
- memoization() // Requires: batching, provides: caching
756
+ batching() // Requires: core, provides: batching
761
757
  );
762
- // Automatically ordered: batching -> memoization -> devtools
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, memoization } from '@signaltree/core';
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(), // Batch updates for optimal rendering
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
- // Expensive computations are automatically cached
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 { createDevTree, TREE_PRESETS } from '@signaltree/core';
1752
+ import { signalTree, batching, devTools, withTimeTravel } from '@signaltree/core';
1774
1753
 
1775
- // Use presets for common patterns
1776
- const devTree = createDevTree({
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
- // Includes: batching, memoization, devtools, time-travel
1782
-
1783
- // Or use preset configurations directly
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(), memoization());
1800
+ tree = tree.with(batching());
1822
1801
  }
1823
1802
 
1824
1803
  if (needsTimeTravel) {
@@ -2101,10 +2080,10 @@ tree.destroy(); // Cleanup resources
2101
2080
  SignalTree Core includes all enhancers built-in:
2102
2081
 
2103
2082
  ```typescript
2104
- import { signalTree, batching, memoization, withTimeTravel } from '@signaltree/core';
2083
+ import { signalTree, batching, withTimeTravel } from '@signaltree/core';
2105
2084
 
2106
2085
  // All enhancers available from @signaltree/core
2107
- const tree = signalTree(initialState).with(batching(), memoization(), withTimeTravel());
2086
+ const tree = signalTree(initialState).with(batching(), withTimeTravel());
2108
2087
  ```
2109
2088
 
2110
2089
  ### Available enhancers
@@ -2112,13 +2091,10 @@ const tree = signalTree(initialState).with(batching(), memoization(), withTimeTr
2112
2091
  All enhancers are included in `@signaltree/core`:
2113
2092
 
2114
2093
  - **batching()** - Batch multiple updates for better performance
2115
- - **memoization()** - Intelligent caching & performance optimization
2116
2094
  - **entities()** - Advanced entity management & CRUD operations
2117
2095
  - **devTools()** - Redux DevTools integration for debugging
2118
2096
  - **withTimeTravel()** - Undo/redo functionality & state history
2119
2097
  - **serialization()** - State persistence & SSR support
2120
- - **createDevTree()** - Pre-configured development setup
2121
- - **TREE_PRESETS** - Common configuration patterns (PERFORMANCE, DASHBOARD, etc.)
2122
2098
 
2123
2099
  ## When to use core only
2124
2100
 
@@ -2132,7 +2108,7 @@ Perfect for:
2132
2108
 
2133
2109
  Consider enhancers when you need:
2134
2110
 
2135
- - ⚡ Performance optimization (batching, memoization)
2111
+ - ⚡ Performance optimization (batching)
2136
2112
  - 🐛 Advanced debugging (devTools, withTimeTravel)
2137
2113
  - 📦 Entity management (entities)
2138
2114
 
@@ -2144,6 +2120,21 @@ Consider separate packages when you need:
2144
2120
 
2145
2121
  ## Migration from NgRx
2146
2122
 
2123
+ ### Case Study
2124
+
2125
+ 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.
2126
+
2127
+ | Metric | NgRx | SignalTree | Change |
2128
+ | --- | --- | --- | --- |
2129
+ | **App state code** | 11,735 lines / 45 files | 2,825 lines / 23 files | **-76%** |
2130
+ | **npm packages** | 4 (@ngrx/\*) | 1 (@signaltree/core) | **-75%** |
2131
+ | **State bundle (gzip)** | ~50KB | ~27KB | **-46%** |
2132
+ | **Boilerplate files** | 17 custom `withX` helpers | 0 (built-in) | **Eliminated** |
2133
+
2134
+ 13 separate stores → 1 unified tree. `entityMap()` replaced a 222-line `withEntityCrud` wrapper. Derived tiers replaced scattered `withComputed` blocks.
2135
+
2136
+ ### Migration Steps
2137
+
2147
2138
  ```typescript
2148
2139
  // Step 1: Create parallel tree
2149
2140
  const tree = signalTree(initialState);
@@ -2265,7 +2256,6 @@ All enhancers are now consolidated in the core package. The following features a
2265
2256
  ### Performance & Optimization
2266
2257
 
2267
2258
  - **batching()** (+1.27KB gzipped) - Batch multiple updates for better performance
2268
- - **memoization()** (+2.33KB gzipped) - Intelligent caching & performance optimization
2269
2259
 
2270
2260
  ### Advanced Features
2271
2261
 
@@ -2279,8 +2269,6 @@ All enhancers are now consolidated in the core package. The following features a
2279
2269
  ### Integration & Convenience
2280
2270
 
2281
2271
  - **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
2272
 
2285
2273
  ### Quick Start with Extensions
2286
2274
 
@@ -2294,13 +2282,10 @@ npm install @signaltree/core
2294
2282
  import {
2295
2283
  signalTree,
2296
2284
  batching,
2297
- memoization,
2298
2285
  entities,
2299
2286
  devTools,
2300
2287
  withTimeTravel,
2301
- serialization,
2302
- ecommercePreset,
2303
- dashboardPreset
2288
+ serialization
2304
2289
  } from '@signaltree/core';
2305
2290
  ```
2306
2291
 
@@ -2626,7 +2611,7 @@ export default {
2626
2611
 
2627
2612
  **Start with just `@signaltree/core`** - it includes comprehensive enhancers for most applications:
2628
2613
 
2629
- - Performance optimization (batching, memoization)
2614
+ - Performance optimization (batching)
2630
2615
  - Data management (entities, async operations)
2631
2616
  - Development tools (devtools, time-travel)
2632
2617
  - State persistence (serialization)
@@ -2671,3 +2656,4 @@ MIT License with AI Training Restriction - see the [LICENSE](../../LICENSE) file
2671
2656
  ---
2672
2657
 
2673
2658
  **Ready to get started?** This core package provides everything you need for most applications. Add extensions only when you need them! 🚀
2659
+ ````
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signaltree/core",
3
- "version": "9.0.0",
3
+ "version": "9.0.1",
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",
@@ -1 +1 @@
1
- export { BatchingMethods, BatchingConfig, MemoizationMethods, MemoizationConfig, TimeTravelMethods, TimeTravelConfig, EffectsMethods, DevToolsMethods, DevToolsConfig, EntitiesEnabled, Enhancer, EnhancerWithMeta, EnhancerMeta, } from '../lib/types';
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, FullSignalTree, ProdSignalTree, TreeNode, CallableWritableSignal, AccessibleNode, NodeAccessor, Primitive, NotFn, TreeConfig, TreePreset, Enhancer, EnhancerMeta, EnhancerWithMeta, EntitySignal, EntityMapMarker, EntityConfig, MutationOptions, AddOptions, AddManyOptions, TimeTravelEntry, TimeTravelMethods, EnhancerCleanup, } from './lib/types';
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';
@@ -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' {
@@ -61,20 +54,6 @@ export interface BatchingMethods<T = unknown> {
61
54
  hasPendingNotifications(): boolean;
62
55
  flushNotifications(): void;
63
56
  }
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
57
  export interface TimeTravelMethods<T = unknown> {
79
58
  undo(): void;
80
59
  redo(): void;
@@ -125,10 +104,8 @@ export interface TimeTravelEntry<T> {
125
104
  state: T;
126
105
  payload?: unknown;
127
106
  }
128
- export type TreePreset = 'basic' | 'performance' | 'development' | 'production';
129
107
  export interface TreeConfig {
130
108
  batchUpdates?: boolean;
131
- useMemoization?: boolean;
132
109
  enableTimeTravel?: boolean;
133
110
  useLazySignals?: boolean;
134
111
  useShallowComparison?: boolean;
@@ -310,9 +287,6 @@ export interface EnhancerMeta {
310
287
  provides?: string[];
311
288
  description?: string;
312
289
  }
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
290
  export type SignalTree<T> = ISignalTree<T> & TreeNode<T>;
317
291
  export type SignalTreeBase<T> = ISignalTree<T> & TreeNode<T>;
318
292
  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 };
@@ -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,2 +0,0 @@
1
- export { TREE_PRESETS, combinePresets, createPresetConfig, getAvailablePresets, validatePreset } from './enhancers/presets/lib/presets.js';
2
- export { createDevTree, createMinimalTree, createProdTree } from './lib/presets.js';
@@ -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,3 +0,0 @@
1
- import '@angular/compiler';
2
- import 'zone.js';
3
- import 'zone.js/testing';
@@ -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 };
@@ -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
@@ -1,2 +0,0 @@
1
- export { TREE_PRESETS, createPresetConfig, validatePreset, getAvailablePresets, combinePresets, createDevTree, createProdTree, createMinimalTree, } from './enhancers/presets/lib/presets';
2
- export type { TreePreset } from './lib/types';