Files
cloud-server/src/types.ts
kappa de790988b4 refactor: code review 기반 품질 개선
- HonoVariables 타입 중앙화 (types.ts로 추출, 5개 파일 중복 제거)
- 6시간 pricing update cron 핸들러 추가 (syncPricingOnly 메서드)
- Response.json() → c.json() 패턴 통일 (Hono 표준)
- SORT_FIELD_MAP 중앙화 (constants.ts, 12개 필드 지원)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 10:58:27 +09:00

575 lines
16 KiB
TypeScript

// ============================================================
// Database Entity Types
// ============================================================
export interface Provider {
id: number;
name: string;
display_name: string;
api_base_url: string | null;
last_sync_at: string | null; // ISO 8601 datetime
sync_status: 'pending' | 'syncing' | 'success' | 'error';
sync_error: string | null;
created_at: string;
updated_at: string;
}
export interface Region {
id: number;
provider_id: number;
region_code: string;
region_name: string;
country_code: string | null; // ISO 3166-1 alpha-2
latitude: number | null;
longitude: number | null;
available: number; // SQLite boolean (0/1)
created_at: string;
updated_at: string;
}
export type InstanceFamily = 'general' | 'compute' | 'memory' | 'storage' | 'gpu';
export interface InstanceType {
id: number;
provider_id: number;
instance_id: string;
instance_name: string;
vcpu: number;
memory_mb: number;
storage_gb: number;
transfer_tb: number | null;
network_speed_gbps: number | null;
gpu_count: number;
gpu_type: string | null;
instance_family: InstanceFamily | null;
metadata: string | null; // JSON string
created_at: string;
updated_at: string;
}
export interface Pricing {
id: number;
instance_type_id: number;
region_id: number;
hourly_price: number;
monthly_price: number;
currency: string;
available: number; // SQLite boolean (0/1)
created_at: string;
updated_at: string;
}
export interface PriceHistory {
id: number;
pricing_id: number;
hourly_price: number;
monthly_price: number;
recorded_at: string;
}
export interface GpuInstance {
id: number;
provider_id: number;
instance_id: string;
instance_name: string;
vcpu: number;
memory_mb: number;
storage_gb: number;
transfer_tb: number | null;
network_speed_gbps: number | null;
gpu_count: number;
gpu_type: string;
gpu_memory_gb: number | null;
metadata: string | null; // JSON string
created_at: string;
updated_at: string;
}
export interface GpuPricing {
id: number;
gpu_instance_id: number;
region_id: number;
hourly_price: number;
monthly_price: number;
currency: string;
available: number; // SQLite boolean (0/1)
created_at: string;
updated_at: string;
}
export interface G8Instance {
id: number;
provider_id: number;
instance_id: string;
instance_name: string;
vcpu: number;
memory_mb: number;
storage_gb: number;
transfer_tb: number | null;
network_speed_gbps: number | null;
metadata: string | null; // JSON string
created_at: string;
updated_at: string;
}
export interface G8Pricing {
id: number;
g8_instance_id: number;
region_id: number;
hourly_price: number;
monthly_price: number;
currency: string;
available: number; // SQLite boolean (0/1)
created_at: string;
updated_at: string;
}
export interface VpuInstance {
id: number;
provider_id: number;
instance_id: string;
instance_name: string;
vcpu: number;
memory_mb: number;
storage_gb: number;
transfer_tb: number | null;
network_speed_gbps: number | null;
vpu_type: string;
metadata: string | null; // JSON string
created_at: string;
updated_at: string;
}
export interface VpuPricing {
id: number;
vpu_instance_id: number;
region_id: number;
hourly_price: number;
monthly_price: number;
currency: string;
available: number; // SQLite boolean (0/1)
created_at: string;
updated_at: string;
}
// ============================================================
// Repository Input Types (for create/update operations)
// ============================================================
export type ProviderInput = Omit<Provider, 'id' | 'created_at' | 'updated_at'>;
export type RegionInput = Omit<Region, 'id' | 'created_at' | 'updated_at'>;
export type InstanceTypeInput = Omit<InstanceType, 'id' | 'created_at' | 'updated_at'>;
export type PricingInput = Omit<Pricing, 'id' | 'created_at' | 'updated_at'>;
export type GpuInstanceInput = Omit<GpuInstance, 'id' | 'created_at' | 'updated_at'>;
export type GpuPricingInput = Omit<GpuPricing, 'id' | 'created_at' | 'updated_at' | 'hourly_price_krw' | 'monthly_price_krw' | 'hourly_price_retail' | 'monthly_price_retail'>;
export type G8InstanceInput = Omit<G8Instance, 'id' | 'created_at' | 'updated_at'>;
export type G8PricingInput = Omit<G8Pricing, 'id' | 'created_at' | 'updated_at' | 'hourly_price_krw' | 'monthly_price_krw' | 'hourly_price_retail' | 'monthly_price_retail'>;
export type VpuInstanceInput = Omit<VpuInstance, 'id' | 'created_at' | 'updated_at'>;
export type VpuPricingInput = Omit<VpuPricing, 'id' | 'created_at' | 'updated_at' | 'hourly_price_krw' | 'monthly_price_krw' | 'hourly_price_retail' | 'monthly_price_retail'>;
// ============================================================
// Error Types
// ============================================================
export class RepositoryError extends Error {
constructor(
message: string,
public readonly code: string,
public readonly cause?: unknown
) {
super(message);
this.name = 'RepositoryError';
}
}
// Common error codes
export const ErrorCodes = {
NOT_FOUND: 'NOT_FOUND',
DUPLICATE: 'DUPLICATE',
CONSTRAINT_VIOLATION: 'CONSTRAINT_VIOLATION',
DATABASE_ERROR: 'DATABASE_ERROR',
TRANSACTION_FAILED: 'TRANSACTION_FAILED',
INVALID_INPUT: 'INVALID_INPUT',
VALIDATION_ERROR: 'VALIDATION_ERROR',
} as const;
// ============================================================
// Pagination Types
// ============================================================
export interface PaginationOptions {
limit?: number;
offset?: number;
}
export interface PaginatedResult<T> {
data: T[];
total: number;
limit: number;
offset: number;
hasMore: boolean;
}
// ============================================================
// API Query and Response Types
// ============================================================
/**
* Query parameters for instance search and filtering
*/
export interface InstanceQueryParams {
/** Provider filter (provider ID or name) */
provider?: string;
/** Region code filter */
region_code?: string;
/** Instance family filter */
family?: InstanceFamily;
/** Minimum vCPU count */
min_vcpu?: number;
/** Maximum vCPU count */
max_vcpu?: number;
/** Minimum memory in MB */
min_memory?: number;
/** Maximum memory in MB */
max_memory?: number;
/** Minimum hourly price */
min_price?: number;
/** Maximum hourly price */
max_price?: number;
/** Filter for GPU instances */
has_gpu?: boolean;
/** CPU architecture filter */
architecture?: string;
/** Sort field (e.g., 'hourly_price', 'vcpu', 'memory_mb') */
sort_by?: string;
/** Sort order ('asc' or 'desc') */
sort_order?: 'asc' | 'desc';
/** Page number for pagination (1-indexed) */
page?: number;
/** Number of results per page */
limit?: number;
/** Force total count calculation (performance optimization: only runs on first page by default) */
include_count?: boolean;
}
/**
* Combined instance data with pricing and relationships
*/
export interface InstanceData extends InstanceType {
/** Provider information */
provider: Provider;
/** Region information (nullable if no pricing data) */
region: Region | null;
/** Current pricing information (nullable if no pricing data) */
pricing: Pricing | null;
}
/**
* Paginated API response for instance queries
*/
export interface InstanceResponse {
/** Array of instance data */
data: InstanceData[];
/** Pagination metadata */
pagination: {
/** Current page number (1-indexed) */
current_page: number;
/** Total number of pages */
total_pages: number;
/** Number of results per page */
per_page: number;
/** Total number of results */
total_results: number;
/** Whether there is a next page */
has_next: boolean;
/** Whether there is a previous page */
has_previous: boolean;
};
/** Query execution metadata */
meta: {
/** Query execution time in milliseconds */
query_time_ms: number;
/** Applied filters summary */
filters_applied: Partial<InstanceQueryParams>;
};
}
// ============================================================
// Sync Report Types
// ============================================================
/**
* Synchronization report for a single provider
*/
export interface ProviderSyncResult {
/** Provider identifier */
provider: string;
/** Synchronization success status */
success: boolean;
/** Number of regions synced */
regions_synced: number;
/** Number of instances synced */
instances_synced: number;
/** Number of pricing records synced */
pricing_synced: number;
/** Sync duration in milliseconds */
duration_ms: number;
/** Error message if sync failed */
error?: string;
/** Detailed error information */
error_details?: Record<string, unknown>;
}
/**
* Complete synchronization report for all providers
*/
export interface SyncReport {
/** Overall sync success status */
success: boolean;
/** Sync start timestamp (ISO 8601) */
started_at: string;
/** Sync completion timestamp (ISO 8601) */
completed_at: string;
/** Total sync duration in milliseconds */
total_duration_ms: number;
/** Results for each provider */
providers: ProviderSyncResult[];
/** Summary statistics */
summary: {
/** Total number of providers synced */
total_providers: number;
/** Number of successful provider syncs */
successful_providers: number;
/** Number of failed provider syncs */
failed_providers: number;
/** Total regions synced across all providers */
total_regions: number;
/** Total instances synced across all providers */
total_instances: number;
/** Total pricing records synced across all providers */
total_pricing: number;
};
}
// ============================================================
// Health Check Types
// ============================================================
/**
* Health check response
*/
export interface HealthResponse {
/** Service health status */
status: 'healthy' | 'degraded' | 'unhealthy';
/** Service version */
version: string;
/** Response timestamp (ISO 8601) */
timestamp: string;
/** Database connection status */
database: {
/** Database connection status */
connected: boolean;
/** Database response time in milliseconds */
latency_ms?: number;
};
/** Uptime in seconds */
uptime_seconds: number;
/** Additional health metrics */
metrics?: {
/** Total number of instances in database */
total_instances?: number;
/** Total number of providers */
total_providers?: number;
/** Last successful sync timestamp (ISO 8601) */
last_sync_at?: string;
};
}
// ============================================================
// Cloudflare Worker Environment Types
// ============================================================
/**
* Cloudflare Worker environment bindings and variables
*/
export interface Env {
/** D1 Database binding */
DB: D1Database;
/** KV namespace for rate limiting */
RATE_LIMIT_KV: KVNamespace;
/** API key for request authentication */
API_KEY: string;
/** Provider API credentials (from Wrangler secrets) */
LINODE_API_TOKEN?: string;
VULTR_API_KEY?: string;
AWS_ACCESS_KEY_ID?: string;
AWS_SECRET_ACCESS_KEY?: string;
/** Batch size for synchronization operations */
SYNC_BATCH_SIZE?: string;
/** Cache TTL in seconds */
CACHE_TTL_SECONDS?: string;
/** Log level (debug, info, warn, error, none) - Controls logging verbosity */
LOG_LEVEL?: string;
/** CORS origin for Access-Control-Allow-Origin header */
CORS_ORIGIN?: string;
/** Environment (development|production) - Controls development-specific features */
ENVIRONMENT?: string;
}
/**
* Hono context variables
* Shared across all request contexts
*/
export interface HonoVariables {
/** Unique request ID for tracing */
requestId: string;
/** Authentication status (set by auth middleware) */
authenticated?: boolean;
}
// ============================================================
// Synchronization Types
// ============================================================
/**
* Synchronization stage enumeration
*/
export enum SyncStage {
/** Initial stage before sync starts */
IDLE = 'idle',
/** Initialization stage */
INIT = 'init',
/** Fetching regions from provider API */
FETCH_REGIONS = 'fetch_regions',
/** Fetching instance types from provider API */
FETCH_INSTANCES = 'fetch_instances',
/** Fetching pricing data from provider API */
FETCH_PRICING = 'fetch_pricing',
/** Normalizing and transforming data */
NORMALIZE = 'normalize',
/** Legacy alias for NORMALIZE */
NORMALIZE_DATA = 'normalize_data',
/** Storing data in database */
PERSIST = 'persist',
/** Legacy alias for PERSIST */
STORE_DATA = 'store_data',
/** Validation stage */
VALIDATE = 'validate',
/** Synchronizing Anvil pricing from source provider */
SYNC_ANVIL_PRICING = 'sync_anvil_pricing',
/** Sync completed successfully */
COMPLETE = 'complete',
/** Legacy alias for COMPLETE */
COMPLETED = 'completed',
/** Sync failed with error */
FAILED = 'failed',
}
/**
* Normalized data structure for batch database operations
*/
export interface NormalizedData {
/** Normalized region data ready for insertion */
regions: RegionInput[];
/** Normalized instance type data ready for insertion */
instances: InstanceTypeInput[];
/** Normalized pricing data ready for insertion */
pricing: PricingInput[];
}
// ============================================================
// Additional Utility Types
// ============================================================
/**
* Generic API error response
*/
export interface ApiError {
/** Error status code */
status: number;
/** Error type identifier */
error: string;
/** Human-readable error message */
message: string;
/** Additional error details */
details?: Record<string, unknown>;
/** Request timestamp (ISO 8601) */
timestamp: string;
/** Request path that caused the error */
path?: string;
}
// ============================================================
// Anvil Product Types
// ============================================================
/**
* Anvil Region - Anvil-branded regional datacenters
*/
export interface AnvilRegion {
id: number;
name: string; // "anvil-tyo1", "anvil-sel1"
display_name: string; // "Tokyo 1", "Seoul 1"
country_code: string; // "JP", "KR"
source_provider: string; // "linode", "vultr"
source_region_code: string; // "jp-tyo-3", "nrt"
source_region_id: number; // FK to regions.id
active: number; // SQLite boolean (0/1)
created_at: string;
updated_at: string;
}
/**
* Anvil Instance - Anvil-branded instance specifications
*/
export interface AnvilInstance {
id: number;
name: string; // "anvil-1g-1c"
display_name: string; // "Basic 1GB"
category: 'vm' | 'gpu' | 'g8' | 'vpu';
vcpus: number;
memory_gb: number;
disk_gb: number;
transfer_tb: number | null;
network_gbps: number | null;
gpu_model: string | null; // GPU-specific
gpu_vram_gb: number | null; // GPU-specific
active: number; // SQLite boolean (0/1)
created_at: string;
updated_at: string;
}
/**
* Anvil Pricing - Anvil retail pricing (USD only)
*/
export interface AnvilPricing {
id: number;
anvil_instance_id: number;
anvil_region_id: number;
hourly_price: number; // Retail price (USD)
monthly_price: number; // Retail price (USD)
source_instance_id: number | null; // FK to source tables
created_at: string;
updated_at: string;
}
/**
* Anvil Transfer Pricing - Data transfer pricing per region (USD only)
*/
export interface AnvilTransferPricing {
id: number;
anvil_region_id: number;
price_per_gb: number; // USD/GB
created_at: string;
updated_at: string;
}
// ============================================================
// Anvil Input Types (auto-derived, excluding id and timestamps)
// ============================================================
export type AnvilRegionInput = Omit<AnvilRegion, 'id' | 'created_at' | 'updated_at'>;
export type AnvilInstanceInput = Omit<AnvilInstance, 'id' | 'created_at' | 'updated_at'>;
export type AnvilPricingInput = Omit<AnvilPricing, 'id' | 'created_at' | 'updated_at'>;
export type AnvilTransferPricingInput = Omit<AnvilTransferPricing, 'id' | 'created_at' | 'updated_at'>;