@veams/status-quo-query 0.11.0 → 0.13.0
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/README.md +166 -34
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +1 -1
- 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 +3 -2
- package/dist/provider.js +2 -0
- package/dist/provider.js.map +1 -1
- package/dist/query.d.ts +26 -15
- package/dist/query.js +40 -31
- package/dist/query.js.map +1 -1
- package/dist/react/hooks/index.d.ts +2 -1
- package/dist/react/hooks/index.js +2 -1
- 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/hooks/use-query-handle.d.ts +2 -0
- package/dist/react/hooks/use-query-handle.js +71 -0
- package/dist/react/hooks/use-query-handle.js.map +1 -0
- package/dist/react/hooks/use-query-subscription.d.ts +1 -2
- package/dist/react/hooks/use-query-subscription.js +1 -72
- package/dist/react/hooks/use-query-subscription.js.map +1 -1
- 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 -8
- package/src/__tests__/provider.spec.ts +8 -0
- package/src/index.ts +0 -2
- package/src/mutation.ts +27 -27
- package/src/provider.ts +13 -9
- package/src/query.ts +84 -64
- package/src/react/__tests__/use-mutation-handle.spec.tsx +107 -0
- package/src/react/__tests__/{query-subscription.spec.tsx → use-query-handle.spec.tsx} +7 -7
- package/src/react/hooks/index.ts +2 -1
- package/src/react/hooks/use-mutation-handle.ts +98 -0
- package/src/react/hooks/{use-query-subscription.ts → use-query-handle.ts} +19 -21
- package/src/react/index.ts +1 -1
- package/dist/query-registry.d.ts +0 -9
- package/dist/query-registry.js +0 -28
- package/dist/query-registry.js.map +0 -1
- package/src/__tests__/query-registry.spec.ts +0 -101
- package/src/query-registry.ts +0 -52
|
@@ -10,6 +10,7 @@ describe('Query Manager API', () => {
|
|
|
10
10
|
const cancelQueriesSpy = jest.spyOn(queryClient, 'cancelQueries');
|
|
11
11
|
const resetQueriesSpy = jest.spyOn(queryClient, 'resetQueries');
|
|
12
12
|
const removeQueriesSpy = jest.spyOn(queryClient, 'removeQueries');
|
|
13
|
+
const getQueryStateSpy = jest.spyOn(queryClient, 'getQueryState');
|
|
13
14
|
const fetchUser = jest.fn().mockResolvedValue({ id: 7 });
|
|
14
15
|
const fetchQuerySpy = jest.spyOn(queryClient, 'fetchQuery');
|
|
15
16
|
const manager = setupQueryManager(queryClient);
|
|
@@ -17,6 +18,12 @@ describe('Query Manager API', () => {
|
|
|
17
18
|
manager.setQueryData<{ id: number }>(['user', 42], { id: 42 });
|
|
18
19
|
|
|
19
20
|
expect(manager.getQueryData<{ id: number }>(['user', 42])).toEqual({ id: 42 });
|
|
21
|
+
expect(manager.getQueryState(['user', 42])).toEqual(
|
|
22
|
+
expect.objectContaining({
|
|
23
|
+
data: { id: 42 },
|
|
24
|
+
status: 'success',
|
|
25
|
+
})
|
|
26
|
+
);
|
|
20
27
|
expect(manager.unsafe_getClient()).toBe(queryClient);
|
|
21
28
|
await expect(
|
|
22
29
|
manager.fetchQuery({
|
|
@@ -37,6 +44,7 @@ describe('Query Manager API', () => {
|
|
|
37
44
|
queryFn: fetchUser,
|
|
38
45
|
staleTime: 60_000,
|
|
39
46
|
});
|
|
47
|
+
expect(getQueryStateSpy).toHaveBeenCalledWith(['user', 42]);
|
|
40
48
|
expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['user'] });
|
|
41
49
|
expect(refetchQueriesSpy).toHaveBeenCalledWith({ queryKey: ['user'] });
|
|
42
50
|
expect(cancelQueriesSpy).toHaveBeenCalledWith({ queryKey: ['user'] });
|
package/src/index.ts
CHANGED
|
@@ -4,8 +4,6 @@ export * from './mutation.js';
|
|
|
4
4
|
export * from './query.js';
|
|
5
5
|
// Re-export all provider-related types and functions for cache management.
|
|
6
6
|
export * from './provider.js';
|
|
7
|
-
// Re-export query registry helpers for memoizing query services by key.
|
|
8
|
-
export * from './query-registry.js';
|
|
9
7
|
// Re-export tracked dependency types used by the additive tracked facade.
|
|
10
8
|
export type {
|
|
11
9
|
TrackedDependencyRecord,
|
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
|
-
// Factory for creating a dependency-tracked query
|
|
59
|
+
// Factory for creating a dependency-tracked query handle within the context of this provider.
|
|
60
60
|
createQuery: CreateQuery;
|
|
61
|
-
// Factory for creating an untracked query
|
|
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;
|
|
@@ -70,6 +70,8 @@ export interface QueryManager {
|
|
|
70
70
|
fetchQuery: QueryClient['fetchQuery'];
|
|
71
71
|
// Synchronously retrieves a snapshot of the current query data.
|
|
72
72
|
getQueryData: QueryClient['getQueryData'];
|
|
73
|
+
// Synchronously retrieves the raw TanStack query state for one query key.
|
|
74
|
+
getQueryState: QueryClient['getQueryState'];
|
|
73
75
|
// Marks queries as invalid to trigger a refetch if they are active.
|
|
74
76
|
invalidateQueries: QueryClient['invalidateQueries'];
|
|
75
77
|
// Forces a refetch of queries matching the specified filters.
|
|
@@ -124,7 +126,7 @@ export function setupQueryManager(queryClient: QueryClient): QueryManager {
|
|
|
124
126
|
> = <TData = unknown, TError = Error, TVariables = void, TOnMutateResult = unknown>(
|
|
125
127
|
mutationFn: MutationFunction<TData, TVariables>,
|
|
126
128
|
options?: Omit<
|
|
127
|
-
|
|
129
|
+
TrackedMutationHandleOptions<
|
|
128
130
|
Record<TDependencyKeys[number], TrackedDependencyValue>,
|
|
129
131
|
TData,
|
|
130
132
|
TError,
|
|
@@ -155,6 +157,8 @@ export function setupQueryManager(queryClient: QueryClient): QueryManager {
|
|
|
155
157
|
fetchQuery: queryClient.fetchQuery.bind(queryClient),
|
|
156
158
|
// Proxy for retrieving query data with this client context.
|
|
157
159
|
getQueryData: queryClient.getQueryData.bind(queryClient),
|
|
160
|
+
// Proxy for retrieving raw query state with this client context.
|
|
161
|
+
getQueryState: queryClient.getQueryState.bind(queryClient),
|
|
158
162
|
// Proxy for invalidating queries with this client context.
|
|
159
163
|
invalidateQueries: queryClient.invalidateQueries.bind(queryClient),
|
|
160
164
|
// Proxy for refetching queries with this client context.
|
package/src/query.ts
CHANGED
|
@@ -36,9 +36,9 @@ export type QueryFetchStatus = FetchStatus;
|
|
|
36
36
|
export type QueryStatus = TanstackQueryStatus;
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
|
-
* Represents a stable snapshot of
|
|
39
|
+
* Represents a stable snapshot of one query handle's state.
|
|
40
40
|
*/
|
|
41
|
-
export interface
|
|
41
|
+
export interface QueryHandleSnapshot<TData, TError> {
|
|
42
42
|
// The data retrieved from a successful query.
|
|
43
43
|
data: TData | undefined;
|
|
44
44
|
// The error object if the query failed.
|
|
@@ -57,6 +57,14 @@ export interface QueryServiceSnapshot<TData, TError> {
|
|
|
57
57
|
isSuccess: boolean;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Represents the lightweight data/error read model for one query handle.
|
|
62
|
+
*/
|
|
63
|
+
export interface QueryHandleData<TData, TError> {
|
|
64
|
+
data: TData | undefined;
|
|
65
|
+
error: TError | null;
|
|
66
|
+
}
|
|
67
|
+
|
|
60
68
|
/**
|
|
61
69
|
* Defines a subset of query state containing only the status and fetch status.
|
|
62
70
|
*/
|
|
@@ -66,15 +74,15 @@ export interface QueryMetaState {
|
|
|
66
74
|
}
|
|
67
75
|
|
|
68
76
|
/**
|
|
69
|
-
* Defines the public API for a query
|
|
77
|
+
* Defines the public API for a query handle.
|
|
70
78
|
*/
|
|
71
|
-
export interface
|
|
79
|
+
export interface QueryHandle<TData, TError> {
|
|
72
80
|
// Returns the current state snapshot of the query.
|
|
73
|
-
getSnapshot: () =>
|
|
81
|
+
getSnapshot: () => QueryHandleSnapshot<TData, TError>;
|
|
74
82
|
// Subscribes a listener to state changes; returns an unsubscribe function.
|
|
75
|
-
subscribe: (listener: (snapshot:
|
|
83
|
+
subscribe: (listener: (snapshot: QueryHandleSnapshot<TData, TError>) => void) => () => void;
|
|
76
84
|
// Manually triggers a refetch of this query.
|
|
77
|
-
refetch: (options?: RefetchOptions) => Promise<
|
|
85
|
+
refetch: (options?: RefetchOptions) => Promise<QueryHandleSnapshot<TData, TError>>;
|
|
78
86
|
// Marks this specific query as invalid in the cache to trigger a refetch if active.
|
|
79
87
|
invalidate: (options?: QueryInvalidateOptions) => Promise<void>;
|
|
80
88
|
// Escape hatch: provides direct access to the underlying Tanstack Query observer result.
|
|
@@ -93,14 +101,14 @@ type QueryDependencyDerivedOptions<TQueryKey extends QueryKey = QueryKey> = {
|
|
|
93
101
|
queryKey?: TQueryKey;
|
|
94
102
|
};
|
|
95
103
|
|
|
96
|
-
type
|
|
104
|
+
type QueryHandleRuntimeOptions<
|
|
97
105
|
TQueryFnData = unknown,
|
|
98
106
|
TError = Error,
|
|
99
107
|
TData = TQueryFnData,
|
|
100
108
|
TQueryData = TQueryFnData,
|
|
101
109
|
TQueryKey extends QueryKey = QueryKey,
|
|
102
110
|
> = Omit<
|
|
103
|
-
|
|
111
|
+
QueryHandleOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey>,
|
|
104
112
|
'dependsOn'
|
|
105
113
|
>;
|
|
106
114
|
|
|
@@ -108,9 +116,9 @@ export type QueryDependencyTuple<
|
|
|
108
116
|
TSources extends readonly unknown[],
|
|
109
117
|
TQueryKey extends QueryKey = QueryKey,
|
|
110
118
|
> = readonly [
|
|
111
|
-
sources: { readonly [K in keyof TSources]:
|
|
119
|
+
sources: { readonly [K in keyof TSources]: QueryHandle<TSources[K], Error> },
|
|
112
120
|
deriveOptions: (
|
|
113
|
-
sourceSnapshots: { readonly [K in keyof TSources]:
|
|
121
|
+
sourceSnapshots: { readonly [K in keyof TSources]: QueryHandleSnapshot<TSources[K], Error> }
|
|
114
122
|
) => QueryDependencyDerivedOptions<TQueryKey>,
|
|
115
123
|
];
|
|
116
124
|
|
|
@@ -131,15 +139,15 @@ export interface CreateUntrackedQuery {
|
|
|
131
139
|
// The asynchronous function that performs the data fetch.
|
|
132
140
|
queryFn: QueryFunction<TQueryFnData, TQueryKey>,
|
|
133
141
|
// Optional configuration for behavior like staleness, retry, and refetching.
|
|
134
|
-
options?:
|
|
135
|
-
):
|
|
142
|
+
options?: QueryHandleOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey, TSources>
|
|
143
|
+
): QueryHandle<TData, TError>;
|
|
136
144
|
}
|
|
137
145
|
|
|
138
146
|
/**
|
|
139
147
|
* Function signature for the default query factory that derives dependencies from the final
|
|
140
148
|
* query-key segment.
|
|
141
149
|
*
|
|
142
|
-
* The tracked query handle deliberately stays API-compatible with the normal query
|
|
150
|
+
* The tracked query handle deliberately stays API-compatible with the normal query handle.
|
|
143
151
|
* The only extra behavior is invisible: dependency registration and on-demand re-registration.
|
|
144
152
|
*/
|
|
145
153
|
export interface CreateQuery {
|
|
@@ -154,14 +162,14 @@ export interface CreateQuery {
|
|
|
154
162
|
>(
|
|
155
163
|
queryKey: TQueryKey,
|
|
156
164
|
queryFn: QueryFunction<TQueryFnData, TQueryKey>,
|
|
157
|
-
options?:
|
|
158
|
-
):
|
|
165
|
+
options?: QueryHandleOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey, TSources>
|
|
166
|
+
): QueryHandle<TData, TError>;
|
|
159
167
|
}
|
|
160
168
|
|
|
161
169
|
/**
|
|
162
|
-
* Configuration options for creating a query
|
|
170
|
+
* Configuration options for creating a query handle, excluding function and key.
|
|
163
171
|
*/
|
|
164
|
-
export type
|
|
172
|
+
export type QueryHandleOptions<
|
|
165
173
|
TQueryFnData = unknown,
|
|
166
174
|
TError = Error,
|
|
167
175
|
TData = TQueryFnData,
|
|
@@ -179,7 +187,7 @@ export type QueryServiceOptions<
|
|
|
179
187
|
* Extracts and maps status and fetchStatus to our QueryMetaState interface.
|
|
180
188
|
*/
|
|
181
189
|
export function toQueryMetaState<TData, TError>(
|
|
182
|
-
snapshot: Pick<
|
|
190
|
+
snapshot: Pick<QueryHandleSnapshot<TData, TError>, 'fetchStatus' | 'status'>
|
|
183
191
|
): QueryMetaState {
|
|
184
192
|
// Return a simplified state object for UI or other services.
|
|
185
193
|
return {
|
|
@@ -188,6 +196,18 @@ export function toQueryMetaState<TData, TError>(
|
|
|
188
196
|
};
|
|
189
197
|
}
|
|
190
198
|
|
|
199
|
+
/**
|
|
200
|
+
* Extracts only data and error from a query snapshot.
|
|
201
|
+
*/
|
|
202
|
+
export function toQueryHandleData<TData, TError>(
|
|
203
|
+
snapshot: Pick<QueryHandleSnapshot<TData, TError>, 'data' | 'error'>
|
|
204
|
+
): QueryHandleData<TData, TError> {
|
|
205
|
+
return {
|
|
206
|
+
data: snapshot.data,
|
|
207
|
+
error: snapshot.error,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
191
211
|
/**
|
|
192
212
|
* Helper function to check if the query is in its initial loading state.
|
|
193
213
|
*/
|
|
@@ -200,7 +220,7 @@ export function isQueryLoading(query: QueryMetaState): boolean {
|
|
|
200
220
|
* Prepares the query factory by binding it to a specific QueryClient instance.
|
|
201
221
|
*/
|
|
202
222
|
export function setupQuery(queryClient: QueryClient): CreateUntrackedQuery {
|
|
203
|
-
// Returns the actual factory function for creating individual query
|
|
223
|
+
// Returns the actual factory function for creating individual query handles.
|
|
204
224
|
return function createQuery<
|
|
205
225
|
TSources extends readonly unknown[] = [],
|
|
206
226
|
TQueryFnData = unknown,
|
|
@@ -211,16 +231,16 @@ export function setupQuery(queryClient: QueryClient): CreateUntrackedQuery {
|
|
|
211
231
|
>(
|
|
212
232
|
queryKey: TQueryKey,
|
|
213
233
|
queryFn: QueryFunction<TQueryFnData, TQueryKey>,
|
|
214
|
-
options?:
|
|
215
|
-
):
|
|
216
|
-
const { dependsOn, runtimeOptions } =
|
|
217
|
-
const
|
|
234
|
+
options?: QueryHandleOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey, TSources>
|
|
235
|
+
): QueryHandle<TData, TError> {
|
|
236
|
+
const { dependsOn, runtimeOptions } = splitQueryHandleOptions(options);
|
|
237
|
+
const handle = createQueryHandle(queryClient, queryKey, queryFn, runtimeOptions);
|
|
218
238
|
|
|
219
239
|
if (!dependsOn) {
|
|
220
|
-
return
|
|
240
|
+
return handle.handle;
|
|
221
241
|
}
|
|
222
242
|
|
|
223
|
-
return bindQueryDependencies(
|
|
243
|
+
return bindQueryDependencies(handle, queryKey, dependsOn);
|
|
224
244
|
};
|
|
225
245
|
}
|
|
226
246
|
|
|
@@ -249,33 +269,33 @@ export function setupTrackedQuery(
|
|
|
249
269
|
>(
|
|
250
270
|
queryKey: TQueryKey,
|
|
251
271
|
queryFn: QueryFunction<TQueryFnData, TQueryKey>,
|
|
252
|
-
options?:
|
|
253
|
-
):
|
|
254
|
-
const { dependsOn, runtimeOptions } =
|
|
255
|
-
// Reuse the same core query
|
|
256
|
-
const
|
|
272
|
+
options?: QueryHandleOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey, TSources>
|
|
273
|
+
): QueryHandle<TData, TError> {
|
|
274
|
+
const { dependsOn, runtimeOptions } = splitQueryHandleOptions(options);
|
|
275
|
+
// Reuse the same core query-handle implementation as the untracked API.
|
|
276
|
+
const handle = createQueryHandle(queryClient, queryKey, queryFn, runtimeOptions);
|
|
257
277
|
// We only need re-registration on the transition from zero to one subscribers.
|
|
258
278
|
let subscriberCount = 0;
|
|
259
279
|
|
|
260
280
|
// Register the current query hash immediately so future tracked mutations can find it.
|
|
261
281
|
trackingRegistry.register(
|
|
262
|
-
|
|
263
|
-
extractTrackedDependencies(
|
|
282
|
+
handle.observer.getCurrentQuery().queryHash,
|
|
283
|
+
extractTrackedDependencies(handle.getCurrentQueryKey())
|
|
264
284
|
);
|
|
265
285
|
|
|
266
286
|
const applyTrackedDerivedState = (derivedOptions: QueryDependencyDerivedOptions<TQueryKey>) => {
|
|
267
|
-
const previousQueryHash =
|
|
287
|
+
const previousQueryHash = handle.observer.getCurrentQuery().queryHash;
|
|
268
288
|
|
|
269
|
-
|
|
289
|
+
handle.setDerivedState(derivedOptions);
|
|
270
290
|
|
|
271
|
-
const nextQueryHash =
|
|
291
|
+
const nextQueryHash = handle.observer.getCurrentQuery().queryHash;
|
|
272
292
|
|
|
273
293
|
if (nextQueryHash === previousQueryHash) {
|
|
274
294
|
return;
|
|
275
295
|
}
|
|
276
296
|
|
|
277
297
|
trackingRegistry.unregister(previousQueryHash);
|
|
278
|
-
trackingRegistry.register(nextQueryHash, extractTrackedDependencies(
|
|
298
|
+
trackingRegistry.register(nextQueryHash, extractTrackedDependencies(handle.getCurrentQueryKey()));
|
|
279
299
|
};
|
|
280
300
|
|
|
281
301
|
const dependencyController = dependsOn
|
|
@@ -291,9 +311,9 @@ export function setupTrackedQuery(
|
|
|
291
311
|
// the same mechanism TanStack uses internally when a query gets recreated after GC.
|
|
292
312
|
const liveQuery = queryClient.getQueryCache().build(
|
|
293
313
|
queryClient,
|
|
294
|
-
|
|
314
|
+
handle.getCurrentObserverOptions()
|
|
295
315
|
);
|
|
296
|
-
const liveDependencies = extractTrackedDependencies(
|
|
316
|
+
const liveDependencies = extractTrackedDependencies(handle.getCurrentQueryKey());
|
|
297
317
|
|
|
298
318
|
// Re-register only when TanStack has recreated the query and the registry has already
|
|
299
319
|
// cleaned up the previous hash. This keeps the edge-case handling cheap in the common case.
|
|
@@ -303,12 +323,12 @@ export function setupTrackedQuery(
|
|
|
303
323
|
};
|
|
304
324
|
|
|
305
325
|
return {
|
|
306
|
-
...
|
|
326
|
+
...handle.handle,
|
|
307
327
|
refetch: async (refetchOptions) => {
|
|
308
328
|
await dependencyController?.evaluateForRefetch();
|
|
309
329
|
// Refetch is one of the two explicit reactivation paths agreed on in the design.
|
|
310
330
|
ensureRegistered();
|
|
311
|
-
return
|
|
331
|
+
return handle.handle.refetch(refetchOptions);
|
|
312
332
|
},
|
|
313
333
|
subscribe: (listener) => {
|
|
314
334
|
// The first active subscriber is the other reactivation path. Re-running registration
|
|
@@ -320,7 +340,7 @@ export function setupTrackedQuery(
|
|
|
320
340
|
|
|
321
341
|
subscriberCount += 1;
|
|
322
342
|
|
|
323
|
-
const unsubscribe =
|
|
343
|
+
const unsubscribe = handle.handle.subscribe(listener);
|
|
324
344
|
|
|
325
345
|
return () => {
|
|
326
346
|
// Keep the counter bounded so accidental double-unsubscribe cannot push it negative.
|
|
@@ -338,10 +358,10 @@ export function setupTrackedQuery(
|
|
|
338
358
|
/**
|
|
339
359
|
* Internal helper to transform a raw Tanstack query result into our public snapshot format.
|
|
340
360
|
*/
|
|
341
|
-
function
|
|
361
|
+
function toQueryHandleSnapshot<TData, TError>(
|
|
342
362
|
result: QueryObserverResult<TData, TError>
|
|
343
|
-
):
|
|
344
|
-
// Extract and return the relevant fields for the UI or other
|
|
363
|
+
): QueryHandleSnapshot<TData, TError> {
|
|
364
|
+
// Extract and return the relevant fields for the UI or other handle consumers.
|
|
345
365
|
return {
|
|
346
366
|
data: result.data,
|
|
347
367
|
error: result.error,
|
|
@@ -354,7 +374,7 @@ function toQueryServiceSnapshot<TData, TError>(
|
|
|
354
374
|
};
|
|
355
375
|
}
|
|
356
376
|
|
|
357
|
-
function
|
|
377
|
+
function createQueryHandle<
|
|
358
378
|
TQueryFnData = unknown,
|
|
359
379
|
TError = Error,
|
|
360
380
|
TData = TQueryFnData,
|
|
@@ -364,12 +384,12 @@ function createQueryService<
|
|
|
364
384
|
queryClient: QueryClient,
|
|
365
385
|
queryKey: TQueryKey,
|
|
366
386
|
queryFn: QueryFunction<TQueryFnData, TQueryKey>,
|
|
367
|
-
options?:
|
|
387
|
+
options?: QueryHandleRuntimeOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey>
|
|
368
388
|
): {
|
|
369
389
|
// Expose the observer internally so tracked queries can access the current query hash.
|
|
370
390
|
observer: QueryObserver<TQueryFnData, TError, TData, TQueryData, TQueryKey>;
|
|
371
|
-
// Preserve the public query-
|
|
372
|
-
|
|
391
|
+
// Preserve the public query-handle shape for all callers.
|
|
392
|
+
handle: QueryHandle<TData, TError>;
|
|
373
393
|
getCurrentObserverOptions: () => QueryOptions<TQueryFnData, TError, TQueryData, TQueryKey> &
|
|
374
394
|
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey>;
|
|
375
395
|
getCurrentQueryKey: () => TQueryKey;
|
|
@@ -401,14 +421,14 @@ function createQueryService<
|
|
|
401
421
|
getCurrentObserverOptions,
|
|
402
422
|
getCurrentQueryKey: () => resolvedQueryKey,
|
|
403
423
|
setDerivedState,
|
|
404
|
-
|
|
405
|
-
getSnapshot: () =>
|
|
424
|
+
handle: {
|
|
425
|
+
getSnapshot: () => toQueryHandleSnapshot(observer.getCurrentResult()),
|
|
406
426
|
subscribe: (listener) =>
|
|
407
427
|
observer.subscribe((result) => {
|
|
408
|
-
listener(
|
|
428
|
+
listener(toQueryHandleSnapshot(result));
|
|
409
429
|
}),
|
|
410
430
|
refetch: async (refetchOptions) =>
|
|
411
|
-
|
|
431
|
+
toQueryHandleSnapshot(await observer.refetch(refetchOptions)),
|
|
412
432
|
invalidate: (invalidateOptions) =>
|
|
413
433
|
queryClient.invalidateQueries(
|
|
414
434
|
{
|
|
@@ -433,24 +453,24 @@ function bindQueryDependencies<
|
|
|
433
453
|
TQueryData = TQueryFnData,
|
|
434
454
|
TQueryKey extends QueryKey = QueryKey,
|
|
435
455
|
>(
|
|
436
|
-
|
|
437
|
-
typeof
|
|
456
|
+
queryHandle: ReturnType<
|
|
457
|
+
typeof createQueryHandle<TQueryFnData, TError, TData, TQueryData, TQueryKey>
|
|
438
458
|
>,
|
|
439
459
|
queryKey: TQueryKey,
|
|
440
460
|
dependsOn: QueryDependencyTuple<TSources, TQueryKey>
|
|
441
|
-
):
|
|
461
|
+
): QueryHandle<TData, TError> {
|
|
442
462
|
const dependencyController = createDependencyController(
|
|
443
463
|
queryKey,
|
|
444
|
-
|
|
464
|
+
queryHandle.setDerivedState,
|
|
445
465
|
dependsOn
|
|
446
466
|
);
|
|
447
467
|
let subscriberCount = 0;
|
|
448
468
|
|
|
449
469
|
return {
|
|
450
|
-
...
|
|
470
|
+
...queryHandle.handle,
|
|
451
471
|
refetch: async (refetchOptions) => {
|
|
452
472
|
await dependencyController.evaluateForRefetch();
|
|
453
|
-
return
|
|
473
|
+
return queryHandle.handle.refetch(refetchOptions);
|
|
454
474
|
},
|
|
455
475
|
subscribe: (listener) => {
|
|
456
476
|
if (subscriberCount === 0) {
|
|
@@ -459,7 +479,7 @@ function bindQueryDependencies<
|
|
|
459
479
|
|
|
460
480
|
subscriberCount += 1;
|
|
461
481
|
|
|
462
|
-
const unsubscribe =
|
|
482
|
+
const unsubscribe = queryHandle.handle.subscribe(listener);
|
|
463
483
|
|
|
464
484
|
return () => {
|
|
465
485
|
subscriberCount = Math.max(0, subscriberCount - 1);
|
|
@@ -551,7 +571,7 @@ function createDependencyController<
|
|
|
551
571
|
};
|
|
552
572
|
}
|
|
553
573
|
|
|
554
|
-
function
|
|
574
|
+
function splitQueryHandleOptions<
|
|
555
575
|
TQueryFnData = unknown,
|
|
556
576
|
TError = Error,
|
|
557
577
|
TData = TQueryFnData,
|
|
@@ -559,10 +579,10 @@ function splitQueryServiceOptions<
|
|
|
559
579
|
TQueryKey extends QueryKey = QueryKey,
|
|
560
580
|
TSources extends readonly unknown[] = [],
|
|
561
581
|
>(
|
|
562
|
-
options?:
|
|
582
|
+
options?: QueryHandleOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey, TSources>
|
|
563
583
|
): {
|
|
564
584
|
dependsOn?: QueryDependencyTuple<TSources, TQueryKey>;
|
|
565
|
-
runtimeOptions:
|
|
585
|
+
runtimeOptions: QueryHandleRuntimeOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey> | undefined;
|
|
566
586
|
} {
|
|
567
587
|
if (options === undefined) {
|
|
568
588
|
return {
|
|
@@ -588,7 +608,7 @@ function toQueryOptions<
|
|
|
588
608
|
>(
|
|
589
609
|
queryKey: TQueryKey,
|
|
590
610
|
queryFn: QueryFunction<TQueryFnData, TQueryKey>,
|
|
591
|
-
options?:
|
|
611
|
+
options?: QueryHandleRuntimeOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey>
|
|
592
612
|
): QueryOptions<TQueryFnData, TError, TQueryData, TQueryKey> &
|
|
593
613
|
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey> {
|
|
594
614
|
// Centralize option assembly so both normal queries and tracked queries build observers and
|