import { logger } from '../observability/logger.js'; const PEERINGDB_BASE = 'https://www.peeringdb.com/api'; const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour const FETCH_TIMEOUT_MS = 5000; interface CacheEntry { value: T; expiresAt: number; } interface PeeringDbOrg { id: number; name: string; website: string; social_media: unknown[]; } interface PeeringDbNet { id: number; org_id: number; org: PeeringDbOrg; name: string; aka: string; website: string; asn: number; info_type: string; info_prefixes4: number; info_prefixes6: number; policy_general: string; status: string; } interface PeeringDbIx { id: number; name: string; name_long: string; city: string; country: string; website: string; status: string; } interface PeeringDbResponse { data: T[]; meta: Record; } // In-memory LRU-style cache (simple map with TTL) const cache = new Map>(); function getCached(key: string): T | null { const entry = cache.get(key) as CacheEntry | undefined; if (!entry) return null; if (Date.now() > entry.expiresAt) { cache.delete(key); return null; } return entry.value; } function setCached(key: string, value: T): void { // Evict old entries if cache grows large if (cache.size > 1000) { const now = Date.now(); for (const [k, v] of cache) { if (now > v.expiresAt) { cache.delete(k); } } } cache.set(key, { value, expiresAt: Date.now() + CACHE_TTL_MS }); } async function fetchPeeringDb(path: string): Promise> { const url = `${PEERINGDB_BASE}${path}`; const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS); try { const response = await fetch(url, { signal: controller.signal, headers: { Accept: 'application/json', 'User-Agent': 'llm-gateway/1.0 (github.com/public-maintainer/llm-gateway)', }, }); if (!response.ok) { throw new Error(`PeeringDB HTTP ${response.status}`); } return await response.json() as PeeringDbResponse; } finally { clearTimeout(timer); } } export async function lookupAsn(asn: number): Promise { const cacheKey = `asn:${asn}`; const cached = getCached(cacheKey); if (cached !== null || cache.has(cacheKey)) return cached; try { const result = await fetchPeeringDb(`/net?asn=${asn}&status=ok&depth=2`); const net = result.data[0] ?? null; setCached(cacheKey, net); return net; } catch (err) { logger.debug({ err, asn }, 'PeeringDB ASN lookup failed'); return null; } } export async function lookupIx(name: string): Promise { const cacheKey = `ix:${name.toLowerCase()}`; const cached = getCached(cacheKey); if (cached !== null || cache.has(cacheKey)) return cached; try { const result = await fetchPeeringDb(`/ix?name__icontains=${encodeURIComponent(name)}&status=ok`); const ix = result.data[0] ?? null; setCached(cacheKey, ix); return ix; } catch (err) { logger.debug({ err, name }, 'PeeringDB IX lookup failed'); return null; } } export async function lookupOrgByAsn(asn: number): Promise { const net = await lookupAsn(asn); if (!net) return null; return net.org ?? null; } export function clearCache(): void { cache.clear(); } export function getCacheSize(): number { return cache.size; }