UNPKG

crewai-ts

Version:

TypeScript port of crewAI for agent-based workflows

244 lines (243 loc) 8.52 kB
/** * InMemoryStorageAdapter * * An in-memory implementation of the StorageAdapter interface * Optimized for performance in development environments */ import { BaseStorageAdapter } from './StorageAdapter.js'; /** * InMemoryStorageAdapter implements the StorageAdapter interface * using an in-memory Map with optional LRU eviction */ export class InMemoryStorageAdapter extends BaseStorageAdapter { storage = new Map(); accessOrder = []; // For LRU tracking maxItems; persistent; constructor(options = {}) { super(options); this.maxItems = options.maxItems || 10000; this.persistent = options.persistent || false; // If persistent is enabled, try to load items from localStorage if (this.persistent) { this.loadFromPersistence(); } } /** * Save a value with optimized memory usage */ async save(key, value) { const namespacedKey = this.getNamespacedKey(key); // Create storage item with metadata const item = { value, timestamp: Date.now() }; // Check if we need to evict items (LRU policy) if (this.storage.size >= this.maxItems && !this.storage.has(namespacedKey)) { this.evictLRU(); } // Store the item this.storage.set(namespacedKey, item); // Update access order for LRU tracking this.updateAccessOrder(namespacedKey); // Persist if enabled if (this.persistent) { this.persistToDisk(); } } /** * Load a value with optimized retrieval */ async load(key) { const namespacedKey = this.getNamespacedKey(key); const item = this.storage.get(namespacedKey); if (!item) return null; // Check if item has expired if (this.isExpired(item.timestamp)) { // Remove expired item await this.delete(key); return null; } // Update access order for LRU tracking this.updateAccessOrder(namespacedKey); return item.value; } /** * Delete a value with cleanup */ async delete(key) { const namespacedKey = this.getNamespacedKey(key); const deleted = this.storage.delete(namespacedKey); if (deleted) { // Remove from access order tracking this.accessOrder = this.accessOrder.filter(k => k !== namespacedKey); // Persist changes if enabled if (this.persistent) { this.persistToDisk(); } } return deleted; } /** * Clear all values */ async clear() { this.storage.clear(); this.accessOrder = []; // Clear persistence if enabled if (this.persistent) { this.clearPersistence(); } } /** * Get all keys with namespace removed */ async keys() { const prefix = `${this.options.namespace}:`; const allKeys = Array.from(this.storage.keys()); // Remove namespace from keys and filter out expired items const validKeys = []; for (const key of allKeys) { // Remove namespace prefix if (key.startsWith(prefix)) { const baseKey = key.substring(prefix.length); const item = this.storage.get(key); // Only include non-expired items if (item && !this.isExpired(item.timestamp)) { validKeys.push(baseKey); } else if (item) { // Clean up expired items this.storage.delete(key); this.accessOrder = this.accessOrder.filter(k => k !== key); } } } return validKeys; } /** * Get memory usage statistics */ getStats() { const size = this.storage.size; // Estimate memory usage in bytes (rough approximation) let memoryUsage = 0; for (const [key, item] of this.storage.entries()) { memoryUsage += key.length * 2; // String size (2 bytes per char) memoryUsage += 8; // timestamp (number = 8 bytes) // Estimate value size const valueStr = JSON.stringify(item.value); memoryUsage += valueStr ? valueStr.length * 2 : 0; } return { size, memoryUsage }; } /** * Helper: Update access order for LRU tracking */ updateAccessOrder(key) { // Remove from current position this.accessOrder = this.accessOrder.filter(k => k !== key); // Add to end (most recently used) this.accessOrder.push(key); } /** * Helper: Evict least recently used item */ evictLRU() { if (this.accessOrder.length === 0) return; // Get the least recently used key const oldestKey = this.accessOrder.shift(); if (oldestKey) { this.storage.delete(oldestKey); // Persist changes if enabled if (this.persistent) { this.persistToDisk(); } } } /** * Helper: Persist to disk (if possible) */ persistToDisk() { // Only try if we're in a browser environment with localStorage if (typeof localStorage !== 'undefined') { try { // Convert storage to a serializable format const serializableData = {}; for (const [key, item] of this.storage.entries()) { serializableData[key] = { value: this.serialize(item.value), timestamp: item.timestamp }; } // Store data localStorage.setItem(`${this.options.namespace}_storage`, JSON.stringify(serializableData)); // Store access order localStorage.setItem(`${this.options.namespace}_access_order`, JSON.stringify(this.accessOrder)); } catch (e) { if (this.options.debug) { console.error('Failed to persist memory to localStorage:', e); } } } } /** * Helper: Load from persistence */ loadFromPersistence() { // Only try if we're in a browser environment with localStorage if (typeof localStorage !== 'undefined') { try { // Load storage data const storageData = localStorage.getItem(`${this.options.namespace}_storage`); if (storageData) { const parsed = JSON.parse(storageData); // Rebuild storage for (const [key, itemData] of Object.entries(parsed)) { const item = itemData; // Skip expired items if (this.isExpired(item.timestamp)) continue; this.storage.set(key, { value: this.deserialize(item.value), timestamp: item.timestamp }); } } // Load access order const accessOrderData = localStorage.getItem(`${this.options.namespace}_access_order`); if (accessOrderData) { this.accessOrder = JSON.parse(accessOrderData); // Filter out any keys that don't exist in storage this.accessOrder = this.accessOrder.filter(key => this.storage.has(key)); } } catch (e) { if (this.options.debug) { console.error('Failed to load memory from localStorage:', e); } } } } /** * Helper: Clear persistence data */ clearPersistence() { // Only try if we're in a browser environment with localStorage if (typeof localStorage !== 'undefined') { try { localStorage.removeItem(`${this.options.namespace}_storage`); localStorage.removeItem(`${this.options.namespace}_access_order`); } catch (e) { if (this.options.debug) { console.error('Failed to clear persisted memory:', e); } } } } }