import { AxiosInstance, AxiosResponse } from 'axios'; import { QueryKey, UseQueryOptions, UseQueryResult, UseMutationOptions, UseMutationResult } from '@tanstack/react-query'; /** * configure — @linkup/api-hooks 전역 설정 주입 * * 앱 최상단(providers, _app.tsx 등)에서 한 번만 호출한다. * * @example * configure({ * axios: myAxiosInstance, * onError: (err) => toast.error(getErrorMessage(err)), * onPendingChange: (delta) => setPendingCount((c) => c + delta), * extractData: (res) => res.data.data, * }); */ interface ApiHooksConfig { /** 프로젝트에서 사용하는 axios 인스턴스 (인터셉터 포함) */ axios: AxiosInstance; /** * API 에러 발생 시 호출되는 핸들러. * toast, 로깅 등을 여기서 처리한다. */ onError?: (error: unknown) => void; /** * 뮤테이션 pending 상태 변화 콜백. * delta 1 = 요청 시작, -1 = 요청 종료. * 전역 로딩 스피너 등에 활용한다. */ onPendingChange?: (delta: 1 | -1) => void; /** * axios 응답에서 실제 데이터를 추출하는 함수. * 기본값: (res) => res.data.data * * @example // { code, message, data } 구조일 때 * extractData: (res) => res.data.data * * @example // { result } 구조일 때 * extractData: (res) => res.data.result */ extractData?: (response: AxiosResponse) => T; } /** 전역 설정 등록. 앱 초기화 시 한 번만 호출한다. */ declare function configure(config: ApiHooksConfig): void; /** * useApiQuery — GET 요청 공통 훅 (React Query useQuery 래퍼) * * 기본 동작: * - 윈도우 포커스 시 자동 재요청 * - 실패 시 재시도 없음 * - 이전 데이터 유지 (placeholderData) * * @example // 기본 * const { data, isLoading } = useApiQuery({ * queryKey: ["members"], * url: "/api/members", * }); * * @example // 파라미터 + 타입 변환(select) * const { data } = useApiQuery({ * queryKey: ["members", page], * url: "/api/members", * params: { page, size: 20 }, * options: { * select: (res) => res.list, * enabled: !!userId, * }, * }); */ interface UseApiQueryProps { /** React Query 캐시 키 */ queryKey: QueryKey; /** GET 요청 URL */ url: string; /** 쿼리 파라미터 (?key=value) */ params?: Record; /** useQuery 추가 옵션 (queryKey/queryFn 제외) */ options?: Omit, "queryKey" | "queryFn">; } declare function useApiQuery({ queryKey, url, params, options, }: UseApiQueryProps): UseQueryResult; /** * useApiMutation — POST/PATCH/PUT/DELETE 요청 공통 훅 (React Query useMutation 래퍼) * * 주요 기능: * - 동일 URL+Method 중복 요청 자동 차단 (inFlight) * - 요청 시작/종료 시 onPendingChange 콜백 (전역 로딩 스피너 연동) * - 성공 후 refreshKeys 자동 invalidate * - 에러 발생 시 onError 콜백 (configure에서 설정한 핸들러 호출) * * 호출 방식 A — 훅에 url/method 고정: * @example * const mutation = useApiMutation({ url: "/api/members", method: "post" }); * mutation.mutate({ name: "홍길동", email: "hong@link-up.kr" }); * * 호출 방식 B — mutate 시점에 url/method 결정 (url/method 생략): * @example * const mutation = useApiMutation({}); * mutation.mutate({ url: "/api/members/1", method: "patch", data: { name: "홍길동" } }); * * refreshKeys 사용: * @example * const mutation = useApiMutation({ * url: "/api/members", * method: "post", * refreshKeys: ["members", "members-stats"], * }); */ type HttpMethod = "post" | "patch" | "put" | "delete"; /** * mutate() 에 url/method/data 를 직접 넘기는 동적 변수 형태. * 훅에 url/method 를 고정하지 않을 때 사용한다. */ interface DynamicMutationVariables { url: string; method: HttpMethod; data?: TData; /** 이 mutate 호출에만 적용할 캐시 무효화 키 (훅의 refreshKeys 보다 우선) */ refreshKeys?: string[]; } interface UseApiMutationProps { /** 요청 URL. 훅 레벨에서 고정할 때 사용. */ url?: string; /** HTTP 메서드. 훅 레벨에서 고정할 때 사용. */ method?: HttpMethod; /** * 성공 후 무효화할 React Query 키 목록 (문자열 배열). * @example refreshKeys={["members", "members-stats"]} */ refreshKeys?: string[]; /** 성공 콜백 (onSuccess) */ onSuccess?: (data: TResult, variables: TVariables) => void; /** 에러 콜백 — configure 의 onError 에 추가로 호출된다 */ onError?: (error: Error, variables: TVariables) => void; /** useMutation 나머지 옵션 (mutationFn / onSuccess / onError 제외) */ options?: Omit, "mutationFn" | "onSuccess" | "onError">; } declare function useApiMutation({ url: hookUrl, method: hookMethod, refreshKeys: hookRefreshKeys, onSuccess: externalOnSuccess, onError: externalOnError, options, }: UseApiMutationProps): UseMutationResult; /** * createApiErrorHandler — 클라이언트 공통 API 에러 핸들러 팩토리 * * configure()의 onError 콜백으로 주입해 useApiMutation 에러를 전역 처리한다. * toast, redirect 등 프로젝트별 로직은 옵션으로 주입한다. * * 서버 응답 형식: { detail?: string, message?: string, fieldErrors?: unknown[] } * fieldErrors가 있으면(zod 검증 에러) message를 우선 추출하고, * 없으면 detail을 우선 추출한다(그 다음 message). * * @example * // providers.tsx * import { createApiErrorHandler, configure } from "@sunkim4638/admin-modules/api-hooks"; * import { toast } from "sonner"; * * const handleApiErr = createApiErrorHandler({ * onMessage: (msg) => toast.error(msg), * onUnauthorized: () => window.location.href = "/login", * }); * * configure({ axios: ax, onError: handleApiErr }); */ interface ApiErrorHandlerOptions { /** * 에러 메시지를 표시할 함수. * toast.error, alert 등 프로젝트에 맞게 주입한다. * 기본: console.error */ onMessage?: (message: string) => void; /** 401 Unauthorized 처리 — 기본: 아무것도 안 함 */ onUnauthorized?: () => void; /** 403 Forbidden 처리 — 기본: onMessage로 "권한이 없습니다." 표시 */ onForbidden?: () => void; /** * 서버 응답에서 메시지를 추출하는 함수 * 기본: data.detail → data.message 순으로 추출 */ extractMessage?: (data: any) => string | undefined; /** 개발 환경에서 console.error 출력 여부 — 기본: true */ debug?: boolean; } declare function createApiErrorHandler(options?: ApiErrorHandlerOptions): (error: unknown) => void; /** * handleApiErr — sonner toast를 사용한 공통 API 에러 핸들러 * * configure()의 onError에 직접 전달한다. * 401 처리는 axios interceptor에서 하고 여기선 메시지 표시만 담당한다. * * @example * // providers.tsx * import { handleApiErr, configure } from "@sunkim4638/admin-modules/api-hooks"; * * configure({ axios: ax, onError: handleApiErr }); */ declare const handleApiErr: (error: unknown) => void; export { type ApiErrorHandlerOptions, type ApiHooksConfig, type DynamicMutationVariables, type HttpMethod, type UseApiMutationProps, type UseApiQueryProps, configure, createApiErrorHandler, handleApiErr, useApiMutation, useApiQuery };