File size: 2,985 Bytes
e0ce113
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
/**
 * Simple In-Memory Cache with TTL
 * 
 * A lightweight caching utility for reducing expensive computations.
 * Can be replaced with Redis for distributed caching if needed.
 */

interface CacheEntry<T> {
    value: T;
    expiresAt: number;
}

class TTLCache<T> {
    private cache = new Map<string, CacheEntry<T>>();
    private defaultTTL: number;

    /**
     * @param defaultTTLMs Default time-to-live in milliseconds (default: 5 minutes)
     */
    constructor(defaultTTLMs: number = 5 * 60 * 1000) {
        this.defaultTTL = defaultTTLMs;
    }

    /**
     * Get a value from cache
     * @returns The cached value or undefined if not found/expired
     */
    get(key: string): T | undefined {
        const entry = this.cache.get(key);

        if (!entry) {
            return undefined;
        }

        // Check if expired
        if (Date.now() > entry.expiresAt) {
            this.cache.delete(key);
            console.log(`[Cache] MISS (expired): ${key}`);
            return undefined;
        }

        console.log(`[Cache] HIT: ${key}`);
        return entry.value;
    }

    /**
     * Set a value in cache
     * @param ttlMs Optional TTL override for this specific entry
     */
    set(key: string, value: T, ttlMs?: number): void {
        const ttl = ttlMs ?? this.defaultTTL;
        this.cache.set(key, {
            value,
            expiresAt: Date.now() + ttl,
        });
        console.log(`[Cache] SET: ${key} (TTL: ${ttl}ms)`);
    }

    /**
     * Invalidate a specific key
     */
    invalidate(key: string): void {
        this.cache.delete(key);
        console.log(`[Cache] INVALIDATE: ${key}`);
    }

    /**
     * Invalidate all keys matching a pattern (prefix)
     */
    invalidatePrefix(prefix: string): void {
        let count = 0;
        for (const key of this.cache.keys()) {
            if (key.startsWith(prefix)) {
                this.cache.delete(key);
                count++;
            }
        }
        console.log(`[Cache] INVALIDATE PREFIX: ${prefix} (${count} keys)`);
    }

    /**
     * Clear entire cache
     */
    clear(): void {
        this.cache.clear();
        console.log(`[Cache] CLEARED`);
    }

    /**
     * Get cache statistics
     */
    stats(): { size: number; keys: string[] } {
        return {
            size: this.cache.size,
            keys: Array.from(this.cache.keys()),
        };
    }
}

// Pre-configured caches for different use cases

/** Streak cache - 5 minute TTL */
export const streakCache = new TTLCache<{
    current_streak: number;
    longest_streak: number;
    is_active: boolean;
    total_contribution_days: number;
}>(5 * 60 * 1000);

/** Calendar cache - 10 minute TTL (larger data, less frequently changing) */
export const calendarCache = new TTLCache<Array<{ date: string; contributions: number }>>(10 * 60 * 1000);

/** Generic cache for other uses */
export const genericCache = new TTLCache<unknown>(5 * 60 * 1000);

export { TTLCache };