From b15219335df7ada68619199585fb3dfba04a89a3 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 28 Jul 2026 17:49:17 +0700 Subject: [PATCH] feat: create infrastructure cache layer Co-Authored-By: Claude Opus 5 (1M context) --- src/infrastructure/cache/index.ts | 129 ++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 src/infrastructure/cache/index.ts diff --git a/src/infrastructure/cache/index.ts b/src/infrastructure/cache/index.ts new file mode 100644 index 0000000..d3226a0 --- /dev/null +++ b/src/infrastructure/cache/index.ts @@ -0,0 +1,129 @@ +/** + * Represents an entry in the cache with a value and expiration timestamp. + * @template T - The type of the cached value. + */ +interface CacheEntry { + value: T; + expiresAt: number; +} + +/** + * A generic in-memory cache with configurable TTL (time-to-live) support. + * + * Provides simple get/set/delete operations with automatic expiration. + * Expired entries are lazily evicted on read and can be proactively cleaned up + * via the {@link cleanup} method. + * + * @template T - The type of values stored in the cache. + */ +class Cache { + private store = new Map>(); + private ttlMs: number; + + /** + * Creates a new cache instance. + * @param ttlSeconds - Default TTL in seconds for cached entries (default 3600). + */ + constructor(ttlSeconds: number = 3600) { + this.ttlMs = ttlSeconds * 1000; + } + + /** + * Stores a value in the cache under the given key. + * @param key - The cache key. + * @param value - The value to cache. + */ + set(key: string, value: T): void { + this.store.set(key, { + value, + expiresAt: Date.now() + this.ttlMs, + }); + } + + /** + * Retrieves a value from the cache by key. + * Returns `null` if the key does not exist or the entry has expired. + * Expired entries are automatically deleted on access. + * @param key - The cache key. + * @returns The cached value, or `null` if not found or expired. + */ + get(key: string): T | null { + const entry = this.store.get(key); + if (!entry) return null; + + if (Date.now() > entry.expiresAt) { + this.store.delete(key); + return null; + } + + return entry.value; + } + + /** + * Checks whether a key exists in the cache and has not expired. + * @param key - The cache key. + * @returns `true` if the key exists and is still valid, `false` otherwise. + */ + has(key: string): boolean { + return this.get(key) !== null; + } + + /** + * Deletes a key from the cache. + * @param key - The cache key to remove. + */ + delete(key: string): void { + this.store.delete(key); + } + + /** + * Clears all entries from the cache. + */ + clear(): void { + this.store.clear(); + } + + /** + * Returns the total number of entries currently in the cache (including expired ones). + * @returns The number of entries in the internal store. + */ + size(): number { + return this.store.size; + } + + /** + * Removes all expired entries from the cache. + * @returns The number of entries removed. + */ + cleanup(): number { + let removed = 0; + const now = Date.now(); + + for (const [key, entry] of this.store.entries()) { + if (now > entry.expiresAt) { + this.store.delete(key); + removed++; + } + } + + return removed; + } +} + +/** + * Metadata shape stored in the file info cache. + */ +interface FileInfoCacheValue { + file_size: number; + mime_type: string; + file_path: string; + bot_token: string; +} + +/** + * Singleton cache for file information with a 1-hour TTL. + * Used to store metadata about files previously uploaded to Telegram. + */ +export const fileInfoCache = new Cache(3600); + +export { Cache };