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