@krymskyimaksym/react-api-client 2.0.0-beta.1 → 2.0.0-beta.3

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/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { createContext, useMemo, createElement, useContext, useCallback, useState, useEffect, useRef } from 'react';
1
+ import { createContext, useCallback, useState, useEffect, useMemo, createElement, useContext, useRef } from 'react';
2
2
 
3
3
  // src/config.ts
4
4
  var globalConfig = null;
@@ -151,6 +151,21 @@ function handleResponse(result, onSuccess, onError) {
151
151
  }
152
152
  }
153
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
+
154
169
  // src/query/focus-manager.ts
155
170
  var FocusManager = class {
156
171
  constructor() {
@@ -322,6 +337,7 @@ var QueryCache = class {
322
337
  inflight: null,
323
338
  inflightController: null,
324
339
  gcTimer: null,
340
+ lastQueryFn: null,
325
341
  staleTime: staleTime ?? DEFAULT_STALE_TIME,
326
342
  gcTime: gcTime ?? DEFAULT_GC_TIME
327
343
  };
@@ -392,6 +408,7 @@ var QueryCache = class {
392
408
  const gcTime = options.gcTime ?? DEFAULT_GC_TIME;
393
409
  const entry = this.ensureEntry(key, staleTime, gcTime);
394
410
  const isFresh = entry.state.status === "success" && !entry.state.isStale && Date.now() - entry.state.updatedAt < staleTime;
411
+ entry.lastQueryFn = queryFn;
395
412
  if (!options.force && isFresh && entry.state.data !== void 0) {
396
413
  return entry.state.data;
397
414
  }
@@ -451,6 +468,26 @@ var QueryCache = class {
451
468
  }
452
469
  return invalidated;
453
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
+ }
454
491
  /**
455
492
  * Отменяет «привязку» inflight-промиса к ключу. Сам HTTP-запрос
456
493
  * продолжит исполняться (executeRequest не использует AbortSignal),
@@ -488,6 +525,19 @@ var QueryCache = class {
488
525
  }
489
526
  if (removed) this.notifyGlobal();
490
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;
540
+ }
491
541
  /** Только для тестов / DevTools. */
492
542
  _debugEntries() {
493
543
  return this.entries;
@@ -545,7 +595,8 @@ var QueryClient = class {
545
595
  return this.cache.fetch(key, queryFn, options);
546
596
  }
547
597
  invalidateQueries(predicate) {
548
- this.cache.invalidate(predicate);
598
+ const hashes = this.cache.invalidate(predicate);
599
+ if (hashes.length > 0) callLogger("onInvalidate", hashes);
549
600
  }
550
601
  removeQueries(predicate) {
551
602
  this.cache.remove(predicate);
@@ -553,6 +604,19 @@ var QueryClient = class {
553
604
  cancelQueries(predicate) {
554
605
  this.cache.cancelQueries(predicate);
555
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
+ }
556
620
  };
557
621
  var globalClient = null;
558
622
  function getQueryClient() {
@@ -614,12 +678,14 @@ function createUseFetch(endpoint, fetchConfig) {
614
678
  const lastNotifiedRef = useRef({});
615
679
  const runFetch = useCallback(
616
680
  async (force) => {
681
+ callLogger("onFetchStart", queryKey);
617
682
  try {
618
683
  const data = await cache.fetch(queryKey, queryFn, {
619
684
  staleTime,
620
685
  gcTime,
621
686
  force
622
687
  });
688
+ callLogger("onFetchSuccess", queryKey, data);
623
689
  if (lastNotifiedRef.current.success !== data) {
624
690
  lastNotifiedRef.current.success = data;
625
691
  handleResponse(
@@ -629,6 +695,7 @@ function createUseFetch(endpoint, fetchConfig) {
629
695
  );
630
696
  }
631
697
  } catch (err) {
698
+ callLogger("onFetchError", queryKey, err);
632
699
  const e = err;
633
700
  const hash = `${e.name}:${e.message}`;
634
701
  if (lastNotifiedRef.current.errorHash !== hash) {
@@ -712,6 +779,47 @@ function createUseFetch(endpoint, fetchConfig) {
712
779
  };
713
780
  };
714
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
715
823
  function createUseMutation(endpoint, fetchConfig) {
716
824
  return (options = {}) => {
717
825
  const {
@@ -738,10 +846,21 @@ function createUseMutation(endpoint, fetchConfig) {
738
846
  (vars, result) => {
739
847
  if (!invalidateKeys) return;
740
848
  const client = getQueryClient();
741
- const keys = typeof invalidateKeys === "function" ? invalidateKeys(vars, result) : invalidateKeys;
742
- if (keys.length === 0) return;
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;
743
862
  client.invalidateQueries(
744
- (k) => keys.some((prefix) => matchQueryKey(prefix, k))
863
+ (k) => out.some((prefix) => matchQueryKey(prefix, k))
745
864
  );
746
865
  },
747
866
  [invalidateKeys]
@@ -752,6 +871,10 @@ function createUseMutation(endpoint, fetchConfig) {
752
871
  setIsSuccess(false);
753
872
  setIsError(false);
754
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);
755
878
  let context;
756
879
  try {
757
880
  if (onMutate) {
@@ -762,6 +885,7 @@ function createUseMutation(endpoint, fetchConfig) {
762
885
  setData(result);
763
886
  if (result.status) {
764
887
  setIsSuccess(true);
888
+ callLogger("onMutationSuccess", endpointId, variables, result);
765
889
  if (setQueryData) {
766
890
  setQueryData(getQueryClient(), variables, result);
767
891
  }
@@ -773,6 +897,7 @@ function createUseMutation(endpoint, fetchConfig) {
773
897
  const err = new Error(result.message ?? "Mutation failed");
774
898
  setIsError(true);
775
899
  setError(err);
900
+ callLogger("onMutationError", endpointId, variables, err);
776
901
  if (onError) {
777
902
  await onError(err, variables, context);
778
903
  }
@@ -786,6 +911,7 @@ function createUseMutation(endpoint, fetchConfig) {
786
911
  setError(error2);
787
912
  setIsError(true);
788
913
  setIsSuccess(false);
914
+ callLogger("onMutationError", endpointId, variables, error2);
789
915
  if (onError) {
790
916
  await onError(error2, variables, context);
791
917
  }
@@ -794,6 +920,7 @@ function createUseMutation(endpoint, fetchConfig) {
794
920
  }
795
921
  throw error2;
796
922
  } finally {
923
+ mutationCounter.stop(mutationId);
797
924
  setIsLoading(false);
798
925
  }
799
926
  },
@@ -996,6 +1123,33 @@ function createUsePaginate(endpoint, fetchConfig, options) {
996
1123
  };
997
1124
  };
998
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
+ }
999
1153
 
1000
1154
  // src/query/persist.ts
1001
1155
  function persistQueryClient(options) {
@@ -1157,6 +1311,6 @@ function apiPaginate(endpoint, fetchConfig = {}, options) {
1157
1311
  }
1158
1312
  var index_default = apiClient;
1159
1313
 
1160
- 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, 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 };
1161
1315
  //# sourceMappingURL=index.mjs.map
1162
1316
  //# sourceMappingURL=index.mjs.map