@@ -72,6 +72,38 @@ dotnet add package EasyAppDev.Blazor.Store
7272
7373---
7474
75+ ## Required Services
76+
77+ Before using ` StoreComponent<TState> ` or async helpers, you ** MUST** register utility services:
78+
79+ ### Quick Setup (Recommended)
80+ ``` csharp
81+ builder .Services .AddStoreWithUtilities (
82+ new MyState (),
83+ (store , sp ) => store .WithDefaults (sp , " MyStore" ));
84+ ```
85+
86+ ### Manual Setup
87+ ``` csharp
88+ // 1. Register utilities (required for UpdateDebounced, UpdateThrottled, LazyLoad)
89+ builder .Services .AddStoreUtilities ();
90+
91+ // 2. Register async executor (required for ExecuteAsync)
92+ builder .Services .AddAsyncActionExecutor <MyState >();
93+
94+ // 3. Register store
95+ builder .Services .AddStore (
96+ new MyState (),
97+ (store , sp ) => store .WithDefaults (sp , " MyStore" ));
98+ ```
99+
100+ ### What Each Service Provides
101+ - ** ` AddStoreUtilities() ` ** : Registers ` IDebounceManager ` , ` IThrottleManager ` , ` ILazyCache ` (scoped)
102+ - ** ` AddAsyncActionExecutor<T>() ` ** : Enables ` ExecuteAsync ` helper methods (scoped)
103+ - ** ` AddStoreWithUtilities() ` ** : One-liner that combines all registrations (recommended)
104+
105+ ---
106+
75107## Quick Start
76108
77109** 3 steps. 1 minute. Zero boilerplate.**
@@ -104,12 +136,21 @@ public record CounterState(int Count, string? LastAction = null)
104136In your ` Program.cs ` :
105137
106138``` csharp
139+ // Option 1: All-in-one registration (RECOMMENDED)
140+ builder .Services .AddStoreWithUtilities (
141+ new CounterState (0 ),
142+ (store , sp ) => store .WithDefaults (sp , " Counter" ));
143+
144+ // Option 2: Manual registration (if you need more control)
145+ builder .Services .AddStoreUtilities (); // Required for async helpers
146+ builder .Services .AddAsyncActionExecutor <CounterState >(); // Required for ExecuteAsync
107147builder .Services .AddStore (
108148 new CounterState (0 ),
109149 (store , sp ) => store .WithDefaults (sp , " Counter" ));
110150```
111151
112- > 💡 ** Tip** : Use ` WithDefaults() ` for one-liner setup with JSRuntime, DevTools, and Logging!
152+ > 💡 ** Tip** : Use ` AddStoreWithUtilities() ` for one-liner setup that includes all required services!
153+ > 💡 ** Tip** : Use ` WithDefaults() ` for JSRuntime, DevTools, and Logging configuration!
113154
114155### Step 3: Use in Components
115156
@@ -708,7 +749,7 @@ public record CounterState(int Count, string? LastAction = null)
708749}
709750
710751// Program.cs
711- builder .Services .AddStore (
752+ builder .Services .AddStoreWithUtilities (
712753 new CounterState (0 ),
713754 (store , sp ) => store .WithDefaults (sp , " Counter" ));
714755
@@ -758,7 +799,7 @@ public record TodoState(ImmutableList<Todo> Todos)
758799}
759800
760801// Program.cs
761- builder .Services .AddStore (
802+ builder .Services .AddStoreWithUtilities (
762803 TodoState .Initial ,
763804 (store , sp ) => store
764805 .WithDefaults (sp , " Todos" )
@@ -885,7 +926,7 @@ public record AuthState(User? CurrentUser, bool IsLoading, string? Error)
885926
886927// Program.cs
887928builder .Services .AddScoped <IAuthService , AuthService >();
888- builder .Services .AddStore (
929+ builder .Services .AddStoreWithUtilities (
889930 AuthState .Initial ,
890931 (store , sp ) => store
891932 .WithDefaults (sp , " Auth" )
@@ -987,7 +1028,7 @@ public record ChartDataPoint(DateTime Time, decimal Value);
9871028public record ServerStatus (bool IsHealthy , int ResponseTimeMs );
9881029
9891030// Program.cs
990- builder .Services .AddStore (
1031+ builder .Services .AddStoreWithUtilities (
9911032 DashboardState .Initial ,
9921033 (store , sp ) => store .WithDefaults (sp , " Dashboard" ));
9931034
@@ -1511,7 +1552,7 @@ Explore the async helpers in action with our interactive demos:
15111552Automatically save and restore state :
15121553
15131554```csharp
1514- builder .Services .AddStore (
1555+ builder .Services .AddStoreWithUtilities (
15151556 new AppState (),
15161557 (store , sp ) => store
15171558 .WithDefaults (sp , " MyApp" )
@@ -1530,7 +1571,9 @@ Time-travel debugging with Redux DevTools:
15301571``` csharp
15311572builder .Services .AddStore (
15321573 new AppState (),
1533- store => store .WithDevTools (" MyApp" ));
1574+ (store , sp ) => store
1575+ .WithJSRuntime (sp .GetRequiredService <IJSRuntime >())
1576+ .WithDevTools (" MyApp" ));
15341577```
15351578
15361579** Features:**
@@ -1646,13 +1689,13 @@ public class LoggingMiddleware<TState> : IMiddleware<TState> where TState : notn
16461689
16471690 public LoggingMiddleware (ILogger <LoggingMiddleware <TState >> logger ) => _logger = logger ;
16481691
1649- public Task OnBeforeUpdateAsync (TState currentState , string action )
1692+ public Task OnBeforeUpdateAsync (TState currentState , string ? action )
16501693 {
16511694 _logger .LogInformation (" Before: {Action}" , action );
16521695 return Task .CompletedTask ;
16531696 }
16541697
1655- public Task OnAfterUpdateAsync (TState newState , string action )
1698+ public Task OnAfterUpdateAsync (TState previousState , TState newState , string ? action )
16561699 {
16571700 _logger .LogInformation (" After: {Action} -> {State}" , action , newState );
16581701 return Task .CompletedTask ;
@@ -1662,7 +1705,10 @@ public class LoggingMiddleware<TState> : IMiddleware<TState> where TState : notn
16621705// Register
16631706builder .Services .AddStore (
16641707 new AppState (),
1665- store => store .WithMiddleware <LoggingMiddleware <AppState >>());
1708+ (store , sp ) => {
1709+ var logger = sp .GetRequiredService <ILogger <LoggingMiddleware <AppState >>>();
1710+ return store .WithMiddleware (new LoggingMiddleware <AppState >(logger ));
1711+ });
16661712```
16671713
16681714** Built-in middleware:**
@@ -1677,80 +1723,170 @@ builder.Services.AddStore(
16771723
16781724### StoreComponent<TState >
16791725
1680- Base component class with automatic subscription:
1726+ Base component class with automatic subscription and async helpers :
16811727
16821728``` csharp
16831729public abstract class StoreComponent <TState > : ComponentBase
16841730{
1685- protected TState State { get ; } // Current state
1686- protected Task Update (Func <TState , TState > updater ); // Update (returns Task)
1687- protected Task UpdateAsync (Func <TState , Task <TState >> updater ); // Async update
1731+ // State access
1732+ protected TState State { get ; }
1733+
1734+ // Core update methods
1735+ protected Task Update (Func <TState , TState > updater , string ? action = null );
1736+ protected Task UpdateAsync (Func <TState , Task <TState >> asyncUpdater , string ? action = null );
1737+
1738+ // Debounced updates
1739+ protected Task UpdateDebounced (
1740+ Func <TState , TState > updater ,
1741+ int delayMilliseconds ,
1742+ [CallerMemberName ] string ? action = null );
1743+ protected Task UpdateDebouncedAsync (
1744+ Func <TState , Task <TState >> asyncUpdater ,
1745+ int delayMilliseconds ,
1746+ [CallerMemberName ] string ? action = null );
1747+
1748+ // Throttled updates
1749+ protected Task UpdateThrottled (
1750+ Func <TState , TState > updater ,
1751+ int intervalMilliseconds ,
1752+ [CallerMemberName ] string ? action = null );
1753+ protected Task UpdateThrottledAsync (
1754+ Func <TState , Task <TState >> asyncUpdater ,
1755+ int intervalMilliseconds ,
1756+ [CallerMemberName ] string ? action = null );
1757+
1758+ // Async action execution (requires IAsyncActionExecutor<TState> registration)
1759+ protected Task ExecuteAsync <TResult >(
1760+ Func <Task <TResult >> asyncAction ,
1761+ Func <TState , TState > loading ,
1762+ Func <TState , TResult , TState > success ,
1763+ Func <TState , Exception , TState >? error = null ,
1764+ [CallerMemberName ] string ? action = null );
1765+
1766+ // Lazy loading with caching
1767+ protected Task <T > LazyLoad <T >(
1768+ string cacheKey ,
1769+ Func <Task <T >> loader ,
1770+ TimeSpan ? cacheFor = null );
16881771}
16891772```
16901773
1774+ > ** Note** : ` [CallerMemberName] ` automatically fills the ` action ` parameter with the calling method name for DevTools/logging.
1775+
16911776### SelectorStoreComponent<TState >
16921777
16931778Optimized component with granular reactivity:
16941779
16951780``` csharp
16961781public abstract class SelectorStoreComponent <TState > : ComponentBase
16971782{
1783+ // State access
16981784 protected TState State { get ; }
16991785 protected object ? Selected { get ; }
1700- protected abstract object SelectState (TState state ); // Override this
1786+
1787+ // Required: Override this to select specific state slice
1788+ protected abstract object SelectState (TState state );
1789+
1790+ // Update methods (same as StoreComponent)
1791+ protected Task Update (Func <TState , TState > updater , string ? action = null );
1792+ protected Task UpdateAsync (Func <TState , Task <TState >> asyncUpdater , string ? action = null );
17011793}
17021794```
17031795
1796+ > ** Note** : ` SelectorStoreComponent ` does NOT include async helpers (UpdateDebounced, ExecuteAsync, LazyLoad). It's focused solely on granular re-rendering optimization. Use ` StoreComponent ` if you need async helpers.
1797+
17041798### IStore<TState >
17051799
17061800Store interface (rarely used directly):
17071801
17081802``` csharp
1709- public interface IStore <TState >
1803+ public interface IStore <TState > :
1804+ IStateReader <TState >,
1805+ IStateWriter <TState >,
1806+ IStateObservable <TState >,
1807+ IDisposable
1808+ where TState : notnull
1809+ {
1810+ // Composed from three segregated interfaces (Interface Segregation Principle)
1811+ }
1812+ ```
1813+
1814+ ** Component Interfaces:**
1815+
1816+ ``` csharp
1817+ // Read-only state access
1818+ public interface IStateReader <TState > where TState : notnull
17101819{
17111820 TState GetState ();
1821+ }
1822+
1823+ // State update operations
1824+ public interface IStateWriter <TState > where TState : notnull
1825+ {
17121826 void Update (Func <TState , TState > updater , string ? action = null );
17131827 Task UpdateAsync (Func <TState , TState > updater , string ? action = null );
17141828 Task UpdateAsync (Func <TState , Task <TState >> asyncUpdater , string ? action = null );
1829+ }
1830+
1831+ // Subscription management
1832+ public interface IStateObservable <TState > where TState : notnull
1833+ {
17151834 IDisposable Subscribe (Action <TState > callback );
17161835 IDisposable Subscribe <TSelected >(
17171836 Func <TState , TSelected > selector ,
17181837 Action <TSelected > callback );
1838+ IDisposable Subscribe <TSelected >(
1839+ Func <TState , TSelected > selector ,
1840+ Action <TSelected > callback ,
1841+ IEqualityComparer <TSelected > comparer );
17191842}
17201843```
17211844
1845+ > ** Note** : The segregated interface design allows components to depend only on the capabilities they need (SOLID principles).
1846+
17221847### Registration
17231848
17241849``` csharp
1725- // Simple - no configuration
1726- services .AddStore (new MyState ());
1727-
1728- // With defaults (recommended)
1729- services .AddStore (
1850+ // RECOMMENDED: All-in-one with utilities
1851+ builder .Services .AddStoreWithUtilities (
17301852 new MyState (),
17311853 (store , sp ) => store .WithDefaults (sp , " MyStore" ));
17321854
1733- // Full configuration
1734- services .AddStore (
1855+ // Manual registration (when you need control)
1856+ builder .Services .AddStoreUtilities (); // Registers async helper services
1857+ builder .Services .AddAsyncActionExecutor <MyState >(); // Required for ExecuteAsync
1858+ builder .Services .AddStore (
17351859 new MyState (),
1736- (store , sp ) => store
1737- .WithDefaults (sp , " StoreName" )
1738- .WithPersistence (sp , " storage-key" )
1739- .WithDiagnosticsIfAvailable (sp )
1740- .WithMiddleware <MyMiddleware >()
1741- );
1860+ (store , sp ) => store .WithDefaults (sp , " MyStore" ));
1861+
1862+ // Simple - minimal configuration (no async helpers)
1863+ builder .Services .AddStore (new MyState ());
17421864
1743- // Manual configuration (for advanced scenarios)
1744- services . AddStore (
1865+ // Full configuration with middleware
1866+ builder . Services . AddStoreWithUtilities (
17451867 new MyState (),
1746- (store , sp ) => store
1747- .WithJSRuntime (sp .GetRequiredService <IJSRuntime >())
1748- .WithDevTools (" StoreName" )
1749- .WithLogging ()
1750- .WithMiddleware <MyMiddleware >()
1751- );
1868+ (store , sp ) => {
1869+ var customMiddleware = new MyCustomMiddleware <MyState >();
1870+ return store
1871+ .WithDefaults (sp , " StoreName" )
1872+ .WithPersistence (sp , " storage-key" )
1873+ .WithDiagnosticsIfAvailable (sp )
1874+ .WithMiddleware (customMiddleware ); // Instance, not generic!
1875+ });
1876+
1877+ // Scoped stores (Blazor Server)
1878+ builder .Services .AddScopedStoreWithUtilities (
1879+ new SessionState (),
1880+ store => store .WithLogging ());
1881+
1882+ // Scoped with factory
1883+ builder .Services .AddScopedStoreWithUtilities (
1884+ sp => new UserState (sp .GetRequiredService <IUserContext >().UserId ),
1885+ store => store .WithLogging ());
17521886```
17531887
1888+ > ** Important** : Use ` WithMiddleware(instance) ` with a middleware ** instance** , NOT ` WithMiddleware<T>() ` - the generic method doesn't exist!
1889+
17541890
17551891---
17561892
@@ -1996,6 +2132,33 @@ Update(s => s.LoadDataAsync(service)); // Returns Task!
19962132await UpdateAsync (async s => await s .LoadDataAsync (service ));
19972133```
19982134
2135+ ### ExecuteAsync not available / throwing InvalidOperationException?
2136+
2137+ ❌ ** Error:** ` IAsyncActionExecutor<MyState> is not registered `
2138+
2139+ ✅ ** Solution:** Register the async executor before using ` ExecuteAsync ` :
2140+
2141+ ``` csharp
2142+ // Option 1: All-in-one (recommended)
2143+ builder .Services .AddStoreWithUtilities (
2144+ new MyState (),
2145+ (store , sp ) => store .WithDefaults (sp , " MyStore" ));
2146+
2147+ // Option 2: Manual registration
2148+ builder .Services .AddAsyncActionExecutor <MyState >();
2149+ builder .Services .AddStore (new MyState (), .. .);
2150+ ```
2151+
2152+ ### UpdateDebounced/UpdateThrottled/LazyLoad not available?
2153+
2154+ ✅ ** Solution:** Register utility services:
2155+
2156+ ``` csharp
2157+ builder .Services .AddStoreUtilities (); // Required!
2158+ // OR
2159+ builder .Services .AddStoreWithUtilities (.. .); // Includes utilities
2160+ ```
2161+
19992162---
20002163
20012164## Documentation
0 commit comments