@sailboat-computer/data-storage
Version:
Shared data storage library for sailboat computer v3
503 lines (465 loc) • 13.2 kB
text/typescript
/**
* Local file storage provider implementation
*/
import {
StorageProvider,
DataQuery,
StoredData,
CleanupResult,
StorageStatus,
DataMetadata,
HealthStatus,
AlertSeverity
} from '../../types';
import { StorageError, StorageErrorCode } from '../../utils/errors';
/**
* Local file provider options
*/
export interface LocalFileProviderOptions {
directory: string;
maxSizeBytes?: number;
encrypt?: boolean;
compressionLevel?: number;
}
/**
* Local file storage provider implementation
*/
export class LocalFileProvider implements StorageProvider {
private initialized = false;
private index: Record<string, string> = {}; // In-memory index of data
private indexPath: string;
private lastOperation: Date = new Date();
private metrics = {
operationsPerSecond: 0,
averageLatency: 0,
errorRate: 0,
averageQueryTime: 0,
queriesPerSecond: 0
};
/**
* Create a new local file storage provider
*
* @param options - Provider options
*/
constructor(private options: LocalFileProviderOptions) {
this.indexPath = `${options.directory}/index.json`;
}
/**
* Initialize the provider
*/
async initialize(): Promise<void> {
if (this.initialized) {
return;
}
try {
// In a real implementation, this would create the directory if it doesn't exist
// and load the index from disk
this.initialized = true;
console.log('Local file provider initialized');
} catch (error) {
console.error('Failed to initialize local file provider:', error);
throw new StorageError(
StorageErrorCode.CONNECTION_FAILED,
'Failed to initialize local file provider',
{ error }
);
}
}
/**
* Store data
*
* @param data - Data to store
* @param metadata - Data metadata
* @returns Data ID
*/
async store(data: any, metadata: DataMetadata): Promise<string> {
this.ensureInitialized();
try {
const id = metadata.id || crypto.randomUUID();
const filePath = `${this.options.directory}/${metadata.category}/${id}.json`;
// In a real implementation, this would create the directory if it doesn't exist
// and write the data to disk
// Update index
this.index[id] = filePath;
// In a real implementation, this would write the index to disk
return id;
} catch (error) {
console.error('Failed to store data in local file:', error);
throw new StorageError(
StorageErrorCode.STORE_FAILED,
'Failed to store data in local file',
{ error }
);
}
}
/**
* Retrieve data
*
* @param query - Data query
* @returns Stored data
*/
async retrieve(query: DataQuery): Promise<StoredData[]> {
this.ensureInitialized();
try {
// In a real implementation, this would read data from disk based on the query
// For now, just return an empty array
return [];
} catch (error) {
console.error('Failed to retrieve data from local file:', error);
throw new StorageError(
StorageErrorCode.RETRIEVE_FAILED,
'Failed to retrieve data from local file',
{ error }
);
}
}
/**
* Update data
*
* @param data - Data to update
*/
async update(data: StoredData): Promise<void> {
this.ensureInitialized();
try {
const id = data.metadata.id;
if (!id) {
throw new Error('Data ID is required for update');
}
// Check if data exists
if (!this.index[id]) {
throw new Error(`Data with ID ${id} not found`);
}
// In a real implementation, this would write the data to disk
} catch (error) {
console.error('Failed to update data in local file:', error);
throw new StorageError(
StorageErrorCode.UPDATE_FAILED,
'Failed to update data in local file',
{ error }
);
}
}
/**
* Delete data
*
* @param id - Data ID
*/
async delete(id: string): Promise<void> {
this.ensureInitialized();
try {
// Check if data exists
if (!this.index[id]) {
throw new Error(`Data with ID ${id} not found`);
}
// In a real implementation, this would delete the file from disk
// Update index
delete this.index[id];
// In a real implementation, this would write the index to disk
} catch (error) {
console.error('Failed to delete data from local file:', error);
throw new StorageError(
StorageErrorCode.DELETE_FAILED,
'Failed to delete data from local file',
{ error }
);
}
}
/**
* Store multiple data items
*
* @param items - Data items to store
* @returns Data IDs
*/
async storeBatch(items: Array<{data: any, metadata: DataMetadata}>): Promise<string[]> {
this.ensureInitialized();
try {
// Store each item
const ids = await Promise.all(items.map(item => this.store(item.data, item.metadata)));
return ids;
} catch (error) {
console.error('Failed to store batch data in local file:', error);
throw new StorageError(
StorageErrorCode.STORE_FAILED,
'Failed to store batch data in local file',
{ error }
);
}
}
/**
* Clean up old data
*
* @param category - Data category
* @param retentionDays - Retention period in days
* @returns Cleanup result
*/
async cleanup(category: string, retentionDays: number): Promise<CleanupResult> {
this.ensureInitialized();
try {
// In a real implementation, this would delete old files from disk
return {
itemsRemoved: 0,
bytesFreed: 0,
duration: 0
};
} catch (error) {
console.error('Failed to clean up data in local file:', error);
throw new StorageError(
StorageErrorCode.CLEANUP_FAILED,
'Failed to clean up data in local file',
{ error }
);
}
}
/**
* Get storage status
*
* @returns Storage status
*/
async getStatus(): Promise<StorageStatus> {
try {
// In a real implementation, this would get disk usage statistics
return {
connected: this.initialized,
healthy: this.initialized,
lastOperation: this.lastOperation,
metrics: {
operationsPerSecond: this.metrics.operationsPerSecond,
averageLatency: this.metrics.averageLatency,
errorRate: this.metrics.errorRate,
storageUsed: 0,
storageAvailable: this.options.maxSizeBytes || 0
},
queryPerformance: {
hot: {
averageQueryTime: this.metrics.averageQueryTime,
queriesPerSecond: this.metrics.queriesPerSecond
},
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: this.metrics.averageQueryTime,
queriesPerSecond: this.metrics.queriesPerSecond
},
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: 'Local file storage',
component: 'local-file',
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 local file status:', error);
return {
connected: false,
healthy: false,
error: `Failed to get local file 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 local file status: ${error}`,
component: 'local-file',
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: {}
}
}
}
};
}
}
/**
* Shutdown the provider
*/
async shutdown(): Promise<void> {
if (!this.initialized) {
return;
}
try {
// In a real implementation, this would flush any pending writes to disk
this.initialized = false;
console.log('Local file provider closed');
} catch (error) {
console.error('Failed to close local file provider:', error);
throw new StorageError(
StorageErrorCode.CONNECTION_FAILED,
'Failed to close local file provider',
{ error }
);
}
}
/**
* Ensure provider is initialized
*
* @throws StorageError if not initialized
*/
private ensureInitialized(): void {
if (!this.initialized) {
throw new StorageError(
StorageErrorCode.CONNECTION_FAILED,
'Local file provider not initialized',
{}
);
}
}
}
/**
* Create a new local file storage provider
*
* @param options - Provider options
* @returns Local file storage provider
*/
export function createLocalFileProvider(options: LocalFileProviderOptions): StorageProvider {
return new LocalFileProvider(options);
}