@nanggo/social-preview
Version:
Generate beautiful social media preview images from any URL
149 lines (148 loc) • 4.28 kB
JavaScript
"use strict";
/**
* LRU Cache implementation with TTL support
* Used for caching metadata extraction results
*
* @example
* ```typescript
* import { metadataCache, stopCacheCleanup, startCacheCleanup } from './cache';
*
* // For graceful server shutdown
* process.on('SIGTERM', () => {
* stopCacheCleanup();
* // ... other cleanup
* });
*
* // For testing environments
* afterAll(() => {
* stopCacheCleanup();
* });
*
* // Custom cleanup interval (5 minutes)
* stopCacheCleanup();
* startCacheCleanup(5 * 60 * 1000);
* ```
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.previewCache = exports.metadataCache = exports.LRUCache = void 0;
exports.startCacheCleanup = startCacheCleanup;
exports.stopCacheCleanup = stopCacheCleanup;
exports.isCacheCleanupRunning = isCacheCleanupRunning;
class LRUCache {
cache = new Map();
maxSize;
defaultTTL;
constructor(maxSize = 100, defaultTTL = 5 * 60 * 1000) {
this.maxSize = maxSize;
this.defaultTTL = defaultTTL;
}
set(key, value, ttl) {
const now = Date.now();
const entryTTL = ttl ?? this.defaultTTL;
// Remove existing entry if present (for LRU ordering)
if (this.cache.has(key)) {
this.cache.delete(key);
}
// Remove oldest entries if cache is full
if (this.cache.size >= this.maxSize) {
const firstKey = this.cache.keys().next().value;
if (firstKey) {
this.cache.delete(firstKey);
}
}
// Add new entry (will be most recently used)
this.cache.set(key, {
value,
timestamp: now,
ttl: entryTTL
});
}
get(key) {
const entry = this.cache.get(key);
if (!entry) {
return undefined;
}
const now = Date.now();
// Check if entry has expired
if (now - entry.timestamp > entry.ttl) {
this.cache.delete(key);
return undefined;
}
// Move to end (most recently used)
this.cache.delete(key);
this.cache.set(key, entry);
return entry.value;
}
has(key) {
return this.get(key) !== undefined;
}
delete(key) {
return this.cache.delete(key);
}
clear() {
this.cache.clear();
}
size() {
return this.cache.size;
}
// Clean expired entries
cleanup() {
const now = Date.now();
let removedCount = 0;
for (const [key, entry] of this.cache.entries()) {
if (now - entry.timestamp > entry.ttl) {
this.cache.delete(key);
removedCount++;
}
}
return removedCount;
}
// Get cache statistics
getStats() {
return {
size: this.cache.size,
maxSize: this.maxSize,
defaultTTL: this.defaultTTL
};
}
}
exports.LRUCache = LRUCache;
exports.metadataCache = new LRUCache(100, 5 * 60 * 1000); // 100 entries, 5 minutes TTL
exports.previewCache = new LRUCache(50, 5 * 60 * 1000); // 50 entries, 5 minutes TTL
// Cache cleanup management
let cleanupInterval = null;
const CLEANUP_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
/**
* Starts automatic cache cleanup if not already running.
* @param intervalMs - Cleanup interval in milliseconds (default: 10 minutes)
*/
function startCacheCleanup(intervalMs = CLEANUP_INTERVAL_MS) {
if (cleanupInterval) {
return; // Already running
}
cleanupInterval = setInterval(() => {
exports.metadataCache.cleanup();
exports.previewCache.cleanup();
}, intervalMs);
// Don't prevent Node.js process from exiting
cleanupInterval.unref();
}
/**
* Stops the automatic cache cleanup interval.
* Useful for graceful shutdown in applications and testing environments.
*/
function stopCacheCleanup() {
if (cleanupInterval) {
clearInterval(cleanupInterval);
cleanupInterval = null;
}
}
/**
* Checks if automatic cache cleanup is currently running.
* @returns true if cleanup interval is active
*/
function isCacheCleanupRunning() {
return cleanupInterval !== null;
}
// Start automatic cleanup by default
startCacheCleanup();