veza/apps/web/src/services/api/helpers.ts

166 lines
4 KiB
TypeScript
Raw Normal View History

/**
* 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<T>(
requestFn: (signal: AbortSignal) => Promise<T>,
): { request: Promise<T>; 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<T>(
requestFn: (signal: AbortSignal) => Promise<T>,
timeoutMs: number,
): { request: Promise<T>; 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: <T = any>(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<T>);
}
}
return requestDeduplication.getOrCreateRequest(
{ ...config, method: 'GET', url },
() => apiClient.get<T>(url, config),
);
},
post: <T = any>(
url: string,
data?: any,
config?: InternalAxiosRequestConfig,
) => {
return requestDeduplication.getOrCreateRequest(
{ ...config, method: 'POST', url, data },
() => apiClient.post<T>(url, data, config),
);
},
put: <T = any>(
url: string,
data?: any,
config?: InternalAxiosRequestConfig,
) => {
return requestDeduplication.getOrCreateRequest(
{ ...config, method: 'PUT', url, data },
() => apiClient.put<T>(url, data, config),
);
},
patch: <T = any>(
url: string,
data?: any,
config?: InternalAxiosRequestConfig,
) => {
return requestDeduplication.getOrCreateRequest(
{ ...config, method: 'PATCH', url, data },
() => apiClient.patch<T>(url, data, config),
);
},
delete: <T = any>(url: string, config?: InternalAxiosRequestConfig) => {
return requestDeduplication.getOrCreateRequest(
{ ...config, method: 'DELETE', url },
() => apiClient.delete<T>(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;
}