@tanstack/solid-query 6.0.0-rc.2 → 6.0.0-rc.3

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,15 @@ 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` 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.
321
330
  */
322
331
  declare const FLIGHT_DATA_SOURCE = "sq";
323
332
  export { FLIGHT_DATA_SOURCE }
@@ -318,6 +318,15 @@ 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` 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.
321
330
  */
322
331
  declare const FLIGHT_DATA_SOURCE = "sq";
323
332
  export { FLIGHT_DATA_SOURCE }
package/build/dev.cjs CHANGED
@@ -14,6 +14,17 @@ 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
+ 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));
21
+ for (const key of keys) {
22
+ client.invalidateQueries({
23
+ queryKey: [key],
24
+ predicate: (query) => !covered.has(query.queryHash)
25
+ }).catch(() => void 0);
26
+ }
27
+ }
17
28
  exports.QueryClientContext = solidJs.createContext(null);
18
29
  exports.useQueryClient = (queryClient) => {
19
30
  if (queryClient) {
@@ -64,8 +75,9 @@ exports.QueryClientProvider = (props) => {
64
75
  props.client.clear();
65
76
  });
66
77
  } else {
67
- solidJs.onCleanup(serverFunctions.subscribeFlightData(exports.FLIGHT_DATA_SOURCE, (data) => {
78
+ solidJs.onCleanup(serverFunctions.subscribeFlightData(exports.FLIGHT_DATA_SOURCE, (data, context) => {
68
79
  queryCore.hydrate(props.client, data);
80
+ sweepRevalidatedQueries(props.client, data, context.response);
69
81
  }));
70
82
  }
71
83
  return web.createComponent(exports.QueryClientContext, {
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,17 @@ 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
+ 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));
20
+ for (const key of keys) {
21
+ client.invalidateQueries({
22
+ queryKey: [key],
23
+ predicate: (query) => !covered.has(query.queryHash)
24
+ }).catch(() => void 0);
25
+ }
26
+ }
16
27
  var QueryClientContext = createContext(null);
17
28
  var useQueryClient = (queryClient) => {
18
29
  if (queryClient) {
@@ -63,8 +74,9 @@ var QueryClientProvider = (props) => {
63
74
  props.client.clear();
64
75
  });
65
76
  } else {
66
- onCleanup(subscribeFlightData(FLIGHT_DATA_SOURCE, (data) => {
77
+ onCleanup(subscribeFlightData(FLIGHT_DATA_SOURCE, (data, context) => {
67
78
  hydrate(props.client, data);
79
+ sweepRevalidatedQueries(props.client, data, context.response);
68
80
  }));
69
81
  }
70
82
  return createComponent(QueryClientContext, {
package/build/index.cjs CHANGED
@@ -14,6 +14,17 @@ 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
+ 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));
21
+ for (const key of keys) {
22
+ client.invalidateQueries({
23
+ queryKey: [key],
24
+ predicate: (query) => !covered.has(query.queryHash)
25
+ }).catch(() => void 0);
26
+ }
27
+ }
17
28
  exports.QueryClientContext = solidJs.createContext(null);
18
29
  exports.useQueryClient = (queryClient) => {
19
30
  if (queryClient) {
@@ -64,8 +75,9 @@ exports.QueryClientProvider = (props) => {
64
75
  props.client.clear();
65
76
  });
66
77
  } else {
67
- solidJs.onCleanup(serverFunctions.subscribeFlightData(exports.FLIGHT_DATA_SOURCE, (data) => {
78
+ solidJs.onCleanup(serverFunctions.subscribeFlightData(exports.FLIGHT_DATA_SOURCE, (data, context) => {
68
79
  queryCore.hydrate(props.client, data);
80
+ sweepRevalidatedQueries(props.client, data, context.response);
69
81
  }));
70
82
  }
71
83
  return web.createComponent(exports.QueryClientContext, {
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,17 @@ 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
+ 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));
20
+ for (const key of keys) {
21
+ client.invalidateQueries({
22
+ queryKey: [key],
23
+ predicate: (query) => !covered.has(query.queryHash)
24
+ }).catch(() => void 0);
25
+ }
26
+ }
16
27
  var QueryClientContext = createContext(null);
17
28
  var useQueryClient = (queryClient) => {
18
29
  if (queryClient) {
@@ -63,8 +74,9 @@ var QueryClientProvider = (props) => {
63
74
  props.client.clear();
64
75
  });
65
76
  } else {
66
- onCleanup(subscribeFlightData(FLIGHT_DATA_SOURCE, (data) => {
77
+ onCleanup(subscribeFlightData(FLIGHT_DATA_SOURCE, (data, context) => {
67
78
  hydrate(props.client, data);
79
+ sweepRevalidatedQueries(props.client, data, context.response);
68
80
  }));
69
81
  }
70
82
  return createComponent(QueryClientContext, {
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.3",
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,53 @@ 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` 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.
38
48
  */
39
49
  export const FLIGHT_DATA_SOURCE = 'sq'
40
50
 
51
+ /**
52
+ * The client half of key-scoped invalidation. A mutation declares its
53
+ * invalidation scope with `X-Revalidate` keys; the flight payload covers
54
+ * the part of that scope the server's collector recomputed (typically the
55
+ * target page's loader graph). Whatever a declared key matches BEYOND the
56
+ * payload — parameterized instances only this client holds, queries no
57
+ * loader owns — is invalidated here: active queries refetch in the
58
+ * background, inactive ones are marked stale for their next mount. This
59
+ * mirrors how Solid Router consumes the same header for its own cache.
60
+ *
61
+ * A key matches by queryKey prefix — `revalidate: 'users'` sweeps every
62
+ * query whose key begins with `'users'`. Payload-covered hashes are exempt:
63
+ * they hydrated with fresh data a moment ago, and refetching them would
64
+ * spend the round trip single flight just saved.
65
+ */
66
+ function sweepRevalidatedQueries(
67
+ client: QueryClient,
68
+ payload: DehydratedState,
69
+ response: Response,
70
+ ): 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))
74
+ for (const key of keys) {
75
+ 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.
82
+ .catch(() => undefined)
83
+ }
84
+ }
85
+
41
86
  export const QueryClientContext = createContext<(() => QueryClient) | null>(
42
87
  null,
43
88
  )
@@ -160,9 +205,16 @@ export const QueryClientProvider = (
160
205
  // Client-only: the server's consumer registry is module state shared
161
206
  // across requests — registering there would leak between them.
162
207
  onCleanup(
163
- subscribeFlightData<DehydratedState>(FLIGHT_DATA_SOURCE, (data) => {
164
- hydrate(props.client, data)
165
- }),
208
+ subscribeFlightData<DehydratedState>(
209
+ FLIGHT_DATA_SOURCE,
210
+ (data, context) => {
211
+ hydrate(props.client, data)
212
+ // Fresh data first, then the declared-scope sweep: stale marks
213
+ // land synchronously, refetches run in the background — the
214
+ // mutation's promise never waits on them.
215
+ sweepRevalidatedQueries(props.client, data, context.response)
216
+ },
217
+ ),
166
218
  )
167
219
  }
168
220