UNPKG

@sailboat-computer/data-storage

Version:

Shared data storage library for sailboat computer v3

1,144 lines (1,022 loc) 33.4 kB
/** * Unified storage manager implementation */ import { UnifiedStorageManager, UnifiedStorageManagerOptions, StorageManager, StorageConfig, StorageStatus, LocalStorageStatus, ClearResult, SyncResult, ResilienceMetrics, DataQuery, StoredData, StorageTier, StorageTierType, CleanupResult, StorageOptions, HealthStatus, AlertSeverity, BatchPriority, BatchPriorityType } from '../types'; import { StorageError, StorageErrorCode } from '../utils/errors'; import { createStorageManager } from './manager'; import { createLocalFileProvider } from './providers/local-file'; import { LocalFileProvider } from './providers/local-file'; /** * Default options for unified storage manager */ const DEFAULT_OPTIONS: UnifiedStorageManagerOptions = { localStorage: { directory: './data', maxSizeBytes: 1024 * 1024 * 100, // 100 MB encrypt: false, compressionLevel: 0 }, sync: { enabled: true, intervalMs: 60000, // 1 minute batchSize: 100 } }; /** * Unified storage manager implementation */ export class UnifiedStorageManagerImpl implements UnifiedStorageManager { private initialized = false; private remoteStorageManager: StorageManager; private localStorageProvider: LocalFileProvider; private syncInterval?: NodeJS.Timeout; private offlineBuffer: StoredData[] = []; private lastOperation: Date = new Date(); private metrics = { operationsPerSecond: 0, averageLatency: 0, errorRate: 0, operationCount: 0, errorCount: 0, totalLatency: 0, syncCount: 0, syncSuccessCount: 0, syncFailureCount: 0, syncItemCount: 0, syncConflictCount: 0 }; /** * Create a new unified storage manager * * @param config - Storage configuration * @param options - Storage manager options */ constructor( private config: StorageConfig, private options: UnifiedStorageManagerOptions = DEFAULT_OPTIONS ) { // Set default options with non-nullable properties const defaultedOptions = { ...DEFAULT_OPTIONS, ...options }; // Ensure localStorage and sync are not undefined this.options = { ...defaultedOptions, localStorage: { ...DEFAULT_OPTIONS.localStorage, ...(options?.localStorage || {}) }, sync: { ...DEFAULT_OPTIONS.sync, ...(options?.sync || {}) } }; // Create remote storage manager this.remoteStorageManager = createStorageManager(config, { retryAttempts: this.options.resilience?.retry?.maxRetries || 3, retryDelayMs: this.options.resilience?.retry?.baseDelayMs || 100, enableMetrics: true }); // Create local storage provider this.localStorageProvider = createLocalFileProvider({ directory: this.options.localStorage.directory, maxSizeBytes: this.options.localStorage.maxSizeBytes, encrypt: this.options.localStorage.encrypt, compressionLevel: this.options.localStorage.compressionLevel }) as LocalFileProvider; } /** * Initialize the storage manager */ async initialize(): Promise<void> { if (this.initialized) { return; } try { // Initialize local storage provider await this.localStorageProvider.initialize(); // Initialize remote storage manager try { await this.remoteStorageManager.initialize(); } catch (error) { console.warn('Failed to initialize remote storage manager:', error); // Continue with local storage only } // Start sync interval if enabled if (this.options.sync.enabled) { this.startSyncInterval(); } this.initialized = true; console.log('Unified storage manager initialized'); } catch (error) { console.error('Failed to initialize unified storage manager:', error); throw new StorageError( StorageErrorCode.CONNECTION_FAILED, 'Failed to initialize unified storage manager', { error } ); } } /** * Store data * * @param data - Data to store * @param category - Data category * @param options - Storage options * @returns Data ID */ async store(data: any, category: string, options: StorageOptions = {}): Promise<string> { this.ensureInitialized(); const startTime = Date.now(); let error: Error | null = null; try { // Try to store in remote storage first try { const id = await this.remoteStorageManager.store(data, category, options); this.updateMetrics(Date.now() - startTime); return id; } catch (remoteError) { console.warn('Failed to store data in remote storage:', remoteError); // Store in local storage as fallback const id = await this.localStorageProvider.store(data, { category, timestamp: options.timestamp ? options.timestamp.toISOString() : new Date().toISOString(), tags: options.tags || {} }); // Add to offline buffer for later sync this.offlineBuffer.push({ data, metadata: { id, category, timestamp: options.timestamp ? options.timestamp.toISOString() : new Date().toISOString(), tags: options.tags || {} } }); this.updateMetrics(Date.now() - startTime); return id; } } catch (err) { error = err instanceof Error ? err : new Error(String(err)); this.updateMetrics(Date.now() - startTime, true); throw new StorageError( StorageErrorCode.STORE_FAILED, 'Failed to store data', { category, error } ); } } /** * Retrieve data * * @param query - Data query * @param tier - Storage tier to query (optional) * @returns Stored data */ async retrieve(query: DataQuery, tier?: StorageTierType): Promise<StoredData[]> { this.ensureInitialized(); const startTime = Date.now(); let error: Error | null = null; try { // If tier is specified, query only that tier if (tier) { if (tier === StorageTier.HOT) { // Query local storage const results = await this.localStorageProvider.retrieve(query); this.updateMetrics(Date.now() - startTime); return results; } else { // Query remote storage try { const results = await this.remoteStorageManager.retrieve(query, tier); this.updateMetrics(Date.now() - startTime); return results; } catch (remoteError) { console.warn('Failed to retrieve data from remote storage:', remoteError); this.updateMetrics(Date.now() - startTime, true); return []; } } } // Query both local and remote storage let localResults: StoredData[] = []; let remoteResults: StoredData[] = []; // Query local storage localResults = await this.localStorageProvider.retrieve(query); // Query remote storage try { remoteResults = await this.remoteStorageManager.retrieve(query); } catch (remoteError) { console.warn('Failed to retrieve data from remote storage:', remoteError); // Continue with local results only } // Merge results, preferring local results for the same ID const resultsMap = new Map<string, StoredData>(); // Add remote results first for (const result of remoteResults) { if (result.metadata.id) { resultsMap.set(result.metadata.id, result); } } // Add local results, overwriting remote results with the same ID for (const result of localResults) { if (result.metadata.id) { resultsMap.set(result.metadata.id, result); } } // Convert map to array const results = Array.from(resultsMap.values()); // Apply sorting if specified if (query.sort) { results.sort((a, b) => { const aValue = query.sort?.field === 'timestamp' ? new Date(a.metadata.timestamp).getTime() : a.data[query.sort?.field || '']; const bValue = query.sort?.field === 'timestamp' ? new Date(b.metadata.timestamp).getTime() : b.data[query.sort?.field || '']; if (query.sort?.order === 'desc') { return bValue - aValue; } else { return aValue - bValue; } }); } // Apply pagination if specified if (query.limit) { const offset = query.offset || 0; const limit = query.limit; this.updateMetrics(Date.now() - startTime); return results.slice(offset, offset + limit); } this.updateMetrics(Date.now() - startTime); return results; } catch (err) { error = err instanceof Error ? err : new Error(String(err)); this.updateMetrics(Date.now() - startTime, true); throw new StorageError( StorageErrorCode.RETRIEVE_FAILED, 'Failed to retrieve data', { query, tier, error } ); } } /** * Update data * * @param data - Data to update */ async update(data: StoredData): Promise<void> { this.ensureInitialized(); const startTime = Date.now(); let error: Error | null = null; try { // Try to update in remote storage first try { await this.remoteStorageManager.update(data); } catch (remoteError) { console.warn('Failed to update data in remote storage:', remoteError); // Update in local storage as fallback await this.localStorageProvider.update(data); // Add to offline buffer for later sync this.offlineBuffer.push(data); } // Also update in local storage to keep it in sync try { await this.localStorageProvider.update(data); } catch (localError) { console.warn('Failed to update data in local storage:', localError); } this.updateMetrics(Date.now() - startTime); } catch (err) { error = err instanceof Error ? err : new Error(String(err)); this.updateMetrics(Date.now() - startTime, true); throw new StorageError( StorageErrorCode.UPDATE_FAILED, 'Failed to update data', { id: data.metadata.id, error } ); } } /** * Delete data * * @param id - Data ID */ async delete(id: string): Promise<void> { this.ensureInitialized(); const startTime = Date.now(); let error: Error | null = null; try { // Try to delete from remote storage first try { await this.remoteStorageManager.delete(id); } catch (remoteError) { console.warn('Failed to delete data from remote storage:', remoteError); } // Also delete from local storage try { await this.localStorageProvider.delete(id); } catch (localError) { console.warn('Failed to delete data from local storage:', localError); } // Remove from offline buffer if present this.offlineBuffer = this.offlineBuffer.filter(item => item.metadata.id !== id); this.updateMetrics(Date.now() - startTime); } catch (err) { error = err instanceof Error ? err : new Error(String(err)); this.updateMetrics(Date.now() - startTime, true); throw new StorageError( StorageErrorCode.DELETE_FAILED, 'Failed to delete data', { id, error } ); } } /** * Store multiple data items * * @param items - Data items to store * @returns Data IDs */ async storeBatch(items: Array<{data: any, category: string, options?: StorageOptions}>): Promise<string[]> { this.ensureInitialized(); const startTime = Date.now(); let error: Error | null = null; try { // Try to store in remote storage first try { const ids = await this.remoteStorageManager.storeBatch(items); this.updateMetrics(Date.now() - startTime); return ids; } catch (remoteError) { console.warn('Failed to store batch data in remote storage:', remoteError); // Store in local storage as fallback const ids: string[] = []; for (const item of items) { const id = await this.localStorageProvider.store(item.data, { category: item.category, timestamp: item.options?.timestamp ? item.options.timestamp.toISOString() : new Date().toISOString(), tags: item.options?.tags || {} }); ids.push(id); // Add to offline buffer for later sync this.offlineBuffer.push({ data: item.data, metadata: { id, category: item.category, timestamp: item.options?.timestamp ? item.options.timestamp.toISOString() : new Date().toISOString(), tags: item.options?.tags || {} } }); } this.updateMetrics(Date.now() - startTime); return ids; } } catch (err) { error = err instanceof Error ? err : new Error(String(err)); this.updateMetrics(Date.now() - startTime, true); throw new StorageError( StorageErrorCode.STORE_FAILED, 'Failed to store batch data', { itemCount: items.length, error } ); } } /** * Retrieve multiple data sets * * @param queries - Data queries * @param tier - Storage tier to query (optional) * @returns Stored data sets */ async retrieveBatch(queries: DataQuery[], tier?: StorageTierType): Promise<StoredData[][]> { this.ensureInitialized(); const startTime = Date.now(); let error: Error | null = null; try { const results: StoredData[][] = []; for (const query of queries) { const queryResults = await this.retrieve(query, tier); results.push(queryResults); } this.updateMetrics(Date.now() - startTime); return results; } catch (err) { error = err instanceof Error ? err : new Error(String(err)); this.updateMetrics(Date.now() - startTime, true); throw new StorageError( StorageErrorCode.RETRIEVE_FAILED, 'Failed to retrieve batch data', { queryCount: queries.length, error } ); } } /** * Clean up old data * * @param category - Data category * @param tier - Storage tier * @param retentionDays - Retention period in days * @returns Cleanup result */ async cleanup(category: string, tier: StorageTierType, retentionDays: number): Promise<CleanupResult> { this.ensureInitialized(); const startTime = Date.now(); let error: Error | null = null; try { if (tier === StorageTier.HOT) { // Clean up local storage return await this.localStorageProvider.cleanup(category, retentionDays); } else { // Clean up remote storage return await this.remoteStorageManager.cleanup(category, tier, retentionDays); } } catch (err) { error = err instanceof Error ? err : new Error(String(err)); this.updateMetrics(Date.now() - startTime, true); throw new StorageError( StorageErrorCode.CLEANUP_FAILED, 'Failed to clean up data', { category, tier, retentionDays, error } ); } } /** * Migrate data between tiers * * @param id - Data ID * @param fromTier - Source tier * @param toTier - Destination tier */ async migrate(id: string, fromTier: StorageTierType, toTier: StorageTierType): Promise<void> { this.ensureInitialized(); const startTime = Date.now(); let error: Error | null = null; try { if (fromTier === StorageTier.HOT && toTier !== StorageTier.HOT) { // Migrate from local to remote const data = await this.localStorageProvider.retrieve({ id } as DataQuery); if (data.length === 0) { throw new Error(`Data with ID ${id} not found in local storage`); } // Store in remote storage await this.remoteStorageManager.store(data[0].data, data[0].metadata.category, { timestamp: new Date(data[0].metadata.timestamp), tags: data[0].metadata.tags, tier: toTier }); // Delete from local storage await this.localStorageProvider.delete(id); } else if (fromTier !== StorageTier.HOT && toTier === StorageTier.HOT) { // Migrate from remote to local const data = await this.remoteStorageManager.retrieve({ id } as DataQuery, fromTier); if (data.length === 0) { throw new Error(`Data with ID ${id} not found in remote storage`); } // Store in local storage await this.localStorageProvider.store(data[0].data, { id, category: data[0].metadata.category, timestamp: data[0].metadata.timestamp, tags: data[0].metadata.tags }); // Delete from remote storage await this.remoteStorageManager.delete(id); } else if (fromTier !== StorageTier.HOT && toTier !== StorageTier.HOT) { // Migrate between remote tiers await this.remoteStorageManager.migrate(id, fromTier, toTier); } else { // No migration needed console.log(`No migration needed for ${id} from ${fromTier} to ${toTier}`); } this.updateMetrics(Date.now() - startTime); } catch (err) { error = err instanceof Error ? err : new Error(String(err)); this.updateMetrics(Date.now() - startTime, true); throw new StorageError( StorageErrorCode.MIGRATION_FAILED, 'Failed to migrate data', { id, fromTier, toTier, error } ); } } /** * Shutdown the storage manager */ async shutdown(): Promise<void> { if (!this.initialized) { return; } try { // Stop sync interval if (this.syncInterval) { clearInterval(this.syncInterval); this.syncInterval = undefined; } // Shutdown local storage provider await this.localStorageProvider.shutdown(); // Shutdown remote storage manager await this.remoteStorageManager.shutdown(); this.initialized = false; console.log('Unified storage manager closed'); } catch (error) { console.error('Error closing unified storage manager:', error); throw new StorageError( StorageErrorCode.SYSTEM_ERROR, 'Failed to close unified storage manager', { error } ); } } /** * Get storage status * * @returns Storage status */ async getStatus(): Promise<StorageStatus> { this.ensureInitialized(); try { // Get local storage status const localStatus = await this.getLocalStatus(); // Get remote storage status let remoteStatus: StorageStatus | undefined; try { remoteStatus = await this.remoteStorageManager.getStatus(); } catch (error) { console.warn('Failed to get remote storage status:', error); } // Combine statuses return { connected: remoteStatus?.connected || false, healthy: localStatus.healthy && (remoteStatus?.healthy || false), lastOperation: this.lastOperation, metrics: { operationsPerSecond: this.metrics.operationsPerSecond, averageLatency: this.metrics.averageLatency, errorRate: this.metrics.errorRate, storageUsed: localStatus.usedSizeBytes + (remoteStatus?.metrics?.storageUsed || 0), storageAvailable: localStatus.freeSizeBytes + (remoteStatus?.metrics?.storageAvailable || 0) }, queryPerformance: { hot: { averageQueryTime: 0, queriesPerSecond: 0 }, warm: { averageQueryTime: 0, queriesPerSecond: 0 }, cold: { averageQueryTime: 0, queriesPerSecond: 0 }, pendingItems: this.offlineBuffer.length, averageBatchSize: 0, totalBatches: 0, failedBatches: 0, itemsRemoved: 0, bytesFreed: 0, duration: 0, hotStorageUsage: localStatus.usedSizeBytes, warmStorageUsage: 0, coldStorageUsage: 0, totalStorageUsage: localStatus.usedSizeBytes + (remoteStatus?.metrics?.storageUsed || 0), hotStorageCapacity: localStatus.totalSizeBytes, warmStorageCapacity: 0, coldStorageCapacity: 0, totalStorageCapacity: localStatus.totalSizeBytes + (remoteStatus?.metrics?.storageAvailable || 0), operationsPerSecond: this.metrics.operationsPerSecond, averageLatency: this.metrics.averageLatency, errorRate: this.metrics.errorRate, queryPerformance: { hot: { averageQueryTime: 0, queriesPerSecond: 0 }, warm: { averageQueryTime: 0, queriesPerSecond: 0 }, cold: { averageQueryTime: 0, queriesPerSecond: 0 }, overall: HealthStatus.HEALTHY, providers: { hot: HealthStatus.HEALTHY, warm: HealthStatus.HEALTHY, cold: HealthStatus.HEALTHY }, issues: { severity: AlertSeverity.INFO, message: 'Unified storage manager status', component: 'unified-storage-manager', timestamp: new Date() }, circuitBreaker: { state: {}, failures: {}, lastFailure: {} }, retry: { retryCount: {}, successCount: {}, failureCount: {}, averageDelay: {} }, fallback: { fallbackCount: {}, successCount: {}, failureCount: {} }, timeout: { timeoutCount: {}, averageDuration: {} }, bulkhead: { concurrentOperations: {}, queueSize: {}, rejectionCount: {} }, rateLimit: { limitedCount: {}, bypassCount: {}, waitTimeMs: {}, avgTokensPerSecond: {}, currentTokens: {} } } } }; } catch (error) { console.error('Failed to get unified storage status:', error); return { connected: false, healthy: false, error: `Failed to get unified storage status: ${error}`, queryPerformance: { hot: { averageQueryTime: 0, queriesPerSecond: 0 }, warm: { averageQueryTime: 0, queriesPerSecond: 0 }, cold: { averageQueryTime: 0, queriesPerSecond: 0 }, pendingItems: 0, averageBatchSize: 0, totalBatches: 0, failedBatches: 0, itemsRemoved: 0, bytesFreed: 0, duration: 0, hotStorageUsage: 0, warmStorageUsage: 0, coldStorageUsage: 0, totalStorageUsage: 0, hotStorageCapacity: 0, warmStorageCapacity: 0, coldStorageCapacity: 0, totalStorageCapacity: 0, operationsPerSecond: 0, averageLatency: 0, errorRate: 0, queryPerformance: { hot: { averageQueryTime: 0, queriesPerSecond: 0 }, warm: { averageQueryTime: 0, queriesPerSecond: 0 }, cold: { averageQueryTime: 0, queriesPerSecond: 0 }, overall: HealthStatus.UNHEALTHY, providers: { hot: HealthStatus.UNHEALTHY, warm: HealthStatus.UNHEALTHY, cold: HealthStatus.UNHEALTHY }, issues: { severity: AlertSeverity.ALARM, message: `Failed to get unified storage status: ${error}`, component: 'unified-storage-manager', timestamp: new Date() }, circuitBreaker: { state: {}, failures: {}, lastFailure: {} }, retry: { retryCount: {}, successCount: {}, failureCount: {}, averageDelay: {} }, fallback: { fallbackCount: {}, successCount: {}, failureCount: {} }, timeout: { timeoutCount: {}, averageDuration: {} }, bulkhead: { concurrentOperations: {}, queueSize: {}, rejectionCount: {} }, rateLimit: { limitedCount: {}, bypassCount: {}, waitTimeMs: {}, avgTokensPerSecond: {}, currentTokens: {} } } } }; } } /** * Get local storage status * * @returns Local storage status */ async getLocalStatus(): Promise<LocalStorageStatus> { this.ensureInitialized(); try { // In a real implementation, this would get disk usage statistics // Get the maxSizeBytes with a default value to avoid undefined const maxSizeBytes = this.options.localStorage.maxSizeBytes || DEFAULT_OPTIONS.localStorage.maxSizeBytes || 0; return { available: true, healthy: true, totalSizeBytes: maxSizeBytes, usedSizeBytes: 0, freeSizeBytes: maxSizeBytes, itemCount: 0 }; } catch (error) { console.error('Failed to get local storage status:', error); return { available: false, healthy: false, totalSizeBytes: 0, usedSizeBytes: 0, freeSizeBytes: 0, itemCount: 0, lastError: `Failed to get local storage status: ${error}` }; } } /** * Clear local cache * * @returns Clear result */ async clearLocalCache(): Promise<ClearResult> { this.ensureInitialized(); try { // In a real implementation, this would delete all files from the local storage directory return { success: true, itemsRemoved: 0, bytesFreed: 0, durationMs: 0 }; } catch (error) { console.error('Failed to clear local cache:', error); return { success: false, itemsRemoved: 0, bytesFreed: 0, durationMs: 0, error: `Failed to clear local cache: ${error}` }; } } /** * Synchronize local and remote storage * * @returns Sync result */ async synchronize(): Promise<SyncResult> { this.ensureInitialized(); const startTime = Date.now(); try { // Check if there are items in the offline buffer if (this.offlineBuffer.length === 0) { return { success: true, itemCount: 0, conflictCount: 0, failedCount: 0, durationMs: Date.now() - startTime }; } // Get items to sync const batchSize = this.options.sync.batchSize; const itemsToSync = this.offlineBuffer.slice(0, batchSize); // Track sync metrics this.metrics.syncCount++; this.metrics.syncItemCount += itemsToSync.length; // Sync each item let successCount = 0; let conflictCount = 0; let failedCount = 0; for (const item of itemsToSync) { try { // Check if item exists in remote storage const remoteItems = await this.remoteStorageManager.retrieve({ id: item.metadata.id } as DataQuery); if (remoteItems.length > 0) { // Item exists in remote storage, check for conflicts const remoteItem = remoteItems[0]; const remoteTimestamp = new Date(remoteItem.metadata.timestamp).getTime(); const localTimestamp = new Date(item.metadata.timestamp).getTime(); if (remoteTimestamp > localTimestamp) { // Remote item is newer, keep it conflictCount++; continue; } // Update remote item with local data await this.remoteStorageManager.update(item); successCount++; } else { // Item doesn't exist in remote storage, create it await this.remoteStorageManager.store(item.data, item.metadata.category, { timestamp: new Date(item.metadata.timestamp), tags: item.metadata.tags }); successCount++; } // Remove from offline buffer this.offlineBuffer = this.offlineBuffer.filter(bufferItem => bufferItem.metadata.id !== item.metadata.id ); } catch (error) { console.error('Failed to sync item:', error); failedCount++; } } // Update sync metrics this.metrics.syncSuccessCount += successCount; this.metrics.syncFailureCount += failedCount; this.metrics.syncConflictCount += conflictCount; return { success: failedCount === 0, itemCount: itemsToSync.length, conflictCount, failedCount, durationMs: Date.now() - startTime }; } catch (error) { console.error('Failed to synchronize storage:', error); return { success: false, itemCount: 0, conflictCount: 0, failedCount: 0, durationMs: Date.now() - startTime, error: `Failed to synchronize storage: ${error}` }; } } /** * Start sync interval */ private startSyncInterval(): void { if (this.syncInterval) { clearInterval(this.syncInterval); } const intervalMs = this.options.sync.intervalMs; this.syncInterval = setInterval(async () => { try { await this.synchronize(); } catch (error) { console.error('Error during scheduled sync:', error); } }, intervalMs); console.log(`Sync interval started (${intervalMs}ms)`); } /** * Update metrics * * @param latencyMs - Operation latency in milliseconds * @param isError - Whether the operation resulted in an error */ private updateMetrics(latencyMs: number, isError: boolean = false): void { this.lastOperation = new Date(); this.metrics.operationCount++; this.metrics.totalLatency += latencyMs; if (isError) { this.metrics.errorCount++; } // Calculate averages this.metrics.averageLatency = this.metrics.totalLatency / this.metrics.operationCount; this.metrics.errorRate = this.metrics.errorCount / this.metrics.operationCount; // Calculate operations per second (over the last minute) const oneMinuteAgo = Date.now() - 60000; this.metrics.operationsPerSecond = this.metrics.operationCount / 60; } /** * Get resilience metrics * * @returns Resilience metrics */ getResilienceMetrics(): ResilienceMetrics { return { circuitBreaker: { state: {}, failures: {}, lastFailure: {} }, retry: { retryCount: {}, successCount: {}, failureCount: {}, averageDelay: {} }, fallback: { fallbackCount: {}, successCount: {}, failureCount: {} }, timeout: { timeoutCount: {}, averageDuration: {} }, bulkhead: { concurrentOperations: {}, queueSize: {}, rejectionCount: {} }, rateLimit: { limitedCount: {}, bypassCount: {}, waitTimeMs: {}, avgTokensPerSecond: {}, currentTokens: {} } }; } /** * Ensure provider is initialized * * @throws StorageError if not initialized */ private ensureInitialized(): void { if (!this.initialized) { throw new StorageError( StorageErrorCode.CONNECTION_FAILED, 'Unified storage manager not initialized', {} ); } } } /** * Create a new unified storage manager * * @param config - Storage configuration * @param options - Storage manager options * @returns Unified storage manager */ export function createUnifiedStorageManager( config: StorageConfig, options?: UnifiedStorageManagerOptions ): UnifiedStorageManager { return new UnifiedStorageManagerImpl(config, options); }