import { AxiosError, InternalAxiosRequestConfig } from 'axios'; import { Ref } from 'vue'; import { ZodError, ZodType } from 'zod'; import { $ZodFlattenedError } from 'zod/v4/core'; /** * HTTP methods supported by the API client * @example * const method: HTTPMethod = "GET"; */ export type HTTPMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; /** * Infer TypeScript type from Zod schema * @template T - Zod schema type * @example * const userSchema = z.object({ id: z.number(), name: z.string() }); * type User = Infer; // { id: number; name: string } */ export type Infer = T extends ZodType ? U : any; /** * Defines a query endpoint configuration (supports GET and POST methods) * @template TParams - Zod schema for query/path parameters * @template TData - Zod schema for request body (POST only) * @template TResponse - Zod schema for response data * @example * const getUsers: ApiQuery = { * method: "GET", * path: "/users", * params: z.object({ page: z.number() }), * response: z.array(z.object({ id: z.number(), name: z.string() })) * }; * @example * const searchUsers: ApiQuery = { * method: "POST", * path: "/users/search", * data: z.object({ query: z.string() }), * response: z.array(z.object({ id: z.number(), name: z.string() })) * }; */ export interface ApiQuery | undefined = ZodType | undefined, TData extends ZodType | undefined = ZodType | undefined, TResponse extends ZodType | undefined = ZodType | undefined> { method?: Extract; path: string; params?: TParams; data?: TData; response?: TResponse; onBeforeRequest?: (config: InternalAxiosRequestConfig) => Promise | void | any; } /** * Defines a mutation (POST, PUT, PATCH, DELETE) endpoint configuration * @template TData - Zod schema for request body * @template TResponse - Zod schema for response data * @template TParams - Zod schema for path/query parameters * @example * const createUser: ApiMutation = { * method: "POST", * path: "/users", * data: z.object({ name: z.string(), email: z.string().email() }), * response: z.object({ id: z.number(), name: z.string(), email: z.string() }) * }; */ export interface ApiMutation | undefined = ZodType | undefined, TResponse extends ZodType | undefined = ZodType | undefined, TParams extends ZodType | undefined = ZodType | undefined> { method: HTTPMethod; path: string; params?: TParams; data?: TData; response?: TResponse; isMultipart?: boolean; multipartBooleanStyle?: "trueFalse" | "numeric"; onBeforeRequest?: (config: InternalAxiosRequestConfig) => Promise | void | any; } /** * Recursive type to represent nested query or mutation structure */ export type NestedStructure = T | { [key: string]: NestedStructure; }; /** * Extract query hook function from ApiQuery definition */ export type QueryHookFromDefinition = (options?: UseQueryOptions, Infer, Infer>) => QueryResult>; /** * Extract mutation hook function from ApiMutation definition */ export type MutationHookFromDefinition = (options?: UseMutationOptions>) => MutationResult, Infer, Infer>; /** * Recursively transform nested query definitions into query hooks */ export type QueryHooksFromDefinitions = { [K in keyof Q]: Q[K] extends ApiQuery ? QueryHookFromDefinition : Q[K] extends Record> ? QueryHooksFromDefinitions : never; }; /** * Recursively transform nested mutation definitions into mutation hooks */ export type MutationHooksFromDefinitions = { [K in keyof M]: M[K] extends ApiMutation ? MutationHookFromDefinition : M[K] extends Record> ? MutationHooksFromDefinitions : never; }; /** * Configuration options for creating an API client * @template Q - Record of query endpoint definitions (can be nested) * @template M - Record of mutation endpoint definitions (can be nested) * @example * const options: ApiClientOptions = { * baseURL: "https://api.example.com", * headers: { Authorization: "Bearer token" }, * withCredentials: true, * csrfRefreshEndpoint: "/auth/refresh-csrf", * queries: { getUsers: { path: "/users" } }, * mutations: { createUser: { method: "POST", path: "/users" } }, * onBeforeRequest: async (config) => { * // Modify config before request * return config; * }, * onStartRequest: async () => { * console.log("Request started"); * }, * onFinishRequest: async () => { * console.log("Request finished"); * }, * onError: ({ err, message }) => { * console.error("API Error:", message); * }, * onZodError: (zodError) => { * console.error("Validation Error:", zodError); * } * }; * @example * // Nested structure * const options: ApiClientOptions = { * baseURL: "https://api.example.com", * queries: { * users: { * getAll: { path: "/users" }, * getById: { path: "/users/{id}" } * } * } * }; */ export interface ApiClientOptions> = Record>, M extends Record> = Record>> { baseURL: string; headers?: Record; withCredentials?: boolean; withXSRFToken?: boolean; csrfRefreshEndpoint?: string; queries?: Q; mutations?: M; onBeforeRequest?: (config: InternalAxiosRequestConfig) => Promise | void | any; onStartRequest?: () => Promise | void | any; onFinishRequest?: () => Promise | void | any; onError?: (error: { err: AxiosError | ZodError | Error; message: string; }) => void; onZodError?: (zodError: $ZodFlattenedError) => void; } /** * Options for configuring a query hook * @template TParams - Type of query parameters * @template TData - Type of request body data (for POST queries) * @template TResult - Type of result data * @example * const options: UseQueryOptions = { * params: { page: 1 }, * loadOnMount: true, * debounce: 300, * onResult: (data) => console.log(data), * onError: (error) => console.error(error) * }; * @example * const options: UseQueryOptions = { * data: { query: "search term" }, * loadOnMount: true, * onResult: (data) => console.log(data) * }; */ export interface UseQueryOptions { params?: TParams; data?: TData; loadOnMount?: boolean; debounce?: number; onResult?: (result: TResult) => void; onError?: (error: AxiosError | ZodError | Error) => void; onZodError?: (zodError: $ZodFlattenedError) => void; onBeforeRequest?: (config: InternalAxiosRequestConfig) => Promise | void | any; } /** * Options for configuring a mutation hook * @template TResult - Type of result data * @example * const options: UseMutationOptions = { * onResult: (data) => console.log("Success:", data), * onError: (error) => console.error("Error:", error), * onUploadProgress: (progress) => console.log(`Upload: ${progress}%`) * }; */ export interface UseMutationOptions { onResult?: (result: TResult) => void; onError?: (error: AxiosError | ZodError | Error) => void; onZodError?: (zodError: $ZodFlattenedError) => void; onUploadProgress?: (progress: number) => void; onBeforeRequest?: (config: InternalAxiosRequestConfig) => Promise | void | any; } /** * Return type from a query hook * @template TResult - Type of result data * @example * const { result, isLoading, errorMessage, refetch } = useGetUsers(); * // result.value contains the data * // isLoading.value indicates loading state * // errorMessage.value contains any error message * // refetch() to manually trigger a new request * // uploadProgress.value shows upload progress (0-100) for POST queries with file uploads */ export interface QueryResult { result: Ref; errorMessage: Ref; zodError: Ref<$ZodFlattenedError | undefined>; isLoading: Ref; isDone: Ref; refetch: () => Promise; cancel: () => void; } /** * Return type from a mutation hook * @template TResult - Type of result data * @template TData - Type of mutation input data * @example * const { mutate, isLoading, result, errorMessage } = useCreateUser(); * // Call mutate() to trigger the mutation * await mutate({ name: "John", email: "john@example.com" }); * // result.value contains the response * // isLoading.value indicates loading state * // uploadProgress.value shows upload progress (0-100) */ export interface MutationResult { result: Ref; errorMessage: Ref; zodError: Ref<$ZodFlattenedError | undefined>; isLoading: Ref; isDone: Ref; uploadProgress: Ref; mutate: (rgs?: { data?: TData; params?: TParams; }) => Promise; cancel: () => void; }