/** * S1.1: API helper utilities * Extracted from client.ts — Cancellable requests, deduplication, utilities */ import axios, { InternalAxiosRequestConfig, AxiosResponse } from 'axios'; import { logger } from '@/utils/logger'; import { requestDeduplication } from '../requestDeduplication'; import { responseCache } from '../responseCache'; import { apiClient } from './httpClient'; /** * Edge 2.2: Create a cancellable request with AbortController support. */ export function createCancellableRequest( requestFn: (signal: AbortSignal) => Promise, ): { request: Promise; abort: () => void } { const abortController = new AbortController(); const signal = abortController.signal; const request = requestFn(signal).catch((error) => { if ( axios.isCancel(error) || error.name === 'AbortError' || signal.aborted ) { throw error; } throw error; }); return { request, abort: () => { if (!signal.aborted) { abortController.abort(); } }, }; } /** * Edge 2.2: Create a request with automatic timeout cancellation. */ export function createRequestWithTimeout( requestFn: (signal: AbortSignal) => Promise, timeoutMs: number, ): { request: Promise; abort: () => void } { const abortController = new AbortController(); const signal = abortController.signal; const timeoutId = setTimeout(() => { if (!signal.aborted) { abortController.abort(); } }, timeoutMs); const request = requestFn(signal) .catch((error) => { if ( axios.isCancel(error) || error.name === 'AbortError' || signal.aborted ) { throw error; } throw error; }) .finally(() => { clearTimeout(timeoutId); }); return { request, abort: () => { clearTimeout(timeoutId); if (!signal.aborted) { abortController.abort(); } }, }; } /** * FE-API-016/017: Enhanced API client with deduplication and caching */ export const deduplicatedApiClient = { get: (url: string, config?: InternalAxiosRequestConfig) => { if (!(config as any)?._disableCache) { const cachedResponse = responseCache.get({ ...config, method: 'GET', url, }); if (cachedResponse) { logger.debug(`[API] Using cached response for: ${url}`); return Promise.resolve(cachedResponse as AxiosResponse); } } return requestDeduplication.getOrCreateRequest( { ...config, method: 'GET', url }, () => apiClient.get(url, config), ); }, post: ( url: string, data?: any, config?: InternalAxiosRequestConfig, ) => { return requestDeduplication.getOrCreateRequest( { ...config, method: 'POST', url, data }, () => apiClient.post(url, data, config), ); }, put: ( url: string, data?: any, config?: InternalAxiosRequestConfig, ) => { return requestDeduplication.getOrCreateRequest( { ...config, method: 'PUT', url, data }, () => apiClient.put(url, data, config), ); }, patch: ( url: string, data?: any, config?: InternalAxiosRequestConfig, ) => { return requestDeduplication.getOrCreateRequest( { ...config, method: 'PATCH', url, data }, () => apiClient.patch(url, data, config), ); }, delete: (url: string, config?: InternalAxiosRequestConfig) => { return requestDeduplication.getOrCreateRequest( { ...config, method: 'DELETE', url }, () => apiClient.delete(url, config), ); }, }; /** * Edge 2.3: Check if a request is slow */ export function isSlowRequest( config?: InternalAxiosRequestConfig, ): boolean { if (!config) return false; return (config as any)?._isSlowRequest === true; } /** * Edge 2.3: Get request duration in milliseconds */ export function getRequestDuration( config?: InternalAxiosRequestConfig, ): number | undefined { if (!config) return undefined; return (config as any)?._requestDuration; }