← back to Wine Finder Next
lib/performance.ts
255 lines
// Performance monitoring utilities
export class PerformanceMonitor {
private static instance: PerformanceMonitor;
private metrics: Map<string, number[]> = new Map();
static getInstance(): PerformanceMonitor {
if (!PerformanceMonitor.instance) {
PerformanceMonitor.instance = new PerformanceMonitor();
}
return PerformanceMonitor.instance;
}
// Measure function execution time
async measureAsync<T>(
name: string,
fn: () => Promise<T>
): Promise<T> {
const start = performance.now();
try {
const result = await fn();
const duration = performance.now() - start;
this.recordMetric(name, duration);
return result;
} catch (error) {
const duration = performance.now() - start;
this.recordMetric(name, duration);
throw error;
}
}
// Record a metric
recordMetric(name: string, value: number): void {
if (!this.metrics.has(name)) {
this.metrics.set(name, []);
}
const values = this.metrics.get(name)!;
values.push(value);
// Keep only last 100 values
if (values.length > 100) {
values.shift();
}
}
// Get statistics for a metric
getStats(name: string) {
const values = this.metrics.get(name) || [];
if (values.length === 0) {
return null;
}
const sorted = [...values].sort((a, b) => a - b);
const sum = values.reduce((a, b) => a + b, 0);
return {
count: values.length,
mean: sum / values.length,
median: sorted[Math.floor(sorted.length / 2)],
p95: sorted[Math.floor(sorted.length * 0.95)],
p99: sorted[Math.floor(sorted.length * 0.99)],
min: sorted[0],
max: sorted[sorted.length - 1],
};
}
// Get all metrics
getAllStats() {
const stats: Record<string, any> = {};
for (const [name] of this.metrics) {
stats[name] = this.getStats(name);
}
return stats;
}
// Clear metrics
clear(): void {
this.metrics.clear();
}
}
// Export singleton
export const performanceMonitor = PerformanceMonitor.getInstance();
// Database query optimizer
export class QueryOptimizer {
// Add indexes to optimize queries
static getOptimizedIndexes() {
return {
bottles: [
{ name: 1 },
{ vintage: 1 },
{ status: 1 },
{ availableMembershipUnits: 1 },
{ createdAt: -1 },
],
membershipUnits: [
{ bottleId: 1, userId: 1 },
{ userId: 1 },
{ status: 1 },
],
votes: [
{ bottleId: 1 },
{ status: 1, endDate: -1 },
{ userId: 1 },
],
marketplace: [
{ status: 1, listedAt: -1 },
{ bottleId: 1 },
{ sellerId: 1 },
{ price: 1 },
],
};
}
// Optimize query with projection
static optimizeProjection(fields: string[]): Record<string, 0 | 1> {
const projection: Record<string, 0 | 1> = {};
fields.forEach(field => {
projection[field] = 1;
});
return projection;
}
// Add pagination
static paginate(query: any, page: number, limit: number) {
const skip = (page - 1) * limit;
return query.skip(skip).limit(limit);
}
}
// Image optimization utilities
export class ImageOptimizer {
static getOptimizedSrc(src: string, width: number, quality = 75): string {
// Next.js Image optimization API
const params = new URLSearchParams({
url: src,
w: width.toString(),
q: quality.toString(),
});
return `/_next/image?${params.toString()}`;
}
static getSrcSet(src: string, widths: number[], quality = 75): string {
return widths
.map(w => `${this.getOptimizedSrc(src, w, quality)} ${w}w`)
.join(', ');
}
static getResponsiveSizes(): string {
return '(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw';
}
}
// Lazy loading utilities
export class LazyLoader {
private static observer: IntersectionObserver | null = null;
static initObserver(callback: IntersectionObserverCallback): void {
if (typeof window === 'undefined') return;
this.observer = new IntersectionObserver(callback, {
rootMargin: '50px',
threshold: 0.01,
});
}
static observe(element: Element): void {
if (this.observer) {
this.observer.observe(element);
}
}
static unobserve(element: Element): void {
if (this.observer) {
this.observer.unobserve(element);
}
}
static disconnect(): void {
if (this.observer) {
this.observer.disconnect();
this.observer = null;
}
}
}
// Request debouncing
export function debounce<T extends (...args: any[]) => any>(
func: T,
wait: number
): (...args: Parameters<T>) => void {
let timeout: NodeJS.Timeout;
return function executedFunction(...args: Parameters<T>) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Request throttling
export function throttle<T extends (...args: any[]) => any>(
func: T,
limit: number
): (...args: Parameters<T>) => void {
let inThrottle: boolean;
return function executedFunction(...args: Parameters<T>) {
if (!inThrottle) {
func.apply(null, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// Memory cache for client-side
export class MemoryCache<T> {
private cache: Map<string, { value: T; expiry: number }> = new Map();
set(key: string, value: T, ttlMs: number): void {
const expiry = Date.now() + ttlMs;
this.cache.set(key, { value, expiry });
}
get(key: string): T | null {
const item = this.cache.get(key);
if (!item) return null;
if (Date.now() > item.expiry) {
this.cache.delete(key);
return null;
}
return item.value;
}
clear(): void {
this.cache.clear();
}
// Clean expired entries
cleanup(): void {
const now = Date.now();
for (const [key, item] of this.cache) {
if (now > item.expiry) {
this.cache.delete(key);
}
}
}
}