@sailboat-computer/data-storage
Version:
Shared data storage library for sailboat computer v3
544 lines (543 loc) • 20.1 kB
JavaScript
;
/**
* PostgreSQL storage provider implementation
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.createPostgreSQLProvider = exports.PostgreSQLStorageProvider = void 0;
const types_1 = require("../../types");
const errors_1 = require("../../utils/errors");
/**
* PostgreSQL storage provider implementation
*/
class PostgreSQLStorageProvider {
/**
* Create a new PostgreSQL storage provider
*
* @param config - PostgreSQL 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 a PostgreSQL client
this.client = {
connect: async () => { },
query: async (query, params) => ({ rows: [] }),
end: async () => { }
};
// Connect to PostgreSQL
await this.client.connect();
// Create tables if they don't exist
await this.createTables();
this.initialized = true;
console.log('PostgreSQL provider initialized');
}
catch (error) {
console.error('Failed to initialize PostgreSQL provider:', error);
throw new errors_1.StorageError(errors_1.StorageErrorCode.CONNECTION_FAILED, 'Failed to initialize PostgreSQL provider', { error });
}
}
/**
* Create tables if they don't exist
*/
async createTables() {
// In a real implementation, this would create the necessary tables
const createTableQuery = `
CREATE TABLE IF NOT EXISTS data (
id TEXT PRIMARY KEY,
category TEXT NOT NULL,
timestamp TIMESTAMP NOT NULL,
tags JSONB NOT NULL,
data JSONB NOT NULL,
metadata JSONB NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_data_category ON data (category);
CREATE INDEX IF NOT EXISTS idx_data_timestamp ON data (timestamp);
CREATE INDEX IF NOT EXISTS idx_data_tags ON data USING GIN (tags);
`;
await this.client.query(createTableQuery, []);
}
/**
* 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();
// Insert data into PostgreSQL
const query = `
INSERT INTO data (id, category, timestamp, tags, data, metadata)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (id) DO UPDATE
SET category = $2, timestamp = $3, tags = $4, data = $5, metadata = $6
`;
const params = [
id,
metadata.category,
new Date(metadata.timestamp),
JSON.stringify(metadata.tags),
JSON.stringify(data),
JSON.stringify({
...metadata,
id
})
];
await this.client.query(query, params);
return id;
}
catch (error) {
console.error('Failed to store data in PostgreSQL:', error);
throw new errors_1.StorageError(errors_1.StorageErrorCode.STORE_FAILED, 'Failed to store data in PostgreSQL', { error });
}
}
/**
* Retrieve data
*
* @param query - Data query
* @returns Stored data
*/
async retrieve(query) {
this.ensureInitialized();
try {
// Build SQL query
let sql = `SELECT * FROM data WHERE 1=1`;
const params = [];
let paramIndex = 1;
// Add category filter
if (query.category) {
sql += ` AND category = $${paramIndex++}`;
params.push(query.category);
}
// Add ID filter
if (query.id) {
sql += ` AND id = $${paramIndex++}`;
params.push(query.id);
}
// Add time range filter
if (query.timeRange) {
if (query.timeRange.start) {
sql += ` AND timestamp >= $${paramIndex++}`;
params.push(new Date(query.timeRange.start));
}
if (query.timeRange.end) {
sql += ` AND timestamp <= $${paramIndex++}`;
params.push(new Date(query.timeRange.end));
}
}
// Add tag filters
if (query.tags) {
for (const [key, value] of Object.entries(query.tags)) {
sql += ` AND tags->>'${key}' = $${paramIndex++}`;
params.push(value);
}
}
// Add sorting
if (query.sort) {
const field = query.sort.field === 'timestamp' ? 'timestamp' : `data->>'${query.sort.field}'`;
const order = query.sort.order === 'desc' ? 'DESC' : 'ASC';
sql += ` ORDER BY ${field} ${order}`;
}
else {
sql += ` ORDER BY timestamp DESC`;
}
// Add pagination
if (query.limit) {
sql += ` LIMIT $${paramIndex++}`;
params.push(query.limit);
if (query.offset) {
sql += ` OFFSET $${paramIndex++}`;
params.push(query.offset);
}
}
// Execute query
const result = await this.client.query(sql, params);
// Process results
return result.rows.map((row) => ({
data: row.data,
metadata: row.metadata
}));
}
catch (error) {
console.error('Failed to retrieve data from PostgreSQL:', error);
throw new errors_1.StorageError(errors_1.StorageErrorCode.RETRIEVE_FAILED, 'Failed to retrieve data from PostgreSQL', { 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');
}
// Update data in PostgreSQL
const query = `
UPDATE data
SET category = $2, timestamp = $3, tags = $4, data = $5, metadata = $6
WHERE id = $1
`;
const params = [
id,
data.metadata.category,
new Date(data.metadata.timestamp),
JSON.stringify(data.metadata.tags),
JSON.stringify(data.data),
JSON.stringify(data.metadata)
];
const result = await this.client.query(query, params);
if (result.rowCount === 0) {
throw new Error(`Data with ID ${id} not found`);
}
}
catch (error) {
console.error('Failed to update data in PostgreSQL:', error);
throw new errors_1.StorageError(errors_1.StorageErrorCode.UPDATE_FAILED, 'Failed to update data in PostgreSQL', { error });
}
}
/**
* Delete data
*
* @param id - Data ID
*/
async delete(id) {
this.ensureInitialized();
try {
// Delete data from PostgreSQL
const query = `DELETE FROM data WHERE id = $1`;
const result = await this.client.query(query, [id]);
if (result.rowCount === 0) {
throw new Error(`Data with ID ${id} not found`);
}
}
catch (error) {
console.error('Failed to delete data from PostgreSQL:', error);
throw new errors_1.StorageError(errors_1.StorageErrorCode.DELETE_FAILED, 'Failed to delete data from PostgreSQL', { 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 PostgreSQL:', error);
throw new errors_1.StorageError(errors_1.StorageErrorCode.STORE_FAILED, 'Failed to store batch data in PostgreSQL', { 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 {
// Delete old data from PostgreSQL
const query = `
DELETE FROM data
WHERE category = $1 AND timestamp < NOW() - INTERVAL '${retentionDays} days'
RETURNING id
`;
const result = await this.client.query(query, [category]);
return {
itemsRemoved: result.rowCount,
bytesFreed: 0, // In a real implementation, this would be calculated
duration: 0 // In a real implementation, this would be measured
};
}
catch (error) {
console.error('Failed to clean up data in PostgreSQL:', error);
throw new errors_1.StorageError(errors_1.StorageErrorCode.CLEANUP_FAILED, 'Failed to clean up data in PostgreSQL', { error });
}
}
/**
* Get storage status
*
* @returns Storage status
*/
async getStatus() {
try {
// Check if PostgreSQL is connected
const result = await this.client.query('SELECT 1', []);
const connected = result.rows.length > 0 && result.rows[0]['?column?'] === 1;
// Get database size
const sizeResult = await this.client.query(`
SELECT pg_database_size(current_database()) as size
`, []);
const storageUsed = sizeResult.rows.length > 0 ? parseInt(sizeResult.rows[0].size, 10) : 0;
return {
connected,
healthy: connected,
lastOperation: this.lastOperation,
metrics: {
operationsPerSecond: this.metrics.operationsPerSecond,
averageLatency: this.metrics.averageLatency,
errorRate: this.metrics.errorRate,
storageUsed,
storageAvailable: 0 // In a real implementation, this would be calculated
},
queryPerformance: {
hot: {
averageQueryTime: 0,
queriesPerSecond: 0
},
warm: {
averageQueryTime: 0,
queriesPerSecond: 0
},
cold: {
averageQueryTime: this.metrics.averageQueryTime,
queriesPerSecond: this.metrics.queriesPerSecond
},
pendingItems: 0,
averageBatchSize: 0,
totalBatches: 0,
failedBatches: 0,
itemsRemoved: 0,
bytesFreed: 0,
duration: 0,
hotStorageUsage: 0,
warmStorageUsage: 0,
coldStorageUsage: storageUsed,
totalStorageUsage: storageUsed,
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: this.metrics.averageQueryTime,
queriesPerSecond: this.metrics.queriesPerSecond
},
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: 'PostgreSQL storage',
component: 'postgresql',
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 PostgreSQL status:', error);
return {
connected: false,
healthy: false,
error: `Failed to get PostgreSQL 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 PostgreSQL status: ${error}`,
component: 'postgresql',
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.end();
this.initialized = false;
console.log('PostgreSQL provider closed');
}
catch (error) {
console.error('Failed to close PostgreSQL provider:', error);
throw new errors_1.StorageError(errors_1.StorageErrorCode.CONNECTION_FAILED, 'Failed to close PostgreSQL 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, 'PostgreSQL provider not initialized', {});
}
}
}
exports.PostgreSQLStorageProvider = PostgreSQLStorageProvider;
/**
* Create a new PostgreSQL storage provider
*
* @param config - PostgreSQL configuration
* @returns PostgreSQL storage provider
*/
function createPostgreSQLProvider(config) {
return new PostgreSQLStorageProvider(config);
}
exports.createPostgreSQLProvider = createPostgreSQLProvider;
//# sourceMappingURL=postgresql.js.map