@veams/status-quo-query 0.12.0 → 0.13.1
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/.turbo/turbo-build.log +3 -3
- package/CHANGELOG.md +5 -0
- package/README.md +136 -15
- package/dist/mutation.d.ts +8 -8
- package/dist/mutation.js +9 -9
- package/dist/mutation.js.map +1 -1
- package/dist/provider.d.ts +2 -2
- package/dist/query.js +49 -2
- package/dist/query.js.map +1 -1
- package/dist/react/hooks/index.d.ts +1 -0
- package/dist/react/hooks/index.js +1 -0
- package/dist/react/hooks/index.js.map +1 -1
- package/dist/react/hooks/use-mutation-handle.d.ts +2 -0
- package/dist/react/hooks/use-mutation-handle.js +71 -0
- package/dist/react/hooks/use-mutation-handle.js.map +1 -0
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js +1 -1
- package/dist/react/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/query.spec.ts +35 -1
- package/src/mutation.ts +27 -27
- package/src/provider.ts +7 -7
- package/src/query.ts +58 -3
- package/src/react/__tests__/use-mutation-handle.spec.tsx +107 -0
- package/src/react/hooks/index.ts +1 -0
- package/src/react/hooks/use-mutation-handle.ts +98 -0
- package/src/react/index.ts +1 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
> @veams/status-quo-query@0.
|
|
3
|
+
> @veams/status-quo-query@0.12.0 build
|
|
4
4
|
> npm-run-all compile
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
> @veams/status-quo-query@0.
|
|
7
|
+
> @veams/status-quo-query@0.12.0 compile
|
|
8
8
|
> npm-run-all bundle:ts
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
> @veams/status-quo-query@0.
|
|
11
|
+
> @veams/status-quo-query@0.12.0 bundle:ts
|
|
12
12
|
> tsc --project tsconfig.json
|
|
13
13
|
|
|
14
14
|
⠙[1G[0K⠙[1G[0K⠙[1G[0K
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
- Fixed `QueryHandle.subscribe(...)` to mount the TanStack `QueryClient` lifecycle while a query handle subscription is active, restoring `refetchOnWindowFocus` and reconnect behavior for stale subscribed queries.
|
|
6
|
+
- Documented how `staleTime`, `refetchOnWindowFocus`, `bindSubscribable(...)`, and passive cache reads interact in the wrapper.
|
|
7
|
+
|
|
3
8
|
## 0.1.0
|
|
4
9
|
|
|
5
10
|
- Initial package scaffold for TanStack Query and mutation services.
|
package/README.md
CHANGED
|
@@ -19,11 +19,11 @@ npm install react
|
|
|
19
19
|
Status Quo Query deliberately keeps the public surface small:
|
|
20
20
|
|
|
21
21
|
- `QueryHandle<TData, TError>` is the read handle for one query.
|
|
22
|
-
- `
|
|
22
|
+
- `MutationHandle<TData, TError, TVariables>` is the write handle for one mutation.
|
|
23
23
|
- snapshots are passive state objects returned from `getSnapshot()` and `subscribe(...)`.
|
|
24
24
|
- commands stay on the handle: `refetch()`, `invalidate()`, `mutate()`, `reset()`.
|
|
25
25
|
- `QueryManager` is the broader coordination layer for cross-query work.
|
|
26
|
-
- `@veams/status-quo-query/react` is optional and adds
|
|
26
|
+
- `@veams/status-quo-query/react` is optional and adds React subscription hooks (`useQueryHandle`, `useMutationHandle`) over the same handle shape.
|
|
27
27
|
|
|
28
28
|
That keeps the package usable in service code, query handlers, state handlers, and React components without changing the core query or mutation API.
|
|
29
29
|
|
|
@@ -49,13 +49,13 @@ Root exports:
|
|
|
49
49
|
- `CreateUntrackedMutation`
|
|
50
50
|
- `QueryHandle`
|
|
51
51
|
- `QueryHandleData`
|
|
52
|
-
- `
|
|
52
|
+
- `MutationHandle`
|
|
53
53
|
- `QueryHandleSnapshot`
|
|
54
|
-
- `
|
|
54
|
+
- `MutationHandleSnapshot`
|
|
55
55
|
- `QueryDependencyTuple`
|
|
56
56
|
- `QueryHandleOptions`
|
|
57
|
-
- `
|
|
58
|
-
- `
|
|
57
|
+
- `MutationHandleOptions`
|
|
58
|
+
- `TrackedMutationHandleOptions`
|
|
59
59
|
- `QueryInvalidateOptions`
|
|
60
60
|
- `QueryMetaState`
|
|
61
61
|
- `TrackedDependencyRecord`
|
|
@@ -70,7 +70,7 @@ Subpath exports:
|
|
|
70
70
|
- `@veams/status-quo-query/provider`
|
|
71
71
|
- `@veams/status-quo-query/query`
|
|
72
72
|
- `@veams/status-quo-query/mutation`
|
|
73
|
-
- `@veams/status-quo-query/react`
|
|
73
|
+
- `@veams/status-quo-query/react` (`useQueryHandle`, `useMutationHandle`)
|
|
74
74
|
|
|
75
75
|
## Quickstart
|
|
76
76
|
|
|
@@ -133,8 +133,9 @@ await userQuery.invalidate({ refetchType: 'none' });
|
|
|
133
133
|
|
|
134
134
|
## React Bindings
|
|
135
135
|
|
|
136
|
-
The React entrypoint exposes `useQueryHandle(...)` and keeps `react` optional unless you
|
|
137
|
-
|
|
136
|
+
The React entrypoint exposes `useQueryHandle(...)` and `useMutationHandle(...)` and keeps `react` optional unless you import `@veams/status-quo-query/react`.
|
|
137
|
+
|
|
138
|
+
### `useQueryHandle`
|
|
138
139
|
|
|
139
140
|
```tsx
|
|
140
141
|
import { useQueryHandle } from '@veams/status-quo-query/react';
|
|
@@ -153,6 +154,35 @@ Use the hook when a component should subscribe directly to a query handle and re
|
|
|
153
154
|
- call `query.refetch()` or `query.invalidate()` on the handle itself
|
|
154
155
|
- derive view-specific values in the component instead of adding selector logic to the hook
|
|
155
156
|
|
|
157
|
+
### `useMutationHandle`
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
import { useMutationHandle } from '@veams/status-quo-query/react';
|
|
161
|
+
import type { MutationHandle } from '@veams/status-quo-query';
|
|
162
|
+
|
|
163
|
+
function SaveButton({
|
|
164
|
+
mutation,
|
|
165
|
+
payload,
|
|
166
|
+
}: {
|
|
167
|
+
mutation: MutationHandle<{ ok: boolean }, Error, { name: string }>;
|
|
168
|
+
payload: { name: string };
|
|
169
|
+
}) {
|
|
170
|
+
const snapshot = useMutationHandle(mutation);
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<button onClick={() => mutation.mutate(payload)} disabled={snapshot.isPending}>
|
|
174
|
+
{snapshot.isPending ? 'Saving…' : 'Save'}
|
|
175
|
+
</button>
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Use `useMutationHandle` when a component should react to mutation state (pending, success, error). Keep imperative calls on the handle itself:
|
|
181
|
+
|
|
182
|
+
- read `status`, `isPending`, `isError`, `isSuccess`, `data`, `error`, `variables` from the snapshot
|
|
183
|
+
- call `mutation.mutate(variables)` or `mutation.reset()` on the handle itself
|
|
184
|
+
- the hook does not trigger the mutation — it only subscribes to its state
|
|
185
|
+
|
|
156
186
|
## Status Quo Integration
|
|
157
187
|
|
|
158
188
|
The same query handle can also feed a `status-quo` handler through `bindSubscribable(...)`.
|
|
@@ -763,7 +793,7 @@ Only `deps` participates in automatic invalidation tracking. `view` is optional
|
|
|
763
793
|
|
|
764
794
|
`createQuery(queryKey, queryFn, options?)` returns the same `QueryHandle<TData, TError>` shape as `createUntrackedQuery(...)`, but it registers the query hash under every `deps` entry, re-registers on `refetch()` or the first `subscribe(...)` if TanStack has removed the cache entry in the meantime, and keeps the registry in sync when `dependsOn` derives a new tracked key at runtime.
|
|
765
795
|
|
|
766
|
-
`createMutation(mutationFn, options?)` returns the same `
|
|
796
|
+
`createMutation(mutationFn, options?)` returns the same `MutationHandle<TData, TError, TVariables, TOnMutateResult>` shape as `createUntrackedMutation(...)`, but adds:
|
|
767
797
|
|
|
768
798
|
- `dependencyKeys?`
|
|
769
799
|
- `resolveDependencies?`
|
|
@@ -786,7 +816,7 @@ Captures dependency names once and returns:
|
|
|
786
816
|
- the tracked query factory
|
|
787
817
|
- a tracked mutation factory whose default resolver reads `variables[dependencyKey]`
|
|
788
818
|
|
|
789
|
-
The tracked query factory still expects a query key with a final `{ deps, view? }` segment. The tracked mutation factory keeps the same `
|
|
819
|
+
The tracked query factory still expects a query key with a final `{ deps, view? }` segment. The tracked mutation factory keeps the same `MutationHandle` shape as `createMutation(...)`, but no longer needs `dependencyKeys` repeated in each call.
|
|
790
820
|
|
|
791
821
|
Reach for standalone `createMutation(...)` when:
|
|
792
822
|
|
|
@@ -840,15 +870,106 @@ It also adds:
|
|
|
840
870
|
|
|
841
871
|
`unsafe_getResult()` returns the raw TanStack `QueryObserverResult`.
|
|
842
872
|
|
|
873
|
+
### Window focus and stale queries
|
|
874
|
+
|
|
875
|
+
`QueryHandle.subscribe(...)` activates the same TanStack `QueryClient` lifecycle that
|
|
876
|
+
`QueryClientProvider` activates for `useQuery`.
|
|
877
|
+
|
|
878
|
+
TanStack's `refetchOnWindowFocus` support does not live on the individual
|
|
879
|
+
`QueryObserver` alone. A `QueryClient` has to be mounted so TanStack can subscribe to
|
|
880
|
+
the global `focusManager` and forward browser focus changes to the query cache. Native
|
|
881
|
+
React Query users usually get this through:
|
|
882
|
+
|
|
883
|
+
```tsx
|
|
884
|
+
<QueryClientProvider client={queryClient}>
|
|
885
|
+
<App />
|
|
886
|
+
</QueryClientProvider>
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
Status Quo Query does not require that provider because query handles can be used in
|
|
890
|
+
service code, state handlers, and React components. Instead, a subscribed query handle
|
|
891
|
+
mounts the provided `QueryClient` for as long as that handle subscription is active:
|
|
892
|
+
|
|
893
|
+
```ts
|
|
894
|
+
const query = createUntrackedQuery(['clock'], fetchClock, {
|
|
895
|
+
staleTime: 5_000,
|
|
896
|
+
refetchOnWindowFocus: true,
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
const unsubscribe = query.subscribe((snapshot) => {
|
|
900
|
+
console.log(snapshot.data);
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
// Later, when the state handler or component is destroyed:
|
|
904
|
+
unsubscribe();
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
With an active subscription, the flow is:
|
|
908
|
+
|
|
909
|
+
1. The first `subscribe(...)` mounts the `QueryClient`.
|
|
910
|
+
2. TanStack installs its focus and online listeners through `focusManager` and
|
|
911
|
+
`onlineManager`.
|
|
912
|
+
3. The `QueryObserver` performs its normal mount behavior and starts stale timers.
|
|
913
|
+
4. Once `staleTime` has elapsed, the observer marks the current result stale.
|
|
914
|
+
5. When the browser window becomes focused again, TanStack calls `queryCache.onFocus()`.
|
|
915
|
+
6. Active stale queries with `refetchOnWindowFocus: true` refetch.
|
|
916
|
+
7. Unsubscribing removes the observer and unmounts the client lifecycle for that
|
|
917
|
+
subscription.
|
|
918
|
+
|
|
919
|
+
This means `staleTime` and `refetchOnWindowFocus` behave the same way through a
|
|
920
|
+
`QueryHandle` subscription as they do through `useQuery`:
|
|
921
|
+
|
|
922
|
+
```ts
|
|
923
|
+
const query = createUntrackedQuery(
|
|
924
|
+
['staleTime'],
|
|
925
|
+
() => Promise.resolve(new Date()),
|
|
926
|
+
{
|
|
927
|
+
staleTime: 5_000,
|
|
928
|
+
}
|
|
929
|
+
);
|
|
930
|
+
```
|
|
931
|
+
|
|
932
|
+
The default TanStack behavior is preserved. `refetchOnWindowFocus` defaults to `true`,
|
|
933
|
+
so the query above refetches on focus only after it has become stale. Use
|
|
934
|
+
`refetchOnWindowFocus: 'always'` when focus should refetch even while the cached data is
|
|
935
|
+
still fresh, or `refetchOnWindowFocus: false` when focus should never refetch that
|
|
936
|
+
query.
|
|
937
|
+
|
|
938
|
+
`bindSubscribable(...)` integrations also participate automatically because they call
|
|
939
|
+
`QueryHandle.subscribe(...)` internally:
|
|
940
|
+
|
|
941
|
+
```ts
|
|
942
|
+
this.bindSubscribable(productQuery, (snapshot) => {
|
|
943
|
+
this.setState({
|
|
944
|
+
product: snapshot.data,
|
|
945
|
+
productStatus: snapshot.status,
|
|
946
|
+
});
|
|
947
|
+
});
|
|
948
|
+
```
|
|
949
|
+
|
|
950
|
+
The state handler receives:
|
|
951
|
+
|
|
952
|
+
- an initial snapshot, either synchronously from the observer or through
|
|
953
|
+
`getSnapshot()`
|
|
954
|
+
- the stale-result update after `staleTime` expires
|
|
955
|
+
- a fresh snapshot after the window regains focus and the stale query refetches
|
|
956
|
+
|
|
957
|
+
One-off reads do not install focus listeners. `getSnapshot()`, `getQueryData(...)`,
|
|
958
|
+
`getQueryState(...)`, and `fetchQuery(...)` are passive cache reads or explicit fetches;
|
|
959
|
+
they are not live observers. Use `subscribe(...)`, `useQueryHandle(...)`, or
|
|
960
|
+
`bindSubscribable(...)` when the query should respond to TanStack observer lifecycle
|
|
961
|
+
events such as `refetchOnWindowFocus`, `refetchOnReconnect`, stale timers, and refetch
|
|
962
|
+
intervals.
|
|
963
|
+
|
|
843
964
|
### `setupMutation(queryClient)`
|
|
844
965
|
|
|
845
966
|
Creates a `createUntrackedMutation` factory bound to a `QueryClient`.
|
|
846
967
|
|
|
847
|
-
`createUntrackedMutation(mutationFn, options?)` returns `
|
|
968
|
+
`createUntrackedMutation(mutationFn, options?)` returns `MutationHandle<TData, TError, TVariables, TOnMutateResult>`.
|
|
848
969
|
|
|
849
|
-
`
|
|
970
|
+
`MutationHandleOptions` is based on TanStack `MutationObserverOptions`, without `mutationFn` because it is provided directly to `createUntrackedMutation`.
|
|
850
971
|
|
|
851
|
-
`
|
|
972
|
+
`MutationHandle` methods:
|
|
852
973
|
|
|
853
974
|
- `getSnapshot()`
|
|
854
975
|
- `subscribe(listener)`
|
|
@@ -856,7 +977,7 @@ Creates a `createUntrackedMutation` factory bound to a `QueryClient`.
|
|
|
856
977
|
- `reset()`
|
|
857
978
|
- `unsafe_getResult()`
|
|
858
979
|
|
|
859
|
-
`
|
|
980
|
+
`MutationHandleSnapshot<TData, TError, TVariables>` fields:
|
|
860
981
|
|
|
861
982
|
- `data`
|
|
862
983
|
- `error`
|
package/dist/mutation.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export type MutationStatus = TanstackMutationStatus;
|
|
|
4
4
|
/**
|
|
5
5
|
* Represents a stable snapshot of the mutation service's state.
|
|
6
6
|
*/
|
|
7
|
-
export interface
|
|
7
|
+
export interface MutationHandleSnapshot<TData = unknown, TError = Error, TVariables = void> {
|
|
8
8
|
data: TData | undefined;
|
|
9
9
|
error: TError | null;
|
|
10
10
|
status: MutationStatus;
|
|
@@ -17,9 +17,9 @@ export interface MutationServiceSnapshot<TData = unknown, TError = Error, TVaria
|
|
|
17
17
|
/**
|
|
18
18
|
* Defines the public API for a mutation service.
|
|
19
19
|
*/
|
|
20
|
-
export interface
|
|
21
|
-
getSnapshot: () =>
|
|
22
|
-
subscribe: (listener: (snapshot:
|
|
20
|
+
export interface MutationHandle<TData = unknown, TError = Error, TVariables = void, TOnMutateResult = unknown> {
|
|
21
|
+
getSnapshot: () => MutationHandleSnapshot<TData, TError, TVariables>;
|
|
22
|
+
subscribe: (listener: (snapshot: MutationHandleSnapshot<TData, TError, TVariables>) => void) => () => void;
|
|
23
23
|
mutate: (variables: TVariables, options?: MutateOptions<TData, TError, TVariables, TOnMutateResult>) => Promise<TData>;
|
|
24
24
|
reset: () => void;
|
|
25
25
|
unsafe_getResult: () => MutationObserverResult<TData, TError, TVariables, TOnMutateResult>;
|
|
@@ -27,12 +27,12 @@ export interface MutationService<TData = unknown, TError = Error, TVariables = v
|
|
|
27
27
|
/**
|
|
28
28
|
* Configuration options for creating a mutation service, excluding the mutation function itself.
|
|
29
29
|
*/
|
|
30
|
-
export type
|
|
30
|
+
export type MutationHandleOptions<TData = unknown, TError = Error, TVariables = void, TOnMutateResult = unknown> = Omit<MutationObserverOptions<TData, TError, TVariables, TOnMutateResult>, 'mutationFn'>;
|
|
31
31
|
/**
|
|
32
32
|
* Function signature for the untracked mutation factory.
|
|
33
33
|
*/
|
|
34
34
|
export interface CreateUntrackedMutation {
|
|
35
|
-
<TData = unknown, TError = Error, TVariables = void, TOnMutateResult = unknown>(mutationFn: MutationFunction<TData, TVariables>, options?:
|
|
35
|
+
<TData = unknown, TError = Error, TVariables = void, TOnMutateResult = unknown>(mutationFn: MutationFunction<TData, TVariables>, options?: MutationHandleOptions<TData, TError, TVariables, TOnMutateResult>): MutationHandle<TData, TError, TVariables, TOnMutateResult>;
|
|
36
36
|
}
|
|
37
37
|
/**
|
|
38
38
|
* Additional options for tracked mutations that invalidate queries automatically.
|
|
@@ -41,7 +41,7 @@ export interface CreateUntrackedMutation {
|
|
|
41
41
|
* options only describe how the facade should derive dependency values and when it should
|
|
42
42
|
* invalidate matching tracked queries after the mutation lifecycle settles.
|
|
43
43
|
*/
|
|
44
|
-
export interface
|
|
44
|
+
export interface TrackedMutationHandleOptions<TDeps extends TrackedDependencyRecord = TrackedDependencyRecord, TData = unknown, TError = Error, TVariables = void, TOnMutateResult = unknown> extends MutationHandleOptions<TData, TError, TVariables, TOnMutateResult> {
|
|
45
45
|
dependencyKeys?: readonly (keyof TDeps & string)[];
|
|
46
46
|
resolveDependencies?: (variables: TVariables) => Partial<TDeps>;
|
|
47
47
|
invalidateOn?: TrackedInvalidateOn;
|
|
@@ -51,7 +51,7 @@ export interface TrackedMutationServiceOptions<TDeps extends TrackedDependencyRe
|
|
|
51
51
|
* Function signature for the default mutation factory with automatic invalidation.
|
|
52
52
|
*/
|
|
53
53
|
export interface CreateMutation {
|
|
54
|
-
<TDeps extends TrackedDependencyRecord = TrackedDependencyRecord, TData = unknown, TError = Error, TVariables = void, TOnMutateResult = unknown>(mutationFn: MutationFunction<TData, TVariables>, options?:
|
|
54
|
+
<TDeps extends TrackedDependencyRecord = TrackedDependencyRecord, TData = unknown, TError = Error, TVariables = void, TOnMutateResult = unknown>(mutationFn: MutationFunction<TData, TVariables>, options?: TrackedMutationHandleOptions<TDeps, TData, TError, TVariables, TOnMutateResult>): MutationHandle<TData, TError, TVariables, TOnMutateResult>;
|
|
55
55
|
}
|
|
56
56
|
/**
|
|
57
57
|
* Prepares the mutation factory by binding it to a specific QueryClient instance.
|
package/dist/mutation.js
CHANGED
|
@@ -8,7 +8,7 @@ import { pickTrackedDependencies, resolveTrackedQueries, toTrackedDependencyEntr
|
|
|
8
8
|
export function setupMutation(queryClient) {
|
|
9
9
|
// Returns the actual factory function for creating individual mutation services.
|
|
10
10
|
return function createMutation(mutationFn, options) {
|
|
11
|
-
return
|
|
11
|
+
return createMutationHandle(queryClient, mutationFn, options);
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
@@ -22,8 +22,8 @@ export function setupTrackedMutation(queryClient, trackingRegistry, defaultDepen
|
|
|
22
22
|
return function createMutation(mutationFn, options) {
|
|
23
23
|
// Split tracked-only options from the underlying TanStack mutation observer options.
|
|
24
24
|
const { dependencyKeys, invalidateOn = 'success', matchMode = 'intersection', resolveDependencies, ...mutationOptions } = options ?? {};
|
|
25
|
-
// Reuse the normal mutation
|
|
26
|
-
const
|
|
25
|
+
// Reuse the normal mutation handle so snapshots and subscription behavior stay identical.
|
|
26
|
+
const handle = createMutationHandle(queryClient, mutationFn, mutationOptions);
|
|
27
27
|
// The paired helper injects dependency keys here, while standalone tracked mutations can
|
|
28
28
|
// still provide them directly or bypass them with a custom resolver.
|
|
29
29
|
const resolvedDependencyKeys = (dependencyKeys ?? defaultDependencyKeys);
|
|
@@ -41,12 +41,12 @@ export function setupTrackedMutation(queryClient, trackingRegistry, defaultDepen
|
|
|
41
41
|
})));
|
|
42
42
|
};
|
|
43
43
|
return {
|
|
44
|
-
...
|
|
44
|
+
...handle,
|
|
45
45
|
mutate: async (variables, mutateOptions) => {
|
|
46
46
|
try {
|
|
47
47
|
// Let TanStack finish the mutation first so its own callbacks and state machine remain
|
|
48
48
|
// authoritative. The facade only coordinates the follow-up invalidation.
|
|
49
|
-
const result = await
|
|
49
|
+
const result = await handle.mutate(variables, mutateOptions);
|
|
50
50
|
if (invalidateOn === 'success' || invalidateOn === 'settled') {
|
|
51
51
|
await invalidateTrackedQueries(variables);
|
|
52
52
|
}
|
|
@@ -71,7 +71,7 @@ export function setupTrackedMutation(queryClient, trackingRegistry, defaultDepen
|
|
|
71
71
|
/**
|
|
72
72
|
* Internal helper to transform a raw Tanstack mutation result into our public snapshot format.
|
|
73
73
|
*/
|
|
74
|
-
function
|
|
74
|
+
function toMutationHandleSnapshot(result) {
|
|
75
75
|
// Extract and return the relevant fields for the UI or other services.
|
|
76
76
|
return {
|
|
77
77
|
data: result.data,
|
|
@@ -84,7 +84,7 @@ function toMutationServiceSnapshot(result) {
|
|
|
84
84
|
isSuccess: result.isSuccess,
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
|
-
function
|
|
87
|
+
function createMutationHandle(queryClient, mutationFn, options) {
|
|
88
88
|
// Keep the original mutation implementation in one place so tracked and untracked mutations
|
|
89
89
|
// always expose the same observer-backed runtime behavior.
|
|
90
90
|
const observer = new MutationObserver(queryClient, {
|
|
@@ -92,9 +92,9 @@ function createMutationService(queryClient, mutationFn, options) {
|
|
|
92
92
|
mutationFn,
|
|
93
93
|
});
|
|
94
94
|
return {
|
|
95
|
-
getSnapshot: () =>
|
|
95
|
+
getSnapshot: () => toMutationHandleSnapshot(observer.getCurrentResult()),
|
|
96
96
|
subscribe: (listener) => observer.subscribe((result) => {
|
|
97
|
-
listener(
|
|
97
|
+
listener(toMutationHandleSnapshot(result));
|
|
98
98
|
}),
|
|
99
99
|
mutate: (variables, mutateOptions) => observer.mutate(variables, mutateOptions),
|
|
100
100
|
reset: () => observer.reset(),
|
package/dist/mutation.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mutation.js","sourceRoot":"","sources":["../src/mutation.ts"],"names":[],"mappings":"AAAA,OAAO;AACL,2DAA2D;AAC3D,gBAAgB,GAajB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAML,uBAAuB,EACvB,qBAAqB,EACrB,0BAA0B,GAC3B,MAAM,eAAe,CAAC;AAmHvB;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,WAAwB;IACpD,iFAAiF;IACjF,OAAO,SAAS,cAAc,CAM5B,UAA+C,EAC/C,
|
|
1
|
+
{"version":3,"file":"mutation.js","sourceRoot":"","sources":["../src/mutation.ts"],"names":[],"mappings":"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"}
|
package/dist/provider.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type QueryClient, type MutationFunction } from '@tanstack/query-core';
|
|
2
|
-
import { type CreateMutation, type CreateUntrackedMutation, type
|
|
2
|
+
import { type CreateMutation, type CreateUntrackedMutation, type MutationHandle, type TrackedMutationHandleOptions } from './mutation.js';
|
|
3
3
|
import { type CreateQuery, type CreateUntrackedQuery } from './query.js';
|
|
4
4
|
import { type TrackedDependencyValue } from './tracking.js';
|
|
5
5
|
/**
|
|
@@ -9,7 +9,7 @@ import { type TrackedDependencyValue } from './tracking.js';
|
|
|
9
9
|
* once at setup time and injects them automatically for each tracked mutation it creates.
|
|
10
10
|
*/
|
|
11
11
|
export interface CreateMutationWithDefaults<TDependencyKey extends string> {
|
|
12
|
-
<TData = unknown, TError = Error, TVariables = void, TOnMutateResult = unknown>(mutationFn: MutationFunction<TData, TVariables>, options?: Omit<
|
|
12
|
+
<TData = unknown, TError = Error, TVariables = void, TOnMutateResult = unknown>(mutationFn: MutationFunction<TData, TVariables>, options?: Omit<TrackedMutationHandleOptions<Record<TDependencyKey, TrackedDependencyValue>, TData, TError, TVariables, TOnMutateResult>, 'dependencyKeys'>): MutationHandle<TData, TError, TVariables, TOnMutateResult>;
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
15
|
* Paired tracked helper that captures dependency keys once for default mutation resolution.
|
package/dist/query.js
CHANGED
|
@@ -152,9 +152,13 @@ function createQueryHandle(queryClient, queryKey, queryFn, options) {
|
|
|
152
152
|
setDerivedState,
|
|
153
153
|
handle: {
|
|
154
154
|
getSnapshot: () => toQueryHandleSnapshot(observer.getCurrentResult()),
|
|
155
|
-
subscribe: (listener) =>
|
|
155
|
+
subscribe: (listener) =>
|
|
156
|
+
// QueryObserver subscriptions alone are not enough for browser lifecycle events.
|
|
157
|
+
// TanStack wires focus/reconnect refetching through QueryClient.mount(), so every
|
|
158
|
+
// live QueryHandle subscription must also activate the client lifecycle.
|
|
159
|
+
subscribeToMountedQueryClient(queryClient, () => observer.subscribe((result) => {
|
|
156
160
|
listener(toQueryHandleSnapshot(result));
|
|
157
|
-
}),
|
|
161
|
+
})),
|
|
158
162
|
refetch: async (refetchOptions) => toQueryHandleSnapshot(await observer.refetch(refetchOptions)),
|
|
159
163
|
invalidate: (invalidateOptions) => queryClient.invalidateQueries({
|
|
160
164
|
exact: true,
|
|
@@ -167,6 +171,49 @@ function createQueryHandle(queryClient, queryKey, queryFn, options) {
|
|
|
167
171
|
},
|
|
168
172
|
};
|
|
169
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* Subscribes to a TanStack observer while the owning QueryClient is mounted.
|
|
176
|
+
*
|
|
177
|
+
* Native `useQuery` users normally get this lifecycle from `QueryClientProvider`, which calls
|
|
178
|
+
* `queryClient.mount()` and lets TanStack subscribe to `focusManager` and `onlineManager`.
|
|
179
|
+
* Status Quo Query can be used without a React provider, so the wrapper has to mount the client
|
|
180
|
+
* when a query handle becomes live.
|
|
181
|
+
*
|
|
182
|
+
* Mounting is intentionally scoped to `subscribe(...)` rather than `setupQuery(...)`:
|
|
183
|
+
*
|
|
184
|
+
* - passive reads such as `getSnapshot()` and `getQueryData(...)` should not install global
|
|
185
|
+
* focus/online listeners
|
|
186
|
+
* - short-lived factories should not leave a permanently mounted client behind
|
|
187
|
+
* - TanStack reference-counts `mount()` / `unmount()`, so multiple active handles can safely
|
|
188
|
+
* mount the same client at the same time
|
|
189
|
+
*
|
|
190
|
+
* This is the piece that makes `staleTime` + `refetchOnWindowFocus` behave like native
|
|
191
|
+
* `useQuery`: the observer marks itself stale after the stale timer expires, then the mounted
|
|
192
|
+
* client forwards the next focus event to the query cache so active stale queries refetch.
|
|
193
|
+
*/
|
|
194
|
+
function subscribeToMountedQueryClient(queryClient, subscribe) {
|
|
195
|
+
queryClient.mount();
|
|
196
|
+
let isSubscribed = true;
|
|
197
|
+
let unsubscribe;
|
|
198
|
+
try {
|
|
199
|
+
unsubscribe = subscribe();
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
// Keep mount/unmount balanced if the observer subscription throws before returning cleanup.
|
|
203
|
+
queryClient.unmount();
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
return () => {
|
|
207
|
+
if (!isSubscribed) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
isSubscribed = false;
|
|
211
|
+
unsubscribe();
|
|
212
|
+
// Balance the mount for this specific subscription. TanStack keeps the client mounted while
|
|
213
|
+
// any other active subscription has also called mount().
|
|
214
|
+
queryClient.unmount();
|
|
215
|
+
};
|
|
216
|
+
}
|
|
170
217
|
function bindQueryDependencies(queryHandle, queryKey, dependsOn) {
|
|
171
218
|
const dependencyController = createDependencyController(queryKey, queryHandle.setDerivedState, dependsOn);
|
|
172
219
|
let subscriberCount = 0;
|
package/dist/query.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query.js","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"query.js","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/react/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/react/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Import the React hooks needed to memoize callbacks, hold mutable cache state,
|
|
2
|
+
// and connect an external store to React rendering.
|
|
3
|
+
import { useCallback, useRef, useSyncExternalStore } from 'react';
|
|
4
|
+
// Subscribe a React component to a MutationHandle and return its latest snapshot.
|
|
5
|
+
export function useMutationHandle(
|
|
6
|
+
// Receive the external mutation handle instance to subscribe to.
|
|
7
|
+
mutationHandle) {
|
|
8
|
+
// Count store notifications so we can tell when our cached snapshot is stale.
|
|
9
|
+
const snapshotVersionRef = useRef(0);
|
|
10
|
+
// Cache one client-side snapshot per observed version to keep getSnapshot stable.
|
|
11
|
+
const snapshotCacheRef = useRef(null);
|
|
12
|
+
// Cache the server snapshot separately for the useSyncExternalStore SSR fallback.
|
|
13
|
+
const serverSnapshotCacheRef = useRef(null);
|
|
14
|
+
// Create the subscribe function expected by useSyncExternalStore.
|
|
15
|
+
const subscribe = useCallback(
|
|
16
|
+
// React passes a listener that must run whenever the external store changes.
|
|
17
|
+
(listener) =>
|
|
18
|
+
// Forward the subscription to the mutation handle.
|
|
19
|
+
mutationHandle.subscribe(() => {
|
|
20
|
+
// Bump the version so later reads know the previous cache is outdated.
|
|
21
|
+
snapshotVersionRef.current += 1;
|
|
22
|
+
// Drop the cached client snapshot because the store just changed.
|
|
23
|
+
snapshotCacheRef.current = null;
|
|
24
|
+
// Drop the cached server snapshot for the same reason.
|
|
25
|
+
serverSnapshotCacheRef.current = null;
|
|
26
|
+
// Notify React that it should read a fresh snapshot.
|
|
27
|
+
listener();
|
|
28
|
+
}),
|
|
29
|
+
// Recreate the subscription function only when the handle instance changes.
|
|
30
|
+
[mutationHandle]);
|
|
31
|
+
// Read the current client snapshot in a referentially stable way for React.
|
|
32
|
+
const getSnapshot = useCallback(() => {
|
|
33
|
+
// Read the latest store version number.
|
|
34
|
+
const version = snapshotVersionRef.current;
|
|
35
|
+
// Read the last cached client snapshot, if there is one.
|
|
36
|
+
const cachedSnapshot = snapshotCacheRef.current;
|
|
37
|
+
// Reuse the cached snapshot when it was produced for the current version.
|
|
38
|
+
if (cachedSnapshot && cachedSnapshot.version === version) {
|
|
39
|
+
// Return the cached snapshot so repeated reads in the same render stay stable.
|
|
40
|
+
return cachedSnapshot.snapshot;
|
|
41
|
+
}
|
|
42
|
+
// Ask the mutation handle for the latest snapshot because the cache is empty or stale.
|
|
43
|
+
const snapshot = mutationHandle.getSnapshot();
|
|
44
|
+
// Store the new snapshot together with the version it belongs to.
|
|
45
|
+
snapshotCacheRef.current = {
|
|
46
|
+
snapshot,
|
|
47
|
+
version,
|
|
48
|
+
};
|
|
49
|
+
// Return the freshly read snapshot to React.
|
|
50
|
+
return snapshot;
|
|
51
|
+
}, [mutationHandle]);
|
|
52
|
+
// Read the server snapshot used by React during SSR or hydration fallback paths.
|
|
53
|
+
const getServerSnapshot = useCallback(() => {
|
|
54
|
+
// Read the cached server snapshot, if one was stored earlier.
|
|
55
|
+
const cachedSnapshot = serverSnapshotCacheRef.current;
|
|
56
|
+
// Reuse the cached server snapshot to keep server reads stable.
|
|
57
|
+
if (cachedSnapshot) {
|
|
58
|
+
// Return the cached server snapshot directly.
|
|
59
|
+
return cachedSnapshot;
|
|
60
|
+
}
|
|
61
|
+
// Ask the mutation handle for a snapshot because no server cache exists yet.
|
|
62
|
+
const snapshot = mutationHandle.getSnapshot();
|
|
63
|
+
// Cache that snapshot for the next server read.
|
|
64
|
+
serverSnapshotCacheRef.current = snapshot;
|
|
65
|
+
// Return the freshly read server snapshot.
|
|
66
|
+
return snapshot;
|
|
67
|
+
}, [mutationHandle]);
|
|
68
|
+
// Let React subscribe to the external store and read snapshots through the callbacks above.
|
|
69
|
+
return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=use-mutation-handle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-mutation-handle.js","sourceRoot":"","sources":["../../../src/react/hooks/use-mutation-handle.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,oDAAoD;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAelE,kFAAkF;AAClF,MAAM,UAAU,iBAAiB;AAC/B,iEAAiE;AACjE,cAAyD;IAEzD,8EAA8E;IAC9E,MAAM,kBAAkB,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACrC,kFAAkF;IAClF,MAAM,gBAAgB,GAAG,MAAM,CAAuD,IAAI,CAAC,CAAC;IAC5F,kFAAkF;IAClF,MAAM,sBAAsB,GAAG,MAAM,CACnC,IAAI,CACL,CAAC;IAEF,kEAAkE;IAClE,MAAM,SAAS,GAAG,WAAW;IAC3B,6EAA6E;IAC7E,CAAC,QAAkB,EAAE,EAAE;IACrB,mDAAmD;IACnD,cAAc,CAAC,SAAS,CAAC,GAAG,EAAE;QAC5B,uEAAuE;QACvE,kBAAkB,CAAC,OAAO,IAAI,CAAC,CAAC;QAChC,kEAAkE;QAClE,gBAAgB,CAAC,OAAO,GAAG,IAAI,CAAC;QAChC,uDAAuD;QACvD,sBAAsB,CAAC,OAAO,GAAG,IAAI,CAAC;QACtC,qDAAqD;QACrD,QAAQ,EAAE,CAAC;IACb,CAAC,CAAC;IACJ,4EAA4E;IAC5E,CAAC,cAAc,CAAC,CACjB,CAAC;IAEF,4EAA4E;IAC5E,MAAM,WAAW,GAAG,WAAW,CAAC,GAAG,EAAE;QACnC,wCAAwC;QACxC,MAAM,OAAO,GAAG,kBAAkB,CAAC,OAAO,CAAC;QAC3C,yDAAyD;QACzD,MAAM,cAAc,GAAG,gBAAgB,CAAC,OAAO,CAAC;QAEhD,0EAA0E;QAC1E,IAAI,cAAc,IAAI,cAAc,CAAC,OAAO,KAAK,OAAO,EAAE,CAAC;YACzD,+EAA+E;YAC/E,OAAO,cAAc,CAAC,QAAQ,CAAC;QACjC,CAAC;QAED,uFAAuF;QACvF,MAAM,QAAQ,GAAG,cAAc,CAAC,WAAW,EAAE,CAAC;QAC9C,kEAAkE;QAClE,gBAAgB,CAAC,OAAO,GAAG;YACzB,QAAQ;YACR,OAAO;SACR,CAAC;QAEF,6CAA6C;QAC7C,OAAO,QAAQ,CAAC;IAClB,CAAC,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC;IAErB,iFAAiF;IACjF,MAAM,iBAAiB,GAAG,WAAW,CAAC,GAAG,EAAE;QACzC,8DAA8D;QAC9D,MAAM,cAAc,GAAG,sBAAsB,CAAC,OAAO,CAAC;QAEtD,gEAAgE;QAChE,IAAI,cAAc,EAAE,CAAC;YACnB,8CAA8C;YAC9C,OAAO,cAAc,CAAC;QACxB,CAAC;QAED,6EAA6E;QAC7E,MAAM,QAAQ,GAAG,cAAc,CAAC,WAAW,EAAE,CAAC;QAC9C,gDAAgD;QAChD,sBAAsB,CAAC,OAAO,GAAG,QAAQ,CAAC;QAE1C,2CAA2C;QAC3C,OAAO,QAAQ,CAAC;IAClB,CAAC,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC;IAErB,4FAA4F;IAC5F,OAAO,oBAAoB,CAAC,SAAS,EAAE,WAAW,EAAE,iBAAiB,CAAC,CAAC;AACzE,CAAC"}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { useQueryHandle } from './hooks/index.js';
|
|
1
|
+
export { useQueryHandle, useMutationHandle } from './hooks/index.js';
|
package/dist/react/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { useQueryHandle } from './hooks/index.js';
|
|
1
|
+
export { useQueryHandle, useMutationHandle } from './hooks/index.js';
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/react/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { QueryClient } from '@tanstack/query-core';
|
|
1
|
+
import { focusManager, QueryClient } from '@tanstack/query-core';
|
|
2
2
|
|
|
3
3
|
import { isQueryLoading, setupQuery, toQueryMetaState } from '../query';
|
|
4
4
|
|
|
@@ -43,6 +43,40 @@ describe('Query Service', () => {
|
|
|
43
43
|
expect(statuses).toContain('success');
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
+
it('refetches stale subscribed queries when window focus returns', async () => {
|
|
47
|
+
jest.useFakeTimers();
|
|
48
|
+
|
|
49
|
+
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: 0 } } });
|
|
50
|
+
const createQuery = setupQuery(queryClient);
|
|
51
|
+
const queryFn = jest
|
|
52
|
+
.fn()
|
|
53
|
+
.mockResolvedValueOnce('initial')
|
|
54
|
+
.mockResolvedValueOnce('focused');
|
|
55
|
+
const service = createQuery(['demo', 'focus'], queryFn, { staleTime: 5_000 });
|
|
56
|
+
const unsubscribe = service.subscribe(() => undefined);
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
await flushTasks();
|
|
60
|
+
|
|
61
|
+
expect(queryFn).toHaveBeenCalledTimes(1);
|
|
62
|
+
expect(service.getSnapshot().data).toBe('initial');
|
|
63
|
+
|
|
64
|
+
jest.advanceTimersByTime(5_001);
|
|
65
|
+
await flushTasks();
|
|
66
|
+
|
|
67
|
+
focusManager.setFocused(false);
|
|
68
|
+
focusManager.setFocused(true);
|
|
69
|
+
await flushTasks();
|
|
70
|
+
|
|
71
|
+
expect(queryFn).toHaveBeenCalledTimes(2);
|
|
72
|
+
expect(service.getSnapshot().data).toBe('focused');
|
|
73
|
+
} finally {
|
|
74
|
+
unsubscribe();
|
|
75
|
+
focusManager.setFocused(undefined);
|
|
76
|
+
jest.useRealTimers();
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
46
80
|
it('invalidates its own query key exactly', async () => {
|
|
47
81
|
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: 0 } } });
|
|
48
82
|
const invalidateQueriesSpy = jest.spyOn(queryClient, 'invalidateQueries');
|
package/src/mutation.ts
CHANGED
|
@@ -32,7 +32,7 @@ export type MutationStatus = TanstackMutationStatus;
|
|
|
32
32
|
/**
|
|
33
33
|
* Represents a stable snapshot of the mutation service's state.
|
|
34
34
|
*/
|
|
35
|
-
export interface
|
|
35
|
+
export interface MutationHandleSnapshot<TData = unknown, TError = Error, TVariables = void> {
|
|
36
36
|
// The data returned from a successful mutation.
|
|
37
37
|
data: TData | undefined;
|
|
38
38
|
// The error object if the mutation failed.
|
|
@@ -54,17 +54,17 @@ export interface MutationServiceSnapshot<TData = unknown, TError = Error, TVaria
|
|
|
54
54
|
/**
|
|
55
55
|
* Defines the public API for a mutation service.
|
|
56
56
|
*/
|
|
57
|
-
export interface
|
|
57
|
+
export interface MutationHandle<
|
|
58
58
|
TData = unknown,
|
|
59
59
|
TError = Error,
|
|
60
60
|
TVariables = void,
|
|
61
61
|
TOnMutateResult = unknown,
|
|
62
62
|
> {
|
|
63
63
|
// Returns the current state snapshot of the mutation.
|
|
64
|
-
getSnapshot: () =>
|
|
64
|
+
getSnapshot: () => MutationHandleSnapshot<TData, TError, TVariables>;
|
|
65
65
|
// Subscribes a listener to state changes; returns an unsubscribe function.
|
|
66
66
|
subscribe: (
|
|
67
|
-
listener: (snapshot:
|
|
67
|
+
listener: (snapshot: MutationHandleSnapshot<TData, TError, TVariables>) => void
|
|
68
68
|
) => () => void;
|
|
69
69
|
// Triggers the mutation with the given variables and optional lifecycle callbacks.
|
|
70
70
|
mutate: (
|
|
@@ -80,7 +80,7 @@ export interface MutationService<
|
|
|
80
80
|
/**
|
|
81
81
|
* Configuration options for creating a mutation service, excluding the mutation function itself.
|
|
82
82
|
*/
|
|
83
|
-
export type
|
|
83
|
+
export type MutationHandleOptions<
|
|
84
84
|
TData = unknown,
|
|
85
85
|
TError = Error,
|
|
86
86
|
TVariables = void,
|
|
@@ -95,8 +95,8 @@ export interface CreateUntrackedMutation {
|
|
|
95
95
|
// The asynchronous function that performs the mutation.
|
|
96
96
|
mutationFn: MutationFunction<TData, TVariables>,
|
|
97
97
|
// Optional configuration for behavior like retry or lifecycle hooks.
|
|
98
|
-
options?:
|
|
99
|
-
):
|
|
98
|
+
options?: MutationHandleOptions<TData, TError, TVariables, TOnMutateResult>
|
|
99
|
+
): MutationHandle<TData, TError, TVariables, TOnMutateResult>;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
/**
|
|
@@ -106,13 +106,13 @@ export interface CreateUntrackedMutation {
|
|
|
106
106
|
* options only describe how the facade should derive dependency values and when it should
|
|
107
107
|
* invalidate matching tracked queries after the mutation lifecycle settles.
|
|
108
108
|
*/
|
|
109
|
-
export interface
|
|
109
|
+
export interface TrackedMutationHandleOptions<
|
|
110
110
|
TDeps extends TrackedDependencyRecord = TrackedDependencyRecord,
|
|
111
111
|
TData = unknown,
|
|
112
112
|
TError = Error,
|
|
113
113
|
TVariables = void,
|
|
114
114
|
TOnMutateResult = unknown,
|
|
115
|
-
> extends
|
|
115
|
+
> extends MutationHandleOptions<TData, TError, TVariables, TOnMutateResult> {
|
|
116
116
|
// Optional dependency keys used by the default variable reader.
|
|
117
117
|
dependencyKeys?: readonly (keyof TDeps & string)[];
|
|
118
118
|
// Optional custom resolver when mutation variables do not expose dependency fields directly.
|
|
@@ -135,8 +135,8 @@ export interface CreateMutation {
|
|
|
135
135
|
TOnMutateResult = unknown,
|
|
136
136
|
>(
|
|
137
137
|
mutationFn: MutationFunction<TData, TVariables>,
|
|
138
|
-
options?:
|
|
139
|
-
):
|
|
138
|
+
options?: TrackedMutationHandleOptions<TDeps, TData, TError, TVariables, TOnMutateResult>
|
|
139
|
+
): MutationHandle<TData, TError, TVariables, TOnMutateResult>;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
/**
|
|
@@ -151,9 +151,9 @@ export function setupMutation(queryClient: QueryClient): CreateUntrackedMutation
|
|
|
151
151
|
TOnMutateResult = unknown,
|
|
152
152
|
>(
|
|
153
153
|
mutationFn: MutationFunction<TData, TVariables>,
|
|
154
|
-
options?:
|
|
155
|
-
):
|
|
156
|
-
return
|
|
154
|
+
options?: MutationHandleOptions<TData, TError, TVariables, TOnMutateResult>
|
|
155
|
+
): MutationHandle<TData, TError, TVariables, TOnMutateResult> {
|
|
156
|
+
return createMutationHandle(queryClient, mutationFn, options);
|
|
157
157
|
};
|
|
158
158
|
}
|
|
159
159
|
|
|
@@ -177,8 +177,8 @@ export function setupTrackedMutation(
|
|
|
177
177
|
TOnMutateResult = unknown,
|
|
178
178
|
>(
|
|
179
179
|
mutationFn: MutationFunction<TData, TVariables>,
|
|
180
|
-
options?:
|
|
181
|
-
):
|
|
180
|
+
options?: TrackedMutationHandleOptions<TDeps, TData, TError, TVariables, TOnMutateResult>
|
|
181
|
+
): MutationHandle<TData, TError, TVariables, TOnMutateResult> {
|
|
182
182
|
// Split tracked-only options from the underlying TanStack mutation observer options.
|
|
183
183
|
const {
|
|
184
184
|
dependencyKeys,
|
|
@@ -187,8 +187,8 @@ export function setupTrackedMutation(
|
|
|
187
187
|
resolveDependencies,
|
|
188
188
|
...mutationOptions
|
|
189
189
|
} = options ?? {};
|
|
190
|
-
// Reuse the normal mutation
|
|
191
|
-
const
|
|
190
|
+
// Reuse the normal mutation handle so snapshots and subscription behavior stay identical.
|
|
191
|
+
const handle = createMutationHandle(queryClient, mutationFn, mutationOptions);
|
|
192
192
|
// The paired helper injects dependency keys here, while standalone tracked mutations can
|
|
193
193
|
// still provide them directly or bypass them with a custom resolver.
|
|
194
194
|
const resolvedDependencyKeys = (dependencyKeys ?? defaultDependencyKeys);
|
|
@@ -220,12 +220,12 @@ export function setupTrackedMutation(
|
|
|
220
220
|
};
|
|
221
221
|
|
|
222
222
|
return {
|
|
223
|
-
...
|
|
223
|
+
...handle,
|
|
224
224
|
mutate: async (variables, mutateOptions) => {
|
|
225
225
|
try {
|
|
226
226
|
// Let TanStack finish the mutation first so its own callbacks and state machine remain
|
|
227
227
|
// authoritative. The facade only coordinates the follow-up invalidation.
|
|
228
|
-
const result = await
|
|
228
|
+
const result = await handle.mutate(variables, mutateOptions);
|
|
229
229
|
|
|
230
230
|
if (invalidateOn === 'success' || invalidateOn === 'settled') {
|
|
231
231
|
await invalidateTrackedQueries(variables);
|
|
@@ -252,9 +252,9 @@ export function setupTrackedMutation(
|
|
|
252
252
|
/**
|
|
253
253
|
* Internal helper to transform a raw Tanstack mutation result into our public snapshot format.
|
|
254
254
|
*/
|
|
255
|
-
function
|
|
255
|
+
function toMutationHandleSnapshot<TData, TError, TVariables, TOnMutateResult>(
|
|
256
256
|
result: MutationObserverResult<TData, TError, TVariables, TOnMutateResult>
|
|
257
|
-
):
|
|
257
|
+
): MutationHandleSnapshot<TData, TError, TVariables> {
|
|
258
258
|
// Extract and return the relevant fields for the UI or other services.
|
|
259
259
|
return {
|
|
260
260
|
data: result.data,
|
|
@@ -268,7 +268,7 @@ function toMutationServiceSnapshot<TData, TError, TVariables, TOnMutateResult>(
|
|
|
268
268
|
};
|
|
269
269
|
}
|
|
270
270
|
|
|
271
|
-
function
|
|
271
|
+
function createMutationHandle<
|
|
272
272
|
TData = unknown,
|
|
273
273
|
TError = Error,
|
|
274
274
|
TVariables = void,
|
|
@@ -276,8 +276,8 @@ function createMutationService<
|
|
|
276
276
|
>(
|
|
277
277
|
queryClient: QueryClient,
|
|
278
278
|
mutationFn: MutationFunction<TData, TVariables>,
|
|
279
|
-
options?:
|
|
280
|
-
):
|
|
279
|
+
options?: MutationHandleOptions<TData, TError, TVariables, TOnMutateResult>
|
|
280
|
+
): MutationHandle<TData, TError, TVariables, TOnMutateResult> {
|
|
281
281
|
// Keep the original mutation implementation in one place so tracked and untracked mutations
|
|
282
282
|
// always expose the same observer-backed runtime behavior.
|
|
283
283
|
const observer = new MutationObserver<TData, TError, TVariables, TOnMutateResult>(queryClient, {
|
|
@@ -286,10 +286,10 @@ function createMutationService<
|
|
|
286
286
|
});
|
|
287
287
|
|
|
288
288
|
return {
|
|
289
|
-
getSnapshot: () =>
|
|
289
|
+
getSnapshot: () => toMutationHandleSnapshot(observer.getCurrentResult()),
|
|
290
290
|
subscribe: (listener) =>
|
|
291
291
|
observer.subscribe((result) => {
|
|
292
|
-
listener(
|
|
292
|
+
listener(toMutationHandleSnapshot(result));
|
|
293
293
|
}),
|
|
294
294
|
mutate: (variables, mutateOptions) => observer.mutate(variables, mutateOptions),
|
|
295
295
|
reset: () => observer.reset(),
|
package/src/provider.ts
CHANGED
|
@@ -8,8 +8,8 @@ import {
|
|
|
8
8
|
import {
|
|
9
9
|
type CreateMutation,
|
|
10
10
|
type CreateUntrackedMutation,
|
|
11
|
-
type
|
|
12
|
-
type
|
|
11
|
+
type MutationHandle,
|
|
12
|
+
type TrackedMutationHandleOptions,
|
|
13
13
|
setupMutation,
|
|
14
14
|
setupTrackedMutation,
|
|
15
15
|
} from './mutation.js';
|
|
@@ -29,7 +29,7 @@ export interface CreateMutationWithDefaults<TDependencyKey extends string> {
|
|
|
29
29
|
<TData = unknown, TError = Error, TVariables = void, TOnMutateResult = unknown>(
|
|
30
30
|
mutationFn: MutationFunction<TData, TVariables>,
|
|
31
31
|
options?: Omit<
|
|
32
|
-
|
|
32
|
+
TrackedMutationHandleOptions<
|
|
33
33
|
Record<TDependencyKey, TrackedDependencyValue>,
|
|
34
34
|
TData,
|
|
35
35
|
TError,
|
|
@@ -38,7 +38,7 @@ export interface CreateMutationWithDefaults<TDependencyKey extends string> {
|
|
|
38
38
|
>,
|
|
39
39
|
'dependencyKeys'
|
|
40
40
|
>
|
|
41
|
-
):
|
|
41
|
+
): MutationHandle<TData, TError, TVariables, TOnMutateResult>;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
/**
|
|
@@ -54,13 +54,13 @@ export interface CreateQueryAndMutation {
|
|
|
54
54
|
* Defines the public API for the query manager facade.
|
|
55
55
|
*/
|
|
56
56
|
export interface QueryManager {
|
|
57
|
-
// Factory for creating a dependency-tracked mutation
|
|
57
|
+
// Factory for creating a dependency-tracked mutation handle within the context of this provider.
|
|
58
58
|
createMutation: CreateMutation;
|
|
59
59
|
// Factory for creating a dependency-tracked query handle within the context of this provider.
|
|
60
60
|
createQuery: CreateQuery;
|
|
61
61
|
// Factory for creating an untracked query handle within the context of this provider.
|
|
62
62
|
createUntrackedQuery: CreateUntrackedQuery;
|
|
63
|
-
// Factory for creating an untracked mutation
|
|
63
|
+
// Factory for creating an untracked mutation handle within the context of this provider.
|
|
64
64
|
createUntrackedMutation: CreateUntrackedMutation;
|
|
65
65
|
// Convenience helper that shares dependency keys between tracked queries and mutations.
|
|
66
66
|
createQueryAndMutation: CreateQueryAndMutation;
|
|
@@ -126,7 +126,7 @@ export function setupQueryManager(queryClient: QueryClient): QueryManager {
|
|
|
126
126
|
> = <TData = unknown, TError = Error, TVariables = void, TOnMutateResult = unknown>(
|
|
127
127
|
mutationFn: MutationFunction<TData, TVariables>,
|
|
128
128
|
options?: Omit<
|
|
129
|
-
|
|
129
|
+
TrackedMutationHandleOptions<
|
|
130
130
|
Record<TDependencyKeys[number], TrackedDependencyValue>,
|
|
131
131
|
TData,
|
|
132
132
|
TError,
|
package/src/query.ts
CHANGED
|
@@ -424,9 +424,14 @@ function createQueryHandle<
|
|
|
424
424
|
handle: {
|
|
425
425
|
getSnapshot: () => toQueryHandleSnapshot(observer.getCurrentResult()),
|
|
426
426
|
subscribe: (listener) =>
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
427
|
+
// QueryObserver subscriptions alone are not enough for browser lifecycle events.
|
|
428
|
+
// TanStack wires focus/reconnect refetching through QueryClient.mount(), so every
|
|
429
|
+
// live QueryHandle subscription must also activate the client lifecycle.
|
|
430
|
+
subscribeToMountedQueryClient(queryClient, () =>
|
|
431
|
+
observer.subscribe((result) => {
|
|
432
|
+
listener(toQueryHandleSnapshot(result));
|
|
433
|
+
})
|
|
434
|
+
),
|
|
430
435
|
refetch: async (refetchOptions) =>
|
|
431
436
|
toQueryHandleSnapshot(await observer.refetch(refetchOptions)),
|
|
432
437
|
invalidate: (invalidateOptions) =>
|
|
@@ -445,6 +450,56 @@ function createQueryHandle<
|
|
|
445
450
|
};
|
|
446
451
|
}
|
|
447
452
|
|
|
453
|
+
/**
|
|
454
|
+
* Subscribes to a TanStack observer while the owning QueryClient is mounted.
|
|
455
|
+
*
|
|
456
|
+
* Native `useQuery` users normally get this lifecycle from `QueryClientProvider`, which calls
|
|
457
|
+
* `queryClient.mount()` and lets TanStack subscribe to `focusManager` and `onlineManager`.
|
|
458
|
+
* Status Quo Query can be used without a React provider, so the wrapper has to mount the client
|
|
459
|
+
* when a query handle becomes live.
|
|
460
|
+
*
|
|
461
|
+
* Mounting is intentionally scoped to `subscribe(...)` rather than `setupQuery(...)`:
|
|
462
|
+
*
|
|
463
|
+
* - passive reads such as `getSnapshot()` and `getQueryData(...)` should not install global
|
|
464
|
+
* focus/online listeners
|
|
465
|
+
* - short-lived factories should not leave a permanently mounted client behind
|
|
466
|
+
* - TanStack reference-counts `mount()` / `unmount()`, so multiple active handles can safely
|
|
467
|
+
* mount the same client at the same time
|
|
468
|
+
*
|
|
469
|
+
* This is the piece that makes `staleTime` + `refetchOnWindowFocus` behave like native
|
|
470
|
+
* `useQuery`: the observer marks itself stale after the stale timer expires, then the mounted
|
|
471
|
+
* client forwards the next focus event to the query cache so active stale queries refetch.
|
|
472
|
+
*/
|
|
473
|
+
function subscribeToMountedQueryClient(
|
|
474
|
+
queryClient: QueryClient,
|
|
475
|
+
subscribe: () => () => void
|
|
476
|
+
): () => void {
|
|
477
|
+
queryClient.mount();
|
|
478
|
+
|
|
479
|
+
let isSubscribed = true;
|
|
480
|
+
let unsubscribe: () => void;
|
|
481
|
+
|
|
482
|
+
try {
|
|
483
|
+
unsubscribe = subscribe();
|
|
484
|
+
} catch (error) {
|
|
485
|
+
// Keep mount/unmount balanced if the observer subscription throws before returning cleanup.
|
|
486
|
+
queryClient.unmount();
|
|
487
|
+
throw error;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return () => {
|
|
491
|
+
if (!isSubscribed) {
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
isSubscribed = false;
|
|
496
|
+
unsubscribe();
|
|
497
|
+
// Balance the mount for this specific subscription. TanStack keeps the client mounted while
|
|
498
|
+
// any other active subscription has also called mount().
|
|
499
|
+
queryClient.unmount();
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
448
503
|
function bindQueryDependencies<
|
|
449
504
|
TSources extends readonly unknown[] = [],
|
|
450
505
|
TQueryFnData = unknown,
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import React, { act } from 'react';
|
|
2
|
+
import { createRoot } from 'react-dom/client';
|
|
3
|
+
import { QueryClient } from '@tanstack/query-core';
|
|
4
|
+
|
|
5
|
+
import { setupMutation } from '../../mutation.js';
|
|
6
|
+
import { useMutationHandle } from '../hooks/use-mutation-handle.js';
|
|
7
|
+
|
|
8
|
+
import type { MutationHandle, MutationHandleSnapshot } from '../../mutation.js';
|
|
9
|
+
|
|
10
|
+
declare global {
|
|
11
|
+
var IS_REACT_ACT_ENVIRONMENT: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe('useMutationHandle', () => {
|
|
15
|
+
let container: HTMLDivElement;
|
|
16
|
+
|
|
17
|
+
beforeAll(() => {
|
|
18
|
+
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
container = document.createElement('div');
|
|
23
|
+
document.body.appendChild(container);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
container.remove();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('renders the latest mutation snapshot', async () => {
|
|
31
|
+
const queryClient = new QueryClient({ defaultOptions: { mutations: { retry: 0 } } });
|
|
32
|
+
const createMutation = setupMutation(queryClient);
|
|
33
|
+
const mutationFn = jest.fn((_payload: { name: string }) =>
|
|
34
|
+
Promise.resolve({ ok: true as const })
|
|
35
|
+
);
|
|
36
|
+
const handle = createMutation(mutationFn);
|
|
37
|
+
const statuses: string[] = [];
|
|
38
|
+
|
|
39
|
+
const Consumer = () => {
|
|
40
|
+
const snapshot = useMutationHandle(handle);
|
|
41
|
+
statuses.push(snapshot.status);
|
|
42
|
+
|
|
43
|
+
return <span>{snapshot.status}</span>;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const root = createRoot(container);
|
|
47
|
+
|
|
48
|
+
await act(async () => {
|
|
49
|
+
root.render(<Consumer />);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
expect(container.textContent).toBe('idle');
|
|
53
|
+
|
|
54
|
+
await act(async () => {
|
|
55
|
+
await handle.mutate({ name: 'Ada' });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
expect(container.textContent).toBe('success');
|
|
59
|
+
expect(statuses).toContain('idle');
|
|
60
|
+
expect(statuses).toContain('pending');
|
|
61
|
+
expect(statuses).toContain('success');
|
|
62
|
+
|
|
63
|
+
await act(async () => {
|
|
64
|
+
root.unmount();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('cleans up the store subscription on unmount', async () => {
|
|
69
|
+
const snapshot: MutationHandleSnapshot<{ ok: true }, Error, void> = {
|
|
70
|
+
data: { ok: true },
|
|
71
|
+
error: null,
|
|
72
|
+
status: 'success',
|
|
73
|
+
variables: undefined,
|
|
74
|
+
isError: false,
|
|
75
|
+
isIdle: false,
|
|
76
|
+
isPending: false,
|
|
77
|
+
isSuccess: true,
|
|
78
|
+
};
|
|
79
|
+
const unsubscribe = jest.fn();
|
|
80
|
+
const handle: MutationHandle<{ ok: true }, Error, void> = {
|
|
81
|
+
getSnapshot: () => snapshot,
|
|
82
|
+
subscribe: jest.fn(() => unsubscribe),
|
|
83
|
+
mutate: jest.fn(async () => ({ ok: true as const })),
|
|
84
|
+
reset: jest.fn(),
|
|
85
|
+
unsafe_getResult: jest.fn(),
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const root = createRoot(container);
|
|
89
|
+
|
|
90
|
+
const Consumer = () => {
|
|
91
|
+
useMutationHandle(handle);
|
|
92
|
+
return <span>ready</span>;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
await act(async () => {
|
|
96
|
+
root.render(<Consumer />);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
expect(handle.subscribe).toHaveBeenCalledTimes(1);
|
|
100
|
+
|
|
101
|
+
await act(async () => {
|
|
102
|
+
root.unmount();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
expect(unsubscribe).toHaveBeenCalledTimes(1);
|
|
106
|
+
});
|
|
107
|
+
});
|
package/src/react/hooks/index.ts
CHANGED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// Import the React hooks needed to memoize callbacks, hold mutable cache state,
|
|
2
|
+
// and connect an external store to React rendering.
|
|
3
|
+
import { useCallback, useRef, useSyncExternalStore } from 'react';
|
|
4
|
+
|
|
5
|
+
// Import the mutation-handle contract and the stable snapshot shape the hook returns.
|
|
6
|
+
import type { MutationHandle, MutationHandleSnapshot } from '../../mutation.js';
|
|
7
|
+
// Describe the no-argument listener shape expected by useSyncExternalStore.
|
|
8
|
+
type Listener = () => void;
|
|
9
|
+
|
|
10
|
+
// Store one cached snapshot together with the store version it belongs to.
|
|
11
|
+
type SnapshotCacheEntry<TData, TError, TVariables> = {
|
|
12
|
+
// Hold the snapshot returned by the mutation handle for this version.
|
|
13
|
+
snapshot: MutationHandleSnapshot<TData, TError, TVariables>;
|
|
14
|
+
// Track which subscription version produced the cached snapshot.
|
|
15
|
+
version: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Subscribe a React component to a MutationHandle and return its latest snapshot.
|
|
19
|
+
export function useMutationHandle<TData, TError = Error, TVariables = void>(
|
|
20
|
+
// Receive the external mutation handle instance to subscribe to.
|
|
21
|
+
mutationHandle: MutationHandle<TData, TError, TVariables>
|
|
22
|
+
) {
|
|
23
|
+
// Count store notifications so we can tell when our cached snapshot is stale.
|
|
24
|
+
const snapshotVersionRef = useRef(0);
|
|
25
|
+
// Cache one client-side snapshot per observed version to keep getSnapshot stable.
|
|
26
|
+
const snapshotCacheRef = useRef<SnapshotCacheEntry<TData, TError, TVariables> | null>(null);
|
|
27
|
+
// Cache the server snapshot separately for the useSyncExternalStore SSR fallback.
|
|
28
|
+
const serverSnapshotCacheRef = useRef<MutationHandleSnapshot<TData, TError, TVariables> | null>(
|
|
29
|
+
null
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
// Create the subscribe function expected by useSyncExternalStore.
|
|
33
|
+
const subscribe = useCallback(
|
|
34
|
+
// React passes a listener that must run whenever the external store changes.
|
|
35
|
+
(listener: Listener) =>
|
|
36
|
+
// Forward the subscription to the mutation handle.
|
|
37
|
+
mutationHandle.subscribe(() => {
|
|
38
|
+
// Bump the version so later reads know the previous cache is outdated.
|
|
39
|
+
snapshotVersionRef.current += 1;
|
|
40
|
+
// Drop the cached client snapshot because the store just changed.
|
|
41
|
+
snapshotCacheRef.current = null;
|
|
42
|
+
// Drop the cached server snapshot for the same reason.
|
|
43
|
+
serverSnapshotCacheRef.current = null;
|
|
44
|
+
// Notify React that it should read a fresh snapshot.
|
|
45
|
+
listener();
|
|
46
|
+
}),
|
|
47
|
+
// Recreate the subscription function only when the handle instance changes.
|
|
48
|
+
[mutationHandle]
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Read the current client snapshot in a referentially stable way for React.
|
|
52
|
+
const getSnapshot = useCallback(() => {
|
|
53
|
+
// Read the latest store version number.
|
|
54
|
+
const version = snapshotVersionRef.current;
|
|
55
|
+
// Read the last cached client snapshot, if there is one.
|
|
56
|
+
const cachedSnapshot = snapshotCacheRef.current;
|
|
57
|
+
|
|
58
|
+
// Reuse the cached snapshot when it was produced for the current version.
|
|
59
|
+
if (cachedSnapshot && cachedSnapshot.version === version) {
|
|
60
|
+
// Return the cached snapshot so repeated reads in the same render stay stable.
|
|
61
|
+
return cachedSnapshot.snapshot;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Ask the mutation handle for the latest snapshot because the cache is empty or stale.
|
|
65
|
+
const snapshot = mutationHandle.getSnapshot();
|
|
66
|
+
// Store the new snapshot together with the version it belongs to.
|
|
67
|
+
snapshotCacheRef.current = {
|
|
68
|
+
snapshot,
|
|
69
|
+
version,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Return the freshly read snapshot to React.
|
|
73
|
+
return snapshot;
|
|
74
|
+
}, [mutationHandle]);
|
|
75
|
+
|
|
76
|
+
// Read the server snapshot used by React during SSR or hydration fallback paths.
|
|
77
|
+
const getServerSnapshot = useCallback(() => {
|
|
78
|
+
// Read the cached server snapshot, if one was stored earlier.
|
|
79
|
+
const cachedSnapshot = serverSnapshotCacheRef.current;
|
|
80
|
+
|
|
81
|
+
// Reuse the cached server snapshot to keep server reads stable.
|
|
82
|
+
if (cachedSnapshot) {
|
|
83
|
+
// Return the cached server snapshot directly.
|
|
84
|
+
return cachedSnapshot;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Ask the mutation handle for a snapshot because no server cache exists yet.
|
|
88
|
+
const snapshot = mutationHandle.getSnapshot();
|
|
89
|
+
// Cache that snapshot for the next server read.
|
|
90
|
+
serverSnapshotCacheRef.current = snapshot;
|
|
91
|
+
|
|
92
|
+
// Return the freshly read server snapshot.
|
|
93
|
+
return snapshot;
|
|
94
|
+
}, [mutationHandle]);
|
|
95
|
+
|
|
96
|
+
// Let React subscribe to the external store and read snapshots through the callbacks above.
|
|
97
|
+
return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
98
|
+
}
|
package/src/react/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { useQueryHandle } from './hooks/index.js';
|
|
1
|
+
export { useQueryHandle, useMutationHandle } from './hooks/index.js';
|