UNPKG

@dbs-portal/core-api

Version:

HTTP client and API utilities for DBS Portal

273 lines 7.44 kB
/** * Browser storage cache implementation */ /** * Browser storage cache implementation (localStorage/sessionStorage) */ export class StorageCache { storage = null; prefix; constructor(storageType = 'localStorage') { this.prefix = `dbs_api_cache_${storageType}_`; try { this.storage = storageType === 'localStorage' ? localStorage : sessionStorage; // Test if storage is available const testKey = this.prefix + 'test'; this.storage.setItem(testKey, 'test'); this.storage.removeItem(testKey); } catch { this.storage = null; console.warn(`${storageType} is not available, falling back to memory cache`); } } /** * Gets a cache entry */ get(key) { if (!this.storage) { return null; } try { const item = this.storage.getItem(this.prefix + key); if (!item) { return null; } const entry = JSON.parse(item); // Check if entry has expired if (Date.now() - entry.timestamp > entry.ttl) { this.delete(key); return null; } return entry; } catch { return null; } } /** * Sets a cache entry */ set(key, entry) { if (!this.storage) { return; } try { const serialized = JSON.stringify(entry); this.storage.setItem(this.prefix + key, serialized); } catch (error) { // Handle quota exceeded error if (this.isQuotaExceededError(error)) { this.cleanup(); // Try again after cleanup try { const serialized = JSON.stringify(entry); this.storage.setItem(this.prefix + key, serialized); } catch { // If still failing, remove oldest entries this.evictOldest(5); try { const serialized = JSON.stringify(entry); this.storage.setItem(this.prefix + key, serialized); } catch { console.warn('Unable to store cache entry due to storage quota'); } } } } } /** * Deletes a cache entry */ delete(key) { if (!this.storage) { return; } this.storage.removeItem(this.prefix + key); } /** * Clears all cache entries */ clear() { if (!this.storage) { return; } const keys = this.keys(); for (const key of keys) { this.delete(key); } } /** * Gets the number of cached entries */ size() { return this.keys().length; } /** * Gets all cache keys */ keys() { if (!this.storage) { return []; } const keys = []; for (let i = 0; i < this.storage.length; i++) { const key = this.storage.key(i); if (key && key.startsWith(this.prefix)) { keys.push(key.substring(this.prefix.length)); } } return keys; } /** * Gets all cache entries */ entries() { const entries = []; const keys = this.keys(); for (const key of keys) { const entry = this.get(key); if (entry) { entries.push([key, entry]); } } return entries; } /** * Checks if a key exists in cache */ has(key) { if (!this.storage) { return false; } return this.storage.getItem(this.prefix + key) !== null; } /** * Gets storage usage information */ getStorageInfo() { if (!this.storage) { return { used: 0, available: 0, total: 0 }; } let used = 0; // Calculate used space for (let i = 0; i < this.storage.length; i++) { const key = this.storage.key(i); if (key) { const value = this.storage.getItem(key); used += key.length + (value?.length || 0); } } // Estimate total available space (varies by browser) const total = 5 * 1024 * 1024; // 5MB estimate const available = Math.max(0, total - used); return { used, available, total }; } /** * Cleans up expired entries */ cleanup() { if (!this.storage) { return 0; } let cleaned = 0; const keys = this.keys(); const now = Date.now(); for (const key of keys) { try { const item = this.storage.getItem(this.prefix + key); if (item) { const entry = JSON.parse(item); if (now - entry.timestamp > entry.ttl) { this.delete(key); cleaned++; } } } catch { // Remove corrupted entries this.delete(key); cleaned++; } } return cleaned; } /** * Evicts oldest entries */ evictOldest(count) { if (!this.storage) { return []; } const entries = this.entries(); const sortedEntries = entries.sort(([, a], [, b]) => a.timestamp - b.timestamp); const evicted = []; for (let i = 0; i < count && i < sortedEntries.length; i++) { const [key] = sortedEntries[i]; this.delete(key); evicted.push(key); } return evicted; } /** * Checks if error is quota exceeded */ isQuotaExceededError(error) { return (error instanceof DOMException && (error.code === 22 || error.code === 1014 || error.name === 'QuotaExceededError' || error.name === 'NS_ERROR_DOM_QUOTA_REACHED')); } /** * Gets cache statistics */ getStats() { const storageInfo = this.getStorageInfo(); return { size: this.size(), storageUsed: storageInfo.used, storageAvailable: storageInfo.available, hitRate: 0, // Would require additional tracking }; } /** * Exports cache data */ export() { const exported = {}; const entries = this.entries(); for (const [key, entry] of entries) { exported[key] = entry; } return exported; } /** * Imports cache data */ import(data) { this.clear(); for (const [key, entry] of Object.entries(data)) { this.set(key, entry); } } /** * Checks if storage is available */ isAvailable() { return this.storage !== null; } /** * Gets storage type */ getStorageType() { if (!this.storage) { return 'unavailable'; } return this.storage === localStorage ? 'localStorage' : 'sessionStorage'; } } //# sourceMappingURL=storage-cache.js.map