- Remove KRW pricing calculations from all pricing tables - Simplify pricing table to store only wholesale USD prices - Simplify anvil_pricing to store only retail USD prices - Remove KRW environment variables (KRW_EXCHANGE_RATE, KRW_MARGIN_RATE) - Remove KRW functions from constants.ts - Update GPU/G8/VPU pricing repositories to match - Add Anvil tables and repositories for branded product support Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
8.0 KiB
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
regionstable viasource_region_id
Columns:
id,name(unique),display_name,country_codesource_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,categoryvcpus,memory_gb,disk_gbtransfer_tb,network_gbpsgpu_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:
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 namefindByCountry(countryCode: string)- Get all regions in a countryfindBySourceRegion(sourceRegionId: number)- Reverse lookupfindActive()- Get all active regionsupdateActive(id: number, active: boolean)- Toggle active statusupsertMany(regions: AnvilRegionInput[])- Bulk insert/update
AnvilInstancesRepository
findByName(name: string)- Find by instance namefindByCategory(category)- Filter by vm/gpu/g8/vpufindActive(category?)- Get active instances with optional category filtersearchByResources(minVcpus?, minMemoryGb?, minDiskGb?, category?)- Resource-based searchupdateActive(id: number, active: boolean)- Toggle active statusupsertMany(instances: AnvilInstanceInput[])- Bulk insert/update
AnvilPricingRepository
findByInstance(anvilInstanceId: number)- All pricing for an instancefindByRegion(anvilRegionId: number)- All pricing in a regionfindByInstanceAndRegion(instanceId, regionId)- Specific pricing recordfindActive(instanceId?, regionId?)- Active pricing with optional filterssearchByPriceRange(minHourly?, maxHourly?, minMonthly?, maxMonthly?)- Price range searchupdateActive(id: number, active: boolean)- Toggle active statusupsertMany(pricing: AnvilPricingInput[])- Bulk insert/update with auto KRW calculation
AnvilTransferPricingRepository
findByRegion(anvilRegionId: number)- Get transfer pricing for a regionfindActive()- Get all active transfer pricingupdateActive(id: number, active: boolean)- Toggle active statusupsertMany(pricing: AnvilTransferPricingInput[])- Bulk insert/update with KRW conversion
4. Repository Factory Updates
Updated RepositoryFactory class to include:
anvilRegions: AnvilRegionsRepositoryanvilInstances: AnvilInstancesRepositoryanvilPricing: AnvilPricingRepositoryanvilTransferPricing: 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
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
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)
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
// 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:
- Populate Instance Data: Create Anvil instance definitions (VM, GPU, G8, VPU tiers)
- Set Pricing: Map Anvil instances to source providers and set retail pricing
- API Endpoints: Create endpoints for querying Anvil products
- Sync Service: Implement sync logic to update costs from source providers
- 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