@tanstack/solid-query 6.0.0-rc.2 → 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.
@@ -318,6 +318,14 @@ export { FetchStatus }
318
318
  * The slice's payload is a `DehydratedState`; the provider consumes it
319
319
  * with `hydrate()`, so every mounted query on those keys updates before
320
320
  * the mutation's promise resolves — no follow-up refetches.
321
+ *
322
+ * A mutation can additionally declare its invalidation SCOPE with
323
+ * `X-Revalidate` keys (the `revalidate` option of Solid's `reload`/
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.
321
329
  */
322
330
  declare const FLIGHT_DATA_SOURCE = "sq";
323
331
  export { FLIGHT_DATA_SOURCE }
@@ -318,6 +318,14 @@ export { FetchStatus }
318
318
  * The slice's payload is a `DehydratedState`; the provider consumes it
319
319
  * with `hydrate()`, so every mounted query on those keys updates before
320
320
  * the mutation's promise resolves — no follow-up refetches.
321
+ *
322
+ * A mutation can additionally declare its invalidation SCOPE with
323
+ * `X-Revalidate` keys (the `revalidate` option of Solid's `reload`/
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.
321
329
  */
322
330
  declare const FLIGHT_DATA_SOURCE = "sq";
323
331
  export { FLIGHT_DATA_SOURCE }
package/build/dev.cjs CHANGED
@@ -14,6 +14,27 @@ 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 = "*";
18
+ function sweepRevalidatedQueries(client, payload, response) {
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
+ }
30
+ for (const key of keys) {
31
+ if (!key) continue;
32
+ client.invalidateQueries({
33
+ queryKey: [key],
34
+ predicate: uncovered
35
+ }).catch(() => void 0);
36
+ }
37
+ }
17
38
  exports.QueryClientContext = solidJs.createContext(null);
18
39
  exports.useQueryClient = (queryClient) => {
19
40
  if (queryClient) {
@@ -64,8 +85,9 @@ exports.QueryClientProvider = (props) => {
64
85
  props.client.clear();
65
86
  });
66
87
  } else {
67
- solidJs.onCleanup(serverFunctions.subscribeFlightData(exports.FLIGHT_DATA_SOURCE, (data) => {
88
+ solidJs.onCleanup(serverFunctions.subscribeFlightData(exports.FLIGHT_DATA_SOURCE, (data, context) => {
68
89
  queryCore.hydrate(props.client, data);
90
+ sweepRevalidatedQueries(props.client, data, context.response);
69
91
  }));
70
92
  }
71
93
  return web.createComponent(exports.QueryClientContext, {
@@ -134,6 +156,7 @@ function useBaseQueryLayer(options, Observer, queryClient) {
134
156
  let disposed = false;
135
157
  let shouldAttach = false;
136
158
  let activeClient = solidJs.untrack(client);
159
+ let lastQuery;
137
160
  const attach = () => {
138
161
  if (!disposed && !observerSub && !solidJs.untrack(isRestoring)) {
139
162
  shouldAttach = true;
@@ -143,6 +166,7 @@ function useBaseQueryLayer(options, Observer, queryClient) {
143
166
  const syncClient = (c) => {
144
167
  if (isServer2 || c === activeClient) return;
145
168
  activeClient = c;
169
+ lastQuery = void 0;
146
170
  cacheSub?.();
147
171
  cacheSub = c.getQueryCache().subscribe(onCacheEvent);
148
172
  observerSub?.();
@@ -234,7 +258,14 @@ function useBaseQueryLayer(options, Observer, queryClient) {
234
258
  version();
235
259
  const c = client();
236
260
  syncClient(c);
237
- 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);
238
269
  };
239
270
  const isEnabled = () => {
240
271
  const opts = defaultedOptions();
@@ -293,6 +324,7 @@ function useBaseQueryLayer(options, Observer, queryClient) {
293
324
  if (!isServer2) observer.setOptions(opts);
294
325
  return chainOnce(q.fetch(opts), select, wrap);
295
326
  }
327
+ if (isServer2) return { value: void 0 };
296
328
  return NEVER;
297
329
  };
298
330
  const dataStore = solidJs.createProjection(
package/build/dev.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { noop, QueryClient as QueryClient$1, hydrate, dehydrate, shouldThrowError, InfiniteQueryObserver, QueryObserver, replaceEqualDeep } from '@tanstack/query-core';
2
2
  export * from '@tanstack/query-core';
3
3
  import { createContext, useContext, onCleanup, createMemo, createOptimistic, createSignal, action, createRenderEffect, untrack, repeat, $TRACK, sharedConfig, createEffect, createProjection, runWithOwner, isPending, resolve } from 'solid-js';
4
- import { createComponent } from '@solidjs/web';
4
+ import { createComponent, REVALIDATE_HEADER } from '@solidjs/web';
5
5
  import { subscribeFlightData } from '@solidjs/web/server-functions';
6
6
 
7
7
  // src/index.ts
@@ -13,6 +13,27 @@ 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 = "*";
17
+ function sweepRevalidatedQueries(client, payload, response) {
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
+ }
29
+ for (const key of keys) {
30
+ if (!key) continue;
31
+ client.invalidateQueries({
32
+ queryKey: [key],
33
+ predicate: uncovered
34
+ }).catch(() => void 0);
35
+ }
36
+ }
16
37
  var QueryClientContext = createContext(null);
17
38
  var useQueryClient = (queryClient) => {
18
39
  if (queryClient) {
@@ -63,8 +84,9 @@ var QueryClientProvider = (props) => {
63
84
  props.client.clear();
64
85
  });
65
86
  } else {
66
- onCleanup(subscribeFlightData(FLIGHT_DATA_SOURCE, (data) => {
87
+ onCleanup(subscribeFlightData(FLIGHT_DATA_SOURCE, (data, context) => {
67
88
  hydrate(props.client, data);
89
+ sweepRevalidatedQueries(props.client, data, context.response);
68
90
  }));
69
91
  }
70
92
  return createComponent(QueryClientContext, {
@@ -133,6 +155,7 @@ function useBaseQueryLayer(options, Observer, queryClient) {
133
155
  let disposed = false;
134
156
  let shouldAttach = false;
135
157
  let activeClient = untrack(client);
158
+ let lastQuery;
136
159
  const attach = () => {
137
160
  if (!disposed && !observerSub && !untrack(isRestoring)) {
138
161
  shouldAttach = true;
@@ -142,6 +165,7 @@ function useBaseQueryLayer(options, Observer, queryClient) {
142
165
  const syncClient = (c) => {
143
166
  if (isServer2 || c === activeClient) return;
144
167
  activeClient = c;
168
+ lastQuery = void 0;
145
169
  cacheSub?.();
146
170
  cacheSub = c.getQueryCache().subscribe(onCacheEvent);
147
171
  observerSub?.();
@@ -233,7 +257,14 @@ function useBaseQueryLayer(options, Observer, queryClient) {
233
257
  version();
234
258
  const c = client();
235
259
  syncClient(c);
236
- 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);
237
268
  };
238
269
  const isEnabled = () => {
239
270
  const opts = defaultedOptions();
@@ -292,6 +323,7 @@ function useBaseQueryLayer(options, Observer, queryClient) {
292
323
  if (!isServer2) observer.setOptions(opts);
293
324
  return chainOnce(q.fetch(opts), select, wrap);
294
325
  }
326
+ if (isServer2) return { value: void 0 };
295
327
  return NEVER;
296
328
  };
297
329
  const dataStore = createProjection(
package/build/index.cjs CHANGED
@@ -14,6 +14,27 @@ 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 = "*";
18
+ function sweepRevalidatedQueries(client, payload, response) {
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
+ }
30
+ for (const key of keys) {
31
+ if (!key) continue;
32
+ client.invalidateQueries({
33
+ queryKey: [key],
34
+ predicate: uncovered
35
+ }).catch(() => void 0);
36
+ }
37
+ }
17
38
  exports.QueryClientContext = solidJs.createContext(null);
18
39
  exports.useQueryClient = (queryClient) => {
19
40
  if (queryClient) {
@@ -64,8 +85,9 @@ exports.QueryClientProvider = (props) => {
64
85
  props.client.clear();
65
86
  });
66
87
  } else {
67
- solidJs.onCleanup(serverFunctions.subscribeFlightData(exports.FLIGHT_DATA_SOURCE, (data) => {
88
+ solidJs.onCleanup(serverFunctions.subscribeFlightData(exports.FLIGHT_DATA_SOURCE, (data, context) => {
68
89
  queryCore.hydrate(props.client, data);
90
+ sweepRevalidatedQueries(props.client, data, context.response);
69
91
  }));
70
92
  }
71
93
  return web.createComponent(exports.QueryClientContext, {
@@ -134,6 +156,7 @@ function useBaseQueryLayer(options, Observer, queryClient) {
134
156
  let disposed = false;
135
157
  let shouldAttach = false;
136
158
  let activeClient = solidJs.untrack(client);
159
+ let lastQuery;
137
160
  const attach = () => {
138
161
  if (!disposed && !observerSub && !solidJs.untrack(isRestoring)) {
139
162
  shouldAttach = true;
@@ -143,6 +166,7 @@ function useBaseQueryLayer(options, Observer, queryClient) {
143
166
  const syncClient = (c) => {
144
167
  if (isServer2 || c === activeClient) return;
145
168
  activeClient = c;
169
+ lastQuery = void 0;
146
170
  cacheSub?.();
147
171
  cacheSub = c.getQueryCache().subscribe(onCacheEvent);
148
172
  observerSub?.();
@@ -234,7 +258,14 @@ function useBaseQueryLayer(options, Observer, queryClient) {
234
258
  version();
235
259
  const c = client();
236
260
  syncClient(c);
237
- 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);
238
269
  };
239
270
  const isEnabled = () => {
240
271
  const opts = defaultedOptions();
@@ -293,6 +324,7 @@ function useBaseQueryLayer(options, Observer, queryClient) {
293
324
  if (!isServer2) observer.setOptions(opts);
294
325
  return chainOnce(q.fetch(opts), select, wrap);
295
326
  }
327
+ if (isServer2) return { value: void 0 };
296
328
  return NEVER;
297
329
  };
298
330
  const dataStore = solidJs.createProjection(
package/build/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { noop, QueryClient as QueryClient$1, hydrate, dehydrate, shouldThrowError, InfiniteQueryObserver, QueryObserver, replaceEqualDeep } from '@tanstack/query-core';
2
2
  export * from '@tanstack/query-core';
3
3
  import { createContext, useContext, onCleanup, createMemo, createOptimistic, createSignal, action, createRenderEffect, untrack, repeat, $TRACK, sharedConfig, createEffect, createProjection, runWithOwner, isPending, resolve } from 'solid-js';
4
- import { createComponent } from '@solidjs/web';
4
+ import { createComponent, REVALIDATE_HEADER } from '@solidjs/web';
5
5
  import { subscribeFlightData } from '@solidjs/web/server-functions';
6
6
 
7
7
  // src/index.ts
@@ -13,6 +13,27 @@ 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 = "*";
17
+ function sweepRevalidatedQueries(client, payload, response) {
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
+ }
29
+ for (const key of keys) {
30
+ if (!key) continue;
31
+ client.invalidateQueries({
32
+ queryKey: [key],
33
+ predicate: uncovered
34
+ }).catch(() => void 0);
35
+ }
36
+ }
16
37
  var QueryClientContext = createContext(null);
17
38
  var useQueryClient = (queryClient) => {
18
39
  if (queryClient) {
@@ -63,8 +84,9 @@ var QueryClientProvider = (props) => {
63
84
  props.client.clear();
64
85
  });
65
86
  } else {
66
- onCleanup(subscribeFlightData(FLIGHT_DATA_SOURCE, (data) => {
87
+ onCleanup(subscribeFlightData(FLIGHT_DATA_SOURCE, (data, context) => {
67
88
  hydrate(props.client, data);
89
+ sweepRevalidatedQueries(props.client, data, context.response);
68
90
  }));
69
91
  }
70
92
  return createComponent(QueryClientContext, {
@@ -133,6 +155,7 @@ function useBaseQueryLayer(options, Observer, queryClient) {
133
155
  let disposed = false;
134
156
  let shouldAttach = false;
135
157
  let activeClient = untrack(client);
158
+ let lastQuery;
136
159
  const attach = () => {
137
160
  if (!disposed && !observerSub && !untrack(isRestoring)) {
138
161
  shouldAttach = true;
@@ -142,6 +165,7 @@ function useBaseQueryLayer(options, Observer, queryClient) {
142
165
  const syncClient = (c) => {
143
166
  if (isServer2 || c === activeClient) return;
144
167
  activeClient = c;
168
+ lastQuery = void 0;
145
169
  cacheSub?.();
146
170
  cacheSub = c.getQueryCache().subscribe(onCacheEvent);
147
171
  observerSub?.();
@@ -233,7 +257,14 @@ function useBaseQueryLayer(options, Observer, queryClient) {
233
257
  version();
234
258
  const c = client();
235
259
  syncClient(c);
236
- 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);
237
268
  };
238
269
  const isEnabled = () => {
239
270
  const opts = defaultedOptions();
@@ -292,6 +323,7 @@ function useBaseQueryLayer(options, Observer, queryClient) {
292
323
  if (!isServer2) observer.setOptions(opts);
293
324
  return chainOnce(q.fetch(opts), select, wrap);
294
325
  }
326
+ if (isServer2) return { value: void 0 };
295
327
  return NEVER;
296
328
  };
297
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.2",
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",
@@ -1,4 +1,5 @@
1
1
  import { createContext, onCleanup, sharedConfig, useContext } from 'solid-js'
2
+ import { REVALIDATE_HEADER } from '@solidjs/web'
2
3
  import { hydrate } from '@tanstack/query-core'
3
4
  import { subscribeFlightData } from '@solidjs/web/server-functions'
4
5
  import type { DehydratedState, Query } from '@tanstack/query-core'
@@ -35,9 +36,79 @@ export const HYDRATION_KEY_PREFIX = 'sq:'
35
36
  * The slice's payload is a `DehydratedState`; the provider consumes it
36
37
  * with `hydrate()`, so every mounted query on those keys updates before
37
38
  * the mutation's promise resolves — no follow-up refetches.
39
+ *
40
+ * A mutation can additionally declare its invalidation SCOPE with
41
+ * `X-Revalidate` keys (the `revalidate` option of Solid's `reload`/
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.
38
47
  */
39
48
  export const FLIGHT_DATA_SOURCE = 'sq'
40
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
+
57
+ /**
58
+ * The client half of key-scoped invalidation. A mutation declares its
59
+ * invalidation scope with `X-Revalidate` keys; the flight payload covers
60
+ * the part of that scope the server's collector recomputed (typically the
61
+ * target page's loader graph). Whatever a declared key matches BEYOND the
62
+ * payload — parameterized instances only this client holds, queries no
63
+ * loader owns — is invalidated here: active queries refetch in the
64
+ * background, inactive ones are marked stale for their next mount. This
65
+ * mirrors how Solid Router consumes the same header for its own cache.
66
+ *
67
+ * A key matches by queryKey prefix — `revalidate: 'users'` sweeps every
68
+ * query whose key begins with `'users'`. Payload-covered hashes are exempt:
69
+ * they hydrated with fresh data a moment ago, and refetching them would
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.
83
+ */
84
+ function sweepRevalidatedQueries(
85
+ client: QueryClient,
86
+ payload: DehydratedState | undefined,
87
+ response: Response,
88
+ ): void {
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
+ }
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
106
+ client
107
+ .invalidateQueries({ queryKey: [key], predicate: uncovered })
108
+ .catch(() => undefined)
109
+ }
110
+ }
111
+
41
112
  export const QueryClientContext = createContext<(() => QueryClient) | null>(
42
113
  null,
43
114
  )
@@ -160,9 +231,18 @@ export const QueryClientProvider = (
160
231
  // Client-only: the server's consumer registry is module state shared
161
232
  // across requests — registering there would leak between them.
162
233
  onCleanup(
163
- subscribeFlightData<DehydratedState>(FLIGHT_DATA_SOURCE, (data) => {
164
- hydrate(props.client, data)
165
- }),
234
+ subscribeFlightData<DehydratedState>(
235
+ FLIGHT_DATA_SOURCE,
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.
239
+ hydrate(props.client, data)
240
+ // Fresh data first, then the declared-scope sweep: stale marks
241
+ // land synchronously, refetches run in the background — the
242
+ // mutation's promise never waits on them.
243
+ sweepRevalidatedQueries(props.client, data, context.response)
244
+ },
245
+ ),
166
246
  )
167
247
  }
168
248
 
@@ -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