- 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>
575 lines
16 KiB
TypeScript
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'>;
|