@sailboat-computer/data-storage
Version:
Shared data storage library for sailboat computer v3
524 lines • 19.3 kB
JavaScript
;
/**
* InfluxDB storage provider implementation
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.createInfluxDBProvider = exports.InfluxDBStorageProvider = void 0;
const types_1 = require("../../types");
const errors_1 = require("../../utils/errors");
/**
* InfluxDB storage provider implementation
*/
class InfluxDBStorageProvider {
/**
* Create a new InfluxDB storage provider
*
* @param config - InfluxDB configuration
*/
constructor(config) {
this.config = config;
this.initialized = false;
this.lastOperation = new Date();
this.metrics = {
operationsPerSecond: 0,
averageLatency: 0,
errorRate: 0,
averageQueryTime: 0,
queriesPerSecond: 0
};
}
/**
* Initialize the provider
*/
async initialize() {
if (this.initialized) {
return;
}
try {
// In a real implementation, this would create an InfluxDB client
this.client = {
connect: async () => { },
write: async (point) => { },
query: async (query) => [],
shutdown: async () => { },
health: async () => ({ status: 'pass' }),
getStatus: async () => ({ up: true })
};
// Connect to InfluxDB
await this.client.connect();
this.initialized = true;
console.log('InfluxDB provider initialized');
}
catch (error) {
console.error('Failed to initialize InfluxDB provider:', error);
throw new errors_1.StorageError(errors_1.StorageErrorCode.CONNECTION_FAILED, 'Failed to initialize InfluxDB provider', { error });
}
}
/**
* Store data
*
* @param data - Data to store
* @param metadata - Data metadata
* @returns Data ID
*/
async store(data, metadata) {
this.ensureInitialized();
try {
const id = metadata.id || crypto.randomUUID();
// In a real implementation, this would create an InfluxDB point
const point = {
measurement: metadata.category,
tags: {
id,
...metadata.tags
},
fields: {
data: JSON.stringify(data),
metadata: JSON.stringify({
...metadata,
id
})
},
timestamp: new Date(metadata.timestamp)
};
// Write point to InfluxDB
await this.client.write(point);
return id;
}
catch (error) {
console.error('Failed to store data in InfluxDB:', error);
throw new errors_1.StorageError(errors_1.StorageErrorCode.STORE_FAILED, 'Failed to store data in InfluxDB', { error });
}
}
/**
* Retrieve data
*
* @param query - Data query
* @returns Stored data
*/
async retrieve(query) {
this.ensureInitialized();
try {
// Build Flux query
let fluxQuery = `from(bucket: "${this.config.bucket}")`;
// Add measurement filter
if (query.category) {
fluxQuery += `\n |> filter(fn: (r) => r._measurement == "${query.category}")`;
}
// Add ID filter
if (query.id) {
fluxQuery += `\n |> filter(fn: (r) => r.id == "${query.id}")`;
}
// Add time range filter
if (query.timeRange) {
const start = query.timeRange.start || '-30d';
const end = query.timeRange.end || 'now()';
fluxQuery += `\n |> range(start: ${start}, stop: ${end})`;
}
else {
fluxQuery += `\n |> range(start: -30d)`;
}
// Add tag filters
if (query.tags) {
for (const [key, value] of Object.entries(query.tags)) {
fluxQuery += `\n |> filter(fn: (r) => r.${key} == "${value}")`;
}
}
// Execute query
const result = await this.client.query(fluxQuery);
// Process results
const data = [];
for (const row of result) {
try {
const parsedData = JSON.parse(row.data);
const parsedMetadata = JSON.parse(row.metadata);
data.push({
data: parsedData,
metadata: parsedMetadata
});
}
catch (error) {
console.warn('Failed to parse data from InfluxDB:', error);
}
}
// Apply sorting
if (query.sort) {
data.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 (query.offset || query.limit) {
const offset = query.offset || 0;
const limit = query.limit || data.length;
return data.slice(offset, offset + limit);
}
return data;
}
catch (error) {
console.error('Failed to retrieve data from InfluxDB:', error);
throw new errors_1.StorageError(errors_1.StorageErrorCode.RETRIEVE_FAILED, 'Failed to retrieve data from InfluxDB', { error });
}
}
/**
* Update data
*
* @param data - Data to update
*/
async update(data) {
this.ensureInitialized();
try {
const id = data.metadata.id;
if (!id) {
throw new Error('Data ID is required for update');
}
// In InfluxDB, we can't update data directly, so we delete and re-insert
// First, retrieve the existing data to check if it exists
const existingData = await this.retrieve({ id });
if (existingData.length === 0) {
throw new Error(`Data with ID ${id} not found`);
}
// Delete existing data (not a real implementation, just a placeholder)
await this.delete(id);
// Store new data
await this.store(data.data, data.metadata);
}
catch (error) {
console.error('Failed to update data in InfluxDB:', error);
throw new errors_1.StorageError(errors_1.StorageErrorCode.UPDATE_FAILED, 'Failed to update data in InfluxDB', { error });
}
}
/**
* Delete data
*
* @param id - Data ID
*/
async delete(id) {
this.ensureInitialized();
try {
// Build Flux delete query
const fluxQuery = `
from(bucket: "${this.config.bucket}")
|> filter(fn: (r) => r.id == "${id}")
|> drop()
`;
// Execute query
await this.client.query(fluxQuery);
}
catch (error) {
console.error('Failed to delete data from InfluxDB:', error);
throw new errors_1.StorageError(errors_1.StorageErrorCode.DELETE_FAILED, 'Failed to delete data from InfluxDB', { error });
}
}
/**
* Store multiple data items
*
* @param items - Data items to store
* @returns Data IDs
*/
async storeBatch(items) {
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 InfluxDB:', error);
throw new errors_1.StorageError(errors_1.StorageErrorCode.STORE_FAILED, 'Failed to store batch data in InfluxDB', { error });
}
}
/**
* Clean up old data
*
* @param category - Data category
* @param retentionDays - Retention period in days
* @returns Cleanup result
*/
async cleanup(category, retentionDays) {
this.ensureInitialized();
try {
// Build Flux delete query
const fluxQuery = `
from(bucket: "${this.config.bucket}")
|> filter(fn: (r) => r._measurement == "${category}")
|> filter(fn: (r) => r._time < ${-retentionDays}d)
|> drop()
`;
// Execute query
await this.client.query(fluxQuery);
// In a real implementation, we would get the number of deleted points
return {
itemsRemoved: 0,
bytesFreed: 0,
duration: 0
};
}
catch (error) {
console.error('Failed to clean up data in InfluxDB:', error);
throw new errors_1.StorageError(errors_1.StorageErrorCode.CLEANUP_FAILED, 'Failed to clean up data in InfluxDB', { error });
}
}
/**
* Get storage status
*
* @returns Storage status
*/
async getStatus() {
try {
// Get InfluxDB health
const health = await this.client.health();
const status = await this.client.getStatus();
return {
connected: status.up,
healthy: health.status === 'pass',
lastOperation: this.lastOperation,
metrics: {
operationsPerSecond: this.metrics.operationsPerSecond,
averageLatency: this.metrics.averageLatency,
errorRate: this.metrics.errorRate,
storageUsed: 0,
storageAvailable: 0
},
queryPerformance: {
hot: {
averageQueryTime: 0,
queriesPerSecond: 0
},
warm: {
averageQueryTime: this.metrics.averageQueryTime,
queriesPerSecond: this.metrics.queriesPerSecond
},
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: this.metrics.averageQueryTime,
queriesPerSecond: this.metrics.queriesPerSecond
},
cold: {
averageQueryTime: 0,
queriesPerSecond: 0
},
overall: types_1.HealthStatus.HEALTHY,
providers: {
hot: types_1.HealthStatus.HEALTHY,
warm: types_1.HealthStatus.HEALTHY,
cold: types_1.HealthStatus.HEALTHY
},
issues: {
severity: types_1.AlertSeverity.INFO,
message: 'InfluxDB storage',
component: 'influxdb',
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 InfluxDB status:', error);
return {
connected: false,
healthy: false,
error: `Failed to get InfluxDB 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: types_1.HealthStatus.UNHEALTHY,
providers: {
hot: types_1.HealthStatus.UNHEALTHY,
warm: types_1.HealthStatus.UNHEALTHY,
cold: types_1.HealthStatus.UNHEALTHY
},
issues: {
severity: types_1.AlertSeverity.ALARM,
message: `Failed to get InfluxDB status: ${error}`,
component: 'influxdb',
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() {
if (!this.initialized) {
return;
}
try {
await this.client.shutdown();
this.initialized = false;
console.log('InfluxDB provider closed');
}
catch (error) {
console.error('Failed to close InfluxDB provider:', error);
throw new errors_1.StorageError(errors_1.StorageErrorCode.CONNECTION_FAILED, 'Failed to close InfluxDB provider', { error });
}
}
/**
* Ensure provider is initialized
*
* @throws StorageError if not initialized
*/
ensureInitialized() {
if (!this.initialized) {
throw new errors_1.StorageError(errors_1.StorageErrorCode.CONNECTION_FAILED, 'InfluxDB provider not initialized', {});
}
}
}
exports.InfluxDBStorageProvider = InfluxDBStorageProvider;
/**
* Create a new InfluxDB storage provider
*
* @param config - InfluxDB configuration
* @returns InfluxDB storage provider
*/
function createInfluxDBProvider(config) {
return new InfluxDBStorageProvider(config);
}
exports.createInfluxDBProvider = createInfluxDBProvider;
//# sourceMappingURL=influxdb.js.map