@braine/quantum-query 1.3.2 → 1.3.4

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
@@ -76,7 +76,7 @@ function atom(initialValue, options) {
76
76
  return s;
77
77
  }
78
78
  function setupPersistence(s, options) {
79
- const { key, storage = "local", debug } = options;
79
+ const { key, storage = "local", debug, hydrateSync } = options;
80
80
  if (!key) return;
81
81
  let engine = null;
82
82
  if (typeof storage === "string") {
@@ -87,27 +87,34 @@ function setupPersistence(s, options) {
87
87
  engine = storage;
88
88
  }
89
89
  if (!engine) return;
90
- try {
91
- const stored = engine.getItem(key);
92
- const applyValue = (val) => {
93
- try {
94
- const parsed = JSON.parse(val);
95
- const validated = options.validate ? options.validate(parsed) : parsed;
96
- s.set(validated);
97
- if (debug) console.log(`[Quantum] Hydrated atom '${key}'`);
98
- } catch (e) {
99
- if (debug) console.error(`[Quantum] Hydration validation failed for '${key}'`, e);
90
+ const hydrate2 = () => {
91
+ try {
92
+ const stored = engine?.getItem(key);
93
+ const applyValue = (val) => {
94
+ try {
95
+ const parsed = JSON.parse(val);
96
+ const validated = options.validate ? options.validate(parsed) : parsed;
97
+ s.set(validated);
98
+ if (debug) console.log(`[Quantum] Hydrated atom '${key}'`);
99
+ } catch (e) {
100
+ if (debug) console.error(`[Quantum] Hydration validation failed for '${key}'`, e);
101
+ }
102
+ };
103
+ if (stored instanceof Promise) {
104
+ stored.then((val) => {
105
+ if (val) applyValue(val);
106
+ });
107
+ } else if (stored) {
108
+ applyValue(stored);
100
109
  }
101
- };
102
- if (stored instanceof Promise) {
103
- stored.then((val) => {
104
- if (val) applyValue(val);
105
- });
106
- } else if (stored) {
107
- applyValue(stored);
110
+ } catch (err) {
111
+ if (debug) console.error(`[Quantum] Hydration error`, err);
108
112
  }
109
- } catch (err) {
110
- if (debug) console.error(`[Quantum] Hydration error`, err);
113
+ };
114
+ if (hydrateSync) {
115
+ hydrate2();
116
+ } else {
117
+ Promise.resolve().then(hydrate2);
111
118
  }
112
119
  s.subscribe((value) => {
113
120
  try {
@@ -138,6 +145,19 @@ function SignalValue({ signal: signal2, render, children }) {
138
145
  return /* @__PURE__ */ jsx(Fragment, { children: renderer(value) });
139
146
  }
140
147
 
148
+ // src/react/QueryMatch.tsx
149
+ import "react";
150
+ import { jsx as jsx2 } from "react/jsx-runtime";
151
+ function QueryMatch({ signal: signal2, selector, children }) {
152
+ return /* @__PURE__ */ jsx2(SignalValue, { signal: signal2, children: (value) => {
153
+ const selected = selector(value);
154
+ return children(selected);
155
+ } });
156
+ }
157
+ function Match({ signal: signal2, when, children }) {
158
+ return /* @__PURE__ */ jsx2(SignalValue, { signal: signal2, children: (value) => when(value) ? children : null });
159
+ }
160
+
141
161
  // src/store/scheduler.ts
142
162
  var pending = /* @__PURE__ */ new Set();
143
163
  var timer = null;
@@ -201,41 +221,90 @@ function stableHash(value, depth = 0) {
201
221
  const keys = Object.keys(value).sort();
202
222
  return `object:{${keys.map((key) => `${key}:${stableHash(value[key], depth + 1)}`).join(",")}}`;
203
223
  }
204
- function isDeepEqual(a, b) {
205
- if (a === b) return true;
206
- if (a && b && typeof a === "object" && typeof b === "object") {
207
- const objA = a;
208
- const objB = b;
209
- if (objA.constructor !== objB.constructor) return false;
210
- let length, i, keys;
211
- if (Array.isArray(a)) {
212
- if (!Array.isArray(b)) return false;
213
- length = a.length;
214
- if (length !== b.length) return false;
215
- for (i = length; i-- !== 0; ) {
216
- if (!isDeepEqual(a[i], b[i])) return false;
224
+
225
+ // src/query/trie.ts
226
+ var TrieNode = class {
227
+ children = /* @__PURE__ */ new Map();
228
+ keys = /* @__PURE__ */ new Set();
229
+ // Stores the full hashed keys valid at this path
230
+ };
231
+ var QueryKeyTrie = class {
232
+ root = new TrieNode();
233
+ insert(queryKey, hashedKey) {
234
+ const parts = this.normalizeParts(queryKey);
235
+ let node = this.root;
236
+ for (const part of parts) {
237
+ const hash = stableHash(part);
238
+ let child = node.children.get(hash);
239
+ if (!child) {
240
+ child = new TrieNode();
241
+ node.children.set(hash, child);
217
242
  }
218
- return true;
243
+ node = child;
244
+ }
245
+ node.keys.add(hashedKey);
246
+ }
247
+ remove(queryKey, hashedKey) {
248
+ const parts = this.normalizeParts(queryKey);
249
+ this.removeRecursive(this.root, parts, 0, hashedKey);
250
+ }
251
+ removeRecursive(node, parts, index, hashedKey) {
252
+ if (index === parts.length) {
253
+ node.keys.delete(hashedKey);
254
+ return node.children.size === 0 && node.keys.size === 0;
255
+ }
256
+ const part = parts[index];
257
+ const hash = stableHash(part);
258
+ const child = node.children.get(hash);
259
+ if (child) {
260
+ const shouldDeleteChild = this.removeRecursive(child, parts, index + 1, hashedKey);
261
+ if (shouldDeleteChild) {
262
+ node.children.delete(hash);
263
+ }
264
+ }
265
+ return node.children.size === 0 && node.keys.size === 0;
266
+ }
267
+ /**
268
+ * Get all hashed keys that match the given partial query key (prefix)
269
+ */
270
+ getMatchingKeys(partialKey) {
271
+ const parts = this.normalizeParts(partialKey);
272
+ let node = this.root;
273
+ for (const part of parts) {
274
+ const hash = stableHash(part);
275
+ const child = node.children.get(hash);
276
+ if (!child) {
277
+ return /* @__PURE__ */ new Set();
278
+ }
279
+ node = child;
219
280
  }
220
- if (objA.valueOf !== Object.prototype.valueOf) return objA.valueOf() === objB.valueOf();
221
- if (objA.toString !== Object.prototype.toString) return objA.toString() === objB.toString();
222
- keys = Object.keys(objA);
223
- length = keys.length;
224
- if (length !== Object.keys(objB).length) return false;
225
- for (const key of keys) {
226
- if (!Object.prototype.hasOwnProperty.call(objB, key)) return false;
281
+ const results = /* @__PURE__ */ new Set();
282
+ this.collectKeys(node, results);
283
+ return results;
284
+ }
285
+ collectKeys(node, results) {
286
+ for (const key of node.keys) {
287
+ results.add(key);
227
288
  }
228
- for (const key of keys) {
229
- if (!isDeepEqual(objA[key], objB[key])) return false;
289
+ for (const child of node.children.values()) {
290
+ this.collectKeys(child, results);
230
291
  }
231
- return true;
232
292
  }
233
- return a !== a && b !== b;
234
- }
293
+ normalizeParts(queryKey) {
294
+ if (Array.isArray(queryKey)) {
295
+ return queryKey;
296
+ }
297
+ if (queryKey && typeof queryKey === "object" && "key" in queryKey) {
298
+ const qk = queryKey;
299
+ return [qk.key, qk.params];
300
+ }
301
+ return [queryKey];
302
+ }
303
+ };
235
304
 
236
305
  // src/query/queryStorage.ts
237
306
  var QueryStorage = class {
238
- // Tracks access order (least to most recent)
307
+ // 10/10: O(K) Lookup
239
308
  // Default configuration
240
309
  constructor(defaultStaleTime = 5 * 60 * 1e3, defaultCacheTime = 5 * 60 * 1e3, maxSize = 100) {
241
310
  this.defaultStaleTime = defaultStaleTime;
@@ -245,6 +314,8 @@ var QueryStorage = class {
245
314
  signals = /* @__PURE__ */ new Map();
246
315
  gcTimers = /* @__PURE__ */ new Map();
247
316
  lruOrder = /* @__PURE__ */ new Set();
317
+ // Tracks access order (least to most recent)
318
+ trie = new QueryKeyTrie();
248
319
  generateKey(queryKey) {
249
320
  const key = Array.isArray(queryKey) ? stableHash(queryKey) : stableHash([queryKey.key, queryKey.params]);
250
321
  return key;
@@ -319,6 +390,7 @@ var QueryStorage = class {
319
390
  }
320
391
  this.enforceMaxSize();
321
392
  }
393
+ this.trie.insert(entry.key, key);
322
394
  }
323
395
  delete(key) {
324
396
  const entry = this.signals.get(key)?.get();
@@ -331,6 +403,9 @@ var QueryStorage = class {
331
403
  }
332
404
  }
333
405
  }
406
+ if (entry?.key) {
407
+ this.trie.remove(entry.key, key);
408
+ }
334
409
  this.signals.delete(key);
335
410
  this.lruOrder.delete(key);
336
411
  this.cancelGC(key);
@@ -355,6 +430,7 @@ var QueryStorage = class {
355
430
  clear() {
356
431
  this.signals.clear();
357
432
  this.tagIndex.clear();
433
+ this.trie = new QueryKeyTrie();
358
434
  this.lruOrder.clear();
359
435
  this.gcTimers.forEach((timer2) => clearTimeout(timer2));
360
436
  this.gcTimers.clear();
@@ -907,11 +983,26 @@ var QueryClient = class {
907
983
  });
908
984
  this.pluginManager.onFetchStart(normalizedKey);
909
985
  try {
910
- const data = await this.remotes.fetch(key, fn, {
986
+ const fetchPromise = this.remotes.fetch(key, fn, {
911
987
  signal: options?.signal,
912
988
  retry: options?.retry,
913
989
  retryDelay: options?.retryDelay
914
990
  });
991
+ this.storage.set(key, {
992
+ data: currentEntry?.data,
993
+ status: currentEntry?.status || "pending",
994
+ error: null,
995
+ isFetching: true,
996
+ fetchDirection: direction,
997
+ timestamp: currentEntry?.timestamp || Date.now(),
998
+ staleTime: currentEntry?.staleTime ?? this.defaultStaleTime,
999
+ cacheTime: currentEntry?.cacheTime ?? this.defaultCacheTime,
1000
+ key: queryKey,
1001
+ tags: mergedTags,
1002
+ promise: fetchPromise
1003
+ // Store for Suspense
1004
+ });
1005
+ const data = await fetchPromise;
915
1006
  this.storage.set(key, {
916
1007
  data,
917
1008
  status: "success",
@@ -924,7 +1015,9 @@ var QueryClient = class {
924
1015
  staleTime: currentEntry?.staleTime ?? this.defaultStaleTime,
925
1016
  cacheTime: currentEntry?.cacheTime ?? this.defaultCacheTime,
926
1017
  key: queryKey,
927
- tags: mergedTags
1018
+ tags: mergedTags,
1019
+ promise: void 0
1020
+ // Clear promise
928
1021
  });
929
1022
  const schema = options?.schema || this.defaultSchema;
930
1023
  const validatedData = validateWithSchema(data, schema);
@@ -942,7 +1035,9 @@ var QueryClient = class {
942
1035
  staleTime: currentEntry?.staleTime ?? this.defaultStaleTime,
943
1036
  cacheTime: currentEntry?.cacheTime ?? this.defaultCacheTime,
944
1037
  key: queryKey,
945
- tags: mergedTags
1038
+ tags: mergedTags,
1039
+ promise: void 0
1040
+ // Clear promise
946
1041
  });
947
1042
  this.pluginManager.onFetchError(normalizedKey, err);
948
1043
  throw err;
@@ -952,18 +1047,15 @@ var QueryClient = class {
952
1047
  * Invalidate queries
953
1048
  */
954
1049
  invalidate = (queryKey) => {
955
- const prefix = this.storage.generateKey(queryKey);
956
1050
  const normalizedKey = this.normalizeKey(queryKey);
957
1051
  this.pluginManager.onInvalidate(normalizedKey);
958
- const allKeys = this.storage.getSnapshot().keys();
959
- for (const key of allKeys) {
960
- if (key === prefix || key.startsWith(prefix.slice(0, -1))) {
961
- const signal2 = this.storage.get(key, false);
962
- if (signal2) {
963
- const current = signal2.get();
964
- if (current) {
965
- signal2.set({ ...current, isInvalidated: true });
966
- }
1052
+ const matchingKeys = this.storage.trie.getMatchingKeys(queryKey);
1053
+ for (const key of matchingKeys) {
1054
+ const signal2 = this.storage.get(key, false);
1055
+ if (signal2) {
1056
+ const current = signal2.get();
1057
+ if (current) {
1058
+ signal2.set({ ...current, isInvalidated: true });
967
1059
  }
968
1060
  }
969
1061
  }
@@ -1056,13 +1148,13 @@ var QueryClient = class {
1056
1148
  };
1057
1149
 
1058
1150
  // src/query/context.tsx
1059
- import { jsx as jsx2 } from "react/jsx-runtime";
1151
+ import { jsx as jsx3 } from "react/jsx-runtime";
1060
1152
  var QueryClientContext = createContext(void 0);
1061
1153
  var QueryClientProvider = ({
1062
1154
  client,
1063
1155
  children
1064
1156
  }) => {
1065
- return /* @__PURE__ */ jsx2(QueryClientContext.Provider, { value: client, children: children || null });
1157
+ return /* @__PURE__ */ jsx3(QueryClientContext.Provider, { value: client, children: children || null });
1066
1158
  };
1067
1159
  var useQueryClient = () => {
1068
1160
  const client = useContext(QueryClientContext);
@@ -1086,7 +1178,7 @@ function useQueryStore() {
1086
1178
  }
1087
1179
 
1088
1180
  // src/devtools/QueryPanel.tsx
1089
- import { jsx as jsx3, jsxs } from "react/jsx-runtime";
1181
+ import { jsx as jsx4, jsxs } from "react/jsx-runtime";
1090
1182
  function QueryPanel() {
1091
1183
  const cache = useQueryStore();
1092
1184
  const client = useQueryClient();
@@ -1111,7 +1203,7 @@ function QueryPanel() {
1111
1203
  display: "flex",
1112
1204
  gap: "8px"
1113
1205
  }, children: [
1114
- /* @__PURE__ */ jsx3(
1206
+ /* @__PURE__ */ jsx4(
1115
1207
  "input",
1116
1208
  {
1117
1209
  type: "text",
@@ -1130,7 +1222,7 @@ function QueryPanel() {
1130
1222
  }
1131
1223
  }
1132
1224
  ),
1133
- /* @__PURE__ */ jsx3(
1225
+ /* @__PURE__ */ jsx4(
1134
1226
  "button",
1135
1227
  {
1136
1228
  onClick: () => client.invalidateAll(),
@@ -1147,7 +1239,7 @@ function QueryPanel() {
1147
1239
  }
1148
1240
  )
1149
1241
  ] }),
1150
- /* @__PURE__ */ jsx3("div", { style: {
1242
+ /* @__PURE__ */ jsx4("div", { style: {
1151
1243
  flex: 1,
1152
1244
  overflowY: "auto",
1153
1245
  padding: "8px",
@@ -1155,7 +1247,7 @@ function QueryPanel() {
1155
1247
  flexDirection: "column",
1156
1248
  gap: "8px",
1157
1249
  background: "#050505"
1158
- }, children: entries.length === 0 ? /* @__PURE__ */ jsx3("div", { style: { padding: "20px", textAlign: "center", color: "#444", fontSize: "12px" }, children: "No active queries." }) : filteredEntries.map(([keyHash, entry]) => /* @__PURE__ */ jsx3(
1250
+ }, children: entries.length === 0 ? /* @__PURE__ */ jsx4("div", { style: { padding: "20px", textAlign: "center", color: "#444", fontSize: "12px" }, children: "No active queries." }) : filteredEntries.map(([keyHash, entry]) => /* @__PURE__ */ jsx4(
1159
1251
  QueryItem,
1160
1252
  {
1161
1253
  entry,
@@ -1188,12 +1280,12 @@ function QueryItem({ entry, client, isStale }) {
1188
1280
  },
1189
1281
  children: [
1190
1282
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "8px", alignItems: "center", overflow: "hidden" }, children: [
1191
- /* @__PURE__ */ jsx3("span", { style: {
1283
+ /* @__PURE__ */ jsx4("span", { style: {
1192
1284
  color: isStale ? "#d69e2e" : "#b0fb5d",
1193
1285
  fontSize: "12px",
1194
1286
  fontWeight: "bold"
1195
1287
  }, children: "\u2022" }),
1196
- /* @__PURE__ */ jsx3("span", { style: {
1288
+ /* @__PURE__ */ jsx4("span", { style: {
1197
1289
  color: "#e0e0e0",
1198
1290
  fontSize: "12px",
1199
1291
  whiteSpace: "nowrap",
@@ -1201,7 +1293,7 @@ function QueryItem({ entry, client, isStale }) {
1201
1293
  textOverflow: "ellipsis"
1202
1294
  }, children: JSON.stringify(entry.key) })
1203
1295
  ] }),
1204
- /* @__PURE__ */ jsx3("span", { style: {
1296
+ /* @__PURE__ */ jsx4("span", { style: {
1205
1297
  fontSize: "9px",
1206
1298
  color: isStale ? "#d69e2e" : "#b0fb5d",
1207
1299
  padding: "1px 4px",
@@ -1218,7 +1310,7 @@ function QueryItem({ entry, client, isStale }) {
1218
1310
  background: "#0a0a0a"
1219
1311
  }, children: [
1220
1312
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "8px", marginBottom: "8px" }, children: [
1221
- /* @__PURE__ */ jsx3(
1313
+ /* @__PURE__ */ jsx4(
1222
1314
  "button",
1223
1315
  {
1224
1316
  onClick: (e) => {
@@ -1229,7 +1321,7 @@ function QueryItem({ entry, client, isStale }) {
1229
1321
  children: "Invalidate"
1230
1322
  }
1231
1323
  ),
1232
- /* @__PURE__ */ jsx3(
1324
+ /* @__PURE__ */ jsx4(
1233
1325
  "button",
1234
1326
  {
1235
1327
  onClick: (e) => {
@@ -1241,13 +1333,13 @@ function QueryItem({ entry, client, isStale }) {
1241
1333
  }
1242
1334
  )
1243
1335
  ] }),
1244
- /* @__PURE__ */ jsx3("pre", { style: { margin: 0, fontSize: "10px", color: "#a0a0a0", overflowX: "auto", fontFamily: "monospace" }, children: JSON.stringify(entry.data, null, 2) })
1336
+ /* @__PURE__ */ jsx4("pre", { style: { margin: 0, fontSize: "10px", color: "#a0a0a0", overflowX: "auto", fontFamily: "monospace" }, children: JSON.stringify(entry.data, null, 2) })
1245
1337
  ] })
1246
1338
  ] });
1247
1339
  }
1248
1340
 
1249
1341
  // src/devtools/index.tsx
1250
- import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
1342
+ import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
1251
1343
  function QuantumDevTools() {
1252
1344
  const [isOpen, setIsOpen] = useState4(false);
1253
1345
  const [activeTab, setActiveTab] = useState4("queries");
@@ -1271,7 +1363,7 @@ function QuantumDevTools() {
1271
1363
  };
1272
1364
  }, []);
1273
1365
  if (!isOpen) {
1274
- return /* @__PURE__ */ jsx4(
1366
+ return /* @__PURE__ */ jsx5(
1275
1367
  "button",
1276
1368
  {
1277
1369
  onClick: () => setIsOpen(true),
@@ -1309,7 +1401,7 @@ function QuantumDevTools() {
1309
1401
  flexDirection: "column",
1310
1402
  fontFamily: "monospace"
1311
1403
  }, children: [
1312
- /* @__PURE__ */ jsx4(
1404
+ /* @__PURE__ */ jsx5(
1313
1405
  "div",
1314
1406
  {
1315
1407
  onMouseDown: () => {
@@ -1328,19 +1420,19 @@ function QuantumDevTools() {
1328
1420
  borderBottom: "1px solid #222"
1329
1421
  }, children: [
1330
1422
  /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: "16px", alignItems: "center" }, children: [
1331
- /* @__PURE__ */ jsx4("span", { style: { color: "#b0fb5d", fontWeight: "bold" }, children: "Quantum DevTools" }),
1423
+ /* @__PURE__ */ jsx5("span", { style: { color: "#b0fb5d", fontWeight: "bold" }, children: "Quantum DevTools" }),
1332
1424
  /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: "4px", background: "#000", padding: "2px", borderRadius: "4px" }, children: [
1333
- /* @__PURE__ */ jsx4(TabButton, { active: activeTab === "queries", onClick: () => setActiveTab("queries"), children: "Queries" }),
1334
- /* @__PURE__ */ jsx4(TabButton, { active: activeTab === "state", onClick: () => setActiveTab("state"), children: "State" })
1425
+ /* @__PURE__ */ jsx5(TabButton, { active: activeTab === "queries", onClick: () => setActiveTab("queries"), children: "Queries" }),
1426
+ /* @__PURE__ */ jsx5(TabButton, { active: activeTab === "state", onClick: () => setActiveTab("state"), children: "State" })
1335
1427
  ] })
1336
1428
  ] }),
1337
- /* @__PURE__ */ jsx4("button", { onClick: () => setIsOpen(false), style: { background: "none", border: "none", color: "#666", cursor: "pointer" }, children: "\xD7" })
1429
+ /* @__PURE__ */ jsx5("button", { onClick: () => setIsOpen(false), style: { background: "none", border: "none", color: "#666", cursor: "pointer" }, children: "\xD7" })
1338
1430
  ] }),
1339
- /* @__PURE__ */ jsx4("div", { style: { flex: 1, overflow: "hidden" }, children: activeTab === "queries" ? /* @__PURE__ */ jsx4(QueryPanel, {}) : /* @__PURE__ */ jsx4("div", { children: "State Panel (Under Construction)" }) })
1431
+ /* @__PURE__ */ jsx5("div", { style: { flex: 1, overflow: "hidden" }, children: activeTab === "queries" ? /* @__PURE__ */ jsx5(QueryPanel, {}) : /* @__PURE__ */ jsx5("div", { children: "State Panel (Under Construction)" }) })
1340
1432
  ] });
1341
1433
  }
1342
1434
  function TabButton({ active, children, onClick }) {
1343
- return /* @__PURE__ */ jsx4(
1435
+ return /* @__PURE__ */ jsx5(
1344
1436
  "button",
1345
1437
  {
1346
1438
  onClick,
@@ -1506,6 +1598,8 @@ var RetryMiddleware = async (ctx, next) => {
1506
1598
  let config;
1507
1599
  if (typeof retryConfigRaw === "number") {
1508
1600
  config = { retries: retryConfigRaw };
1601
+ } else if (typeof retryConfigRaw === "boolean") {
1602
+ config = retryConfigRaw ? { retries: 3 } : { retries: 0 };
1509
1603
  } else if (typeof retryConfigRaw === "object" && retryConfigRaw !== null) {
1510
1604
  config = retryConfigRaw;
1511
1605
  } else {
@@ -1932,7 +2026,7 @@ var QueryObserver = class {
1932
2026
  status: selectorError ? "error" : status,
1933
2027
  refetch: this.refetch
1934
2028
  };
1935
- const isDataEqual = lastResult?.data === nextResult.data || isDeepEqual(lastResult?.data, nextResult.data);
2029
+ const isDataEqual = lastResult?.data === nextResult.data;
1936
2030
  if (lastResult && isDataEqual && lastResult.status === nextResult.status && lastResult.isFetching === nextResult.isFetching && lastResult.isStale === nextResult.isStale && lastResult.error === nextResult.error) {
1937
2031
  return lastResult;
1938
2032
  }
@@ -2076,6 +2170,33 @@ function useQuery(options) {
2076
2170
  };
2077
2171
  }
2078
2172
 
2173
+ // src/query/useSuspenseQuery.ts
2174
+ function useSuspenseQuery(options) {
2175
+ const client = useQueryClient();
2176
+ const signal2 = client.getSignal(options.queryKey);
2177
+ const entry = signal2.get();
2178
+ if (entry?.status === "error") {
2179
+ throw entry.error;
2180
+ }
2181
+ if (!entry || entry.status === "pending" && entry.data === void 0) {
2182
+ if (entry?.promise) {
2183
+ throw entry.promise;
2184
+ }
2185
+ const fetchPromise = client.fetch(options.queryKey, options.queryFn, {
2186
+ retry: options.retry,
2187
+ retryDelay: options.retryDelay,
2188
+ tags: options.tags,
2189
+ schema: options.schema
2190
+ });
2191
+ throw fetchPromise;
2192
+ }
2193
+ const query = useQuery(options);
2194
+ return {
2195
+ ...query,
2196
+ data: query.data
2197
+ };
2198
+ }
2199
+
2079
2200
  // src/query/useMutation.ts
2080
2201
  import { useCallback as useCallback3, useEffect as useEffect6, useSyncExternalStore as useSyncExternalStore3, useState as useState7 } from "react";
2081
2202
 
@@ -2241,7 +2362,7 @@ var InfiniteQueryObserver = class {
2241
2362
  options$;
2242
2363
  result$;
2243
2364
  unsubscribe = null;
2244
- lastFetchTime = 0;
2365
+ abortController = null;
2245
2366
  constructor(client, options) {
2246
2367
  this.client = client;
2247
2368
  this.options$ = createSignal(options);
@@ -2294,8 +2415,7 @@ var InfiniteQueryObserver = class {
2294
2415
  fetchPreviousPage: this.fetchPreviousPage,
2295
2416
  refetch: this.refetch
2296
2417
  };
2297
- const isDataEqual = lastResult?.data === nextResult.data || isDeepEqual(lastResult?.data, nextResult.data);
2298
- if (lastResult && isDataEqual && lastResult.isFetching === nextResult.isFetching && lastResult.status === nextResult.status && lastResult.hasNextPage === nextResult.hasNextPage && lastResult.hasPreviousPage === nextResult.hasPreviousPage) {
2418
+ if (lastResult && lastResult.data === nextResult.data && lastResult.isFetching === nextResult.isFetching && lastResult.status === nextResult.status && lastResult.hasNextPage === nextResult.hasNextPage && lastResult.hasPreviousPage === nextResult.hasPreviousPage && lastResult.error === nextResult.error) {
2299
2419
  return lastResult;
2300
2420
  }
2301
2421
  lastResult = nextResult;
@@ -2307,8 +2427,8 @@ var InfiniteQueryObserver = class {
2307
2427
  const current = this.options$.get();
2308
2428
  if (current === options) return;
2309
2429
  const isKeyEqual = stableHash(current.queryKey) === stableHash(options.queryKey);
2310
- const isConfigEqual = current.enabled === options.enabled && current.staleTime === options.staleTime;
2311
- if (!isKeyEqual || !isConfigEqual || current.getNextPageParam !== options.getNextPageParam || current.getPreviousPageParam !== options.getPreviousPageParam) {
2430
+ const isConfigEqual = current.enabled === options.enabled && current.staleTime === options.staleTime && current.retry === options.retry;
2431
+ if (!isKeyEqual || !isConfigEqual) {
2312
2432
  this.options$.set(options);
2313
2433
  }
2314
2434
  }
@@ -2341,25 +2461,37 @@ var InfiniteQueryObserver = class {
2341
2461
  }
2342
2462
  });
2343
2463
  this.unsubscribe = () => {
2464
+ this.cancel();
2344
2465
  dispose();
2345
2466
  disposeFocus();
2346
2467
  disposeOnline();
2347
2468
  };
2348
2469
  }
2470
+ cancel() {
2471
+ if (this.abortController) {
2472
+ this.abortController.abort();
2473
+ this.abortController = null;
2474
+ }
2475
+ }
2349
2476
  fetchInitial = async (options) => {
2477
+ this.cancel();
2478
+ this.abortController = new AbortController();
2479
+ const signal2 = this.abortController.signal;
2350
2480
  const opts = this.options$.get();
2351
2481
  const infiniteKey = [...opts.queryKey, "__infinite__"];
2352
2482
  try {
2353
2483
  const entrySignal = this.client.getSignal(infiniteKey);
2354
- const data = entrySignal.get()?.data;
2355
- if (data && data.pageParams.length > 0 && !options?.force) {
2356
- const firstParam2 = data.pageParams[0];
2484
+ const currentState = entrySignal.get()?.data;
2485
+ if (currentState && currentState.pageParams.length > 0 && !options?.force) {
2486
+ const firstParam2 = currentState.pageParams[0];
2357
2487
  const firstPage = await opts.queryFn({ pageParam: firstParam2 });
2488
+ if (signal2.aborted) return;
2358
2489
  const latest = entrySignal.get()?.data;
2359
2490
  if (!latest) return;
2360
2491
  const updatedData = {
2361
2492
  ...latest,
2362
2493
  pages: [firstPage, ...latest.pages.slice(1)]
2494
+ // pageParams remain the same
2363
2495
  };
2364
2496
  this.client.set(infiniteKey, updatedData, {
2365
2497
  staleTime: opts.staleTime,
@@ -2369,107 +2501,116 @@ var InfiniteQueryObserver = class {
2369
2501
  }
2370
2502
  const initialParam = opts.initialPageParam;
2371
2503
  const firstParam = initialParam !== void 0 ? initialParam : 0;
2372
- const initialData = await this.client.fetch(infiniteKey, async () => {
2504
+ const initialData = await this.client.fetch(infiniteKey, async (ctx) => {
2373
2505
  const firstPage = await opts.queryFn({ pageParam: firstParam });
2374
2506
  return {
2375
2507
  pages: [firstPage],
2376
2508
  pageParams: [firstParam]
2377
2509
  };
2378
- }, { fetchDirection: "initial", retry: opts.retry });
2379
- this.client.set(infiniteKey, initialData, {
2380
- staleTime: opts.staleTime,
2381
- cacheTime: opts.cacheTime
2510
+ }, {
2511
+ fetchDirection: "initial",
2512
+ retry: opts.retry,
2513
+ signal: signal2
2382
2514
  });
2515
+ if (!signal2.aborted) {
2516
+ this.client.set(infiniteKey, initialData, {
2517
+ staleTime: opts.staleTime,
2518
+ cacheTime: opts.cacheTime
2519
+ });
2520
+ }
2383
2521
  } catch (err) {
2384
- getLogger().error("Initial fetch failed", err);
2522
+ if (!signal2.aborted) {
2523
+ getLogger().error("Initial fetch failed", err);
2524
+ }
2385
2525
  }
2386
2526
  };
2387
2527
  fetchNextPage = async () => {
2388
2528
  const res = this.result$.get();
2389
- const opts = this.options$.get();
2390
2529
  if (!res.hasNextPage || res.isFetching || !res.data) return;
2530
+ this.cancel();
2531
+ this.abortController = new AbortController();
2532
+ const signal2 = this.abortController.signal;
2533
+ const opts = this.options$.get();
2391
2534
  const infiniteKey = [...opts.queryKey, "__infinite__"];
2392
2535
  const lastPage = res.data.pages[res.data.pages.length - 1];
2393
- if (!lastPage) {
2394
- return;
2395
- }
2536
+ if (!lastPage) return;
2396
2537
  const nextPageParam = opts.getNextPageParam?.(lastPage, res.data.pages);
2397
2538
  if (nextPageParam === void 0) return;
2398
2539
  try {
2399
- const updatedData = await this.client.fetch(infiniteKey, async () => {
2540
+ const updatedData = await this.client.fetch(infiniteKey, async (ctx) => {
2400
2541
  const newPage = await opts.queryFn({ pageParam: nextPageParam });
2542
+ if (ctx.signal?.aborted) throw new Error("Aborted");
2401
2543
  const currentData = this.client.getSignal(infiniteKey).get()?.data;
2402
2544
  if (!currentData) throw new Error("Infinite query data missing");
2403
- const updatedParams = [...currentData.pageParams, nextPageParam];
2404
- const nextCursor = opts.getNextPageParam?.(newPage, [...currentData.pages, newPage]);
2405
- if (nextCursor !== void 0) {
2406
- updatedParams.push(nextCursor);
2407
- }
2408
2545
  return {
2409
2546
  pages: [...currentData.pages, newPage],
2410
- pageParams: updatedParams
2547
+ pageParams: [...currentData.pageParams, nextPageParam]
2411
2548
  };
2412
- }, { fetchDirection: "next", retry: opts.retry });
2413
- this.client.set(infiniteKey, updatedData, {
2414
- staleTime: opts.staleTime,
2415
- cacheTime: opts.cacheTime
2549
+ }, {
2550
+ fetchDirection: "next",
2551
+ retry: opts.retry,
2552
+ signal: signal2
2416
2553
  });
2554
+ if (!signal2.aborted) {
2555
+ this.client.set(infiniteKey, updatedData, {
2556
+ staleTime: opts.staleTime,
2557
+ cacheTime: opts.cacheTime
2558
+ });
2559
+ }
2417
2560
  } catch (err) {
2418
- getLogger().error("Fetch next page failed", err);
2561
+ if (!signal2.aborted) {
2562
+ getLogger().error("Fetch next page failed", err);
2563
+ }
2419
2564
  }
2420
2565
  };
2421
2566
  fetchPreviousPage = async () => {
2422
2567
  const res = this.result$.get();
2423
- const opts = this.options$.get();
2424
2568
  if (!res.hasPreviousPage || res.isFetching || !res.data) return;
2569
+ this.cancel();
2570
+ this.abortController = new AbortController();
2571
+ const signal2 = this.abortController.signal;
2572
+ const opts = this.options$.get();
2425
2573
  const infiniteKey = [...opts.queryKey, "__infinite__"];
2426
2574
  const firstPage = res.data.pages[0];
2427
- if (!firstPage) {
2428
- return;
2429
- }
2575
+ if (!firstPage) return;
2430
2576
  const previousPageParam = opts.getPreviousPageParam?.(firstPage, res.data.pages);
2431
2577
  if (previousPageParam === void 0) return;
2432
2578
  try {
2433
- const updatedData = await this.client.fetch(infiniteKey, async () => {
2579
+ const updatedData = await this.client.fetch(infiniteKey, async (ctx) => {
2434
2580
  const newPage = await opts.queryFn({ pageParam: previousPageParam });
2581
+ if (ctx.signal?.aborted) throw new Error("Aborted");
2435
2582
  const currentData = this.client.getSignal(infiniteKey).get()?.data;
2436
2583
  if (!currentData) throw new Error("Infinite query data missing");
2437
2584
  return {
2438
2585
  pages: [newPage, ...currentData.pages],
2439
2586
  pageParams: [previousPageParam, ...currentData.pageParams]
2440
2587
  };
2441
- }, { fetchDirection: "previous", retry: opts.retry });
2442
- this.client.set(infiniteKey, updatedData, {
2443
- staleTime: opts.staleTime,
2444
- cacheTime: opts.cacheTime
2588
+ }, {
2589
+ fetchDirection: "previous",
2590
+ retry: opts.retry,
2591
+ signal: signal2
2445
2592
  });
2593
+ if (!signal2.aborted) {
2594
+ this.client.set(infiniteKey, updatedData, {
2595
+ staleTime: opts.staleTime,
2596
+ cacheTime: opts.cacheTime
2597
+ });
2598
+ }
2446
2599
  } catch (err) {
2447
- getLogger().error("Fetch previous page failed", err);
2600
+ if (!signal2.aborted) {
2601
+ getLogger().error("Fetch previous page failed", err);
2602
+ }
2448
2603
  }
2449
2604
  };
2450
2605
  refetch = async () => {
2606
+ this.cancel();
2451
2607
  const opts = this.options$.get();
2452
2608
  const infiniteKey = [...opts.queryKey, "__infinite__"];
2453
2609
  this.client.invalidate(infiniteKey);
2454
- const initialParam = opts.initialPageParam;
2455
- const firstParam = initialParam !== void 0 ? initialParam : 0;
2456
- try {
2457
- const initialData = await this.client.fetch(infiniteKey, async () => {
2458
- const firstPage = await opts.queryFn({ pageParam: firstParam });
2459
- return {
2460
- pages: [firstPage],
2461
- pageParams: [firstParam]
2462
- };
2463
- }, { fetchDirection: "initial", retry: opts.retry });
2464
- this.client.set(infiniteKey, initialData, {
2465
- staleTime: opts.staleTime,
2466
- cacheTime: opts.cacheTime
2467
- });
2468
- } catch (err) {
2469
- getLogger().error("Refetch failed", err);
2470
- }
2610
+ await this.fetchInitial({ force: true });
2471
2611
  };
2472
2612
  destroy() {
2613
+ this.cancel();
2473
2614
  if (this.unsubscribe) this.unsubscribe();
2474
2615
  }
2475
2616
  };
@@ -2521,7 +2662,7 @@ function hydrate(client, state) {
2521
2662
  }
2522
2663
 
2523
2664
  // src/query/HydrationBoundary.tsx
2524
- import { Fragment as Fragment2, jsx as jsx5 } from "react/jsx-runtime";
2665
+ import { Fragment as Fragment2, jsx as jsx6 } from "react/jsx-runtime";
2525
2666
  function HydrationBoundary({ state, children }) {
2526
2667
  const client = useQueryClient();
2527
2668
  const hydratedRef = useRef4(false);
@@ -2529,34 +2670,7 @@ function HydrationBoundary({ state, children }) {
2529
2670
  hydrate(client, state);
2530
2671
  hydratedRef.current = true;
2531
2672
  }
2532
- return /* @__PURE__ */ jsx5(Fragment2, { children });
2533
- }
2534
-
2535
- // src/query/useSuspenseQuery.ts
2536
- function useSuspenseQuery(options) {
2537
- const client = useQueryClient();
2538
- const signal2 = client.getSignal(options.queryKey);
2539
- const entry = signal2.get();
2540
- const shouldSuspend = !entry || entry.status === "pending" && entry.data === void 0;
2541
- if (shouldSuspend) {
2542
- const fetchPromise = client.fetch(
2543
- options.queryKey,
2544
- (ctx) => options.queryFn({ ...ctx, signal: void 0 }),
2545
- { signal: void 0 }
2546
- ).then((data) => {
2547
- client.set(options.queryKey, data);
2548
- return data;
2549
- });
2550
- throw fetchPromise;
2551
- }
2552
- if (entry?.status === "error") {
2553
- throw entry.error;
2554
- }
2555
- const query = useQuery(options);
2556
- return {
2557
- ...query,
2558
- data: query.data
2559
- };
2673
+ return /* @__PURE__ */ jsx6(Fragment2, { children });
2560
2674
  }
2561
2675
 
2562
2676
  // src/query/useQuerySignal.ts
@@ -2638,9 +2752,12 @@ function useCombinedQueries(queries, client) {
2638
2752
  }
2639
2753
  export {
2640
2754
  HydrationBoundary,
2755
+ Match,
2756
+ MutationCache,
2641
2757
  QuantumDevTools,
2642
2758
  QueryClient,
2643
2759
  QueryClientProvider,
2760
+ QueryMatch,
2644
2761
  SignalValue,
2645
2762
  atom,
2646
2763
  createHttpClient,