@tanstack/solid-query 6.0.0-rc.3 → 6.0.0-rc.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.
@@ -321,12 +321,11 @@ export { FetchStatus }
321
321
  *
322
322
  * A mutation can additionally declare its invalidation SCOPE with
323
323
  * `X-Revalidate` keys (the `revalidate` option of Solid's `reload`/
324
- * `redirect` response helpers). The payload covers the slice of that scope
325
- * the server could recompute; whatever a declared key matches beyond it is
326
- * swept client-side (see the consumer below). For the sweep to be
327
- * delivered, the collector must contribute a slice — return the dehydrated
328
- * state even when it holds no queries if `outcome.revalidateKeys` is
329
- * present, rather than skipping the source.
324
+ * `redirect`/`respond` response helpers). The payload covers the slice of
325
+ * that scope the server could recompute; whatever a declared key matches
326
+ * beyond it is swept client-side (see the consumer below). The transport
327
+ * delivers a response carrying keys to the consumer whether or not the
328
+ * collector contributed a slice — with no slice, the whole scope is swept.
330
329
  */
331
330
  declare const FLIGHT_DATA_SOURCE = "sq";
332
331
  export { FLIGHT_DATA_SOURCE }
@@ -321,12 +321,11 @@ export { FetchStatus }
321
321
  *
322
322
  * A mutation can additionally declare its invalidation SCOPE with
323
323
  * `X-Revalidate` keys (the `revalidate` option of Solid's `reload`/
324
- * `redirect` response helpers). The payload covers the slice of that scope
325
- * the server could recompute; whatever a declared key matches beyond it is
326
- * swept client-side (see the consumer below). For the sweep to be
327
- * delivered, the collector must contribute a slice — return the dehydrated
328
- * state even when it holds no queries if `outcome.revalidateKeys` is
329
- * present, rather than skipping the source.
324
+ * `redirect`/`respond` response helpers). The payload covers the slice of
325
+ * that scope the server could recompute; whatever a declared key matches
326
+ * beyond it is swept client-side (see the consumer below). The transport
327
+ * delivers a response carrying keys to the consumer whether or not the
328
+ * collector contributed a slice — with no slice, the whole scope is swept.
330
329
  */
331
330
  declare const FLIGHT_DATA_SOURCE = "sq";
332
331
  export { FLIGHT_DATA_SOURCE }
