{"version":3,"file":"api-hooks-factory.mjs","sources":["../../../src/lib/api/api-hooks-factory.ts"],"sourcesContent":["/**\n * @file API Hooks Factory\n * @description Factory for generating type-safe React Query hooks from API endpoint definitions.\n * Provides automatic query key management, optimistic updates, and standardized error handling.\n *\n * This module enables:\n * - Automatic generation of typed useQuery hooks from endpoint definitions\n * - Automatic generation of typed useMutation hooks with optimistic updates\n * - Standardized query key generation and cache management\n * - Built-in error handling patterns with recovery options\n * - Integration with TanStack Query for caching and synchronization\n *\n * @example\n * ```typescript\n * import { createApiHooks } from '@/lib/api';\n *\n * // Define API contract\n * const userApi = {\n *   getUser: {\n *     method: 'GET' as const,\n *     path: '/users/:id',\n *   },\n *   createUser: {\n *     method: 'POST' as const,\n *     path: '/users',\n *   },\n * };\n *\n * // Generate hooks\n * const { useGetUser, useCreateUser } = createApiHooks(userApi);\n *\n * // Use in component\n * const { data: user } = useGetUser({ pathParams: { id: '123' } });\n * const createMutation = useCreateUser({\n *   onSuccess: () => invalidateQueries(['users']),\n * });\n * ```\n */\n\nimport {\n  useQuery,\n  useMutation,\n  useInfiniteQuery,\n  useQueryClient,\n  type UseQueryOptions,\n  type QueryKey,\n  type QueryClient,\n} from '@tanstack/react-query';\nimport { useMemo, useCallback } from 'react';\nimport type {\n  ApiEndpoint,\n  ApiError,\n  RequestConfig,\n  QueryParams,\n  ApiResponse,\n  PaginatedResponse,\n  PaginationMeta,\n} from './types';\nimport { apiClient } from './api-client';\nimport type { ApiClient } from './api-client';\nimport { TIMING } from '@/config';\n\n// =============================================================================\n// TYPES\n// =============================================================================\n\n/**\n * Query hook configuration\n */\nexport interface QueryHookConfig<TResponse, TError = ApiError> {\n  /** Enable/disable the query */\n  enabled?: boolean;\n  /** Stale time in milliseconds */\n  staleTime?: number;\n  /** Garbage collection time */\n  gcTime?: number;\n  /** Refetch on window focus */\n  refetchOnWindowFocus?: boolean;\n  /** Refetch on mount behavior */\n  refetchOnMount?: boolean | 'always';\n  /** Refetch interval */\n  refetchInterval?: number | false;\n  /** Retry configuration */\n  retry?: number | boolean | ((failureCount: number, error: TError) => boolean);\n  /** Select/transform data */\n  select?: (data: TResponse) => TResponse;\n  /** On success callback */\n  onSuccess?: (data: TResponse) => void;\n  /** On error callback */\n  onError?: (error: TError) => void;\n  /** Keep previous data */\n  keepPreviousData?: boolean;\n  /** Placeholder data */\n  placeholderData?: TResponse | (() => TResponse | undefined);\n  /** Initial data */\n  initialData?: TResponse | (() => TResponse);\n  /** Custom query key suffix */\n  queryKeySuffix?: unknown[];\n}\n\n/**\n * Mutation hook configuration\n */\nexport interface MutationHookConfig<TResponse, TVariables, TError = ApiError, TContext = unknown> {\n  /** On mutate callback for optimistic updates */\n  onMutate?: (variables: TVariables) => Promise<TContext> | TContext;\n  /** On success callback */\n  onSuccess?: (data: TResponse, variables: TVariables, context: TContext | undefined) => void;\n  /** On error callback */\n  onError?: (error: TError, variables: TVariables, context: TContext | undefined) => void;\n  /** On settled callback */\n  onSettled?: (\n    data: TResponse | undefined,\n    error: TError | null,\n    variables: TVariables,\n    context: TContext | undefined\n  ) => void;\n  /** Query keys to invalidate on success */\n  invalidateQueries?: QueryKey[];\n  /** Query keys to refetch on success */\n  refetchQueries?: QueryKey[];\n  /** Retry configuration */\n  retry?: number | boolean;\n}\n\n/**\n * Infinite query hook configuration\n */\nexport interface InfiniteQueryHookConfig<TResponse, TError = ApiError> extends Omit<\n  QueryHookConfig<TResponse, TError>,\n  'select'\n> {\n  /** Get next page parameter from response */\n  getNextPageParam?: (lastPage: TResponse, pages: TResponse[]) => unknown | undefined;\n  /** Get previous page parameter from response */\n  getPreviousPageParam?: (firstPage: TResponse, pages: TResponse[]) => unknown | undefined;\n}\n\n/**\n * Request parameters for query hooks\n */\nexport interface QueryRequestParams {\n  /** Path parameters for URL substitution */\n  pathParams?: Record<string, string | number>;\n  /** Query parameters */\n  queryParams?: QueryParams;\n  /** Additional request config */\n  config?: Partial<RequestConfig>;\n}\n\n/**\n * Request parameters for mutation hooks\n */\nexport interface MutationRequestParams<TBody = unknown> {\n  /** Path parameters for URL substitution */\n  pathParams?: Record<string, string | number>;\n  /** Query parameters */\n  queryParams?: QueryParams;\n  /** Request body */\n  body?: TBody;\n  /** Additional request config */\n  config?: Partial<RequestConfig>;\n}\n\n/**\n * Generated hook type for queries\n */\nexport type GeneratedQueryHook<\n  TResponse,\n  TParams extends QueryRequestParams = QueryRequestParams,\n> = (\n  params?: TParams,\n  config?: QueryHookConfig<TResponse>\n) => ReturnType<typeof useQuery<TResponse, ApiError>>;\n\n/**\n * Generated hook type for mutations\n */\nexport type GeneratedMutationHook<\n  TResponse,\n  TVariables extends MutationRequestParams = MutationRequestParams,\n> = (\n  config?: MutationHookConfig<TResponse, TVariables>\n) => ReturnType<typeof useMutation<TResponse, ApiError, TVariables>>;\n\n/**\n * API hooks collection type\n */\nexport type ApiHooks<TContract extends Record<string, ApiEndpoint>> = {\n  [K in keyof TContract as `use${Capitalize<string & K>}`]: TContract[K]['method'] extends 'GET'\n    ? GeneratedQueryHook<TContract[K] extends ApiEndpoint<unknown, infer R> ? R : unknown>\n    : GeneratedMutationHook<\n        TContract[K] extends ApiEndpoint<unknown, infer R> ? R : unknown,\n        MutationRequestParams<TContract[K] extends ApiEndpoint<infer Req> ? Req : unknown>\n      >;\n};\n\n// =============================================================================\n// QUERY KEY FACTORY\n// =============================================================================\n\n/**\n * Create query key factory for an API contract\n */\nexport function createQueryKeyFactory<TContract extends Record<string, ApiEndpoint>>(\n  namespace: string\n): Record<keyof TContract, (...args: unknown[]) => QueryKey> {\n  const cache = new Map<string, (...args: unknown[]) => QueryKey>();\n\n  return new Proxy({} as Record<keyof TContract, (...args: unknown[]) => QueryKey>, {\n    get: (_, prop: string) => {\n      if (!cache.has(prop)) {\n        cache.set(prop, (...args: unknown[]) => [namespace, prop, ...args]);\n      }\n      return cache.get(prop);\n    },\n  });\n}\n\n/**\n * Build query key from endpoint and params\n */\nfunction buildQueryKey(\n  namespace: string,\n  endpointName: string,\n  params?: QueryRequestParams\n): QueryKey {\n  const key: unknown[] = [namespace, endpointName];\n\n  if (params?.pathParams) {\n    key.push(params.pathParams);\n  }\n\n  if (params?.queryParams && Object.keys(params.queryParams).length > 0) {\n    key.push(params.queryParams);\n  }\n\n  return key;\n}\n\n// =============================================================================\n// HOOK FACTORY\n// =============================================================================\n\n/**\n * Configuration for the hooks factory\n */\nexport interface HooksFactoryConfig {\n  /** API client instance to use */\n  client?: ApiClient;\n  /** Namespace for query keys */\n  namespace: string;\n  /** Default stale time */\n  defaultStaleTime?: number;\n  /** Default retry configuration */\n  defaultRetry?: number | boolean;\n  /** Global error handler */\n  onError?: (error: ApiError) => void;\n  /** Global success handler */\n  onSuccess?: (data: unknown) => void;\n}\n\n/**\n * Create typed API hooks from an endpoint contract\n *\n * @typeParam TContract - API contract type mapping endpoint names to definitions\n * @param contract - Object defining API endpoints\n * @param config - Factory configuration\n * @returns Object with generated hooks for each endpoint\n *\n * @example\n * ```typescript\n * // Define the contract\n * const usersApi = {\n *   getUsers: {\n *     method: 'GET' as const,\n *     path: '/users',\n *   },\n *   getUser: {\n *     method: 'GET' as const,\n *     path: '/users/:id',\n *   },\n *   createUser: {\n *     method: 'POST' as const,\n *     path: '/users',\n *   },\n *   updateUser: {\n *     method: 'PATCH' as const,\n *     path: '/users/:id',\n *   },\n *   deleteUser: {\n *     method: 'DELETE' as const,\n *     path: '/users/:id',\n *   },\n * } as const;\n *\n * // Generate hooks\n * const userHooks = createApiHooks(usersApi, { namespace: 'users' });\n *\n * // Use hooks\n * const { data: users } = userHooks.useGetUsers();\n * const { data: user } = userHooks.useGetUser({ pathParams: { id: '123' } });\n * const createMutation = userHooks.useCreateUser();\n * ```\n */\nexport function createApiHooks<TContract extends Record<string, ApiEndpoint>>(\n  contract: TContract,\n  config: HooksFactoryConfig\n): ApiHooks<TContract> {\n  const { client = apiClient, namespace, defaultStaleTime = TIMING.QUERY.STALE.MEDIUM } = config;\n\n  const hooks: Record<string, unknown> = {};\n\n  for (const [endpointName, endpoint] of Object.entries(contract)) {\n    const hookName = `use${capitalize(endpointName)}`;\n\n    if (endpoint.method === 'GET') {\n      // Generate query hook\n      hooks[hookName] = createQueryHook(\n        client,\n        namespace,\n        endpointName,\n        endpoint,\n        defaultStaleTime,\n        config.onError\n      );\n    } else {\n      // Generate mutation hook\n      hooks[hookName] = createMutationHook(\n        client,\n        namespace,\n        endpointName,\n        endpoint,\n        config.onError,\n        config.onSuccess\n      );\n    }\n  }\n\n  return hooks as ApiHooks<TContract>;\n}\n\n/**\n * Create a query hook for a GET endpoint\n */\nfunction createQueryHook<TResponse>(\n  client: ApiClient,\n  namespace: string,\n  endpointName: string,\n  endpoint: ApiEndpoint,\n  defaultStaleTime: number,\n  _globalOnError?: (error: ApiError) => void\n) {\n  return (params?: QueryRequestParams, hookConfig?: QueryHookConfig<TResponse>) => {\n    const queryKey = useMemo(\n      () => [\n        ...buildQueryKey(namespace, endpointName, params),\n        ...(hookConfig?.queryKeySuffix ?? []),\n      ],\n      [params, hookConfig?.queryKeySuffix]\n    );\n\n    const queryFn = useCallback(async (): Promise<TResponse> => {\n      const url = substitutePathParams(endpoint.path, params?.pathParams);\n\n      const response = await client.get<TResponse>(url, {\n        params: params?.queryParams,\n        timeout: endpoint.timeout,\n        ...params?.config,\n      });\n\n      return response.data;\n    }, [params]);\n\n    return useQuery<TResponse, ApiError>({\n      queryKey,\n      queryFn,\n      enabled: hookConfig?.enabled,\n      staleTime: hookConfig?.staleTime ?? defaultStaleTime,\n      gcTime: hookConfig?.gcTime,\n      refetchOnWindowFocus: hookConfig?.refetchOnWindowFocus,\n      refetchOnMount: hookConfig?.refetchOnMount,\n      refetchInterval: hookConfig?.refetchInterval,\n      retry: hookConfig?.retry,\n      select: hookConfig?.select,\n      placeholderData: hookConfig?.placeholderData as UseQueryOptions<\n        TResponse,\n        ApiError\n      >['placeholderData'],\n      initialData: hookConfig?.initialData,\n    });\n  };\n}\n\n/**\n * Create a mutation hook for POST/PUT/PATCH/DELETE endpoints\n */\nfunction createMutationHook<TResponse, TBody = unknown>(\n  client: ApiClient,\n  _namespace: string,\n  _endpointName: string,\n  endpoint: ApiEndpoint,\n  globalOnError?: (error: ApiError) => void,\n  globalOnSuccess?: (data: unknown) => void\n) {\n  return <TContext = unknown>(\n    hookConfig?: MutationHookConfig<TResponse, MutationRequestParams<TBody>, ApiError, TContext>\n  ) => {\n    const queryClient = useQueryClient();\n\n    const mutationFn = useCallback(\n      async (variables: MutationRequestParams<TBody>): Promise<TResponse> => {\n        const url = substitutePathParams(endpoint.path, variables?.pathParams);\n\n        let response: ApiResponse<TResponse>;\n\n        switch (endpoint.method) {\n          case 'POST':\n            response = await client.post<TResponse, TBody>(url, variables?.body, {\n              params: variables?.queryParams,\n              timeout: endpoint.timeout,\n              ...variables?.config,\n            });\n            break;\n          case 'PUT':\n            response = await client.put<TResponse, TBody>(url, variables?.body, {\n              params: variables?.queryParams,\n              timeout: endpoint.timeout,\n              ...variables?.config,\n            });\n            break;\n          case 'PATCH':\n            response = await client.patch<TResponse, TBody>(url, variables?.body, {\n              params: variables?.queryParams,\n              timeout: endpoint.timeout,\n              ...variables?.config,\n            });\n            break;\n          case 'DELETE':\n            response = await client.delete<TResponse>(url, {\n              params: variables?.queryParams,\n              timeout: endpoint.timeout,\n              ...variables?.config,\n            });\n            break;\n          default:\n            response = await client.request<TResponse>({\n              method: endpoint.method,\n              url,\n              body: variables?.body,\n              params: variables?.queryParams,\n              timeout: endpoint.timeout,\n              ...variables?.config,\n            });\n        }\n\n        return response.data;\n      },\n      []\n    );\n\n    return useMutation<TResponse, ApiError, MutationRequestParams<TBody>, TContext>({\n      mutationFn,\n      onMutate: hookConfig?.onMutate,\n      onSuccess: async (data, variables, context) => {\n        // Call custom success handler\n        hookConfig?.onSuccess?.(data, variables, context);\n        globalOnSuccess?.(data);\n\n        // Invalidate queries\n        if (hookConfig?.invalidateQueries) {\n          await Promise.all(\n            hookConfig.invalidateQueries.map(async (key) =>\n              queryClient.invalidateQueries({ queryKey: key })\n            )\n          );\n        }\n\n        // Refetch queries\n        if (hookConfig?.refetchQueries) {\n          await Promise.all(\n            hookConfig.refetchQueries.map(async (key) =>\n              queryClient.refetchQueries({ queryKey: key })\n            )\n          );\n        }\n      },\n      onError: (error, variables, context) => {\n        hookConfig?.onError?.(error, variables, context);\n        globalOnError?.(error);\n      },\n      onSettled: hookConfig?.onSettled,\n      retry: hookConfig?.retry,\n    });\n  };\n}\n\n// =============================================================================\n// INFINITE QUERY HOOK FACTORY\n// =============================================================================\n\n/**\n * Create an infinite query hook for paginated endpoints\n *\n * @example\n * ```typescript\n * const useInfiniteUsers = createInfiniteQueryHook<User[]>({\n *   client: apiClient,\n *   namespace: 'users',\n *   endpointName: 'list',\n *   endpoint: { method: 'GET', path: '/users' },\n *   getNextPageParam: (lastPage) => lastPage.pagination?.nextCursor,\n * });\n *\n * // In component\n * const { data, fetchNextPage, hasNextPage } = useInfiniteUsers();\n * ```\n */\nexport function createInfiniteQueryHook<TResponse>(config: {\n  client?: ApiClient;\n  namespace: string;\n  endpointName: string;\n  endpoint: ApiEndpoint;\n  getNextPageParam?: (lastPage: TResponse, pages: TResponse[]) => unknown | undefined;\n  getPreviousPageParam?: (firstPage: TResponse, pages: TResponse[]) => unknown | undefined;\n  pageParamName?: string;\n}) {\n  const {\n    client = apiClient,\n    namespace,\n    endpointName,\n    endpoint,\n    getNextPageParam,\n    getPreviousPageParam,\n    pageParamName = 'cursor',\n  } = config;\n\n  return (\n    params?: QueryRequestParams,\n    hookConfig?: InfiniteQueryHookConfig<TResponse>\n  ): ReturnType<typeof useInfiniteQuery<TResponse, ApiError>> => {\n    const queryKey = useMemo(\n      () => [...buildQueryKey(namespace, `${endpointName}.infinite`, params)],\n      [params]\n    );\n\n    return useInfiniteQuery<TResponse, ApiError>({\n      queryKey,\n      queryFn: async ({ pageParam }) => {\n        const url = substitutePathParams(endpoint.path, params?.pathParams);\n\n        const response = await client.get<TResponse>(url, {\n          params: {\n            ...(params?.queryParams as Record<string, string | number>),\n            [pageParamName]: pageParam,\n          } as import('./types').QueryParams,\n          timeout: endpoint.timeout,\n          ...params?.config,\n        });\n\n        return response.data;\n      },\n      initialPageParam: undefined,\n      getNextPageParam: (hookConfig?.getNextPageParam ?? getNextPageParam) as (\n        lastPage: TResponse,\n        allPages: TResponse[]\n      ) => string | number | undefined,\n      getPreviousPageParam: (hookConfig?.getPreviousPageParam ?? getPreviousPageParam) as (\n        firstPage: TResponse,\n        allPages: TResponse[]\n      ) => string | number | undefined,\n      enabled: hookConfig?.enabled,\n      staleTime: hookConfig?.staleTime,\n      gcTime: hookConfig?.gcTime,\n      refetchOnWindowFocus: hookConfig?.refetchOnWindowFocus,\n      refetchOnMount: hookConfig?.refetchOnMount,\n      retry: hookConfig?.retry,\n    });\n  };\n}\n\n// =============================================================================\n// OPTIMISTIC UPDATE HELPERS\n// =============================================================================\n\n/**\n * Configuration for optimistic updates\n */\nexport interface OptimisticUpdateConfig<TData, TVariables> {\n  /** Query key to update */\n  queryKey: QueryKey;\n  /** Function to compute optimistic update */\n  updater: (oldData: TData | undefined, variables: TVariables) => TData;\n  /** Whether to await the mutation */\n  awaitMutation?: boolean;\n  /**\n   * QueryClient instance for cache operations.\n   * Required - do not rely on global state.\n   */\n  queryClient: QueryClient;\n}\n\n/**\n * Create optimistic mutation handlers.\n *\n * IMPORTANT: Requires explicit QueryClient instance to avoid global state issues.\n * Use useQueryClient() hook in your component to get the client instance.\n *\n * @example\n * ```typescript\n * function UserComponent() {\n *   const queryClient = useQueryClient();\n *\n *   const updateUserMutation = useUpdateUser({\n *     ...createOptimisticMutation({\n *       queryClient,\n *       queryKey: ['users', userId],\n *       updater: (oldUser, variables) => ({ ...oldUser, ...variables.body }),\n *     }),\n *   });\n * }\n * ```\n */\nexport function createOptimisticMutation<TData, TVariables>(\n  config: OptimisticUpdateConfig<TData, TVariables>\n): Pick<\n  MutationHookConfig<TData, TVariables, ApiError, { previousData: TData | undefined }>,\n  'onMutate' | 'onError' | 'onSettled'\n> {\n  const { queryClient } = config;\n\n  return {\n    onMutate: async (variables: TVariables) => {\n      // Cancel outgoing refetches\n      await queryClient.cancelQueries({ queryKey: config.queryKey });\n\n      // Snapshot previous value\n      const previousData = queryClient.getQueryData<TData>(config.queryKey);\n\n      // Optimistically update\n      queryClient.setQueryData<TData>(config.queryKey, (old) => config.updater(old, variables));\n\n      return { previousData };\n    },\n\n    onError: (_error, _variables, context) => {\n      // Rollback on error\n      if (context?.previousData !== undefined) {\n        void queryClient.setQueryData(config.queryKey, context.previousData);\n      }\n    },\n\n    onSettled: () => {\n      // Refetch after mutation\n      void queryClient.invalidateQueries({ queryKey: config.queryKey });\n    },\n  };\n}\n\n/**\n * Create optimistic list update handlers (for adding/removing items).\n *\n * IMPORTANT: Requires explicit QueryClient instance to avoid global state issues.\n * Use useQueryClient() hook in your component to get the client instance.\n *\n * @example\n * ```typescript\n * function UserListComponent() {\n *   const queryClient = useQueryClient();\n *\n *   const deleteUserMutation = useDeleteUser({\n *     ...createOptimisticListMutation({\n *       queryClient,\n *       queryKey: ['users', 'list'],\n *       operation: 'remove',\n *       getItemId: (item) => item.id,\n *       getVariableId: (variables) => variables.pathParams?.id,\n *     }),\n *   });\n * }\n * ```\n */\nexport function createOptimisticListMutation<\n  TItem extends { id: string | number },\n  TVariables,\n>(config: {\n  /** QueryClient instance - required */\n  queryClient: QueryClient;\n  queryKey: QueryKey;\n  operation: 'add' | 'remove' | 'update';\n  getItemId: (item: TItem) => string | number;\n  getVariableId?: (variables: TVariables) => string | number | undefined;\n  getNewItem?: (variables: TVariables) => TItem;\n  getUpdatedItem?: (oldItem: TItem, variables: TVariables) => TItem;\n}): Pick<\n  MutationHookConfig<TItem, TVariables, ApiError, { previousData: TItem[] | undefined }>,\n  'onMutate' | 'onError' | 'onSettled'\n> {\n  const { queryClient } = config;\n\n  return {\n    onMutate: async (variables: TVariables) => {\n      await queryClient.cancelQueries({ queryKey: config.queryKey });\n\n      const previousData = queryClient.getQueryData<TItem[]>(config.queryKey);\n\n      queryClient.setQueryData<TItem[]>(config.queryKey, (old) => {\n        if (!old) return old;\n\n        switch (config.operation) {\n          case 'add':\n            if (config.getNewItem) {\n              return [...old, config.getNewItem(variables)];\n            }\n            return old;\n\n          case 'remove': {\n            const id = config.getVariableId?.(variables);\n            if (id === undefined) return old;\n            return old.filter((item) => config.getItemId(item) !== id);\n          }\n\n          case 'update': {\n            const id = config.getVariableId?.(variables);\n            if (id === undefined) return old;\n            return old.map((item) => {\n              if (config.getItemId(item) === id && config.getUpdatedItem) {\n                return config.getUpdatedItem(item, variables);\n              }\n              return item;\n            });\n          }\n\n          default:\n            return old;\n        }\n      });\n\n      return { previousData };\n    },\n\n    onError: (_error, _variables, context) => {\n      if (context?.previousData !== undefined) {\n        void queryClient.setQueryData(config.queryKey, context.previousData);\n      }\n    },\n\n    onSettled: () => {\n      void queryClient.invalidateQueries({ queryKey: config.queryKey });\n    },\n  };\n}\n\n// =============================================================================\n// ERROR HANDLING PATTERNS\n// =============================================================================\n\n/**\n * Error handling configuration\n */\nexport interface ErrorHandlerConfig {\n  /** Show toast notification */\n  showToast?: boolean;\n  /** Report to error tracking service */\n  reportError?: boolean;\n  /** Custom error messages by code */\n  messages?: Record<string, string>;\n  /** Retry configuration */\n  retryConfig?: {\n    maxRetries: number;\n    retryCondition?: (error: ApiError) => boolean;\n  };\n  /** Fallback data on error */\n  fallbackData?: unknown;\n  /** Custom error handler */\n  onError?: (error: ApiError) => void;\n}\n\n/**\n * Create standardized error handlers for queries\n *\n * @example\n * ```typescript\n * const errorHandler = createErrorHandler({\n *   showToast: true,\n *   messages: {\n *     'HTTP_404': 'User not found',\n *     'HTTP_403': 'You do not have permission to view this user',\n *   },\n * });\n *\n * const { data } = useUser({\n *   onError: errorHandler.onError,\n * });\n * ```\n */\nexport function createErrorHandler(config: ErrorHandlerConfig = {}): {\n  onError: (error: ApiError) => void;\n  retry: (failureCount: number, error: ApiError) => boolean;\n  fallbackData?: unknown;\n} {\n  const { showToast = true, reportError = true, messages = {} } = config;\n\n  return {\n    onError: (error: ApiError): void => {\n      // Get custom message or use default\n      const message = messages[error.code] ?? error.message;\n\n      // Show toast notification\n      if (showToast) {\n        // Integration with toast system\n        console.error('[API Error]', message, { error });\n      }\n\n      // Report to error tracking\n      if (reportError && error.severity === 'error') {\n        // Integration with error tracking service\n        console.error('[Error Tracking]', error);\n      }\n\n      // Call custom handler\n      config.onError?.(error);\n    },\n\n    retry: (failureCount: number, error: ApiError): boolean => {\n      if (!config.retryConfig) return false;\n      if (failureCount >= config.retryConfig.maxRetries) return false;\n      if (config.retryConfig.retryCondition) {\n        return config.retryConfig.retryCondition(error);\n      }\n      return error.retryable;\n    },\n\n    fallbackData: config.fallbackData,\n  };\n}\n\n// =============================================================================\n// PREFETCH HELPERS\n// =============================================================================\n\n/**\n * Create prefetch function for an endpoint\n *\n * @example\n * ```typescript\n * const prefetchUser = createPrefetch({\n *   client: apiClient,\n *   queryClient,\n *   namespace: 'users',\n *   endpointName: 'get',\n *   endpoint: { method: 'GET', path: '/users/:id' },\n * });\n *\n * // Prefetch on hover\n * onMouseEnter={() => prefetchUser({ pathParams: { id: userId } })}\n * ```\n */\nexport function createPrefetch<TResponse>(config: {\n  client?: ApiClient;\n  queryClient: QueryClient;\n  namespace: string;\n  endpointName: string;\n  endpoint: ApiEndpoint;\n  staleTime?: number;\n}) {\n  const {\n    client = apiClient,\n    queryClient,\n    namespace,\n    endpointName,\n    endpoint,\n    staleTime = TIMING.QUERY.STALE.MEDIUM,\n  } = config;\n\n  return async (params?: QueryRequestParams): Promise<void> => {\n    const queryKey = buildQueryKey(namespace, endpointName, params);\n    const url = substitutePathParams(endpoint.path, params?.pathParams);\n\n    await queryClient.prefetchQuery({\n      queryKey,\n      queryFn: async () => {\n        const response = await client.get<TResponse>(url, {\n          params: params?.queryParams,\n          timeout: endpoint.timeout,\n        });\n        return response.data;\n      },\n      staleTime,\n    });\n  };\n}\n\n// =============================================================================\n// UTILITY FUNCTIONS\n// =============================================================================\n\n/**\n * Substitute path parameters in URL\n */\nfunction substitutePathParams(path: string, params?: Record<string, string | number>): string {\n  if (!params) return path;\n\n  let url = path;\n  for (const [key, value] of Object.entries(params)) {\n    url = url.replace(`:${key}`, encodeURIComponent(String(value)));\n  }\n  return url;\n}\n\n/**\n * Capitalize first letter\n */\nfunction capitalize(str: string): string {\n  return str.charAt(0).toUpperCase() + str.slice(1);\n}\n\n/**\n * Convert endpoint name to hook name\n */\nexport function toHookName(name: string): string {\n  return `use${capitalize(name)}`;\n}\n\n/**\n * Extract pagination from response\n */\nexport function extractPaginationFromResponse<T>(response: PaginatedResponse<T>): PaginationMeta {\n  return response.pagination;\n}\n\n/**\n * Merge infinite query pages\n */\nexport function mergeInfinitePages<T>(pages: PaginatedResponse<T>[]): T[] {\n  return pages.flatMap((page) => page.items);\n}\n\n// =============================================================================\n// HOOK UTILITIES\n// =============================================================================\n\n/**\n * Hook to get stable query key reference\n */\nexport function useStableQueryKey(key: QueryKey): QueryKey {\n  const serialized = JSON.stringify(key);\n  // eslint-disable-next-line react-hooks/exhaustive-deps\n  return useMemo(() => key, [serialized]);\n}\n\n/**\n * Hook to track mutation loading state across multiple mutations\n */\nexport function useMutationLoadingState(...mutations: Array<{ isPending: boolean }>): boolean {\n  return mutations.some((m) => m.isPending);\n}\n\n/**\n * Hook to combine multiple mutation errors\n */\nexport function useCombinedMutationError(\n  ...mutations: Array<{ error: ApiError | null }>\n): ApiError | null {\n  return mutations.find((m) => m.error)?.error ?? null;\n}\n"],"names":["createQueryKeyFactory","namespace","cache","_","prop","args","buildQueryKey","endpointName","params","key","createApiHooks","contract","config","client","apiClient","defaultStaleTime","TIMING","hooks","endpoint","hookName","capitalize","createQueryHook","createMutationHook","_globalOnError","hookConfig","queryKey","useMemo","queryFn","useCallback","url","substitutePathParams","useQuery","_namespace","_endpointName","globalOnError","globalOnSuccess","queryClient","useQueryClient","mutationFn","variables","response","useMutation","data","context","error","createInfiniteQueryHook","getNextPageParam","getPreviousPageParam","pageParamName","useInfiniteQuery","pageParam","createOptimisticMutation","previousData","old","_error","_variables","createOptimisticListMutation","id","item","createErrorHandler","showToast","reportError","messages","message","failureCount","createPrefetch","staleTime","path","value","str","toHookName","name","extractPaginationFromResponse","mergeInfinitePages","pages","page","useStableQueryKey","serialized","useMutationLoadingState","mutations","m","useCombinedMutationError"],"mappings":";;;;;;AA4MO,SAASA,EACdC,GAC2D;AAC3D,QAAMC,wBAAY,IAAA;AAElB,SAAO,IAAI,MAAM,IAAiE;AAAA,IAChF,KAAK,CAACC,GAAGC,OACFF,EAAM,IAAIE,CAAI,KACjBF,EAAM,IAAIE,GAAM,IAAIC,MAAoB,CAACJ,GAAWG,GAAM,GAAGC,CAAI,CAAC,GAE7DH,EAAM,IAAIE,CAAI;AAAA,EACvB,CACD;AACH;AAKA,SAASE,EACPL,GACAM,GACAC,GACU;AACV,QAAMC,IAAiB,CAACR,GAAWM,CAAY;AAE/C,SAAIC,GAAQ,cACVC,EAAI,KAAKD,EAAO,UAAU,GAGxBA,GAAQ,eAAe,OAAO,KAAKA,EAAO,WAAW,EAAE,SAAS,KAClEC,EAAI,KAAKD,EAAO,WAAW,GAGtBC;AACT;AAmEO,SAASC,EACdC,GACAC,GACqB;AACrB,QAAM,EAAE,QAAAC,IAASC,GAAW,WAAAb,GAAW,kBAAAc,IAAmBC,EAAO,MAAM,MAAM,OAAA,IAAWJ,GAElFK,IAAiC,CAAA;AAEvC,aAAW,CAACV,GAAcW,CAAQ,KAAK,OAAO,QAAQP,CAAQ,GAAG;AAC/D,UAAMQ,IAAW,MAAMC,EAAWb,CAAY,CAAC;AAE/C,IAAIW,EAAS,WAAW,QAEtBD,EAAME,CAAQ,IAAIE;AAAA,MAChBR;AAAA,MACAZ;AAAA,MACAM;AAAA,MACAW;AAAA,MACAH;AAAA,MACAH,EAAO;AAAA,IAAA,IAITK,EAAME,CAAQ,IAAIG;AAAA,MAChBT;AAAA,MACAZ;AAAA,MACAM;AAAA,MACAW;AAAA,MACAN,EAAO;AAAA,MACPA,EAAO;AAAA,IAAA;AAAA,EAGb;AAEA,SAAOK;AACT;AAKA,SAASI,EACPR,GACAZ,GACAM,GACAW,GACAH,GACAQ,GACA;AACA,SAAO,CAACf,GAA6BgB,MAA4C;AAC/E,UAAMC,IAAWC;AAAA,MACf,MAAM;AAAA,QACJ,GAAGpB,EAAcL,GAAWM,GAAcC,CAAM;AAAA,QAChD,GAAIgB,GAAY,kBAAkB,CAAA;AAAA,MAAC;AAAA,MAErC,CAAChB,GAAQgB,GAAY,cAAc;AAAA,IAAA,GAG/BG,IAAUC,EAAY,YAAgC;AAC1D,YAAMC,IAAMC,EAAqBZ,EAAS,MAAMV,GAAQ,UAAU;AAQlE,cANiB,MAAMK,EAAO,IAAegB,GAAK;AAAA,QAChD,QAAQrB,GAAQ;AAAA,QAChB,SAASU,EAAS;AAAA,QAClB,GAAGV,GAAQ;AAAA,MAAA,CACZ,GAEe;AAAA,IAClB,GAAG,CAACA,CAAM,CAAC;AAEX,WAAOuB,EAA8B;AAAA,MACnC,UAAAN;AAAA,MACA,SAAAE;AAAA,MACA,SAASH,GAAY;AAAA,MACrB,WAAWA,GAAY,aAAaT;AAAA,MACpC,QAAQS,GAAY;AAAA,MACpB,sBAAsBA,GAAY;AAAA,MAClC,gBAAgBA,GAAY;AAAA,MAC5B,iBAAiBA,GAAY;AAAA,MAC7B,OAAOA,GAAY;AAAA,MACnB,QAAQA,GAAY;AAAA,MACpB,iBAAiBA,GAAY;AAAA,MAI7B,aAAaA,GAAY;AAAA,IAAA,CAC1B;AAAA,EACH;AACF;AAKA,SAASF,EACPT,GACAmB,GACAC,GACAf,GACAgB,GACAC,GACA;AACA,SAAO,CACLX,MACG;AACH,UAAMY,IAAcC,EAAA,GAEdC,IAAaV;AAAA,MACjB,OAAOW,MAAgE;AACrE,cAAMV,IAAMC,EAAqBZ,EAAS,MAAMqB,GAAW,UAAU;AAErE,YAAIC;AAEJ,gBAAQtB,EAAS,QAAA;AAAA,UACf,KAAK;AACH,YAAAsB,IAAW,MAAM3B,EAAO,KAAuBgB,GAAKU,GAAW,MAAM;AAAA,cACnE,QAAQA,GAAW;AAAA,cACnB,SAASrB,EAAS;AAAA,cAClB,GAAGqB,GAAW;AAAA,YAAA,CACf;AACD;AAAA,UACF,KAAK;AACH,YAAAC,IAAW,MAAM3B,EAAO,IAAsBgB,GAAKU,GAAW,MAAM;AAAA,cAClE,QAAQA,GAAW;AAAA,cACnB,SAASrB,EAAS;AAAA,cAClB,GAAGqB,GAAW;AAAA,YAAA,CACf;AACD;AAAA,UACF,KAAK;AACH,YAAAC,IAAW,MAAM3B,EAAO,MAAwBgB,GAAKU,GAAW,MAAM;AAAA,cACpE,QAAQA,GAAW;AAAA,cACnB,SAASrB,EAAS;AAAA,cAClB,GAAGqB,GAAW;AAAA,YAAA,CACf;AACD;AAAA,UACF,KAAK;AACH,YAAAC,IAAW,MAAM3B,EAAO,OAAkBgB,GAAK;AAAA,cAC7C,QAAQU,GAAW;AAAA,cACnB,SAASrB,EAAS;AAAA,cAClB,GAAGqB,GAAW;AAAA,YAAA,CACf;AACD;AAAA,UACF;AACE,YAAAC,IAAW,MAAM3B,EAAO,QAAmB;AAAA,cACzC,QAAQK,EAAS;AAAA,cACjB,KAAAW;AAAA,cACA,MAAMU,GAAW;AAAA,cACjB,QAAQA,GAAW;AAAA,cACnB,SAASrB,EAAS;AAAA,cAClB,GAAGqB,GAAW;AAAA,YAAA,CACf;AAAA,QAAA;AAGL,eAAOC,EAAS;AAAA,MAClB;AAAA,MACA,CAAA;AAAA,IAAC;AAGH,WAAOC,EAAyE;AAAA,MAC9E,YAAAH;AAAA,MACA,UAAUd,GAAY;AAAA,MACtB,WAAW,OAAOkB,GAAMH,GAAWI,MAAY;AAE7C,QAAAnB,GAAY,YAAYkB,GAAMH,GAAWI,CAAO,GAChDR,IAAkBO,CAAI,GAGlBlB,GAAY,qBACd,MAAM,QAAQ;AAAA,UACZA,EAAW,kBAAkB;AAAA,YAAI,OAAOf,MACtC2B,EAAY,kBAAkB,EAAE,UAAU3B,GAAK;AAAA,UAAA;AAAA,QACjD,GAKAe,GAAY,kBACd,MAAM,QAAQ;AAAA,UACZA,EAAW,eAAe;AAAA,YAAI,OAAOf,MACnC2B,EAAY,eAAe,EAAE,UAAU3B,GAAK;AAAA,UAAA;AAAA,QAC9C;AAAA,MAGN;AAAA,MACA,SAAS,CAACmC,GAAOL,GAAWI,MAAY;AACtC,QAAAnB,GAAY,UAAUoB,GAAOL,GAAWI,CAAO,GAC/CT,IAAgBU,CAAK;AAAA,MACvB;AAAA,MACA,WAAWpB,GAAY;AAAA,MACvB,OAAOA,GAAY;AAAA,IAAA,CACpB;AAAA,EACH;AACF;AAuBO,SAASqB,EAAmCjC,GAQhD;AACD,QAAM;AAAA,IACJ,QAAAC,IAASC;AAAA,IACT,WAAAb;AAAA,IACA,cAAAM;AAAA,IACA,UAAAW;AAAA,IACA,kBAAA4B;AAAA,IACA,sBAAAC;AAAA,IACA,eAAAC,IAAgB;AAAA,EAAA,IACdpC;AAEJ,SAAO,CACLJ,GACAgB,MAC6D;AAC7D,UAAMC,IAAWC;AAAA,MACf,MAAM,CAAC,GAAGpB,EAAcL,GAAW,GAAGM,CAAY,aAAaC,CAAM,CAAC;AAAA,MACtE,CAACA,CAAM;AAAA,IAAA;AAGT,WAAOyC,EAAsC;AAAA,MAC3C,UAAAxB;AAAA,MACA,SAAS,OAAO,EAAE,WAAAyB,QAAgB;AAChC,cAAMrB,IAAMC,EAAqBZ,EAAS,MAAMV,GAAQ,UAAU;AAWlE,gBATiB,MAAMK,EAAO,IAAegB,GAAK;AAAA,UAChD,QAAQ;AAAA,YACN,GAAIrB,GAAQ;AAAA,YACZ,CAACwC,CAAa,GAAGE;AAAA,UAAA;AAAA,UAEnB,SAAShC,EAAS;AAAA,UAClB,GAAGV,GAAQ;AAAA,QAAA,CACZ,GAEe;AAAA,MAClB;AAAA,MACA,kBAAkB;AAAA,MAClB,kBAAmBgB,GAAY,oBAAoBsB;AAAA,MAInD,sBAAuBtB,GAAY,wBAAwBuB;AAAA,MAI3D,SAASvB,GAAY;AAAA,MACrB,WAAWA,GAAY;AAAA,MACvB,QAAQA,GAAY;AAAA,MACpB,sBAAsBA,GAAY;AAAA,MAClC,gBAAgBA,GAAY;AAAA,MAC5B,OAAOA,GAAY;AAAA,IAAA,CACpB;AAAA,EACH;AACF;AA4CO,SAAS2B,EACdvC,GAIA;AACA,QAAM,EAAE,aAAAwB,MAAgBxB;AAExB,SAAO;AAAA,IACL,UAAU,OAAO2B,MAA0B;AAEzC,YAAMH,EAAY,cAAc,EAAE,UAAUxB,EAAO,UAAU;AAG7D,YAAMwC,IAAehB,EAAY,aAAoBxB,EAAO,QAAQ;AAGpE,aAAAwB,EAAY,aAAoBxB,EAAO,UAAU,CAACyC,MAAQzC,EAAO,QAAQyC,GAAKd,CAAS,CAAC,GAEjF,EAAE,cAAAa,EAAA;AAAA,IACX;AAAA,IAEA,SAAS,CAACE,GAAQC,GAAYZ,MAAY;AAExC,MAAIA,GAAS,iBAAiB,UACvBP,EAAY,aAAaxB,EAAO,UAAU+B,EAAQ,YAAY;AAAA,IAEvE;AAAA,IAEA,WAAW,MAAM;AAEf,MAAKP,EAAY,kBAAkB,EAAE,UAAUxB,EAAO,UAAU;AAAA,IAClE;AAAA,EAAA;AAEJ;AAyBO,SAAS4C,EAGd5C,GAYA;AACA,QAAM,EAAE,aAAAwB,MAAgBxB;AAExB,SAAO;AAAA,IACL,UAAU,OAAO2B,MAA0B;AACzC,YAAMH,EAAY,cAAc,EAAE,UAAUxB,EAAO,UAAU;AAE7D,YAAMwC,IAAehB,EAAY,aAAsBxB,EAAO,QAAQ;AAEtE,aAAAwB,EAAY,aAAsBxB,EAAO,UAAU,CAACyC,MAAQ;AAC1D,YAAI,CAACA,EAAK,QAAOA;AAEjB,gBAAQzC,EAAO,WAAA;AAAA,UACb,KAAK;AACH,mBAAIA,EAAO,aACF,CAAC,GAAGyC,GAAKzC,EAAO,WAAW2B,CAAS,CAAC,IAEvCc;AAAA,UAET,KAAK,UAAU;AACb,kBAAMI,IAAK7C,EAAO,gBAAgB2B,CAAS;AAC3C,mBAAIkB,MAAO,SAAkBJ,IACtBA,EAAI,OAAO,CAACK,MAAS9C,EAAO,UAAU8C,CAAI,MAAMD,CAAE;AAAA,UAC3D;AAAA,UAEA,KAAK,UAAU;AACb,kBAAMA,IAAK7C,EAAO,gBAAgB2B,CAAS;AAC3C,mBAAIkB,MAAO,SAAkBJ,IACtBA,EAAI,IAAI,CAACK,MACV9C,EAAO,UAAU8C,CAAI,MAAMD,KAAM7C,EAAO,iBACnCA,EAAO,eAAe8C,GAAMnB,CAAS,IAEvCmB,CACR;AAAA,UACH;AAAA,UAEA;AACE,mBAAOL;AAAA,QAAA;AAAA,MAEb,CAAC,GAEM,EAAE,cAAAD,EAAA;AAAA,IACX;AAAA,IAEA,SAAS,CAACE,GAAQC,GAAYZ,MAAY;AACxC,MAAIA,GAAS,iBAAiB,UACvBP,EAAY,aAAaxB,EAAO,UAAU+B,EAAQ,YAAY;AAAA,IAEvE;AAAA,IAEA,WAAW,MAAM;AACf,MAAKP,EAAY,kBAAkB,EAAE,UAAUxB,EAAO,UAAU;AAAA,IAClE;AAAA,EAAA;AAEJ;AA6CO,SAAS+C,EAAmB/C,IAA6B,IAI9D;AACA,QAAM,EAAE,WAAAgD,IAAY,IAAM,aAAAC,IAAc,IAAM,UAAAC,IAAW,CAAA,MAAOlD;AAEhE,SAAO;AAAA,IACL,SAAS,CAACgC,MAA0B;AAElC,YAAMmB,IAAUD,EAASlB,EAAM,IAAI,KAAKA,EAAM;AAG9C,MAAIgB,KAEF,QAAQ,MAAM,eAAeG,GAAS,EAAE,OAAAnB,GAAO,GAI7CiB,KAAejB,EAAM,aAAa,WAEpC,QAAQ,MAAM,oBAAoBA,CAAK,GAIzChC,EAAO,UAAUgC,CAAK;AAAA,IACxB;AAAA,IAEA,OAAO,CAACoB,GAAsBpB,MACxB,CAAChC,EAAO,eACRoD,KAAgBpD,EAAO,YAAY,aAAmB,KACtDA,EAAO,YAAY,iBACdA,EAAO,YAAY,eAAegC,CAAK,IAEzCA,EAAM;AAAA,IAGf,cAAchC,EAAO;AAAA,EAAA;AAEzB;AAuBO,SAASqD,EAA0BrD,GAOvC;AACD,QAAM;AAAA,IACJ,QAAAC,IAASC;AAAA,IACT,aAAAsB;AAAA,IACA,WAAAnC;AAAA,IACA,cAAAM;AAAA,IACA,UAAAW;AAAA,IACA,WAAAgD,IAAYlD,EAAO,MAAM,MAAM;AAAA,EAAA,IAC7BJ;AAEJ,SAAO,OAAOJ,MAA+C;AAC3D,UAAMiB,IAAWnB,EAAcL,GAAWM,GAAcC,CAAM,GACxDqB,IAAMC,EAAqBZ,EAAS,MAAMV,GAAQ,UAAU;AAElE,UAAM4B,EAAY,cAAc;AAAA,MAC9B,UAAAX;AAAA,MACA,SAAS,aACU,MAAMZ,EAAO,IAAegB,GAAK;AAAA,QAChD,QAAQrB,GAAQ;AAAA,QAChB,SAASU,EAAS;AAAA,MAAA,CACnB,GACe;AAAA,MAElB,WAAAgD;AAAA,IAAA,CACD;AAAA,EACH;AACF;AASA,SAASpC,EAAqBqC,GAAc3D,GAAkD;AAC5F,MAAI,CAACA,EAAQ,QAAO2D;AAEpB,MAAItC,IAAMsC;AACV,aAAW,CAAC1D,GAAK2D,CAAK,KAAK,OAAO,QAAQ5D,CAAM;AAC9C,IAAAqB,IAAMA,EAAI,QAAQ,IAAIpB,CAAG,IAAI,mBAAmB,OAAO2D,CAAK,CAAC,CAAC;AAEhE,SAAOvC;AACT;AAKA,SAAST,EAAWiD,GAAqB;AACvC,SAAOA,EAAI,OAAO,CAAC,EAAE,gBAAgBA,EAAI,MAAM,CAAC;AAClD;AAKO,SAASC,EAAWC,GAAsB;AAC/C,SAAO,MAAMnD,EAAWmD,CAAI,CAAC;AAC/B;AAKO,SAASC,EAAiChC,GAAgD;AAC/F,SAAOA,EAAS;AAClB;AAKO,SAASiC,EAAsBC,GAAoC;AACxE,SAAOA,EAAM,QAAQ,CAACC,MAASA,EAAK,KAAK;AAC3C;AASO,SAASC,EAAkBnE,GAAyB;AACzD,QAAMoE,IAAa,KAAK,UAAUpE,CAAG;AAErC,SAAOiB,EAAQ,MAAMjB,GAAK,CAACoE,CAAU,CAAC;AACxC;AAKO,SAASC,KAA2BC,GAAmD;AAC5F,SAAOA,EAAU,KAAK,CAACC,MAAMA,EAAE,SAAS;AAC1C;AAKO,SAASC,KACXF,GACc;AACjB,SAAOA,EAAU,KAAK,CAACC,MAAMA,EAAE,KAAK,GAAG,SAAS;AAClD;"}