AIOptimizer
Overview
AIOptimizer collects runtime performance data — action execution latencies, selector call counts, and state update frequency — and produces prioritized OptimizationSuggestion objects. AutoOptimizer wraps AIOptimizer and runs analysis on a configurable interval, notifying subscribers when new suggestions appear.
Both classes work entirely locally; no external AI service is required. Suggestions are rule-based: high latency triggers batching recommendations, high dispatch frequency triggers debounce recommendations, and slow actions trigger normalization suggestions.
Key Concepts
- Action latency recording —
recordAction(type, latencyMs)accumulates latency samples per action type. Suggestions appear once a minimum sample size is reached. - Selector tracking —
recordSelector(name)counts how many times each selector is called. Heavily called selectors without memoization get aSELECTOR_MEMOIZATIONsuggestion. - Optimization types — each suggestion has a typed
OptimizationTypeenum value indicating the category of improvement. - Priority — suggestions are sorted by
priority(1–10, descending) so you can act on the most impactful ones first.
API Reference
OptimizationType
enum OptimizationType {
SELECTOR_MEMOIZATION = 'SELECTOR_MEMOIZATION',
ACTION_BATCHING = 'ACTION_BATCHING',
STATE_NORMALIZATION = 'STATE_NORMALIZATION',
LAZY_LOADING = 'LAZY_LOADING',
DERIVED_STATE = 'DERIVED_STATE',
IMMUTABLE_UPDATE = 'IMMUTABLE_UPDATE',
DEBOUNCE_THROTTLE = 'DEBOUNCE_THROTTLE',
VIRTUALIZATION = 'VIRTUALIZATION'
}
OptimizationSuggestion
interface OptimizationSuggestion {
id: string;
type: OptimizationType;
title: string;
description: string;
impact: 'low' | 'medium' | 'high';
effort: 'low' | 'medium' | 'high';
priority: number; // 1–10
codeExample?: string; // TypeScript snippet illustrating the fix
estimatedGain?: string; // human-readable improvement estimate
}
OptimizerConfig
interface OptimizerConfig {
enableAutoOptimization?: boolean; // default true
latencyThreshold?: number; // ms, default 16 (60fps budget)
frequencyThreshold?: number; // actions/second, default 10
minSampleSize?: number; // minimum recordings before suggesting, default 10
}
PerformanceProfile
interface PerformanceProfile {
actionLatencies: Map<string, number[]>;
selectorExecutions: Map<string, number>;
stateUpdateFrequency: number;
averageUpdateTime: number;
slowActions: string[];
frequentActions: string[];
}
AIOptimizer
class AIOptimizer {
constructor(config?: OptimizerConfig)
optimize(profile: Partial<PerformanceProfile>): OptimizationSuggestion[]
recordAction(actionType: string, latency: number): void
recordSelector(selectorName: string): void
getSuggestions(): OptimizationSuggestion[]
getSuggestion(id: string): OptimizationSuggestion | undefined
clearSuggestions(): void
getPerformanceSummary(): {
slowestAction: { type: string; avgLatency: number } | null;
mostFrequentAction: { type: string; count: number } | null;
mostCalledSelector: { name: string; executions: number } | null;
totalActions: number;
totalSelectors: number;
}
}
function createOptimizer(config?: OptimizerConfig): AIOptimizer
AutoOptimizer
class AutoOptimizer {
constructor(config?: OptimizerConfig & { analysisIntervalMs?: number })
start(): void
stop(): void
subscribe(listener: (suggestions: OptimizationSuggestion[]) => void): () => void
getOptimizer(): AIOptimizer
}
Basic Usage
import { AIOptimizer, OptimizationType } from '@tagna/udiot';
const optimizer = new AIOptimizer({ latencyThreshold: 16, minSampleSize: 5 });
// Simulate recording action performance (normally done by @AIPowered)
for (let i = 0; i < 10; i++) {
optimizer.recordAction('FETCH_PRODUCTS', 45); // consistently slow (> 16ms)
}
for (let i = 0; i < 15; i++) {
optimizer.recordAction('SET_FILTER', 2); // fast but very frequent
}
for (let i = 0; i < 60; i++) {
optimizer.recordSelector('filteredProducts'); // called many times
}
// Run analysis
const suggestions = optimizer.optimize({
stateUpdateFrequency: 12, // 12 updates/second — above threshold
});
suggestions.forEach((s) => {
console.log(`[${s.priority}] ${s.title}`);
console.log(` Impact: ${s.impact}, Effort: ${s.effort}`);
console.log(` ${s.description}`);
if (s.codeExample) console.log(' Example:\n', s.codeExample);
});
Common Patterns
Auto-optimizer with subscriber
import { AutoOptimizer, OptimizationSuggestion } from '@tagna/udiot';
const auto = new AutoOptimizer({
latencyThreshold: 16,
analysisIntervalMs: 10_000, // check every 10 seconds
});
const unsubscribe = auto.subscribe((suggestions: OptimizationSuggestion[]) => {
const highPriority = suggestions.filter(s => s.priority >= 8);
if (highPriority.length > 0) {
console.warn('[AutoOptimizer] High-priority suggestions:', highPriority.map(s => s.title));
}
});
auto.start();
// Later, when your application shuts down or the component unmounts:
auto.stop();
unsubscribe();
Accessing the optimizer attached by @AIPowered
import {
ReactiveStore, AIPowered, Store, Mutation,
getOptimizer
} from '@tagna/udiot';
@ReactiveStore({ name: 'ProductStore' })
@AIPowered({ autoOptimize: true })
class ProductStore extends Store<ProductState> {
constructor(state: ProductState) { super(state); }
@Mutation()
setProducts(products: Product[]): void {
this.dispatch({ type: 'SET_PRODUCTS', payload: products });
}
}
const store = new ProductStore({ products: [], filter: '' });
// Dispatch a few actions...
store.setProducts([/* ... */]);
// Then inspect suggestions
const optimizer = getOptimizer(store);
const summary = optimizer?.getPerformanceSummary();
console.log('Slowest action:', summary?.slowestAction);
const suggestions = optimizer?.getSuggestions() ?? [];
suggestions.forEach(s => console.log(`${s.type}: ${s.title}`));
Applying a suggestion: memoizing a selector
Given the suggestion SELECTOR_MEMOIZATION for filteredProducts:
import { Store, Selector } from '@tagna/udiot';
class ProductStore extends Store<ProductState> {
// Before: called directly without memoization
// getFilteredProducts() { return this.getState().products.filter(...); }
// After: @Selector wraps with memoization
@Selector()
getFilteredProducts(): Product[] {
return this.getState().products.filter(p => p.active);
}
}
Integration Notes
@AIPoweredwithautoOptimize: truecallsoptimizer.recordAction(type, duration)on every dispatch, and callsoptimizer.optimize(...)with the current profile. No manual wiring required.optimize()clears and rebuilds the suggestion map each time it is called, so stale suggestions from previous runs do not accumulate.AutoOptimizercreates its ownAIOptimizerinternally. If you need to feed custom performance data (e.g., from a profiling tool), useAIOptimizerdirectly and callrecordAction/recordSelectoryourself.AutoOptimizerusessetIntervalinternally. In Node.js test environments, it calls.unref()on the timer so it does not prevent the process from exiting.
Pitfalls
- Suggestions appear only after
minSampleSizerecordings for a given action type (default 10). In development with few dispatches, the optimizer may produce no output — this is expected. optimize()returns suggestions sorted by priority but does not apply them. All suggestions are advisory; you decide which ones to implement.- The
latencyThresholddefault of 16ms (one 60fps frame) is aggressive for most back-end operations. For Node.js services, raise it to 50–100ms to avoid noise. - Calling
clearSuggestions()does not clear the performance profile (recorded latencies and selector counts). To reset everything, create a newAIOptimizerinstance.
From the canonical UDIOT public documentation.Try it in the playground ↗