mirror of
https://github.com/Shineii86/AniKotoAPI.git
synced 2026-10-22 10:55:10 +00:00
- 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
121 lines
2.8 KiB
JavaScript
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 };
|