/** * Anvil Instances Repository * Handles CRUD operations for Anvil-branded instance specifications */ import { BaseRepository } from './base'; import { AnvilInstance, AnvilInstanceInput, RepositoryError, ErrorCodes } from '../types'; export class AnvilInstancesRepository extends BaseRepository { protected tableName = 'anvil_instances'; protected allowedColumns = [ 'name', 'display_name', 'category', 'vcpus', 'memory_gb', 'disk_gb', 'transfer_tb', 'network_gbps', 'gpu_model', 'gpu_vram_gb', 'active', ]; /** * Find an instance by name (e.g., "anvil-1g-1c") */ async findByName(name: string): Promise { try { const result = await this.db .prepare('SELECT * FROM anvil_instances WHERE name = ?') .bind(name) .first(); return result || null; } catch (error) { this.logger.error('findByName failed', { name, error: error instanceof Error ? error.message : 'Unknown error' }); throw new RepositoryError( `Failed to find Anvil instance by name: ${name}`, ErrorCodes.DATABASE_ERROR, error ); } } /** * Find all instances by category */ async findByCategory(category: 'vm' | 'gpu' | 'g8' | 'vpu'): Promise { try { const result = await this.db .prepare('SELECT * FROM anvil_instances WHERE category = ? ORDER BY name') .bind(category) .all(); return result.results; } catch (error) { this.logger.error('findByCategory failed', { category, error: error instanceof Error ? error.message : 'Unknown error' }); throw new RepositoryError( `Failed to find Anvil instances by category: ${category}`, ErrorCodes.DATABASE_ERROR, error ); } } /** * Get all active instances */ async findActive(category?: 'vm' | 'gpu' | 'g8' | 'vpu'): Promise { try { let query = 'SELECT * FROM anvil_instances WHERE active = 1'; const params: (string | number | boolean | null)[] = []; if (category) { query += ' AND category = ?'; params.push(category); } query += ' ORDER BY name'; const result = await this.db .prepare(query) .bind(...params) .all(); return result.results; } catch (error) { this.logger.error('findActive failed', { category, error: error instanceof Error ? error.message : 'Unknown error' }); throw new RepositoryError( 'Failed to find active Anvil instances', ErrorCodes.DATABASE_ERROR, error ); } } /** * Search instances by resource requirements */ async searchByResources( minVcpus?: number, minMemoryGb?: number, minDiskGb?: number, category?: 'vm' | 'gpu' | 'g8' | 'vpu' ): Promise { try { const conditions: string[] = ['active = 1']; const params: (string | number | boolean | null)[] = []; if (minVcpus !== undefined) { conditions.push('vcpus >= ?'); params.push(minVcpus); } if (minMemoryGb !== undefined) { conditions.push('memory_gb >= ?'); params.push(minMemoryGb); } if (minDiskGb !== undefined) { conditions.push('disk_gb >= ?'); params.push(minDiskGb); } if (category) { conditions.push('category = ?'); params.push(category); } const query = 'SELECT * FROM anvil_instances WHERE ' + conditions.join(' AND ') + ' ORDER BY memory_gb, vcpus'; const result = await this.db .prepare(query) .bind(...params) .all(); return result.results; } catch (error) { this.logger.error('searchByResources failed', { minVcpus, minMemoryGb, minDiskGb, category, error: error instanceof Error ? error.message : 'Unknown error' }); throw new RepositoryError( 'Failed to search Anvil instances by resources', ErrorCodes.DATABASE_ERROR, error ); } } /** * Update instance active status */ async updateActive(id: number, active: boolean): Promise { try { const result = await this.db .prepare('UPDATE anvil_instances SET active = ? WHERE id = ? RETURNING *') .bind(active ? 1 : 0, id) .first(); if (!result) { throw new RepositoryError( `Anvil instance not found: ${id}`, ErrorCodes.NOT_FOUND ); } return result; } catch (error) { this.logger.error('updateActive failed', { id, active, error: error instanceof Error ? error.message : 'Unknown error' }); if (error instanceof RepositoryError) { throw error; } throw new RepositoryError( `Failed to update Anvil instance active status: ${id}`, ErrorCodes.DATABASE_ERROR, error ); } } /** * Bulk upsert instances * Uses batch operations for efficiency */ async upsertMany(instances: AnvilInstanceInput[]): Promise { if (instances.length === 0) { return 0; } try { const statements = instances.map((instance) => { return this.db.prepare( `INSERT INTO anvil_instances ( name, display_name, category, vcpus, memory_gb, disk_gb, transfer_tb, network_gbps, gpu_model, gpu_vram_gb, active ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(name) DO UPDATE SET display_name = excluded.display_name, category = excluded.category, vcpus = excluded.vcpus, memory_gb = excluded.memory_gb, disk_gb = excluded.disk_gb, transfer_tb = excluded.transfer_tb, network_gbps = excluded.network_gbps, gpu_model = excluded.gpu_model, gpu_vram_gb = excluded.gpu_vram_gb, active = excluded.active` ).bind( instance.name, instance.display_name, instance.category, instance.vcpus, instance.memory_gb, instance.disk_gb, instance.transfer_tb, instance.network_gbps, instance.gpu_model, instance.gpu_vram_gb, instance.active ); }); const successCount = await this.executeBatchCount(statements); this.logger.info('Upserted Anvil instances', { count: successCount }); return successCount; } catch (error) { this.logger.error('upsertMany failed', { count: instances.length, error: error instanceof Error ? error.message : 'Unknown error' }); throw new RepositoryError( 'Failed to upsert Anvil instances', ErrorCodes.TRANSACTION_FAILED, error ); } } }