@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.
Files changed (46) hide show
  1. package/.turbo/turbo-build.log +3 -3
  2. package/README.md +166 -34
  3. package/dist/index.d.ts +0 -1
  4. package/dist/index.js +0 -2
  5. package/dist/index.js.map +1 -1
  6. package/dist/mutation.d.ts +8 -8
  7. package/dist/mutation.js +9 -9
  8. package/dist/mutation.js.map +1 -1
  9. package/dist/provider.d.ts +3 -2
  10. package/dist/provider.js +2 -0
  11. package/dist/provider.js.map +1 -1
  12. package/dist/query.d.ts +26 -15
  13. package/dist/query.js +40 -31
  14. package/dist/query.js.map +1 -1
  15. package/dist/react/hooks/index.d.ts +2 -1
  16. package/dist/react/hooks/index.js +2 -1
  17. package/dist/react/hooks/index.js.map +1 -1
  18. package/dist/react/hooks/use-mutation-handle.d.ts +2 -0
  19. package/dist/react/hooks/use-mutation-handle.js +71 -0
  20. package/dist/react/hooks/use-mutation-handle.js.map +1 -0
  21. package/dist/react/hooks/use-query-handle.d.ts +2 -0
  22. package/dist/react/hooks/use-query-handle.js +71 -0
  23. package/dist/react/hooks/use-query-handle.js.map +1 -0
  24. package/dist/react/hooks/use-query-subscription.d.ts +1 -2
  25. package/dist/react/hooks/use-query-subscription.js +1 -72
  26. package/dist/react/hooks/use-query-subscription.js.map +1 -1
  27. package/dist/react/index.d.ts +1 -1
  28. package/dist/react/index.js +1 -1
  29. package/dist/react/index.js.map +1 -1
  30. package/package.json +1 -8
  31. package/src/__tests__/provider.spec.ts +8 -0
  32. package/src/index.ts +0 -2
  33. package/src/mutation.ts +27 -27
  34. package/src/provider.ts +13 -9
  35. package/src/query.ts +84 -64
  36. package/src/react/__tests__/use-mutation-handle.spec.tsx +107 -0
  37. package/src/react/__tests__/{query-subscription.spec.tsx → use-query-handle.spec.tsx} +7 -7
  38. package/src/react/hooks/index.ts +2 -1
  39. package/src/react/hooks/use-mutation-handle.ts +98 -0
  40. package/src/react/hooks/{use-query-subscription.ts → use-query-handle.ts} +19 -21
  41. package/src/react/index.ts +1 -1
  42. package/dist/query-registry.d.ts +0 -9
  43. package/dist/query-registry.js +0 -28
  44. package/dist/query-registry.js.map +0 -1
  45. package/src/__tests__/query-registry.spec.ts +0 -101
  46. 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 MutationServiceSnapshot<TData = unknown, TError = Error, TVariables = void> {
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 MutationService<
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: () => MutationServiceSnapshot<TData, TError, TVariables>;
64
+ getSnapshot: () => MutationHandleSnapshot<TData, TError, TVariables>;
65
65
  // Subscribes a listener to state changes; returns an unsubscribe function.
66
66
  subscribe: (
67
- listener: (snapshot: MutationServiceSnapshot<TData, TError, TVariables>) => void
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 MutationServiceOptions<
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?: MutationServiceOptions<TData, TError, TVariables, TOnMutateResult>
99
- ): MutationService<TData, TError, TVariables, TOnMutateResult>;
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 TrackedMutationServiceOptions<
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 MutationServiceOptions<TData, TError, TVariables, TOnMutateResult> {
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?: TrackedMutationServiceOptions<TDeps, TData, TError, TVariables, TOnMutateResult>
139
- ): MutationService<TData, TError, TVariables, TOnMutateResult>;
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?: MutationServiceOptions<TData, TError, TVariables, TOnMutateResult>
155
- ): MutationService<TData, TError, TVariables, TOnMutateResult> {
156
- return createMutationService(queryClient, mutationFn, options);
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?: TrackedMutationServiceOptions<TDeps, TData, TError, TVariables, TOnMutateResult>
181
- ): MutationService<TData, TError, TVariables, TOnMutateResult> {
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 service so snapshots and subscription behavior stay identical.
191
- const service = createMutationService(queryClient, mutationFn, mutationOptions);
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
- ...service,
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 service.mutate(variables, mutateOptions);
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 toMutationServiceSnapshot<TData, TError, TVariables, TOnMutateResult>(
255
+ function toMutationHandleSnapshot<TData, TError, TVariables, TOnMutateResult>(
256
256
  result: MutationObserverResult<TData, TError, TVariables, TOnMutateResult>
257
- ): MutationServiceSnapshot<TData, TError, TVariables> {
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 createMutationService<
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?: MutationServiceOptions<TData, TError, TVariables, TOnMutateResult>
280
- ): MutationService<TData, TError, TVariables, TOnMutateResult> {
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: () => toMutationServiceSnapshot(observer.getCurrentResult()),
289
+ getSnapshot: () => toMutationHandleSnapshot(observer.getCurrentResult()),
290
290
  subscribe: (listener) =>
291
291
  observer.subscribe((result) => {
292
- listener(toMutationServiceSnapshot(result));
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 MutationService,
12
- type TrackedMutationServiceOptions,
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
- TrackedMutationServiceOptions<
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
- ): MutationService<TData, TError, TVariables, TOnMutateResult>;
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 service within the context of this provider.
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 service within the context of this provider.
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 service within the context of this provider.
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 service within the context of this provider.
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
- TrackedMutationServiceOptions<
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 the query service's state.
39
+ * Represents a stable snapshot of one query handle's state.
40
40
  */
41
- export interface QueryServiceSnapshot<TData, TError> {
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 service.
77
+ * Defines the public API for a query handle.
70
78
  */
71
- export interface QueryService<TData, TError> {
79
+ export interface QueryHandle<TData, TError> {
72
80
  // Returns the current state snapshot of the query.
73
- getSnapshot: () => QueryServiceSnapshot<TData, TError>;
81
+ getSnapshot: () => QueryHandleSnapshot<TData, TError>;
74
82
  // Subscribes a listener to state changes; returns an unsubscribe function.
75
- subscribe: (listener: (snapshot: QueryServiceSnapshot<TData, TError>) => void) => () => void;
83
+ subscribe: (listener: (snapshot: QueryHandleSnapshot<TData, TError>) => void) => () => void;
76
84
  // Manually triggers a refetch of this query.
77
- refetch: (options?: RefetchOptions) => Promise<QueryServiceSnapshot<TData, TError>>;
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 QueryServiceRuntimeOptions<
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
- QueryServiceOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey>,
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]: QueryService<TSources[K], Error> },
119
+ sources: { readonly [K in keyof TSources]: QueryHandle<TSources[K], Error> },
112
120
  deriveOptions: (
113
- sourceSnapshots: { readonly [K in keyof TSources]: QueryServiceSnapshot<TSources[K], Error> }
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?: QueryServiceOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey, TSources>
135
- ): QueryService<TData, TError>;
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 service.
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?: QueryServiceOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey, TSources>
158
- ): QueryService<TData, TError>;
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 service, excluding function and key.
170
+ * Configuration options for creating a query handle, excluding function and key.
163
171
  */
164
- export type QueryServiceOptions<
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<QueryServiceSnapshot<TData, TError>, 'fetchStatus' | 'status'>
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 services.
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?: QueryServiceOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey, TSources>
215
- ): QueryService<TData, TError> {
216
- const { dependsOn, runtimeOptions } = splitQueryServiceOptions(options);
217
- const service = createQueryService(queryClient, queryKey, queryFn, runtimeOptions);
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 service.service;
240
+ return handle.handle;
221
241
  }
222
242
 
223
- return bindQueryDependencies(service, queryKey, dependsOn);
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?: QueryServiceOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey, TSources>
253
- ): QueryService<TData, TError> {
254
- const { dependsOn, runtimeOptions } = splitQueryServiceOptions(options);
255
- // Reuse the same core query service implementation as the untracked API.
256
- const service = createQueryService(queryClient, queryKey, queryFn, runtimeOptions);
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
- service.observer.getCurrentQuery().queryHash,
263
- extractTrackedDependencies(service.getCurrentQueryKey())
282
+ handle.observer.getCurrentQuery().queryHash,
283
+ extractTrackedDependencies(handle.getCurrentQueryKey())
264
284
  );
265
285
 
266
286
  const applyTrackedDerivedState = (derivedOptions: QueryDependencyDerivedOptions<TQueryKey>) => {
267
- const previousQueryHash = service.observer.getCurrentQuery().queryHash;
287
+ const previousQueryHash = handle.observer.getCurrentQuery().queryHash;
268
288
 
269
- service.setDerivedState(derivedOptions);
289
+ handle.setDerivedState(derivedOptions);
270
290
 
271
- const nextQueryHash = service.observer.getCurrentQuery().queryHash;
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(service.getCurrentQueryKey()));
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
- service.getCurrentObserverOptions()
314
+ handle.getCurrentObserverOptions()
295
315
  );
296
- const liveDependencies = extractTrackedDependencies(service.getCurrentQueryKey());
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
- ...service.service,
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 service.service.refetch(refetchOptions);
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 = service.service.subscribe(listener);
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 toQueryServiceSnapshot<TData, TError>(
361
+ function toQueryHandleSnapshot<TData, TError>(
342
362
  result: QueryObserverResult<TData, TError>
343
- ): QueryServiceSnapshot<TData, TError> {
344
- // Extract and return the relevant fields for the UI or other services.
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 createQueryService<
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?: QueryServiceRuntimeOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey>
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-service shape for all callers.
372
- service: QueryService<TData, TError>;
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
- service: {
405
- getSnapshot: () => toQueryServiceSnapshot(observer.getCurrentResult()),
424
+ handle: {
425
+ getSnapshot: () => toQueryHandleSnapshot(observer.getCurrentResult()),
406
426
  subscribe: (listener) =>
407
427
  observer.subscribe((result) => {
408
- listener(toQueryServiceSnapshot(result));
428
+ listener(toQueryHandleSnapshot(result));
409
429
  }),
410
430
  refetch: async (refetchOptions) =>
411
- toQueryServiceSnapshot(await observer.refetch(refetchOptions)),
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
- queryService: ReturnType<
437
- typeof createQueryService<TQueryFnData, TError, TData, TQueryData, TQueryKey>
456
+ queryHandle: ReturnType<
457
+ typeof createQueryHandle<TQueryFnData, TError, TData, TQueryData, TQueryKey>
438
458
  >,
439
459
  queryKey: TQueryKey,
440
460
  dependsOn: QueryDependencyTuple<TSources, TQueryKey>
441
- ): QueryService<TData, TError> {
461
+ ): QueryHandle<TData, TError> {
442
462
  const dependencyController = createDependencyController(
443
463
  queryKey,
444
- queryService.setDerivedState,
464
+ queryHandle.setDerivedState,
445
465
  dependsOn
446
466
  );
447
467
  let subscriberCount = 0;
448
468
 
449
469
  return {
450
- ...queryService.service,
470
+ ...queryHandle.handle,
451
471
  refetch: async (refetchOptions) => {
452
472
  await dependencyController.evaluateForRefetch();
453
- return queryService.service.refetch(refetchOptions);
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 = queryService.service.subscribe(listener);
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 splitQueryServiceOptions<
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?: QueryServiceOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey, TSources>
582
+ options?: QueryHandleOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey, TSources>
563
583
  ): {
564
584
  dependsOn?: QueryDependencyTuple<TSources, TQueryKey>;
565
- runtimeOptions: QueryServiceRuntimeOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey> | undefined;
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?: QueryServiceRuntimeOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey>
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