@braine/quantum-query 1.1.1 → 1.2.1

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.cjs CHANGED
@@ -20,7 +20,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
- HttpError: () => HttpError,
23
+ QuantumDevTools: () => QuantumDevTools,
24
+ QueryCache: () => QueryCache,
25
+ QueryClientProvider: () => QueryClientProvider,
24
26
  computed: () => computed,
25
27
  createHttpClient: () => createHttpClient,
26
28
  createState: () => createState,
@@ -29,9 +31,17 @@ __export(index_exports, {
29
31
  getPromiseState: () => getPromiseState,
30
32
  handlePromise: () => handlePromise,
31
33
  isPromise: () => isPromise,
34
+ optimisticHelpers: () => optimisticHelpers,
35
+ queryCache: () => queryCache,
32
36
  scheduleUpdate: () => scheduleUpdate,
33
37
  subscribe: () => subscribe,
34
38
  unwrapPromise: () => unwrapPromise,
39
+ useInfiniteQuery: () => useInfiniteQuery,
40
+ useMutation: () => useMutation,
41
+ usePaginatedQuery: () => usePaginatedQuery,
42
+ useQuery: () => useQuery,
43
+ useQueryCache: () => useQueryCache,
44
+ useQueryClient: () => useQueryClient,
35
45
  useStore: () => useStore
36
46
  });
37
47
  module.exports = __toCommonJS(index_exports);