package/build/dev.cjs CHANGED
@@ -14,14 +14,24 @@ exports.QueryClient = class QueryClient extends queryCore.QueryClient {
14
14
  var isServer = typeof window === "undefined";
15
15
  var HYDRATION_KEY_PREFIX = "sq:";
16
16
  exports.FLIGHT_DATA_SOURCE = "sq";
17
+ var REVALIDATE_ALL = "*";
17
18
  function sweepRevalidatedQueries(client, payload, response) {
18
- const keys = response.headers.get(web.REVALIDATE_HEADER)?.split(",");
19
- if (!keys) return;
20
- const covered = new Set(payload.queries.map((query) => query.queryHash));
19
+ const declared = response.headers.get(web.REVALIDATE_HEADER);
20
+ if (declared === null) return;
21
+ const covered = new Set(payload?.queries.map((query) => query.queryHash) ?? []);
22
+ const uncovered = (query) => !covered.has(query.queryHash);
23
+ const keys = declared.split(",");
24
+ if (keys.includes(REVALIDATE_ALL)) {
25
+ client.invalidateQueries({
26
+ predicate: uncovered
27
+ }).catch(() => void 0);
28
+ return;
29
+ }
21
30
  for (const key of keys) {
31
+ if (!key) continue;
22
32
  client.invalidateQueries({
23
33
  queryKey: [key],
24
- predicate: (query) => !covered.has(query.queryHash)
34
+ predicate: uncovered
25
35
  }).catch(() => void 0);
26
36
  }
27
37
  }
@@ -146,6 +156,7 @@ function useBaseQueryLayer(options, Observer, queryClient) {
146
156
  let disposed = false;
147
157
  let shouldAttach = false;
148
158
  let activeClient = solidJs.untrack(client);
159
+ let lastQuery;
149
160
  const attach = () => {
150
161
  if (!disposed && !observerSub && !solidJs.untrack(isRestoring)) {
151
162
  shouldAttach = true;
@@ -155,6 +166,7 @@ function useBaseQueryLayer(options, Observer, queryClient) {
155
166
  const syncClient = (c) => {
156
167
  if (isServer2 || c === activeClient) return;
157
168
  activeClient = c;
169
+ lastQuery = void 0;
158
170
  cacheSub?.();
159
171
  cacheSub = c.getQueryCache().subscribe(onCacheEvent);
160
172
  observerSub?.();
@@ -246,7 +258,14 @@ function useBaseQueryLayer(options, Observer, queryClient) {
246
258
  version();
247
259
  const c = client();
248
260
  syncClient(c);
249
- return c.getQueryCache().build(c, defaultedOptions());
261
+ const cache = c.getQueryCache();
262
+ const opts = defaultedOptions();
263
+ const existing = cache.get(
264
+ opts.queryHash
265
+ );
266
+ if (existing) return lastQuery = existing;
267
+ if (lastQuery?.queryHash === opts.queryHash) return lastQuery;
268
+ return lastQuery = cache.build(c, opts);
250
269
  };
251
270
  const isEnabled = () => {
252
271
  const opts = defaultedOptions();
@@ -305,6 +324,7 @@ function useBaseQueryLayer(options, Observer, queryClient) {
305
324
  if (!isServer2) observer.setOptions(opts);
306
325
  return chainOnce(q.fetch(opts), select, wrap);
307
326
  }
327
+ if (isServer2) return { value: void 0 };
308
328
  return NEVER;
309
329
  };
310
330
  const dataStore = solidJs.createProjection(
package/build/dev.js CHANGED
@@ -13,14 +13,24 @@ var QueryClient = class extends QueryClient$1 {
13
13
  var isServer = typeof window === "undefined";
14
14
  var HYDRATION_KEY_PREFIX = "sq:";
15
15
  var FLIGHT_DATA_SOURCE = "sq";
16
+ var REVALIDATE_ALL = "*";
16
17
  function sweepRevalidatedQueries(client, payload, response) {
17
- const keys = response.headers.get(REVALIDATE_HEADER)?.split(",");
18
- if (!keys) return;
19
- const covered = new Set(payload.queries.map((query) => query.queryHash));
18
+ const declared = response.headers.get(REVALIDATE_HEADER);
19
+ if (declared === null) return;
20
+ const covered = new Set(payload?.queries.map((query) => query.queryHash) ?? []);
21
+ const uncovered = (query) => !covered.has(query.queryHash);
22
+ const keys = declared.split(",");
23
+ if (keys.includes(REVALIDATE_ALL)) {
24
+ client.invalidateQueries({
25
+ predicate: uncovered
26
+ }).catch(() => void 0);
27
+ return;
28
+ }
20
29
  for (const key of keys) {
30
+ if (!key) continue;
21
31
  client.invalidateQueries({
22
32
  queryKey: [key],
23
- predicate: (query) => !covered.has(query.queryHash)
33
+ predicate: uncovered
24
34
  }).catch(() => void 0);
25
35
  }
26
36
  }
@@ -145,6 +155,7 @@ function useBaseQueryLayer(options, Observer, queryClient) {
145
155
  let disposed = false;
146
156
  let shouldAttach = false;
147
157
  let activeClient = untrack(client);
158
+ let lastQuery;
148
159
  const attach = () => {
149
160
  if (!disposed && !observerSub && !untrack(isRestoring)) {
150
161
  shouldAttach = true;
@@ -154,6 +165,7 @@ function useBaseQueryLayer(options, Observer, queryClient) {
154
165
  const syncClient = (c) => {
155
166
  if (isServer2 || c === activeClient) return;
156
167
  activeClient = c;
168
+ lastQuery = void 0;
157
169
  cacheSub?.();
158
170
  cacheSub = c.getQueryCache().subscribe(onCacheEvent);
159
171
  observerSub?.();
@@ -245,7 +257,14 @@ function useBaseQueryLayer(options, Observer, queryClient) {
245
257
  version();
246
258
  const c = client();
247
259
  syncClient(c);
248
- return c.getQueryCache().build(c, defaultedOptions());
260
+ const cache = c.getQueryCache();
261
+ const opts = defaultedOptions();
262
+ const existing = cache.get(
263
+ opts.queryHash
264
+ );
265
+ if (existing) return lastQuery = existing;
266
+ if (lastQuery?.queryHash === opts.queryHash) return lastQuery;
267
+ return lastQuery = cache.build(c, opts);
249
268
  };
250
269
  const isEnabled = () => {
251
270
  const opts = defaultedOptions();
@@ -304,6 +323,7 @@ function useBaseQueryLayer(options, Observer, queryClient) {
304
323
  if (!isServer2) observer.setOptions(opts);
305
324
  return chainOnce(q.fetch(opts), select, wrap);
306
325
  }
326
+ if (isServer2) return { value: void 0 };
307
327
  return NEVER;
308
328
  };
309
329
  const dataStore = createProjection(
package/build/index.cjs CHANGED
@@ -14,14 +14,24 @@ exports.QueryClient = class QueryClient extends queryCore.QueryClient {
14
14
  var isServer = typeof window === "undefined";
15
15
  var HYDRATION_KEY_PREFIX = "sq:";
16
16
  exports.FLIGHT_DATA_SOURCE = "sq";
17
+ var REVALIDATE_ALL = "*";
17
18
  function sweepRevalidatedQueries(client, payload, response) {
18
- const keys = response.headers.get(web.REVALIDATE_HEADER)?.split(",");
19
- if (!keys) return;
20
- const covered = new Set(payload.queries.map((query) => query.queryHash));
19
+ const declared = response.headers.get(web.REVALIDATE_HEADER);
20
+ if (declared === null) return;
21
+ const covered = new Set(payload?.queries.map((query) => query.queryHash) ?? []);
22
+ const uncovered = (query) => !covered.has(query.queryHash);
23
+ const keys = declared.split(",");
24
+ if (keys.includes(REVALIDATE_ALL)) {
25
+ client.invalidateQueries({
26
+ predicate: uncovered
27
+ }).catch(() => void 0);
28
+ return;
29
+ }
21
30
  for (const key of keys) {
31
+ if (!key) continue;
22
32
  client.invalidateQueries({
23
33
  queryKey: [key],
24
- predicate: (query) => !covered.has(query.queryHash)
34
+ predicate: uncovered
25
35
  }).catch(() => void 0);
26
36
  }
27
37
  }
@@ -146,6 +156,7 @@ function useBaseQueryLayer(options, Observer, queryClient) {
146
156
  let disposed = false;
147
157
  let shouldAttach = false;
148
158
  let activeClient = solidJs.untrack(client);
159
+ let lastQuery;
149
160
  const attach = () => {
150
161
  if (!disposed && !observerSub && !solidJs.untrack(isRestoring)) {
151
162
  shouldAttach = true;
@@ -155,6 +166,7 @@ function useBaseQueryLayer(options, Observer, queryClient) {
155
166
  const syncClient = (c) => {
156
167
  if (isServer2 || c === activeClient) return;
157
168
  activeClient = c;
169
+ lastQuery = void 0;
158
170
  cacheSub?.();
159
171
  cacheSub = c.getQueryCache().subscribe(onCacheEvent);
160
172
  observerSub?.();
@@ -246,7 +258,14 @@ function useBaseQueryLayer(options, Observer, queryClient) {
246
258
  version();
247
259
  const c = client();
248
260
  syncClient(c);
249
- return c.getQueryCache().build(c, defaultedOptions());
261
+ const cache = c.getQueryCache();
262
+ const opts = defaultedOptions();
263
+ const existing = cache.get(
264
+ opts.queryHash
265
+ );
266
+ if (existing) return lastQuery = existing;
267
+ if (lastQuery?.queryHash === opts.queryHash) return lastQuery;
268
+ return lastQuery = cache.build(c, opts);
250
269
  };
251
270
  const isEnabled = () => {
252
271
  const opts = defaultedOptions();
@@ -305,6 +324,7 @@ function useBaseQueryLayer(options, Observer, queryClient) {
305
324
  if (!isServer2) observer.setOptions(opts);
306
325
  return chainOnce(q.fetch(opts), select, wrap);
307
326
  }
327
+ if (isServer2) return { value: void 0 };
308
328
  return NEVER;
309
329
  };
310
330
  const dataStore = solidJs.createProjection(
package/build/index.js CHANGED
@@ -13,14 +13,24 @@ var QueryClient = class extends QueryClient$1 {
13
13
  var isServer = typeof window === "undefined";
14
14
  var HYDRATION_KEY_PREFIX = "sq:";
15
15
  var FLIGHT_DATA_SOURCE = "sq";
16
+ var REVALIDATE_ALL = "*";
16
17
  function sweepRevalidatedQueries(client, payload, response) {
17
- const keys = response.headers.get(REVALIDATE_HEADER)?.split(",");
18
- if (!keys) return;
19
- const covered = new Set(payload.queries.map((query) => query.queryHash));
18
+ const declared = response.headers.get(REVALIDATE_HEADER);
19
+ if (declared === null) return;
20
+ const covered = new Set(payload?.queries.map((query) => query.queryHash) ?? []);
21
+ const uncovered = (query) => !covered.has(query.queryHash);
22
+ const keys = declared.split(",");
23
+ if (keys.includes(REVALIDATE_ALL)) {
24
+ client.invalidateQueries({
25
+ predicate: uncovered
26
+ }).catch(() => void 0);
27
+ return;
28
+ }
20
29
  for (const key of keys) {
30
+ if (!key) continue;
21
31
  client.invalidateQueries({
22
32
  queryKey: [key],
23
- predicate: (query) => !covered.has(query.queryHash)
33
+ predicate: uncovered
24
34
  }).catch(() => void 0);
25
35
  }
26
36
  }
@@ -145,6 +155,7 @@ function useBaseQueryLayer(options, Observer, queryClient) {
145
155
  let disposed = false;
146
156
  let shouldAttach = false;
147
157
  let activeClient = untrack(client);
158
+ let lastQuery;
148
159
  const attach = () => {
149
160
  if (!disposed && !observerSub && !untrack(isRestoring)) {
150
161
  shouldAttach = true;
@@ -154,6 +165,7 @@ function useBaseQueryLayer(options, Observer, queryClient) {
154
165
  const syncClient = (c) => {
155
166
  if (isServer2 || c === activeClient) return;
156
167
  activeClient = c;
168
+ lastQuery = void 0;
157
169
  cacheSub?.();
158
170
  cacheSub = c.getQueryCache().subscribe(onCacheEvent);
159
171
  observerSub?.();
@@ -245,7 +257,14 @@ function useBaseQueryLayer(options, Observer, queryClient) {
245
257
  version();
246
258
  const c = client();
247
259
  syncClient(c);
248
- return c.getQueryCache().build(c, defaultedOptions());
260
+ const cache = c.getQueryCache();
261
+ const opts = defaultedOptions();
262
+ const existing = cache.get(
263
+ opts.queryHash
264
+ );
265
+ if (existing) return lastQuery = existing;
266
+ if (lastQuery?.queryHash === opts.queryHash) return lastQuery;
267
+ return lastQuery = cache.build(c, opts);
249
268
  };
250
269
  const isEnabled = () => {
251
270
  const opts = defaultedOptions();
@@ -304,6 +323,7 @@ function useBaseQueryLayer(options, Observer, queryClient) {
304
323
  if (!isServer2) observer.setOptions(opts);
305
324
  return chainOnce(q.fetch(opts), select, wrap);
306
325
  }
326
+ if (isServer2) return { value: void 0 };
307
327
  return NEVER;
308
328
  };
309
329
  const dataStore = createProjection(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/solid-query",
3
- "version": "6.0.0-rc.3",
3
+ "version": "6.0.0-rc.4",
4
4
  "description": "Primitives for managing, caching and syncing asynchronous and remote data in Solid",
5
5
  "author": "tannerlinsley",
6
6
  "license": "MIT",
@@ -39,15 +39,21 @@ export const HYDRATION_KEY_PREFIX = 'sq:'
39
39
  *
40
40
  * A mutation can additionally declare its invalidation SCOPE with
41
41
  * `X-Revalidate` keys (the `revalidate` option of Solid's `reload`/
42
- * `redirect` response helpers). The payload covers the slice of that scope
43
- * the server could recompute; whatever a declared key matches beyond it is
44
- * swept client-side (see the consumer below). For the sweep to be
45
- * delivered, the collector must contribute a slice — return the dehydrated
46
- * state even when it holds no queries if `outcome.revalidateKeys` is
47
- * present, rather than skipping the source.
42
+ * `redirect`/`respond` response helpers). The payload covers the slice of
43
+ * that scope the server could recompute; whatever a declared key matches
44
+ * beyond it is swept client-side (see the consumer below). The transport
45
+ * delivers a response carrying keys to the consumer whether or not the
46
+ * collector contributed a slice — with no slice, the whole scope is swept.
48
47
  */
49
48
  export const FLIGHT_DATA_SOURCE = 'sq'
50
49
 
50
+ /**
51
+ * The reserved `X-Revalidate` key meaning every entry: `revalidate: '*'`
52
+ * on Solid's response helpers. Named so a host that keys its cache
53
+ * differently still reads the same spelling as "all".
54
+ */
55
+ const REVALIDATE_ALL = '*'
56
+
51
57
  /**
52
58
  * The client half of key-scoped invalidation. A mutation declares its
53
59
  * invalidation scope with `X-Revalidate` keys; the flight payload covers
@@ -61,24 +67,44 @@ export const FLIGHT_DATA_SOURCE = 'sq'
61
67
  * A key matches by queryKey prefix — `revalidate: 'users'` sweeps every
62
68
  * query whose key begins with `'users'`. Payload-covered hashes are exempt:
63
69
  * they hydrated with fresh data a moment ago, and refetching them would
64
- * spend the round trip single flight just saved.
70
+ * spend the round trip single flight just saved. The payload is absent
71
+ * when the server folded no slice for this cache (no collector registered,
72
+ * a redirect leaving the app) — then nothing is covered and the declared
73
+ * scope is swept in full.
74
+ *
75
+ * The header's three states are three scopes. Absent: the mutation
76
+ * declared nothing, and this cache is left as it is — the route data the
77
+ * router reloads is its own business, and a response helper without
78
+ * `revalidate` is a host-default that Solid Router reads as "everything"
79
+ * but the query cache does not presume. Empty (`revalidate: []`): nothing,
80
+ * explicitly. The reserved key `*` (`revalidate: '*'`): every query in the
81
+ * cache, minus what the payload covered — the way to say "all" to any
82
+ * host, rather than relying on one host's default.
65
83
  */
66
84
  function sweepRevalidatedQueries(
67
85
  client: QueryClient,
68
- payload: DehydratedState,
86
+ payload: DehydratedState | undefined,
69
87
  response: Response,
70
88
  ): void {
71
- const keys = response.headers.get(REVALIDATE_HEADER)?.split(',')
72
- if (!keys) return
73
- const covered = new Set(payload.queries.map((query) => query.queryHash))
89
+ const declared = response.headers.get(REVALIDATE_HEADER)
90
+ if (declared === null) return
91
+ const covered = new Set(
92
+ payload?.queries.map((query) => query.queryHash) ?? [],
93
+ )
94
+ const uncovered = (query: Query) => !covered.has(query.queryHash)
95
+ const keys = declared.split(',')
96
+ // Background refetch failures surface through the queries' own error
97
+ // states; an unhandled rejection here would fail the mutation instead.
98
+ if (keys.includes(REVALIDATE_ALL)) {
99
+ client.invalidateQueries({ predicate: uncovered }).catch(() => undefined)
100
+ return
101
+ }
74
102
  for (const key of keys) {
103
+ // An empty key is the empty declaration's spelling, not a prefix that
104
+ // happens to match everything.
105
+ if (!key) continue
75
106
  client
76
- .invalidateQueries({
77
- queryKey: [key],
78
- predicate: (query) => !covered.has(query.queryHash),
79
- })
80
- // Background refetch failures surface through the queries' own error
81
- // states; an unhandled rejection here would fail the mutation instead.
107
+ .invalidateQueries({ queryKey: [key], predicate: uncovered })
82
108
  .catch(() => undefined)
83
109
  }
84
110
  }
@@ -208,6 +234,8 @@ export const QueryClientProvider = (
208
234
  subscribeFlightData<DehydratedState>(
209
235
  FLIGHT_DATA_SOURCE,
210
236
  (data, context) => {
237
+ // `data` is undefined for a response that carried metadata but
238
+ // no slice for this cache; `hydrate` ignores a non-object.
211
239
  hydrate(props.client, data)
212
240
  // Fresh data first, then the declared-scope sweep: stale marks
213
241
  // land synchronously, refetches run in the background — the
@@ -216,6 +216,27 @@ export function useBaseQueryLayer<
216
216
  * swap re-attaches the rebuilt observer iff its predecessor was attached. */
217
217
  let shouldAttach = false
218
218
  let activeClient = untrack(client)
219
+ /**
220
+ * The entry this hook last read at its current hash, and the fallback for
221
+ * `query()` when the cache no longer holds that hash. Removal is the case
222
+ * that needs it: `removeQueries()` (or `clear()`) fires 'removed' for a
223
+ * hash this hook is mounted on, which bumps `version` and re-runs every
224
+ * derived read, and a plain `build()` there would re-create the entry the
225
+ * caller just removed. That resurrection is not passive, since the 'added'
226
+ * entry also re-points the still-live observer, whose mount-fetch policy
227
+ * then refetches and repopulates the key, contrary to `removeQueries`
228
+ * being documented to remove entries instead of refetching them.
229
+ *
230
+ * The removed instance keeps its final state, so reads hold their last
231
+ * value until options change or a real entry returns (`setQueryData`, a
232
+ * refetch, a later mount). That is what the other adapters do: a React
233
+ * observer keeps rendering the removed query's last result while the
234
+ * cache stays empty.
235
+ *
236
+ * Cleared on a client swap, below: the fallback is only meaningful for the
237
+ * cache it was read from.
238
+ */
239
+ let lastQuery: Query<TQueryFnData, TError, TQueryData, TQueryKey> | undefined
219
240
 
220
241
  const attach = () => {
221
242
  if (!disposed && !observerSub && !untrack(isRestoring)) {
@@ -236,6 +257,7 @@ export function useBaseQueryLayer<
236
257
  const syncClient = (c: QueryClient) => {
237
258
  if (isServer || c === activeClient) return
238
259
  activeClient = c
260
+ lastQuery = undefined
239
261
  cacheSub?.()
240
262
  cacheSub = c.getQueryCache().subscribe(onCacheEvent)
241
263
  observerSub?.()
@@ -365,7 +387,14 @@ export function useBaseQueryLayer<
365
387
  version()
366
388
  const c = client()
367
389
  syncClient(c)
368
- return c.getQueryCache().build(c, defaultedOptions() as any) as any
390
+ const cache = c.getQueryCache()
391
+ const opts = defaultedOptions()
392
+ const existing = cache.get<TQueryFnData, TError, TQueryData, TQueryKey>(
393
+ opts.queryHash,
394
+ )
395
+ if (existing) return (lastQuery = existing)
396
+ if (lastQuery?.queryHash === opts.queryHash) return lastQuery
397
+ return (lastQuery = cache.build(c, opts as any) as any)
369
398
  }
370
399
 
371
400
  const isEnabled = () => {
@@ -531,6 +560,18 @@ export function useBaseQueryLayer<
531
560
  if (!isServer) observer.setOptions(opts as any)
532
561
  return chainOnce(q.fetch(opts as any), select, wrap)
533
562
  }
563
+ /**
564
+ * Disabled, with nothing cached. The three guards above are all
565
+ * unreachable on the server, but this one is not, and parking here has
566
+ * no server meaning: the render has to finish, and nothing will enable
567
+ * the query or write the cache before it does. So commit the idle
568
+ * value — 'pending' with no data IS a disabled query's settled SSR
569
+ * truth, which is the contract `serverMeta` already honors by not
570
+ * tying its read to this node while disabled, and it is the state the
571
+ * client hydrates to. Committed rather than passed through `wrap`:
572
+ * `select` must not be invoked on absent data.
573
+ */
574
+ if (isServer) return { value: undefined as TData }
534
575
  return NEVER
535
576
  }
536
577