refactor: extract fetch helpers to shared util
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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
44
src/utils/fetchHelpers.ts
Normal 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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user