UNPKG

@sailboat-computer/data-storage

Version:

Shared data storage library for sailboat computer v3

370 lines (322 loc) 10.8 kB
/** * Lifecycle manager implementation */ import { LifecycleManager, LifecycleManagerConfig, LifecyclePolicy, StorageManager, StorageTier, StorageTierType, DownsamplingEngine, StoredData, DownsamplingRule, MigrationHandler, DownsamplingHandler } from '../types'; import { StorageError, StorageErrorCode } from '../utils/errors'; /** * Lifecycle manager implementation */ export class LifecycleManagerImpl implements LifecycleManager { private config?: LifecycleManagerConfig; private cleanupInterval?: NodeJS.Timeout; private migrationInterval?: NodeJS.Timeout; private initialized = false; private beforeMigrationHandlers: Array<(data: StoredData, fromTier: StorageTierType, toTier: StorageTierType) => Promise<void>> = []; private afterMigrationHandlers: Array<(data: StoredData, fromTier: StorageTierType, toTier: StorageTierType) => Promise<void>> = []; private beforeDownsamplingHandlers: Array<(data: StoredData[], rule: DownsamplingRule) => Promise<void>> = []; private afterDownsamplingHandlers: Array<(data: StoredData[], rule: DownsamplingRule) => Promise<void>> = []; /** * Create a new lifecycle manager * * @param storageManager - Storage manager * @param downsamplingEngine - Downsampling engine */ constructor( private storageManager: StorageManager, private downsamplingEngine?: DownsamplingEngine ) {} /** * Initialize the lifecycle manager * * @param config - Lifecycle manager configuration */ initialize(config: LifecycleManagerConfig): void { if (this.initialized) { return; } this.config = config; // Start scheduled cleanup if (this.config.cleanupInterval > 0) { this.cleanupInterval = setInterval(() => { this.runCleanup().catch(error => { console.error('Error during scheduled cleanup:', error); }); }, this.config.cleanupInterval); } // Start scheduled migration if (this.config.migrationInterval > 0) { this.migrationInterval = setInterval(() => { this.runMigration().catch(error => { console.error('Error during scheduled migration:', error); }); }, this.config.migrationInterval); } // Initialize downsampling engine if provided if (this.downsamplingEngine && this.config.downsamplingRules) { this.downsamplingEngine.initialize(this.config.downsamplingRules); } this.initialized = true; console.log('Lifecycle manager initialized'); } /** * Run cleanup for all policies */ async runCleanup(): Promise<void> { this.ensureInitialized(); try { // Get all policies const policies = this.config!.policies; // Run cleanup for each policy for (const policy of policies) { await this.runCleanupForPolicy(policy); } } catch (error) { console.error('Failed to run cleanup:', error); throw new StorageError( StorageErrorCode.CLEANUP_FAILED, 'Failed to run cleanup', { error } ); } } /** * Run migration for all policies */ async runMigration(): Promise<void> { this.ensureInitialized(); try { // Get all policies const policies = this.config!.policies; // Run migration for each policy for (const policy of policies) { await this.runMigrationForPolicy(policy); } } catch (error) { console.error('Failed to run migration:', error); throw new StorageError( StorageErrorCode.MIGRATION_FAILED, 'Failed to run migration', { error } ); } } /** * Run downsampling for a specific rule * * @param ruleId - Downsampling rule ID */ async runDownsampling(ruleId: string): Promise<void> { this.ensureInitialized(); if (!this.downsamplingEngine) { throw new StorageError( StorageErrorCode.SYSTEM_ERROR, 'Downsampling engine not provided', {} ); } try { // Find rule const rule = this.config!.downsamplingRules.find(r => r.id === ruleId); if (!rule) { throw new Error(`Downsampling rule ${ruleId} not found`); } // Find policy for rule const policy = this.config!.policies.find(p => p.downsamplingRuleId === ruleId); if (!policy) { throw new Error(`No policy found for downsampling rule ${ruleId}`); } // Get data to downsample const data = await this.storageManager.retrieve({ category: policy.category }); if (data.length === 0) { console.log(`No data found for downsampling rule ${ruleId}`); return; } // Run downsampling const downsampledData = await this.downsamplingEngine.downsample(data); // Update data for (const item of downsampledData) { await this.storageManager.update(item); } console.log(`Downsampling completed for rule ${ruleId}`); } catch (error) { console.error(`Failed to run downsampling for rule ${ruleId}:`, error); throw new StorageError( StorageErrorCode.DOWNSAMPLING_FAILED, `Failed to run downsampling for rule ${ruleId}`, { ruleId, error } ); } } /** * Get all lifecycle policies * * @returns Lifecycle policies */ getPolicies(): LifecyclePolicy[] { this.ensureInitialized(); return this.config!.policies; } /** * Stop the lifecycle manager */ stop(): void { // Stop scheduled cleanup if (this.cleanupInterval) { clearInterval(this.cleanupInterval); this.cleanupInterval = undefined; } // Stop scheduled migration if (this.migrationInterval) { clearInterval(this.migrationInterval); this.migrationInterval = undefined; } console.log('Lifecycle manager stopped'); } /** * Register a handler to be called before migration * * @param handler - Migration handler */ onBeforeMigration(handler: MigrationHandler): void { this.beforeMigrationHandlers.push(handler); } /** * Register a handler to be called after migration * * @param handler - Migration handler */ onAfterMigration(handler: MigrationHandler): void { this.afterMigrationHandlers.push(handler); } /** * Register a handler to be called before downsampling * * @param handler - Downsampling handler */ onBeforeDownsampling(handler: DownsamplingHandler): void { this.beforeDownsamplingHandlers.push(handler); } /** * Register a handler to be called after downsampling * * @param handler - Downsampling handler */ onAfterDownsampling(handler: DownsamplingHandler): void { this.afterDownsamplingHandlers.push(handler); } /** * Run cleanup for a specific policy * * @param policy - Lifecycle policy */ private async runCleanupForPolicy(policy: LifecyclePolicy): Promise<void> { try { // Run cleanup for each tier if (policy.hotRetentionDays > 0) { await this.storageManager.cleanup(policy.category, StorageTier.HOT as StorageTierType, policy.hotRetentionDays); } if (policy.warmRetentionDays > 0) { await this.storageManager.cleanup(policy.category, StorageTier.WARM as StorageTierType, policy.warmRetentionDays); } if (policy.coldRetentionDays > 0) { await this.storageManager.cleanup(policy.category, StorageTier.COLD as StorageTierType, policy.coldRetentionDays); } console.log(`Cleanup completed for category ${policy.category}`); } catch (error) { console.error(`Failed to run cleanup for category ${policy.category}:`, error); throw new StorageError( StorageErrorCode.CLEANUP_FAILED, `Failed to run cleanup for category ${policy.category}`, { category: policy.category, error } ); } } /** * Run migration for a specific policy * * @param policy - Lifecycle policy */ private async runMigrationForPolicy(policy: LifecyclePolicy): Promise<void> { try { // Calculate migration thresholds const now = new Date(); const hotThreshold = new Date(now.getTime() - policy.migrationThresholdDays * 24 * 60 * 60 * 1000); // Find data to migrate from hot to warm const hotData = await this.storageManager.retrieve({ category: policy.category, timeRange: { end: hotThreshold.toISOString() } }, StorageTier.HOT as StorageTierType); // Migrate data from hot to warm for (const item of hotData) { await this.storageManager.migrate(item.metadata.id!, StorageTier.HOT as StorageTierType, StorageTier.WARM as StorageTierType); } // Find data to migrate from warm to cold const warmThreshold = new Date(now.getTime() - policy.warmRetentionDays * 24 * 60 * 60 * 1000); const warmData = await this.storageManager.retrieve({ category: policy.category, timeRange: { end: warmThreshold.toISOString() } }, StorageTier.WARM as StorageTierType); // Migrate data from warm to cold for (const item of warmData) { await this.storageManager.migrate(item.metadata.id!, StorageTier.WARM as StorageTierType, StorageTier.COLD as StorageTierType); } console.log(`Migration completed for category ${policy.category}`); } catch (error) { console.error(`Failed to run migration for category ${policy.category}:`, error); throw new StorageError( StorageErrorCode.MIGRATION_FAILED, `Failed to run migration for category ${policy.category}`, { category: policy.category, error } ); } } /** * Ensure lifecycle manager is initialized * * @throws StorageError if not initialized */ private ensureInitialized(): void { if (!this.initialized || !this.config) { throw new StorageError( StorageErrorCode.SYSTEM_ERROR, 'Lifecycle manager not initialized', {} ); } } } /** * Create a new lifecycle manager * * @param storageManager - Storage manager * @param config - Lifecycle manager configuration * @param downsamplingEngine - Downsampling engine (optional) * @returns Lifecycle manager */ export function createLifecycleManager( storageManager: StorageManager, config: LifecycleManagerConfig, downsamplingEngine?: DownsamplingEngine ): LifecycleManager { const manager = new LifecycleManagerImpl(storageManager, downsamplingEngine); manager.initialize(config); return manager; }