@@ -96,6 +106,7 @@ function getPromiseState(promise) {
96
106
  // src/core/proxy.ts
97
107
  var LISTENERS = /* @__PURE__ */ new WeakMap();
98
108
  var PROXIES = /* @__PURE__ */ new WeakMap();
109
+ var PROXY_TO_TARGET = /* @__PURE__ */ new WeakMap();
99
110
  var activeListener = null;
100
111
  function setActiveListener(listener) {
101
112
  activeListener = listener;
@@ -105,10 +116,11 @@ function getActiveListener() {
105
116
  }
106
117
  var GLOBAL_LISTENERS = /* @__PURE__ */ new WeakMap();
107
118
  function subscribe(store, callback) {
108
- let listeners = GLOBAL_LISTENERS.get(store);
119
+ const target = PROXY_TO_TARGET.get(store) || store;
120
+ let listeners = GLOBAL_LISTENERS.get(target);
109
121
  if (!listeners) {
110
122
  listeners = /* @__PURE__ */ new Set();
111
- GLOBAL_LISTENERS.set(store, listeners);
123
+ GLOBAL_LISTENERS.set(target, listeners);
112
124
  }
113
125
  listeners.add(callback);
114
126
  return () => listeners?.delete(callback);
@@ -125,8 +137,6 @@ var handler = {
125
137
  }
126
138
  const value = Reflect.get(target, prop, receiver);
127
139
  if (isPromise(value)) {
128
- if (prop === "$state") {
129
- }
130
140
  return unwrapPromise(value);
131
141
  }
132
142
  if (typeof value === "object" && value !== null) {
@@ -170,10 +180,18 @@ function createState(initialState) {
170
180
  }
171
181
  const proxy = new Proxy(initialState, handler);
172
182
  PROXIES.set(initialState, proxy);
183
+ PROXY_TO_TARGET.set(proxy, initialState);
173
184
  return proxy;
174
185
  }
175
186
 
176
187
  // src/core/model.ts
188
+ function debounce(fn, ms) {
189
+ let timeout;
190
+ return (...args) => {
191
+ clearTimeout(timeout);
192
+ timeout = setTimeout(() => fn(...args), ms);
193
+ };
194
+ }
177
195
  function defineModel(def) {
178
196
  const target = def.state;
179
197
  if (def.actions) {
@@ -194,7 +212,69 @@ function defineModel(def) {
194
212
  }
195
213
  }
196
214
  }
197
- return createState(target);
215
+ const proxy = createState(target);
216
+ if (def.persist) {
217
+ const { key, storage = "local", paths, debug } = def.persist;
218
+ let engine = null;
219
+ if (typeof storage === "string") {
220
+ if (typeof window !== "undefined") {
221
+ engine = storage === "local" ? window.localStorage : window.sessionStorage;
222
+ }
223
+ } else {
224
+ engine = storage;
225
+ }
226
+ if (engine) {
227
+ const hydrate = () => {
228
+ const process = (stored) => {
229
+ try {
230
+ if (stored) {
231
+ const parsed = JSON.parse(stored);
232
+ Object.assign(proxy, parsed);
233
+ if (debug) console.log(`[Quantum] Hydrated '${key}'`, parsed);
234
+ }
235
+ } catch (err) {
236
+ if (debug) console.error(`[Quantum] Hydration Failed for '${key}'`, err);
237
+ }
238
+ };
239
+ try {
240
+ const result = engine.getItem(key);
241
+ if (result instanceof Promise) {
242
+ result.then(process);
243
+ } else {
244
+ process(result);
245
+ }
246
+ } catch (err) {
247
+ if (debug) console.error(`[Quantum] Storage Access Failed`, err);
248
+ }
249
+ };
250
+ hydrate();
251
+ const save = debounce(async () => {
252
+ try {
253
+ let stateToSave;
254
+ if (paths) {
255
+ stateToSave = {};
256
+ for (const p of paths) {
257
+ stateToSave[p] = proxy[p];
258
+ }
259
+ } else {
260
+ stateToSave = {};
261
+ const keys = Object.keys(def.state);
262
+ for (const k of keys) {
263
+ stateToSave[k] = proxy[k];
264
+ }
265
+ }
266
+ const serialized = JSON.stringify(stateToSave);
267
+ await engine.setItem(key, serialized);
268
+ if (debug) console.log(`[Quantum] Saved '${key}'`);
269
+ } catch (err) {
270
+ }
271
+ }, 100);
272
+ subscribe(proxy, () => {
273
+ save();
274
+ });
275
+ }
276
+ }
277
+ return proxy;
198
278
  }
199
279
 
200
280
  // src/react/autoHook.ts
@@ -255,8 +335,7 @@ function computed(fn) {
255
335
  };
256
336
  }
257
337
 
258
- // src/addon/httpClient.ts
259
- var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
338
+ // src/addon/clientTypes.ts
260
339
  var HttpError = class extends Error {
261
340
  constructor(status, message) {
262
341
  super(message);
@@ -264,156 +343,259 @@ var HttpError = class extends Error {
264
343
  this.name = "HttpError";
265
344
  }
266
345
  };
267
- function createHttpClient(config) {
268
- let isRefreshing = false;
269
- let refreshPromise = null;
270
- const inflightRequests = /* @__PURE__ */ new Map();
271
- const getRetryConfig = (reqConfig) => {
272
- const raw = reqConfig?.retry ?? config.retry;
273
- if (raw === void 0 || raw === 0) return null;
274
- if (typeof raw === "number") {
275
- return { retries: raw, baseDelay: 1e3, maxDelay: 5e3 };
276
- }
277
- return raw;
346
+
347
+ // src/addon/middleware/types.ts
348
+ function compose(middleware) {
349
+ return (ctx, next) => {
350
+ let index = -1;
351
+ async function dispatch(i) {
352
+ index = i;
353
+ let fn = middleware[i];
354
+ if (i === middleware.length) fn = next;
355
+ if (!fn) return Promise.resolve(new Response(null, { status: 404 }));
356
+ try {
357
+ return fn(ctx, dispatch.bind(null, i + 1));
358
+ } catch (err) {
359
+ return Promise.reject(err);
360
+ }
361
+ }
362
+ return dispatch(0);
363
+ };
364
+ }
365
+
366
+ // src/addon/middleware/dedupe.ts
367
+ var DedupeMiddleware = async (ctx, next) => {
368
+ if (ctx.req.method !== "GET") {
369
+ return next(ctx);
370
+ }
371
+ const key = `${ctx.req.method}:${ctx.req.url}`;
372
+ if (ctx.inflight.has(key)) {
373
+ const promise = ctx.inflight.get(key);
374
+ const response = await promise;
375
+ return response.clone();
376
+ }
377
+ const sharedPromise = next(ctx).then((res) => {
378
+ return res;
379
+ }).catch((err) => {
380
+ ctx.inflight.delete(key);
381
+ throw err;
382
+ });
383
+ ctx.inflight.set(key, sharedPromise);
384
+ try {
385
+ const response = await sharedPromise;
386
+ return response.clone();
387
+ } finally {
388
+ ctx.inflight.delete(key);
389
+ }
390
+ };
391
+
392
+ // src/addon/middleware/cache.ts
393
+ var CacheMiddleware = async (ctx, next) => {
394
+ const { method, url } = ctx.req;
395
+ const { cache: cacheConfig } = ctx.config;
396
+ if (method !== "GET" || !cacheConfig?.ttl || cacheConfig.force) {
397
+ return next(ctx);
398
+ }
399
+ const key = `${method}:${url}`;
400
+ const cache = ctx.cache;
401
+ if (cache.has(key)) {
402
+ const entry = cache.get(key);
403
+ if (entry.expiresAt > Date.now()) {
404
+ const blob = JSON.stringify(entry.data);
405
+ return new Response(blob, { status: 200, statusText: "OK (Cached)" });
406
+ } else {
407
+ cache.delete(key);
408
+ }
409
+ }
410
+ const response = await next(ctx);
411
+ if (response.ok) {
412
+ const clone = response.clone();
413
+ const text = await clone.text();
414
+ try {
415
+ const data = JSON.parse(text);
416
+ cache.set(key, {
417
+ data,
418
+ // Storing raw data object
419
+ timestamp: Date.now(),
420
+ expiresAt: Date.now() + cacheConfig.ttl
421
+ });
422
+ } catch {
423
+ }
424
+ }
425
+ return response;
426
+ };
427
+
428
+ // src/addon/middleware/auth.ts
429
+ var AuthMiddleware = async (ctx, next) => {
430
+ const { auth } = ctx.client.config;
431
+ if (auth && auth.getToken && !ctx.req.headers.get("Authorization")) {
432
+ const token = await auth.getToken();
433
+ if (token) {
434
+ const newHeaders = new Headers(ctx.req.headers);
435
+ newHeaders.set("Authorization", `Bearer ${token}`);
436
+ const newReq = new Request(ctx.req, {
437
+ headers: newHeaders
438
+ });
439
+ ctx.req = newReq;
440
+ }
441
+ }
442
+ const response = await next(ctx);
443
+ if (response.status === 401 && auth && auth.onTokenExpired) {
444
+ try {
445
+ const newToken = await auth.onTokenExpired(ctx.client);
446
+ if (newToken) {
447
+ const newHeaders = new Headers(ctx.req.headers);
448
+ newHeaders.set("Authorization", `Bearer ${newToken}`);
449
+ const newReq = new Request(ctx.req.url, {
450
+ method: ctx.req.method,
451
+ headers: newHeaders,
452
+ body: ctx.req.body,
453
+ mode: ctx.req.mode,
454
+ credentials: ctx.req.credentials,
455
+ cache: ctx.req.cache,
456
+ redirect: ctx.req.redirect,
457
+ referrer: ctx.req.referrer,
458
+ integrity: ctx.req.integrity
459
+ });
460
+ ctx.req = newReq;
461
+ return next(ctx);
462
+ } else {
463
+ auth.onAuthFailed?.();
464
+ throw new HttpError(401, "Authentication Failed");
465
+ }
466
+ } catch (err) {
467
+ throw err;
468
+ }
469
+ }
470
+ return response;
471
+ };
472
+
473
+ // src/addon/middleware/fetch.ts
474
+ var FetchMiddleware = async (ctx) => {
475
+ return fetch(ctx.req);
476
+ };
477
+ var delay = (ms) => new Promise((res) => setTimeout(res, ms));
478
+ var RetryMiddleware = async (ctx, next) => {
479
+ const retryConfig = ctx.config.retry;
480
+ if (!retryConfig) return next(ctx);
481
+ const { retries = 0, baseDelay = 1e3, maxDelay = 3e3 } = typeof retryConfig === "number" ? { retries: retryConfig } : retryConfig;
482
+ const attempt = async (count) => {
483
+ try {
484
+ const response = await next(ctx);
485
+ if (!response.ok && response.status !== 401) {
486
+ if (response.status < 500 && response.status !== 429) {
487
+ return response;
488
+ }
489
+ throw new HttpError(response.status, response.statusText);
490
+ }
491
+ return response;
492
+ } catch (err) {
493
+ if (count < retries) {
494
+ if (err.name === "AbortError") throw err;
495
+ const d = Math.min(baseDelay * 2 ** count, maxDelay);
496
+ await delay(d);
497
+ return attempt(count + 1);
498
+ }
499
+ throw err;
500
+ }
278
501
  };
502
+ return attempt(0);
503
+ };
504
+
505
+ // src/addon/httpClient.ts
506
+ function createHttpClient(config) {
507
+ const cache = /* @__PURE__ */ new Map();
508
+ const inflight = /* @__PURE__ */ new Map();
509
+ const pipeline = compose([
510
+ DedupeMiddleware,
511
+ CacheMiddleware,
512
+ AuthMiddleware,
513
+ RetryMiddleware,
514
+ FetchMiddleware
515
+ ]);
279
516
  const client = {
517
+ config,
518
+ // Expose for middleware access
280
519
  async request(endpoint, options = {}) {
281
520
  let url = config.baseURL ? `${config.baseURL}${endpoint}` : endpoint;
282
- const method = options.method || "GET";
283
- const isGet = method.toUpperCase() === "GET";
284
- const dedupeKey = isGet ? `${method}:${url}` : null;
285
- if (dedupeKey && inflightRequests.has(dedupeKey)) {
286
- return inflightRequests.get(dedupeKey);
287
- }
288
- const retryConfig = getRetryConfig(options);
521
+ let headers = new Headers({
522
+ "Content-Type": "application/json",
523
+ ...config.headers,
524
+ ...options.headers
525
+ });
289
526
  const timeoutMs = options.timeout ?? config.timeout ?? 1e4;
290
527
  const controller = new AbortController();
291
528
  const id = setTimeout(() => controller.abort(), timeoutMs);
292
529
  const userSignal = options.signal;
293
- let finalSignal = controller.signal;
294
530
  if (userSignal) {
295
531
  if (userSignal.aborted) {
296
532
  clearTimeout(id);
297
533
  throw new Error("Aborted");
298
534
  }
535
+ userSignal.addEventListener("abort", () => {
536
+ clearTimeout(id);
537
+ controller.abort();
538
+ });
299
539
  }
300
- const executeBaseRequest = async (overrideToken) => {
301
- let headers = {
302
- "Content-Type": "application/json",
303
- ...config.headers,
304
- ...options.headers
305
- };
306
- if (overrideToken) {
307
- headers["Authorization"] = `Bearer ${overrideToken}`;
308
- } else if (config.auth) {
309
- const token = await config.auth.getToken();
310
- if (token) {
311
- headers["Authorization"] = `Bearer ${token}`;
312
- }
313
- }
314
- let requestConfig = {
540
+ let req = new Request(url, {
541
+ ...options,
542
+ headers,
543
+ signal: controller.signal
544
+ });
545
+ if (config.interceptors?.request) {
546
+ const newConfig = await config.interceptors.request({ ...options, headers: Object.fromEntries(headers) });
547
+ req = new Request(url, {
548
+ ...newConfig,
549
+ signal: controller.signal
550
+ });
551
+ }
552
+ const ctx = {
553
+ req,
554
+ config: {
315
555
  ...options,
316
- headers,
317
- signal: finalSignal
318
- };
319
- if (config.interceptors?.request) {
320
- requestConfig = await config.interceptors.request(requestConfig);
556
+ retry: options.retry !== void 0 ? options.retry : config.retry
557
+ },
558
+ // Merge retry
559
+ cache,
560
+ inflight,
561
+ client: this
562
+ // Pass client for Auth access hooks
563
+ };
564
+ try {
565
+ let response = await pipeline(ctx, async () => new Response("Internal Error", { status: 500 }));
566
+ clearTimeout(id);
567
+ if (config.interceptors?.response) {
568
+ response = await config.interceptors.response(response);
321
569
  }
322
- try {
323
- if (userSignal?.aborted) throw new DOMException("Aborted", "AbortError");
324
- let response = await fetch(url, requestConfig);
325
- if (config.interceptors?.response) {
326
- response = await config.interceptors.response(response);
327
- }
328
- return response;
329
- } catch (error) {
330
- throw error;
570
+ if (!response.ok) {
571
+ throw new HttpError(response.status, `HTTP Error ${response.status}`);
331
572
  }
332
- };
333
- const attemptRequest = async (attempt) => {
334
- try {
335
- const response = await executeBaseRequest();
336
- if (response.status === 401 && config.auth) {
337
- if (!isRefreshing) {
338
- isRefreshing = true;
339
- refreshPromise = config.auth.onTokenExpired(client).finally(() => {
340
- isRefreshing = false;
341
- refreshPromise = null;
342
- });
343
- }
344
- const newToken = await refreshPromise;
345
- if (newToken) {
346
- return executeBaseRequest(newToken);
347
- } else {
348
- config.auth.onAuthFailed?.();
349
- throw new HttpError(401, "Authentication Failed");
350
- }
351
- }
352
- if (!response.ok) {
353
- throw new HttpError(response.status, `HTTP Error ${response.status}`);
573
+ let data;
574
+ if (response.status === 204) {
575
+ data = {};
576
+ } else {
577
+ const text = await response.text();
578
+ try {
579
+ data = JSON.parse(text);
580
+ } catch {
581
+ data = text;
354
582
  }
355
- return response;
356
- } catch (error) {
357
- if (retryConfig && attempt < retryConfig.retries) {
358
- const isAbort = error.name === "AbortError";
359
- if (isAbort) throw error;
360
- if (error instanceof HttpError) {
361
- if (error.status < 500 && error.status !== 429) {
362
- throw error;
363
- }
364
- }
365
- const d = Math.min(
366
- retryConfig.baseDelay * 2 ** attempt,
367
- retryConfig.maxDelay
368
- );
369
- await delay(d);
370
- return attemptRequest(attempt + 1);
371
- }
372
- throw error;
373
583
  }
374
- };
375
- const execute = async () => {
376
- try {
377
- const response = await attemptRequest(0);
378
- clearTimeout(id);
379
- let data;
380
- if (response.status === 204) {
381
- data = {};
382
- } else {
383
- const text = await response.text();
384
- try {
385
- data = JSON.parse(text);
386
- } catch {
387
- data = text;
584
+ if (options.schema) {
585
+ try {
586
+ if (options.schema.parse) return options.schema.parse(data);
587
+ if (options.schema.validateSync) return options.schema.validateSync(data);
588
+ } catch (error) {
589
+ if (error.errors || error.name === "ZodError" || error.name === "ValidationError") {
590
+ throw new Error(`Validation Error: ${JSON.stringify(error.errors || error.message)}`);
388
591
  }
592
+ throw error;
389
593
  }
390
- if (options.schema) {
391
- try {
392
- if (options.schema.parse) {
393
- return options.schema.parse(data);
394
- } else if (options.schema.validateSync) {
395
- return options.schema.validateSync(data);
396
- }
397
- } catch (error) {
398
- throw new Error(`Validation Error: ${error}`);
399
- }
400
- }
401
- return data;
402
- } catch (err) {
403
- clearTimeout(id);
404
- throw err;
405
- }
406
- };
407
- const promise = execute();
408
- if (dedupeKey) {
409
- inflightRequests.set(dedupeKey, promise);
410
- }
411
- try {
412
- return await promise;
413
- } finally {
414
- if (dedupeKey) {
415
- inflightRequests.delete(dedupeKey);
416
594
  }
595
+ return data;
596
+ } catch (err) {
597
+ clearTimeout(id);
598
+ throw err;
417
599
  }
418
600
  },
419
601
  get(url, config2) {
@@ -434,9 +616,994 @@ function createHttpClient(config) {
434
616
  };
435
617
  return client;
436
618
  }
619
+
620
+ // src/addon/query/utils.ts
621
+ function stableHash(value) {
622
+ if (value === null || typeof value !== "object") {
623
+ return String(value);
624
+ }
625
+ if (Array.isArray(value)) {
626
+ return "[" + value.map(stableHash).join(",") + "]";
627
+ }
628
+ const keys = Object.keys(value).sort();
629
+ return "{" + keys.map((key) => `${key}:${stableHash(value[key])}`).join(",") + "}";
630
+ }
631
+
632
+ // src/addon/signals.ts
633
+ var pendingSignals = /* @__PURE__ */ new Set();
634
+ var isFlushScheduled = false;
635
+ function flushPendingSignals() {
636
+ const toFlush = Array.from(pendingSignals);
637
+ pendingSignals.clear();
638
+ isFlushScheduled = false;
639
+ toFlush.forEach((signal) => signal.flush());
640
+ }
641
+ var SignalImpl = class {
642
+ value;
643
+ subscribers = /* @__PURE__ */ new Set();
644
+ constructor(initialValue) {
645
+ this.value = initialValue;
646
+ }
647
+ get = () => this.value;
648
+ set = (newValue) => {
649
+ if (this.value === newValue) return;
650
+ this.value = newValue;
651
+ pendingSignals.add(this);
652
+ if (!isFlushScheduled) {
653
+ isFlushScheduled = true;
654
+ queueMicrotask(flushPendingSignals);
655
+ }
656
+ };
657
+ flush() {
658
+ const currentValue = this.value;
659
+ this.subscribers.forEach((fn) => fn(currentValue));
660
+ }
661
+ subscribe = (fn) => {
662
+ this.subscribers.add(fn);
663
+ return () => {
664
+ this.subscribers.delete(fn);
665
+ };
666
+ };
667
+ };
668
+ function createSignal(initialValue) {
669
+ return new SignalImpl(initialValue);
670
+ }
671
+
672
+ // src/addon/query/queryCache.ts
673
+ var QueryCache = class {
674
+ // Store signals instead of raw values
675
+ signals = /* @__PURE__ */ new Map();
676
+ gcInterval = null;
677
+ defaultStaleTime = 0;
678
+ // Immediately stale
679
+ defaultCacheTime = 5 * 60 * 1e3;
680
+ // 5 minutes
681
+ constructor(config) {
682
+ if (config?.enableGC !== false) {
683
+ this.startGarbageCollection();
684
+ }
685
+ }
686
+ /**
687
+ * Generate cache key from query key array
688
+ */
689
+ generateKey = (queryKey) => {
690
+ if (Array.isArray(queryKey)) {
691
+ return stableHash(queryKey);
692
+ }
693
+ return stableHash([queryKey.key, queryKey.params]);
694
+ };
695
+ /**
696
+ * Get data (wrapper around signal.get)
697
+ */
698
+ get = (queryKey) => {
699
+ const key = this.generateKey(queryKey);
700
+ const signal = this.signals.get(key);
701
+ if (!signal) return void 0;
702
+ const entry = signal.get();
703
+ if (!entry) return void 0;
704
+ const now = Date.now();
705
+ const age = now - entry.timestamp;
706
+ if (age > entry.cacheTime) {
707
+ this.signals.delete(key);
708
+ return void 0;
709
+ }
710
+ return entry.data;
711
+ };
712
+ /**
713
+ * Get Signal for a key (Low level API for hooks)
714
+ * Automatically creates a signal if one doesn't exist
715
+ */
716
+ getSignal = (queryKey) => {
717
+ const key = this.generateKey(queryKey);
718
+ let signal = this.signals.get(key);
719
+ if (!signal) {
720
+ signal = createSignal(void 0);
721
+ this.signals.set(key, signal);
722
+ }
723
+ return signal;
724
+ };
725
+ /**
726
+ * Check if data is stale
727
+ */
728
+ isStale = (queryKey) => {
729
+ const key = this.generateKey(queryKey);
730
+ const signal = this.signals.get(key);
731
+ if (!signal) return true;
732
+ const entry = signal.get();
733
+ if (!entry) return true;
734
+ const now = Date.now();
735
+ const age = now - entry.timestamp;
736
+ return age > entry.staleTime;
737
+ };
738
+ /**
739
+ * Set cached data (updates signal)
740
+ */
741
+ set = (queryKey, data, options) => {
742
+ const key = this.generateKey(queryKey);
743
+ const entry = {
744
+ data,
745
+ timestamp: Date.now(),
746
+ staleTime: options?.staleTime !== void 0 ? options.staleTime : this.defaultStaleTime,
747
+ cacheTime: options?.cacheTime !== void 0 ? options.cacheTime : this.defaultCacheTime,
748
+ key: Array.isArray(queryKey) ? queryKey : [queryKey]
749
+ };
750
+ const existingSignal = this.signals.get(key);
751
+ if (existingSignal) {
752
+ existingSignal.set(entry);
753
+ } else {
754
+ this.signals.set(key, createSignal(entry));
755
+ }
756
+ const normalizedKey = Array.isArray(queryKey) ? queryKey : [queryKey.key, queryKey.params];
757
+ this.plugins.forEach((p) => p.onQueryUpdated?.(normalizedKey, data));
758
+ };
759
+ // --- DEDUPLICATION ---
760
+ deduplicationCache = /* @__PURE__ */ new Map();
761
+ // --- MIDDLEWARE / PLUGINS ---
762
+ plugins = [];
763
+ /**
764
+ * Register a middleware plugin
765
+ */
766
+ use = (plugin) => {
767
+ this.plugins.push(plugin);
768
+ return this;
769
+ };
770
+ /**
771
+ * Fetch data with deduplication.
772
+ * If a request for the same key is already in flight, returns the existing promise.
773
+ */
774
+ fetch = async (queryKey, fn) => {
775
+ const key = this.generateKey(queryKey);
776
+ const normalizedKey = Array.isArray(queryKey) ? queryKey : [queryKey.key, queryKey.params];
777
+ if (this.deduplicationCache.has(key)) {
778
+ return this.deduplicationCache.get(key);
779
+ }
780
+ this.plugins.forEach((p) => p.onFetchStart?.(normalizedKey));
781
+ const promise = fn().then(
782
+ (data) => {
783
+ this.deduplicationCache.delete(key);
784
+ this.plugins.forEach((p) => p.onFetchSuccess?.(normalizedKey, data));
785
+ return data;
786
+ },
787
+ (error) => {
788
+ this.deduplicationCache.delete(key);
789
+ this.plugins.forEach((p) => p.onFetchError?.(normalizedKey, error));
790
+ throw error;
791
+ }
792
+ );
793
+ this.deduplicationCache.set(key, promise);
794
+ return promise;
795
+ };
796
+ /**
797
+ * Invalidate queries matching the key prefix
798
+ * Marks them as undefined to trigger refetches without breaking subscriptions
799
+ */
800
+ invalidate = (queryKey) => {
801
+ const prefix = this.generateKey(queryKey);
802
+ const normalizedKey = Array.isArray(queryKey) ? queryKey : [queryKey.key, queryKey.params];
803
+ this.plugins.forEach((p) => p.onInvalidate?.(normalizedKey));
804
+ const invalidateKey = (key) => {
805
+ const signal = this.signals.get(key);
806
+ if (signal) {
807
+ signal.set(void 0);
808
+ }
809
+ };
810
+ invalidateKey(prefix);
811
+ for (const key of this.signals.keys()) {
812
+ if (key.startsWith(prefix.slice(0, -1))) {
813
+ invalidateKey(key);
814
+ }
815
+ }
816
+ };
817
+ /**
818
+ * Remove all cache entries
819
+ */
820
+ clear = () => {
821
+ this.signals.clear();
822
+ };
823
+ /**
824
+ * Prefetch data (same as set but explicit intent)
825
+ */
826
+ prefetch = (queryKey, data, options) => {
827
+ this.set(queryKey, data, options);
828
+ };
829
+ /**
830
+ * Garbage collection - remove expired entries
831
+ */
832
+ startGarbageCollection = () => {
833
+ this.gcInterval = setInterval(() => {
834
+ const now = Date.now();
835
+ for (const [key, signal] of this.signals.entries()) {
836
+ const entry = signal.get();
837
+ if (!entry) continue;
838
+ const age = now - entry.timestamp;
839
+ if (age > entry.cacheTime) {
840
+ this.signals.delete(key);
841
+ }
842
+ }
843
+ }, 60 * 1e3);
844
+ };
845
+ /**
846
+ * Stop garbage collection
847
+ */
848
+ destroy = () => {
849
+ if (this.gcInterval) {
850
+ clearInterval(this.gcInterval);
851
+ this.gcInterval = null;
852
+ }
853
+ this.clear();
854
+ };
855
+ /**
856
+ * Get cache stats (for debugging)
857
+ */
858
+ getStats = () => {
859
+ return {
860
+ size: this.signals.size,
861
+ keys: Array.from(this.signals.keys())
862
+ };
863
+ };
864
+ /**
865
+ * Get all entries (wrapper for DevTools)
866
+ */
867
+ getAll = () => {
868
+ const map = /* @__PURE__ */ new Map();
869
+ for (const [key, signal] of this.signals.entries()) {
870
+ const val = signal.get();
871
+ if (val) map.set(key, val);
872
+ }
873
+ return map;
874
+ };
875
+ };
876
+ var queryCache = new QueryCache();
877
+
878
+ // src/addon/query/pagination.ts
879
+ var import_react2 = require("react");
880
+ function usePaginatedQuery({
881
+ queryKey,
882
+ queryFn,
883
+ pageSize = 20,
884
+ staleTime,
885
+ cacheTime,
886
+ enabled = true
887
+ }) {
888
+ const [page, setPage] = (0, import_react2.useState)(0);
889
+ const [data, setData] = (0, import_react2.useState)();
890
+ const [isLoading, setIsLoading] = (0, import_react2.useState)(true);
891
+ const [isError, setIsError] = (0, import_react2.useState)(false);
892
+ const [error, setError] = (0, import_react2.useState)(null);
893
+ const [hasNext, setHasNext] = (0, import_react2.useState)(true);
894
+ const queryFnRef = (0, import_react2.useRef)(queryFn);
895
+ (0, import_react2.useEffect)(() => {
896
+ queryFnRef.current = queryFn;
897
+ });
898
+ const queryKeyHash = JSON.stringify(queryKey);
899
+ const fetchPage = (0, import_react2.useCallback)(async (pageNum) => {
900
+ if (!enabled) return;
901
+ const pageQueryKey = [...queryKey, "page", pageNum];
902
+ const cached = queryCache.get(pageQueryKey);
903
+ if (cached && !queryCache.isStale(pageQueryKey)) {
904
+ setData(cached);
905
+ setIsLoading(false);
906
+ return;
907
+ }
908
+ try {
909
+ setIsLoading(true);
910
+ setIsError(false);
911
+ setError(null);
912
+ const result = await queryFnRef.current(pageNum);
913
+ queryCache.set(pageQueryKey, result, { staleTime, cacheTime });
914
+ setData(result);
915
+ if (Array.isArray(result)) {
916
+ setHasNext(result.length === pageSize);
917
+ } else if (result && typeof result === "object" && "hasMore" in result) {
918
+ setHasNext(result.hasMore);
919
+ }
920
+ setIsLoading(false);
921
+ } catch (err) {
922
+ setIsError(true);
923
+ setError(err);
924
+ setIsLoading(false);
925
+ }
926
+ }, [queryKeyHash, enabled, pageSize, staleTime, cacheTime]);
927
+ (0, import_react2.useEffect)(() => {
928
+ fetchPage(page);
929
+ }, [page, fetchPage]);
930
+ const nextPage = (0, import_react2.useCallback)(() => {
931
+ if (hasNext) {
932
+ setPage((p) => p + 1);
933
+ }
934
+ }, [hasNext]);
935
+ const previousPage = (0, import_react2.useCallback)(() => {
936
+ if (page > 0) {
937
+ setPage((p) => p - 1);
938
+ }
939
+ }, [page]);
940
+ const refetch = (0, import_react2.useCallback)(async () => {
941
+ queryCache.invalidate([...queryKey, "page", String(page)]);
942
+ await fetchPage(page);
943
+ }, [queryKeyHash, page, fetchPage]);
944
+ return {
945
+ data,
946
+ isLoading,
947
+ isError,
948
+ error,
949
+ page,
950
+ setPage,
951
+ nextPage,
952
+ previousPage,
953
+ hasNext,
954
+ hasPrevious: page > 0,
955
+ refetch
956
+ };
957
+ }
958
+
959
+ // src/addon/query/useQuery.ts
960
+ var import_react4 = require("react");
961
+
962
+ // src/addon/query/context.tsx
963
+ var import_react3 = require("react");
964
+ var import_jsx_runtime = require("react/jsx-runtime");
965
+ var QueryClientContext = (0, import_react3.createContext)(void 0);
966
+ var QueryClientProvider = ({
967
+ client,
968
+ children
969
+ }) => {
970
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(QueryClientContext.Provider, { value: client, children });
971
+ };
972
+ var useQueryClient = () => {
973
+ const client = (0, import_react3.useContext)(QueryClientContext);
974
+ return client || queryCache;
975
+ };
976
+
977
+ // src/addon/query/useQuery.ts
978
+ function useQuery({
979
+ queryKey,
980
+ queryFn,
981
+ schema,
982
+ staleTime = 0,
983
+ cacheTime = 5 * 60 * 1e3,
984
+ enabled = true,
985
+ refetchOnWindowFocus = false,
986
+ refetchOnReconnect = false,
987
+ refetchInterval
988
+ }) {
989
+ const client = useQueryClient();
990
+ const queryKeyHash = stableHash(queryKey);
991
+ const subscribe2 = (0, import_react4.useCallback)((onStoreChange) => {
992
+ const signal = client.getSignal(queryKey);
993
+ return signal.subscribe(() => {
994
+ onStoreChange();
995
+ });
996
+ }, [client, queryKeyHash]);
997
+ const getSnapshot = (0, import_react4.useCallback)(() => {
998
+ const signal = client.getSignal(queryKey);
999
+ return signal.get();
1000
+ }, [client, queryKeyHash]);
1001
+ const cacheEntry = (0, import_react4.useSyncExternalStore)(subscribe2, getSnapshot);
1002
+ const data = cacheEntry?.data;
1003
+ const dataTimestamp = cacheEntry?.timestamp;
1004
+ const [statusState, dispatch] = (0, import_react4.useReducer)(statusReducer, {
1005
+ isFetching: false,
1006
+ error: null
1007
+ });
1008
+ const abortControllerRef = (0, import_react4.useRef)(null);
1009
+ const intervalRef = (0, import_react4.useRef)(null);
1010
+ const isStale = dataTimestamp ? Date.now() - dataTimestamp > staleTime : true;
1011
+ const isLoading = data === void 0 && statusState.isFetching;
1012
+ const derivedIsLoading = data === void 0;
1013
+ const queryFnRef = (0, import_react4.useRef)(queryFn);
1014
+ const schemaRef = (0, import_react4.useRef)(schema);
1015
+ const queryKeyRef = (0, import_react4.useRef)(queryKey);
1016
+ (0, import_react4.useEffect)(() => {
1017
+ queryFnRef.current = queryFn;
1018
+ schemaRef.current = schema;
1019
+ queryKeyRef.current = queryKey;
1020
+ });
1021
+ const fetchData = (0, import_react4.useCallback)(async (background = false) => {
1022
+ if (!enabled) return;
1023
+ if (abortControllerRef.current) abortControllerRef.current.abort();
1024
+ abortControllerRef.current = new AbortController();
1025
+ if (!background) {
1026
+ const currentEntry = getSnapshot();
1027
+ if (currentEntry && Date.now() - currentEntry.timestamp <= staleTime) {
1028
+ return;
1029
+ }
1030
+ }
1031
+ try {
1032
+ dispatch({ type: "FETCH_START", background });
1033
+ const fn = queryFnRef.current;
1034
+ const sc = schemaRef.current;
1035
+ const key = queryKeyRef.current;
1036
+ let result = await client.fetch(key, async () => {
1037
+ let res = await fn();
1038
+ if (sc) {
1039
+ res = sc.parse(res);
1040
+ }
1041
+ return res;
1042
+ });
1043
+ client.set(key, result, { staleTime, cacheTime });
1044
+ dispatch({ type: "FETCH_SUCCESS" });
1045
+ } catch (err) {
1046
+ if (err.name === "AbortError") return;
1047
+ dispatch({ type: "FETCH_ERROR", error: err });
1048
+ }
1049
+ }, [queryKeyHash, enabled, staleTime, cacheTime, client, getSnapshot]);
1050
+ (0, import_react4.useEffect)(() => {
1051
+ if (data === void 0 && !statusState.error) {
1052
+ fetchData();
1053
+ }
1054
+ }, [fetchData, data, statusState.error]);
1055
+ (0, import_react4.useEffect)(() => {
1056
+ if (!enabled || !refetchInterval) return;
1057
+ intervalRef.current = setInterval(() => fetchData(true), refetchInterval);
1058
+ return () => {
1059
+ if (intervalRef.current) clearInterval(intervalRef.current);
1060
+ };
1061
+ }, [enabled, refetchInterval, fetchData]);
1062
+ (0, import_react4.useEffect)(() => {
1063
+ if (!enabled || !refetchOnWindowFocus) return;
1064
+ const handleFocus = () => {
1065
+ const entry = getSnapshot();
1066
+ const isStaleNow = !entry || Date.now() - entry.timestamp > staleTime;
1067
+ if (isStaleNow) fetchData(true);
1068
+ };
1069
+ window.addEventListener("focus", handleFocus);
1070
+ return () => window.removeEventListener("focus", handleFocus);
1071
+ }, [enabled, refetchOnWindowFocus, fetchData, getSnapshot, staleTime]);
1072
+ (0, import_react4.useEffect)(() => {
1073
+ if (!enabled || !refetchOnReconnect) return;
1074
+ const handleOnline = () => {
1075
+ const entry = getSnapshot();
1076
+ const isStaleNow = !entry || Date.now() - entry.timestamp > staleTime;
1077
+ if (isStaleNow) fetchData(true);
1078
+ };
1079
+ window.addEventListener("online", handleOnline);
1080
+ return () => window.removeEventListener("online", handleOnline);
1081
+ }, [enabled, refetchOnReconnect, fetchData, getSnapshot, staleTime]);
1082
+ const refetch = (0, import_react4.useCallback)(async () => {
1083
+ client.invalidate(queryKey);
1084
+ await fetchData();
1085
+ }, [queryKeyHash, fetchData, client]);
1086
+ return {
1087
+ data,
1088
+ isLoading: derivedIsLoading,
1089
+ isError: !!statusState.error,
1090
+ isFetching: statusState.isFetching,
1091
+ isStale,
1092
+ error: statusState.error,
1093
+ refetch
1094
+ };
1095
+ }
1096
+ function statusReducer(state, action) {
1097
+ switch (action.type) {
1098
+ case "FETCH_START":
1099
+ return {
1100
+ ...state,
1101
+ isFetching: true,
1102
+ error: null
1103
+ };
1104
+ case "FETCH_SUCCESS":
1105
+ return {
1106
+ ...state,
1107
+ isFetching: false,
1108
+ error: null
1109
+ };
1110
+ case "FETCH_ERROR":
1111
+ return {
1112
+ ...state,
1113
+ isFetching: false,
1114
+ error: action.error
1115
+ };
1116
+ default:
1117
+ return state;
1118
+ }
1119
+ }
1120
+
1121
+ // src/addon/query/useMutation.ts
1122
+ var import_react5 = require("react");
1123
+ function useMutation({
1124
+ mutationFn,
1125
+ onMutate,
1126
+ onSuccess,
1127
+ onError,
1128
+ onSettled
1129
+ }) {
1130
+ const [data, setData] = (0, import_react5.useState)();
1131
+ const [error, setError] = (0, import_react5.useState)(null);
1132
+ const [isLoading, setIsLoading] = (0, import_react5.useState)(false);
1133
+ const [isError, setIsError] = (0, import_react5.useState)(false);
1134
+ const [isSuccess, setIsSuccess] = (0, import_react5.useState)(false);
1135
+ const mutateAsync = (0, import_react5.useCallback)(async (variables) => {
1136
+ let context;
1137
+ try {
1138
+ setIsLoading(true);
1139
+ setIsError(false);
1140
+ setError(null);
1141
+ setIsSuccess(false);
1142
+ if (onMutate) {
1143
+ context = await onMutate(variables);
1144
+ }
1145
+ const result = await mutationFn(variables);
1146
+ setData(result);
1147
+ setIsSuccess(true);
1148
+ setIsLoading(false);
1149
+ if (onSuccess) {
1150
+ onSuccess(result, variables, context);
1151
+ }
1152
+ if (onSettled) {
1153
+ onSettled(result, null, variables, context);
1154
+ }
1155
+ return result;
1156
+ } catch (err) {
1157
+ setIsError(true);
1158
+ setError(err);
1159
+ setIsLoading(false);
1160
+ if (onError) {
1161
+ onError(err, variables, context);
1162
+ }
1163
+ if (onSettled) {
1164
+ onSettled(void 0, err, variables, context);
1165
+ }
1166
+ throw err;
1167
+ }
1168
+ }, [mutationFn, onMutate, onSuccess, onError, onSettled]);
1169
+ const mutate = (0, import_react5.useCallback)(async (variables) => {
1170
+ try {
1171
+ await mutateAsync(variables);
1172
+ } catch {
1173
+ }
1174
+ }, [mutateAsync]);
1175
+ const reset = (0, import_react5.useCallback)(() => {
1176
+ setData(void 0);
1177
+ setError(null);
1178
+ setIsLoading(false);
1179
+ setIsError(false);
1180
+ setIsSuccess(false);
1181
+ }, []);
1182
+ return {
1183
+ mutate,
1184
+ mutateAsync,
1185
+ data,
1186
+ error,
1187
+ isLoading,
1188
+ isError,
1189
+ isSuccess,
1190
+ reset
1191
+ };
1192
+ }
1193
+ var optimisticHelpers = {
1194
+ /**
1195
+ * Cancel ongoing queries for a key
1196
+ */
1197
+ async cancelQueries(queryKey) {
1198
+ },
1199
+ /**
1200
+ * Get current query data
1201
+ */
1202
+ getQueryData(queryKey) {
1203
+ return queryCache.get(queryKey);
1204
+ },
1205
+ /**
1206
+ * Set query data (for optimistic updates)
1207
+ */
1208
+ setQueryData(queryKey, updater) {
1209
+ const current = queryCache.get(queryKey);
1210
+ const newData = typeof updater === "function" ? updater(current) : updater;
1211
+ queryCache.set(queryKey, newData);
1212
+ return current;
1213
+ },
1214
+ /**
1215
+ * Invalidate queries (trigger refetch)
1216
+ */
1217
+ invalidateQueries(queryKey) {
1218
+ queryCache.invalidate(queryKey);
1219
+ }
1220
+ };
1221
+
1222
+ // src/addon/query/infiniteQuery.ts
1223
+ var import_react6 = require("react");
1224
+ function statusReducer2(state, action) {
1225
+ switch (action.type) {
1226
+ case "FETCH_START":
1227
+ return {
1228
+ ...state,
1229
+ isFetching: true,
1230
+ isFetchingNextPage: action.direction === "next",
1231
+ isFetchingPreviousPage: action.direction === "previous",
1232
+ error: null
1233
+ };
1234
+ case "FETCH_SUCCESS":
1235
+ return {
1236
+ ...state,
1237
+ isFetching: false,
1238
+ isFetchingNextPage: false,
1239
+ isFetchingPreviousPage: false,
1240
+ hasNextPage: action.hasNextPage !== void 0 ? action.hasNextPage : state.hasNextPage,
1241
+ hasPreviousPage: action.hasPreviousPage !== void 0 ? action.hasPreviousPage : state.hasPreviousPage
1242
+ };
1243
+ case "FETCH_ERROR":
1244
+ return {
1245
+ ...state,
1246
+ isFetching: false,
1247
+ isFetchingNextPage: false,
1248
+ isFetchingPreviousPage: false,
1249
+ error: action.error
1250
+ };
1251
+ case "SET_PAGINATION":
1252
+ return {
1253
+ ...state,
1254
+ hasNextPage: action.hasNextPage !== void 0 ? action.hasNextPage : state.hasNextPage,
1255
+ hasPreviousPage: action.hasPreviousPage !== void 0 ? action.hasPreviousPage : state.hasPreviousPage
1256
+ };
1257
+ default:
1258
+ return state;
1259
+ }
1260
+ }
1261
+ function useInfiniteQuery({
1262
+ queryKey,
1263
+ queryFn,
1264
+ getNextPageParam,
1265
+ getPreviousPageParam,
1266
+ initialPageParam,
1267
+ staleTime = 0,
1268
+ cacheTime = 5 * 60 * 1e3,
1269
+ enabled = true
1270
+ }) {
1271
+ const client = useQueryClient();
1272
+ const queryKeyHash = stableHash(queryKey);
1273
+ const infiniteQueryKey = [...queryKey, "__infinite__"];
1274
+ const subscribe2 = (0, import_react6.useCallback)((onStoreChange) => {
1275
+ const signal = client.getSignal(infiniteQueryKey);
1276
+ return signal.subscribe(() => onStoreChange());
1277
+ }, [client, queryKeyHash]);
1278
+ const getSnapshot = (0, import_react6.useCallback)(() => {
1279
+ const signal = client.getSignal(infiniteQueryKey);
1280
+ return signal.get();
1281
+ }, [client, queryKeyHash]);
1282
+ const cacheEntry = (0, import_react6.useSyncExternalStore)(subscribe2, getSnapshot);
1283
+ const data = cacheEntry?.data;
1284
+ const [statusState, dispatch] = (0, import_react6.useReducer)(statusReducer2, {
1285
+ isFetching: false,
1286
+ isFetchingNextPage: false,
1287
+ isFetchingPreviousPage: false,
1288
+ error: null,
1289
+ hasNextPage: false,
1290
+ // Will be set after first fetch
1291
+ hasPreviousPage: false
1292
+ });
1293
+ const queryFnRef = (0, import_react6.useRef)(queryFn);
1294
+ const getNextPageParamRef = (0, import_react6.useRef)(getNextPageParam);
1295
+ const getPreviousPageParamRef = (0, import_react6.useRef)(getPreviousPageParam);
1296
+ const initialFetchDoneRef = (0, import_react6.useRef)(false);
1297
+ const clientRef = (0, import_react6.useRef)(client);
1298
+ const infiniteQueryKeyRef = (0, import_react6.useRef)(infiniteQueryKey);
1299
+ const initialPageParamRef = (0, import_react6.useRef)(initialPageParam);
1300
+ const staleTimeRef = (0, import_react6.useRef)(staleTime);
1301
+ const cacheTimeRef = (0, import_react6.useRef)(cacheTime);
1302
+ (0, import_react6.useEffect)(() => {
1303
+ queryFnRef.current = queryFn;
1304
+ getNextPageParamRef.current = getNextPageParam;
1305
+ getPreviousPageParamRef.current = getPreviousPageParam;
1306
+ clientRef.current = client;
1307
+ infiniteQueryKeyRef.current = infiniteQueryKey;
1308
+ initialPageParamRef.current = initialPageParam;
1309
+ staleTimeRef.current = staleTime;
1310
+ cacheTimeRef.current = cacheTime;
1311
+ });
1312
+ const prevDataRef = (0, import_react6.useRef)(data);
1313
+ (0, import_react6.useEffect)(() => {
1314
+ if (prevDataRef.current && !data) {
1315
+ initialFetchDoneRef.current = false;
1316
+ }
1317
+ prevDataRef.current = data;
1318
+ }, [data]);
1319
+ (0, import_react6.useEffect)(() => {
1320
+ if (!enabled) return;
1321
+ if (data) return;
1322
+ const doFetch = async () => {
1323
+ const initialParam = initialPageParamRef.current;
1324
+ const firstParam = initialParam !== void 0 ? initialParam : 0;
1325
+ if (!initialFetchDoneRef.current) {
1326
+ initialFetchDoneRef.current = true;
1327
+ }
1328
+ dispatch({ type: "FETCH_START", direction: "initial" });
1329
+ const pageKey = [...infiniteQueryKey, "initial", String(firstParam)];
1330
+ let firstPage;
1331
+ try {
1332
+ firstPage = await clientRef.current.fetch(
1333
+ pageKey,
1334
+ () => queryFnRef.current({ pageParam: firstParam })
1335
+ );
1336
+ } catch (error) {
1337
+ dispatch({ type: "FETCH_ERROR", error });
1338
+ return;
1339
+ }
1340
+ if (firstPage) {
1341
+ const initialData = {
1342
+ pages: [firstPage],
1343
+ pageParams: [firstParam]
1344
+ };
1345
+ let hasNext = false;
1346
+ if (getNextPageParamRef.current) {
1347
+ const nextParam = getNextPageParamRef.current(firstPage, [firstPage]);
1348
+ hasNext = nextParam !== void 0;
1349
+ }
1350
+ clientRef.current.set(infiniteQueryKeyRef.current, initialData, {
1351
+ staleTime: staleTimeRef.current,
1352
+ cacheTime: cacheTimeRef.current
1353
+ });
1354
+ dispatch({ type: "FETCH_SUCCESS", hasNextPage: hasNext });
1355
+ }
1356
+ };
1357
+ doFetch();
1358
+ }, [enabled, data]);
1359
+ const fetchPageHelper = (0, import_react6.useCallback)(async (pageParam) => {
1360
+ try {
1361
+ const pageKey = [...infiniteQueryKey, String(pageParam)];
1362
+ return await clientRef.current.fetch(
1363
+ pageKey,
1364
+ () => queryFnRef.current({ pageParam })
1365
+ );
1366
+ } catch (error) {
1367
+ dispatch({ type: "FETCH_ERROR", error });
1368
+ return void 0;
1369
+ }
1370
+ }, [client, infiniteQueryKey]);
1371
+ const fetchNextPage = (0, import_react6.useCallback)(async () => {
1372
+ if (!statusState.hasNextPage || statusState.isFetchingNextPage || !data) return;
1373
+ const lastPage = data.pages[data.pages.length - 1];
1374
+ if (!lastPage || !getNextPageParamRef.current) return;
1375
+ const nextPageParam = getNextPageParamRef.current(lastPage, data.pages);
1376
+ if (nextPageParam === void 0) return;
1377
+ dispatch({ type: "FETCH_START", direction: "next" });
1378
+ const newPage = await fetchPageHelper(nextPageParam);
1379
+ if (newPage) {
1380
+ const updatedData = {
1381
+ pages: [...data.pages, newPage],
1382
+ pageParams: [...data.pageParams, nextPageParam]
1383
+ };
1384
+ let hasNext = false;
1385
+ if (getNextPageParamRef.current) {
1386
+ const nextParam = getNextPageParamRef.current(newPage, updatedData.pages);
1387
+ hasNext = nextParam !== void 0;
1388
+ }
1389
+ clientRef.current.set(infiniteQueryKeyRef.current, updatedData, {
1390
+ staleTime: staleTimeRef.current,
1391
+ cacheTime: cacheTimeRef.current
1392
+ });
1393
+ dispatch({ type: "FETCH_SUCCESS", hasNextPage: hasNext });
1394
+ }
1395
+ }, [statusState.hasNextPage, statusState.isFetchingNextPage, data, fetchPageHelper]);
1396
+ const fetchPreviousPage = (0, import_react6.useCallback)(async () => {
1397
+ if (!statusState.hasPreviousPage || statusState.isFetchingPreviousPage || !data) return;
1398
+ const firstPage = data.pages[0];
1399
+ if (!firstPage || !getPreviousPageParamRef.current) return;
1400
+ const previousPageParam = getPreviousPageParamRef.current(firstPage, data.pages);
1401
+ if (previousPageParam === void 0) return;
1402
+ dispatch({ type: "FETCH_START", direction: "previous" });
1403
+ const newPage = await fetchPageHelper(previousPageParam);
1404
+ if (newPage) {
1405
+ const updatedData = {
1406
+ pages: [newPage, ...data.pages],
1407
+ pageParams: [previousPageParam, ...data.pageParams]
1408
+ };
1409
+ let hasPrev = false;
1410
+ if (getPreviousPageParamRef.current) {
1411
+ const prevParam = getPreviousPageParamRef.current(newPage, updatedData.pages);
1412
+ hasPrev = prevParam !== void 0;
1413
+ }
1414
+ clientRef.current.set(infiniteQueryKeyRef.current, updatedData, {
1415
+ staleTime: staleTimeRef.current,
1416
+ cacheTime: cacheTimeRef.current
1417
+ });
1418
+ dispatch({ type: "FETCH_SUCCESS", hasPreviousPage: hasPrev });
1419
+ }
1420
+ }, [statusState.hasPreviousPage, statusState.isFetchingPreviousPage, data, fetchPageHelper]);
1421
+ const refetch = (0, import_react6.useCallback)(async () => {
1422
+ initialFetchDoneRef.current = false;
1423
+ clientRef.current.invalidate(infiniteQueryKeyRef.current);
1424
+ }, []);
1425
+ return {
1426
+ data,
1427
+ fetchNextPage,
1428
+ fetchPreviousPage,
1429
+ hasNextPage: statusState.hasNextPage,
1430
+ hasPreviousPage: statusState.hasPreviousPage,
1431
+ isFetching: statusState.isFetching,
1432
+ isFetchingNextPage: statusState.isFetchingNextPage,
1433
+ isFetchingPreviousPage: statusState.isFetchingPreviousPage,
1434
+ isLoading: data === void 0 && statusState.isFetching,
1435
+ isError: !!statusState.error,
1436
+ error: statusState.error,
1437
+ refetch
1438
+ };
1439
+ }
1440
+
1441
+ // src/addon/query/devtools.tsx
1442
+ var import_react8 = require("react");
1443
+
1444
+ // src/addon/query/useQueryCache.ts
1445
+ var import_react7 = require("react");
1446
+ function useQueryCache() {
1447
+ const client = useQueryClient();
1448
+ const [cache, setCache] = (0, import_react7.useState)(client.getAll());
1449
+ (0, import_react7.useEffect)(() => {
1450
+ const interval = setInterval(() => {
1451
+ setCache({ ...client.getAll() });
1452
+ }, 500);
1453
+ return () => clearInterval(interval);
1454
+ }, [client]);
1455
+ return cache;
1456
+ }
1457
+
1458
+ // src/addon/query/devtools.tsx
1459
+ var import_jsx_runtime2 = require("react/jsx-runtime");
1460
+ function QuantumDevTools() {
1461
+ const [isOpen, setIsOpen] = (0, import_react8.useState)(false);
1462
+ const cache = useQueryCache();
1463
+ const client = useQueryClient();
1464
+ if (!isOpen) {
1465
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1466
+ "button",
1467
+ {
1468
+ onClick: () => setIsOpen(true),
1469
+ style: {
1470
+ position: "fixed",
1471
+ bottom: "10px",
1472
+ right: "10px",
1473
+ background: "#000",
1474
+ color: "#fff",
1475
+ border: "none",
1476
+ borderRadius: "50%",
1477
+ width: "40px",
1478
+ height: "40px",
1479
+ cursor: "pointer",
1480
+ zIndex: 9999,
1481
+ boxShadow: "0 4px 6px rgba(0,0,0,0.1)",
1482
+ fontSize: "20px",
1483
+ display: "flex",
1484
+ alignItems: "center",
1485
+ justifyContent: "center"
1486
+ },
1487
+ children: "\u26A1\uFE0F"
1488
+ }
1489
+ );
1490
+ }
1491
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: {
1492
+ position: "fixed",
1493
+ bottom: 0,
1494
+ right: 0,
1495
+ width: "100%",
1496
+ maxWidth: "600px",
1497
+ height: "400px",
1498
+ background: "#1a1a1a",
1499
+ color: "#fff",
1500
+ borderTopLeftRadius: "10px",
1501
+ boxShadow: "0 -4px 20px rgba(0,0,0,0.3)",
1502
+ zIndex: 9999,
1503
+ display: "flex",
1504
+ flexDirection: "column",
1505
+ fontFamily: "monospace"
1506
+ }, children: [
1507
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: {
1508
+ padding: "10px",
1509
+ borderBottom: "1px solid #333",
1510
+ display: "flex",
1511
+ justifyContent: "space-between",
1512
+ alignItems: "center",
1513
+ background: "#222",
1514
+ borderTopLeftRadius: "10px"
1515
+ }, children: [
1516
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontWeight: "bold" }, children: "\u26A1\uFE0F Quantum DevTools" }),
1517
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1518
+ "button",
1519
+ {
1520
+ onClick: () => setIsOpen(false),
1521
+ style: {
1522
+ background: "transparent",
1523
+ border: "none",
1524
+ color: "#999",
1525
+ cursor: "pointer",
1526
+ fontSize: "16px"
1527
+ },
1528
+ children: "\u2715"
1529
+ }
1530
+ )
1531
+ ] }),
1532
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
1533
+ flex: 1,
1534
+ overflowY: "auto",
1535
+ padding: "10px",
1536
+ display: "flex",
1537
+ flexDirection: "column",
1538
+ gap: "8px"
1539
+ }, children: Array.from(cache.entries()).length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { padding: "20px", textAlign: "center", color: "#666" }, children: "No active queries" }) : Array.from(cache.entries()).map(([keyHash, entry]) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: {
1540
+ background: "#2a2a2a",
1541
+ borderRadius: "4px",
1542
+ padding: "8px",
1543
+ border: "1px solid #333"
1544
+ }, children: [
1545
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", marginBottom: "8px" }, children: [
1546
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: "#aaa", fontSize: "12px" }, children: entry.key.map((k) => String(k)).join(" / ") }),
1547
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { display: "flex", gap: "5px" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: {
1548
+ fontSize: "10px",
1549
+ padding: "2px 4px",
1550
+ borderRadius: "2px",
1551
+ background: client.isStale(entry.key) ? "#dda0dd" : "#90ee90",
1552
+ color: "#000"
1553
+ }, children: client.isStale(entry.key) ? "STALE" : "FRESH" }) })
1554
+ ] }),
1555
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
1556
+ fontSize: "11px",
1557
+ color: "#ddd",
1558
+ whiteSpace: "pre-wrap",
1559
+ maxHeight: "100px",
1560
+ overflow: "hidden",
1561
+ opacity: 0.8
1562
+ }, children: JSON.stringify(entry.data, null, 2) }),
1563
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginTop: "8px", display: "flex", gap: "8px" }, children: [
1564
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1565
+ "button",
1566
+ {
1567
+ onClick: () => client.invalidate(entry.key),
1568
+ style: {
1569
+ background: "#444",
1570
+ border: "none",
1571
+ color: "#fff",
1572
+ padding: "4px 8px",
1573
+ borderRadius: "3px",
1574
+ cursor: "pointer",
1575
+ fontSize: "10px"
1576
+ },
1577
+ children: "Invalidate"
1578
+ }
1579
+ ),
1580
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1581
+ "button",
1582
+ {
1583
+ onClick: () => {
1584
+ client.invalidate(entry.key);
1585
+ },
1586
+ style: {
1587
+ background: "#444",
1588
+ border: "none",
1589
+ color: "#fff",
1590
+ padding: "4px 8px",
1591
+ borderRadius: "3px",
1592
+ cursor: "pointer",
1593
+ fontSize: "10px"
1594
+ },
1595
+ children: "Refetch"
1596
+ }
1597
+ )
1598
+ ] })
1599
+ ] }, keyHash)) })
1600
+ ] });
1601
+ }
437
1602
  // Annotate the CommonJS export names for ESM import in node:
438
1603
  0 && (module.exports = {
439
- HttpError,
1604
+ QuantumDevTools,
1605
+ QueryCache,
1606
+ QueryClientProvider,
440
1607
  computed,
441
1608
  createHttpClient,
442
1609
  createState,
@@ -445,8 +1612,16 @@ function createHttpClient(config) {
445
1612
  getPromiseState,
446
1613
  handlePromise,
447
1614
  isPromise,
1615
+ optimisticHelpers,
1616
+ queryCache,
448
1617
  scheduleUpdate,
449
1618
  subscribe,
450
1619
  unwrapPromise,
1620
+ useInfiniteQuery,
1621
+ useMutation,
1622
+ usePaginatedQuery,
1623
+ useQuery,
1624
+ useQueryCache,
1625
+ useQueryClient,
451
1626
  useStore
452
1627
  });