Multi-cloud VM instance database with Cloudflare Workers - Linode, Vultr, AWS connector integration - D1 database with regions, instances, pricing - Query API with filtering, caching, pagination - Cron-based auto-sync (daily + 6-hourly) - Health monitoring endpoint Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
239 lines
6.9 KiB
TypeScript
239 lines
6.9 KiB
TypeScript
/**
|
|
* Instance Types Repository
|
|
* Handles CRUD operations for VM instance types
|
|
*/
|
|
|
|
import { BaseRepository } from './base';
|
|
import { InstanceType, InstanceTypeInput, InstanceFamily, RepositoryError, ErrorCodes } from '../types';
|
|
|
|
export class InstancesRepository extends BaseRepository<InstanceType> {
|
|
protected tableName = 'instance_types';
|
|
|
|
/**
|
|
* Find all instance types for a specific provider
|
|
*/
|
|
async findByProvider(providerId: number): Promise<InstanceType[]> {
|
|
try {
|
|
const result = await this.db
|
|
.prepare('SELECT * FROM instance_types WHERE provider_id = ?')
|
|
.bind(providerId)
|
|
.all<InstanceType>();
|
|
|
|
return result.results;
|
|
} catch (error) {
|
|
console.error('[InstancesRepository] findByProvider failed:', error);
|
|
throw new RepositoryError(
|
|
`Failed to find instance types for provider: ${providerId}`,
|
|
ErrorCodes.DATABASE_ERROR,
|
|
error
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Find instance types by family
|
|
*/
|
|
async findByFamily(family: InstanceFamily): Promise<InstanceType[]> {
|
|
try {
|
|
const result = await this.db
|
|
.prepare('SELECT * FROM instance_types WHERE instance_family = ?')
|
|
.bind(family)
|
|
.all<InstanceType>();
|
|
|
|
return result.results;
|
|
} catch (error) {
|
|
console.error('[InstancesRepository] findByFamily failed:', error);
|
|
throw new RepositoryError(
|
|
`Failed to find instance types by family: ${family}`,
|
|
ErrorCodes.DATABASE_ERROR,
|
|
error
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Find an instance type by provider ID and instance ID
|
|
*/
|
|
async findByInstanceId(providerId: number, instanceId: string): Promise<InstanceType | null> {
|
|
try {
|
|
const result = await this.db
|
|
.prepare('SELECT * FROM instance_types WHERE provider_id = ? AND instance_id = ?')
|
|
.bind(providerId, instanceId)
|
|
.first<InstanceType>();
|
|
|
|
return result || null;
|
|
} catch (error) {
|
|
console.error('[InstancesRepository] findByInstanceId failed:', error);
|
|
throw new RepositoryError(
|
|
`Failed to find instance type: ${instanceId}`,
|
|
ErrorCodes.DATABASE_ERROR,
|
|
error
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Bulk upsert instance types for a provider
|
|
* Uses batch operations for efficiency
|
|
*/
|
|
async upsertMany(providerId: number, instances: InstanceTypeInput[]): Promise<number> {
|
|
if (instances.length === 0) {
|
|
return 0;
|
|
}
|
|
|
|
try {
|
|
// Build upsert statements for each instance type
|
|
const statements = instances.map((instance) => {
|
|
return this.db.prepare(
|
|
`INSERT INTO instance_types (
|
|
provider_id, instance_id, instance_name, vcpu, memory_mb,
|
|
storage_gb, transfer_tb, network_speed_gbps, gpu_count,
|
|
gpu_type, instance_family, metadata
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
ON CONFLICT(provider_id, instance_id)
|
|
DO UPDATE SET
|
|
instance_name = excluded.instance_name,
|
|
vcpu = excluded.vcpu,
|
|
memory_mb = excluded.memory_mb,
|
|
storage_gb = excluded.storage_gb,
|
|
transfer_tb = excluded.transfer_tb,
|
|
network_speed_gbps = excluded.network_speed_gbps,
|
|
gpu_count = excluded.gpu_count,
|
|
gpu_type = excluded.gpu_type,
|
|
instance_family = excluded.instance_family,
|
|
metadata = excluded.metadata`
|
|
).bind(
|
|
providerId,
|
|
instance.instance_id,
|
|
instance.instance_name,
|
|
instance.vcpu,
|
|
instance.memory_mb,
|
|
instance.storage_gb,
|
|
instance.transfer_tb || null,
|
|
instance.network_speed_gbps || null,
|
|
instance.gpu_count,
|
|
instance.gpu_type || null,
|
|
instance.instance_family || null,
|
|
instance.metadata || null
|
|
);
|
|
});
|
|
|
|
const results = await this.executeBatch(statements);
|
|
|
|
// Count successful operations
|
|
const successCount = results.reduce(
|
|
(sum, result) => sum + (result.meta.changes ?? 0),
|
|
0
|
|
);
|
|
|
|
console.log(`[InstancesRepository] Upserted ${successCount} instance types for provider ${providerId}`);
|
|
return successCount;
|
|
} catch (error) {
|
|
console.error('[InstancesRepository] upsertMany failed:', error);
|
|
throw new RepositoryError(
|
|
`Failed to upsert instance types for provider: ${providerId}`,
|
|
ErrorCodes.TRANSACTION_FAILED,
|
|
error
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Find GPU instances only
|
|
*/
|
|
async findGpuInstances(providerId?: number): Promise<InstanceType[]> {
|
|
try {
|
|
let query = 'SELECT * FROM instance_types WHERE gpu_count > 0';
|
|
const params: any[] = [];
|
|
|
|
if (providerId !== undefined) {
|
|
query += ' AND provider_id = ?';
|
|
params.push(providerId);
|
|
}
|
|
|
|
const result = await this.db
|
|
.prepare(query)
|
|
.bind(...params)
|
|
.all<InstanceType>();
|
|
|
|
return result.results;
|
|
} catch (error) {
|
|
console.error('[InstancesRepository] findGpuInstances failed:', error);
|
|
throw new RepositoryError(
|
|
'Failed to find GPU instances',
|
|
ErrorCodes.DATABASE_ERROR,
|
|
error
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Search instances by specifications
|
|
*/
|
|
async search(criteria: {
|
|
providerId?: number;
|
|
minVcpu?: number;
|
|
maxVcpu?: number;
|
|
minMemoryMb?: number;
|
|
maxMemoryMb?: number;
|
|
family?: InstanceFamily;
|
|
hasGpu?: boolean;
|
|
}): Promise<InstanceType[]> {
|
|
try {
|
|
const conditions: string[] = [];
|
|
const params: any[] = [];
|
|
|
|
if (criteria.providerId !== undefined) {
|
|
conditions.push('provider_id = ?');
|
|
params.push(criteria.providerId);
|
|
}
|
|
|
|
if (criteria.minVcpu !== undefined) {
|
|
conditions.push('vcpu >= ?');
|
|
params.push(criteria.minVcpu);
|
|
}
|
|
|
|
if (criteria.maxVcpu !== undefined) {
|
|
conditions.push('vcpu <= ?');
|
|
params.push(criteria.maxVcpu);
|
|
}
|
|
|
|
if (criteria.minMemoryMb !== undefined) {
|
|
conditions.push('memory_mb >= ?');
|
|
params.push(criteria.minMemoryMb);
|
|
}
|
|
|
|
if (criteria.maxMemoryMb !== undefined) {
|
|
conditions.push('memory_mb <= ?');
|
|
params.push(criteria.maxMemoryMb);
|
|
}
|
|
|
|
if (criteria.family !== undefined) {
|
|
conditions.push('instance_family = ?');
|
|
params.push(criteria.family);
|
|
}
|
|
|
|
if (criteria.hasGpu !== undefined) {
|
|
conditions.push(criteria.hasGpu ? 'gpu_count > 0' : 'gpu_count = 0');
|
|
}
|
|
|
|
const whereClause = conditions.length > 0 ? ' WHERE ' + conditions.join(' AND ') : '';
|
|
const query = 'SELECT * FROM instance_types' + whereClause;
|
|
|
|
const result = await this.db
|
|
.prepare(query)
|
|
.bind(...params)
|
|
.all<InstanceType>();
|
|
|
|
return result.results;
|
|
} catch (error) {
|
|
console.error('[InstancesRepository] search failed:', error);
|
|
throw new RepositoryError(
|
|
'Failed to search instance types',
|
|
ErrorCodes.DATABASE_ERROR,
|
|
error
|
|
);
|
|
}
|
|
}
|
|
}
|