Files
AniKotoAPI/src/helper/cache.helper.js
T
Shinei Nouzen fe51275db0 feat: upgrade to v2.0.0 with multi-mirror fallback, new endpoints, and LRU cache
- Add multi-mirror fallback system with 5 mirror domains
- Add LRU cache with configurable TTL per endpoint
- Add response compression (gzip)
- Add Seasons endpoint (/api/seasons/:id)
- Add Watch Order endpoint (/api/watch-order/:id)
- Add Latest Updated endpoint (/api/latest-updated)
- Add Download endpoint (/api/download)
- Add cache stats endpoint (/api/cache/stats)
- Add mirror status/reset endpoints (/api/mirrors, /api/mirrors/reset)
- Add configurable rate limiting with X-RateLimit headers
- Add request timeouts and entity too large handling
- Update documentation with all new endpoints
- Update CHANGELOG.md with v2.0.0 entry
- Update package.json version to 2.0.0
- Add 6 AI agent prompts for development
2026-07-04 15:07:07 +05:30

121 lines
2.8 KiB
JavaScript

/*
* ======= • ======= • ======= • ======= • =======• =======
* AniKotoAPI — cache.helper.js
* Repository: https://github.com/Shineii86/AniKotoAPI
*
* @description
* In-memory LRU cache with configurable TTL per endpoint.
* Evicts least recently used entries when max size reached.
* Supports cache statistics and manual invalidation.
*
* @exports
* getCache, setCache, clearCache, getCacheStats, LRUCache
*
* @author Shinei Nouzen
* @license MIT
* ======= • ======= • ======= • ======= • =======• =======
*/
class LRUCache {
constructor(maxSize = 100, defaultTTL = 300000) {
this.cache = new Map();
this.maxSize = maxSize;
this.defaultTTL = defaultTTL;
this.stats = { hits: 0, misses: 0, sets: 0, deletes: 0 };
}
get(key) {
if (!this.cache.has(key)) {
this.stats.misses++;
return undefined;
}
const entry = this.cache.get(key);
if (Date.now() > entry.expiresAt) {
this.cache.delete(key);
this.stats.misses++;
return undefined;
}
this.stats.hits++;
this.cache.delete(key);
this.cache.set(key, entry);
return entry.value;
}
set(key, value, ttl) {
if (this.cache.has(key)) {
this.cache.delete(key);
}
if (this.cache.size >= this.maxSize) {
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(key, {
value,
expiresAt: Date.now() + (ttl || this.defaultTTL)
});
this.stats.sets++;
}
delete(key) {
const existed = this.cache.delete(key);
if (existed) this.stats.deletes++;
return existed;
}
clear() {
const size = this.cache.size;
this.cache.clear();
return size;
}
getStats() {
const total = this.stats.hits + this.stats.misses;
return {
...this.stats,
size: this.cache.size,
hitRate: total > 0 ? ((this.stats.hits / total) * 100).toFixed(2) + '%' : '0%'
};
}
has(key) {
if (!this.cache.has(key)) return false;
const entry = this.cache.get(key);
if (Date.now() > entry.expiresAt) {
this.cache.delete(key);
return false;
}
return true;
}
}
const TTL = {
home: 600000,
search: 300000,
info: 600000,
episodes: 300000,
servers: 600000,
stream: 180000,
spotlight: 600000,
trending: 600000,
schedule: 1800000,
genres: 3600000,
suggestions: 300000,
default: 300000
};
const cache = new LRUCache(
parseInt(process.env.CACHE_MAX_SIZE) || 200,
parseInt(process.env.CACHE_DEFAULT_TTL) || 300000
);
const getCache = (key) => cache.get(key);
const setCache = (key, data, ttl) => cache.set(key, data, ttl);
const clearCache = () => cache.clear();
const getCacheStats = () => cache.getStats();
export { getCache, setCache, clearCache, getCacheStats, LRUCache, TTL };