uikit/src/core/createApi.js
2026-06-21 15:09:19 +03:30

163 lines
3.6 KiB
JavaScript

import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { api, fetcher as defaultFetcher } from "./api.js";
function getBaseKey(key) {
return Array.isArray(key) ? key : [key];
}
function getListKey(key) {
return [...getBaseKey(key), "list"];
}
function getItemKey(key, id) {
return [...getBaseKey(key), id];
}
function buildUrl(url, params) {
if (!params) return url;
const searchParams = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== "") {
searchParams.append(key, value);
}
});
const query = searchParams.toString();
return query ? `${url}?${query}` : url;
}
function resolveUrl(url, data) {
if (typeof url === "function") {
return url(data?.id, data);
}
return url;
}
function omitId(data = {}) {
const { id, ...rest } = data;
return rest;
}
function joinUrl(baseUrl, id) {
return `${baseUrl.replace(/\/$/, "")}/${id}`;
}
function toHookName(actionName) {
return `use${actionName.charAt(0).toUpperCase()}${actionName.slice(1)}`;
}
export function createApi({
key,
url,
actions = {},
axiosInstance = api,
fetcher = defaultFetcher,
}) {
const baseKey = getBaseKey(key);
const listKey = getListKey(key);
function useGet(params, options = {}) {
return useQuery({
queryKey: params ? [...listKey, params] : listKey,
queryFn: () => fetcher(buildUrl(url, params)),
...options,
});
}
function useItem(id, options = {}) {
return useQuery({
queryKey: getItemKey(key, id),
queryFn: () => fetcher(joinUrl(url, id)),
enabled: Boolean(id),
...options,
});
}
function useAction(actionName, options = {}) {
const queryClient = useQueryClient();
const action = actions[actionName];
if (!action) {
throw new Error(`Action "${actionName}" is not defined.`);
}
return useMutation({
mutationKey: [...baseKey, actionName],
mutationFn: async (data = {}) => {
const method = action.method || "post";
const requestUrl = resolveUrl(action.url || url, data);
const payload = action.body
? action.body(data)
: action.sendRawPayload
? data
: omitId(data);
const response =
method === "delete"
? await axiosInstance.delete(
requestUrl,
payload && Object.keys(payload).length > 0
? { data: payload }
: undefined,
)
: await axiosInstance[method](requestUrl, payload);
return response.data;
},
onSuccess: (...args) => {
const [responseData, variables] = args;
queryClient.invalidateQueries({
queryKey: listKey,
});
const id = variables?.id ?? responseData?.id;
if (id) {
queryClient.invalidateQueries({
queryKey: getItemKey(key, id),
});
}
const extraInvalidateKeys =
typeof action.invalidateKeys === "function"
? action.invalidateKeys(responseData, variables)
: action.invalidateKeys;
extraInvalidateKeys?.forEach((queryKey) => {
queryClient.invalidateQueries({ queryKey });
});
options.onSuccess?.(...args);
},
...options,
});
}
const actionHooks = Object.keys(actions).reduce((acc, actionName) => {
acc[toHookName(actionName)] = (options = {}) =>
useAction(actionName, options);
return acc;
}, {});
return {
key: baseKey,
listKey,
useGet,
useItem,
useAction,
...actionHooks,
};
}