162 lines
3.3 KiB
TypeScript
162 lines
3.3 KiB
TypeScript
export interface FetchOptions extends RequestInit {
|
|
params?: Record<string, any>
|
|
timeout?: number
|
|
}
|
|
|
|
export interface FetchResponse<T = any> {
|
|
data: T
|
|
status: number
|
|
statusText: string
|
|
ok: boolean
|
|
error?: string
|
|
}
|
|
|
|
export class FetchError extends Error {
|
|
constructor(
|
|
public message: string,
|
|
public status?: number,
|
|
public response?: any
|
|
) {
|
|
super(message)
|
|
this.name = 'FetchError'
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch wrapper with timeout support and axios-like interface
|
|
*/
|
|
async function fetchWithTimeout(
|
|
url: string,
|
|
options: FetchOptions = {}
|
|
): Promise<Response> {
|
|
const { timeout = 30000, ...fetchOptions } = options
|
|
|
|
const controller = new AbortController()
|
|
const timeoutId = setTimeout(() => controller.abort(), timeout)
|
|
|
|
try {
|
|
const response = await fetch(url, {
|
|
...fetchOptions,
|
|
signal: controller.signal,
|
|
})
|
|
clearTimeout(timeoutId)
|
|
return response
|
|
} catch (error) {
|
|
clearTimeout(timeoutId)
|
|
throw error
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET request
|
|
*/
|
|
export async function get<T = any>(
|
|
url: string,
|
|
options?: FetchOptions
|
|
): Promise<FetchResponse<T>> {
|
|
try {
|
|
const response = await fetchWithTimeout(url, {
|
|
...options,
|
|
method: 'GET',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
...options?.headers,
|
|
},
|
|
})
|
|
|
|
const data = await response.json()
|
|
|
|
return {
|
|
data,
|
|
status: response.status,
|
|
statusText: response.statusText,
|
|
ok: response.ok,
|
|
error: response.ok ? undefined : data?.message || data?.error || response.statusText,
|
|
}
|
|
} catch (error) {
|
|
throw new FetchError(
|
|
error instanceof Error ? error.message : 'Network request failed',
|
|
undefined,
|
|
error
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* POST request
|
|
*/
|
|
export async function post<T = any>(
|
|
url: string,
|
|
data?: any,
|
|
options?: FetchOptions
|
|
): Promise<FetchResponse<T>> {
|
|
try {
|
|
const response = await fetchWithTimeout(url, {
|
|
...options,
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
...options?.headers,
|
|
},
|
|
body: JSON.stringify(data),
|
|
})
|
|
|
|
const responseData = await response.json()
|
|
|
|
return {
|
|
data: responseData,
|
|
status: response.status,
|
|
statusText: response.statusText,
|
|
ok: response.ok,
|
|
error: response.ok ? undefined : responseData?.message || responseData?.error || response.statusText,
|
|
}
|
|
} catch (error) {
|
|
throw new FetchError(
|
|
error instanceof Error ? error.message : 'Network request failed',
|
|
undefined,
|
|
error
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* DELETE request
|
|
*/
|
|
export async function del<T = any>(
|
|
url: string,
|
|
options?: FetchOptions
|
|
): Promise<FetchResponse<T>> {
|
|
try {
|
|
const response = await fetchWithTimeout(url, {
|
|
...options,
|
|
method: 'DELETE',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
...options?.headers,
|
|
},
|
|
})
|
|
|
|
const data = await response.json().catch(() => ({}))
|
|
|
|
return {
|
|
data,
|
|
status: response.status,
|
|
statusText: response.statusText,
|
|
ok: response.ok,
|
|
error: response.ok ? undefined : data?.message || data?.error || response.statusText,
|
|
}
|
|
} catch (error) {
|
|
throw new FetchError(
|
|
error instanceof Error ? error.message : 'Network request failed',
|
|
undefined,
|
|
error
|
|
)
|
|
}
|
|
}
|
|
|
|
export const fetchClient = {
|
|
get,
|
|
post,
|
|
delete: del,
|
|
}
|