UNPKG

@sailboat-computer/data-storage

Version:

Shared data storage library for sailboat computer v3

544 lines (543 loc) 20.1 kB
"use strict"; /** * 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