@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.js
CHANGED
|
@@ -36,6 +36,52 @@ var ApiError = class _ApiError extends Error {
|
|
|
36
36
|
Object.setPrototypeOf(this, _ApiError.prototype);
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
|
+
var isObject = (v) => typeof v === "object" && v !== null;
|
|
40
|
+
function toApiError(thrown) {
|
|
41
|
+
if (thrown instanceof ApiError) return thrown;
|
|
42
|
+
if (isObject(thrown) && "response" in thrown && isObject(thrown.response)) {
|
|
43
|
+
const r = thrown.response;
|
|
44
|
+
const status = typeof r.status === "number" ? r.status : 0;
|
|
45
|
+
const data = isObject(r.data) ? r.data : void 0;
|
|
46
|
+
const message = (data && typeof data.message === "string" ? data.message : void 0) ?? (thrown instanceof Error ? thrown.message : void 0) ?? `HTTP ${status}`;
|
|
47
|
+
return new ApiError({
|
|
48
|
+
message,
|
|
49
|
+
status,
|
|
50
|
+
code: data && typeof data.code === "string" ? data.code : void 0,
|
|
51
|
+
errors: data?.errors,
|
|
52
|
+
isNetworkError: status === 0,
|
|
53
|
+
isUnauthorized: status === 401,
|
|
54
|
+
isValidationError: status === 422 || data?.errors !== void 0,
|
|
55
|
+
raw: r.data ?? thrown
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
if (thrown instanceof Error) {
|
|
59
|
+
return new ApiError({
|
|
60
|
+
message: thrown.message,
|
|
61
|
+
status: 0,
|
|
62
|
+
isNetworkError: true,
|
|
63
|
+
raw: thrown
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return new ApiError({
|
|
67
|
+
message: "Unknown error",
|
|
68
|
+
status: 0,
|
|
69
|
+
raw: thrown
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function businessErrorToApiError(response) {
|
|
73
|
+
if (!isObject(response)) {
|
|
74
|
+
return new ApiError({ message: "Request failed", status: 200, raw: response });
|
|
75
|
+
}
|
|
76
|
+
return new ApiError({
|
|
77
|
+
message: typeof response.message === "string" ? response.message : "Request failed",
|
|
78
|
+
status: 200,
|
|
79
|
+
code: typeof response.code === "string" ? response.code : void 0,
|
|
80
|
+
errors: response.errors,
|
|
81
|
+
isValidationError: response.errors !== void 0,
|
|
82
|
+
raw: response
|
|
83
|
+
});
|
|
84
|
+
}
|
|
39
85
|
|
|
40
86
|
// src/utils.ts
|
|
41
87
|
function buildEndpoint(endpoint, params) {
|
|
@@ -44,7 +90,7 @@ function buildEndpoint(endpoint, params) {
|
|
|
44
90
|
}
|
|
45
91
|
return endpoint;
|
|
46
92
|
}
|
|
47
|
-
async function executeRequest(endpoint, fetchConfig, params) {
|
|
93
|
+
async function executeRequest(endpoint, fetchConfig, params, signal) {
|
|
48
94
|
const url = buildEndpoint(endpoint, params);
|
|
49
95
|
const config = getConfig();
|
|
50
96
|
try {
|
|
@@ -55,7 +101,8 @@ async function executeRequest(endpoint, fetchConfig, params) {
|
|
|
55
101
|
params: {
|
|
56
102
|
...requestConfig.requestParams,
|
|
57
103
|
...params
|
|
58
|
-
}
|
|
104
|
+
},
|
|
105
|
+
signal
|
|
59
106
|
});
|
|
60
107
|
} else {
|
|
61
108
|
response = await config.httpClient.request(url, {
|
|
@@ -63,38 +110,29 @@ async function executeRequest(endpoint, fetchConfig, params) {
|
|
|
63
110
|
data: {
|
|
64
111
|
...requestConfig.requestParams,
|
|
65
112
|
...params
|
|
66
|
-
}
|
|
113
|
+
},
|
|
114
|
+
signal
|
|
67
115
|
});
|
|
68
116
|
}
|
|
69
117
|
const hasExplicitStatus = response && typeof response === "object" && "status" in response && typeof response.status === "boolean";
|
|
70
118
|
if (config.throwOnError && hasExplicitStatus && response.status === false) {
|
|
71
|
-
|
|
72
|
-
throw new ApiError({
|
|
73
|
-
message: body.message ?? "Request failed",
|
|
74
|
-
status: 200,
|
|
75
|
-
errors: body.errors,
|
|
76
|
-
raw: response
|
|
77
|
-
});
|
|
119
|
+
throw businessErrorToApiError(response);
|
|
78
120
|
}
|
|
79
121
|
return { ...response, status: true };
|
|
80
122
|
} catch (e) {
|
|
81
|
-
if (e instanceof ApiError)
|
|
123
|
+
if (e instanceof ApiError) {
|
|
124
|
+
if (e.status === 401 && config.onUnauthorized) {
|
|
125
|
+
await config.onUnauthorized();
|
|
126
|
+
}
|
|
127
|
+
throw e;
|
|
128
|
+
}
|
|
82
129
|
const error = e;
|
|
83
130
|
const httpStatus = error.response?.status;
|
|
84
131
|
if (httpStatus === 401 && config.onUnauthorized) {
|
|
85
132
|
await config.onUnauthorized();
|
|
86
133
|
}
|
|
87
134
|
if (config.throwOnError) {
|
|
88
|
-
|
|
89
|
-
const isNetwork = httpStatus === void 0;
|
|
90
|
-
throw new ApiError({
|
|
91
|
-
message: data?.message ?? (e instanceof Error ? e.message : void 0) ?? (isNetwork ? "Network error" : "Request error"),
|
|
92
|
-
status: httpStatus ?? 0,
|
|
93
|
-
code: data?.code,
|
|
94
|
-
errors: data,
|
|
95
|
-
isNetworkError: isNetwork,
|
|
96
|
-
raw: e
|
|
97
|
-
});
|
|
135
|
+
throw toApiError(e);
|
|
98
136
|
}
|
|
99
137
|
if (error.response?.data) {
|
|
100
138
|
return {
|
|
@@ -117,6 +155,21 @@ function handleResponse(result, onSuccess, onError) {
|
|
|
117
155
|
}
|
|
118
156
|
}
|
|
119
157
|
|
|
158
|
+
// src/logger.ts
|
|
159
|
+
function getLogger() {
|
|
160
|
+
if (!isConfigured()) return void 0;
|
|
161
|
+
return getConfig().logger;
|
|
162
|
+
}
|
|
163
|
+
function callLogger(method, ...args) {
|
|
164
|
+
const logger = getLogger();
|
|
165
|
+
const fn = logger?.[method];
|
|
166
|
+
if (!fn) return;
|
|
167
|
+
try {
|
|
168
|
+
fn(...args);
|
|
169
|
+
} catch {
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
120
173
|
// src/query/focus-manager.ts
|
|
121
174
|
var FocusManager = class {
|
|
122
175
|
constructor() {
|
|
@@ -255,6 +308,21 @@ var DEFAULT_STALE_TIME = 0;
|
|
|
255
308
|
var QueryCache = class {
|
|
256
309
|
constructor() {
|
|
257
310
|
this.entries = /* @__PURE__ */ new Map();
|
|
311
|
+
this.globalListeners = /* @__PURE__ */ new Set();
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Подписка на любое изменение кэша: setData, invalidate, remove,
|
|
315
|
+
* успешный/ошибочный fetch. Используется persistQueryClient и
|
|
316
|
+
* devtools-подобными адаптерами. Не дублирует `subscribe(key, ...)`.
|
|
317
|
+
*/
|
|
318
|
+
subscribeAll(listener) {
|
|
319
|
+
this.globalListeners.add(listener);
|
|
320
|
+
return () => {
|
|
321
|
+
this.globalListeners.delete(listener);
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
notifyGlobal() {
|
|
325
|
+
for (const listener of this.globalListeners) listener();
|
|
258
326
|
}
|
|
259
327
|
ensureEntry(key, staleTime, gcTime) {
|
|
260
328
|
const hash = hashQueryKey(key);
|
|
@@ -271,7 +339,9 @@ var QueryCache = class {
|
|
|
271
339
|
},
|
|
272
340
|
subscribers: /* @__PURE__ */ new Set(),
|
|
273
341
|
inflight: null,
|
|
342
|
+
inflightController: null,
|
|
274
343
|
gcTimer: null,
|
|
344
|
+
lastQueryFn: null,
|
|
275
345
|
staleTime: staleTime ?? DEFAULT_STALE_TIME,
|
|
276
346
|
gcTime: gcTime ?? DEFAULT_GC_TIME
|
|
277
347
|
};
|
|
@@ -319,6 +389,7 @@ var QueryCache = class {
|
|
|
319
389
|
}
|
|
320
390
|
notify(entry) {
|
|
321
391
|
for (const listener of entry.subscribers) listener();
|
|
392
|
+
this.notifyGlobal();
|
|
322
393
|
}
|
|
323
394
|
scheduleGc(entry) {
|
|
324
395
|
if (entry.gcTimer) clearTimeout(entry.gcTimer);
|
|
@@ -341,18 +412,21 @@ var QueryCache = class {
|
|
|
341
412
|
const gcTime = options.gcTime ?? DEFAULT_GC_TIME;
|
|
342
413
|
const entry = this.ensureEntry(key, staleTime, gcTime);
|
|
343
414
|
const isFresh = entry.state.status === "success" && !entry.state.isStale && Date.now() - entry.state.updatedAt < staleTime;
|
|
415
|
+
entry.lastQueryFn = queryFn;
|
|
344
416
|
if (!options.force && isFresh && entry.state.data !== void 0) {
|
|
345
417
|
return entry.state.data;
|
|
346
418
|
}
|
|
347
419
|
if (entry.inflight) return entry.inflight;
|
|
348
420
|
entry.state = { ...entry.state, status: "loading", error: null };
|
|
349
421
|
this.notify(entry);
|
|
422
|
+
const controller = new AbortController();
|
|
350
423
|
const token = Symbol("inflight");
|
|
351
424
|
entry.inflightToken = token;
|
|
425
|
+
entry.inflightController = controller;
|
|
352
426
|
const isCurrent = () => entry.inflightToken === token;
|
|
353
427
|
const myPromise = (async () => {
|
|
354
428
|
try {
|
|
355
|
-
const data = await queryFn();
|
|
429
|
+
const data = await queryFn({ signal: controller.signal });
|
|
356
430
|
if (!isCurrent()) return data;
|
|
357
431
|
entry.state = {
|
|
358
432
|
data,
|
|
@@ -373,7 +447,10 @@ var QueryCache = class {
|
|
|
373
447
|
this.notify(entry);
|
|
374
448
|
throw err;
|
|
375
449
|
} finally {
|
|
376
|
-
if (isCurrent())
|
|
450
|
+
if (isCurrent()) {
|
|
451
|
+
entry.inflight = null;
|
|
452
|
+
entry.inflightController = null;
|
|
453
|
+
}
|
|
377
454
|
}
|
|
378
455
|
})();
|
|
379
456
|
entry.inflight = myPromise;
|
|
@@ -395,6 +472,26 @@ var QueryCache = class {
|
|
|
395
472
|
}
|
|
396
473
|
return invalidated;
|
|
397
474
|
}
|
|
475
|
+
/**
|
|
476
|
+
* Перезапускает все записи, матчинг predicate, у которых сохранён
|
|
477
|
+
* `lastQueryFn` (т.е. их хоть раз кто-то загрузил через `fetch`).
|
|
478
|
+
* Возвращает promise, который резолвится когда все запросы завершились.
|
|
479
|
+
* Ошибки отдельных запросов проглатываются — общий promise успешный.
|
|
480
|
+
*/
|
|
481
|
+
refetchQueries(predicate) {
|
|
482
|
+
const match = typeof predicate === "function" ? predicate : (k) => matchQueryKey(predicate, k);
|
|
483
|
+
const promises = [];
|
|
484
|
+
for (const entry of this.entries.values()) {
|
|
485
|
+
if (!match(entry.key)) continue;
|
|
486
|
+
if (!entry.lastQueryFn) continue;
|
|
487
|
+
promises.push(
|
|
488
|
+
this.fetch(entry.key, entry.lastQueryFn, { force: true }).catch(
|
|
489
|
+
() => void 0
|
|
490
|
+
)
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
return Promise.all(promises).then(() => void 0);
|
|
494
|
+
}
|
|
398
495
|
/**
|
|
399
496
|
* Отменяет «привязку» inflight-промиса к ключу. Сам HTTP-запрос
|
|
400
497
|
* продолжит исполняться (executeRequest не использует AbortSignal),
|
|
@@ -405,6 +502,8 @@ var QueryCache = class {
|
|
|
405
502
|
const match = typeof predicate === "function" ? predicate : (k) => matchQueryKey(predicate, k);
|
|
406
503
|
for (const entry of this.entries.values()) {
|
|
407
504
|
if (match(entry.key) && entry.inflight) {
|
|
505
|
+
entry.inflightController?.abort();
|
|
506
|
+
entry.inflightController = null;
|
|
408
507
|
entry.inflight = null;
|
|
409
508
|
entry.inflightToken = void 0;
|
|
410
509
|
if (entry.state.status === "loading") {
|
|
@@ -420,12 +519,28 @@ var QueryCache = class {
|
|
|
420
519
|
*/
|
|
421
520
|
remove(predicate) {
|
|
422
521
|
const match = typeof predicate === "function" ? predicate : (k) => matchQueryKey(predicate, k);
|
|
522
|
+
let removed = false;
|
|
423
523
|
for (const [hash, entry] of [...this.entries]) {
|
|
424
524
|
if (match(entry.key)) {
|
|
425
525
|
if (entry.gcTimer) clearTimeout(entry.gcTimer);
|
|
426
526
|
this.entries.delete(hash);
|
|
527
|
+
removed = true;
|
|
427
528
|
}
|
|
428
529
|
}
|
|
530
|
+
if (removed) this.notifyGlobal();
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Количество inflight-запросов в кэше, опционально отфильтрованных
|
|
534
|
+
* по predicate. Используется `useIsFetching()` для глобального
|
|
535
|
+
* индикатора загрузки.
|
|
536
|
+
*/
|
|
537
|
+
countFetching(predicate) {
|
|
538
|
+
const match = !predicate ? () => true : typeof predicate === "function" ? predicate : (k) => matchQueryKey(predicate, k);
|
|
539
|
+
let n = 0;
|
|
540
|
+
for (const entry of this.entries.values()) {
|
|
541
|
+
if (entry.state.status === "loading" && match(entry.key)) n++;
|
|
542
|
+
}
|
|
543
|
+
return n;
|
|
429
544
|
}
|
|
430
545
|
/** Только для тестов / DevTools. */
|
|
431
546
|
_debugEntries() {
|
|
@@ -484,7 +599,8 @@ var QueryClient = class {
|
|
|
484
599
|
return this.cache.fetch(key, queryFn, options);
|
|
485
600
|
}
|
|
486
601
|
invalidateQueries(predicate) {
|
|
487
|
-
this.cache.invalidate(predicate);
|
|
602
|
+
const hashes = this.cache.invalidate(predicate);
|
|
603
|
+
if (hashes.length > 0) callLogger("onInvalidate", hashes);
|
|
488
604
|
}
|
|
489
605
|
removeQueries(predicate) {
|
|
490
606
|
this.cache.remove(predicate);
|
|
@@ -492,6 +608,19 @@ var QueryClient = class {
|
|
|
492
608
|
cancelQueries(predicate) {
|
|
493
609
|
this.cache.cancelQueries(predicate);
|
|
494
610
|
}
|
|
611
|
+
refetchQueries(predicate) {
|
|
612
|
+
return this.cache.refetchQueries(predicate);
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Кладёт запрос в кэш, не пробрасывая ошибки. Подходит для оптимистичной
|
|
616
|
+
* подгрузки следующего экрана при наведении / долгом тапе.
|
|
617
|
+
*/
|
|
618
|
+
prefetchQuery(key, queryFn, options) {
|
|
619
|
+
return this.cache.fetch(key, queryFn, options).then(
|
|
620
|
+
() => void 0,
|
|
621
|
+
() => void 0
|
|
622
|
+
);
|
|
623
|
+
}
|
|
495
624
|
};
|
|
496
625
|
var globalClient = null;
|
|
497
626
|
function getQueryClient() {
|
|
@@ -530,27 +659,37 @@ function createUseFetch(endpoint, fetchConfig) {
|
|
|
530
659
|
}, [customKey ? hashQueryKey(customKey) : null, serializedParams]);
|
|
531
660
|
const client = getQueryClient();
|
|
532
661
|
const cache = client.cache;
|
|
533
|
-
const queryFn = react.useCallback(
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
662
|
+
const queryFn = react.useCallback(
|
|
663
|
+
({ signal }) => {
|
|
664
|
+
const parsedParams = serializedParams ? JSON.parse(serializedParams) : void 0;
|
|
665
|
+
return executeRequest(endpoint, fetchConfig, parsedParams, signal);
|
|
666
|
+
},
|
|
667
|
+
[serializedParams]
|
|
668
|
+
);
|
|
537
669
|
const initialState = cache.getState(queryKey);
|
|
538
670
|
const [, forceRender] = react.useState(0);
|
|
539
671
|
const rerender = react.useCallback(() => forceRender((v) => v + 1), []);
|
|
540
672
|
react.useEffect(() => {
|
|
541
|
-
if (!enabled) return;
|
|
542
673
|
const unsub = cache.subscribe(queryKey, rerender);
|
|
543
|
-
return
|
|
544
|
-
|
|
674
|
+
return () => {
|
|
675
|
+
unsub();
|
|
676
|
+
const state2 = cache._debugEntries().get(hashQueryKey(queryKey));
|
|
677
|
+
if (state2 && state2.subscribers.size === 0 && state2.inflight) {
|
|
678
|
+
cache.cancelQueries(queryKey);
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
}, [cache, hashQueryKey(queryKey), rerender]);
|
|
545
682
|
const lastNotifiedRef = react.useRef({});
|
|
546
683
|
const runFetch = react.useCallback(
|
|
547
684
|
async (force) => {
|
|
685
|
+
callLogger("onFetchStart", queryKey);
|
|
548
686
|
try {
|
|
549
687
|
const data = await cache.fetch(queryKey, queryFn, {
|
|
550
688
|
staleTime,
|
|
551
689
|
gcTime,
|
|
552
690
|
force
|
|
553
691
|
});
|
|
692
|
+
callLogger("onFetchSuccess", queryKey, data);
|
|
554
693
|
if (lastNotifiedRef.current.success !== data) {
|
|
555
694
|
lastNotifiedRef.current.success = data;
|
|
556
695
|
handleResponse(
|
|
@@ -560,6 +699,7 @@ function createUseFetch(endpoint, fetchConfig) {
|
|
|
560
699
|
);
|
|
561
700
|
}
|
|
562
701
|
} catch (err) {
|
|
702
|
+
callLogger("onFetchError", queryKey, err);
|
|
563
703
|
const e = err;
|
|
564
704
|
const hash = `${e.name}:${e.message}`;
|
|
565
705
|
if (lastNotifiedRef.current.errorHash !== hash) {
|
|
@@ -643,6 +783,47 @@ function createUseFetch(endpoint, fetchConfig) {
|
|
|
643
783
|
};
|
|
644
784
|
};
|
|
645
785
|
}
|
|
786
|
+
|
|
787
|
+
// src/query/mutation-counter.ts
|
|
788
|
+
var MutationCounter = class {
|
|
789
|
+
constructor() {
|
|
790
|
+
this.active = /* @__PURE__ */ new Map();
|
|
791
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
792
|
+
}
|
|
793
|
+
start(scope) {
|
|
794
|
+
const id = Symbol("mutation");
|
|
795
|
+
this.active.set(id, scope);
|
|
796
|
+
this.notify();
|
|
797
|
+
return id;
|
|
798
|
+
}
|
|
799
|
+
stop(id) {
|
|
800
|
+
if (this.active.delete(id)) this.notify();
|
|
801
|
+
}
|
|
802
|
+
count(predicate) {
|
|
803
|
+
if (!predicate) return this.active.size;
|
|
804
|
+
let n = 0;
|
|
805
|
+
for (const scope of this.active.values()) {
|
|
806
|
+
if (typeof predicate === "function") {
|
|
807
|
+
if (predicate(scope)) n++;
|
|
808
|
+
} else {
|
|
809
|
+
if (scope && matchQueryKey(predicate, scope)) n++;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
return n;
|
|
813
|
+
}
|
|
814
|
+
subscribe(listener) {
|
|
815
|
+
this.listeners.add(listener);
|
|
816
|
+
return () => {
|
|
817
|
+
this.listeners.delete(listener);
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
notify() {
|
|
821
|
+
for (const l of this.listeners) l();
|
|
822
|
+
}
|
|
823
|
+
};
|
|
824
|
+
var mutationCounter = new MutationCounter();
|
|
825
|
+
|
|
826
|
+
// src/hooks/use-mutation.ts
|
|
646
827
|
function createUseMutation(endpoint, fetchConfig) {
|
|
647
828
|
return (options = {}) => {
|
|
648
829
|
const {
|
|
@@ -669,10 +850,21 @@ function createUseMutation(endpoint, fetchConfig) {
|
|
|
669
850
|
(vars, result) => {
|
|
670
851
|
if (!invalidateKeys) return;
|
|
671
852
|
const client = getQueryClient();
|
|
672
|
-
|
|
673
|
-
|
|
853
|
+
if (Array.isArray(invalidateKeys)) {
|
|
854
|
+
if (invalidateKeys.length === 0) return;
|
|
855
|
+
client.invalidateQueries(
|
|
856
|
+
(k) => invalidateKeys.some((prefix) => matchQueryKey(prefix, k))
|
|
857
|
+
);
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
const out = invalidateKeys(vars, result);
|
|
861
|
+
if (typeof out === "function") {
|
|
862
|
+
client.invalidateQueries(out);
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
if (out.length === 0) return;
|
|
674
866
|
client.invalidateQueries(
|
|
675
|
-
(k) =>
|
|
867
|
+
(k) => out.some((prefix) => matchQueryKey(prefix, k))
|
|
676
868
|
);
|
|
677
869
|
},
|
|
678
870
|
[invalidateKeys]
|
|
@@ -683,6 +875,10 @@ function createUseMutation(endpoint, fetchConfig) {
|
|
|
683
875
|
setIsSuccess(false);
|
|
684
876
|
setIsError(false);
|
|
685
877
|
setError(null);
|
|
878
|
+
const scope = Array.isArray(invalidateKeys) && invalidateKeys.length > 0 ? invalidateKeys[0] : void 0;
|
|
879
|
+
const mutationId = mutationCounter.start(scope);
|
|
880
|
+
const endpointId = buildEndpoint(endpoint, variables);
|
|
881
|
+
callLogger("onMutationStart", endpointId, variables);
|
|
686
882
|
let context;
|
|
687
883
|
try {
|
|
688
884
|
if (onMutate) {
|
|
@@ -693,6 +889,7 @@ function createUseMutation(endpoint, fetchConfig) {
|
|
|
693
889
|
setData(result);
|
|
694
890
|
if (result.status) {
|
|
695
891
|
setIsSuccess(true);
|
|
892
|
+
callLogger("onMutationSuccess", endpointId, variables, result);
|
|
696
893
|
if (setQueryData) {
|
|
697
894
|
setQueryData(getQueryClient(), variables, result);
|
|
698
895
|
}
|
|
@@ -704,6 +901,7 @@ function createUseMutation(endpoint, fetchConfig) {
|
|
|
704
901
|
const err = new Error(result.message ?? "Mutation failed");
|
|
705
902
|
setIsError(true);
|
|
706
903
|
setError(err);
|
|
904
|
+
callLogger("onMutationError", endpointId, variables, err);
|
|
707
905
|
if (onError) {
|
|
708
906
|
await onError(err, variables, context);
|
|
709
907
|
}
|
|
@@ -717,6 +915,7 @@ function createUseMutation(endpoint, fetchConfig) {
|
|
|
717
915
|
setError(error2);
|
|
718
916
|
setIsError(true);
|
|
719
917
|
setIsSuccess(false);
|
|
918
|
+
callLogger("onMutationError", endpointId, variables, error2);
|
|
720
919
|
if (onError) {
|
|
721
920
|
await onError(error2, variables, context);
|
|
722
921
|
}
|
|
@@ -725,6 +924,7 @@ function createUseMutation(endpoint, fetchConfig) {
|
|
|
725
924
|
}
|
|
726
925
|
throw error2;
|
|
727
926
|
} finally {
|
|
927
|
+
mutationCounter.stop(mutationId);
|
|
728
928
|
setIsLoading(false);
|
|
729
929
|
}
|
|
730
930
|
},
|
|
@@ -801,14 +1001,14 @@ function createUsePaginate(endpoint, fetchConfig, options) {
|
|
|
801
1001
|
[keyPrefix, limit]
|
|
802
1002
|
);
|
|
803
1003
|
const pageQueryFn = react.useCallback(
|
|
804
|
-
(page) => () => {
|
|
1004
|
+
(page) => ({ signal }) => {
|
|
805
1005
|
const parsedParams = serializedParams ? JSON.parse(serializedParams) : {};
|
|
806
1006
|
const requestParams = {
|
|
807
1007
|
...parsedParams,
|
|
808
1008
|
page,
|
|
809
1009
|
limit
|
|
810
1010
|
};
|
|
811
|
-
return executeRequest(endpoint, fetchConfig, requestParams);
|
|
1011
|
+
return executeRequest(endpoint, fetchConfig, requestParams, signal);
|
|
812
1012
|
},
|
|
813
1013
|
[serializedParams, limit]
|
|
814
1014
|
);
|
|
@@ -927,6 +1127,33 @@ function createUsePaginate(endpoint, fetchConfig, options) {
|
|
|
927
1127
|
};
|
|
928
1128
|
};
|
|
929
1129
|
}
|
|
1130
|
+
function useIsFetching(predicate) {
|
|
1131
|
+
const client = getQueryClient();
|
|
1132
|
+
const get = react.useCallback(
|
|
1133
|
+
() => client.cache.countFetching(predicate),
|
|
1134
|
+
[client.cache, predicate]
|
|
1135
|
+
);
|
|
1136
|
+
const [count, setCount] = react.useState(get);
|
|
1137
|
+
react.useEffect(() => {
|
|
1138
|
+
setCount(get());
|
|
1139
|
+
const unsub = client.cache.subscribeAll(() => setCount(get()));
|
|
1140
|
+
return unsub;
|
|
1141
|
+
}, [client.cache, get]);
|
|
1142
|
+
return count;
|
|
1143
|
+
}
|
|
1144
|
+
function useIsMutating(predicate) {
|
|
1145
|
+
const get = react.useCallback(
|
|
1146
|
+
() => mutationCounter.count(predicate),
|
|
1147
|
+
[predicate]
|
|
1148
|
+
);
|
|
1149
|
+
const [count, setCount] = react.useState(get);
|
|
1150
|
+
react.useEffect(() => {
|
|
1151
|
+
setCount(get());
|
|
1152
|
+
const unsub = mutationCounter.subscribe(() => setCount(get()));
|
|
1153
|
+
return unsub;
|
|
1154
|
+
}, [get]);
|
|
1155
|
+
return count;
|
|
1156
|
+
}
|
|
930
1157
|
|
|
931
1158
|
// src/query/persist.ts
|
|
932
1159
|
function persistQueryClient(options) {
|
|
@@ -939,19 +1166,26 @@ function persistQueryClient(options) {
|
|
|
939
1166
|
maxAge,
|
|
940
1167
|
version
|
|
941
1168
|
} = options;
|
|
942
|
-
let
|
|
1169
|
+
let pendingTimer = null;
|
|
943
1170
|
let lastSerialized = null;
|
|
1171
|
+
let unsubscribed = false;
|
|
944
1172
|
const persist = async () => {
|
|
945
1173
|
const state = client.cache.dehydrate(allowList);
|
|
946
|
-
if (state.queries.length === 0)
|
|
947
|
-
return;
|
|
948
|
-
}
|
|
1174
|
+
if (state.queries.length === 0) return;
|
|
949
1175
|
const snap = { version, savedAt: Date.now(), state };
|
|
950
1176
|
const serialized = JSON.stringify(snap);
|
|
951
1177
|
if (serialized === lastSerialized) return;
|
|
952
1178
|
lastSerialized = serialized;
|
|
953
1179
|
await storage.setItem(storageKey, serialized);
|
|
954
1180
|
};
|
|
1181
|
+
const scheduleWrite = () => {
|
|
1182
|
+
if (unsubscribed) return;
|
|
1183
|
+
if (pendingTimer) return;
|
|
1184
|
+
pendingTimer = setTimeout(() => {
|
|
1185
|
+
pendingTimer = null;
|
|
1186
|
+
void persist();
|
|
1187
|
+
}, throttleMs);
|
|
1188
|
+
};
|
|
955
1189
|
const restore = async () => {
|
|
956
1190
|
const raw = await storage.getItem(storageKey);
|
|
957
1191
|
if (!raw) return;
|
|
@@ -970,15 +1204,17 @@ function persistQueryClient(options) {
|
|
|
970
1204
|
await storage.removeItem(storageKey);
|
|
971
1205
|
}
|
|
972
1206
|
};
|
|
973
|
-
|
|
974
|
-
void persist();
|
|
975
|
-
}, throttleMs);
|
|
1207
|
+
const unsubscribeFromCache = client.cache.subscribeAll(scheduleWrite);
|
|
976
1208
|
return {
|
|
977
1209
|
restore,
|
|
978
1210
|
persist,
|
|
979
1211
|
unsubscribe: () => {
|
|
980
|
-
|
|
981
|
-
|
|
1212
|
+
unsubscribed = true;
|
|
1213
|
+
unsubscribeFromCache();
|
|
1214
|
+
if (pendingTimer) {
|
|
1215
|
+
clearTimeout(pendingTimer);
|
|
1216
|
+
pendingTimer = null;
|
|
1217
|
+
}
|
|
982
1218
|
}
|
|
983
1219
|
};
|
|
984
1220
|
}
|
|
@@ -1085,6 +1321,7 @@ exports.QueryCache = QueryCache;
|
|
|
1085
1321
|
exports.QueryClient = QueryClient;
|
|
1086
1322
|
exports.apiMutation = apiMutation;
|
|
1087
1323
|
exports.apiPaginate = apiPaginate;
|
|
1324
|
+
exports.businessErrorToApiError = businessErrorToApiError;
|
|
1088
1325
|
exports.configureApiClient = configureApiClient;
|
|
1089
1326
|
exports.default = index_default;
|
|
1090
1327
|
exports.focusManager = focusManager;
|
|
@@ -1099,6 +1336,9 @@ exports.onlineManager = onlineManager;
|
|
|
1099
1336
|
exports.persistQueryClient = persistQueryClient;
|
|
1100
1337
|
exports.setQueryClient = setQueryClient;
|
|
1101
1338
|
exports.summarizeCache = summarizeCache;
|
|
1339
|
+
exports.toApiError = toApiError;
|
|
1340
|
+
exports.useIsFetching = useIsFetching;
|
|
1341
|
+
exports.useIsMutating = useIsMutating;
|
|
1102
1342
|
exports.useQueryClient = useQueryClient;
|
|
1103
1343
|
//# sourceMappingURL=index.js.map
|
|
1104
1344
|
//# sourceMappingURL=index.js.map
|