# Anvil Product Tables Implementation ## Overview Implemented complete database schema and repositories for Anvil-branded cloud products, including regions, instances, pricing, and transfer pricing. ## Implementation Summary ### 1. Database Migration (004_anvil_tables.sql) Created 4 new tables with proper indexes, triggers, and foreign key constraints: #### anvil_regions - Maps Anvil-branded regions to source provider regions - Contains 6 initial regions (Tokyo 1-3, Osaka 1-2, Seoul 1) - Links to existing `regions` table via `source_region_id` **Columns:** - `id`, `name` (unique), `display_name`, `country_code` - `source_provider`, `source_region_code`, `source_region_id` (FK) - `active`, `created_at`, `updated_at` **Initial Data:** ``` anvil-tyo1 → Linode ap-northeast (region_id: 26) anvil-tyo2 → Linode jp-tyo-3 (region_id: 3) anvil-tyo3 → Vultr nrt (region_id: 323) anvil-osa1 → Linode jp-osa (region_id: 13) anvil-osa2 → Vultr itm (region_id: 314) anvil-sel1 → Vultr icn (region_id: 313) ``` #### anvil_instances - Defines Anvil product specifications - Supports categories: vm, gpu, g8, vpu - GPU-specific fields: `gpu_model`, `gpu_vram_gb` **Columns:** - `id`, `name` (unique), `display_name`, `category` - `vcpus`, `memory_gb`, `disk_gb` - `transfer_tb`, `network_gbps` - `gpu_model`, `gpu_vram_gb` (nullable) - `active`, `created_at`, `updated_at` #### anvil_pricing - Retail pricing with cost tracking - Auto-calculates KRW prices using exchange rates - Links to both Anvil instances/regions and source instances **Columns:** - `id`, `anvil_instance_id` (FK), `anvil_region_id` (FK) - `hourly_price`, `monthly_price` (retail USD) - `hourly_price_krw`, `monthly_price_krw` (retail KRW) - `cost_hourly`, `cost_monthly` (wholesale USD) - `source_instance_id` (optional FK to source tables) - `active`, `created_at`, `updated_at` - UNIQUE constraint on (anvil_instance_id, anvil_region_id) #### anvil_transfer_pricing - Data transfer pricing per region - KRW conversion support **Columns:** - `id`, `anvil_region_id` (FK) - `price_per_gb` (USD), `price_per_gb_krw` (KRW) - `included_tb` (reference only) - `active`, `created_at`, `updated_at` - UNIQUE constraint on anvil_region_id ### 2. TypeScript Types (src/types.ts) Added 8 new type definitions: ```typescript AnvilRegion AnvilInstance AnvilPricing AnvilTransferPricing AnvilRegionInput AnvilInstanceInput AnvilPricingInput AnvilTransferPricingInput ``` All Input types auto-derive from their base types, excluding `id`, `created_at`, and `updated_at`. ### 3. Repositories Created 4 repository classes following existing BaseRepository pattern: #### AnvilRegionsRepository - `findByName(name: string)` - Find by Anvil region name - `findByCountry(countryCode: string)` - Get all regions in a country - `findBySourceRegion(sourceRegionId: number)` - Reverse lookup - `findActive()` - Get all active regions - `updateActive(id: number, active: boolean)` - Toggle active status - `upsertMany(regions: AnvilRegionInput[])` - Bulk insert/update #### AnvilInstancesRepository - `findByName(name: string)` - Find by instance name - `findByCategory(category)` - Filter by vm/gpu/g8/vpu - `findActive(category?)` - Get active instances with optional category filter - `searchByResources(minVcpus?, minMemoryGb?, minDiskGb?, category?)` - Resource-based search - `updateActive(id: number, active: boolean)` - Toggle active status - `upsertMany(instances: AnvilInstanceInput[])` - Bulk insert/update #### AnvilPricingRepository - `findByInstance(anvilInstanceId: number)` - All pricing for an instance - `findByRegion(anvilRegionId: number)` - All pricing in a region - `findByInstanceAndRegion(instanceId, regionId)` - Specific pricing record - `findActive(instanceId?, regionId?)` - Active pricing with optional filters - `searchByPriceRange(minHourly?, maxHourly?, minMonthly?, maxMonthly?)` - Price range search - `updateActive(id: number, active: boolean)` - Toggle active status - `upsertMany(pricing: AnvilPricingInput[])` - Bulk insert/update with auto KRW calculation #### AnvilTransferPricingRepository - `findByRegion(anvilRegionId: number)` - Get transfer pricing for a region - `findActive()` - Get all active transfer pricing - `updateActive(id: number, active: boolean)` - Toggle active status - `upsertMany(pricing: AnvilTransferPricingInput[])` - Bulk insert/update with KRW conversion ### 4. Repository Factory Updates Updated `RepositoryFactory` class to include: - `anvilRegions: AnvilRegionsRepository` - `anvilInstances: AnvilInstancesRepository` - `anvilPricing: AnvilPricingRepository` - `anvilTransferPricing: AnvilTransferPricingRepository` All repositories use lazy singleton pattern for efficient caching. ### 5. KRW Pricing Support Pricing repositories automatically calculate KRW prices using environment variables: - `KRW_EXCHANGE_RATE` - Base USD to KRW conversion rate (default: 1450) - `KRW_VAT_RATE` - VAT multiplier (default: 1.1 for 10% VAT) - `KRW_MARKUP_RATE` - Markup multiplier (default: 1.1 for 10% markup) KRW prices are auto-calculated during `upsertMany()` operations. ## Database Schema ``` anvil_regions (1) ──< anvil_pricing (M) anvil_instances (1) ──< anvil_pricing (M) anvil_regions (1) ──< anvil_transfer_pricing (1) regions (1) ──< anvil_regions (M) [source mapping] ``` ## Deployment Status ✅ Migration 004 applied to remote database (2026-01-25) ✅ 17 queries executed successfully ✅ 6 initial regions inserted ✅ All 4 tables created with indexes and triggers ✅ TypeScript compilation successful ✅ Deployed to production (Version: 5905e5df-7265-4872-b05e-e3fe4b7d0619) ## Usage Examples ### Query Anvil Regions ```typescript const repos = new RepositoryFactory(env.DB, env); // Get all Japan regions const jpRegions = await repos.anvilRegions.findByCountry('JP'); // Returns: anvil-tyo1, anvil-tyo2, anvil-tyo3, anvil-osa1, anvil-osa2 // Find specific region const tyo1 = await repos.anvilRegions.findByName('anvil-tyo1'); ``` ### Create Anvil Instance ```typescript await repos.anvilInstances.create({ name: 'anvil-1g-1c', display_name: 'Basic 1GB', category: 'vm', vcpus: 1, memory_gb: 1, disk_gb: 25, transfer_tb: 1, network_gbps: 1, gpu_model: null, gpu_vram_gb: null, active: 1, }); ``` ### Set Pricing (Auto KRW Calculation) ```typescript await repos.anvilPricing.upsertMany([ { anvil_instance_id: 1, anvil_region_id: 1, hourly_price: 0.015, monthly_price: 10, cost_hourly: 0.012, cost_monthly: 8, source_instance_id: 123, active: 1, } ]); // KRW prices auto-calculated: // hourly_price_krw = 0.015 × 1450 × 1.1 × 1.1 ≈ 26.37 // monthly_price_krw = 10 × 1450 × 1.1 × 1.1 ≈ 17,545 ``` ### Search by Resources ```typescript // Find instances with at least 2 vCPUs and 4GB RAM const instances = await repos.anvilInstances.searchByResources( 2, // minVcpus 4, // minMemoryGb null, // minDiskGb 'vm' // category ); ``` ## Files Changed ### Created - `/migrations/004_anvil_tables.sql` - Database schema - `/src/repositories/anvil-regions.ts` - Regions repository - `/src/repositories/anvil-instances.ts` - Instances repository - `/src/repositories/anvil-pricing.ts` - Pricing repository - `/src/repositories/anvil-transfer-pricing.ts` - Transfer pricing repository - `/src/repositories/anvil-regions.test.ts` - Unit tests ### Modified - `/src/types.ts` - Added 8 new types - `/src/repositories/index.ts` - Added 4 new repository exports and factory getters ## Next Steps To complete the Anvil product implementation: 1. **Populate Instance Data**: Create Anvil instance definitions (VM, GPU, G8, VPU tiers) 2. **Set Pricing**: Map Anvil instances to source providers and set retail pricing 3. **API Endpoints**: Create endpoints for querying Anvil products 4. **Sync Service**: Implement sync logic to update costs from source providers 5. **Documentation**: Add API documentation for Anvil endpoints ## Testing Basic repository tests created in `anvil-regions.test.ts`. Additional test coverage recommended for: - Instance search and filtering - Pricing calculations and KRW conversion - Transfer pricing queries - Edge cases and error handling