UNPKG

polyv-live-cli

Version:

CLI tool for managing PolyV live streaming services.

368 lines 12.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ApiOptimizer = void 0; const events_1 = require("events"); const crypto_1 = require("crypto"); class ApiOptimizer extends events_1.EventEmitter { constructor(config = {}) { super(); this.cache = new Map(); this.pendingRequests = new Map(); this.recentRequests = new Map(); this.coalescingTimers = new Map(); this.config = { enableCaching: true, cacheTtl: 30000, maxCacheSize: 1000, enableDeduplication: true, deduplicationWindow: 1000, enableCoalescing: true, coalescingWindow: 100, enableCompression: true, compressionThreshold: 1024, ...config, }; this.stats = { totalRequests: 0, cachedRequests: 0, deduplicatedRequests: 0, coalescedRequests: 0, cacheHitRatio: 0, averageResponseTime: 0, bytesSaved: 0, currentCacheSize: 0, }; this.setupCacheCleanup(); } async optimizeRequest(method, url, params = {}, body, options = {}) { const startTime = Date.now(); this.stats.totalRequests++; const signature = this.createRequestSignature(method, url, params, body); const requestHash = this.hashSignature(signature); try { if (this.config.enableCaching && !options.bypassCache) { const cachedResponse = this.getCachedResponse(requestHash); if (cachedResponse) { this.stats.cachedRequests++; this.updateStats(startTime); this.emit('cacheHit', { signature, timestamp: Date.now(), size: cachedResponse.size, }); return cachedResponse.data; } } if (this.config.enableDeduplication) { const duplicateResponse = this.checkForDuplicate(signature, requestHash); if (duplicateResponse) { this.stats.deduplicatedRequests++; this.updateStats(startTime); this.emit('requestDeduplicated', { signature, timestamp: Date.now(), }); return duplicateResponse; } } if (this.config.enableCoalescing) { const coalescedResponse = await this.coalesceRequest(signature, requestHash, options.priority || 'medium'); if (coalescedResponse !== null) { this.stats.coalescedRequests++; this.updateStats(startTime); this.emit('requestCoalesced', { signature, timestamp: Date.now(), }); return coalescedResponse; } } const response = await this.executeRequest(method, url, params, body); if (this.config.enableCaching) { this.cacheResponse(requestHash, response, options.ttl); } this.recentRequests.set(requestHash, Date.now()); this.updateStats(startTime); this.emit('requestCompleted', { signature, responseTime: Date.now() - startTime, timestamp: Date.now(), }); return response; } catch (error) { this.updateStats(startTime); this.emit('requestError', { signature, error, timestamp: Date.now(), }); throw error; } } invalidateCache(pattern) { if (!pattern) { this.cache.clear(); this.stats.currentCacheSize = 0; this.emit('cacheCleared', { timestamp: Date.now() }); return; } const regex = new RegExp(pattern); const keysToDelete = []; for (const [key, entry] of this.cache) { if (regex.test(key) || regex.test(entry.hash)) { keysToDelete.push(key); } } for (const key of keysToDelete) { this.cache.delete(key); } this.stats.currentCacheSize = this.cache.size; this.emit('cacheInvalidated', { pattern, entriesRemoved: keysToDelete.length, timestamp: Date.now(), }); } getStats() { return { ...this.stats }; } getCacheInfo() { const entries = Array.from(this.cache.entries()).map(([hash, entry]) => ({ hash, timestamp: entry.timestamp, ttl: entry.ttl, hitCount: entry.hitCount, size: entry.size, })); return { size: this.cache.size, entries, }; } updateConfig(newConfig) { this.config = { ...this.config, ...newConfig }; this.emit('configUpdated', { config: this.config, timestamp: Date.now(), }); } reset() { this.cache.clear(); this.pendingRequests.clear(); this.recentRequests.clear(); for (const timer of this.coalescingTimers.values()) { clearTimeout(timer); } this.coalescingTimers.clear(); this.stats = { totalRequests: 0, cachedRequests: 0, deduplicatedRequests: 0, coalescedRequests: 0, cacheHitRatio: 0, averageResponseTime: 0, bytesSaved: 0, currentCacheSize: 0, }; this.emit('optimizerReset', { timestamp: Date.now() }); } createRequestSignature(method, url, params, body) { const paramsHash = this.hashObject(params); const bodyHash = body ? this.hashObject(body) : undefined; return { method: method.toUpperCase(), url, paramsHash, bodyHash: bodyHash || '', }; } hashSignature(signature) { const combined = `${signature.method}:${signature.url}:${signature.paramsHash}:${signature.bodyHash || ''}`; return (0, crypto_1.createHash)('md5').update(combined).digest('hex'); } hashObject(obj) { const serialized = JSON.stringify(obj, Object.keys(obj).sort()); return (0, crypto_1.createHash)('md5').update(serialized).digest('hex'); } getCachedResponse(requestHash) { const entry = this.cache.get(requestHash); if (!entry) { return null; } const now = Date.now(); if (now - entry.timestamp > entry.ttl) { this.cache.delete(requestHash); this.stats.currentCacheSize = this.cache.size; return null; } entry.hitCount++; return entry; } checkForDuplicate(_signature, requestHash) { const lastRequestTime = this.recentRequests.get(requestHash); if (!lastRequestTime) { return null; } const now = Date.now(); if (now - lastRequestTime > this.config.deduplicationWindow) { this.recentRequests.delete(requestHash); return null; } const pendingRequest = this.pendingRequests.get(requestHash); if (pendingRequest) { return new Promise((resolve, reject) => { pendingRequest.resolvers.push({ resolve, reject }); }); } return null; } async coalesceRequest(signature, requestHash, priority) { const existingPending = this.pendingRequests.get(requestHash); if (existingPending) { return new Promise((resolve, reject) => { existingPending.resolvers.push({ resolve, reject }); if (this.getPriorityValue(priority) > this.getPriorityValue(existingPending.priority)) { existingPending.priority = priority; } }); } const pendingRequest = { signature, timestamp: Date.now(), resolvers: [], priority, }; this.pendingRequests.set(requestHash, pendingRequest); const timer = setTimeout(() => { this.executePendingRequest(requestHash); }, this.config.coalescingWindow); this.coalescingTimers.set(requestHash, timer); return new Promise((resolve, reject) => { pendingRequest.resolvers.push({ resolve, reject }); }); } async executePendingRequest(requestHash) { const pendingRequest = this.pendingRequests.get(requestHash); if (!pendingRequest) { return; } const timer = this.coalescingTimers.get(requestHash); if (timer) { clearTimeout(timer); this.coalescingTimers.delete(requestHash); } this.pendingRequests.delete(requestHash); try { const { signature } = pendingRequest; const response = await this.executeRequest(signature.method, signature.url, {}, undefined); for (const { resolve } of pendingRequest.resolvers) { resolve(response); } if (this.config.enableCaching) { this.cacheResponse(requestHash, response); } } catch (error) { for (const { reject } of pendingRequest.resolvers) { reject(error); } } } async executeRequest(method, url, params, body) { await new Promise(resolve => setTimeout(resolve, 50 + Math.random() * 200)); const response = { method, url, params, body, timestamp: Date.now(), data: `Mock response for ${method} ${url}`, }; return response; } cacheResponse(requestHash, response, ttl) { const now = Date.now(); const responseSize = this.estimateSize(response); const entry = { data: response, timestamp: now, ttl: ttl || this.config.cacheTtl, hash: requestHash, hitCount: 0, size: responseSize, }; if (this.cache.size >= this.config.maxCacheSize) { this.evictOldestEntry(); } this.cache.set(requestHash, entry); this.stats.currentCacheSize = this.cache.size; this.emit('responseCached', { hash: requestHash, size: responseSize, timestamp: now, }); } evictOldestEntry() { let oldestKey = ''; let oldestTime = Date.now(); for (const [key, entry] of this.cache) { if (entry.timestamp < oldestTime) { oldestTime = entry.timestamp; oldestKey = key; } } if (oldestKey) { this.cache.delete(oldestKey); this.emit('cacheEvicted', { hash: oldestKey, timestamp: Date.now(), }); } } estimateSize(data) { return JSON.stringify(data).length; } getPriorityValue(priority) { switch (priority) { case 'low': return 1; case 'medium': return 2; case 'high': return 3; default: return 2; } } updateStats(startTime) { const responseTime = Date.now() - startTime; this.stats.averageResponseTime = ((this.stats.averageResponseTime * (this.stats.totalRequests - 1)) + responseTime) / this.stats.totalRequests; this.stats.cacheHitRatio = this.stats.totalRequests > 0 ? this.stats.cachedRequests / this.stats.totalRequests : 0; this.stats.currentCacheSize = this.cache.size; } setupCacheCleanup() { setInterval(() => { this.cleanupExpiredEntries(); }, 60000); } cleanupExpiredEntries() { const now = Date.now(); const keysToDelete = []; for (const [key, entry] of this.cache) { if (now - entry.timestamp > entry.ttl) { keysToDelete.push(key); } } for (const key of keysToDelete) { this.cache.delete(key); } if (keysToDelete.length > 0) { this.stats.currentCacheSize = this.cache.size; this.emit('cacheCleanup', { entriesRemoved: keysToDelete.length, timestamp: now, }); } } } exports.ApiOptimizer = ApiOptimizer; //# sourceMappingURL=api-optimizer.js.map