Jake Vanderwerf
2026-01-01 58dccc86754deda247eb49310c266f6cba86d36a
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
// cache.js - Client-side caching utilities
 
/**
 * Simple cache implementation with time-based expiration
 */
class Cache {
    constructor(options = {}) {
        this.storage = {};
        this.options = {
            defaultTTL: 3600000, // 1 hour in milliseconds
            namespace: 'jvb_cache_',
            useLocalStorage: true,
            ...options
        };
 
        // Load persisted data from localStorage if enabled
        if (this.options.useLocalStorage) {
            this.loadFromStorage();
        }
    }
 
    /**
     * Set a cache item
     * @param {string} key - Cache key
     * @param {*} value - Value to store
     * @param {number} ttl - Time to live in milliseconds (optional)
     */
    set(key, value, ttl = this.options.defaultTTL) {
        const cacheKey = this.formatKey(key);
        const expiry = Date.now() + ttl;
 
        this.storage[cacheKey] = {
            value,
            expiry
        };
 
        // Persist to localStorage if enabled
        if (this.options.useLocalStorage) {
            this.saveToStorage();
        }
 
        return value;
    }
 
    /**
     * Get a cache item
     * @param {string} key - Cache key
     * @param {*} defaultValue - Default value if item not found or expired
     * @returns {*} Cached value or default
     */
    get(key, defaultValue = null) {
        const cacheKey = this.formatKey(key);
        const item = this.storage[cacheKey];
 
        // Return default if item doesn't exist
        if (!item) {
            return defaultValue;
        }
 
        // Check if item has expired
        if (item.expiry < Date.now()) {
            this.remove(key);
            return defaultValue;
        }
 
        return item.value;
    }
 
    /**
     * Get or set a cache item
     * @param {string} key - Cache key
     * @param {Function} callback - Function to generate value if not cached
     * @param {number} ttl - Time to live in milliseconds (optional)
     * @returns {*} Cached or generated value
     */
    getOrSet(key, callback, ttl = this.options.defaultTTL) {
        const value = this.get(key);
 
        if (value !== null) {
            return value;
        }
 
        // Generate new value
        const newValue = callback();
 
        // Store in cache
        return this.set(key, newValue, ttl);
    }
 
    /**
     * Remove a cache item
     * @param {string} key - Cache key
     */
    remove(key) {
        const cacheKey = this.formatKey(key);
        delete this.storage[cacheKey];
 
        // Update localStorage if enabled
        if (this.options.useLocalStorage) {
            this.saveToStorage();
        }
    }
 
    /**
     * Clear all cache items
     */
    clear() {
        this.storage = {};
 
        // Clear from localStorage if enabled
        if (this.options.useLocalStorage) {
            this.clearFromStorage();
        }
    }
 
    /**
     * Check if a cache item exists and is not expired
     * @param {string} key - Cache key
     * @returns {boolean} True if item exists and is valid
     */
    has(key) {
        const cacheKey = this.formatKey(key);
        const item = this.storage[cacheKey];
 
        if (!item) {
            return false;
        }
 
        return item.expiry >= Date.now();
    }
 
    /**
     * Format a key with namespace
     * @param {string} key - Original key
     * @returns {string} Namespaced key
     */
    formatKey(key) {
        return `${this.options.namespace}${key}`;
    }
 
    /**
     * Save cache to localStorage
     */
    saveToStorage() {
        try {
            localStorage.setItem('jvb_cache', JSON.stringify({
                timestamp: Date.now(),
                data: this.storage
            }));
        } catch (e) {
            console.warn('Failed to save cache to localStorage:', e);
        }
    }
 
    /**
     * Load cache from localStorage
     */
    loadFromStorage() {
        try {
            const stored = localStorage.getItem('jvb_cache');
 
            if (stored) {
                const parsed = JSON.parse(stored);
 
                // Only use if not too old (24 hours max)
                const maxAge = 24 * 60 * 60 * 1000; // 24 hours
                if (parsed.timestamp && (Date.now() - parsed.timestamp) < maxAge) {
                    this.storage = parsed.data || {};
 
                    // Clean expired items
                    this.cleanExpired();
                }
            }
        } catch (e) {
            console.warn('Failed to load cache from localStorage:', e);
            this.storage = {};
        }
    }
 
    /**
     * Clear cache from localStorage
     */
    clearFromStorage() {
        try {
            localStorage.removeItem('jvb_cache');
        } catch (e) {
            console.warn('Failed to clear cache from localStorage:', e);
        }
    }
 
    /**
     * Remove expired items
     */
    cleanExpired() {
        const now = Date.now();
 
        Object.keys(this.storage).forEach(key => {
            const item = this.storage[key];
            if (item.expiry < now) {
                delete this.storage[key];
            }
        });
    }
}
 
// Create and export a singleton instance
const cache = new Cache();
 
export default cache;
 
// Also export the class for custom instances
export { Cache };