import { useQuery, useMutation, useInfiniteQuery, QueryKey, QueryClient } from '@tanstack/react-query'; import { ApiEndpoint, ApiError, RequestConfig, QueryParams, PaginatedResponse, PaginationMeta } from './types'; import { ApiClient } from './api-client'; /** * Query hook configuration */ export interface QueryHookConfig { /** Enable/disable the query */ enabled?: boolean; /** Stale time in milliseconds */ staleTime?: number; /** Garbage collection time */ gcTime?: number; /** Refetch on window focus */ refetchOnWindowFocus?: boolean; /** Refetch on mount behavior */ refetchOnMount?: boolean | 'always'; /** Refetch interval */ refetchInterval?: number | false; /** Retry configuration */ retry?: number | boolean | ((failureCount: number, error: TError) => boolean); /** Select/transform data */ select?: (data: TResponse) => TResponse; /** On success callback */ onSuccess?: (data: TResponse) => void; /** On error callback */ onError?: (error: TError) => void; /** Keep previous data */ keepPreviousData?: boolean; /** Placeholder data */ placeholderData?: TResponse | (() => TResponse | undefined); /** Initial data */ initialData?: TResponse | (() => TResponse); /** Custom query key suffix */ queryKeySuffix?: unknown[]; } /** * Mutation hook configuration */ export interface MutationHookConfig { /** On mutate callback for optimistic updates */ onMutate?: (variables: TVariables) => Promise | TContext; /** On success callback */ onSuccess?: (data: TResponse, variables: TVariables, context: TContext | undefined) => void; /** On error callback */ onError?: (error: TError, variables: TVariables, context: TContext | undefined) => void; /** On settled callback */ onSettled?: (data: TResponse | undefined, error: TError | null, variables: TVariables, context: TContext | undefined) => void; /** Query keys to invalidate on success */ invalidateQueries?: QueryKey[]; /** Query keys to refetch on success */ refetchQueries?: QueryKey[]; /** Retry configuration */ retry?: number | boolean; } /** * Infinite query hook configuration */ export interface InfiniteQueryHookConfig extends Omit, 'select'> { /** Get next page parameter from response */ getNextPageParam?: (lastPage: TResponse, pages: TResponse[]) => unknown | undefined; /** Get previous page parameter from response */ getPreviousPageParam?: (firstPage: TResponse, pages: TResponse[]) => unknown | undefined; } /** * Request parameters for query hooks */ export interface QueryRequestParams { /** Path parameters for URL substitution */ pathParams?: Record; /** Query parameters */ queryParams?: QueryParams; /** Additional request config */ config?: Partial; } /** * Request parameters for mutation hooks */ export interface MutationRequestParams { /** Path parameters for URL substitution */ pathParams?: Record; /** Query parameters */ queryParams?: QueryParams; /** Request body */ body?: TBody; /** Additional request config */ config?: Partial; } /** * Generated hook type for queries */ export type GeneratedQueryHook = (params?: TParams, config?: QueryHookConfig) => ReturnType>; /** * Generated hook type for mutations */ export type GeneratedMutationHook = (config?: MutationHookConfig) => ReturnType>; /** * API hooks collection type */ export type ApiHooks> = { [K in keyof TContract as `use${Capitalize}`]: TContract[K]['method'] extends 'GET' ? GeneratedQueryHook ? R : unknown> : GeneratedMutationHook ? R : unknown, MutationRequestParams ? Req : unknown>>; }; /** * Create query key factory for an API contract */ export declare function createQueryKeyFactory>(namespace: string): Record QueryKey>; /** * Configuration for the hooks factory */ export interface HooksFactoryConfig { /** API client instance to use */ client?: ApiClient; /** Namespace for query keys */ namespace: string; /** Default stale time */ defaultStaleTime?: number; /** Default retry configuration */ defaultRetry?: number | boolean; /** Global error handler */ onError?: (error: ApiError) => void; /** Global success handler */ onSuccess?: (data: unknown) => void; } /** * Create typed API hooks from an endpoint contract * * @typeParam TContract - API contract type mapping endpoint names to definitions * @param contract - Object defining API endpoints * @param config - Factory configuration * @returns Object with generated hooks for each endpoint * * @example * ```typescript * // Define the contract * const usersApi = { * getUsers: { * method: 'GET' as const, * path: '/users', * }, * getUser: { * method: 'GET' as const, * path: '/users/:id', * }, * createUser: { * method: 'POST' as const, * path: '/users', * }, * updateUser: { * method: 'PATCH' as const, * path: '/users/:id', * }, * deleteUser: { * method: 'DELETE' as const, * path: '/users/:id', * }, * } as const; * * // Generate hooks * const userHooks = createApiHooks(usersApi, { namespace: 'users' }); * * // Use hooks * const { data: users } = userHooks.useGetUsers(); * const { data: user } = userHooks.useGetUser({ pathParams: { id: '123' } }); * const createMutation = userHooks.useCreateUser(); * ``` */ export declare function createApiHooks>(contract: TContract, config: HooksFactoryConfig): ApiHooks; /** * Create an infinite query hook for paginated endpoints * * @example * ```typescript * const useInfiniteUsers = createInfiniteQueryHook({ * client: apiClient, * namespace: 'users', * endpointName: 'list', * endpoint: { method: 'GET', path: '/users' }, * getNextPageParam: (lastPage) => lastPage.pagination?.nextCursor, * }); * * // In component * const { data, fetchNextPage, hasNextPage } = useInfiniteUsers(); * ``` */ export declare function createInfiniteQueryHook(config: { client?: ApiClient; namespace: string; endpointName: string; endpoint: ApiEndpoint; getNextPageParam?: (lastPage: TResponse, pages: TResponse[]) => unknown | undefined; getPreviousPageParam?: (firstPage: TResponse, pages: TResponse[]) => unknown | undefined; pageParamName?: string; }): (params?: QueryRequestParams, hookConfig?: InfiniteQueryHookConfig) => ReturnType>; /** * Configuration for optimistic updates */ export interface OptimisticUpdateConfig { /** Query key to update */ queryKey: QueryKey; /** Function to compute optimistic update */ updater: (oldData: TData | undefined, variables: TVariables) => TData; /** Whether to await the mutation */ awaitMutation?: boolean; /** * QueryClient instance for cache operations. * Required - do not rely on global state. */ queryClient: QueryClient; } /** * Create optimistic mutation handlers. * * IMPORTANT: Requires explicit QueryClient instance to avoid global state issues. * Use useQueryClient() hook in your component to get the client instance. * * @example * ```typescript * function UserComponent() { * const queryClient = useQueryClient(); * * const updateUserMutation = useUpdateUser({ * ...createOptimisticMutation({ * queryClient, * queryKey: ['users', userId], * updater: (oldUser, variables) => ({ ...oldUser, ...variables.body }), * }), * }); * } * ``` */ export declare function createOptimisticMutation(config: OptimisticUpdateConfig): Pick, 'onMutate' | 'onError' | 'onSettled'>; /** * Create optimistic list update handlers (for adding/removing items). * * IMPORTANT: Requires explicit QueryClient instance to avoid global state issues. * Use useQueryClient() hook in your component to get the client instance. * * @example * ```typescript * function UserListComponent() { * const queryClient = useQueryClient(); * * const deleteUserMutation = useDeleteUser({ * ...createOptimisticListMutation({ * queryClient, * queryKey: ['users', 'list'], * operation: 'remove', * getItemId: (item) => item.id, * getVariableId: (variables) => variables.pathParams?.id, * }), * }); * } * ``` */ export declare function createOptimisticListMutation(config: { /** QueryClient instance - required */ queryClient: QueryClient; queryKey: QueryKey; operation: 'add' | 'remove' | 'update'; getItemId: (item: TItem) => string | number; getVariableId?: (variables: TVariables) => string | number | undefined; getNewItem?: (variables: TVariables) => TItem; getUpdatedItem?: (oldItem: TItem, variables: TVariables) => TItem; }): Pick, 'onMutate' | 'onError' | 'onSettled'>; /** * Error handling configuration */ export interface ErrorHandlerConfig { /** Show toast notification */ showToast?: boolean; /** Report to error tracking service */ reportError?: boolean; /** Custom error messages by code */ messages?: Record; /** Retry configuration */ retryConfig?: { maxRetries: number; retryCondition?: (error: ApiError) => boolean; }; /** Fallback data on error */ fallbackData?: unknown; /** Custom error handler */ onError?: (error: ApiError) => void; } /** * Create standardized error handlers for queries * * @example * ```typescript * const errorHandler = createErrorHandler({ * showToast: true, * messages: { * 'HTTP_404': 'User not found', * 'HTTP_403': 'You do not have permission to view this user', * }, * }); * * const { data } = useUser({ * onError: errorHandler.onError, * }); * ``` */ export declare function createErrorHandler(config?: ErrorHandlerConfig): { onError: (error: ApiError) => void; retry: (failureCount: number, error: ApiError) => boolean; fallbackData?: unknown; }; /** * Create prefetch function for an endpoint * * @example * ```typescript * const prefetchUser = createPrefetch({ * client: apiClient, * queryClient, * namespace: 'users', * endpointName: 'get', * endpoint: { method: 'GET', path: '/users/:id' }, * }); * * // Prefetch on hover * onMouseEnter={() => prefetchUser({ pathParams: { id: userId } })} * ``` */ export declare function createPrefetch(config: { client?: ApiClient; queryClient: QueryClient; namespace: string; endpointName: string; endpoint: ApiEndpoint; staleTime?: number; }): (params?: QueryRequestParams) => Promise; /** * Convert endpoint name to hook name */ export declare function toHookName(name: string): string; /** * Extract pagination from response */ export declare function extractPaginationFromResponse(response: PaginatedResponse): PaginationMeta; /** * Merge infinite query pages */ export declare function mergeInfinitePages(pages: PaginatedResponse[]): T[]; /** * Hook to get stable query key reference */ export declare function useStableQueryKey(key: QueryKey): QueryKey; /** * Hook to track mutation loading state across multiple mutations */ export declare function useMutationLoadingState(...mutations: Array<{ isPending: boolean; }>): boolean; /** * Hook to combine multiple mutation errors */ export declare function useCombinedMutationError(...mutations: Array<{ error: ApiError | null; }>): ApiError | null;