/** * Tiered Fetcher - Intelligent orchestration between rendering strategies * * Implements a cascade of rendering strategies from fastest to most capable: * * Tier 1: Content Intelligence (fastest, ~50-200ms) * - Framework data extraction (__NEXT_DATA__, etc.) * - Structured data (JSON-LD) * - API prediction * - Google Cache / Archive.org * - Static HTML parsing * - Best for: Most sites, especially modern frameworks * * Tier 2: Lightweight JS (~200-500ms) * - HTTP fetch + linkedom + Node VM script execution * - Handles basic JS-rendered content * - Best for: Sites that need simple JS but not full browser * * Tier 3: Full browser (slowest, ~2-5s, OPTIONAL) * - Playwright with full Chromium (if installed) * - Handles everything including anti-bot * - Best for: Complex SPAs, sites with anti-bot * - Gracefully skipped if Playwright not available * * The fetcher learns over time which tier works best for each domain. */ import { type ExtractionStrategy } from './content-intelligence.js'; import { BrowserManager, type Page } from './browser-manager.js'; import { ContentExtractor } from '../utils/content-extractor.js'; import { LearningEngine } from './learning-engine.js'; import type { NetworkRequest, ApiPattern, TierAttempt, AccessibilityTree } from '../types/index.js'; import { performanceTracker, type TimingBreakdown } from '../utils/performance-tracker.js'; export type RenderTier = 'intelligence' | 'lightweight' | 'playwright'; /** * Freshness requirement for content * - 'realtime': Always fetch fresh content, never use cache * - 'cached': Prefer cached content, only fetch if not in cache * - 'any': Use cache if available and not stale, otherwise fetch */ export type FreshnessRequirement = 'realtime' | 'cached' | 'any'; export interface TieredFetchOptions { forceTier?: RenderTier; minContentLength?: number; tierTimeout?: number; enableLearning?: boolean; headers?: Record; sessionProfile?: string; waitFor?: 'load' | 'domcontentloaded' | 'networkidle'; waitForSelector?: string; useRateLimiting?: boolean; maxLatencyMs?: number; maxCostTier?: RenderTier; freshnessRequirement?: FreshnessRequirement; debug?: { visible?: boolean; slowMotion?: number; screenshots?: boolean; consoleLogs?: boolean; }; includeAccessibilityTree?: boolean; includeAccessibilityBounds?: boolean; scrollToLoad?: boolean; } export interface TieredFetchResult { html: string; content: { markdown: string; text: string; title: string; structured?: Record; }; tier: RenderTier; extractionStrategy?: ExtractionStrategy; finalUrl: string; fellBack: boolean; tiersAttempted: RenderTier[]; tierAttempts: TierAttempt[]; tierReason: string; networkRequests: NetworkRequest[]; discoveredApis: ApiPattern[]; websocketConnections?: import('../types/websocket-patterns.js').WebSocketConnection[]; page?: Page; accessibilityTree?: AccessibilityTree; timing: { total: number; perTier: Record; breakdown?: TimingBreakdown; }; detection: { isStatic: boolean; isJSHeavy: boolean; needsFullBrowser: boolean; contentComplete: boolean; playwrightAvailable: boolean; }; budget?: { latencyExceeded: boolean; tiersSkipped: RenderTier[]; maxCostTierEnforced?: RenderTier; usedCache: boolean; freshnessApplied?: FreshnessRequirement; }; } export interface DomainPreference { domain: string; preferredTier: RenderTier; successCount: number; failureCount: number; lastUsed: number; avgResponseTime: number; } export declare class TieredFetcher { private contentIntelligence; private lightweightRenderer; private browserManager; private contentExtractor; private apiAnalyzer; private learningEngine; private domainPreferences; constructor(browserManager: BrowserManager, contentExtractor: ContentExtractor, learningEngine: LearningEngine); /** * Fetch a URL using the optimal tier */ fetch(url: string, options?: TieredFetchOptions): Promise; /** * Execute a specific tier */ private executeTier; /** * Tier 1: Content Intelligence (framework extraction, structured data, API prediction, caches) */ private executeIntelligence; /** * Tier 2: Lightweight JS */ private executeLightweight; /** * Tier 3: Full Playwright browser */ private executePlaywright; /** * Determine which tier to start with */ private determineStartingTier; /** * Get the order of tiers to try, respecting budget constraints * @param startTier - The tier to start from * @param maxCostTier - Maximum cost tier allowed (CX-005) * @returns Object containing tier order and any skipped tiers */ private getTierOrder; /** * Validate that the result has sufficient content * Returns detailed validation information for CX-003 decision trace */ private validateResult; /** * Record a successful fetch for learning */ private recordSuccess; /** * Record a failed fetch for learning */ private recordFailure; /** * Get statistics about tier usage */ getStats(): { totalDomains: number; byTier: Record; avgResponseTimes: Record; playwrightAvailable: boolean; }; /** * Get preference for a specific domain */ getDomainPreference(domain: string): DomainPreference | undefined; /** * Manually set tier preference for a domain */ setDomainPreference(domain: string, tier: RenderTier): void; /** * Export preferences for persistence */ exportPreferences(): DomainPreference[]; /** * Import preferences from persistence */ importPreferences(preferences: DomainPreference[]): void; /** * Clear all learned preferences */ clearPreferences(): void; /** * Get the performance tracker for metrics access */ getPerformanceTracker(): typeof performanceTracker; /** * PRI-014: Scroll page to load lazy content * Scrolls through the page to trigger lazy loading of content. * This ensures dynamically loaded elements are in the DOM before extraction. */ private scrollToLoadContent; } //# sourceMappingURL=tiered-fetcher.d.ts.map