import type { RegionInput, InstanceTypeInput } from '../types'; import { logger } from '../utils/logger'; /** * Raw region data from provider API (before normalization) * Structure varies by provider */ export interface RawRegion { [key: string]: unknown; } /** * Raw instance type data from provider API (before normalization) * Structure varies by provider */ export interface RawInstanceType { [key: string]: unknown; } /** * Custom error class for connector operations * * @example * throw new ConnectorError('linode', 'fetchRegions', 500, 'API rate limit exceeded'); */ export class ConnectorError extends Error { constructor( public provider: string, public operation: string, public statusCode: number | undefined, message: string ) { super(message); this.name = 'ConnectorError'; } } /** * RateLimiter - Token Bucket algorithm implementation * * Controls API request rate to prevent hitting provider rate limits. * Tokens are consumed for each request and refilled at a fixed rate. * * @example * const limiter = new RateLimiter(10, 2); // 10 tokens, refill 2 per second * await limiter.waitForToken(); // Wait until token is available * // Make API call */ export class RateLimiter { private tokens: number; private lastRefillTime: number; /** * Create a new rate limiter * * @param maxTokens - Maximum number of tokens in the bucket * @param refillRate - Number of tokens to refill per second */ constructor( private readonly maxTokens: number, private readonly refillRate: number ) { this.tokens = maxTokens; this.lastRefillTime = Date.now(); logger.debug('[RateLimiter] Initialized', { maxTokens, refillRate }); } /** * Wait until a token is available, then consume it * Automatically refills tokens based on elapsed time * * @returns Promise that resolves when a token is available */ async waitForToken(): Promise { this.refillTokens(); // If no tokens available, wait until next refill while (this.tokens < 1) { const timeUntilNextToken = (1 / this.refillRate) * 1000; // ms per token await this.sleep(timeUntilNextToken); this.refillTokens(); } // Consume one token this.tokens -= 1; logger.debug('[RateLimiter] Token consumed', { remaining: this.tokens }); } /** * Get the current number of available tokens * * @returns Number of available tokens (may include fractional tokens) */ getAvailableTokens(): number { this.refillTokens(); return this.tokens; } /** * Refill tokens based on elapsed time * Tokens are added proportionally to the time elapsed since last refill */ private refillTokens(): void { const now = Date.now(); const elapsedSeconds = (now - this.lastRefillTime) / 1000; const tokensToAdd = elapsedSeconds * this.refillRate; // Add tokens, capped at maxTokens this.tokens = Math.min(this.maxTokens, this.tokens + tokensToAdd); this.lastRefillTime = now; } /** * Sleep for specified milliseconds * * @param ms - Milliseconds to sleep */ private sleep(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)); } } /** * Note: CloudConnector base class has been deprecated. * Each connector now uses Env directly for credentials instead of Vault. * See LinodeConnector, VultrConnector, AWSConnector for current implementation patterns. */