@sailboat-computer/data-storage
Version:
Shared data storage library for sailboat computer v3
370 lines (322 loc) • 10.8 kB
text/typescript
/**
* 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;
}