@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/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createContext,
|
|
1
|
+
import { createContext, useCallback, useState, useEffect, useMemo, createElement, useContext, useRef } from 'react';
|
|
2
2
|
|
|
3
3
|
// src/config.ts
|
|
4
4
|
var globalConfig = null;
|
|
@@ -32,6 +32,52 @@ var ApiError = class _ApiError extends Error {
|
|
|
32
32
|
Object.setPrototypeOf(this, _ApiError.prototype);
|
|
33
33
|
}
|
|
34
34
|
};
|
|
35
|
+
var isObject = (v) => typeof v === "object" && v !== null;
|
|
36
|
+
function toApiError(thrown) {
|
|
37
|
+
if (thrown instanceof ApiError) return thrown;
|
|
38
|
+
if (isObject(thrown) && "response" in thrown && isObject(thrown.response)) {
|
|
39
|
+
const r = thrown.response;
|
|
40
|
+
const status = typeof r.status === "number" ? r.status : 0;
|
|
41
|
+
const data = isObject(r.data) ? r.data : void 0;
|
|
42
|
+
const message = (data && typeof data.message === "string" ? data.message : void 0) ?? (thrown instanceof Error ? thrown.message : void 0) ?? `HTTP ${status}`;
|
|
43
|
+
return new ApiError({
|
|
44
|
+
message,
|
|
45
|
+
status,
|
|
46
|
+
code: data && typeof data.code === "string" ? data.code : void 0,
|
|
47
|
+
errors: data?.errors,
|
|
48
|
+
isNetworkError: status === 0,
|
|
49
|
+
isUnauthorized: status === 401,
|
|
50
|
+
isValidationError: status === 422 || data?.errors !== void 0,
|
|
51
|
+
raw: r.data ?? thrown
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
if (thrown instanceof Error) {
|
|
55
|
+
return new ApiError({
|
|
56
|
+
message: thrown.message,
|
|
57
|
+
status: 0,
|
|
58
|
+
isNetworkError: true,
|
|
59
|
+
raw: thrown
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return new ApiError({
|
|
63
|
+
message: "Unknown error",
|
|
64
|
+
status: 0,
|
|
65
|
+
raw: thrown
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function businessErrorToApiError(response) {
|
|
69
|
+
if (!isObject(response)) {
|
|
70
|
+
return new ApiError({ message: "Request failed", status: 200, raw: response });
|
|
71
|
+
}
|
|
72
|
+
return new ApiError({
|
|
73
|
+
message: typeof response.message === "string" ? response.message : "Request failed",
|
|
74
|
+
status: 200,
|
|
75
|
+
code: typeof response.code === "string" ? response.code : void 0,
|
|
76
|
+
errors: response.errors,
|
|
77
|
+
isValidationError: response.errors !== void 0,
|
|
78
|
+
raw: response
|
|
79
|
+
});
|
|
80
|
+
}
|
|
35
81
|
|
|
36
82
|
// src/utils.ts
|
|
37
83
|
function buildEndpoint(endpoint, params) {
|
|
@@ -40,7 +86,7 @@ function buildEndpoint(endpoint, params) {
|
|
|
40
86
|
}
|
|
41
87
|
return endpoint;
|
|
42
88
|
}
|
|
43
|
-
async function executeRequest(endpoint, fetchConfig, params) {
|
|
89
|
+
async function executeRequest(endpoint, fetchConfig, params, signal) {
|
|
44
90
|
const url = buildEndpoint(endpoint, params);
|
|
45
91
|
const config = getConfig();
|
|
46
92
|
try {
|
|
@@ -51,7 +97,8 @@ async function executeRequest(endpoint, fetchConfig, params) {
|
|
|
51
97
|
params: {
|
|
52
98
|
...requestConfig.requestParams,
|
|
53
99
|
...params
|
|
54
|
-
}
|
|
100
|
+
},
|
|
101
|
+
signal
|
|
55
102
|
});
|
|
56
103
|
} else {
|
|
57
104
|
response = await config.httpClient.request(url, {
|
|
@@ -59,38 +106,29 @@ async function executeRequest(endpoint, fetchConfig, params) {
|
|
|
59
106
|
data: {
|
|
60
107
|
...requestConfig.requestParams,
|
|
61
108
|
...params
|
|
62
|
-
}
|
|
109
|
+
},
|
|
110
|
+
signal
|
|
63
111
|
});
|
|
64
112
|
}
|
|
65
113
|
const hasExplicitStatus = response && typeof response === "object" && "status" in response && typeof response.status === "boolean";
|
|
66
114
|
if (config.throwOnError && hasExplicitStatus && response.status === false) {
|
|
67
|
-
|
|
68
|
-
throw new ApiError({
|
|
69
|
-
message: body.message ?? "Request failed",
|
|
70
|
-
status: 200,
|
|
71
|
-
errors: body.errors,
|
|
72
|
-
raw: response
|
|
73
|
-
});
|
|
115
|
+
throw businessErrorToApiError(response);
|
|
74
116
|
}
|
|
75
117
|
return { ...response, status: true };
|
|
76
118
|
} catch (e) {
|
|
77
|
-
if (e instanceof ApiError)
|
|
119
|
+
if (e instanceof ApiError) {
|
|
120
|
+
if (e.status === 401 && config.onUnauthorized) {
|
|
121
|
+
await config.onUnauthorized();
|
|
122
|
+
}
|
|
123
|
+
throw e;
|
|
124
|
+
}
|
|
78
125
|
const error = e;
|
|
79
126
|
const httpStatus = error.response?.status;
|
|
80
127
|
if (httpStatus === 401 && config.onUnauthorized) {
|
|
81
128
|
await config.onUnauthorized();
|
|
82
129
|
}
|
|
83
130
|
if (config.throwOnError) {
|
|
84
|
-
|
|
85
|
-
const isNetwork = httpStatus === void 0;
|
|
86
|
-
throw new ApiError({
|
|
87
|
-
message: data?.message ?? (e instanceof Error ? e.message : void 0) ?? (isNetwork ? "Network error" : "Request error"),
|
|
88
|
-
status: httpStatus ?? 0,
|
|
89
|
-
code: data?.code,
|
|
90
|
-
errors: data,
|
|
91
|
-
isNetworkError: isNetwork,
|
|
92
|
-
raw: e
|
|
93
|
-
});
|
|
131
|
+
throw toApiError(e);
|
|
94
132
|
}
|
|
95
133
|
if (error.response?.data) {
|
|
96
134
|
return {
|
|
@@ -113,6 +151,21 @@ function handleResponse(result, onSuccess, onError) {
|
|
|
113
151
|
}
|
|
114
152
|
}
|
|
115
153
|
|
|
154
|
+
// src/logger.ts
|
|
155
|
+
function getLogger() {
|
|
156
|
+
if (!isConfigured()) return void 0;
|
|
157
|
+
return getConfig().logger;
|
|
158
|
+
}
|
|
159
|
+
function callLogger(method, ...args) {
|
|
160
|
+
const logger = getLogger();
|
|
161
|
+
const fn = logger?.[method];
|
|
162
|
+
if (!fn) return;
|
|
163
|
+
try {
|
|
164
|
+
fn(...args);
|
|
165
|
+
} catch {
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
116
169
|
// src/query/focus-manager.ts
|
|
117
170
|
var FocusManager = class {
|
|
118
171
|
constructor() {
|
|
@@ -251,6 +304,21 @@ var DEFAULT_STALE_TIME = 0;
|
|
|
251
304
|
var QueryCache = class {
|
|
252
305
|
constructor() {
|
|
253
306
|
this.entries = /* @__PURE__ */ new Map();
|
|
307
|
+
this.globalListeners = /* @__PURE__ */ new Set();
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Подписка на любое изменение кэша: setData, invalidate, remove,
|
|
311
|
+
* успешный/ошибочный fetch. Используется persistQueryClient и
|
|
312
|
+
* devtools-подобными адаптерами. Не дублирует `subscribe(key, ...)`.
|
|
313
|
+
*/
|
|
314
|
+
subscribeAll(listener) {
|
|
315
|
+
this.globalListeners.add(listener);
|
|
316
|
+
return () => {
|
|
317
|
+
this.globalListeners.delete(listener);
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
notifyGlobal() {
|
|
321
|
+
for (const listener of this.globalListeners) listener();
|
|
254
322
|
}
|
|
255
323
|
ensureEntry(key, staleTime, gcTime) {
|
|
256
324
|
const hash = hashQueryKey(key);
|
|
@@ -267,7 +335,9 @@ var QueryCache = class {
|
|
|
267
335
|
},
|
|
268
336
|
subscribers: /* @__PURE__ */ new Set(),
|
|
269
337
|
inflight: null,
|
|
338
|
+
inflightController: null,
|
|
270
339
|
gcTimer: null,
|
|
340
|
+
lastQueryFn: null,
|
|
271
341
|
staleTime: staleTime ?? DEFAULT_STALE_TIME,
|
|
272
342
|
gcTime: gcTime ?? DEFAULT_GC_TIME
|
|
273
343
|
};
|
|
@@ -315,6 +385,7 @@ var QueryCache = class {
|
|
|
315
385
|
}
|
|
316
386
|
notify(entry) {
|
|
317
387
|
for (const listener of entry.subscribers) listener();
|
|
388
|
+
this.notifyGlobal();
|
|
318
389
|
}
|
|
319
390
|
scheduleGc(entry) {
|
|
320
391
|
if (entry.gcTimer) clearTimeout(entry.gcTimer);
|
|
@@ -337,18 +408,21 @@ var QueryCache = class {
|
|
|
337
408
|
const gcTime = options.gcTime ?? DEFAULT_GC_TIME;
|
|
338
409
|
const entry = this.ensureEntry(key, staleTime, gcTime);
|
|
339
410
|
const isFresh = entry.state.status === "success" && !entry.state.isStale && Date.now() - entry.state.updatedAt < staleTime;
|
|
411
|
+
entry.lastQueryFn = queryFn;
|
|
340
412
|
if (!options.force && isFresh && entry.state.data !== void 0) {
|
|
341
413
|
return entry.state.data;
|
|
342
414
|
}
|
|
343
415
|
if (entry.inflight) return entry.inflight;
|
|
344
416
|
entry.state = { ...entry.state, status: "loading", error: null };
|
|
345
417
|
this.notify(entry);
|
|
418
|
+
const controller = new AbortController();
|
|
346
419
|
const token = Symbol("inflight");
|
|
347
420
|
entry.inflightToken = token;
|
|
421
|
+
entry.inflightController = controller;
|
|
348
422
|
const isCurrent = () => entry.inflightToken === token;
|
|
349
423
|
const myPromise = (async () => {
|
|
350
424
|
try {
|
|
351
|
-
const data = await queryFn();
|
|
425
|
+
const data = await queryFn({ signal: controller.signal });
|
|
352
426
|
if (!isCurrent()) return data;
|
|
353
427
|
entry.state = {
|
|
354
428
|
data,
|
|
@@ -369,7 +443,10 @@ var QueryCache = class {
|
|
|
369
443
|
this.notify(entry);
|
|
370
444
|
throw err;
|
|
371
445
|
} finally {
|
|
372
|
-
if (isCurrent())
|
|
446
|
+
if (isCurrent()) {
|
|
447
|
+
entry.inflight = null;
|
|
448
|
+
entry.inflightController = null;
|
|
449
|
+
}
|
|
373
450
|
}
|
|
374
451
|
})();
|
|
375
452
|
entry.inflight = myPromise;
|
|
@@ -391,6 +468,26 @@ var QueryCache = class {
|
|
|
391
468
|
}
|
|
392
469
|
return invalidated;
|
|
393
470
|
}
|
|
471
|
+
/**
|
|
472
|
+
* Перезапускает все записи, матчинг predicate, у которых сохранён
|
|
473
|
+
* `lastQueryFn` (т.е. их хоть раз кто-то загрузил через `fetch`).
|
|
474
|
+
* Возвращает promise, который резолвится когда все запросы завершились.
|
|
475
|
+
* Ошибки отдельных запросов проглатываются — общий promise успешный.
|
|
476
|
+
*/
|
|
477
|
+
refetchQueries(predicate) {
|
|
478
|
+
const match = typeof predicate === "function" ? predicate : (k) => matchQueryKey(predicate, k);
|
|
479
|
+
const promises = [];
|
|
480
|
+
for (const entry of this.entries.values()) {
|
|
481
|
+
if (!match(entry.key)) continue;
|
|
482
|
+
if (!entry.lastQueryFn) continue;
|
|
483
|
+
promises.push(
|
|
484
|
+
this.fetch(entry.key, entry.lastQueryFn, { force: true }).catch(
|
|
485
|
+
() => void 0
|
|
486
|
+
)
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
return Promise.all(promises).then(() => void 0);
|
|
490
|
+
}
|
|
394
491
|
/**
|
|
395
492
|
* Отменяет «привязку» inflight-промиса к ключу. Сам HTTP-запрос
|
|
396
493
|
* продолжит исполняться (executeRequest не использует AbortSignal),
|
|
@@ -401,6 +498,8 @@ var QueryCache = class {
|
|
|
401
498
|
const match = typeof predicate === "function" ? predicate : (k) => matchQueryKey(predicate, k);
|
|
402
499
|
for (const entry of this.entries.values()) {
|
|
403
500
|
if (match(entry.key) && entry.inflight) {
|
|
501
|
+
entry.inflightController?.abort();
|
|
502
|
+
entry.inflightController = null;
|
|
404
503
|
entry.inflight = null;
|
|
405
504
|
entry.inflightToken = void 0;
|
|
406
505
|
if (entry.state.status === "loading") {
|
|
@@ -416,12 +515,28 @@ var QueryCache = class {
|
|
|
416
515
|
*/
|
|
417
516
|
remove(predicate) {
|
|
418
517
|
const match = typeof predicate === "function" ? predicate : (k) => matchQueryKey(predicate, k);
|
|
518
|
+
let removed = false;
|
|
419
519
|
for (const [hash, entry] of [...this.entries]) {
|
|
420
520
|
if (match(entry.key)) {
|
|
421
521
|
if (entry.gcTimer) clearTimeout(entry.gcTimer);
|
|
422
522
|
this.entries.delete(hash);
|
|
523
|
+
removed = true;
|
|
423
524
|
}
|
|
424
525
|
}
|
|
526
|
+
if (removed) this.notifyGlobal();
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Количество inflight-запросов в кэше, опционально отфильтрованных
|
|
530
|
+
* по predicate. Используется `useIsFetching()` для глобального
|
|
531
|
+
* индикатора загрузки.
|
|
532
|
+
*/
|
|
533
|
+
countFetching(predicate) {
|
|
534
|
+
const match = !predicate ? () => true : typeof predicate === "function" ? predicate : (k) => matchQueryKey(predicate, k);
|
|
535
|
+
let n = 0;
|
|
536
|
+
for (const entry of this.entries.values()) {
|
|
537
|
+
if (entry.state.status === "loading" && match(entry.key)) n++;
|
|
538
|
+
}
|
|
539
|
+
return n;
|
|
425
540
|
}
|
|
426
541
|
/** Только для тестов / DevTools. */
|
|
427
542
|
_debugEntries() {
|
|
@@ -480,7 +595,8 @@ var QueryClient = class {
|
|
|
480
595
|
return this.cache.fetch(key, queryFn, options);
|
|
481
596
|
}
|
|
482
597
|
invalidateQueries(predicate) {
|
|
483
|
-
this.cache.invalidate(predicate);
|
|
598
|
+
const hashes = this.cache.invalidate(predicate);
|
|
599
|
+
if (hashes.length > 0) callLogger("onInvalidate", hashes);
|
|
484
600
|
}
|
|
485
601
|
removeQueries(predicate) {
|
|
486
602
|
this.cache.remove(predicate);
|
|
@@ -488,6 +604,19 @@ var QueryClient = class {
|
|
|
488
604
|
cancelQueries(predicate) {
|
|
489
605
|
this.cache.cancelQueries(predicate);
|
|
490
606
|
}
|
|
607
|
+
refetchQueries(predicate) {
|
|
608
|
+
return this.cache.refetchQueries(predicate);
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Кладёт запрос в кэш, не пробрасывая ошибки. Подходит для оптимистичной
|
|
612
|
+
* подгрузки следующего экрана при наведении / долгом тапе.
|
|
613
|
+
*/
|
|
614
|
+
prefetchQuery(key, queryFn, options) {
|
|
615
|
+
return this.cache.fetch(key, queryFn, options).then(
|
|
616
|
+
() => void 0,
|
|
617
|
+
() => void 0
|
|
618
|
+
);
|
|
619
|
+
}
|
|
491
620
|
};
|
|
492
621
|
var globalClient = null;
|
|
493
622
|
function getQueryClient() {
|
|
@@ -526,27 +655,37 @@ function createUseFetch(endpoint, fetchConfig) {
|
|
|
526
655
|
}, [customKey ? hashQueryKey(customKey) : null, serializedParams]);
|
|
527
656
|
const client = getQueryClient();
|
|
528
657
|
const cache = client.cache;
|
|
529
|
-
const queryFn = useCallback(
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
658
|
+
const queryFn = useCallback(
|
|
659
|
+
({ signal }) => {
|
|
660
|
+
const parsedParams = serializedParams ? JSON.parse(serializedParams) : void 0;
|
|
661
|
+
return executeRequest(endpoint, fetchConfig, parsedParams, signal);
|
|
662
|
+
},
|
|
663
|
+
[serializedParams]
|
|
664
|
+
);
|
|
533
665
|
const initialState = cache.getState(queryKey);
|
|
534
666
|
const [, forceRender] = useState(0);
|
|
535
667
|
const rerender = useCallback(() => forceRender((v) => v + 1), []);
|
|
536
668
|
useEffect(() => {
|
|
537
|
-
if (!enabled) return;
|
|
538
669
|
const unsub = cache.subscribe(queryKey, rerender);
|
|
539
|
-
return
|
|
540
|
-
|
|
670
|
+
return () => {
|
|
671
|
+
unsub();
|
|
672
|
+
const state2 = cache._debugEntries().get(hashQueryKey(queryKey));
|
|
673
|
+
if (state2 && state2.subscribers.size === 0 && state2.inflight) {
|
|
674
|
+
cache.cancelQueries(queryKey);
|
|
675
|
+
}
|
|
676
|
+
};
|
|
677
|
+
}, [cache, hashQueryKey(queryKey), rerender]);
|
|
541
678
|
const lastNotifiedRef = useRef({});
|
|
542
679
|
const runFetch = useCallback(
|
|
543
680
|
async (force) => {
|
|
681
|
+
callLogger("onFetchStart", queryKey);
|
|
544
682
|
try {
|
|
545
683
|
const data = await cache.fetch(queryKey, queryFn, {
|
|
546
684
|
staleTime,
|
|
547
685
|
gcTime,
|
|
548
686
|
force
|
|
549
687
|
});
|
|
688
|
+
callLogger("onFetchSuccess", queryKey, data);
|
|
550
689
|
if (lastNotifiedRef.current.success !== data) {
|
|
551
690
|
lastNotifiedRef.current.success = data;
|
|
552
691
|
handleResponse(
|
|
@@ -556,6 +695,7 @@ function createUseFetch(endpoint, fetchConfig) {
|
|
|
556
695
|
);
|
|
557
696
|
}
|
|
558
697
|
} catch (err) {
|
|
698
|
+
callLogger("onFetchError", queryKey, err);
|
|
559
699
|
const e = err;
|
|
560
700
|
const hash = `${e.name}:${e.message}`;
|
|
561
701
|
if (lastNotifiedRef.current.errorHash !== hash) {
|
|
@@ -639,6 +779,47 @@ function createUseFetch(endpoint, fetchConfig) {
|
|
|
639
779
|
};
|
|
640
780
|
};
|
|
641
781
|
}
|
|
782
|
+
|
|
783
|
+
// src/query/mutation-counter.ts
|
|
784
|
+
var MutationCounter = class {
|
|
785
|
+
constructor() {
|
|
786
|
+
this.active = /* @__PURE__ */ new Map();
|
|
787
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
788
|
+
}
|
|
789
|
+
start(scope) {
|
|
790
|
+
const id = Symbol("mutation");
|
|
791
|
+
this.active.set(id, scope);
|
|
792
|
+
this.notify();
|
|
793
|
+
return id;
|
|
794
|
+
}
|
|
795
|
+
stop(id) {
|
|
796
|
+
if (this.active.delete(id)) this.notify();
|
|
797
|
+
}
|
|
798
|
+
count(predicate) {
|
|
799
|
+
if (!predicate) return this.active.size;
|
|
800
|
+
let n = 0;
|
|
801
|
+
for (const scope of this.active.values()) {
|
|
802
|
+
if (typeof predicate === "function") {
|
|
803
|
+
if (predicate(scope)) n++;
|
|
804
|
+
} else {
|
|
805
|
+
if (scope && matchQueryKey(predicate, scope)) n++;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
return n;
|
|
809
|
+
}
|
|
810
|
+
subscribe(listener) {
|
|
811
|
+
this.listeners.add(listener);
|
|
812
|
+
return () => {
|
|
813
|
+
this.listeners.delete(listener);
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
notify() {
|
|
817
|
+
for (const l of this.listeners) l();
|
|
818
|
+
}
|
|
819
|
+
};
|
|
820
|
+
var mutationCounter = new MutationCounter();
|
|
821
|
+
|
|
822
|
+
// src/hooks/use-mutation.ts
|
|
642
823
|
function createUseMutation(endpoint, fetchConfig) {
|
|
643
824
|
return (options = {}) => {
|
|
644
825
|
const {
|
|
@@ -665,10 +846,21 @@ function createUseMutation(endpoint, fetchConfig) {
|
|
|
665
846
|
(vars, result) => {
|
|
666
847
|
if (!invalidateKeys) return;
|
|
667
848
|
const client = getQueryClient();
|
|
668
|
-
|
|
669
|
-
|
|
849
|
+
if (Array.isArray(invalidateKeys)) {
|
|
850
|
+
if (invalidateKeys.length === 0) return;
|
|
851
|
+
client.invalidateQueries(
|
|
852
|
+
(k) => invalidateKeys.some((prefix) => matchQueryKey(prefix, k))
|
|
853
|
+
);
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
const out = invalidateKeys(vars, result);
|
|
857
|
+
if (typeof out === "function") {
|
|
858
|
+
client.invalidateQueries(out);
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
if (out.length === 0) return;
|
|
670
862
|
client.invalidateQueries(
|
|
671
|
-
(k) =>
|
|
863
|
+
(k) => out.some((prefix) => matchQueryKey(prefix, k))
|
|
672
864
|
);
|
|
673
865
|
},
|
|
674
866
|
[invalidateKeys]
|
|
@@ -679,6 +871,10 @@ function createUseMutation(endpoint, fetchConfig) {
|
|
|
679
871
|
setIsSuccess(false);
|
|
680
872
|
setIsError(false);
|
|
681
873
|
setError(null);
|
|
874
|
+
const scope = Array.isArray(invalidateKeys) && invalidateKeys.length > 0 ? invalidateKeys[0] : void 0;
|
|
875
|
+
const mutationId = mutationCounter.start(scope);
|
|
876
|
+
const endpointId = buildEndpoint(endpoint, variables);
|
|
877
|
+
callLogger("onMutationStart", endpointId, variables);
|
|
682
878
|
let context;
|
|
683
879
|
try {
|
|
684
880
|
if (onMutate) {
|
|
@@ -689,6 +885,7 @@ function createUseMutation(endpoint, fetchConfig) {
|
|
|
689
885
|
setData(result);
|
|
690
886
|
if (result.status) {
|
|
691
887
|
setIsSuccess(true);
|
|
888
|
+
callLogger("onMutationSuccess", endpointId, variables, result);
|
|
692
889
|
if (setQueryData) {
|
|
693
890
|
setQueryData(getQueryClient(), variables, result);
|
|
694
891
|
}
|
|
@@ -700,6 +897,7 @@ function createUseMutation(endpoint, fetchConfig) {
|
|
|
700
897
|
const err = new Error(result.message ?? "Mutation failed");
|
|
701
898
|
setIsError(true);
|
|
702
899
|
setError(err);
|
|
900
|
+
callLogger("onMutationError", endpointId, variables, err);
|
|
703
901
|
if (onError) {
|
|
704
902
|
await onError(err, variables, context);
|
|
705
903
|
}
|
|
@@ -713,6 +911,7 @@ function createUseMutation(endpoint, fetchConfig) {
|
|
|
713
911
|
setError(error2);
|
|
714
912
|
setIsError(true);
|
|
715
913
|
setIsSuccess(false);
|
|
914
|
+
callLogger("onMutationError", endpointId, variables, error2);
|
|
716
915
|
if (onError) {
|
|
717
916
|
await onError(error2, variables, context);
|
|
718
917
|
}
|
|
@@ -721,6 +920,7 @@ function createUseMutation(endpoint, fetchConfig) {
|
|
|
721
920
|
}
|
|
722
921
|
throw error2;
|
|
723
922
|
} finally {
|
|
923
|
+
mutationCounter.stop(mutationId);
|
|
724
924
|
setIsLoading(false);
|
|
725
925
|
}
|
|
726
926
|
},
|
|
@@ -797,14 +997,14 @@ function createUsePaginate(endpoint, fetchConfig, options) {
|
|
|
797
997
|
[keyPrefix, limit]
|
|
798
998
|
);
|
|
799
999
|
const pageQueryFn = useCallback(
|
|
800
|
-
(page) => () => {
|
|
1000
|
+
(page) => ({ signal }) => {
|
|
801
1001
|
const parsedParams = serializedParams ? JSON.parse(serializedParams) : {};
|
|
802
1002
|
const requestParams = {
|
|
803
1003
|
...parsedParams,
|
|
804
1004
|
page,
|
|
805
1005
|
limit
|
|
806
1006
|
};
|
|
807
|
-
return executeRequest(endpoint, fetchConfig, requestParams);
|
|
1007
|
+
return executeRequest(endpoint, fetchConfig, requestParams, signal);
|
|
808
1008
|
},
|
|
809
1009
|
[serializedParams, limit]
|
|
810
1010
|
);
|
|
@@ -923,6 +1123,33 @@ function createUsePaginate(endpoint, fetchConfig, options) {
|
|
|
923
1123
|
};
|
|
924
1124
|
};
|
|
925
1125
|
}
|
|
1126
|
+
function useIsFetching(predicate) {
|
|
1127
|
+
const client = getQueryClient();
|
|
1128
|
+
const get = useCallback(
|
|
1129
|
+
() => client.cache.countFetching(predicate),
|
|
1130
|
+
[client.cache, predicate]
|
|
1131
|
+
);
|
|
1132
|
+
const [count, setCount] = useState(get);
|
|
1133
|
+
useEffect(() => {
|
|
1134
|
+
setCount(get());
|
|
1135
|
+
const unsub = client.cache.subscribeAll(() => setCount(get()));
|
|
1136
|
+
return unsub;
|
|
1137
|
+
}, [client.cache, get]);
|
|
1138
|
+
return count;
|
|
1139
|
+
}
|
|
1140
|
+
function useIsMutating(predicate) {
|
|
1141
|
+
const get = useCallback(
|
|
1142
|
+
() => mutationCounter.count(predicate),
|
|
1143
|
+
[predicate]
|
|
1144
|
+
);
|
|
1145
|
+
const [count, setCount] = useState(get);
|
|
1146
|
+
useEffect(() => {
|
|
1147
|
+
setCount(get());
|
|
1148
|
+
const unsub = mutationCounter.subscribe(() => setCount(get()));
|
|
1149
|
+
return unsub;
|
|
1150
|
+
}, [get]);
|
|
1151
|
+
return count;
|
|
1152
|
+
}
|
|
926
1153
|
|
|
927
1154
|
// src/query/persist.ts
|
|
928
1155
|
function persistQueryClient(options) {
|
|
@@ -935,19 +1162,26 @@ function persistQueryClient(options) {
|
|
|
935
1162
|
maxAge,
|
|
936
1163
|
version
|
|
937
1164
|
} = options;
|
|
938
|
-
let
|
|
1165
|
+
let pendingTimer = null;
|
|
939
1166
|
let lastSerialized = null;
|
|
1167
|
+
let unsubscribed = false;
|
|
940
1168
|
const persist = async () => {
|
|
941
1169
|
const state = client.cache.dehydrate(allowList);
|
|
942
|
-
if (state.queries.length === 0)
|
|
943
|
-
return;
|
|
944
|
-
}
|
|
1170
|
+
if (state.queries.length === 0) return;
|
|
945
1171
|
const snap = { version, savedAt: Date.now(), state };
|
|
946
1172
|
const serialized = JSON.stringify(snap);
|
|
947
1173
|
if (serialized === lastSerialized) return;
|
|
948
1174
|
lastSerialized = serialized;
|
|
949
1175
|
await storage.setItem(storageKey, serialized);
|
|
950
1176
|
};
|
|
1177
|
+
const scheduleWrite = () => {
|
|
1178
|
+
if (unsubscribed) return;
|
|
1179
|
+
if (pendingTimer) return;
|
|
1180
|
+
pendingTimer = setTimeout(() => {
|
|
1181
|
+
pendingTimer = null;
|
|
1182
|
+
void persist();
|
|
1183
|
+
}, throttleMs);
|
|
1184
|
+
};
|
|
951
1185
|
const restore = async () => {
|
|
952
1186
|
const raw = await storage.getItem(storageKey);
|
|
953
1187
|
if (!raw) return;
|
|
@@ -966,15 +1200,17 @@ function persistQueryClient(options) {
|
|
|
966
1200
|
await storage.removeItem(storageKey);
|
|
967
1201
|
}
|
|
968
1202
|
};
|
|
969
|
-
|
|
970
|
-
void persist();
|
|
971
|
-
}, throttleMs);
|
|
1203
|
+
const unsubscribeFromCache = client.cache.subscribeAll(scheduleWrite);
|
|
972
1204
|
return {
|
|
973
1205
|
restore,
|
|
974
1206
|
persist,
|
|
975
1207
|
unsubscribe: () => {
|
|
976
|
-
|
|
977
|
-
|
|
1208
|
+
unsubscribed = true;
|
|
1209
|
+
unsubscribeFromCache();
|
|
1210
|
+
if (pendingTimer) {
|
|
1211
|
+
clearTimeout(pendingTimer);
|
|
1212
|
+
pendingTimer = null;
|
|
1213
|
+
}
|
|
978
1214
|
}
|
|
979
1215
|
};
|
|
980
1216
|
}
|
|
@@ -1075,6 +1311,6 @@ function apiPaginate(endpoint, fetchConfig = {}, options) {
|
|
|
1075
1311
|
}
|
|
1076
1312
|
var index_default = apiClient;
|
|
1077
1313
|
|
|
1078
|
-
export { ApiClientProvider, ApiError, QueryCache, QueryClient, apiMutation, apiPaginate, configureApiClient, index_default as default, focusManager, getConfig, getQueryClient, hashQueryKey, inspectCache, invalidateAll, isConfigured, matchQueryKey, onlineManager, persistQueryClient, setQueryClient, summarizeCache, useQueryClient };
|
|
1314
|
+
export { ApiClientProvider, ApiError, QueryCache, QueryClient, apiMutation, apiPaginate, businessErrorToApiError, configureApiClient, index_default as default, focusManager, getConfig, getQueryClient, hashQueryKey, inspectCache, invalidateAll, isConfigured, matchQueryKey, onlineManager, persistQueryClient, setQueryClient, summarizeCache, toApiError, useIsFetching, useIsMutating, useQueryClient };
|
|
1079
1315
|
//# sourceMappingURL=index.mjs.map
|
|
1080
1316
|
//# sourceMappingURL=index.mjs.map
|