From 8bdfc850279cd2787d983d979dd916e22d27449c Mon Sep 17 00:00:00 2001 From: fahed Date: Thu, 26 Mar 2026 15:02:43 +0300 Subject: [PATCH] refactor: extract fetch helpers to shared util Co-Authored-By: Claude Opus 4.6 (1M context) --- src/services/dataService.ts | 42 +---------------------------------- src/utils/fetchHelpers.ts | 44 +++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 41 deletions(-) create mode 100644 src/utils/fetchHelpers.ts diff --git a/src/services/dataService.ts b/src/services/dataService.ts index 96e8e84..b369569 100644 --- a/src/services/dataService.ts +++ b/src/services/dataService.ts @@ -18,57 +18,17 @@ import type { DataErrorType } from '../types'; import { DataError } from '../types'; +import { fetchWithRetry } from '../utils/fetchHelpers'; const NOCODB_URL = import.meta.env.VITE_NOCODB_URL || ''; const NOCODB_TOKEN = import.meta.env.VITE_NOCODB_TOKEN || ''; const NOCODB_BASE_ID = import.meta.env.VITE_NOCODB_BASE_ID || ''; -const FETCH_TIMEOUT_MS = 10_000; -const MAX_RETRIES = 3; const VAT_RATE = 1.15; // Table IDs discovered dynamically from NocoDB meta API let discoveredTables: Record | null = null; -// ============================================ -// Fetch Helpers (timeout + retry) -// ============================================ - -async function fetchWithTimeout(url: string, options: RequestInit = {}, timeoutMs: number = FETCH_TIMEOUT_MS): Promise { - const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), timeoutMs); - try { - const res = await fetch(url, { ...options, signal: controller.signal }); - return res; - } catch (err) { - if ((err as Error).name === 'AbortError') { - throw new Error(`Request timed out after ${timeoutMs}ms`); - } - throw err; - } finally { - clearTimeout(timer); - } -} - -async function fetchWithRetry(url: string, options: RequestInit = {}, retries: number = MAX_RETRIES): Promise { - let lastError: Error | null = null; - for (let attempt = 0; attempt < retries; attempt++) { - try { - const res = await fetchWithTimeout(url, options); - if (!res.ok) throw new Error(`HTTP ${res.status}`); - return res; - } catch (err) { - lastError = err as Error; - if (attempt < retries - 1) { - const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s - console.warn(`Fetch attempt ${attempt + 1} failed, retrying in ${delay}ms...`); - await new Promise(r => setTimeout(r, delay)); - } - } - } - throw lastError; -} - async function discoverTableIds(): Promise> { if (discoveredTables) return discoveredTables; diff --git a/src/utils/fetchHelpers.ts b/src/utils/fetchHelpers.ts new file mode 100644 index 0000000..af4f6f6 --- /dev/null +++ b/src/utils/fetchHelpers.ts @@ -0,0 +1,44 @@ +const FETCH_TIMEOUT_MS = 10_000; +const MAX_RETRIES = 3; + +export async function fetchWithTimeout( + url: string, + options: RequestInit = {}, + timeoutMs: number = FETCH_TIMEOUT_MS +): Promise { + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), timeoutMs); + try { + return await fetch(url, { ...options, signal: controller.signal }); + } catch (err) { + if ((err as Error).name === 'AbortError') { + throw new Error(`Request timed out after ${timeoutMs}ms`); + } + throw err; + } finally { + clearTimeout(timer); + } +} + +export async function fetchWithRetry( + url: string, + options: RequestInit = {}, + retries: number = MAX_RETRIES +): Promise { + let lastError: Error | null = null; + for (let attempt = 0; attempt < retries; attempt++) { + try { + const res = await fetchWithTimeout(url, options); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + return res; + } catch (err) { + lastError = err as Error; + if (attempt < retries - 1) { + const delay = Math.pow(2, attempt) * 1000; + console.warn(`Fetch attempt ${attempt + 1} failed, retrying in ${delay}ms...`); + await new Promise(r => setTimeout(r, delay)); + } + } + } + throw lastError; +}