/** * Timeout wrapper utility for external API calls * Prevents jobs from hanging indefinitely on slow or unresponsive services */ export class TimeoutError extends Error { constructor( message: string, public readonly timeoutMs: number, ) { super(message); this.name = "TimeoutError"; } } /** * Wrap a promise with a timeout * @param promise The promise to wrap * @param timeoutMs Timeout in milliseconds * @param errorMessage Optional custom error message * @returns The promise result or throws TimeoutError */ export async function withTimeout( promise: Promise, timeoutMs: number, errorMessage?: string, ): Promise { let timeoutId: ReturnType | undefined; const timeoutPromise = new Promise((_, reject) => { timeoutId = setTimeout(() => { reject( new TimeoutError( errorMessage || `Operation timed out after ${timeoutMs}ms`, timeoutMs, ), ); }, timeoutMs); }); try { return await Promise.race([promise, timeoutPromise]); } finally { if (timeoutId) { clearTimeout(timeoutId); } } } /** * Default timeout configurations for different operation types */ export const TIMEOUTS = { EMBEDDING: 30_000, // 30 seconds for embedding generation DOCUMENT_PROCESSING: 600_000, // 10 minutes for document processing // Multi-pass extraction can take time: Pass 1 (primary), Pass 2 (fallback), // Pass 3 (field re-extraction), Pass 4 (consistency validation), plus retries FILE_DOWNLOAD: 60_000, // 1 minute for file downloads FILE_UPLOAD: 60_000, // 1 minute for file uploads DATABASE_QUERY: 15_000, // 15 seconds for database queries EXTERNAL_API: 30_000, // 30 seconds for external API calls AI_CLASSIFICATION: 90_000, // 90 seconds for AI document/image classification // AI classification can be slow for complex documents with OCR, multiple pages, // or when the model is under load. 90s provides sufficient buffer. CLASSIFICATION_JOB_WAIT: 180_000, // 3 minutes for waiting on classification jobs // Must be >= AI_CLASSIFICATION (90s) + FILE_DOWNLOAD (60s for images) + job overhead // Using 180s to ensure parent job doesn't timeout while child classification is valid // This prevents race conditions where parent marks "failed" but child completes "completed" } as const; /** * Image size configurations for processing * Based on research: 2048px is optimal for vision models + OCR * - Preserves text legibility (x-height >= 20px for receipts) * - Within all major AI model limits (Gemini, GPT-4V, Claude) * - Good balance between OCR quality and processing speed */ export const IMAGE_SIZES = { MAX_DIMENSION: 2048, // Max width/height for image processing } as const;