// 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 };
|