@dbs-portal/core-api
Version:
HTTP client and API utilities for DBS Portal
273 lines • 7.44 kB
JavaScript
/**
* 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