@krymskyimaksym/react-api-client 2.0.0-beta.0 → 2.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -0
- package/dist/index.d.mts +142 -14
- package/dist/index.d.ts +142 -14
- package/dist/index.js +285 -45
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +283 -47
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -290,6 +290,70 @@ const httpClient = {
|
|
|
290
290
|
};
|
|
291
291
|
```
|
|
292
292
|
|
|
293
|
+
## `mutate` vs `mutateAsync`
|
|
294
|
+
|
|
295
|
+
`useMutation` возвращает оба варианта вызова — намеренно с разными типами.
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
const { mutate, mutateAsync } = api.useMutation();
|
|
299
|
+
|
|
300
|
+
// ❌ Анти-паттерн: mutate возвращает void, не Promise.
|
|
301
|
+
// `await` отдаст undefined; try/catch не сработает.
|
|
302
|
+
await mutate({ id: 1 });
|
|
303
|
+
|
|
304
|
+
// ✅ Правильно: для последовательной логики или try/catch — mutateAsync.
|
|
305
|
+
try {
|
|
306
|
+
const result = await mutateAsync({ id: 1 });
|
|
307
|
+
} catch (e) {
|
|
308
|
+
// обработка
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ✅ Правильно: для fire-and-forget (кнопка с onClick) — mutate.
|
|
312
|
+
<Button onPress={() => mutate({ id: 1 })} />;
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Если включён `throwOnError: true` — для критичных мутаций используй
|
|
316
|
+
`mutateAsync` и обёртку `try/catch`, иначе ошибка не будет
|
|
317
|
+
поймана в caller'е.
|
|
318
|
+
|
|
319
|
+
## SSR / hydrate
|
|
320
|
+
|
|
321
|
+
Кэш сериализуется через `cache.dehydrate()` и восстанавливается через
|
|
322
|
+
`cache.hydrate(state)`. Стандартный SSR-паттерн:
|
|
323
|
+
|
|
324
|
+
```ts
|
|
325
|
+
// На сервере
|
|
326
|
+
const client = new QueryClient();
|
|
327
|
+
await client.fetchQuery(['orders'], () => fetchOrders());
|
|
328
|
+
const dehydratedState = client.cache.dehydrate();
|
|
329
|
+
|
|
330
|
+
// Встраиваем в HTML
|
|
331
|
+
res.send(`
|
|
332
|
+
<html>
|
|
333
|
+
<body>
|
|
334
|
+
<div id="root">${renderToString(<App />)}</div>
|
|
335
|
+
<script>
|
|
336
|
+
window.__APP_DATA__ = ${JSON.stringify(dehydratedState)};
|
|
337
|
+
</script>
|
|
338
|
+
</body>
|
|
339
|
+
</html>
|
|
340
|
+
`);
|
|
341
|
+
|
|
342
|
+
// На клиенте
|
|
343
|
+
const client = new QueryClient();
|
|
344
|
+
client.cache.hydrate(window.__APP_DATA__);
|
|
345
|
+
ReactDOM.hydrateRoot(
|
|
346
|
+
document.getElementById('root'),
|
|
347
|
+
<ApiClientProvider client={client}>
|
|
348
|
+
<App />
|
|
349
|
+
</ApiClientProvider>,
|
|
350
|
+
);
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Гидратированные данные сразу помечаются как `isStale: true` → подписанные
|
|
354
|
+
`useFetch` отдают серверные данные мгновенно и в фоне делают refetch для
|
|
355
|
+
проверки актуальности.
|
|
356
|
+
|
|
293
357
|
## License
|
|
294
358
|
|
|
295
359
|
MIT
|
package/dist/index.d.mts
CHANGED
|
@@ -34,11 +34,29 @@ type QueryEntry<T> = {
|
|
|
34
34
|
state: QueryState<T>;
|
|
35
35
|
subscribers: Set<Listener$2>;
|
|
36
36
|
inflight: Promise<T> | null;
|
|
37
|
+
inflightController: AbortController | null;
|
|
37
38
|
gcTimer: ReturnType<typeof setTimeout> | null;
|
|
38
39
|
staleTime: number;
|
|
39
40
|
gcTime: number;
|
|
41
|
+
/**
|
|
42
|
+
* Последний queryFn, переданный в fetch для этого ключа.
|
|
43
|
+
* Используется refetchQueries: позволяет перезапустить запрос,
|
|
44
|
+
* не зная queryFn из caller'а (например, push-handler).
|
|
45
|
+
*/
|
|
46
|
+
lastQueryFn: QueryFn<T> | null;
|
|
47
|
+
};
|
|
48
|
+
type QueryFnContext = {
|
|
49
|
+
signal: AbortSignal;
|
|
40
50
|
};
|
|
41
|
-
|
|
51
|
+
/**
|
|
52
|
+
* queryFn принимает контекст с AbortSignal. Если HTTP-клиент его
|
|
53
|
+
* использует — отмена будет реальной; если игнорирует — поведение
|
|
54
|
+
* деградирует до текущего (запрос идёт до конца, но кэш игнорирует результат).
|
|
55
|
+
*
|
|
56
|
+
* Для обратной совместимости старая сигнатура `() => Promise<T>` тоже
|
|
57
|
+
* принимается — пакет просто не передаст signal внутрь.
|
|
58
|
+
*/
|
|
59
|
+
type QueryFn<T> = (ctx: QueryFnContext) => Promise<T>;
|
|
42
60
|
type FetchOptions = {
|
|
43
61
|
staleTime?: number;
|
|
44
62
|
gcTime?: number;
|
|
@@ -51,6 +69,14 @@ type FetchOptions = {
|
|
|
51
69
|
*/
|
|
52
70
|
declare class QueryCache {
|
|
53
71
|
private entries;
|
|
72
|
+
private globalListeners;
|
|
73
|
+
/**
|
|
74
|
+
* Подписка на любое изменение кэша: setData, invalidate, remove,
|
|
75
|
+
* успешный/ошибочный fetch. Используется persistQueryClient и
|
|
76
|
+
* devtools-подобными адаптерами. Не дублирует `subscribe(key, ...)`.
|
|
77
|
+
*/
|
|
78
|
+
subscribeAll(listener: Listener$2): () => void;
|
|
79
|
+
private notifyGlobal;
|
|
54
80
|
private ensureEntry;
|
|
55
81
|
getState<T>(key: QueryKey): QueryState<T> | undefined;
|
|
56
82
|
getData<T>(key: QueryKey): T | undefined;
|
|
@@ -72,6 +98,13 @@ declare class QueryCache {
|
|
|
72
98
|
* Если есть подписчики — они получат уведомление, чтобы инициировать refetch.
|
|
73
99
|
*/
|
|
74
100
|
invalidate(predicate: QueryKey | ((key: QueryKey) => boolean)): string[];
|
|
101
|
+
/**
|
|
102
|
+
* Перезапускает все записи, матчинг predicate, у которых сохранён
|
|
103
|
+
* `lastQueryFn` (т.е. их хоть раз кто-то загрузил через `fetch`).
|
|
104
|
+
* Возвращает promise, который резолвится когда все запросы завершились.
|
|
105
|
+
* Ошибки отдельных запросов проглатываются — общий promise успешный.
|
|
106
|
+
*/
|
|
107
|
+
refetchQueries(predicate: QueryKey | ((key: QueryKey) => boolean)): Promise<void>;
|
|
75
108
|
/**
|
|
76
109
|
* Отменяет «привязку» inflight-промиса к ключу. Сам HTTP-запрос
|
|
77
110
|
* продолжит исполняться (executeRequest не использует AbortSignal),
|
|
@@ -84,6 +117,12 @@ declare class QueryCache {
|
|
|
84
117
|
* Используется редко — обычно достаточно invalidate.
|
|
85
118
|
*/
|
|
86
119
|
remove(predicate: QueryKey | ((key: QueryKey) => boolean)): void;
|
|
120
|
+
/**
|
|
121
|
+
* Количество inflight-запросов в кэше, опционально отфильтрованных
|
|
122
|
+
* по predicate. Используется `useIsFetching()` для глобального
|
|
123
|
+
* индикатора загрузки.
|
|
124
|
+
*/
|
|
125
|
+
countFetching(predicate?: QueryKey | ((key: QueryKey) => boolean)): number;
|
|
87
126
|
/** Только для тестов / DevTools. */
|
|
88
127
|
_debugEntries(): ReadonlyMap<string, QueryEntry<unknown>>;
|
|
89
128
|
/**
|
|
@@ -121,6 +160,12 @@ declare class QueryClient {
|
|
|
121
160
|
invalidateQueries(predicate: QueryKey | ((key: QueryKey) => boolean)): void;
|
|
122
161
|
removeQueries(predicate: QueryKey | ((key: QueryKey) => boolean)): void;
|
|
123
162
|
cancelQueries(predicate: QueryKey | ((key: QueryKey) => boolean)): void;
|
|
163
|
+
refetchQueries(predicate: QueryKey | ((key: QueryKey) => boolean)): Promise<void>;
|
|
164
|
+
/**
|
|
165
|
+
* Кладёт запрос в кэш, не пробрасывая ошибки. Подходит для оптимистичной
|
|
166
|
+
* подгрузки следующего экрана при наведении / долгом тапе.
|
|
167
|
+
*/
|
|
168
|
+
prefetchQuery<T>(key: QueryKey, queryFn: QueryFn<T>, options?: FetchOptions): Promise<void>;
|
|
124
169
|
}
|
|
125
170
|
/** Singleton-доступ для тех мест, где Provider недоступен (push-handler и т.п.). */
|
|
126
171
|
declare function getQueryClient(): QueryClient;
|
|
@@ -136,6 +181,18 @@ type ResponseWrapper<DataType, ErrorsType = unknown> = {
|
|
|
136
181
|
errors?: ErrorsType;
|
|
137
182
|
} & DataType;
|
|
138
183
|
type UseFetchOptions<T, TSelected = T> = {
|
|
184
|
+
/**
|
|
185
|
+
* При `false`:
|
|
186
|
+
* - запрос НЕ инициируется (ни на mount, ни на focus/reconnect/poll);
|
|
187
|
+
* - но хук **подписан на ключ кэша** и перерисуется, если данные
|
|
188
|
+
* обновит другой источник (другой `useFetch` с тем же ключом,
|
|
189
|
+
* `setQueryData` / `invalidateQueries`, мутация, push-handler).
|
|
190
|
+
*
|
|
191
|
+
* То есть `enabled: false` превращает хук в read-only слушателя.
|
|
192
|
+
* Если нужно полностью «потушить» хук — просто не вызывай его.
|
|
193
|
+
*
|
|
194
|
+
* По умолчанию `true`.
|
|
195
|
+
*/
|
|
139
196
|
enabled?: boolean;
|
|
140
197
|
refetchOnMount?: boolean;
|
|
141
198
|
/** Refetch при возврате на экран / в браузерное окно. */
|
|
@@ -216,11 +273,16 @@ type UseMutationOptions<TData, TVariables, TContext = unknown> = {
|
|
|
216
273
|
onError?: (error: Error, variables: TVariables, context: TContext | undefined) => void | Promise<void>;
|
|
217
274
|
onSettled?: (data: TData | null, error: Error | null, variables: TVariables, context: TContext | undefined) => void | Promise<void>;
|
|
218
275
|
/**
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
276
|
+
* Что инвалидировать в кэше после успешной мутации.
|
|
277
|
+
*
|
|
278
|
+
* Три формы:
|
|
279
|
+
* - `QueryKey[]` — массив префиксов; матч по `matchQueryKey`
|
|
280
|
+
* - `(vars, data) => QueryKey[]` — динамический список префиксов
|
|
281
|
+
* - `(vars, data) => (key: QueryKey) => boolean` — произвольный предикат
|
|
282
|
+
* по каждому ключу кэша (например «инвалидируй всё, где встречается
|
|
283
|
+
* этот orderId, в любой позиции ключа»).
|
|
222
284
|
*/
|
|
223
|
-
invalidateKeys?: QueryKey[] | ((vars: TVariables, data: TData) => QueryKey[]);
|
|
285
|
+
invalidateKeys?: QueryKey[] | ((vars: TVariables, data: TData) => QueryKey[]) | ((vars: TVariables, data: TData) => (key: QueryKey) => boolean);
|
|
224
286
|
/**
|
|
225
287
|
* Точечно патчит кэш после успеха — до invalidate. Удобно для
|
|
226
288
|
* «сервер вернул свежий объект, положим его прямо в ['orders', id]».
|
|
@@ -240,12 +302,29 @@ type UseMutationResult<TData, TVariables> = {
|
|
|
240
302
|
interface IHttpClient {
|
|
241
303
|
get<T>(url: string, config?: {
|
|
242
304
|
params?: Record<string, unknown>;
|
|
305
|
+
signal?: AbortSignal;
|
|
243
306
|
}): Promise<T>;
|
|
244
307
|
request<T>(url: string, config: {
|
|
245
308
|
method?: string;
|
|
246
309
|
data?: Record<string, unknown>;
|
|
310
|
+
signal?: AbortSignal;
|
|
247
311
|
}): Promise<T>;
|
|
248
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* Колбэки для логирования / отладки. Все опциональны.
|
|
315
|
+
* Пробрасываются в Reactotron / Flipper или просто в console.log.
|
|
316
|
+
* Ошибки в самих колбэках проглатываются — логгер не должен ломать
|
|
317
|
+
* приложение.
|
|
318
|
+
*/
|
|
319
|
+
type ApiClientLogger = {
|
|
320
|
+
onFetchStart?: (key: readonly unknown[]) => void;
|
|
321
|
+
onFetchSuccess?: (key: readonly unknown[], data: unknown) => void;
|
|
322
|
+
onFetchError?: (key: readonly unknown[], error: unknown) => void;
|
|
323
|
+
onInvalidate?: (invalidatedHashes: string[]) => void;
|
|
324
|
+
onMutationStart?: (endpoint: string, vars: unknown) => void;
|
|
325
|
+
onMutationSuccess?: (endpoint: string, vars: unknown, data: unknown) => void;
|
|
326
|
+
onMutationError?: (endpoint: string, vars: unknown, error: unknown) => void;
|
|
327
|
+
};
|
|
249
328
|
type ApiClientConfig = {
|
|
250
329
|
httpClient: IHttpClient;
|
|
251
330
|
onUnauthorized?: () => void | Promise<void>;
|
|
@@ -257,10 +336,12 @@ type ApiClientConfig = {
|
|
|
257
336
|
* `await *.fetch()` обёрнуты в try/catch.
|
|
258
337
|
*/
|
|
259
338
|
throwOnError?: boolean;
|
|
339
|
+
/** Опциональный логгер для отладки. */
|
|
340
|
+
logger?: ApiClientLogger;
|
|
260
341
|
};
|
|
261
342
|
type ApiClientReturn<ResponseType, RequestParamsType, ErrorResponseType = unknown> = {
|
|
262
343
|
fetch: (params?: RequestParamsType) => Promise<ResponseWrapper<ResponseType, ErrorResponseType>>;
|
|
263
|
-
useFetch: (params?: RequestParamsType, options?: UseFetchOptions<ResponseWrapper<ResponseType, ErrorResponseType
|
|
344
|
+
useFetch: <TSelected = ResponseWrapper<ResponseType, ErrorResponseType>>(params?: RequestParamsType, options?: UseFetchOptions<ResponseWrapper<ResponseType, ErrorResponseType>, TSelected>) => UseFetchResult<TSelected>;
|
|
264
345
|
};
|
|
265
346
|
type ApiMutationReturn<ResponseType, RequestParamsType, ErrorResponseType = unknown> = {
|
|
266
347
|
mutate: (params?: RequestParamsType) => Promise<ResponseWrapper<ResponseType, ErrorResponseType>>;
|
|
@@ -320,6 +401,36 @@ declare class ApiError<E = unknown> extends Error {
|
|
|
320
401
|
readonly raw: unknown;
|
|
321
402
|
constructor(init: ApiErrorInit<E>);
|
|
322
403
|
}
|
|
404
|
+
/**
|
|
405
|
+
* Конвертация произвольного throw'нутого значения в `ApiError`.
|
|
406
|
+
* Покрывает 4 кейса:
|
|
407
|
+
* - `ApiError` → passthrough
|
|
408
|
+
* - axios-like `{ response: { status, data } }` → ApiError с полями из data
|
|
409
|
+
* - `Error` (сетевая ошибка, таймаут) → ApiError(isNetworkError, status: 0)
|
|
410
|
+
* - неизвестный объект → ApiError(message: 'Unknown error', status: 0)
|
|
411
|
+
*/
|
|
412
|
+
declare function toApiError(thrown: unknown): ApiError;
|
|
413
|
+
/**
|
|
414
|
+
* Для 2xx ответа, в теле которого Laravel-style `{ status: false }`.
|
|
415
|
+
* Возвращает ApiError(status: 200, ...). Вызывается из executeRequest
|
|
416
|
+
* только при `throwOnError: true`.
|
|
417
|
+
*/
|
|
418
|
+
declare function businessErrorToApiError(response: unknown): ApiError;
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Возвращает число активных (inflight) запросов в кэше.
|
|
422
|
+
* Полезно для глобального индикатора загрузки в шапке.
|
|
423
|
+
*
|
|
424
|
+
* С опциональным predicate — считает только матчинг запросы:
|
|
425
|
+
* `useIsFetching(['orders'])` — сколько inflight'ов под префиксом ['orders'].
|
|
426
|
+
*/
|
|
427
|
+
declare function useIsFetching(predicate?: QueryKey | ((key: QueryKey) => boolean)): number;
|
|
428
|
+
/**
|
|
429
|
+
* Возвращает число активных мутаций. С predicate — только матчинг.
|
|
430
|
+
* Скоуп мутации = первый ключ из её `invalidateKeys`, если задан массив;
|
|
431
|
+
* иначе — без скоупа (попадает только в безусловный счётчик).
|
|
432
|
+
*/
|
|
433
|
+
declare function useIsMutating(predicate?: QueryKey | ((scope: QueryKey | undefined) => boolean)): number;
|
|
323
434
|
|
|
324
435
|
/**
|
|
325
436
|
* Storage-адаптер. Совместим с AsyncStorage и expo-secure-store:
|
|
@@ -350,14 +461,17 @@ type PersistOptions = {
|
|
|
350
461
|
};
|
|
351
462
|
/**
|
|
352
463
|
* Подключает QueryClient к persistent storage.
|
|
353
|
-
*
|
|
354
|
-
*
|
|
464
|
+
*
|
|
465
|
+
* С версии 2.0.0 — подписан на `cache.subscribeAll`, поэтому
|
|
466
|
+
* автоматически сохраняет состояние через `throttleMs` после любого
|
|
467
|
+
* изменения (setData, успешный fetch, invalidate, remove). Ручной
|
|
468
|
+
* `persist()` остаётся доступным для критичных моментов (logout,
|
|
469
|
+
* shutdown), но в обычном потоке не нужен.
|
|
470
|
+
*
|
|
471
|
+
* Возвращает:
|
|
472
|
+
* - `restore()` — гидратирует кэш из storage. Вызывать на старте.
|
|
355
473
|
* - `persist()` — форс-запись текущего состояния.
|
|
356
474
|
* - `unsubscribe()` — отключить авто-сохранение.
|
|
357
|
-
*
|
|
358
|
-
* Простая модель: на каждое изменение через client.cache._debugEntries
|
|
359
|
-
* нет подписки, поэтому persist вызываем вручную из мутаций / по таймеру.
|
|
360
|
-
* Здесь — таймер по throttleMs. Этого хватает для чатов/архива.
|
|
361
475
|
*/
|
|
362
476
|
declare function persistQueryClient(options: PersistOptions): {
|
|
363
477
|
restore: () => Promise<void>;
|
|
@@ -475,7 +589,21 @@ declare function apiClient<ResponseType = void, RequestParamsType = void, ErrorR
|
|
|
475
589
|
*/
|
|
476
590
|
declare function apiMutation<ResponseType = void, RequestParamsType = void, ErrorResponseType = unknown>(endpoint: string | ((arg0: RequestParamsType) => string), fetchConfig?: RequestConfig): ApiMutationReturn<ResponseType, RequestParamsType, ErrorResponseType>;
|
|
477
591
|
/**
|
|
478
|
-
* Creates an API client for paginated requests
|
|
592
|
+
* Creates an API client for paginated requests.
|
|
593
|
+
*
|
|
594
|
+
* Каждая страница хранится в кэше под собственным подключом
|
|
595
|
+
* `['__paginate__', endpoint, params, { page, limit }]`. Это значит, что
|
|
596
|
+
* мутация может одной операцией пометить stale **все страницы** списка:
|
|
597
|
+
*
|
|
598
|
+
* ```ts
|
|
599
|
+
* confirmOrder.useMutation({
|
|
600
|
+
* invalidateKeys: [['__paginate__', '/orders/archive']],
|
|
601
|
+
* });
|
|
602
|
+
* ```
|
|
603
|
+
*
|
|
604
|
+
* Префикс матчится по `matchQueryKey` → подключи каждой страницы тоже
|
|
605
|
+
* считаются совпадающими.
|
|
606
|
+
*
|
|
479
607
|
* @param endpoint - URL string or function that generates URL from params
|
|
480
608
|
* @param fetchConfig - Request configuration
|
|
481
609
|
* @param options - Pagination options (data/total extractors)
|
|
@@ -494,4 +622,4 @@ declare function apiPaginate<ResponseType extends {
|
|
|
494
622
|
totalExtractor?: (response: ResponseType) => number;
|
|
495
623
|
}): ApiPaginateReturn<ResponseType, RequestParamsType, TData, ErrorResponseType>;
|
|
496
624
|
|
|
497
|
-
export { type ApiClientConfig, ApiClientProvider, type ApiClientProviderProps, ApiError, type ApiErrorInit, type CacheEntrySnapshot, type DehydratedQuery, type DehydratedState, type FetchOptions, type IHttpClient, type PersistOptions, type PersistStorage, QueryCache, QueryClient, type QueryFn, type QueryKey, type QueryState, type QueryStatus, type ResponseWrapper, type UseFetchOptions, type UseFetchResult, type UseMutationOptions, type UseMutationResult, type UsePaginateOptions, type UsePaginateResult, apiMutation, apiPaginate, configureApiClient, apiClient as default, focusManager, getConfig, getQueryClient, hashQueryKey, inspectCache, invalidateAll, isConfigured, matchQueryKey, onlineManager, persistQueryClient, setQueryClient, summarizeCache, useQueryClient };
|
|
625
|
+
export { type ApiClientConfig, type ApiClientLogger, ApiClientProvider, type ApiClientProviderProps, ApiError, type ApiErrorInit, type CacheEntrySnapshot, type DehydratedQuery, type DehydratedState, type FetchOptions, type IHttpClient, type PersistOptions, type PersistStorage, QueryCache, QueryClient, type QueryFn, type QueryKey, type QueryState, type QueryStatus, type ResponseWrapper, type UseFetchOptions, type UseFetchResult, type UseMutationOptions, type UseMutationResult, type UsePaginateOptions, type UsePaginateResult, apiMutation, apiPaginate, businessErrorToApiError, configureApiClient, apiClient as default, focusManager, getConfig, getQueryClient, hashQueryKey, inspectCache, invalidateAll, isConfigured, matchQueryKey, onlineManager, persistQueryClient, setQueryClient, summarizeCache, toApiError, useIsFetching, useIsMutating, useQueryClient };
|
package/dist/index.d.ts
CHANGED
|
@@ -34,11 +34,29 @@ type QueryEntry<T> = {
|
|
|
34
34
|
state: QueryState<T>;
|
|
35
35
|
subscribers: Set<Listener$2>;
|
|
36
36
|
inflight: Promise<T> | null;
|
|
37
|
+
inflightController: AbortController | null;
|
|
37
38
|
gcTimer: ReturnType<typeof setTimeout> | null;
|
|
38
39
|
staleTime: number;
|
|
39
40
|
gcTime: number;
|
|
41
|
+
/**
|
|
42
|
+
* Последний queryFn, переданный в fetch для этого ключа.
|
|
43
|
+
* Используется refetchQueries: позволяет перезапустить запрос,
|
|
44
|
+
* не зная queryFn из caller'а (например, push-handler).
|
|
45
|
+
*/
|
|
46
|
+
lastQueryFn: QueryFn<T> | null;
|
|
47
|
+
};
|
|
48
|
+
type QueryFnContext = {
|
|
49
|
+
signal: AbortSignal;
|
|
40
50
|
};
|
|
41
|
-
|
|
51
|
+
/**
|
|
52
|
+
* queryFn принимает контекст с AbortSignal. Если HTTP-клиент его
|
|
53
|
+
* использует — отмена будет реальной; если игнорирует — поведение
|
|
54
|
+
* деградирует до текущего (запрос идёт до конца, но кэш игнорирует результат).
|
|
55
|
+
*
|
|
56
|
+
* Для обратной совместимости старая сигнатура `() => Promise<T>` тоже
|
|
57
|
+
* принимается — пакет просто не передаст signal внутрь.
|
|
58
|
+
*/
|
|
59
|
+
type QueryFn<T> = (ctx: QueryFnContext) => Promise<T>;
|
|
42
60
|
type FetchOptions = {
|
|
43
61
|
staleTime?: number;
|
|
44
62
|
gcTime?: number;
|
|
@@ -51,6 +69,14 @@ type FetchOptions = {
|
|
|
51
69
|
*/
|
|
52
70
|
declare class QueryCache {
|
|
53
71
|
private entries;
|
|
72
|
+
private globalListeners;
|
|
73
|
+
/**
|
|
74
|
+
* Подписка на любое изменение кэша: setData, invalidate, remove,
|
|
75
|
+
* успешный/ошибочный fetch. Используется persistQueryClient и
|
|
76
|
+
* devtools-подобными адаптерами. Не дублирует `subscribe(key, ...)`.
|
|
77
|
+
*/
|
|
78
|
+
subscribeAll(listener: Listener$2): () => void;
|
|
79
|
+
private notifyGlobal;
|
|
54
80
|
private ensureEntry;
|
|
55
81
|
getState<T>(key: QueryKey): QueryState<T> | undefined;
|
|
56
82
|
getData<T>(key: QueryKey): T | undefined;
|
|
@@ -72,6 +98,13 @@ declare class QueryCache {
|
|
|
72
98
|
* Если есть подписчики — они получат уведомление, чтобы инициировать refetch.
|
|
73
99
|
*/
|
|
74
100
|
invalidate(predicate: QueryKey | ((key: QueryKey) => boolean)): string[];
|
|
101
|
+
/**
|
|
102
|
+
* Перезапускает все записи, матчинг predicate, у которых сохранён
|
|
103
|
+
* `lastQueryFn` (т.е. их хоть раз кто-то загрузил через `fetch`).
|
|
104
|
+
* Возвращает promise, который резолвится когда все запросы завершились.
|
|
105
|
+
* Ошибки отдельных запросов проглатываются — общий promise успешный.
|
|
106
|
+
*/
|
|
107
|
+
refetchQueries(predicate: QueryKey | ((key: QueryKey) => boolean)): Promise<void>;
|
|
75
108
|
/**
|
|
76
109
|
* Отменяет «привязку» inflight-промиса к ключу. Сам HTTP-запрос
|
|
77
110
|
* продолжит исполняться (executeRequest не использует AbortSignal),
|
|
@@ -84,6 +117,12 @@ declare class QueryCache {
|
|
|
84
117
|
* Используется редко — обычно достаточно invalidate.
|
|
85
118
|
*/
|
|
86
119
|
remove(predicate: QueryKey | ((key: QueryKey) => boolean)): void;
|
|
120
|
+
/**
|
|
121
|
+
* Количество inflight-запросов в кэше, опционально отфильтрованных
|
|
122
|
+
* по predicate. Используется `useIsFetching()` для глобального
|
|
123
|
+
* индикатора загрузки.
|
|
124
|
+
*/
|
|
125
|
+
countFetching(predicate?: QueryKey | ((key: QueryKey) => boolean)): number;
|
|
87
126
|
/** Только для тестов / DevTools. */
|
|
88
127
|
_debugEntries(): ReadonlyMap<string, QueryEntry<unknown>>;
|
|
89
128
|
/**
|
|
@@ -121,6 +160,12 @@ declare class QueryClient {
|
|
|
121
160
|
invalidateQueries(predicate: QueryKey | ((key: QueryKey) => boolean)): void;
|
|
122
161
|
removeQueries(predicate: QueryKey | ((key: QueryKey) => boolean)): void;
|
|
123
162
|
cancelQueries(predicate: QueryKey | ((key: QueryKey) => boolean)): void;
|
|
163
|
+
refetchQueries(predicate: QueryKey | ((key: QueryKey) => boolean)): Promise<void>;
|
|
164
|
+
/**
|
|
165
|
+
* Кладёт запрос в кэш, не пробрасывая ошибки. Подходит для оптимистичной
|
|
166
|
+
* подгрузки следующего экрана при наведении / долгом тапе.
|
|
167
|
+
*/
|
|
168
|
+
prefetchQuery<T>(key: QueryKey, queryFn: QueryFn<T>, options?: FetchOptions): Promise<void>;
|
|
124
169
|
}
|
|
125
170
|
/** Singleton-доступ для тех мест, где Provider недоступен (push-handler и т.п.). */
|
|
126
171
|
declare function getQueryClient(): QueryClient;
|
|
@@ -136,6 +181,18 @@ type ResponseWrapper<DataType, ErrorsType = unknown> = {
|
|
|
136
181
|
errors?: ErrorsType;
|
|
137
182
|
} & DataType;
|
|
138
183
|
type UseFetchOptions<T, TSelected = T> = {
|
|
184
|
+
/**
|
|
185
|
+
* При `false`:
|
|
186
|
+
* - запрос НЕ инициируется (ни на mount, ни на focus/reconnect/poll);
|
|
187
|
+
* - но хук **подписан на ключ кэша** и перерисуется, если данные
|
|
188
|
+
* обновит другой источник (другой `useFetch` с тем же ключом,
|
|
189
|
+
* `setQueryData` / `invalidateQueries`, мутация, push-handler).
|
|
190
|
+
*
|
|
191
|
+
* То есть `enabled: false` превращает хук в read-only слушателя.
|
|
192
|
+
* Если нужно полностью «потушить» хук — просто не вызывай его.
|
|
193
|
+
*
|
|
194
|
+
* По умолчанию `true`.
|
|
195
|
+
*/
|
|
139
196
|
enabled?: boolean;
|
|
140
197
|
refetchOnMount?: boolean;
|
|
141
198
|
/** Refetch при возврате на экран / в браузерное окно. */
|
|
@@ -216,11 +273,16 @@ type UseMutationOptions<TData, TVariables, TContext = unknown> = {
|
|
|
216
273
|
onError?: (error: Error, variables: TVariables, context: TContext | undefined) => void | Promise<void>;
|
|
217
274
|
onSettled?: (data: TData | null, error: Error | null, variables: TVariables, context: TContext | undefined) => void | Promise<void>;
|
|
218
275
|
/**
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
276
|
+
* Что инвалидировать в кэше после успешной мутации.
|
|
277
|
+
*
|
|
278
|
+
* Три формы:
|
|
279
|
+
* - `QueryKey[]` — массив префиксов; матч по `matchQueryKey`
|
|
280
|
+
* - `(vars, data) => QueryKey[]` — динамический список префиксов
|
|
281
|
+
* - `(vars, data) => (key: QueryKey) => boolean` — произвольный предикат
|
|
282
|
+
* по каждому ключу кэша (например «инвалидируй всё, где встречается
|
|
283
|
+
* этот orderId, в любой позиции ключа»).
|
|
222
284
|
*/
|
|
223
|
-
invalidateKeys?: QueryKey[] | ((vars: TVariables, data: TData) => QueryKey[]);
|
|
285
|
+
invalidateKeys?: QueryKey[] | ((vars: TVariables, data: TData) => QueryKey[]) | ((vars: TVariables, data: TData) => (key: QueryKey) => boolean);
|
|
224
286
|
/**
|
|
225
287
|
* Точечно патчит кэш после успеха — до invalidate. Удобно для
|
|
226
288
|
* «сервер вернул свежий объект, положим его прямо в ['orders', id]».
|
|
@@ -240,12 +302,29 @@ type UseMutationResult<TData, TVariables> = {
|
|
|
240
302
|
interface IHttpClient {
|
|
241
303
|
get<T>(url: string, config?: {
|
|
242
304
|
params?: Record<string, unknown>;
|
|
305
|
+
signal?: AbortSignal;
|
|
243
306
|
}): Promise<T>;
|
|
244
307
|
request<T>(url: string, config: {
|
|
245
308
|
method?: string;
|
|
246
309
|
data?: Record<string, unknown>;
|
|
310
|
+
signal?: AbortSignal;
|
|
247
311
|
}): Promise<T>;
|
|
248
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* Колбэки для логирования / отладки. Все опциональны.
|
|
315
|
+
* Пробрасываются в Reactotron / Flipper или просто в console.log.
|
|
316
|
+
* Ошибки в самих колбэках проглатываются — логгер не должен ломать
|
|
317
|
+
* приложение.
|
|
318
|
+
*/
|
|
319
|
+
type ApiClientLogger = {
|
|
320
|
+
onFetchStart?: (key: readonly unknown[]) => void;
|
|
321
|
+
onFetchSuccess?: (key: readonly unknown[], data: unknown) => void;
|
|
322
|
+
onFetchError?: (key: readonly unknown[], error: unknown) => void;
|
|
323
|
+
onInvalidate?: (invalidatedHashes: string[]) => void;
|
|
324
|
+
onMutationStart?: (endpoint: string, vars: unknown) => void;
|
|
325
|
+
onMutationSuccess?: (endpoint: string, vars: unknown, data: unknown) => void;
|
|
326
|
+
onMutationError?: (endpoint: string, vars: unknown, error: unknown) => void;
|
|
327
|
+
};
|
|
249
328
|
type ApiClientConfig = {
|
|
250
329
|
httpClient: IHttpClient;
|
|
251
330
|
onUnauthorized?: () => void | Promise<void>;
|
|
@@ -257,10 +336,12 @@ type ApiClientConfig = {
|
|
|
257
336
|
* `await *.fetch()` обёрнуты в try/catch.
|
|
258
337
|
*/
|
|
259
338
|
throwOnError?: boolean;
|
|
339
|
+
/** Опциональный логгер для отладки. */
|
|
340
|
+
logger?: ApiClientLogger;
|
|
260
341
|
};
|
|
261
342
|
type ApiClientReturn<ResponseType, RequestParamsType, ErrorResponseType = unknown> = {
|
|
262
343
|
fetch: (params?: RequestParamsType) => Promise<ResponseWrapper<ResponseType, ErrorResponseType>>;
|
|
263
|
-
useFetch: (params?: RequestParamsType, options?: UseFetchOptions<ResponseWrapper<ResponseType, ErrorResponseType
|
|
344
|
+
useFetch: <TSelected = ResponseWrapper<ResponseType, ErrorResponseType>>(params?: RequestParamsType, options?: UseFetchOptions<ResponseWrapper<ResponseType, ErrorResponseType>, TSelected>) => UseFetchResult<TSelected>;
|
|
264
345
|
};
|
|
265
346
|
type ApiMutationReturn<ResponseType, RequestParamsType, ErrorResponseType = unknown> = {
|
|
266
347
|
mutate: (params?: RequestParamsType) => Promise<ResponseWrapper<ResponseType, ErrorResponseType>>;
|
|
@@ -320,6 +401,36 @@ declare class ApiError<E = unknown> extends Error {
|
|
|
320
401
|
readonly raw: unknown;
|
|
321
402
|
constructor(init: ApiErrorInit<E>);
|
|
322
403
|
}
|
|
404
|
+
/**
|
|
405
|
+
* Конвертация произвольного throw'нутого значения в `ApiError`.
|
|
406
|
+
* Покрывает 4 кейса:
|
|
407
|
+
* - `ApiError` → passthrough
|
|
408
|
+
* - axios-like `{ response: { status, data } }` → ApiError с полями из data
|
|
409
|
+
* - `Error` (сетевая ошибка, таймаут) → ApiError(isNetworkError, status: 0)
|
|
410
|
+
* - неизвестный объект → ApiError(message: 'Unknown error', status: 0)
|
|
411
|
+
*/
|
|
412
|
+
declare function toApiError(thrown: unknown): ApiError;
|
|
413
|
+
/**
|
|
414
|
+
* Для 2xx ответа, в теле которого Laravel-style `{ status: false }`.
|
|
415
|
+
* Возвращает ApiError(status: 200, ...). Вызывается из executeRequest
|
|
416
|
+
* только при `throwOnError: true`.
|
|
417
|
+
*/
|
|
418
|
+
declare function businessErrorToApiError(response: unknown): ApiError;
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Возвращает число активных (inflight) запросов в кэше.
|
|
422
|
+
* Полезно для глобального индикатора загрузки в шапке.
|
|
423
|
+
*
|
|
424
|
+
* С опциональным predicate — считает только матчинг запросы:
|
|
425
|
+
* `useIsFetching(['orders'])` — сколько inflight'ов под префиксом ['orders'].
|
|
426
|
+
*/
|
|
427
|
+
declare function useIsFetching(predicate?: QueryKey | ((key: QueryKey) => boolean)): number;
|
|
428
|
+
/**
|
|
429
|
+
* Возвращает число активных мутаций. С predicate — только матчинг.
|
|
430
|
+
* Скоуп мутации = первый ключ из её `invalidateKeys`, если задан массив;
|
|
431
|
+
* иначе — без скоупа (попадает только в безусловный счётчик).
|
|
432
|
+
*/
|
|
433
|
+
declare function useIsMutating(predicate?: QueryKey | ((scope: QueryKey | undefined) => boolean)): number;
|
|
323
434
|
|
|
324
435
|
/**
|
|
325
436
|
* Storage-адаптер. Совместим с AsyncStorage и expo-secure-store:
|
|
@@ -350,14 +461,17 @@ type PersistOptions = {
|
|
|
350
461
|
};
|
|
351
462
|
/**
|
|
352
463
|
* Подключает QueryClient к persistent storage.
|
|
353
|
-
*
|
|
354
|
-
*
|
|
464
|
+
*
|
|
465
|
+
* С версии 2.0.0 — подписан на `cache.subscribeAll`, поэтому
|
|
466
|
+
* автоматически сохраняет состояние через `throttleMs` после любого
|
|
467
|
+
* изменения (setData, успешный fetch, invalidate, remove). Ручной
|
|
468
|
+
* `persist()` остаётся доступным для критичных моментов (logout,
|
|
469
|
+
* shutdown), но в обычном потоке не нужен.
|
|
470
|
+
*
|
|
471
|
+
* Возвращает:
|
|
472
|
+
* - `restore()` — гидратирует кэш из storage. Вызывать на старте.
|
|
355
473
|
* - `persist()` — форс-запись текущего состояния.
|
|
356
474
|
* - `unsubscribe()` — отключить авто-сохранение.
|
|
357
|
-
*
|
|
358
|
-
* Простая модель: на каждое изменение через client.cache._debugEntries
|
|
359
|
-
* нет подписки, поэтому persist вызываем вручную из мутаций / по таймеру.
|
|
360
|
-
* Здесь — таймер по throttleMs. Этого хватает для чатов/архива.
|
|
361
475
|
*/
|
|
362
476
|
declare function persistQueryClient(options: PersistOptions): {
|
|
363
477
|
restore: () => Promise<void>;
|
|
@@ -475,7 +589,21 @@ declare function apiClient<ResponseType = void, RequestParamsType = void, ErrorR
|
|
|
475
589
|
*/
|
|
476
590
|
declare function apiMutation<ResponseType = void, RequestParamsType = void, ErrorResponseType = unknown>(endpoint: string | ((arg0: RequestParamsType) => string), fetchConfig?: RequestConfig): ApiMutationReturn<ResponseType, RequestParamsType, ErrorResponseType>;
|
|
477
591
|
/**
|
|
478
|
-
* Creates an API client for paginated requests
|
|
592
|
+
* Creates an API client for paginated requests.
|
|
593
|
+
*
|
|
594
|
+
* Каждая страница хранится в кэше под собственным подключом
|
|
595
|
+
* `['__paginate__', endpoint, params, { page, limit }]`. Это значит, что
|
|
596
|
+
* мутация может одной операцией пометить stale **все страницы** списка:
|
|
597
|
+
*
|
|
598
|
+
* ```ts
|
|
599
|
+
* confirmOrder.useMutation({
|
|
600
|
+
* invalidateKeys: [['__paginate__', '/orders/archive']],
|
|
601
|
+
* });
|
|
602
|
+
* ```
|
|
603
|
+
*
|
|
604
|
+
* Префикс матчится по `matchQueryKey` → подключи каждой страницы тоже
|
|
605
|
+
* считаются совпадающими.
|
|
606
|
+
*
|
|
479
607
|
* @param endpoint - URL string or function that generates URL from params
|
|
480
608
|
* @param fetchConfig - Request configuration
|
|
481
609
|
* @param options - Pagination options (data/total extractors)
|
|
@@ -494,4 +622,4 @@ declare function apiPaginate<ResponseType extends {
|
|
|
494
622
|
totalExtractor?: (response: ResponseType) => number;
|
|
495
623
|
}): ApiPaginateReturn<ResponseType, RequestParamsType, TData, ErrorResponseType>;
|
|
496
624
|
|
|
497
|
-
export { type ApiClientConfig, ApiClientProvider, type ApiClientProviderProps, ApiError, type ApiErrorInit, type CacheEntrySnapshot, type DehydratedQuery, type DehydratedState, type FetchOptions, type IHttpClient, type PersistOptions, type PersistStorage, QueryCache, QueryClient, type QueryFn, type QueryKey, type QueryState, type QueryStatus, type ResponseWrapper, type UseFetchOptions, type UseFetchResult, type UseMutationOptions, type UseMutationResult, type UsePaginateOptions, type UsePaginateResult, apiMutation, apiPaginate, configureApiClient, apiClient as default, focusManager, getConfig, getQueryClient, hashQueryKey, inspectCache, invalidateAll, isConfigured, matchQueryKey, onlineManager, persistQueryClient, setQueryClient, summarizeCache, useQueryClient };
|
|
625
|
+
export { type ApiClientConfig, type ApiClientLogger, ApiClientProvider, type ApiClientProviderProps, ApiError, type ApiErrorInit, type CacheEntrySnapshot, type DehydratedQuery, type DehydratedState, type FetchOptions, type IHttpClient, type PersistOptions, type PersistStorage, QueryCache, QueryClient, type QueryFn, type QueryKey, type QueryState, type QueryStatus, type ResponseWrapper, type UseFetchOptions, type UseFetchResult, type UseMutationOptions, type UseMutationResult, type UsePaginateOptions, type UsePaginateResult, apiMutation, apiPaginate, businessErrorToApiError, configureApiClient, apiClient as default, focusManager, getConfig, getQueryClient, hashQueryKey, inspectCache, invalidateAll, isConfigured, matchQueryKey, onlineManager, persistQueryClient, setQueryClient, summarizeCache, toApiError, useIsFetching, useIsMutating, useQueryClient };
|