Skip to content

Commit 150c4f9

Browse files
committed
feat: Simplify store configuration with default middleware and diagnostics support
1 parent a59b00e commit 150c4f9

3 files changed

Lines changed: 263 additions & 99 deletions

File tree

README.md

Lines changed: 173 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ Inspired by Zustand • Built for C# • Designed for Developer Joy
2323
-**Blazing-fast performance** - Granular updates with selectors for 25x fewer re-renders in high-frequency scenarios
2424
- 🎨 **Smart optimizations** - Memoization, batch updates, and lazy loading built right in
2525
- 🧰 **Powerful DevTools** - Time-travel debugging with Redux DevTools integration
26+
- 📊 **Built-in Diagnostics** - Real-time monitoring of state changes, renders, and performance metrics (DEBUG mode)
2627
- 🚀 **Async-first** - First-class support for async operations without extra ceremony
2728
- 📦 **Tiny API surface** - Learn the entire API in minutes, not days
29+
- 🎁 **Simplified API** - One-liner setup with `WithDefaults()` for common configurations
2830

2931
**If you've used Zustand in React, you'll feel right at home. If you haven't, you'll wonder why state management was ever complicated.**
3032

@@ -50,6 +52,7 @@ Inspired by Zustand • Built for C# • Designed for Developer Joy
5052
- [Async Operations](#async-operations)
5153
- [Persistence](#persistence)
5254
- [DevTools](#devtools)
55+
- [Diagnostics & Monitoring](#diagnostics--monitoring) 🆕
5356
- [Middleware](#middleware)
5457
- [API Reference](#api-reference)
5558
- [Best Practices](#best-practices)
@@ -98,11 +101,22 @@ public record CounterState(int Count, string? LastAction = null)
98101
In your `Program.cs`:
99102

100103
```csharp
104+
// Simple - with default setup (DevTools + Logging)
101105
builder.Services.AddStore(
102-
new CounterState(0), // Initial state
103-
store => store.WithDevTools("Counter")); // Optional: Enable DevTools
106+
new CounterState(0),
107+
(store, sp) => store.WithDefaults(sp, "Counter"));
108+
109+
// Or manual configuration
110+
builder.Services.AddStore(
111+
new CounterState(0),
112+
(store, sp) => store
113+
.WithJSRuntime(sp.GetRequiredService<IJSRuntime>())
114+
.WithDevTools("Counter")
115+
.WithLogging());
104116
```
105117

118+
> 💡 **New in this version**: Use `WithDefaults()` for one-liner setup with JSRuntime, DevTools, and Logging!
119+
106120
### Step 3: Use in Components
107121

108122
```razor
@@ -1184,20 +1198,30 @@ async Task LoadUsers()
11841198
Automatically save and restore state:
11851199

11861200
```csharp
1201+
// NEW: Simplified API
11871202
builder.Services.AddStore(
11881203
new AppState(),
1189-
store => store.WithPersistence(
1190-
PersistenceType.LocalStorage, // or SessionStorage
1191-
"app-state", // storage key
1192-
debounceMs: 500 // optional: debounce saves
1193-
));
1204+
(store, sp) => store
1205+
.WithDefaults(sp, "MyApp")
1206+
.WithPersistence(sp, "app-state")); // Auto-creates LocalStorageProvider
1207+
1208+
// Or with manual provider configuration
1209+
builder.Services.AddStore(
1210+
new AppState(),
1211+
(store, sp) => {
1212+
var jsRuntime = sp.GetRequiredService<IJSRuntime>();
1213+
var localStorage = new LocalStorageProvider(jsRuntime);
1214+
return store.WithPersistence(localStorage, "app-state");
1215+
});
11941216
```
11951217

11961218
State is automatically:
11971219
- Saved to storage on every update (debounced)
11981220
- Restored when the app loads
11991221
- Serialized/deserialized with System.Text.Json
12001222

1223+
> 💡 **New**: The simplified `WithPersistence(sp, key)` method automatically creates a LocalStorageProvider for you!
1224+
12011225
### DevTools
12021226

12031227
Time-travel debugging with Redux DevTools:
@@ -1217,6 +1241,101 @@ builder.Services.AddStore(
12171241

12181242
**Install:** [Redux DevTools Extension](https://github.com/reduxjs/redux-devtools)
12191243

1244+
---
1245+
1246+
### Diagnostics & Monitoring
1247+
1248+
> 🆕 **New Feature**: Real-time diagnostics panel for monitoring state changes, component renders, and performance.
1249+
1250+
**Built-in diagnostics system** (available only in DEBUG builds) provides deep visibility into your application's state management:
1251+
1252+
#### Setup
1253+
1254+
```csharp
1255+
// In Program.cs
1256+
builder.Services.AddStoreDiagnostics(); // Register diagnostics service
1257+
1258+
builder.Services.AddStore(
1259+
new CounterState(0),
1260+
(store, sp) => store
1261+
.WithDefaults(sp, "Counter")
1262+
.WithDiagnosticsIfAvailable(sp)); // Safely adds diagnostics if available
1263+
```
1264+
1265+
#### Features
1266+
1267+
The diagnostics system tracks:
1268+
1269+
- **📜 Action History** - Every state update with:
1270+
- Action names and timestamps
1271+
- Before/after state snapshots
1272+
- State diffs showing what changed
1273+
- Update duration timings
1274+
1275+
- **📊 Performance Metrics** - Real-time performance data:
1276+
- Average, min, and max update times
1277+
- Total update count
1278+
- P95/P99 percentiles
1279+
1280+
- **🎨 Render Tracking** - Component render monitoring:
1281+
- Which components rendered and when
1282+
- Render frequency per component
1283+
- Render counts for optimization insights
1284+
1285+
- **🔌 Subscription Monitoring** - Track all subscriptions:
1286+
- Active subscriptions count
1287+
- Component lifecycle tracking
1288+
- Subscription creation/disposal events
1289+
1290+
#### Usage
1291+
1292+
Add the `DiagnosticPanel` component to any page:
1293+
1294+
```razor
1295+
@page "/diagnostics"
1296+
1297+
<h1>Store Diagnostics</h1>
1298+
1299+
#if DEBUG
1300+
<DiagnosticPanel DisplayMode="DiagnosticDisplayMode.Inline" InitiallyCollapsed="false" />
1301+
#else
1302+
<p>Diagnostics only available in DEBUG builds</p>
1303+
#endif
1304+
```
1305+
1306+
**Display modes:**
1307+
- `Inline` - Full-width panel embedded in page
1308+
- `Floating` - Draggable overlay (default)
1309+
1310+
**Example diagnostic output:**
1311+
1312+
```
1313+
📊 Performance Metrics
1314+
Store: Counter
1315+
├─ Total Updates: 47
1316+
├─ Avg Duration: 0.8ms
1317+
├─ Min Duration: 0.3ms
1318+
├─ Max Duration: 2.1ms
1319+
└─ P95 Duration: 1.5ms
1320+
1321+
📜 Recent Actions
1322+
1. INCREMENT (0.5ms ago)
1323+
Counter: 5 → 6
1324+
2. INCREMENT (1.2s ago)
1325+
Counter: 4 → 5
1326+
3. RESET (5.3s ago)
1327+
Counter: 15 → 0
1328+
1329+
🎨 Component Renders
1330+
├─ Counter.razor: 8 renders
1331+
├─ CounterDisplay.razor: 8 renders
1332+
└─ CounterStats.razor: 3 renders
1333+
```
1334+
1335+
> ⚠️ **Note**: Diagnostics are compiled out in Release builds to ensure zero performance overhead in production.
1336+
1337+
---
1338+
12201339
### Middleware
12211340

12221341
Intercept state updates for cross-cutting concerns:
@@ -1251,6 +1370,7 @@ builder.Services.AddStore(
12511370
- `DevToolsMiddleware` - Redux DevTools integration
12521371
- `PersistenceMiddleware` - LocalStorage/SessionStorage sync
12531372
- `LoggingMiddleware` - State change logging
1373+
- `DiagnosticsMiddleware` - Performance monitoring and diagnostics (DEBUG only)
12541374

12551375
---
12561376

@@ -1298,19 +1418,37 @@ public interface IStore<TState>
12981418
### Registration
12991419

13001420
```csharp
1301-
// Simple
1421+
// Simple - no configuration
13021422
services.AddStore(new MyState());
13031423

1304-
// With configuration
1424+
// With defaults (recommended)
13051425
services.AddStore(
13061426
new MyState(),
1307-
store => store
1427+
(store, sp) => store.WithDefaults(sp, "MyStore"));
1428+
1429+
// Full configuration
1430+
services.AddStore(
1431+
new MyState(),
1432+
(store, sp) => store
1433+
.WithDefaults(sp, "StoreName")
1434+
.WithPersistence(sp, "storage-key")
1435+
.WithDiagnosticsIfAvailable(sp)
1436+
.WithMiddleware<MyMiddleware>()
1437+
);
1438+
1439+
// Manual configuration (for advanced scenarios)
1440+
services.AddStore(
1441+
new MyState(),
1442+
(store, sp) => store
1443+
.WithJSRuntime(sp.GetRequiredService<IJSRuntime>())
13081444
.WithDevTools("StoreName")
1309-
.WithPersistence(PersistenceType.LocalStorage, "key")
1445+
.WithLogging()
13101446
.WithMiddleware<MyMiddleware>()
13111447
);
13121448
```
13131449

1450+
> 💡 **Tip**: The new `(store, sp) =>` pattern gives you access to the service provider for dependency injection within store configuration.
1451+
13141452
---
13151453

13161454
## Best Practices
@@ -1629,6 +1767,30 @@ Explore comprehensive documentation:
16291767

16301768
---
16311769

1770+
## What's New
1771+
1772+
### Recent Updates
1773+
1774+
**🆕 Diagnostics & Monitoring System**
1775+
- Built-in diagnostics panel for monitoring state changes, renders, and performance
1776+
- Real-time action history with state diffs
1777+
- Performance metrics tracking (update timings, P95/P99)
1778+
- Component render tracking and subscription monitoring
1779+
- Zero performance overhead (DEBUG-only compilation)
1780+
1781+
**🎁 Simplified API**
1782+
- `WithDefaults(sp, name)` - One-liner for common setup
1783+
- `WithPersistence(sp, key)` - Simplified persistence without manual provider creation
1784+
- `WithDiagnosticsIfAvailable(sp)` - Safe diagnostics addition
1785+
- New `(store, sp) =>` registration pattern for dependency injection
1786+
1787+
**📦 Enhanced Store Registration**
1788+
- Service provider access in configuration
1789+
- Cleaner, more composable builder pattern
1790+
- Better support for dependency injection
1791+
1792+
---
1793+
16321794
## Contributing
16331795

16341796
We welcome contributions! This library is designed to stay simple and focused.

0 commit comments

Comments
 (0)