@tanstack/solid-query 6.0.0-rc.0 → 6.0.0-rc.2
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/README.md +15 -1
- package/build/_tsup-dts-rollup.d.cts +203 -152
- package/build/_tsup-dts-rollup.d.ts +203 -152
- package/build/dev.cjs +755 -559
- package/build/dev.d.cts +2 -22
- package/build/dev.d.ts +2 -22
- package/build/dev.js +757 -559
- package/build/index.cjs +755 -550
- package/build/index.d.cts +2 -22
- package/build/index.d.ts +2 -22
- package/build/index.js +757 -550
- package/package.json +7 -6
- package/src/QueryClient.ts +24 -45
- package/src/QueryClientProvider.tsx +136 -58
- package/src/cacheAggregate.ts +90 -0
- package/src/dehydrateSettled.ts +42 -0
- package/src/index.ts +3 -29
- package/src/types.ts +62 -44
- package/src/useBaseQuery.ts +674 -412
- package/src/useInfiniteQuery.ts +156 -4
- package/src/useIsFetching.ts +6 -16
- package/src/useIsMutating.ts +6 -16
- package/src/useMutation.ts +317 -56
- package/src/useMutationState.ts +9 -22
- package/src/useQueries.ts +61 -110
- package/src/hydrationChannel.ts +0 -260
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/solid-query",
|
|
3
|
-
"version": "6.0.0-rc.
|
|
3
|
+
"version": "6.0.0-rc.2",
|
|
4
4
|
"description": "Primitives for managing, caching and syncing asynchronous and remote data in Solid",
|
|
5
5
|
"author": "tannerlinsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -46,22 +46,23 @@
|
|
|
46
46
|
"!src/__tests__"
|
|
47
47
|
],
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@tanstack/query-core": "5.101.
|
|
49
|
+
"@tanstack/query-core": "5.101.4"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@babel/core": "^7.28.0",
|
|
53
53
|
"@babel/preset-typescript": "^7.18.6",
|
|
54
54
|
"@solidjs/testing-library": "^0.8.10",
|
|
55
55
|
"@solidjs/vite-plugin": "^3.0.0-next.27",
|
|
56
|
-
"@solidjs/web": "^2.0.0-rc.
|
|
57
|
-
"babel-preset-solid": "^2.0.0-rc.
|
|
56
|
+
"@solidjs/web": "^2.0.0-rc.6",
|
|
57
|
+
"babel-preset-solid": "^2.0.0-rc.2",
|
|
58
58
|
"npm-run-all2": "^5.0.0",
|
|
59
|
-
"solid-js": "^2.0.0-rc.
|
|
59
|
+
"solid-js": "^2.0.0-rc.6",
|
|
60
60
|
"tsup-preset-solid": "^2.2.0",
|
|
61
61
|
"@tanstack/query-test-utils": "0.0.0"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
|
-
"
|
|
64
|
+
"@solidjs/web": ">=2.0.0-rc.6 <3.0.0",
|
|
65
|
+
"solid-js": ">=2.0.0-rc.6 <3.0.0"
|
|
65
66
|
},
|
|
66
67
|
"scripts": {
|
|
67
68
|
"clean": "premove ./build ./coverage ./dist-ts",
|
package/src/QueryClient.ts
CHANGED
|
@@ -9,63 +9,42 @@ import type {
|
|
|
9
9
|
QueryKey,
|
|
10
10
|
} from '@tanstack/query-core'
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Core observer options pass through unchanged. The old adapter omitted
|
|
14
|
+
* `structuralSharing` and replaced it with a store-level `reconcile`
|
|
15
|
+
* option; with reads derived directly from cache state, core's
|
|
16
|
+
* cache-level structural sharing is exactly what keeps the data memo
|
|
17
|
+
* referentially stable, so it is exposed again and `reconcile` is gone.
|
|
18
|
+
*/
|
|
19
|
+
export type QueryObserverOptions<
|
|
13
20
|
TQueryFnData = unknown,
|
|
14
21
|
TError = DefaultError,
|
|
15
22
|
TData = TQueryFnData,
|
|
16
23
|
TQueryData = TQueryFnData,
|
|
17
24
|
TQueryKey extends QueryKey = QueryKey,
|
|
18
25
|
TPageParam = never,
|
|
19
|
-
>
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
>,
|
|
28
|
-
'structuralSharing'
|
|
29
|
-
> {
|
|
30
|
-
/**
|
|
31
|
-
* Set this to a reconciliation key to enable reconciliation between query results.
|
|
32
|
-
* Set this to `false` to disable reconciliation between query results.
|
|
33
|
-
* Set this to a function which accepts the old and new data and returns resolved data of the same type to implement custom reconciliation logic.
|
|
34
|
-
* Defaults reconciliation to false.
|
|
35
|
-
*/
|
|
36
|
-
reconcile?:
|
|
37
|
-
| string
|
|
38
|
-
| false
|
|
39
|
-
| ((oldData: TData | undefined, newData: TData) => TData)
|
|
40
|
-
}
|
|
26
|
+
> = QueryCoreObserverOptions<
|
|
27
|
+
TQueryFnData,
|
|
28
|
+
TError,
|
|
29
|
+
TData,
|
|
30
|
+
TQueryData,
|
|
31
|
+
TQueryKey,
|
|
32
|
+
TPageParam
|
|
33
|
+
>
|
|
41
34
|
|
|
42
|
-
export
|
|
35
|
+
export type InfiniteQueryObserverOptions<
|
|
43
36
|
TQueryFnData = unknown,
|
|
44
37
|
TError = DefaultError,
|
|
45
38
|
TData = TQueryFnData,
|
|
46
39
|
TQueryKey extends QueryKey = QueryKey,
|
|
47
40
|
TPageParam = unknown,
|
|
48
|
-
>
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
>,
|
|
56
|
-
'structuralSharing'
|
|
57
|
-
> {
|
|
58
|
-
/**
|
|
59
|
-
* Set this to a reconciliation key to enable reconciliation between query results.
|
|
60
|
-
* Set this to `false` to disable reconciliation between query results.
|
|
61
|
-
* Set this to a function which accepts the old and new data and returns resolved data of the same type to implement custom reconciliation logic.
|
|
62
|
-
* Defaults reconciliation to false.
|
|
63
|
-
*/
|
|
64
|
-
reconcile?:
|
|
65
|
-
| string
|
|
66
|
-
| false
|
|
67
|
-
| ((oldData: TData | undefined, newData: TData) => TData)
|
|
68
|
-
}
|
|
41
|
+
> = QueryCoreInfiniteQueryObserverOptions<
|
|
42
|
+
TQueryFnData,
|
|
43
|
+
TError,
|
|
44
|
+
TData,
|
|
45
|
+
TQueryKey,
|
|
46
|
+
TPageParam
|
|
47
|
+
>
|
|
69
48
|
|
|
70
49
|
export interface DefaultOptions<
|
|
71
50
|
TError = DefaultError,
|
|
@@ -1,21 +1,43 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
onCleanup,
|
|
6
|
-
useContext,
|
|
7
|
-
} from 'solid-js'
|
|
8
|
-
import {
|
|
9
|
-
HydrationCoordinatorContext,
|
|
10
|
-
createHydrationCoordinator,
|
|
11
|
-
createServerDehydrationChannel,
|
|
12
|
-
} from './hydrationChannel'
|
|
13
|
-
import type { DehydrationChannelYield } from './hydrationChannel'
|
|
1
|
+
import { createContext, onCleanup, sharedConfig, useContext } from 'solid-js'
|
|
2
|
+
import { hydrate } from '@tanstack/query-core'
|
|
3
|
+
import { subscribeFlightData } from '@solidjs/web/server-functions'
|
|
4
|
+
import type { DehydratedState, Query } from '@tanstack/query-core'
|
|
14
5
|
import type { QueryClient } from './QueryClient'
|
|
15
6
|
import type { JSX } from '@solidjs/web'
|
|
16
7
|
|
|
17
8
|
const isServer = typeof window === 'undefined'
|
|
18
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Namespace prefix for cache entries in Solid's hydration registry — the
|
|
12
|
+
* registry is shared with positional node ids and other libraries'
|
|
13
|
+
* content-addressed keys (Solid Router uses its own cache keys), so query
|
|
14
|
+
* hashes get their own prefix.
|
|
15
|
+
*/
|
|
16
|
+
export const HYDRATION_KEY_PREFIX = 'sq:'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The query cache's single-flight source id. Mutation responses can fold
|
|
20
|
+
* fresh data for multiple caches at once (Solid's multi-source
|
|
21
|
+
* single-flight protocol); this is the slice the query cache claims — the
|
|
22
|
+
* provider subscribes its consumer under it, and server collectors
|
|
23
|
+
* register under it to produce the data:
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* import { registerFlightDataSource } from '@solidjs/web/server-functions/server'
|
|
27
|
+
* import { FLIGHT_DATA_SOURCE, dehydrate } from '@tanstack/solid-query'
|
|
28
|
+
*
|
|
29
|
+
* registerFlightDataSource(FLIGHT_DATA_SOURCE, async (event, outcome) => {
|
|
30
|
+
* // rebuild the data for outcome.targetUrl into a QueryClient, then
|
|
31
|
+
* return dehydrate(queryClient)
|
|
32
|
+
* })
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* The slice's payload is a `DehydratedState`; the provider consumes it
|
|
36
|
+
* with `hydrate()`, so every mounted query on those keys updates before
|
|
37
|
+
* the mutation's promise resolves — no follow-up refetches.
|
|
38
|
+
*/
|
|
39
|
+
export const FLIGHT_DATA_SOURCE = 'sq'
|
|
40
|
+
|
|
19
41
|
export const QueryClientContext = createContext<(() => QueryClient) | null>(
|
|
20
42
|
null,
|
|
21
43
|
)
|
|
@@ -38,59 +60,115 @@ export type QueryClientProviderProps = {
|
|
|
38
60
|
children?: JSX.Element
|
|
39
61
|
}
|
|
40
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Server side of hydration: serialize every query the request touches into
|
|
65
|
+
* Solid's hydration registry, content-addressed by query hash (the Solid
|
|
66
|
+
* Router `query()` pattern). Serialization happens at fetch-DISPATCH time —
|
|
67
|
+
* synchronously, while the request's serialization context is live — by
|
|
68
|
+
* handing seroval the fetch promise; it streams the `{ data, t }` payload
|
|
69
|
+
* whenever the fetch settles. Settled entries (initialData, setQueryData,
|
|
70
|
+
* loader prefetches that already landed) serialize their value directly.
|
|
71
|
+
*
|
|
72
|
+
* Content addressing is what makes this cover MORE than rendered
|
|
73
|
+
* components: a query prefetched in a loader and never read by any
|
|
74
|
+
* component still transfers, and any client hook (hydrating or mounted
|
|
75
|
+
* long after) finds it by hash. One entry per hash regardless of how many
|
|
76
|
+
* hooks read it; the payload object is the same reference the data nodes
|
|
77
|
+
* serialize, so seroval's cross-reference dedupe emits it once.
|
|
78
|
+
*/
|
|
79
|
+
function serializeCacheOnServer(client: QueryClient): void {
|
|
80
|
+
const ctx = (
|
|
81
|
+
sharedConfig as unknown as {
|
|
82
|
+
context?: {
|
|
83
|
+
async?: boolean
|
|
84
|
+
noHydrate?: boolean
|
|
85
|
+
serialize: (key: string, value: unknown) => void
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
).context
|
|
89
|
+
if (!ctx || !ctx.async || ctx.noHydrate) return
|
|
90
|
+
|
|
91
|
+
const cache = client.getQueryCache()
|
|
92
|
+
// The standard dehydrate filter gates the wire here too, so apps keep
|
|
93
|
+
// sensitive or oversized queries out of the HTML with the same option
|
|
94
|
+
// they'd pass any other transport.
|
|
95
|
+
const shouldDehydrateQuery =
|
|
96
|
+
client.getDefaultOptions().dehydrate?.shouldDehydrateQuery
|
|
97
|
+
const seen = new Set<string>()
|
|
98
|
+
const serializeQuery = (query: Query<any, any, any, any>) => {
|
|
99
|
+
if (seen.has(query.queryHash)) return
|
|
100
|
+
// Consulted per cache event until it passes, so a filter that rejects
|
|
101
|
+
// pending queries (e.g. the core default) still admits the settled
|
|
102
|
+
// value if it lands while the request's serialization context is live.
|
|
103
|
+
if (shouldDehydrateQuery && !shouldDehydrateQuery(query)) return
|
|
104
|
+
const state = query.state
|
|
105
|
+
if (state.status === 'success') {
|
|
106
|
+
seen.add(query.queryHash)
|
|
107
|
+
ctx.serialize(HYDRATION_KEY_PREFIX + query.queryHash, {
|
|
108
|
+
data: state.data,
|
|
109
|
+
t: state.dataUpdatedAt,
|
|
110
|
+
})
|
|
111
|
+
} else if (state.fetchStatus !== 'idle') {
|
|
112
|
+
// Serialize the PROMISE now, while the context is live — the settle
|
|
113
|
+
// event fires from IO, where no request context exists anymore.
|
|
114
|
+
const promise = query.promise as Promise<unknown> | undefined
|
|
115
|
+
if (!promise) return
|
|
116
|
+
seen.add(query.queryHash)
|
|
117
|
+
ctx.serialize(
|
|
118
|
+
HYDRATION_KEY_PREFIX + query.queryHash,
|
|
119
|
+
promise.then(() => ({
|
|
120
|
+
data: query.state.data,
|
|
121
|
+
t: query.state.dataUpdatedAt,
|
|
122
|
+
})),
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
for (const query of cache.getAll()) serializeQuery(query)
|
|
128
|
+
onCleanup(cache.subscribe((event) => serializeQuery(event.query)))
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Provides the QueryClient and manages its mount lifecycle. On the server
|
|
133
|
+
* it also registers the cache serializer above; on the client, hooks prime
|
|
134
|
+
* the cache from their hash-keyed registry entries themselves (see
|
|
135
|
+
* `useBaseQuery`) and the provider subscribes the cache's single-flight
|
|
136
|
+
* consumer: mutation responses carrying a `FLIGHT_DATA_SOURCE` slice (a
|
|
137
|
+
* `DehydratedState` produced by a server collector registered under the
|
|
138
|
+
* same id) hydrate this client before the mutation's promise resolves.
|
|
139
|
+
* Subscribing is inert when no server collector exists — the server just
|
|
140
|
+
* folds nothing — so it is unconditional. One consumer per source: with
|
|
141
|
+
* nested providers, the innermost mounted one owns the slice.
|
|
142
|
+
*/
|
|
41
143
|
export const QueryClientProvider = (
|
|
42
144
|
props: QueryClientProviderProps,
|
|
43
145
|
): JSX.Element => {
|
|
44
146
|
props.client.mount()
|
|
45
147
|
onCleanup(() => props.client.unmount())
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
// the replay dropped every buffered yield after the first, including
|
|
67
|
-
// the terminal `done` snapshot. The render effect below hands each
|
|
68
|
-
// signal value to the coordinator, which primes the QueryClient via
|
|
69
|
-
// query-core hydrate() (newer-wins) and unblocks `useBaseQuery`
|
|
70
|
-
// subscribers waiting on their query's entry.
|
|
71
|
-
//
|
|
72
|
-
// Client, fresh mount: the compute returns undefined and the effect
|
|
73
|
-
// never fires.
|
|
74
|
-
const [channelValue] = createSignal<DehydrationChannelYield | undefined>(
|
|
75
|
-
() => (isServer ? createServerDehydrationChannel(props.client) : undefined),
|
|
76
|
-
)
|
|
77
|
-
const coordinator = isServer
|
|
78
|
-
? null
|
|
79
|
-
: createHydrationCoordinator(() => props.client)
|
|
80
|
-
createRenderEffect(
|
|
81
|
-
() => (isServer ? undefined : channelValue()),
|
|
82
|
-
(value) => {
|
|
83
|
-
if (value && coordinator) {
|
|
84
|
-
coordinator.applyYield(value)
|
|
85
|
-
}
|
|
86
|
-
},
|
|
87
|
-
)
|
|
148
|
+
if (isServer) {
|
|
149
|
+
serializeCacheOnServer(props.client)
|
|
150
|
+
// Render disposal ends the request: abort what's still in flight and
|
|
151
|
+
// drop the cache so user-configured finite gcTime timers can't pin
|
|
152
|
+
// the per-request client (and whatever its queries closed over) until
|
|
153
|
+
// they fire. Serialized promises are already in seroval's hands, so
|
|
154
|
+
// clearing here can't affect the streamed payload.
|
|
155
|
+
onCleanup(() => {
|
|
156
|
+
props.client.cancelQueries().catch(() => undefined)
|
|
157
|
+
props.client.clear()
|
|
158
|
+
})
|
|
159
|
+
} else {
|
|
160
|
+
// Client-only: the server's consumer registry is module state shared
|
|
161
|
+
// across requests — registering there would leak between them.
|
|
162
|
+
onCleanup(
|
|
163
|
+
subscribeFlightData<DehydratedState>(FLIGHT_DATA_SOURCE, (data) => {
|
|
164
|
+
hydrate(props.client, data)
|
|
165
|
+
}),
|
|
166
|
+
)
|
|
167
|
+
}
|
|
88
168
|
|
|
89
169
|
return (
|
|
90
170
|
<QueryClientContext value={() => props.client}>
|
|
91
|
-
|
|
92
|
-
{props.children}
|
|
93
|
-
</HydrationCoordinatorContext>
|
|
171
|
+
{props.children}
|
|
94
172
|
</QueryClientContext>
|
|
95
173
|
)
|
|
96
174
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createEffect,
|
|
3
|
+
createOptimistic,
|
|
4
|
+
createSignal,
|
|
5
|
+
onCleanup,
|
|
6
|
+
sharedConfig,
|
|
7
|
+
untrack,
|
|
8
|
+
} from 'solid-js'
|
|
9
|
+
import type { Accessor } from 'solid-js'
|
|
10
|
+
|
|
11
|
+
const isServer = typeof window === 'undefined'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Shared reactive core for the cross-cache aggregate hooks — `useIsFetching`,
|
|
15
|
+
* `useIsMutating`, `useMutationState` — which all reduce to "a value derived
|
|
16
|
+
* from a whole cache, refreshed on its events". Two invariants live here so
|
|
17
|
+
* each hook doesn't restate them:
|
|
18
|
+
*
|
|
19
|
+
* Dual write. Cache events can fire inside an action transaction
|
|
20
|
+
* (`useMutation` rides core `action`; an onSuccess invalidation dispatches
|
|
21
|
+
* refetches mid-action), where a plain signal write is held until settle —
|
|
22
|
+
* the in-flight value would be invisible. An optimistic override alone fails
|
|
23
|
+
* the other way: outside a transaction, overrides drop at batch end. So a
|
|
24
|
+
* durable signal carries committed state and an optimistic node tracks it as
|
|
25
|
+
* its base; in-transaction values surface through the override, and the held
|
|
26
|
+
* durable write becomes the base when the settle lands.
|
|
27
|
+
*
|
|
28
|
+
* Hydration. A cross-cache aggregate is not tied to a single async source,
|
|
29
|
+
* so it cannot ride the boundary contract per-query meta uses (suspend until
|
|
30
|
+
* settled). Its contract is fixed by construction instead: a hydrating
|
|
31
|
+
* client can only ever observe the empty value at claim time — its own
|
|
32
|
+
* fetches are held inside the hydration window, channel-primed entries are
|
|
33
|
+
* settled, and mutations do not transfer across SSR. So the server
|
|
34
|
+
* serializes the empty value, and a hydrated mount latches there until its
|
|
35
|
+
* window closes: the subscription and first sync run from an effect half,
|
|
36
|
+
* deferred past hydration — the earliest globally-safe moment for a
|
|
37
|
+
* cross-cache read to go live (on a streamed page, an already-hydrated
|
|
38
|
+
* region's refetch can be in flight while this region is still claiming).
|
|
39
|
+
*
|
|
40
|
+
* Every node here is created on the server too, where it is never read or
|
|
41
|
+
* written: hydration id assignment is positional, so both sides must create
|
|
42
|
+
* the same reactive nodes or every id downstream shifts and the subtree
|
|
43
|
+
* key-misses.
|
|
44
|
+
*/
|
|
45
|
+
export function createCacheAggregate<T>(
|
|
46
|
+
subscribe: (onEvent: () => void) => () => void,
|
|
47
|
+
read: (prev: T) => T,
|
|
48
|
+
empty: T,
|
|
49
|
+
): Accessor<T> {
|
|
50
|
+
const hydratedMount =
|
|
51
|
+
!isServer && (sharedConfig as { hydrating?: boolean }).hydrating === true
|
|
52
|
+
|
|
53
|
+
// Cast: createSignal's value overload excludes functions (a function
|
|
54
|
+
// argument means a compute); aggregate values are counts and arrays.
|
|
55
|
+
const [durable, setDurable] = createSignal<T>(
|
|
56
|
+
(hydratedMount || isServer ? empty : untrack(() => read(empty))) as Exclude<
|
|
57
|
+
T,
|
|
58
|
+
Function
|
|
59
|
+
>,
|
|
60
|
+
{ ownedWrite: true },
|
|
61
|
+
)
|
|
62
|
+
const [value, setValue] = createOptimistic(() => durable(), {
|
|
63
|
+
ownedWrite: true,
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const sync = () => {
|
|
67
|
+
const next = untrack(() => read(untrack(durable)))
|
|
68
|
+
setDurable(() => next)
|
|
69
|
+
setValue(() => next)
|
|
70
|
+
}
|
|
71
|
+
let unsubscribe: (() => void) | null = null
|
|
72
|
+
const attach = () => {
|
|
73
|
+
unsubscribe ??= untrack(() => subscribe(sync))
|
|
74
|
+
}
|
|
75
|
+
onCleanup(() => unsubscribe?.())
|
|
76
|
+
|
|
77
|
+
createEffect(
|
|
78
|
+
() => undefined,
|
|
79
|
+
() => {
|
|
80
|
+
if (hydratedMount) {
|
|
81
|
+
attach()
|
|
82
|
+
sync()
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
if (isServer) return () => empty
|
|
88
|
+
if (!hydratedMount) attach()
|
|
89
|
+
return value
|
|
90
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { dehydrate } from '@tanstack/query-core'
|
|
2
|
+
import type { DehydrateOptions, DehydratedState } from '@tanstack/query-core'
|
|
3
|
+
import type { QueryClient } from './QueryClient'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Waits for every fetch the client has in flight to settle, then
|
|
7
|
+
* dehydrates. This is the extraction half of a single-flight collector:
|
|
8
|
+
* after route data functions run for the mutation's target URL, loaders
|
|
9
|
+
* commonly kick off prefetches without awaiting them — plain
|
|
10
|
+
* `dehydrate()` would snapshot those mid-fetch and ship nothing.
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* registerFlightDataSource(FLIGHT_DATA_SOURCE, (event, outcome) =>
|
|
14
|
+
* loadFlightTarget({
|
|
15
|
+
* router,
|
|
16
|
+
* event,
|
|
17
|
+
* outcome,
|
|
18
|
+
* collect: () => dehydrateSettled(queryClient),
|
|
19
|
+
* }),
|
|
20
|
+
* )
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* Settling is chased to quiescence — awaiting one batch of fetches can
|
|
24
|
+
* dispatch more (dependent queries keyed off a first result) — so an
|
|
25
|
+
* unconditionally self-refetching query would keep this pending; that's
|
|
26
|
+
* an app bug mirrored, not guarded.
|
|
27
|
+
*/
|
|
28
|
+
export async function dehydrateSettled(
|
|
29
|
+
client: QueryClient,
|
|
30
|
+
options?: DehydrateOptions,
|
|
31
|
+
): Promise<DehydratedState> {
|
|
32
|
+
const cache = client.getQueryCache()
|
|
33
|
+
for (;;) {
|
|
34
|
+
const pending = cache
|
|
35
|
+
.getAll()
|
|
36
|
+
.filter((query) => query.state.fetchStatus !== 'idle')
|
|
37
|
+
.map((query) => query.promise)
|
|
38
|
+
if (pending.length === 0) break
|
|
39
|
+
await Promise.allSettled(pending)
|
|
40
|
+
}
|
|
41
|
+
return dehydrate(client, options)
|
|
42
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
/* istanbul ignore file */
|
|
2
2
|
|
|
3
|
-
import { useQuery } from './useQuery'
|
|
4
|
-
import { useInfiniteQuery } from './useInfiniteQuery'
|
|
5
|
-
import { useMutation } from './useMutation'
|
|
6
|
-
import { useQueries } from './useQueries'
|
|
7
|
-
|
|
8
3
|
// Re-export core
|
|
9
4
|
export * from '@tanstack/query-core'
|
|
10
5
|
|
|
@@ -23,27 +18,11 @@ export type {
|
|
|
23
18
|
UseBaseQueryResult,
|
|
24
19
|
UseInfiniteQueryOptions,
|
|
25
20
|
UseInfiniteQueryResult,
|
|
26
|
-
UseMutateAsyncFunction,
|
|
27
21
|
UseMutateFunction,
|
|
28
22
|
UseMutationOptions,
|
|
29
23
|
UseMutationResult,
|
|
30
24
|
UseQueryOptions,
|
|
31
25
|
UseQueryResult,
|
|
32
|
-
// Aliases (create* and use* are both supported)
|
|
33
|
-
UseBaseQueryOptions as CreateBaseQueryOptions,
|
|
34
|
-
UseBaseQueryResult as CreateBaseQueryResult,
|
|
35
|
-
UseInfiniteQueryOptions as CreateInfiniteQueryOptions,
|
|
36
|
-
UseInfiniteQueryResult as CreateInfiniteQueryResult,
|
|
37
|
-
UseMutateAsyncFunction as CreateMutateAsyncFunction,
|
|
38
|
-
UseMutateFunction as CreateMutateFunction,
|
|
39
|
-
UseMutationOptions as CreateMutationOptions,
|
|
40
|
-
UseMutationResult as CreateMutationResult,
|
|
41
|
-
UseBaseMutationResult as CreateBaseMutationResult,
|
|
42
|
-
UseQueryOptions as CreateQueryOptions,
|
|
43
|
-
UseQueryResult as CreateQueryResult,
|
|
44
|
-
DefinedUseBaseQueryResult as DefinedCreateBaseQueryResult,
|
|
45
|
-
DefinedUseInfiniteQueryResult as DefinedCreateInfiniteQueryResult,
|
|
46
|
-
DefinedUseQueryResult as DefinedCreateQueryResult,
|
|
47
26
|
} from './types'
|
|
48
27
|
|
|
49
28
|
export { QueryClient } from './QueryClient'
|
|
@@ -54,22 +33,21 @@ export type {
|
|
|
54
33
|
InfiniteQueryObserverOptions,
|
|
55
34
|
} from './QueryClient'
|
|
56
35
|
export { useQuery } from './useQuery'
|
|
57
|
-
export const createQuery = useQuery
|
|
58
36
|
export { queryOptions } from './queryOptions'
|
|
59
37
|
export type {
|
|
60
38
|
DefinedInitialDataOptions,
|
|
61
39
|
UndefinedInitialDataOptions,
|
|
62
40
|
} from './queryOptions'
|
|
63
41
|
export {
|
|
42
|
+
FLIGHT_DATA_SOURCE,
|
|
64
43
|
QueryClientContext,
|
|
65
44
|
QueryClientProvider,
|
|
66
45
|
useQueryClient,
|
|
67
46
|
} from './QueryClientProvider'
|
|
68
47
|
export type { QueryClientProviderProps } from './QueryClientProvider'
|
|
48
|
+
export { dehydrateSettled } from './dehydrateSettled'
|
|
69
49
|
export { useIsFetching } from './useIsFetching'
|
|
70
|
-
export {
|
|
71
|
-
export { useInfiniteQuery }
|
|
72
|
-
export const createInfiniteQuery = useInfiniteQuery
|
|
50
|
+
export { useInfiniteQuery } from './useInfiniteQuery'
|
|
73
51
|
export { infiniteQueryOptions } from './infiniteQueryOptions'
|
|
74
52
|
export type {
|
|
75
53
|
DefinedInitialDataInfiniteOptions,
|
|
@@ -77,11 +55,7 @@ export type {
|
|
|
77
55
|
} from './infiniteQueryOptions'
|
|
78
56
|
export { useMutation } from './useMutation'
|
|
79
57
|
export { mutationOptions } from './mutationOptions'
|
|
80
|
-
export const createMutation = useMutation
|
|
81
58
|
export { useIsMutating } from './useIsMutating'
|
|
82
|
-
export { useIsMutating as createIsMutating } from './useIsMutating'
|
|
83
59
|
export { useMutationState } from './useMutationState'
|
|
84
|
-
export { useMutationState as createMutationState } from './useMutationState'
|
|
85
60
|
export { useQueries } from './useQueries'
|
|
86
|
-
export const createQueries = useQueries
|
|
87
61
|
export { useIsRestoring, IsRestoringContext } from './isRestoring'
|