@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.
- package/build/_tsup-dts-rollup.d.cts +8 -0
- package/build/_tsup-dts-rollup.d.ts +8 -0
- package/build/dev.cjs +34 -2
- package/build/dev.js +35 -3
- package/build/index.cjs +34 -2
- package/build/index.js +35 -3
- package/package.json +1 -1
- package/src/QueryClientProvider.tsx +83 -3
- package/src/useBaseQuery.ts +42 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,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>(
|
|
164
|
-
|
|
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
|
|
package/src/useBaseQuery.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|