StateAnalyzer
Overview
StateAnalyzer performs static and dynamic analysis of store state. It measures structural complexity, detects anti-patterns (deep nesting, circular references, oversized state), identifies action sequence patterns, and produces a 0–100 health score. Results are cached by state content to avoid redundant computation on unchanged state.
Key Concepts
- Score — 0–100 quality rating. 100 is ideal; each detected issue deducts points based on severity. Scores below 70 trigger insight logging when
logInsights: trueis set in@AIPowered. - Issues — specific problems found in the state structure or action history (e.g.,
DEEP_NESTING,CIRCULAR_REFERENCE,LARGE_STATE,HIGH_MEMORY). - Suggestions — actionable recommendations paired to issues, including estimated effort and priority.
- Metrics — raw measurements: property count, nesting depth, array count, object count, circular reference count, estimated memory in KB.
- Patterns — recurring action sequences detected in the dispatch history (requires action history to be passed).
API Reference
StateAnalyzerConfig
interface StateAnalyzerConfig {
enablePatternDetection?: boolean; // default true
enablePerformanceAnalysis?: boolean; // default true
enableStructureAnalysis?: boolean; // default true
maxPatternHistory?: number; // default 100
complexityThreshold?: number; // default 10
}
AnalysisResult
interface AnalysisResult {
score: number; // 0–100
issues: AnalysisIssue[];
suggestions: AnalysisSuggestion[];
metrics: AnalysisMetrics;
patterns: DetectedPattern[];
}
AnalysisIssue
interface AnalysisIssue {
severity: 'low' | 'medium' | 'high' | 'critical';
type: string; // e.g. 'DEEP_NESTING', 'CIRCULAR_REFERENCE', 'LARGE_STATE', 'HIGH_MEMORY'
message: string;
location?: string;
impact: string;
}
AnalysisSuggestion
interface AnalysisSuggestion {
type: string; // e.g. 'FLATTEN_STATE', 'SPLIT_STORE', 'OPTIMIZE_DATA'
message: string;
benefit: string;
effort: 'low' | 'medium' | 'high';
priority: number; // 1–10, higher is more urgent
}
AnalysisMetrics
interface AnalysisMetrics {
stateComplexity: number;
nestingDepth: number;
propertyCount: number;
arrayCount: number;
objectCount: number;
circularReferences: number;
estimatedMemoryKB: number;
}
DetectedPattern
interface DetectedPattern {
name: string; // action type sequence, e.g. "ADD_ITEM → VALIDATE → SAVE"
description: string;
frequency: number;
lastOccurrence: number; // Unix timestamp ms
examples: any[];
}
StateAnalyzer
class StateAnalyzer {
constructor(config?: StateAnalyzerConfig)
analyzeState(state: any, actionHistory?: any[]): AnalysisResult
analyzeActionSequence(actions: any[]): {
commonSequences: string[][];
inefficientPatterns: string[];
redundantActions: string[];
}
getOptimizationRecommendations(analysisResult: AnalysisResult): string[]
clearCache(): void
clearPatterns(): void
}
function createStateAnalyzer(config?: StateAnalyzerConfig): StateAnalyzer
Basic Usage
import { StateAnalyzer, createStateAnalyzer } from '@tagna/udiot';
interface AppState {
users: User[];
products: Product[];
ui: { loading: boolean; activeTab: string };
}
const state: AppState = {
users: [{ id: '1', name: 'Alice' }],
products: [],
ui: { loading: false, activeTab: 'home' },
};
const analyzer = createStateAnalyzer({
enablePatternDetection: true,
enablePerformanceAnalysis: true,
enableStructureAnalysis: true,
});
const result = analyzer.analyzeState(state);
console.log('Score:', result.score);
// Score: 95
console.log('Issues:', result.issues);
// [] (clean state)
console.log('Metrics:', result.metrics);
// { nestingDepth: 2, propertyCount: 6, estimatedMemoryKB: 0.08, ... }
const recommendations = analyzer.getOptimizationRecommendations(result);
console.log('Recommendations:', recommendations);
Common Patterns
Running analysis on every dispatch
import { Store, ReactiveStore, Mutation } from '@tagna/udiot';
import { StateAnalyzer } from '@tagna/udiot';
const analyzer = new StateAnalyzer({ complexityThreshold: 15 });
@ReactiveStore({ name: 'MonitoredStore' })
class MonitoredStore extends Store<AppState> {
constructor(initialState: AppState) {
super(initialState);
this.subscribe((state) => {
const result = analyzer.analyzeState(state);
if (result.score < 80) {
console.warn('[StateAnalyzer] Score dropped to', result.score);
result.issues.forEach(i => console.warn(` - ${i.severity}: ${i.message}`));
}
});
}
}
Analyzing action sequences for inefficiencies
const actionHistory = [
{ type: 'SET_FILTER', timestamp: 1 },
{ type: 'SET_FILTER', timestamp: 50 }, // < 100ms apart — redundant
{ type: 'FETCH_ITEMS', timestamp: 100 },
{ type: 'SET_FILTER', timestamp: 150 },
{ type: 'FETCH_ITEMS', timestamp: 200 },
];
const { redundantActions, inefficientPatterns } = analyzer.analyzeActionSequence(actionHistory);
console.log('Redundant:', redundantActions); // ['SET_FILTER']
console.log('Inefficient:', inefficientPatterns); // actions dispatched > 10 times
Integration with @AIPowered
When @AIPowered is applied to a store, it creates a StateAnalyzer internally. Retrieve it with the helper:
import { AIPowered, getAnalyzer, analyzeStore, Store, ReactiveStore } from '@tagna/udiot';
@ReactiveStore({ name: 'SmartStore' })
@AIPowered({ autoAnalyze: true })
class SmartStore extends Store<AppState> {
constructor(initialState: AppState) { super(initialState); }
}
const store = new SmartStore({ /* ... */ });
// After some dispatches...
const result = analyzeStore(store); // convenience function
console.log('Score:', result.score);
console.log('Recommendations:', result.recommendations);
// Or get the analyzer directly for fine-grained access
const analyzer = getAnalyzer(store);
const detailed = analyzer?.analyzeState(store.getState());
Integration Notes
analyzeStatecaches results by the JSON representation of the state. Large states that cannot be JSON-serialized (circular references, functions) will always produce a fresh analysis with a random cache key.- Pattern detection requires
actionHistoryto be passed explicitly toanalyzeState, or it will return an emptypatternsarray.@AIPoweredpasses the internal action log automatically. - The
clearCache()andclearPatterns()methods are useful in tests to ensure each assertion starts from a clean slate.
Pitfalls
analyzeStateusesJSON.stringifyto estimate memory usage. States with non-serializable values (Date objects, functions, class instances with circular refs) will produce inaccurateestimatedMemoryKBand may returnNaNfor the score.- The complexity score is heuristic-based, not a formal complexity metric. A score of 60 does not necessarily mean the application is broken; treat it as a guide rather than a hard threshold.
- Caching on the full JSON string means a state with one changed property invalidates the entire cache entry. For very large states analyzed frequently, consider increasing
complexityThresholdand disabling features you do not need to reduce CPU overhead.
From the canonical UDIOT public documentation.Try it in the playground ↗