@braine/quantum-query 1.2.5 → 1.2.6

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.js CHANGED
@@ -176,7 +176,7 @@ function defineModel(def) {
176
176
  engine = storage;
177
177
  }
178
178
  if (engine) {
179
- const hydrate = () => {
179
+ const hydrate2 = () => {
180
180
  const process = (stored) => {
181
181
  try {
182
182
  if (stored) {
@@ -199,7 +199,7 @@ function defineModel(def) {
199
199
  if (debug) console.error(`[Quantum] Storage Access Failed`, err);
200
200
  }
201
201
  };
202
- hydrate();
202
+ hydrate2();
203
203
  const save = debounce(async () => {
204
204
  try {
205
205
  let stateToSave;
@@ -262,8 +262,48 @@ function useStore(store) {
262
262
  return proxy;
263
263
  }
264
264
 
265
+ // src/addon/signals.ts
266
+ import { signal, computed as preactComputed, effect, batch } from "@preact/signals-core";
267
+ function createSignal(initialValue, options) {
268
+ const s = signal(initialValue);
269
+ let unsubscribeActive;
270
+ let subscriberCount = 0;
271
+ return {
272
+ get: () => s.value,
273
+ set: (newValue) => {
274
+ s.value = newValue;
275
+ },
276
+ subscribe: (fn) => {
277
+ if (subscriberCount === 0) {
278
+ options?.onActive?.();
279
+ }
280
+ subscriberCount++;
281
+ const dispose = effect(() => {
282
+ fn(s.value);
283
+ });
284
+ return () => {
285
+ dispose();
286
+ subscriberCount--;
287
+ if (subscriberCount === 0) {
288
+ options?.onInactive?.();
289
+ }
290
+ };
291
+ }
292
+ };
293
+ }
294
+
295
+ // src/devtools/registry.ts
296
+ var stores = createSignal([]);
297
+ function registerStore(store, name = "Store") {
298
+ const current = stores.get();
299
+ if (!current.find((s) => s.store === store)) {
300
+ stores.set([...current, { name, store }]);
301
+ }
302
+ }
303
+
265
304
  // src/middleware/devtools.ts
266
305
  function enableDevTools(store, name = "Store") {
306
+ registerStore(store, name);
267
307
  if (typeof window === "undefined" || !window.__REDUX_DEVTOOLS_EXTENSION__) return;
268
308
  const devTools = window.__REDUX_DEVTOOLS_EXTENSION__.connect({ name });
269
309
  devTools.init(store);
@@ -428,9 +468,15 @@ var FetchMiddleware = async (ctx) => {
428
468
  };
429
469
  var delay = (ms) => new Promise((res) => setTimeout(res, ms));
430
470
  var RetryMiddleware = async (ctx, next) => {
431
- const retryConfig = ctx.config.retry;
432
- if (!retryConfig) return next(ctx);
433
- const { retries = 0, baseDelay = 1e3, maxDelay = 3e3 } = typeof retryConfig === "number" ? { retries: retryConfig } : retryConfig;
471
+ const retryConfigRaw = ctx.config.retry;
472
+ if (!retryConfigRaw) return next(ctx);
473
+ let config;
474
+ if (typeof retryConfigRaw === "number") {
475
+ config = { retries: retryConfigRaw };
476
+ } else {
477
+ config = retryConfigRaw;
478
+ }
479
+ const { retries = 0, baseDelay = 1e3, maxDelay = 3e3 } = config;
434
480
  const attempt = async (count) => {
435
481
  try {
436
482
  const response = await next(ctx);
@@ -443,7 +489,7 @@ var RetryMiddleware = async (ctx, next) => {
443
489
  return response;
444
490
  } catch (err) {
445
491
  if (count < retries) {
446
- if (err.name === "AbortError") throw err;
492
+ if (err instanceof Error && err.name === "AbortError") throw err;
447
493
  const d = Math.min(baseDelay * 2 ** count, maxDelay);
448
494
  await delay(d);
449
495
  return attempt(count + 1);
@@ -581,319 +627,548 @@ function stableHash(value) {
581
627
  return "{" + keys.map((key) => `${key}:${stableHash(value[key])}`).join(",") + "}";
582
628
  }
583
629
 
584
- // src/addon/signals.ts
585
- var pendingSignals = /* @__PURE__ */ new Set();
586
- var isFlushScheduled = false;
587
- function flushPendingSignals() {
588
- const toFlush = Array.from(pendingSignals);
589
- pendingSignals.clear();
590
- isFlushScheduled = false;
591
- toFlush.forEach((signal) => signal.flush());
592
- }
593
- var SignalImpl = class {
594
- value;
595
- subscribers = /* @__PURE__ */ new Set();
596
- constructor(initialValue) {
597
- this.value = initialValue;
598
- }
599
- get = () => this.value;
600
- set = (newValue) => {
601
- if (this.value === newValue) return;
602
- this.value = newValue;
603
- pendingSignals.add(this);
604
- if (!isFlushScheduled) {
605
- isFlushScheduled = true;
606
- queueMicrotask(flushPendingSignals);
630
+ // src/addon/query/queryStorage.ts
631
+ var QueryStorage = class {
632
+ // Default configuration
633
+ constructor(defaultStaleTime = 0, defaultCacheTime = 5 * 60 * 1e3) {
634
+ this.defaultStaleTime = defaultStaleTime;
635
+ this.defaultCacheTime = defaultCacheTime;
636
+ }
637
+ signals = /* @__PURE__ */ new Map();
638
+ gcTimers = /* @__PURE__ */ new Map();
639
+ generateKey(queryKey) {
640
+ const key = Array.isArray(queryKey) ? stableHash(queryKey) : stableHash([queryKey.key, queryKey.params]);
641
+ return key;
642
+ }
643
+ get(key, autoCreate = true) {
644
+ let signal2 = this.signals.get(key);
645
+ if (!signal2 && autoCreate) {
646
+ const newSignal = createSignal(void 0, {
647
+ onActive: () => {
648
+ this.cancelGC(key);
649
+ },
650
+ onInactive: () => {
651
+ const entry = this.signals.get(key)?.get();
652
+ const cacheTime = entry?.cacheTime ?? this.defaultCacheTime;
653
+ this.scheduleGC(key, cacheTime);
654
+ }
655
+ });
656
+ this.signals.set(key, newSignal);
657
+ signal2 = newSignal;
607
658
  }
608
- };
609
- flush() {
610
- const currentValue = this.value;
611
- this.subscribers.forEach((fn) => fn(currentValue));
659
+ if (!signal2) return void 0;
660
+ return signal2;
612
661
  }
613
- subscribe = (fn) => {
614
- this.subscribers.add(fn);
615
- return () => {
616
- this.subscribers.delete(fn);
662
+ set(key, entry) {
663
+ const signal2 = this.signals.get(key);
664
+ if (signal2) {
665
+ signal2.set(entry);
666
+ } else {
667
+ this.signals.set(key, createSignal(entry, {
668
+ onActive: () => {
669
+ this.cancelGC(key);
670
+ },
671
+ onInactive: () => {
672
+ const entry2 = this.signals.get(key)?.get();
673
+ const cacheTime = entry2?.cacheTime ?? this.defaultCacheTime;
674
+ this.scheduleGC(key, cacheTime);
675
+ }
676
+ }));
677
+ }
678
+ }
679
+ delete(key) {
680
+ this.signals.delete(key);
681
+ this.cancelGC(key);
682
+ }
683
+ clear() {
684
+ this.signals.clear();
685
+ this.gcTimers.forEach((timer2) => clearTimeout(timer2));
686
+ this.gcTimers.clear();
687
+ }
688
+ getAll() {
689
+ const map = /* @__PURE__ */ new Map();
690
+ for (const [key, signal2] of this.signals.entries()) {
691
+ const val = signal2.get();
692
+ if (val) map.set(key, val);
693
+ }
694
+ return map;
695
+ }
696
+ getStats() {
697
+ return {
698
+ size: this.signals.size,
699
+ keys: Array.from(this.signals.keys())
617
700
  };
618
- };
701
+ }
702
+ getSnapshot() {
703
+ return this.signals;
704
+ }
705
+ // --- GC Logic ---
706
+ scheduleGC(key, delay2) {
707
+ if (this.gcTimers.has(key)) {
708
+ clearTimeout(this.gcTimers.get(key));
709
+ }
710
+ const timer2 = setTimeout(() => {
711
+ this.delete(key);
712
+ }, delay2);
713
+ this.gcTimers.set(key, timer2);
714
+ }
715
+ cancelGC(key) {
716
+ if (this.gcTimers.has(key)) {
717
+ clearTimeout(this.gcTimers.get(key));
718
+ this.gcTimers.delete(key);
719
+ }
720
+ }
619
721
  };
620
- function createSignal(initialValue) {
621
- return new SignalImpl(initialValue);
622
- }
623
722
 
624
- // src/addon/query/queryCache.ts
625
- var QueryCache = class {
626
- // Store signals instead of raw values
627
- signals = /* @__PURE__ */ new Map();
628
- gcInterval = null;
629
- defaultStaleTime = 0;
630
- // Immediately stale
631
- defaultCacheTime = 5 * 60 * 1e3;
632
- // 5 minutes
633
- constructor(config) {
634
- if (config?.enableGC !== false) {
635
- this.startGarbageCollection();
723
+ // src/addon/query/remotes.ts
724
+ var QueryRemotes = class {
725
+ deduplicationCache = /* @__PURE__ */ new Map();
726
+ async fetch(key, fn, options) {
727
+ if (this.deduplicationCache.has(key)) {
728
+ return this.deduplicationCache.get(key);
729
+ }
730
+ const promise = this.executeWithRetry(fn, options);
731
+ this.deduplicationCache.set(key, promise);
732
+ try {
733
+ const result = await promise;
734
+ this.deduplicationCache.delete(key);
735
+ return result;
736
+ } catch (error) {
737
+ this.deduplicationCache.delete(key);
738
+ throw error;
636
739
  }
637
740
  }
741
+ async executeWithRetry(fn, options) {
742
+ const maxRetries = this.resolveRetry(options?.retry);
743
+ let attempt = 0;
744
+ while (true) {
745
+ attempt++;
746
+ try {
747
+ return await fn({ signal: options?.signal });
748
+ } catch (error) {
749
+ if (attempt > maxRetries || options?.signal && options.signal.aborted) {
750
+ throw error;
751
+ }
752
+ let delay2 = 1e3 * 2 ** (attempt - 1);
753
+ if (options?.retryDelay) {
754
+ delay2 = typeof options.retryDelay === "number" ? options.retryDelay : options.retryDelay(attempt - 1);
755
+ }
756
+ delay2 = Math.min(delay2, 3e4);
757
+ await this.wait(delay2, options?.signal);
758
+ }
759
+ }
760
+ }
761
+ resolveRetry(retry) {
762
+ if (typeof retry === "number") return retry;
763
+ if (retry === false) return 0;
764
+ return 3;
765
+ }
766
+ wait(ms, signal2) {
767
+ return new Promise((resolve, reject) => {
768
+ if (signal2?.aborted) return reject(new Error("Aborted"));
769
+ const timer2 = setTimeout(() => {
770
+ cleanup();
771
+ resolve();
772
+ }, ms);
773
+ const onAbort = () => {
774
+ cleanup();
775
+ reject(new Error("Aborted"));
776
+ };
777
+ const cleanup = () => {
778
+ clearTimeout(timer2);
779
+ signal2?.removeEventListener("abort", onAbort);
780
+ };
781
+ signal2?.addEventListener("abort", onAbort);
782
+ });
783
+ }
784
+ };
785
+
786
+ // src/addon/query/mutationCache.ts
787
+ var MutationCache = class {
788
+ // Map<ID, Signal> - Stores state for every unique mutation execution
789
+ mutations = /* @__PURE__ */ new Map();
790
+ // Map<KeyHash, Set<ID>> - Index to find mutations by key
791
+ mutationKeys = /* @__PURE__ */ new Map();
792
+ /**
793
+ * Get or Create a Signal for a specific mutation instance (ID)
794
+ */
795
+ getSignal = (id) => {
796
+ let signal2 = this.mutations.get(id);
797
+ if (!signal2) {
798
+ const initialState = {
799
+ data: void 0,
800
+ error: null,
801
+ variables: void 0,
802
+ context: void 0,
803
+ status: "idle",
804
+ submittedAt: 0
805
+ };
806
+ signal2 = createSignal(initialState);
807
+ this.mutations.set(id, signal2);
808
+ }
809
+ return signal2;
810
+ };
811
+ /**
812
+ * Register a mutation ID with a Key (for tracking shared keys)
813
+ */
814
+ register = (id, key) => {
815
+ if (!key) return;
816
+ const hash = stableHash(key);
817
+ if (!this.mutationKeys.has(hash)) {
818
+ this.mutationKeys.set(hash, /* @__PURE__ */ new Set());
819
+ }
820
+ this.mutationKeys.get(hash).add(id);
821
+ };
822
+ /**
823
+ * Unregister cleanup
824
+ */
825
+ unregister = (id, key) => {
826
+ if (!key) return;
827
+ const hash = stableHash(key);
828
+ const set = this.mutationKeys.get(hash);
829
+ if (set) {
830
+ set.delete(id);
831
+ if (set.size === 0) {
832
+ this.mutationKeys.delete(hash);
833
+ }
834
+ }
835
+ this.mutations.delete(id);
836
+ };
837
+ notify = (id, state) => {
838
+ const signal2 = this.getSignal(id);
839
+ const current = signal2.get();
840
+ signal2.set({ ...current, ...state });
841
+ };
638
842
  /**
639
- * Generate cache key from query key array
843
+ * Get number of mutations currently pending
640
844
  */
641
- generateKey = (queryKey) => {
642
- if (Array.isArray(queryKey)) {
643
- return stableHash(queryKey);
845
+ isMutating = (filters) => {
846
+ let count = 0;
847
+ if (filters?.mutationKey) {
848
+ const hash = stableHash(filters.mutationKey);
849
+ const ids = this.mutationKeys.get(hash);
850
+ if (!ids) return 0;
851
+ for (const id of ids) {
852
+ if (this.mutations.get(id)?.get().status === "pending") {
853
+ count++;
854
+ }
855
+ }
856
+ } else {
857
+ for (const signal2 of this.mutations.values()) {
858
+ if (signal2.get().status === "pending") count++;
859
+ }
644
860
  }
645
- return stableHash([queryKey.key, queryKey.params]);
861
+ return count;
646
862
  };
863
+ };
864
+
865
+ // src/addon/query/pluginManager.ts
866
+ var PluginManager = class {
867
+ plugins = [];
868
+ add(plugin) {
869
+ this.plugins.push(plugin);
870
+ }
871
+ onFetchStart(queryKey) {
872
+ this.plugins.forEach((p) => p.onFetchStart?.(queryKey));
873
+ }
874
+ onFetchSuccess(queryKey, data) {
875
+ this.plugins.forEach((p) => p.onFetchSuccess?.(queryKey, data));
876
+ }
877
+ onFetchError(queryKey, error) {
878
+ this.plugins.forEach((p) => p.onFetchError?.(queryKey, error));
879
+ }
880
+ onInvalidate(queryKey) {
881
+ this.plugins.forEach((p) => p.onInvalidate?.(queryKey));
882
+ }
883
+ onQueryUpdated(queryKey, data) {
884
+ this.plugins.forEach((p) => p.onQueryUpdated?.(queryKey, data));
885
+ }
886
+ };
887
+
888
+ // src/addon/query/queryCache.ts
889
+ var QueryCache = class {
890
+ // Components
891
+ storage;
892
+ remotes;
893
+ // Mutation Cache
894
+ mutationCache = new MutationCache();
895
+ // Plugins
896
+ pluginManager = new PluginManager();
897
+ // Config defaults
898
+ defaultStaleTime;
899
+ defaultCacheTime;
900
+ constructor(config) {
901
+ this.defaultStaleTime = config?.defaultStaleTime ?? 0;
902
+ this.defaultCacheTime = config?.defaultCacheTime ?? 5 * 60 * 1e3;
903
+ this.storage = new QueryStorage(this.defaultStaleTime, this.defaultCacheTime);
904
+ this.remotes = new QueryRemotes();
905
+ }
906
+ // --- FACADE API ---
647
907
  /**
648
- * Get data (wrapper around signal.get)
908
+ * Get data from storage
649
909
  */
650
910
  get = (queryKey) => {
651
- const key = this.generateKey(queryKey);
652
- const signal = this.signals.get(key);
653
- if (!signal) return void 0;
654
- const entry = signal.get();
911
+ const key = this.storage.generateKey(queryKey);
912
+ const signal2 = this.storage.get(key, false);
913
+ if (!signal2) return void 0;
914
+ const entry = signal2.get();
655
915
  if (!entry) return void 0;
656
916
  const now = Date.now();
657
917
  const age = now - entry.timestamp;
658
918
  if (age > entry.cacheTime) {
659
- this.signals.delete(key);
919
+ this.storage.delete(key);
660
920
  return void 0;
661
921
  }
662
922
  return entry.data;
663
923
  };
664
924
  /**
665
- * Get Signal for a key (Low level API for hooks)
666
- * Automatically creates a signal if one doesn't exist
925
+ * Get Signal for reactivity (used by hooks)
667
926
  */
668
927
  getSignal = (queryKey) => {
669
- const key = this.generateKey(queryKey);
670
- let signal = this.signals.get(key);
671
- if (!signal) {
672
- signal = createSignal(void 0);
673
- this.signals.set(key, signal);
674
- }
675
- return signal;
676
- };
677
- /**
678
- * Check if data is stale
679
- */
680
- isStale = (queryKey) => {
681
- const key = this.generateKey(queryKey);
682
- const signal = this.signals.get(key);
683
- if (!signal) return true;
684
- const entry = signal.get();
685
- if (!entry) return true;
686
- const now = Date.now();
687
- const age = now - entry.timestamp;
688
- return age > entry.staleTime;
928
+ const key = this.storage.generateKey(queryKey);
929
+ return this.storage.get(key, true);
689
930
  };
690
931
  /**
691
- * Set cached data (updates signal)
932
+ * Set data manually (Optimistic updates / Prefetch)
692
933
  */
693
934
  set = (queryKey, data, options) => {
694
- const key = this.generateKey(queryKey);
935
+ const key = this.storage.generateKey(queryKey);
695
936
  const entry = {
696
937
  data,
938
+ status: "success",
939
+ error: null,
940
+ isFetching: false,
941
+ fetchDirection: "idle",
697
942
  timestamp: Date.now(),
698
- staleTime: options?.staleTime !== void 0 ? options.staleTime : this.defaultStaleTime,
699
- cacheTime: options?.cacheTime !== void 0 ? options.cacheTime : this.defaultCacheTime,
700
- key: Array.isArray(queryKey) ? queryKey : [queryKey]
943
+ staleTime: options?.staleTime ?? this.defaultStaleTime,
944
+ cacheTime: options?.cacheTime ?? this.defaultCacheTime,
945
+ key: queryKey,
946
+ tags: options?.tags
701
947
  };
702
- const existingSignal = this.signals.get(key);
703
- if (existingSignal) {
704
- existingSignal.set(entry);
705
- } else {
706
- this.signals.set(key, createSignal(entry));
707
- }
708
- const normalizedKey = Array.isArray(queryKey) ? queryKey : [queryKey.key, queryKey.params];
709
- this.plugins.forEach((p) => p.onQueryUpdated?.(normalizedKey, data));
948
+ this.storage.set(key, entry);
949
+ const normalizedKey = this.normalizeKey(queryKey);
950
+ this.pluginManager.onQueryUpdated(normalizedKey, data);
710
951
  };
711
- // --- DEDUPLICATION ---
712
- deduplicationCache = /* @__PURE__ */ new Map();
713
- // --- MIDDLEWARE / PLUGINS ---
714
- plugins = [];
715
952
  /**
716
- * Register a middleware plugin
953
+ * Restore cache entry (Hydration)
717
954
  */
718
- use = (plugin) => {
719
- this.plugins.push(plugin);
720
- return this;
955
+ restore = (queryKey, entry) => {
956
+ const key = this.storage.generateKey(queryKey);
957
+ this.storage.set(key, entry);
958
+ const normalizedKey = this.normalizeKey(queryKey);
959
+ this.pluginManager.onQueryUpdated(normalizedKey, entry.data);
721
960
  };
722
961
  /**
723
- * Fetch data with deduplication.
724
- * If a request for the same key is already in flight, returns the existing promise.
962
+ * Fetch data (Orchestration)
725
963
  */
726
- fetch = async (queryKey, fn) => {
727
- const key = this.generateKey(queryKey);
728
- const normalizedKey = Array.isArray(queryKey) ? queryKey : [queryKey.key, queryKey.params];
729
- if (this.deduplicationCache.has(key)) {
730
- return this.deduplicationCache.get(key);
731
- }
732
- this.plugins.forEach((p) => p.onFetchStart?.(normalizedKey));
733
- const promise = fn().then(
734
- (data) => {
735
- this.deduplicationCache.delete(key);
736
- this.plugins.forEach((p) => p.onFetchSuccess?.(normalizedKey, data));
737
- return data;
738
- },
739
- (error) => {
740
- this.deduplicationCache.delete(key);
741
- this.plugins.forEach((p) => p.onFetchError?.(normalizedKey, error));
742
- throw error;
964
+ fetch = async (queryKey, fn, options) => {
965
+ const key = this.storage.generateKey(queryKey);
966
+ const normalizedKey = this.normalizeKey(queryKey);
967
+ const direction = options?.fetchDirection || "initial";
968
+ const signal2 = this.storage.get(key, true);
969
+ const currentEntry = signal2.get();
970
+ this.storage.set(key, {
971
+ data: currentEntry?.data,
972
+ status: currentEntry?.status || "pending",
973
+ error: null,
974
+ isFetching: true,
975
+ // Mark fetching
976
+ fetchDirection: direction,
977
+ timestamp: currentEntry?.timestamp || Date.now(),
978
+ staleTime: currentEntry?.staleTime ?? this.defaultStaleTime,
979
+ cacheTime: currentEntry?.cacheTime ?? this.defaultCacheTime,
980
+ key: queryKey,
981
+ tags: currentEntry?.tags
982
+ });
983
+ this.pluginManager.onFetchStart(normalizedKey);
984
+ try {
985
+ const data = await this.remotes.fetch(key, fn, {
986
+ signal: options?.signal,
987
+ retry: options?.retry,
988
+ retryDelay: options?.retryDelay
989
+ });
990
+ this.pluginManager.onFetchSuccess(normalizedKey, data);
991
+ return data;
992
+ } catch (error) {
993
+ const current = signal2.get();
994
+ if (current) {
995
+ this.storage.set(key, {
996
+ ...current,
997
+ status: "error",
998
+ error,
999
+ isFetching: false,
1000
+ fetchDirection: "idle"
1001
+ });
743
1002
  }
744
- );
745
- this.deduplicationCache.set(key, promise);
746
- return promise;
747
- };
748
- /**
749
- * Invalidate all queries
750
- */
751
- invalidateAll = () => {
752
- for (const key of this.signals.keys()) {
753
- this.signals.get(key)?.set(void 0);
1003
+ this.pluginManager.onFetchError(normalizedKey, error);
1004
+ throw error;
754
1005
  }
755
1006
  };
756
1007
  /**
757
- * Remove a specific query from cache
758
- */
759
- remove = (queryKey) => {
760
- const key = this.generateKey(queryKey);
761
- this.signals.delete(key);
762
- };
763
- /**
764
- * Invalidate queries matching the key prefix
765
- * Marks them as undefined to trigger refetches without breaking subscriptions
1008
+ * Invalidate queries
766
1009
  */
767
1010
  invalidate = (queryKey) => {
768
- const prefix = this.generateKey(queryKey);
769
- const normalizedKey = Array.isArray(queryKey) ? queryKey : [queryKey.key, queryKey.params];
770
- this.plugins.forEach((p) => p.onInvalidate?.(normalizedKey));
771
- const invalidateKey = (key) => {
772
- const signal = this.signals.get(key);
773
- if (signal) {
774
- signal.set(void 0);
1011
+ const prefix = this.storage.generateKey(queryKey);
1012
+ const normalizedKey = this.normalizeKey(queryKey);
1013
+ this.pluginManager.onInvalidate(normalizedKey);
1014
+ const allKeys = this.storage.getSnapshot().keys();
1015
+ for (const key of allKeys) {
1016
+ if (key === prefix || key.startsWith(prefix.slice(0, -1))) {
1017
+ const signal2 = this.storage.get(key, false);
1018
+ if (signal2) {
1019
+ const current = signal2.get();
1020
+ if (current) {
1021
+ signal2.set({ ...current, isInvalidated: true });
1022
+ }
1023
+ }
775
1024
  }
776
- };
777
- invalidateKey(prefix);
778
- for (const key of this.signals.keys()) {
779
- if (key.startsWith(prefix.slice(0, -1))) {
780
- invalidateKey(key);
1025
+ }
1026
+ };
1027
+ invalidateTags = (tags) => {
1028
+ const tagsToInvalidate = new Set(tags);
1029
+ const snapshot = this.storage.getSnapshot();
1030
+ for (const [key, signal2] of snapshot.entries()) {
1031
+ const entry = signal2.get();
1032
+ if (entry && entry.tags) {
1033
+ if (entry.tags.some((tag) => tagsToInvalidate.has(tag))) {
1034
+ signal2.set({ ...entry, isInvalidated: true });
1035
+ }
781
1036
  }
782
1037
  }
783
1038
  };
784
- /**
785
- * Remove all cache entries
786
- */
787
- clear = () => {
788
- this.signals.clear();
1039
+ // --- Helpers ---
1040
+ use = (plugin) => {
1041
+ this.pluginManager.add(plugin);
1042
+ return this;
789
1043
  };
790
- /**
791
- * Prefetch data (same as set but explicit intent)
792
- */
1044
+ isStale = (queryKey) => {
1045
+ const key = this.storage.generateKey(queryKey);
1046
+ const signal2 = this.storage.get(key);
1047
+ const entry = signal2?.get();
1048
+ if (!entry) return true;
1049
+ if (entry.isInvalidated) return true;
1050
+ return Date.now() - entry.timestamp > entry.staleTime;
1051
+ };
1052
+ getAll = () => this.storage.getAll();
1053
+ snapshot = () => this.storage.getSnapshot();
1054
+ clear = () => this.storage.clear();
1055
+ remove = (key) => this.storage.delete(this.storage.generateKey(key));
1056
+ // Restored Methods
793
1057
  prefetch = (queryKey, data, options) => {
794
1058
  this.set(queryKey, data, options);
795
1059
  };
796
- /**
797
- * Garbage collection - remove expired entries
798
- */
799
- startGarbageCollection = () => {
800
- this.gcInterval = setInterval(() => {
801
- const now = Date.now();
802
- for (const [key, signal] of this.signals.entries()) {
803
- const entry = signal.get();
804
- if (!entry) continue;
805
- const age = now - entry.timestamp;
806
- if (age > entry.cacheTime) {
807
- this.signals.delete(key);
808
- }
809
- }
810
- }, 60 * 1e3);
811
- };
812
- /**
813
- * Stop garbage collection
814
- */
815
1060
  destroy = () => {
816
- if (this.gcInterval) {
817
- clearInterval(this.gcInterval);
818
- this.gcInterval = null;
819
- }
820
- this.clear();
1061
+ this.storage.clear();
821
1062
  };
822
- /**
823
- * Get cache stats (for debugging)
824
- */
825
- getStats = () => {
826
- return {
827
- size: this.signals.size,
828
- keys: Array.from(this.signals.keys())
829
- };
830
- };
831
- /**
832
- * Get all entries (wrapper for DevTools)
833
- */
834
- getAll = () => {
835
- const map = /* @__PURE__ */ new Map();
836
- for (const [key, signal] of this.signals.entries()) {
837
- const val = signal.get();
838
- if (val) map.set(key, val);
1063
+ getStats = () => this.storage.getStats();
1064
+ // For Hydration
1065
+ getSnapshot = () => this.storage.getSnapshot();
1066
+ invalidateAll = () => {
1067
+ for (const key of this.storage.getSnapshot().keys()) {
1068
+ const signal2 = this.storage.get(key);
1069
+ const entry = signal2?.get();
1070
+ if (entry) {
1071
+ signal2?.set({ ...entry, isInvalidated: true });
1072
+ }
839
1073
  }
840
- return map;
841
1074
  };
1075
+ normalizeKey(queryKey) {
1076
+ return Array.isArray(queryKey) ? queryKey : [queryKey.key, queryKey.params];
1077
+ }
1078
+ };
1079
+
1080
+ // src/addon/query/pagination.ts
1081
+ import { useState, useEffect as useEffect2, useCallback as useCallback2, useRef as useRef2, useSyncExternalStore as useSyncExternalStore2 } from "react";
1082
+
1083
+ // src/addon/query/context.tsx
1084
+ import { createContext, useContext } from "react";
1085
+ import { jsx } from "react/jsx-runtime";
1086
+ var QueryClientContext = createContext(void 0);
1087
+ var QueryClientProvider = ({
1088
+ client,
1089
+ children
1090
+ }) => {
1091
+ return /* @__PURE__ */ jsx(QueryClientContext.Provider, { value: client, children });
1092
+ };
1093
+ var useQueryClient = () => {
1094
+ const client = useContext(QueryClientContext);
1095
+ if (!client) {
1096
+ throw new Error("No QueryClient set, use QueryClientProvider to set one");
1097
+ }
1098
+ return client;
842
1099
  };
843
- var queryCache = new QueryCache();
844
1100
 
845
1101
  // src/addon/query/pagination.ts
846
- import { useState, useEffect as useEffect2, useCallback as useCallback2, useRef as useRef2 } from "react";
847
1102
  function usePaginatedQuery({
848
1103
  queryKey,
849
1104
  queryFn,
850
1105
  pageSize = 20,
851
1106
  staleTime,
852
1107
  cacheTime,
853
- enabled = true
1108
+ enabled = true,
1109
+ retry
854
1110
  }) {
1111
+ const client = useQueryClient();
855
1112
  const [page, setPage] = useState(0);
856
- const [data, setData] = useState();
857
- const [isLoading, setIsLoading] = useState(true);
858
- const [isError, setIsError] = useState(false);
859
- const [error, setError] = useState(null);
860
- const [hasNext, setHasNext] = useState(true);
1113
+ const pageQueryKey = [...queryKey, "page", page];
1114
+ const pageQueryKeyHash = JSON.stringify(pageQueryKey);
1115
+ const subscribe2 = useCallback2((onStoreChange) => {
1116
+ const signal2 = client.getSignal(pageQueryKey);
1117
+ return signal2.subscribe(() => onStoreChange());
1118
+ }, [client, pageQueryKeyHash]);
1119
+ const getSnapshot = useCallback2(() => {
1120
+ const signal2 = client.getSignal(pageQueryKey);
1121
+ return signal2.get();
1122
+ }, [client, pageQueryKeyHash]);
1123
+ const cacheEntry = useSyncExternalStore2(subscribe2, getSnapshot);
1124
+ const data = cacheEntry?.data;
1125
+ const status = cacheEntry?.status || "pending";
1126
+ const error = cacheEntry?.error || null;
1127
+ const isFetching = cacheEntry?.isFetching || false;
1128
+ const dataTimestamp = cacheEntry?.timestamp;
1129
+ const isError = status === "error";
1130
+ const isLoading = data === void 0 && (isFetching || status === "pending");
1131
+ let hasNext = true;
1132
+ if (data) {
1133
+ if (Array.isArray(data)) {
1134
+ hasNext = data.length === pageSize;
1135
+ } else if (typeof data === "object" && "hasMore" in data) {
1136
+ hasNext = data.hasMore;
1137
+ }
1138
+ }
1139
+ const hasPrevious = page > 0;
861
1140
  const queryFnRef = useRef2(queryFn);
1141
+ const staleTimeRef = useRef2(staleTime);
1142
+ const cacheTimeRef = useRef2(cacheTime);
862
1143
  useEffect2(() => {
863
1144
  queryFnRef.current = queryFn;
1145
+ staleTimeRef.current = staleTime;
1146
+ cacheTimeRef.current = cacheTime;
864
1147
  });
865
- const queryKeyHash = JSON.stringify(queryKey);
866
- const fetchPage = useCallback2(async (pageNum) => {
1148
+ const fetchPage = useCallback2(async (background = false) => {
867
1149
  if (!enabled) return;
868
- const pageQueryKey = [...queryKey, "page", pageNum];
869
- const cached = queryCache.get(pageQueryKey);
870
- if (cached && !queryCache.isStale(pageQueryKey)) {
871
- setData(cached);
872
- setIsLoading(false);
873
- return;
1150
+ if (!background) {
1151
+ const currentEntry = getSnapshot();
1152
+ if (currentEntry && Date.now() - currentEntry.timestamp <= (staleTimeRef.current || 0)) {
1153
+ return;
1154
+ }
874
1155
  }
875
1156
  try {
876
- setIsLoading(true);
877
- setIsError(false);
878
- setError(null);
879
- const result = await queryFnRef.current(pageNum);
880
- queryCache.set(pageQueryKey, result, { staleTime, cacheTime });
881
- setData(result);
882
- if (Array.isArray(result)) {
883
- setHasNext(result.length === pageSize);
884
- } else if (result && typeof result === "object" && "hasMore" in result) {
885
- setHasNext(result.hasMore);
886
- }
887
- setIsLoading(false);
1157
+ const result = await client.fetch(pageQueryKey, async () => {
1158
+ return await queryFnRef.current(page);
1159
+ }, { retry });
1160
+ client.set(pageQueryKey, result, {
1161
+ staleTime: staleTimeRef.current,
1162
+ cacheTime: cacheTimeRef.current
1163
+ });
888
1164
  } catch (err) {
889
- setIsError(true);
890
- setError(err);
891
- setIsLoading(false);
892
1165
  }
893
- }, [queryKeyHash, enabled, pageSize, staleTime, cacheTime]);
1166
+ }, [pageQueryKeyHash, enabled, client, getSnapshot, page]);
894
1167
  useEffect2(() => {
895
- fetchPage(page);
896
- }, [page, fetchPage]);
1168
+ if (enabled) {
1169
+ fetchPage();
1170
+ }
1171
+ }, [fetchPage, enabled]);
897
1172
  const nextPage = useCallback2(() => {
898
1173
  if (hasNext) {
899
1174
  setPage((p) => p + 1);
@@ -905,9 +1180,9 @@ function usePaginatedQuery({
905
1180
  }
906
1181
  }, [page]);
907
1182
  const refetch = useCallback2(async () => {
908
- queryCache.invalidate([...queryKey, "page", String(page)]);
909
- await fetchPage(page);
910
- }, [queryKeyHash, page, fetchPage]);
1183
+ client.invalidate(pageQueryKey);
1184
+ await fetchPage();
1185
+ }, [pageQueryKeyHash, fetchPage, client]);
911
1186
  return {
912
1187
  data,
913
1188
  isLoading,
@@ -918,313 +1193,447 @@ function usePaginatedQuery({
918
1193
  nextPage,
919
1194
  previousPage,
920
1195
  hasNext,
921
- hasPrevious: page > 0,
1196
+ hasPrevious,
922
1197
  refetch
923
1198
  };
924
1199
  }
925
1200
 
926
1201
  // src/addon/query/useQuery.ts
927
- import { useEffect as useEffect3, useCallback as useCallback3, useRef as useRef3, useSyncExternalStore as useSyncExternalStore2, useReducer } from "react";
1202
+ import { useState as useState2, useEffect as useEffect3, useCallback as useCallback3, useSyncExternalStore as useSyncExternalStore3, useRef as useRef3 } from "react";
928
1203
 
929
- // src/addon/query/context.tsx
930
- import { createContext, useContext } from "react";
931
- import { jsx } from "react/jsx-runtime";
932
- var QueryClientContext = createContext(void 0);
933
- var QueryClientProvider = ({
934
- client,
935
- children
936
- }) => {
937
- return /* @__PURE__ */ jsx(QueryClientContext.Provider, { value: client, children });
938
- };
939
- var useQueryClient = () => {
940
- const client = useContext(QueryClientContext);
941
- return client || queryCache;
942
- };
1204
+ // src/addon/query/plugins/validation.ts
1205
+ function validateWithSchema(data, schema) {
1206
+ if (!schema) {
1207
+ return data;
1208
+ }
1209
+ return schema.parse(data);
1210
+ }
943
1211
 
944
- // src/addon/query/useQuery.ts
945
- function useQuery({
946
- queryKey,
947
- queryFn,
948
- schema,
949
- staleTime = 0,
950
- cacheTime = 5 * 60 * 1e3,
951
- enabled = true,
952
- refetchOnWindowFocus = false,
953
- refetchOnReconnect = false,
954
- refetchInterval
955
- }) {
956
- const client = useQueryClient();
957
- const queryKeyHash = stableHash(queryKey);
958
- const subscribe2 = useCallback3((onStoreChange) => {
959
- const signal = client.getSignal(queryKey);
960
- return signal.subscribe(() => {
961
- onStoreChange();
1212
+ // src/addon/query/queryObserver.ts
1213
+ var QueryObserver = class {
1214
+ client;
1215
+ options;
1216
+ queryKeyHash;
1217
+ signal;
1218
+ listeners = /* @__PURE__ */ new Set();
1219
+ // Internal state management
1220
+ abortController = null;
1221
+ intervalParams = { id: null };
1222
+ unsubscribeSignal = null;
1223
+ // Derived state cache to ensure referential stability where possible
1224
+ currentResult;
1225
+ constructor(client, options) {
1226
+ this.client = client;
1227
+ this.options = options;
1228
+ this.queryKeyHash = stableHash(options.queryKey);
1229
+ this.signal = client.getSignal(options.queryKey);
1230
+ }
1231
+ setOptions(options) {
1232
+ const prevOptions = this.options;
1233
+ this.options = options;
1234
+ const newHash = stableHash(options.queryKey);
1235
+ if (newHash !== this.queryKeyHash) {
1236
+ this.queryKeyHash = newHash;
1237
+ this.updateSignal();
1238
+ this.checkAndFetch();
1239
+ } else {
1240
+ if (options.enabled && !prevOptions.enabled) {
1241
+ this.checkAndFetch();
1242
+ }
1243
+ }
1244
+ if (options.refetchInterval !== prevOptions.refetchInterval) {
1245
+ this.setupInterval();
1246
+ }
1247
+ }
1248
+ subscribe(listener) {
1249
+ this.listeners.add(listener);
1250
+ if (this.listeners.size === 1) {
1251
+ this.init();
1252
+ }
1253
+ return () => {
1254
+ this.listeners.delete(listener);
1255
+ if (this.listeners.size === 0) {
1256
+ this.destroy();
1257
+ }
1258
+ };
1259
+ }
1260
+ init() {
1261
+ this.updateSignal();
1262
+ this.setupGlobalListeners();
1263
+ this.setupInterval();
1264
+ this.checkAndFetch();
1265
+ }
1266
+ destroy() {
1267
+ this.cleanupGlobalListeners();
1268
+ this.cleanupInterval();
1269
+ if (this.unsubscribeSignal) {
1270
+ this.unsubscribeSignal();
1271
+ this.unsubscribeSignal = null;
1272
+ }
1273
+ if (this.abortController) {
1274
+ this.abortController.abort();
1275
+ }
1276
+ }
1277
+ updateSignal() {
1278
+ if (this.unsubscribeSignal) this.unsubscribeSignal();
1279
+ this.signal = this.client.getSignal(this.options.queryKey);
1280
+ this.unsubscribeSignal = this.signal.subscribe(() => {
1281
+ this.notify();
962
1282
  });
963
- }, [client, queryKeyHash]);
964
- const getSnapshot = useCallback3(() => {
965
- const signal = client.getSignal(queryKey);
966
- return signal.get();
967
- }, [client, queryKeyHash]);
968
- const cacheEntry = useSyncExternalStore2(subscribe2, getSnapshot);
969
- const data = cacheEntry?.data;
970
- const dataTimestamp = cacheEntry?.timestamp;
971
- const [statusState, dispatch] = useReducer(statusReducer, {
972
- isFetching: false,
973
- error: null
974
- });
975
- const abortControllerRef = useRef3(null);
976
- const intervalRef = useRef3(null);
977
- const isStale = dataTimestamp ? Date.now() - dataTimestamp > staleTime : true;
978
- const isLoading = data === void 0 && statusState.isFetching;
979
- const derivedIsLoading = data === void 0;
980
- const queryFnRef = useRef3(queryFn);
981
- const schemaRef = useRef3(schema);
982
- const queryKeyRef = useRef3(queryKey);
983
- useEffect3(() => {
984
- queryFnRef.current = queryFn;
985
- schemaRef.current = schema;
986
- queryKeyRef.current = queryKey;
987
- });
988
- const fetchData = useCallback3(async (background = false) => {
989
- if (!enabled) return;
990
- if (abortControllerRef.current) abortControllerRef.current.abort();
991
- abortControllerRef.current = new AbortController();
992
- if (!background) {
993
- const currentEntry = getSnapshot();
994
- if (currentEntry && Date.now() - currentEntry.timestamp <= staleTime) {
995
- return;
1283
+ }
1284
+ computeResult() {
1285
+ const entry = this.signal.get();
1286
+ const data = entry?.data;
1287
+ const status = entry?.status || "pending";
1288
+ const error = entry?.error || null;
1289
+ const isFetching = entry?.isFetching || false;
1290
+ const dataTimestamp = entry?.timestamp;
1291
+ const staleTime = this.options.staleTime ?? 0;
1292
+ const isStale = entry?.isInvalidated || (dataTimestamp ? Date.now() - dataTimestamp > staleTime : true);
1293
+ const isError = status === "error";
1294
+ const isLoading = data === void 0;
1295
+ const nextResult = {
1296
+ data,
1297
+ isLoading,
1298
+ isError,
1299
+ isFetching,
1300
+ isStale,
1301
+ error,
1302
+ status,
1303
+ refetch: this.refetch
1304
+ };
1305
+ if (this.currentResult && this.shallowEqual(this.currentResult, nextResult)) {
1306
+ return this.currentResult;
1307
+ }
1308
+ this.currentResult = nextResult;
1309
+ return nextResult;
1310
+ }
1311
+ getSnapshot = () => {
1312
+ if (!this.currentResult) {
1313
+ return this.computeResult();
1314
+ }
1315
+ return this.currentResult;
1316
+ };
1317
+ shallowEqual(objA, objB) {
1318
+ if (Object.is(objA, objB)) return true;
1319
+ if (typeof objA !== "object" || objA === null || typeof objB !== "object" || objB === null) return false;
1320
+ const recordA = objA;
1321
+ const recordB = objB;
1322
+ const keysA = Object.keys(recordA);
1323
+ const keysB = Object.keys(recordB);
1324
+ if (keysA.length !== keysB.length) return false;
1325
+ for (const key of keysA) {
1326
+ if (!Object.prototype.hasOwnProperty.call(recordB, key) || !Object.is(recordA[key], recordB[key])) {
1327
+ return false;
996
1328
  }
997
1329
  }
1330
+ return true;
1331
+ }
1332
+ notify() {
1333
+ this.computeResult();
1334
+ this.listeners.forEach((l) => l());
1335
+ this.checkAndFetch();
1336
+ }
1337
+ // --- Fetch Logic ---
1338
+ fetch = async (background = false) => {
1339
+ if (this.options.enabled === false) return;
1340
+ if (this.abortController) this.abortController.abort();
1341
+ this.abortController = new AbortController();
1342
+ const signal2 = this.abortController.signal;
998
1343
  try {
999
- dispatch({ type: "FETCH_START", background });
1000
- const fn = queryFnRef.current;
1001
- const sc = schemaRef.current;
1002
- const key = queryKeyRef.current;
1003
- let result = await client.fetch(key, async () => {
1004
- let res = await fn();
1005
- if (sc) {
1006
- res = sc.parse(res);
1007
- }
1008
- return res;
1344
+ let result = await this.client.fetch(
1345
+ this.options.queryKey,
1346
+ (ctx) => this.options.queryFn(ctx),
1347
+ { signal: signal2, retry: this.options.retry, retryDelay: this.options.retryDelay }
1348
+ );
1349
+ try {
1350
+ result = validateWithSchema(result, this.options.schema);
1351
+ } catch (validationErr) {
1352
+ const current = this.signal.get();
1353
+ this.signal.set({
1354
+ ...current,
1355
+ status: "error",
1356
+ error: validationErr,
1357
+ isFetching: false,
1358
+ data: void 0
1359
+ });
1360
+ return;
1361
+ }
1362
+ this.client.set(this.options.queryKey, result, {
1363
+ staleTime: this.options.staleTime,
1364
+ cacheTime: this.options.cacheTime,
1365
+ tags: this.options.tags
1009
1366
  });
1010
- client.set(key, result, { staleTime, cacheTime });
1011
- dispatch({ type: "FETCH_SUCCESS" });
1012
1367
  } catch (err) {
1013
- if (err.name === "AbortError") return;
1014
- dispatch({ type: "FETCH_ERROR", error: err });
1368
+ if (err instanceof Error && err.name === "AbortError") return;
1369
+ if (err instanceof Error && err.name !== "AbortError") {
1370
+ }
1015
1371
  }
1016
- }, [queryKeyHash, enabled, staleTime, cacheTime, client, getSnapshot]);
1017
- useEffect3(() => {
1018
- if (data === void 0 && !statusState.error) {
1019
- fetchData();
1372
+ };
1373
+ checkAndFetch() {
1374
+ if (this.options.enabled === false) return;
1375
+ const snapshot = this.getSnapshot();
1376
+ if (snapshot.isLoading && !snapshot.isFetching && !snapshot.isError) {
1377
+ this.fetch();
1378
+ } else if (snapshot.isStale && !snapshot.isFetching && !snapshot.isError) {
1379
+ this.fetch();
1020
1380
  }
1021
- }, [fetchData, data, statusState.error]);
1022
- useEffect3(() => {
1023
- if (!enabled || !refetchInterval) return;
1024
- intervalRef.current = setInterval(() => fetchData(true), refetchInterval);
1025
- return () => {
1026
- if (intervalRef.current) clearInterval(intervalRef.current);
1027
- };
1028
- }, [enabled, refetchInterval, fetchData]);
1029
- useEffect3(() => {
1030
- if (!enabled || !refetchOnWindowFocus) return;
1031
- const handleFocus = () => {
1032
- const entry = getSnapshot();
1033
- const isStaleNow = !entry || Date.now() - entry.timestamp > staleTime;
1034
- if (isStaleNow) fetchData(true);
1035
- };
1036
- window.addEventListener("focus", handleFocus);
1037
- return () => window.removeEventListener("focus", handleFocus);
1038
- }, [enabled, refetchOnWindowFocus, fetchData, getSnapshot, staleTime]);
1381
+ }
1382
+ refetch = async () => {
1383
+ this.client.invalidate(this.options.queryKey);
1384
+ await this.fetch();
1385
+ };
1386
+ // --- Background Refetching ---
1387
+ setupInterval() {
1388
+ this.cleanupInterval();
1389
+ if (this.options.enabled !== false && this.options.refetchInterval) {
1390
+ this.intervalParams.id = setInterval(() => {
1391
+ this.fetch(true);
1392
+ }, this.options.refetchInterval);
1393
+ }
1394
+ }
1395
+ cleanupInterval() {
1396
+ if (this.intervalParams.id) {
1397
+ clearInterval(this.intervalParams.id);
1398
+ this.intervalParams.id = null;
1399
+ }
1400
+ }
1401
+ onFocus = () => {
1402
+ if (this.options.refetchOnWindowFocus) {
1403
+ const snapshot = this.getSnapshot();
1404
+ if (snapshot.isStale && !snapshot.isFetching) {
1405
+ this.fetch(true);
1406
+ }
1407
+ }
1408
+ };
1409
+ onOnline = () => {
1410
+ if (this.options.refetchOnReconnect) {
1411
+ const snapshot = this.getSnapshot();
1412
+ if (snapshot.isStale && !snapshot.isFetching) {
1413
+ this.fetch(true);
1414
+ }
1415
+ }
1416
+ };
1417
+ setupGlobalListeners() {
1418
+ if (typeof window !== "undefined") {
1419
+ window.addEventListener("focus", this.onFocus);
1420
+ window.addEventListener("online", this.onOnline);
1421
+ }
1422
+ }
1423
+ cleanupGlobalListeners() {
1424
+ if (typeof window !== "undefined") {
1425
+ window.removeEventListener("focus", this.onFocus);
1426
+ window.removeEventListener("online", this.onOnline);
1427
+ }
1428
+ }
1429
+ };
1430
+
1431
+ // src/addon/query/useQuery.ts
1432
+ function useQuery(options) {
1433
+ const client = useQueryClient();
1434
+ const [observer] = useState2(() => new QueryObserver(client, options));
1039
1435
  useEffect3(() => {
1040
- if (!enabled || !refetchOnReconnect) return;
1041
- const handleOnline = () => {
1042
- const entry = getSnapshot();
1043
- const isStaleNow = !entry || Date.now() - entry.timestamp > staleTime;
1044
- if (isStaleNow) fetchData(true);
1045
- };
1046
- window.addEventListener("online", handleOnline);
1047
- return () => window.removeEventListener("online", handleOnline);
1048
- }, [enabled, refetchOnReconnect, fetchData, getSnapshot, staleTime]);
1049
- const refetch = useCallback3(async () => {
1050
- client.invalidate(queryKey);
1051
- await fetchData();
1052
- }, [queryKeyHash, fetchData, client]);
1436
+ observer.setOptions(options);
1437
+ }, [observer, options]);
1438
+ const subscribe2 = useCallback3((onStoreChange) => {
1439
+ return observer.subscribe(onStoreChange);
1440
+ }, [observer]);
1441
+ const select = options?.select;
1442
+ const selectRef = useRef3(select);
1443
+ selectRef.current = select;
1444
+ const memoRef = useRef3({ entry: void 0, selected: void 0 });
1445
+ const getSnapshotWithSelector = useCallback3(() => {
1446
+ const entry = observer.getSnapshot();
1447
+ if (entry === memoRef.current.entry) {
1448
+ return memoRef.current.selected;
1449
+ }
1450
+ let processedResult;
1451
+ if (!entry) {
1452
+ processedResult = void 0;
1453
+ } else {
1454
+ if (selectRef.current && entry.data !== void 0) {
1455
+ processedResult = { ...entry, data: selectRef.current(entry.data) };
1456
+ } else {
1457
+ processedResult = entry;
1458
+ }
1459
+ }
1460
+ memoRef.current = { entry, selected: processedResult };
1461
+ return processedResult;
1462
+ }, [observer]);
1463
+ const result = useSyncExternalStore3(subscribe2, getSnapshotWithSelector);
1464
+ const status = result?.status || "pending";
1465
+ const computedResult = {
1466
+ ...result,
1467
+ isPending: status === "pending",
1468
+ isSuccess: status === "success",
1469
+ isError: status === "error"
1470
+ };
1471
+ const typedResult = computedResult;
1472
+ const signal2 = client.getSignal(options.queryKey);
1053
1473
  return {
1054
- data,
1055
- isLoading: derivedIsLoading,
1056
- isError: !!statusState.error,
1057
- isFetching: statusState.isFetching,
1058
- isStale,
1059
- error: statusState.error,
1060
- refetch
1474
+ ...typedResult,
1475
+ signal: signal2
1476
+ // Keep signal as T (source)
1061
1477
  };
1062
1478
  }
1063
- function statusReducer(state, action) {
1064
- switch (action.type) {
1065
- case "FETCH_START":
1066
- return {
1067
- ...state,
1068
- isFetching: true,
1069
- error: null
1070
- };
1071
- case "FETCH_SUCCESS":
1072
- return {
1073
- ...state,
1074
- isFetching: false,
1075
- error: null
1076
- };
1077
- case "FETCH_ERROR":
1078
- return {
1079
- ...state,
1080
- isFetching: false,
1081
- error: action.error
1082
- };
1083
- default:
1084
- return state;
1085
- }
1086
- }
1087
1479
 
1088
1480
  // src/addon/query/useMutation.ts
1089
- import { useState as useState3, useCallback as useCallback4 } from "react";
1090
- function useMutation({
1091
- mutationFn,
1092
- onMutate,
1093
- onSuccess,
1094
- onError,
1095
- onSettled
1096
- }) {
1097
- const [data, setData] = useState3();
1098
- const [error, setError] = useState3(null);
1099
- const [isLoading, setIsLoading] = useState3(false);
1100
- const [isError, setIsError] = useState3(false);
1101
- const [isSuccess, setIsSuccess] = useState3(false);
1102
- const mutateAsync = useCallback4(async (variables) => {
1481
+ import { useCallback as useCallback4, useEffect as useEffect4, useSyncExternalStore as useSyncExternalStore4, useState as useState3 } from "react";
1482
+
1483
+ // src/addon/query/mutationObserver.ts
1484
+ var generateId = () => {
1485
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
1486
+ return crypto.randomUUID();
1487
+ }
1488
+ return "mutation-" + Date.now() + "-" + Math.random().toString(36).slice(2);
1489
+ };
1490
+ var MutationObserver = class {
1491
+ client;
1492
+ options;
1493
+ currentMutationId = null;
1494
+ // We maintain our own signal for this observer's view of the mutation
1495
+ // This ensures we don't miss updates if we switch IDs.
1496
+ signal;
1497
+ constructor(client, options) {
1498
+ this.client = client;
1499
+ this.options = options;
1500
+ this.signal = createSignal({
1501
+ status: "idle",
1502
+ data: void 0,
1503
+ error: null,
1504
+ variables: void 0,
1505
+ context: void 0,
1506
+ submittedAt: 0
1507
+ });
1508
+ }
1509
+ setOptions(options) {
1510
+ this.options = options;
1511
+ }
1512
+ mutate = (variables) => {
1513
+ const id = generateId();
1514
+ this.currentMutationId = id;
1515
+ this.client.mutationCache.register(id, this.options.mutationKey);
1516
+ const pendingState = {
1517
+ status: "pending",
1518
+ variables,
1519
+ submittedAt: Date.now(),
1520
+ data: void 0,
1521
+ error: null,
1522
+ context: void 0
1523
+ };
1524
+ this.signal.set({
1525
+ ...this.signal.get(),
1526
+ ...pendingState
1527
+ });
1528
+ this.client.mutationCache.notify(id, pendingState);
1529
+ const unsubscribe = this.client.mutationCache.getSignal(id).subscribe((state) => {
1530
+ const current = this.signal.get();
1531
+ if (current.status !== state.status || current.data !== state.data || current.error !== state.error) {
1532
+ this.signal.set(state);
1533
+ }
1534
+ });
1535
+ return this.executeMutation(id, variables).finally(() => {
1536
+ });
1537
+ };
1538
+ executeMutation = async (id, variables) => {
1539
+ const { mutationFn, onMutate, onSuccess, onError, onSettled, invalidatesTags, optimistic, mutationKey } = this.options;
1103
1540
  let context;
1541
+ let optimisticSnapshot;
1542
+ const notify = (update) => {
1543
+ this.client.mutationCache.notify(id, update);
1544
+ };
1104
1545
  try {
1105
- setIsLoading(true);
1106
- setIsError(false);
1107
- setError(null);
1108
- setIsSuccess(false);
1546
+ if (optimistic) {
1547
+ const { queryKey, update } = optimistic;
1548
+ const signal2 = this.client.getSignal(queryKey);
1549
+ const currentData = signal2.get()?.data;
1550
+ optimisticSnapshot = currentData;
1551
+ const optimisticData = update(variables, currentData);
1552
+ this.client.set(queryKey, optimisticData);
1553
+ }
1109
1554
  if (onMutate) {
1110
1555
  context = await onMutate(variables);
1556
+ notify({ context });
1111
1557
  }
1112
1558
  const result = await mutationFn(variables);
1113
- setData(result);
1114
- setIsSuccess(true);
1115
- setIsLoading(false);
1116
- if (onSuccess) {
1117
- onSuccess(result, variables, context);
1559
+ notify({ status: "success", data: result });
1560
+ if (onSuccess) await onSuccess(result, variables, context);
1561
+ if (this.client.invalidateTags && invalidatesTags) {
1562
+ this.client.invalidateTags(invalidatesTags);
1118
1563
  }
1119
- if (onSettled) {
1120
- onSettled(result, null, variables, context);
1564
+ if (optimistic) {
1565
+ this.client.invalidate(optimistic.queryKey);
1121
1566
  }
1567
+ if (onSettled) onSettled(result, null, variables, context);
1122
1568
  return result;
1123
- } catch (err) {
1124
- setIsError(true);
1125
- setError(err);
1126
- setIsLoading(false);
1127
- if (onError) {
1128
- onError(err, variables, context);
1129
- }
1130
- if (onSettled) {
1131
- onSettled(void 0, err, variables, context);
1569
+ } catch (error) {
1570
+ if (optimistic && optimisticSnapshot !== void 0) {
1571
+ const queryCache = this.client;
1572
+ queryCache.set(optimistic.queryKey, optimisticSnapshot);
1132
1573
  }
1133
- throw err;
1134
- }
1135
- }, [mutationFn, onMutate, onSuccess, onError, onSettled]);
1136
- const mutate = useCallback4(async (variables) => {
1137
- try {
1138
- await mutateAsync(variables);
1139
- } catch {
1574
+ notify({ status: "error", error });
1575
+ if (onError) onError(error, variables, context);
1576
+ if (onSettled) onSettled(void 0, error, variables, context);
1577
+ throw error;
1140
1578
  }
1141
- }, [mutateAsync]);
1142
- const reset = useCallback4(() => {
1143
- setData(void 0);
1144
- setError(null);
1145
- setIsLoading(false);
1146
- setIsError(false);
1147
- setIsSuccess(false);
1148
- }, []);
1579
+ };
1580
+ reset = () => {
1581
+ this.signal.set({
1582
+ status: "idle",
1583
+ data: void 0,
1584
+ error: null,
1585
+ variables: void 0,
1586
+ submittedAt: 0
1587
+ });
1588
+ this.currentMutationId = null;
1589
+ };
1590
+ };
1591
+
1592
+ // src/addon/query/useMutation.ts
1593
+ function useMutation(options) {
1594
+ const client = useQueryClient();
1595
+ const [observer] = useState3(() => new MutationObserver(client, options));
1596
+ useEffect4(() => {
1597
+ observer.setOptions(options);
1598
+ }, [observer, options]);
1599
+ const state = useSyncExternalStore4(
1600
+ useCallback4((cb) => observer.signal.subscribe(cb), [observer]),
1601
+ () => observer.signal.get()
1602
+ );
1603
+ const mutateAsync = useCallback4((variables) => {
1604
+ return observer.mutate(variables);
1605
+ }, [observer]);
1606
+ const mutate = useCallback4((variables) => {
1607
+ observer.mutate(variables).catch(() => {
1608
+ });
1609
+ }, [observer]);
1149
1610
  return {
1150
1611
  mutate,
1151
1612
  mutateAsync,
1152
- data,
1153
- error,
1154
- isLoading,
1155
- isError,
1156
- isSuccess,
1157
- reset
1613
+ data: state.data,
1614
+ error: state.error,
1615
+ status: state.status,
1616
+ isLoading: state.status === "pending",
1617
+ isError: state.status === "error",
1618
+ isSuccess: state.status === "success",
1619
+ isIdle: state.status === "idle",
1620
+ reset: observer.reset
1158
1621
  };
1159
1622
  }
1160
- var optimisticHelpers = {
1161
- /**
1162
- * Cancel ongoing queries for a key
1163
- */
1164
- async cancelQueries(queryKey) {
1165
- },
1166
- /**
1167
- * Get current query data
1168
- */
1169
- getQueryData(queryKey) {
1170
- return queryCache.get(queryKey);
1171
- },
1172
- /**
1173
- * Set query data (for optimistic updates)
1174
- */
1175
- setQueryData(queryKey, updater) {
1176
- const current = queryCache.get(queryKey);
1177
- const newData = typeof updater === "function" ? updater(current) : updater;
1178
- queryCache.set(queryKey, newData);
1179
- return current;
1180
- },
1181
- /**
1182
- * Invalidate queries (trigger refetch)
1183
- */
1184
- invalidateQueries(queryKey) {
1185
- queryCache.invalidate(queryKey);
1186
- }
1623
+
1624
+ // src/addon/query/infiniteQuery.ts
1625
+ import { useEffect as useEffect5, useCallback as useCallback5, useRef as useRef4, useSyncExternalStore as useSyncExternalStore5, useMemo } from "react";
1626
+
1627
+ // src/addon/query/plugins/logger.ts
1628
+ var consoleLogger = {
1629
+ log: console.log,
1630
+ warn: console.warn,
1631
+ error: console.error
1187
1632
  };
1633
+ var currentLogger = consoleLogger;
1634
+ var getLogger = () => currentLogger;
1188
1635
 
1189
1636
  // src/addon/query/infiniteQuery.ts
1190
- import { useEffect as useEffect4, useCallback as useCallback5, useRef as useRef4, useReducer as useReducer2, useSyncExternalStore as useSyncExternalStore3 } from "react";
1191
- function statusReducer2(state, action) {
1192
- switch (action.type) {
1193
- case "FETCH_START":
1194
- return {
1195
- ...state,
1196
- isFetching: true,
1197
- isFetchingNextPage: action.direction === "next",
1198
- isFetchingPreviousPage: action.direction === "previous",
1199
- error: null
1200
- };
1201
- case "FETCH_SUCCESS":
1202
- return {
1203
- ...state,
1204
- isFetching: false,
1205
- isFetchingNextPage: false,
1206
- isFetchingPreviousPage: false,
1207
- hasNextPage: action.hasNextPage !== void 0 ? action.hasNextPage : state.hasNextPage,
1208
- hasPreviousPage: action.hasPreviousPage !== void 0 ? action.hasPreviousPage : state.hasPreviousPage
1209
- };
1210
- case "FETCH_ERROR":
1211
- return {
1212
- ...state,
1213
- isFetching: false,
1214
- isFetchingNextPage: false,
1215
- isFetchingPreviousPage: false,
1216
- error: action.error
1217
- };
1218
- case "SET_PAGINATION":
1219
- return {
1220
- ...state,
1221
- hasNextPage: action.hasNextPage !== void 0 ? action.hasNextPage : state.hasNextPage,
1222
- hasPreviousPage: action.hasPreviousPage !== void 0 ? action.hasPreviousPage : state.hasPreviousPage
1223
- };
1224
- default:
1225
- return state;
1226
- }
1227
- }
1228
1637
  function useInfiniteQuery({
1229
1638
  queryKey,
1230
1639
  queryFn,
@@ -1233,619 +1642,319 @@ function useInfiniteQuery({
1233
1642
  initialPageParam,
1234
1643
  staleTime = 0,
1235
1644
  cacheTime = 5 * 60 * 1e3,
1236
- enabled = true
1645
+ enabled = true,
1646
+ retry
1237
1647
  }) {
1238
1648
  const client = useQueryClient();
1239
1649
  const queryKeyHash = stableHash(queryKey);
1240
- const infiniteQueryKey = [...queryKey, "__infinite__"];
1650
+ const infiniteQueryKey = useMemo(() => [...queryKey, "__infinite__"], [queryKeyHash]);
1241
1651
  const subscribe2 = useCallback5((onStoreChange) => {
1242
- const signal = client.getSignal(infiniteQueryKey);
1243
- return signal.subscribe(() => onStoreChange());
1244
- }, [client, queryKeyHash]);
1652
+ const signal2 = client.getSignal(infiniteQueryKey);
1653
+ return signal2.subscribe(() => onStoreChange());
1654
+ }, [client, infiniteQueryKey]);
1245
1655
  const getSnapshot = useCallback5(() => {
1246
- const signal = client.getSignal(infiniteQueryKey);
1247
- return signal.get();
1656
+ const signal2 = client.getSignal(infiniteQueryKey);
1657
+ return signal2.get();
1248
1658
  }, [client, queryKeyHash]);
1249
- const cacheEntry = useSyncExternalStore3(subscribe2, getSnapshot);
1659
+ const cacheEntry = useSyncExternalStore5(subscribe2, getSnapshot);
1250
1660
  const data = cacheEntry?.data;
1251
- const [statusState, dispatch] = useReducer2(statusReducer2, {
1252
- isFetching: false,
1253
- isFetchingNextPage: false,
1254
- isFetchingPreviousPage: false,
1255
- error: null,
1256
- hasNextPage: false,
1257
- // Will be set after first fetch
1258
- hasPreviousPage: false
1259
- });
1661
+ const isFetching = cacheEntry?.isFetching || false;
1662
+ const fetchDirection = cacheEntry?.fetchDirection || "idle";
1663
+ const status = cacheEntry?.status || "pending";
1664
+ const error = cacheEntry?.error || null;
1665
+ const timestamp = cacheEntry?.timestamp || 0;
1666
+ const isInvalidated = cacheEntry?.isInvalidated || false;
1667
+ let hasNextPage = false;
1668
+ let hasPreviousPage = false;
1669
+ if (data && data.pages.length > 0) {
1670
+ if (getNextPageParam) {
1671
+ const lastPage = data.pages[data.pages.length - 1];
1672
+ hasNextPage = getNextPageParam(lastPage, data.pages) !== void 0;
1673
+ }
1674
+ if (getPreviousPageParam) {
1675
+ const firstPage = data.pages[0];
1676
+ hasPreviousPage = getPreviousPageParam(firstPage, data.pages) !== void 0;
1677
+ }
1678
+ }
1679
+ const isFetchingNextPage = isFetching && fetchDirection === "next";
1680
+ const isFetchingPreviousPage = isFetching && fetchDirection === "previous";
1681
+ const isLoading = data === void 0 && isFetching;
1682
+ const isError = status === "error";
1260
1683
  const queryFnRef = useRef4(queryFn);
1261
1684
  const getNextPageParamRef = useRef4(getNextPageParam);
1262
1685
  const getPreviousPageParamRef = useRef4(getPreviousPageParam);
1263
- const initialFetchDoneRef = useRef4(false);
1264
- const clientRef = useRef4(client);
1265
- const infiniteQueryKeyRef = useRef4(infiniteQueryKey);
1266
1686
  const initialPageParamRef = useRef4(initialPageParam);
1267
1687
  const staleTimeRef = useRef4(staleTime);
1268
1688
  const cacheTimeRef = useRef4(cacheTime);
1269
- useEffect4(() => {
1689
+ useEffect5(() => {
1270
1690
  queryFnRef.current = queryFn;
1271
1691
  getNextPageParamRef.current = getNextPageParam;
1272
1692
  getPreviousPageParamRef.current = getPreviousPageParam;
1273
- clientRef.current = client;
1274
- infiniteQueryKeyRef.current = infiniteQueryKey;
1275
1693
  initialPageParamRef.current = initialPageParam;
1276
1694
  staleTimeRef.current = staleTime;
1277
1695
  cacheTimeRef.current = cacheTime;
1278
1696
  });
1279
- const prevDataRef = useRef4(data);
1280
- useEffect4(() => {
1281
- if (prevDataRef.current && !data) {
1282
- initialFetchDoneRef.current = false;
1283
- }
1284
- prevDataRef.current = data;
1285
- }, [data]);
1286
- useEffect4(() => {
1697
+ useEffect5(() => {
1287
1698
  if (!enabled) return;
1288
- if (data) return;
1289
- const doFetch = async () => {
1290
- const initialParam = initialPageParamRef.current;
1291
- const firstParam = initialParam !== void 0 ? initialParam : 0;
1292
- if (!initialFetchDoneRef.current) {
1293
- initialFetchDoneRef.current = true;
1294
- }
1295
- dispatch({ type: "FETCH_START", direction: "initial" });
1296
- const pageKey = [...infiniteQueryKey, "initial", String(firstParam)];
1297
- let firstPage;
1699
+ if (data && !isInvalidated && Date.now() - timestamp <= staleTime) return;
1700
+ if (isFetching) return;
1701
+ const doInitialFetch = async () => {
1298
1702
  try {
1299
- firstPage = await clientRef.current.fetch(
1300
- pageKey,
1301
- () => queryFnRef.current({ pageParam: firstParam })
1302
- );
1303
- } catch (error) {
1304
- dispatch({ type: "FETCH_ERROR", error });
1305
- return;
1306
- }
1307
- if (firstPage) {
1308
- const initialData = {
1309
- pages: [firstPage],
1310
- pageParams: [firstParam]
1311
- };
1312
- let hasNext = false;
1313
- if (getNextPageParamRef.current) {
1314
- const nextParam = getNextPageParamRef.current(firstPage, [firstPage]);
1315
- hasNext = nextParam !== void 0;
1703
+ if (data && data.pageParams.length > 0) {
1704
+ const updatedPages = [];
1705
+ const updatedParams = [];
1706
+ let param = data.pageParams[0];
1707
+ updatedParams.push(param);
1708
+ const limit = data.pages.length;
1709
+ const fetchedData = await client.fetch(infiniteQueryKey, async () => {
1710
+ for (let i = 0; i < limit; i++) {
1711
+ const page = await queryFnRef.current({ pageParam: param });
1712
+ updatedPages.push(page);
1713
+ if (i < limit - 1) {
1714
+ const next = getNextPageParamRef.current?.(page, updatedPages);
1715
+ if (next === void 0) break;
1716
+ param = next;
1717
+ updatedParams.push(param);
1718
+ }
1719
+ }
1720
+ return {
1721
+ pages: updatedPages,
1722
+ pageParams: updatedParams
1723
+ };
1724
+ }, { fetchDirection: "initial", retry });
1725
+ client.set(infiniteQueryKey, fetchedData, {
1726
+ staleTime: staleTimeRef.current,
1727
+ cacheTime: cacheTimeRef.current
1728
+ });
1729
+ return;
1316
1730
  }
1317
- clientRef.current.set(infiniteQueryKeyRef.current, initialData, {
1731
+ const initialParam = initialPageParamRef.current;
1732
+ const firstParam = initialParam !== void 0 ? initialParam : 0;
1733
+ const initialData = await client.fetch(infiniteQueryKey, async () => {
1734
+ const firstPage = await queryFnRef.current({ pageParam: firstParam });
1735
+ return {
1736
+ pages: [firstPage],
1737
+ pageParams: [firstParam]
1738
+ };
1739
+ }, { fetchDirection: "initial", retry });
1740
+ client.set(infiniteQueryKey, initialData, {
1318
1741
  staleTime: staleTimeRef.current,
1319
1742
  cacheTime: cacheTimeRef.current
1320
1743
  });
1321
- dispatch({ type: "FETCH_SUCCESS", hasNextPage: hasNext });
1744
+ } catch (err) {
1745
+ getLogger().error("Initial fetch failed", err);
1322
1746
  }
1323
1747
  };
1324
- doFetch();
1325
- }, [enabled, data]);
1326
- const fetchPageHelper = useCallback5(async (pageParam) => {
1327
- try {
1328
- const pageKey = [...infiniteQueryKey, String(pageParam)];
1329
- return await clientRef.current.fetch(
1330
- pageKey,
1331
- () => queryFnRef.current({ pageParam })
1332
- );
1333
- } catch (error) {
1334
- dispatch({ type: "FETCH_ERROR", error });
1335
- return void 0;
1336
- }
1337
- }, [client, infiniteQueryKey]);
1748
+ doInitialFetch();
1749
+ }, [enabled, data === void 0, isInvalidated, staleTime, timestamp]);
1338
1750
  const fetchNextPage = useCallback5(async () => {
1339
- if (!statusState.hasNextPage || statusState.isFetchingNextPage || !data) return;
1751
+ if (!hasNextPage || isFetching || !data) return;
1340
1752
  const lastPage = data.pages[data.pages.length - 1];
1341
- if (!lastPage || !getNextPageParamRef.current) return;
1342
- const nextPageParam = getNextPageParamRef.current(lastPage, data.pages);
1753
+ const nextPageParam = getNextPageParamRef.current?.(lastPage, data.pages);
1343
1754
  if (nextPageParam === void 0) return;
1344
- dispatch({ type: "FETCH_START", direction: "next" });
1345
- const newPage = await fetchPageHelper(nextPageParam);
1346
- if (newPage) {
1347
- const updatedData = {
1348
- pages: [...data.pages, newPage],
1349
- pageParams: [...data.pageParams, nextPageParam]
1350
- };
1351
- let hasNext = false;
1352
- if (getNextPageParamRef.current) {
1353
- const nextParam = getNextPageParamRef.current(newPage, updatedData.pages);
1354
- hasNext = nextParam !== void 0;
1355
- }
1356
- clientRef.current.set(infiniteQueryKeyRef.current, updatedData, {
1755
+ try {
1756
+ const updatedData = await client.fetch(infiniteQueryKey, async ({ signal: signal2 }) => {
1757
+ const newPage = await queryFnRef.current({ pageParam: nextPageParam });
1758
+ const currentEntry = client.getSignal(infiniteQueryKey).get();
1759
+ const currentData = currentEntry?.data;
1760
+ if (!currentData) throw new Error("Infinite query data missing during fetchNextPage");
1761
+ const nextCursor = getNextPageParamRef.current?.(newPage, [...currentData.pages, newPage]);
1762
+ const updatedParams = [...currentData.pageParams, nextPageParam];
1763
+ if (nextCursor !== void 0) {
1764
+ updatedParams.push(nextCursor);
1765
+ }
1766
+ return {
1767
+ pages: [...currentData.pages, newPage],
1768
+ pageParams: updatedParams
1769
+ };
1770
+ }, {
1771
+ fetchDirection: "next",
1772
+ retry
1773
+ });
1774
+ client.set(infiniteQueryKey, updatedData, {
1357
1775
  staleTime: staleTimeRef.current,
1358
1776
  cacheTime: cacheTimeRef.current
1359
1777
  });
1360
- dispatch({ type: "FETCH_SUCCESS", hasNextPage: hasNext });
1778
+ } catch (err) {
1779
+ getLogger().error("Fetch next page failed", err);
1361
1780
  }
1362
- }, [statusState.hasNextPage, statusState.isFetchingNextPage, data, fetchPageHelper]);
1781
+ }, [hasNextPage, isFetching, data, client, JSON.stringify(infiniteQueryKey)]);
1363
1782
  const fetchPreviousPage = useCallback5(async () => {
1364
- if (!statusState.hasPreviousPage || statusState.isFetchingPreviousPage || !data) return;
1783
+ if (!hasPreviousPage || isFetching || !data) return;
1365
1784
  const firstPage = data.pages[0];
1366
- if (!firstPage || !getPreviousPageParamRef.current) return;
1367
- const previousPageParam = getPreviousPageParamRef.current(firstPage, data.pages);
1785
+ const previousPageParam = getPreviousPageParamRef.current?.(firstPage, data.pages);
1368
1786
  if (previousPageParam === void 0) return;
1369
- dispatch({ type: "FETCH_START", direction: "previous" });
1370
- const newPage = await fetchPageHelper(previousPageParam);
1371
- if (newPage) {
1372
- const updatedData = {
1373
- pages: [newPage, ...data.pages],
1374
- pageParams: [previousPageParam, ...data.pageParams]
1375
- };
1376
- let hasPrev = false;
1377
- if (getPreviousPageParamRef.current) {
1378
- const prevParam = getPreviousPageParamRef.current(newPage, updatedData.pages);
1379
- hasPrev = prevParam !== void 0;
1380
- }
1381
- clientRef.current.set(infiniteQueryKeyRef.current, updatedData, {
1787
+ try {
1788
+ const updatedData = await client.fetch(infiniteQueryKey, async () => {
1789
+ const newPage = await queryFnRef.current({ pageParam: previousPageParam });
1790
+ const currentEntry = client.getSignal(infiniteQueryKey).get();
1791
+ const currentData = currentEntry?.data;
1792
+ if (!currentData) throw new Error("Infinite query data missing during fetchPreviousPage");
1793
+ return {
1794
+ pages: [newPage, ...currentData.pages],
1795
+ pageParams: [previousPageParam, ...currentData.pageParams]
1796
+ };
1797
+ }, { fetchDirection: "previous", retry });
1798
+ client.set(infiniteQueryKey, updatedData, {
1382
1799
  staleTime: staleTimeRef.current,
1383
1800
  cacheTime: cacheTimeRef.current
1384
1801
  });
1385
- dispatch({ type: "FETCH_SUCCESS", hasPreviousPage: hasPrev });
1802
+ } catch (err) {
1803
+ getLogger().error("Fetch previous page failed", err);
1386
1804
  }
1387
- }, [statusState.hasPreviousPage, statusState.isFetchingPreviousPage, data, fetchPageHelper]);
1805
+ }, [hasPreviousPage, isFetching, data, client, JSON.stringify(infiniteQueryKey)]);
1388
1806
  const refetch = useCallback5(async () => {
1389
- initialFetchDoneRef.current = false;
1390
- clientRef.current.invalidate(infiniteQueryKeyRef.current);
1391
- }, []);
1807
+ client.invalidate(infiniteQueryKey);
1808
+ }, [client, JSON.stringify(infiniteQueryKey)]);
1392
1809
  return {
1393
1810
  data,
1394
1811
  fetchNextPage,
1395
1812
  fetchPreviousPage,
1396
- hasNextPage: statusState.hasNextPage,
1397
- hasPreviousPage: statusState.hasPreviousPage,
1398
- isFetching: statusState.isFetching,
1399
- isFetchingNextPage: statusState.isFetchingNextPage,
1400
- isFetchingPreviousPage: statusState.isFetchingPreviousPage,
1401
- isLoading: data === void 0 && statusState.isFetching,
1402
- isError: !!statusState.error,
1403
- error: statusState.error,
1813
+ hasNextPage,
1814
+ hasPreviousPage,
1815
+ isFetching,
1816
+ isFetchingNextPage,
1817
+ isFetchingPreviousPage,
1818
+ isLoading,
1819
+ isError,
1820
+ error,
1404
1821
  refetch
1405
1822
  };
1406
1823
  }
1407
1824
 
1408
- // src/addon/query/devtools.tsx
1409
- import { useState as useState5, useMemo, useRef as useRef5, useEffect as useEffect6 } from "react";
1825
+ // src/addon/query/HydrationBoundary.tsx
1826
+ import { useRef as useRef5 } from "react";
1827
+
1828
+ // src/addon/query/hydration.ts
1829
+ function dehydrate(client) {
1830
+ const queries = [];
1831
+ const snapshot = client.getSnapshot();
1832
+ snapshot.forEach((signal2, hash) => {
1833
+ const state = signal2.get();
1834
+ if (state) {
1835
+ queries.push({
1836
+ queryKey: state.key,
1837
+ queryHash: hash,
1838
+ state
1839
+ });
1840
+ }
1841
+ });
1842
+ return { queries };
1843
+ }
1844
+ function hydrate(client, state) {
1845
+ if (!state || !state.queries) return;
1846
+ state.queries.forEach(({ queryKey, state: queryState }) => {
1847
+ const key = queryKey;
1848
+ client.restore(queryKey, queryState);
1849
+ });
1850
+ }
1410
1851
 
1411
- // src/addon/query/useQueryCache.ts
1412
- import { useState as useState4, useEffect as useEffect5 } from "react";
1413
- function useQueryCache() {
1852
+ // src/addon/query/HydrationBoundary.tsx
1853
+ import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
1854
+ function HydrationBoundary({ state, children }) {
1414
1855
  const client = useQueryClient();
1415
- const [cache, setCache] = useState4(client.getAll());
1416
- useEffect5(() => {
1417
- const interval = setInterval(() => {
1418
- setCache(new Map(client.getAll()));
1419
- }, 500);
1420
- return () => clearInterval(interval);
1421
- }, [client]);
1422
- return cache;
1856
+ const hydratedRef = useRef5(false);
1857
+ if (state && !hydratedRef.current) {
1858
+ hydrate(client, state);
1859
+ hydratedRef.current = true;
1860
+ }
1861
+ return /* @__PURE__ */ jsx2(Fragment, { children });
1423
1862
  }
1424
1863
 
1425
- // src/addon/query/devtools.tsx
1426
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
1427
- function QuantumDevTools() {
1428
- const [isOpen, setIsOpen] = useState5(false);
1429
- const [isMinimized, setIsMinimized] = useState5(false);
1430
- const [height, setHeight] = useState5(450);
1431
- const [filter, setFilter] = useState5("");
1432
- const cache = useQueryCache();
1864
+ // src/addon/query/useSuspenseQuery.ts
1865
+ function useSuspenseQuery(options) {
1433
1866
  const client = useQueryClient();
1434
- const isResizingRef = useRef5(false);
1435
- const containerRef = useRef5(null);
1436
- useEffect6(() => {
1437
- const handleMouseMove = (e) => {
1438
- if (!isResizingRef.current) return;
1439
- const newHeight = window.innerHeight - e.clientY;
1440
- if (newHeight > 200 && newHeight < window.innerHeight - 50) {
1441
- setHeight(newHeight);
1442
- }
1443
- };
1444
- const handleMouseUp = () => {
1445
- isResizingRef.current = false;
1446
- document.body.style.cursor = "default";
1447
- };
1448
- window.addEventListener("mousemove", handleMouseMove);
1449
- window.addEventListener("mouseup", handleMouseUp);
1450
- return () => {
1451
- window.removeEventListener("mousemove", handleMouseMove);
1452
- window.removeEventListener("mouseup", handleMouseUp);
1453
- };
1454
- }, []);
1455
- const entries = useMemo(() => Array.from(cache.entries()), [cache]);
1456
- const filteredEntries = useMemo(() => {
1457
- if (!filter) return entries;
1458
- const search = filter.toLowerCase();
1459
- return entries.filter(
1460
- ([_, entry]) => entry.key.some((k) => String(k).toLowerCase().includes(search))
1461
- );
1462
- }, [entries, filter]);
1463
- if (!isOpen) {
1464
- return /* @__PURE__ */ jsx2(
1465
- "button",
1466
- {
1467
- onClick: () => setIsOpen(true),
1468
- style: {
1469
- position: "fixed",
1470
- bottom: "20px",
1471
- right: "20px",
1472
- background: "#111",
1473
- color: "#b0fb5d",
1474
- // Quantum Green
1475
- border: "1px solid #333",
1476
- borderRadius: "50%",
1477
- width: "48px",
1478
- height: "48px",
1479
- cursor: "pointer",
1480
- zIndex: 9999,
1481
- boxShadow: "0 8px 32px rgba(0, 0, 0, 0.4)",
1482
- fontSize: "20px",
1483
- display: "flex",
1484
- alignItems: "center",
1485
- justifyContent: "center",
1486
- transition: "transform 0.2s",
1487
- fontFamily: "monospace"
1488
- },
1489
- onMouseEnter: (e) => e.currentTarget.style.transform = "scale(1.1)",
1490
- onMouseLeave: (e) => e.currentTarget.style.transform = "scale(1)",
1491
- title: "Open Quantum DevTools",
1492
- children: "\u26A1\uFE0F"
1493
- }
1494
- );
1495
- }
1496
- if (isMinimized) {
1497
- return /* @__PURE__ */ jsxs("div", { style: {
1498
- position: "fixed",
1499
- bottom: "20px",
1500
- right: "20px",
1501
- background: "#111",
1502
- border: "1px solid #333",
1503
- borderRadius: "8px",
1504
- padding: "8px 12px",
1505
- zIndex: 9999,
1506
- display: "flex",
1507
- alignItems: "center",
1508
- gap: "10px",
1509
- boxShadow: "0 8px 32px rgba(0, 0, 0, 0.4)",
1510
- fontFamily: "monospace",
1511
- color: "#e0e0e0",
1512
- cursor: "pointer"
1513
- }, onClick: () => setIsMinimized(false), children: [
1514
- /* @__PURE__ */ jsx2("span", { style: { color: "#b0fb5d" }, children: "\u26A1\uFE0F" }),
1515
- /* @__PURE__ */ jsx2("span", { style: { fontSize: "12px" }, children: "DevTools" }),
1516
- /* @__PURE__ */ jsxs("span", { style: {
1517
- background: "#333",
1518
- padding: "2px 6px",
1519
- borderRadius: "4px",
1520
- fontSize: "10px"
1521
- }, children: [
1522
- entries.length,
1523
- " queries"
1524
- ] })
1525
- ] });
1526
- }
1527
- return /* @__PURE__ */ jsxs(
1528
- "div",
1529
- {
1530
- ref: containerRef,
1531
- style: {
1532
- position: "fixed",
1533
- bottom: 0,
1534
- right: 0,
1535
- width: "100%",
1536
- height: `${height}px`,
1537
- background: "#0a0a0a",
1538
- color: "#e0e0e0",
1539
- borderTopLeftRadius: "12px",
1540
- borderTopRightRadius: "12px",
1541
- boxShadow: "0 -10px 40px rgba(0,0,0,0.5)",
1542
- zIndex: 9999,
1543
- display: "flex",
1544
- flexDirection: "column",
1545
- fontFamily: "'JetBrains Mono', 'Fira Code', monospace",
1546
- fontSize: "13px",
1547
- borderTop: "1px solid #333"
1548
- },
1549
- children: [
1550
- /* @__PURE__ */ jsx2(
1551
- "div",
1552
- {
1553
- onMouseDown: () => {
1554
- isResizingRef.current = true;
1555
- document.body.style.cursor = "ns-resize";
1556
- },
1557
- style: {
1558
- height: "6px",
1559
- width: "100%",
1560
- cursor: "ns-resize",
1561
- position: "absolute",
1562
- top: "-3px",
1563
- left: 0,
1564
- zIndex: 10
1565
- // debugging color: background: 'red', opacity: 0.2
1566
- }
1567
- }
1568
- ),
1569
- /* @__PURE__ */ jsxs("div", { style: {
1570
- padding: "12px 16px",
1571
- borderBottom: "1px solid #222",
1572
- display: "flex",
1573
- justifyContent: "space-between",
1574
- alignItems: "center",
1575
- background: "#111",
1576
- borderTopLeftRadius: "12px",
1577
- borderTopRightRadius: "12px",
1578
- userSelect: "none"
1579
- }, children: [
1580
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
1581
- /* @__PURE__ */ jsx2("span", { style: { color: "#b0fb5d", fontSize: "16px" }, children: "\u26A1\uFE0F" }),
1582
- /* @__PURE__ */ jsx2("span", { style: { fontWeight: 600, letterSpacing: "-0.5px" }, children: "Quantum DevTools" }),
1583
- /* @__PURE__ */ jsx2("span", { style: {
1584
- background: "#222",
1585
- padding: "2px 6px",
1586
- borderRadius: "4px",
1587
- fontSize: "10px",
1588
- color: "#666"
1589
- }, children: "v1.2.3" })
1590
- ] }),
1591
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "8px" }, children: [
1592
- /* @__PURE__ */ jsx2(
1593
- "button",
1594
- {
1595
- onClick: () => setIsMinimized(true),
1596
- title: "Minimize",
1597
- style: {
1598
- background: "transparent",
1599
- border: "none",
1600
- color: "#666",
1601
- cursor: "pointer",
1602
- fontSize: "16px",
1603
- padding: "4px",
1604
- lineHeight: 1
1605
- },
1606
- children: "_"
1607
- }
1608
- ),
1609
- /* @__PURE__ */ jsx2(
1610
- "button",
1611
- {
1612
- onClick: () => setIsOpen(false),
1613
- title: "Close",
1614
- style: {
1615
- background: "transparent",
1616
- border: "none",
1617
- color: "#666",
1618
- cursor: "pointer",
1619
- fontSize: "18px",
1620
- padding: "4px",
1621
- lineHeight: 1
1622
- },
1623
- children: "\xD7"
1624
- }
1625
- )
1626
- ] })
1627
- ] }),
1628
- /* @__PURE__ */ jsxs("div", { style: {
1629
- padding: "8px 16px",
1630
- borderBottom: "1px solid #222",
1631
- background: "#0f0f0f",
1632
- display: "flex",
1633
- gap: "12px"
1634
- }, children: [
1635
- /* @__PURE__ */ jsx2(
1636
- "input",
1637
- {
1638
- type: "text",
1639
- placeholder: "Filter queries...",
1640
- value: filter,
1641
- onChange: (e) => setFilter(e.target.value),
1642
- style: {
1643
- background: "#1a1a1a",
1644
- border: "1px solid #333",
1645
- color: "#fff",
1646
- padding: "6px 10px",
1647
- borderRadius: "4px",
1648
- flex: 1,
1649
- fontSize: "12px",
1650
- outline: "none"
1651
- }
1652
- }
1653
- ),
1654
- /* @__PURE__ */ jsx2(
1655
- "button",
1656
- {
1657
- onClick: () => client.invalidateAll(),
1658
- title: "Invalidate All Queries",
1659
- style: {
1660
- background: "#222",
1661
- border: "1px solid #333",
1662
- color: "#d69e2e",
1663
- borderRadius: "4px",
1664
- padding: "0 12px",
1665
- cursor: "pointer",
1666
- fontSize: "12px",
1667
- fontWeight: 500
1668
- },
1669
- children: "\u21BB Invalidate All"
1670
- }
1671
- )
1672
- ] }),
1673
- /* @__PURE__ */ jsx2("div", { style: {
1674
- flex: 1,
1675
- overflowY: "auto",
1676
- padding: "8px",
1677
- display: "flex",
1678
- flexDirection: "column",
1679
- gap: "8px",
1680
- background: "#050505"
1681
- }, children: entries.length === 0 ? /* @__PURE__ */ jsxs("div", { style: {
1682
- padding: "40px",
1683
- textAlign: "center",
1684
- color: "#444",
1685
- fontStyle: "italic"
1686
- }, children: [
1687
- "No active queries in cache.",
1688
- /* @__PURE__ */ jsx2("br", {}),
1689
- /* @__PURE__ */ jsx2("span", { style: { fontSize: "11px", opacity: 0.7 }, children: "(Note: Only `useQuery` calls appear here. Raw `api.get` calls are not cached globally.)" })
1690
- ] }) : filteredEntries.map(([keyHash, entry]) => /* @__PURE__ */ jsx2(
1691
- QueryItem,
1692
- {
1693
- entry,
1694
- client,
1695
- isStale: client.isStale(entry.key)
1696
- },
1697
- keyHash
1698
- )) })
1699
- ]
1867
+ const signal2 = client.getSignal(options.queryKey);
1868
+ const entry = signal2.get();
1869
+ const shouldSuspend = !entry || entry.status === "pending" && entry.data === void 0;
1870
+ if (shouldSuspend) {
1871
+ const fetchPromise = client.fetch(
1872
+ options.queryKey,
1873
+ // @ts-ignore
1874
+ (ctx) => options.queryFn(ctx),
1875
+ { signal: void 0 }
1876
+ ).then((data) => {
1877
+ client.set(options.queryKey, data);
1878
+ return data;
1879
+ });
1880
+ throw fetchPromise;
1881
+ }
1882
+ if (entry?.status === "error") {
1883
+ throw entry.error;
1884
+ }
1885
+ const query = useQuery(options);
1886
+ return {
1887
+ ...query,
1888
+ data: query.data
1889
+ };
1890
+ }
1891
+
1892
+ // src/core/bridge.ts
1893
+ function fromSignal(signal2) {
1894
+ const initial = signal2.get();
1895
+ const isObject = typeof initial === "object" && initial !== null;
1896
+ const target = isObject ? initial : { value: initial };
1897
+ const proxy = createState(target);
1898
+ signal2.subscribe((newValue) => {
1899
+ if (typeof newValue === "object" && newValue !== null) {
1900
+ Object.assign(proxy, newValue);
1901
+ } else {
1902
+ proxy.value = newValue;
1700
1903
  }
1701
- );
1904
+ });
1905
+ return proxy;
1702
1906
  }
1703
- function QueryItem({ entry, client, isStale }) {
1704
- const [expanded, setExpanded] = useState5(false);
1705
- return /* @__PURE__ */ jsxs("div", { style: {
1706
- background: "#111",
1707
- borderRadius: "6px",
1708
- border: "1px solid #222",
1709
- overflow: "hidden"
1710
- }, children: [
1711
- /* @__PURE__ */ jsxs(
1712
- "div",
1713
- {
1714
- onClick: () => setExpanded(!expanded),
1715
- style: {
1716
- padding: "8px 12px",
1717
- display: "flex",
1718
- alignItems: "center",
1719
- justifyContent: "space-between",
1720
- cursor: "pointer",
1721
- background: expanded ? "#161616" : "transparent"
1722
- },
1723
- children: [
1724
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "10px", alignItems: "center", overflow: "hidden" }, children: [
1725
- /* @__PURE__ */ jsx2("span", { style: {
1726
- color: isStale ? "#d69e2e" : "#b0fb5d",
1727
- fontSize: "14px",
1728
- fontWeight: "bold",
1729
- minWidth: "10px"
1730
- }, children: isStale ? "\u2022" : "\u2022" }),
1731
- /* @__PURE__ */ jsxs("span", { style: {
1732
- color: "#e0e0e0",
1733
- fontWeight: 500,
1734
- whiteSpace: "nowrap",
1735
- overflow: "hidden",
1736
- textOverflow: "ellipsis"
1737
- }, children: [
1738
- "['",
1739
- entry.key.join("', '"),
1740
- "']"
1741
- ] })
1742
- ] }),
1743
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
1744
- /* @__PURE__ */ jsx2("span", { style: {
1745
- fontSize: "10px",
1746
- padding: "2px 6px",
1747
- borderRadius: "3px",
1748
- background: isStale ? "rgba(214, 158, 46, 0.15)" : "rgba(176, 251, 93, 0.15)",
1749
- color: isStale ? "#d69e2e" : "#b0fb5d",
1750
- border: `1px solid ${isStale ? "rgba(214, 158, 46, 0.3)" : "rgba(176, 251, 93, 0.3)"}`
1751
- }, children: isStale ? "STALE" : "FRESH" }),
1752
- /* @__PURE__ */ jsx2("span", { style: { color: "#666", fontSize: "10px" }, children: expanded ? "\u25BC" : "\u25B6" })
1753
- ] })
1754
- ]
1755
- }
1756
- ),
1757
- expanded && /* @__PURE__ */ jsxs("div", { style: {
1758
- padding: "10px",
1759
- borderTop: "1px solid #222",
1760
- background: "#0a0a0a"
1761
- }, children: [
1762
- /* @__PURE__ */ jsxs("div", { style: {
1763
- display: "flex",
1764
- gap: "8px",
1765
- marginBottom: "10px",
1766
- borderBottom: "1px solid #222",
1767
- paddingBottom: "8px"
1768
- }, children: [
1769
- /* @__PURE__ */ jsx2(
1770
- "button",
1771
- {
1772
- onClick: (e) => {
1773
- e.stopPropagation();
1774
- client.invalidate(entry.key);
1775
- },
1776
- style: {
1777
- background: "#222",
1778
- border: "1px solid #333",
1779
- color: "#d69e2e",
1780
- padding: "4px 10px",
1781
- borderRadius: "4px",
1782
- cursor: "pointer",
1783
- fontSize: "11px"
1784
- },
1785
- children: "Invalidate"
1786
- }
1787
- ),
1788
- /* @__PURE__ */ jsx2(
1789
- "button",
1790
- {
1791
- onClick: (e) => {
1792
- e.stopPropagation();
1793
- client.remove(entry.key);
1794
- },
1795
- style: {
1796
- background: "#222",
1797
- border: "1px solid #333",
1798
- color: "#ff4d4f",
1799
- padding: "4px 10px",
1800
- borderRadius: "4px",
1801
- cursor: "pointer",
1802
- fontSize: "11px"
1803
- },
1804
- children: "Remove"
1805
- }
1806
- )
1807
- ] }),
1808
- /* @__PURE__ */ jsx2("div", { style: { position: "relative" }, children: /* @__PURE__ */ jsx2("pre", { style: {
1809
- margin: 0,
1810
- fontSize: "11px",
1811
- color: "#a0a0a0",
1812
- overflowX: "auto",
1813
- fontFamily: "monospace"
1814
- }, children: JSON.stringify(entry.data, null, 2) }) }),
1815
- /* @__PURE__ */ jsxs("div", { style: {
1816
- marginTop: "8px",
1817
- fontSize: "10px",
1818
- color: "#444",
1819
- textAlign: "right"
1820
- }, children: [
1821
- "Updated: ",
1822
- new Date(entry.updatedAt).toLocaleTimeString()
1823
- ] })
1824
- ] })
1825
- ] });
1907
+ function toSignal(selector) {
1908
+ const s = createSignal(void 0);
1909
+ let isComputing = false;
1910
+ const onDependencyChange = () => {
1911
+ if (isComputing) return;
1912
+ run();
1913
+ };
1914
+ const run = () => {
1915
+ isComputing = true;
1916
+ setActiveListener(onDependencyChange);
1917
+ try {
1918
+ const value = selector();
1919
+ s.set(value);
1920
+ } finally {
1921
+ setActiveListener(null);
1922
+ isComputing = false;
1923
+ }
1924
+ };
1925
+ run();
1926
+ return {
1927
+ get: s.get,
1928
+ set: () => {
1929
+ throw new Error("Cannot set a read-only bridge signal");
1930
+ },
1931
+ subscribe: s.subscribe
1932
+ };
1826
1933
  }
1827
1934
  export {
1828
- QuantumDevTools,
1935
+ HydrationBoundary,
1829
1936
  QueryCache,
1830
1937
  QueryClientProvider,
1831
1938
  computed,
1832
1939
  createHttpClient,
1833
1940
  createState,
1834
1941
  defineModel,
1942
+ dehydrate,
1835
1943
  enableDevTools,
1944
+ fromSignal,
1836
1945
  getPromiseState,
1837
1946
  handlePromise,
1947
+ hydrate,
1838
1948
  isPromise,
1839
- optimisticHelpers,
1840
- queryCache,
1841
1949
  scheduleUpdate,
1842
1950
  subscribe,
1951
+ toSignal,
1843
1952
  unwrapPromise,
1844
1953
  useInfiniteQuery,
1845
1954
  useMutation,
1846
1955
  usePaginatedQuery,
1847
1956
  useQuery,
1848
- useQueryCache,
1849
1957
  useQueryClient,
1850
- useStore
1958
+ useStore,
1959
+ useSuspenseQuery
1851
1960
  };