refactor: extract fetch helpers to shared util

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
fahed
2026-03-26 15:02:43 +03:00
parent e84d961536
commit 8bdfc85027
2 changed files with 45 additions and 41 deletions

View File

@@ -18,57 +18,17 @@ import type {
DataErrorType DataErrorType
} from '../types'; } from '../types';
import { DataError } from '../types'; import { DataError } from '../types';
import { fetchWithRetry } from '../utils/fetchHelpers';
const NOCODB_URL = import.meta.env.VITE_NOCODB_URL || ''; const NOCODB_URL = import.meta.env.VITE_NOCODB_URL || '';
const NOCODB_TOKEN = import.meta.env.VITE_NOCODB_TOKEN || ''; const NOCODB_TOKEN = import.meta.env.VITE_NOCODB_TOKEN || '';
const NOCODB_BASE_ID = import.meta.env.VITE_NOCODB_BASE_ID || ''; 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; const VAT_RATE = 1.15;
// Table IDs discovered dynamically from NocoDB meta API // Table IDs discovered dynamically from NocoDB meta API
let discoveredTables: Record<string, string> | null = null; let discoveredTables: Record<string, string> | null = null;
// ============================================
// Fetch Helpers (timeout + retry)
// ============================================
async function fetchWithTimeout(url: string, options: RequestInit = {}, timeoutMs: number = FETCH_TIMEOUT_MS): Promise<Response> {
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<Response> {
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<Record<string, string>> { async function discoverTableIds(): Promise<Record<string, string>> {
if (discoveredTables) return discoveredTables; if (discoveredTables) return discoveredTables;

44
src/utils/fetchHelpers.ts Normal file
View File

@@ -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<Response> {
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<Response> {
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;
}