UNPKG

native-update

Version:

Foundation package for building a comprehensive update system for Capacitor apps. Provides architecture and interfaces but requires backend implementation.

275 lines 8.18 kB
import { ConfigManager } from './config'; import { Logger } from './logger'; import { Directory, Encoding } from '@capacitor/filesystem'; /** * Manages caching with expiration for various data types */ export class CacheManager { constructor() { this.filesystem = null; this.memoryCache = new Map(); this.CACHE_DIR = 'cache'; this.logger = Logger.getInstance(); this.configManager = ConfigManager.getInstance(); } /** * Initialize cache manager */ async initialize() { this.filesystem = this.configManager.get('filesystem'); if (!this.filesystem) { throw new Error('Filesystem not configured'); } // Create cache directory if it doesn't exist try { await this.filesystem.mkdir({ path: this.CACHE_DIR, directory: Directory.Data, recursive: true, }); } catch (error) { this.logger.debug('Cache directory may already exist', error); } // Clean expired cache on initialization await this.cleanExpiredCache(); } /** * Set cache entry with expiration */ async set(key, data, ttlMs) { const expiry = Date.now() + (ttlMs || this.configManager.get('cacheExpiration')); const entry = { data, timestamp: Date.now(), expiry, }; // Update memory cache this.memoryCache.set(key, entry); // Persist to filesystem for larger data if (this.shouldPersist(data)) { await this.persistToFile(key, entry); } this.logger.debug('Cache entry set', { key, expiry: new Date(expiry) }); } /** * Get cache entry */ async get(key) { // Check memory cache first const memEntry = this.memoryCache.get(key); if (memEntry) { if (Date.now() < memEntry.expiry) { return memEntry.data; } else { // Expired - remove from memory this.memoryCache.delete(key); } } // Check filesystem cache const fileEntry = await this.loadFromFile(key); if (fileEntry) { if (Date.now() < fileEntry.expiry) { // Update memory cache this.memoryCache.set(key, fileEntry); return fileEntry.data; } else { // Expired - remove file await this.removeFile(key); } } return null; } /** * Check if cache entry exists and is valid */ async has(key) { const value = await this.get(key); return value !== null; } /** * Remove cache entry */ async remove(key) { this.memoryCache.delete(key); await this.removeFile(key); this.logger.debug('Cache entry removed', { key }); } /** * Clear all cache */ async clear() { this.memoryCache.clear(); // Remove cache directory try { await this.filesystem.rmdir({ path: this.CACHE_DIR, directory: Directory.Data, recursive: true, }); // Recreate empty cache directory await this.filesystem.mkdir({ path: this.CACHE_DIR, directory: Directory.Data, recursive: true, }); } catch (error) { this.logger.warn('Failed to clear cache directory', error); } this.logger.info('Cache cleared'); } /** * Clean expired cache entries */ async cleanExpiredCache() { const now = Date.now(); let cleanedCount = 0; // Clean memory cache for (const [key, entry] of this.memoryCache) { if (now >= entry.expiry) { this.memoryCache.delete(key); cleanedCount++; } } // Clean filesystem cache try { const files = await this.filesystem.readdir({ path: this.CACHE_DIR, directory: Directory.Data, }); for (const file of files.files) { const key = file.name.replace('.json', ''); const entry = await this.loadFromFile(key); if (!entry || now >= entry.expiry) { await this.removeFile(key); cleanedCount++; } } } catch (error) { this.logger.debug('Failed to clean filesystem cache', error); } if (cleanedCount > 0) { this.logger.info('Cleaned expired cache entries', { count: cleanedCount, }); } } /** * Get cache statistics */ async getStats() { let fileEntries = 0; let totalSize = 0; try { const files = await this.filesystem.readdir({ path: this.CACHE_DIR, directory: Directory.Data, }); fileEntries = files.files.length; // Estimate size (this is approximate) for (const file of files.files) { const stat = await this.filesystem.stat({ path: `${this.CACHE_DIR}/${file.name}`, directory: Directory.Data, }); totalSize += stat.size || 0; } } catch (error) { this.logger.debug('Failed to get cache stats', error); } return { memoryEntries: this.memoryCache.size, fileEntries, totalSize, }; } /** * Cache bundle metadata */ async cacheBundleMetadata(bundle) { const key = `bundle_meta_${bundle.bundleId}`; await this.set(key, bundle, 24 * 60 * 60 * 1000); // 24 hours } /** * Get cached bundle metadata */ async getCachedBundleMetadata(bundleId) { const key = `bundle_meta_${bundleId}`; return this.get(key); } /** * Check if data should be persisted to filesystem */ shouldPersist(data) { // Persist objects and large strings if (typeof data === 'object') return true; if (typeof data === 'string' && data.length > 1024) return true; return false; } /** * Persist cache entry to file */ async persistToFile(key, entry) { if (!this.filesystem) return; try { const path = `${this.CACHE_DIR}/${key}.json`; const data = JSON.stringify(entry); await this.filesystem.writeFile({ path, data, directory: Directory.Data, encoding: Encoding.UTF8, }); } catch (error) { this.logger.warn('Failed to persist cache to file', { key, error }); } } /** * Load cache entry from file */ async loadFromFile(key) { if (!this.filesystem) return null; try { const path = `${this.CACHE_DIR}/${key}.json`; const result = await this.filesystem.readFile({ path, directory: Directory.Data, encoding: Encoding.UTF8, }); return JSON.parse(result.data); } catch (_a) { // File doesn't exist or is corrupted return null; } } /** * Remove cache file */ async removeFile(key) { if (!this.filesystem) return; try { const path = `${this.CACHE_DIR}/${key}.json`; await this.filesystem.deleteFile({ path, directory: Directory.Data, }); } catch (error) { // File may not exist this.logger.debug('Failed to remove cache file', { key, error }); } } } //# sourceMappingURL=cache-manager.js.map