122 lines
3.8 KiB
TypeScript
122 lines
3.8 KiB
TypeScript
import type { AxiosInstance, AxiosResponse } from 'axios';
|
|
import { type Fetcher, type Method, createApiClient } from '../generated/api-client/api-client';
|
|
import { TanstackQueryApiClient } from '../generated/api-client/tanstack-client';
|
|
|
|
const API_BASE_URL: string | undefined = import.meta.env.VITE_API_BASE_URL;
|
|
|
|
const get_fetch: (axios: AxiosInstance) => Fetcher['fetch'] =
|
|
(axios) =>
|
|
async ({ method, url: incomingUrl, parameters: params }) => {
|
|
// Use a plain object for Axios headers
|
|
const headers: Record<string, string> = {};
|
|
|
|
// Replace path parameters (supports both {param} and :param formats)
|
|
const actualUrl = replacePathParams(incomingUrl.toString(), (params?.path ?? {}) as Record<string, string>);
|
|
const url = new URL(actualUrl);
|
|
|
|
// Handle query parameters
|
|
if (params?.query) {
|
|
const searchParams = new URLSearchParams();
|
|
Object.entries(params.query).forEach(([key, value]) => {
|
|
if (value != null) {
|
|
// Skip null/undefined values
|
|
if (Array.isArray(value)) {
|
|
value.forEach((val) => val != null && searchParams.append(key, String(val)));
|
|
} else {
|
|
searchParams.append(key, String(value));
|
|
}
|
|
}
|
|
});
|
|
url.search = searchParams.toString();
|
|
}
|
|
|
|
// Handle request body for mutation methods (use Axios `data`)
|
|
const data = (['post', 'put', 'patch', 'delete'] satisfies Method[] as string[]).includes(method.toLowerCase()) ? params?.body : undefined;
|
|
|
|
if (data != null) {
|
|
headers['Content-Type'] = 'application/json';
|
|
}
|
|
|
|
// Add custom headers
|
|
if (params?.header) {
|
|
Object.entries(params.header).forEach(([key, value]) => {
|
|
if (value != null) {
|
|
headers[key] = String(value);
|
|
}
|
|
});
|
|
}
|
|
|
|
const response = await axios(url.toString(), {
|
|
method: method.toUpperCase(),
|
|
...(data !== undefined && { data }),
|
|
headers: headers,
|
|
});
|
|
|
|
return axiosResponseToFetchResponse(response);
|
|
};
|
|
|
|
function axiosResponseToFetchResponse(response: AxiosResponse): Response {
|
|
const headers = new Headers();
|
|
Object.entries(response.headers).forEach(([key, value]) => {
|
|
if (Array.isArray(value)) {
|
|
value.forEach((val) => headers.append(key, val));
|
|
} else {
|
|
headers.append(key, value);
|
|
}
|
|
});
|
|
|
|
// Normalize Axios response.data to a Fetch-compatible BodyInit
|
|
let body: BodyInit | null = null;
|
|
const data = response.data;
|
|
|
|
if (data == null) {
|
|
body = null;
|
|
} else if (
|
|
typeof data === 'string' ||
|
|
data instanceof Blob ||
|
|
data instanceof ArrayBuffer ||
|
|
ArrayBuffer.isView(data) ||
|
|
data instanceof FormData ||
|
|
data instanceof URLSearchParams
|
|
) {
|
|
body = data as BodyInit;
|
|
} else {
|
|
try {
|
|
body = JSON.stringify(data);
|
|
if (!headers.has('content-type')) {
|
|
headers.set('content-type', 'application/json;charset=utf-8');
|
|
}
|
|
} catch {
|
|
console.warn('Failed to stringify response data as JSON, falling back to string conversion.');
|
|
body = String(data);
|
|
}
|
|
}
|
|
|
|
return new Response(body, {
|
|
status: response.status,
|
|
statusText: response.statusText,
|
|
headers: headers,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Replace path parameters in URL
|
|
* Supports both OpenAPI format {param} and Express format :param
|
|
*/
|
|
function replacePathParams(url: string, params: Record<string, string>): string {
|
|
return url.replace(/{(\w+)}/g, (_, key: string) => params[key] || `{${key}}`).replace(/:([a-zA-Z0-9_]+)/g, (_, key: string) => params[key] || `:${key}`);
|
|
}
|
|
|
|
export function createApi(axios: AxiosInstance) {
|
|
return createApiClient(
|
|
{
|
|
fetch: get_fetch(axios),
|
|
},
|
|
API_BASE_URL ?? window.location.origin
|
|
);
|
|
}
|
|
|
|
export function createTanstackApi(axios: AxiosInstance) {
|
|
return new TanstackQueryApiClient(createApi(axios));
|
|
}
|