UNPKG

strata-storage

Version:

Zero-dependency universal storage plugin providing a unified API for all storage operations across web, Android, and iOS platforms

164 lines (163 loc) 4.8 kB
/** * Memory Adapter - In-memory storage implementation * Provides fast, non-persistent storage using Map */ import { BaseAdapter } from "../../core/BaseAdapter.js"; import { deepClone } from "../../utils/index.js"; /** * In-memory storage adapter using Map */ export class MemoryAdapter extends BaseAdapter { name = 'memory'; capabilities = { persistent: false, synchronous: false, // We use async for consistency observable: true, transactional: false, queryable: true, maxSize: -1, // No hard limit, but configurable binary: true, encrypted: false, // Encryption handled by feature layer crossTab: false, // Memory is per-instance }; storage = new Map(); maxSize; currentSize = 0; /** * Check if adapter is available (always true for memory) */ async isAvailable() { return true; } /** * Initialize the adapter */ async initialize(config) { this.maxSize = config?.maxSize; this.startTTLCleanup(); } /** * Get a value from memory */ async get(key) { const value = this.storage.get(key); if (!value) return null; // Check TTL if (this.isExpired(value)) { await this.remove(key); return null; } // Return a deep clone to prevent external modifications return deepClone(value); } /** * Set a value in memory */ async set(key, value) { const oldValue = this.storage.get(key); const newSize = this.calculateSize(value); // Check size limit if configured if (this.maxSize && this.maxSize > 0) { const oldSize = oldValue ? this.calculateSize(oldValue) : 0; const projectedSize = this.currentSize - oldSize + newSize; if (projectedSize > this.maxSize) { throw new Error(`Memory storage size limit exceeded. Limit: ${this.maxSize}, Projected: ${projectedSize}`); } } // Store a deep clone to prevent external modifications const clonedValue = deepClone(value); this.storage.set(key, clonedValue); // Update size tracking if (oldValue) { this.currentSize -= this.calculateSize(oldValue); } this.currentSize += newSize; // Emit change event this.emitChange(key, oldValue?.value, value.value, 'local'); } /** * Remove a value from memory */ async remove(key) { const value = this.storage.get(key); if (!value) return; this.storage.delete(key); this.currentSize -= this.calculateSize(value); // Emit change event this.emitChange(key, value.value, undefined, 'local'); } /** * Clear memory storage */ async clear(options) { if (!options || (!options.pattern && !options.tags && !options.expiredOnly)) { // Clear everything this.storage.clear(); this.currentSize = 0; this.emitChange('*', undefined, undefined, 'local'); return; } // Use base implementation for filtered clear await super.clear(options); } /** * Check if key exists */ async has(key) { const value = this.storage.get(key); return value !== undefined && !this.isExpired(value); } /** * Get all keys */ async keys(pattern) { const allKeys = Array.from(this.storage.keys()); // Remove expired entries const validKeys = []; for (const key of allKeys) { const value = this.storage.get(key); if (value && !this.isExpired(value)) { validKeys.push(key); } else if (value) { // Clean up expired entry await this.remove(key); } } return this.filterKeys(validKeys, pattern); } /** * Query implementation for memory adapter */ async query(condition) { const results = []; for (const [key, item] of this.storage.entries()) { if (!this.isExpired(item) && this.queryEngine.matches(item, condition)) { results.push({ key, value: deepClone(item.value), }); } } return results; } /** * Get current memory usage */ getMemoryUsage() { return { used: this.currentSize, limit: this.maxSize, }; } /** * Close the adapter */ async close() { await super.close(); this.storage.clear(); this.currentSize = 0; } }