Files
frontend-svelte/src/shared/api/api.ts
T
2026-04-17 12:14:55 +03:00

148 lines
3.7 KiB
TypeScript

/**
* HTTP API client with error handling
*
* Provides a typed wrapper around fetch for JSON APIs.
* Automatically handles JSON serialization and error responses.
*
* @example
* ```ts
* import { api } from '$shared/api';
*
* // GET request
* const users = await api.get<User[]>('/api/users');
*
* // POST request
* const newUser = await api.post<User>('/api/users', { name: 'Alice' });
*
* // Error handling
* try {
* const data = await api.get('/api/data');
* } catch (error) {
* if (error instanceof ApiError) {
* console.error(error.status, error.message);
* }
* }
* ```
*/
import type { ApiResponse } from '$shared/types/common';
/**
* Custom error class for API failures
*
* Includes HTTP status code and the original Response object
* for debugging and error handling.
*/
export class ApiError extends Error {
/**
* Creates a new API error
* @param status - HTTP status code
* @param message - Error message
* @param response - Original fetch Response object
*/
constructor(
/**
* HTTP status code
*/
public status: number,
message: string,
/**
* Original Response object for inspection
*/
public response?: Response,
) {
super(message);
this.name = 'ApiError';
}
}
/**
* Internal request handler
*
* Performs fetch with JSON headers and throws ApiError on failure.
*
* @param url - Request URL
* @param options - Fetch options (method, headers, body, etc.)
* @returns Response data and status code
* @throws ApiError when response is not OK
*/
async function request<T>(
url: string,
options?: RequestInit,
): Promise<ApiResponse<T>> {
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
...options?.headers,
},
...options,
});
if (!response.ok) {
throw new ApiError(
response.status,
`Request failed: ${response.statusText}`,
response,
);
}
const data = await response.json() as T;
return {
data,
status: response.status,
};
}
/**
* API client methods
*
* Provides typed methods for common HTTP verbs.
* All methods return ApiResponse with data and status.
*/
export const api = {
/**
* Performs a GET request
* @param url - Request URL
* @param options - Additional fetch options
* @returns Response data
*/
get: <T>(url: string, options?: RequestInit) => request<T>(url, { ...options, method: 'GET' }),
/**
* Performs a POST request with JSON body
* @param url - Request URL
* @param body - Request body (will be JSON stringified)
* @param options - Additional fetch options
* @returns Response data
*/
post: <T>(url: string, body?: unknown, options?: RequestInit) =>
request<T>(url, {
...options,
method: 'POST',
body: JSON.stringify(body),
}),
/**
* Performs a PUT request with JSON body
* @param url - Request URL
* @param body - Request body (will be JSON stringified)
* @param options - Additional fetch options
* @returns Response data
*/
put: <T>(url: string, body?: unknown, options?: RequestInit) =>
request<T>(url, {
...options,
method: 'PUT',
body: JSON.stringify(body),
}),
/**
* Performs a DELETE request
* @param url - Request URL
* @param options - Additional fetch options
* @returns Response data
*/
delete: <T>(url: string, options?: RequestInit) => request<T>(url, { ...options, method: 'DELETE' }),
};