Files
WhiteNightsAdminPanel/src/shared/api/mobxFetch/index.ts
2026-03-18 20:11:07 +03:00

184 lines
4.3 KiB
TypeScript

import { runInAction } from "mobx";
type mobxFetchOptions<RequestType, ResponseType, Store> = {
store: Store;
value?: keyof Store;
values?: Array<keyof Store>;
loading?: keyof Store;
error?: keyof Store;
fn: RequestType extends void
? (signal?: AbortSignal) => Promise<ResponseType>
: (request: RequestType, signal?: AbortSignal) => Promise<ResponseType>;
pollingInterval?: number;
resetValue?: boolean;
transform?: (response: ResponseType) => Partial<Record<string, any>>;
onSuccess?: (response: ResponseType) => void;
};
type FetchFunction<RequestType, ResponseType> = RequestType extends void
? {
(): Promise<ResponseType | null>;
stopPolling?: () => void;
}
: {
(request: RequestType): Promise<ResponseType | null>;
stopPolling?: () => void;
};
export function mobxFetch<ResponseType, Store extends Record<string, any>>(
options: mobxFetchOptions<void, ResponseType, Store>
): FetchFunction<void, ResponseType>;
export function mobxFetch<
RequestType,
ResponseType,
Store extends Record<string, any>,
>(
options: mobxFetchOptions<RequestType, ResponseType, Store>
): FetchFunction<RequestType, ResponseType>;
export function mobxFetch<
RequestType,
ResponseType,
Store extends Record<string, any>,
>(
options: mobxFetchOptions<RequestType, ResponseType, Store>
): FetchFunction<RequestType, ResponseType> {
const {
store,
value,
values,
loading,
error,
fn,
pollingInterval,
resetValue,
transform,
onSuccess,
} = options;
let abortController: AbortController | undefined;
let pollingTimer: ReturnType<typeof setInterval> | undefined;
let currentRequest: RequestType | undefined;
const stopPolling = () => {
if (pollingTimer) {
clearInterval(pollingTimer);
pollingTimer = undefined;
}
abortController?.abort();
};
const fetch = async (request?: RequestType): Promise<ResponseType | null> => {
abortController?.abort();
abortController = new AbortController();
currentRequest = request as RequestType;
runInAction(() => {
if (value) {
(store[value] as any) = resetValue ? null : store[value];
}
if (values) {
values.forEach((key) => {
(store[key] as any) = resetValue ? null : store[key];
});
}
if (error) {
(store[error] as any) = null;
}
if (loading) {
(store[loading] as any) = true;
}
});
try {
const result = await (
fn as (
request?: RequestType,
signal?: AbortSignal
) => Promise<ResponseType>
)(request, abortController.signal);
runInAction(() => {
if (values && transform) {
const transformed = transform(result) as Record<string, any>;
values.forEach((key) => {
const k = key as string;
if (k in transformed) {
(store[key] as any) = transformed[k];
}
});
} else if (value) {
(store[value] as any) = result as ResponseType;
}
if (loading) {
(store[loading] as any) = false;
}
if (error) {
(store[error] as any) = null;
}
});
if (pollingInterval && !pollingTimer) {
pollingTimer = setInterval(() => {
if (currentRequest !== undefined) {
fetch(currentRequest);
} else {
fetch();
}
}, pollingInterval);
}
if (onSuccess) {
onSuccess(result);
}
return result;
} catch (err) {
if (!(err instanceof Error && err.name === "CanceledError")) {
runInAction(() => {
if (error) {
(store[error] as any) =
err instanceof Error ? err.message : String(err);
}
if (loading) {
(store[loading] as any) = false;
}
if (value) {
(store[value] as any) = null;
}
if (values) {
values.forEach((key) => {
(store[key] as any) = null;
});
}
});
throw err;
}
return null;
}
};
const fetchWithStopPolling = fetch as FetchFunction<
RequestType,
ResponseType
>;
if (pollingInterval) {
fetchWithStopPolling.stopPolling = stopPolling;
}
return fetchWithStopPolling;
}