polyv-live-cli
Version:
CLI tool for managing PolyV live streaming services.
368 lines • 12.9 kB
JavaScript
"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