@tanstack/angular-query-experimental 5.103.2 → 5.104.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 (218) hide show
  1. package/dist/create-base-query.d.mts +168 -0
  2. package/dist/create-base-query.d.mts.map +1 -0
  3. package/dist/create-base-query.mjs +91 -0
  4. package/dist/create-base-query.mjs.map +1 -0
  5. package/dist/devtools/index.d.mts +3 -0
  6. package/dist/devtools/index.mjs +2 -0
  7. package/dist/devtools/production/index.d.mts +4 -0
  8. package/dist/devtools/production/index.mjs +2 -0
  9. package/dist/devtools/stub.d.mts +5 -0
  10. package/dist/devtools/stub.d.mts.map +1 -0
  11. package/dist/devtools/stub.mjs +9 -0
  12. package/dist/devtools/stub.mjs.map +1 -0
  13. package/dist/devtools/types.d.mts +106 -0
  14. package/dist/devtools/types.d.mts.map +1 -0
  15. package/dist/devtools/types.mjs +1 -0
  16. package/{devtools/with-devtools.d.ts → dist/devtools/with-devtools.d.mts} +4 -1
  17. package/dist/devtools/with-devtools.d.mts.map +1 -0
  18. package/dist/devtools/with-devtools.mjs +110 -0
  19. package/dist/devtools/with-devtools.mjs.map +1 -0
  20. package/dist/devtools-panel/index.d.mts +3 -0
  21. package/dist/devtools-panel/index.mjs +2 -0
  22. package/{devtools-panel/inject-devtools-panel.d.ts → dist/devtools-panel/inject-devtools-panel.d.mts} +4 -1
  23. package/dist/devtools-panel/inject-devtools-panel.d.mts.map +1 -0
  24. package/dist/devtools-panel/inject-devtools-panel.mjs +67 -0
  25. package/dist/devtools-panel/inject-devtools-panel.mjs.map +1 -0
  26. package/dist/devtools-panel/production/index.d.mts +4 -0
  27. package/dist/devtools-panel/production/index.mjs +2 -0
  28. package/dist/devtools-panel/stub.d.mts +5 -0
  29. package/dist/devtools-panel/stub.d.mts.map +1 -0
  30. package/dist/devtools-panel/stub.mjs +7 -0
  31. package/dist/devtools-panel/stub.mjs.map +1 -0
  32. package/dist/devtools-panel/types.d.mts +51 -0
  33. package/dist/devtools-panel/types.d.mts.map +1 -0
  34. package/dist/devtools-panel/types.mjs +1 -0
  35. package/dist/index.d.mts +16 -0
  36. package/{index.mjs → dist/index.mjs} +8 -21
  37. package/dist/index.mjs.map +1 -0
  38. package/{infinite-query-options.d.ts → dist/infinite-query-options.d.mts} +28 -25
  39. package/dist/infinite-query-options.d.mts.map +1 -0
  40. package/dist/infinite-query-options.mjs +8 -0
  41. package/dist/infinite-query-options.mjs.map +1 -0
  42. package/{inject-infinite-query.d.ts → dist/inject-infinite-query.d.mts} +13 -10
  43. package/dist/inject-infinite-query.d.mts.map +1 -0
  44. package/dist/inject-infinite-query.mjs +13 -0
  45. package/dist/inject-infinite-query.mjs.map +1 -0
  46. package/{inject-is-fetching.d.ts → dist/inject-is-fetching.d.mts} +11 -8
  47. package/dist/inject-is-fetching.d.mts.map +1 -0
  48. package/dist/inject-is-fetching.mjs +69 -0
  49. package/dist/inject-is-fetching.mjs.map +1 -0
  50. package/{inject-is-mutating.d.ts → dist/inject-is-mutating.d.mts} +11 -8
  51. package/dist/inject-is-mutating.d.mts.map +1 -0
  52. package/dist/inject-is-mutating.mjs +52 -0
  53. package/dist/inject-is-mutating.mjs.map +1 -0
  54. package/{inject-is-restoring.d.ts → dist/inject-is-restoring.d.mts} +10 -8
  55. package/dist/inject-is-restoring.d.mts.map +1 -0
  56. package/dist/inject-is-restoring.mjs +35 -0
  57. package/dist/inject-is-restoring.mjs.map +1 -0
  58. package/{inject-mutation-state.d.ts → dist/inject-mutation-state.d.mts} +13 -11
  59. package/dist/inject-mutation-state.d.mts.map +1 -0
  60. package/dist/inject-mutation-state.mjs +103 -0
  61. package/dist/inject-mutation-state.mjs.map +1 -0
  62. package/{inject-mutation.d.ts → dist/inject-mutation.d.mts} +12 -9
  63. package/dist/inject-mutation.d.mts.map +1 -0
  64. package/dist/inject-mutation.mjs +226 -0
  65. package/dist/inject-mutation.mjs.map +1 -0
  66. package/dist/inject-queries-experimental/index.d.mts +2 -0
  67. package/dist/inject-queries-experimental/index.mjs +2 -0
  68. package/{inject-queries.d.ts → dist/inject-queries.d.mts} +33 -53
  69. package/dist/inject-queries.d.mts.map +1 -0
  70. package/dist/inject-queries.mjs +203 -0
  71. package/dist/inject-queries.mjs.map +1 -0
  72. package/{inject-query-client.d.ts → dist/inject-query-client.d.mts} +6 -3
  73. package/dist/inject-query-client.d.mts.map +1 -0
  74. package/dist/inject-query-client.mjs +23 -0
  75. package/dist/inject-query-client.mjs.map +1 -0
  76. package/{inject-query.d.ts → dist/inject-query.d.mts} +13 -10
  77. package/dist/inject-query.d.mts.map +1 -0
  78. package/dist/inject-query.mjs +12 -0
  79. package/dist/inject-query.mjs.map +1 -0
  80. package/{mutation-options.d.ts → dist/mutation-options.d.mts} +5 -2
  81. package/dist/mutation-options.d.mts.map +1 -0
  82. package/dist/mutation-options.mjs +8 -0
  83. package/dist/mutation-options.mjs.map +1 -0
  84. package/dist/pending-tasks-compat.d.mts +9 -0
  85. package/dist/pending-tasks-compat.d.mts.map +1 -0
  86. package/dist/pending-tasks-compat.mjs +13 -0
  87. package/dist/pending-tasks-compat.mjs.map +1 -0
  88. package/{providers.d.ts → dist/providers.d.mts} +7 -5
  89. package/dist/providers.d.mts.map +1 -0
  90. package/dist/providers.mjs +124 -0
  91. package/dist/providers.mjs.map +1 -0
  92. package/{query-options.d.ts → dist/query-options.d.mts} +34 -31
  93. package/dist/query-options.d.mts.map +1 -0
  94. package/dist/query-options.mjs +8 -0
  95. package/dist/query-options.mjs.map +1 -0
  96. package/{signal-proxy.d.ts → dist/signal-proxy.d.mts} +5 -4
  97. package/dist/signal-proxy.d.mts.map +1 -0
  98. package/dist/signal-proxy.mjs +35 -0
  99. package/dist/signal-proxy.mjs.map +1 -0
  100. package/{types.d.ts → dist/types.d.mts} +24 -26
  101. package/dist/types.d.mts.map +1 -0
  102. package/dist/types.mjs +6 -0
  103. package/dist/types.mjs.map +1 -0
  104. package/package.json +50 -20
  105. package/src/__tests__/infinite-query-options.test-d.ts +314 -0
  106. package/src/__tests__/infinite-query-options.test.ts +19 -0
  107. package/src/__tests__/inject-devtools-panel.test.ts +196 -0
  108. package/src/__tests__/inject-infinite-query.test-d.ts +66 -0
  109. package/src/__tests__/inject-infinite-query.test.ts +227 -0
  110. package/src/__tests__/inject-is-fetching.test.ts +108 -0
  111. package/src/__tests__/inject-is-mutating.test.ts +113 -0
  112. package/src/__tests__/inject-is-restoring.test.ts +93 -0
  113. package/src/__tests__/inject-mutation-state.test-d.ts +22 -0
  114. package/src/__tests__/inject-mutation-state.test.ts +198 -0
  115. package/src/__tests__/inject-mutation.test-d.ts +158 -0
  116. package/src/__tests__/inject-mutation.test.ts +1161 -0
  117. package/src/__tests__/inject-queries.test-d.ts +196 -0
  118. package/src/__tests__/inject-queries.test.ts +240 -0
  119. package/src/__tests__/inject-query.test-d.ts +357 -0
  120. package/src/__tests__/inject-query.test.ts +700 -0
  121. package/src/__tests__/mutation-options.test-d.ts +234 -0
  122. package/src/__tests__/mutation-options.test.ts +344 -0
  123. package/src/__tests__/pending-tasks.test.ts +644 -0
  124. package/src/__tests__/provide-query-client.test.ts +38 -0
  125. package/src/__tests__/provide-tanstack-query.test.ts +38 -0
  126. package/src/__tests__/query-options.test-d.ts +262 -0
  127. package/src/__tests__/query-options.test.ts +14 -0
  128. package/src/__tests__/signal-proxy.test.ts +27 -0
  129. package/src/__tests__/test-utils.ts +77 -0
  130. package/src/__tests__/with-devtools.test.ts +674 -0
  131. package/src/create-base-query.ts +176 -0
  132. package/src/devtools/index.ts +8 -0
  133. package/src/devtools/production/index.ts +1 -0
  134. package/src/devtools/stub.ts +7 -0
  135. package/src/devtools/types.ts +116 -0
  136. package/src/devtools/with-devtools.ts +182 -0
  137. package/src/devtools-panel/index.ts +8 -0
  138. package/src/devtools-panel/inject-devtools-panel.ts +110 -0
  139. package/src/devtools-panel/production/index.ts +1 -0
  140. package/src/devtools-panel/stub.ts +7 -0
  141. package/src/devtools-panel/types.ts +57 -0
  142. package/src/index.ts +60 -0
  143. package/src/infinite-query-options.ts +354 -0
  144. package/src/inject-infinite-query.ts +296 -0
  145. package/src/inject-is-fetching.ts +97 -0
  146. package/src/inject-is-mutating.ts +80 -0
  147. package/src/inject-is-restoring.ts +53 -0
  148. package/src/inject-mutation-state.ts +167 -0
  149. package/src/inject-mutation.ts +328 -0
  150. package/src/inject-queries-experimental/index.ts +1 -0
  151. package/src/inject-queries.ts +480 -0
  152. package/src/inject-query-client.ts +22 -0
  153. package/src/inject-query.ts +208 -0
  154. package/src/mutation-options.ts +121 -0
  155. package/src/pending-tasks-compat.ts +27 -0
  156. package/src/providers.ts +170 -0
  157. package/src/query-options.ts +292 -0
  158. package/src/signal-proxy.ts +46 -0
  159. package/src/types.ts +429 -0
  160. package/create-base-query.d.ts +0 -164
  161. package/create-base-query.mjs +0 -99
  162. package/create-base-query.mjs.map +0 -1
  163. package/devtools/index.d.ts +0 -2
  164. package/devtools/index.mjs +0 -5
  165. package/devtools/index.mjs.map +0 -1
  166. package/devtools/production/index.d.ts +0 -1
  167. package/devtools/stub.d.ts +0 -2
  168. package/devtools/stub.mjs +0 -8
  169. package/devtools/stub.mjs.map +0 -1
  170. package/devtools/types.d.ts +0 -103
  171. package/devtools/with-devtools.mjs +0 -106
  172. package/devtools/with-devtools.mjs.map +0 -1
  173. package/devtools-panel/index.d.ts +0 -2
  174. package/devtools-panel/index.mjs +0 -5
  175. package/devtools-panel/index.mjs.map +0 -1
  176. package/devtools-panel/inject-devtools-panel.mjs +0 -72
  177. package/devtools-panel/inject-devtools-panel.mjs.map +0 -1
  178. package/devtools-panel/production/index.d.ts +0 -1
  179. package/devtools-panel/stub.d.ts +0 -2
  180. package/devtools-panel/stub.mjs +0 -8
  181. package/devtools-panel/stub.mjs.map +0 -1
  182. package/devtools-panel/types.d.ts +0 -48
  183. package/index.d.ts +0 -25
  184. package/index.mjs.map +0 -1
  185. package/infinite-query-options.mjs +0 -7
  186. package/infinite-query-options.mjs.map +0 -1
  187. package/inject-infinite-query.mjs +0 -18
  188. package/inject-infinite-query.mjs.map +0 -1
  189. package/inject-is-fetching.mjs +0 -31
  190. package/inject-is-fetching.mjs.map +0 -1
  191. package/inject-is-mutating.mjs +0 -31
  192. package/inject-is-mutating.mjs.map +0 -1
  193. package/inject-is-restoring.mjs +0 -21
  194. package/inject-is-restoring.mjs.map +0 -1
  195. package/inject-mutation-state.mjs +0 -51
  196. package/inject-mutation-state.mjs.map +0 -1
  197. package/inject-mutation.mjs +0 -94
  198. package/inject-mutation.mjs.map +0 -1
  199. package/inject-queries-experimental/index.d.ts +0 -1
  200. package/inject-queries-experimental/index.mjs +0 -5
  201. package/inject-queries-experimental/index.mjs.map +0 -1
  202. package/inject-queries.mjs +0 -82
  203. package/inject-queries.mjs.map +0 -1
  204. package/inject-query-client.mjs +0 -9
  205. package/inject-query-client.mjs.map +0 -1
  206. package/inject-query.mjs +0 -14
  207. package/inject-query.mjs.map +0 -1
  208. package/mutation-options.mjs +0 -7
  209. package/mutation-options.mjs.map +0 -1
  210. package/pending-tasks-compat.d.ts +0 -7
  211. package/pending-tasks-compat.mjs +0 -19
  212. package/pending-tasks-compat.mjs.map +0 -1
  213. package/providers.mjs +0 -32
  214. package/providers.mjs.map +0 -1
  215. package/query-options.mjs +0 -7
  216. package/query-options.mjs.map +0 -1
  217. package/signal-proxy.mjs +0 -29
  218. package/signal-proxy.mjs.map +0 -1
@@ -0,0 +1,480 @@
1
+ import {
2
+ QueriesObserver,
3
+ QueryClient,
4
+ notifyManager,
5
+ } from '@tanstack/query-core'
6
+ import {
7
+ DestroyRef,
8
+ Injector,
9
+ NgZone,
10
+ assertInInjectionContext,
11
+ computed,
12
+ effect,
13
+ inject,
14
+ runInInjectionContext,
15
+ signal,
16
+ untracked,
17
+ } from '@angular/core'
18
+ import { signalProxy } from './signal-proxy'
19
+ import { injectIsRestoring } from './inject-is-restoring'
20
+ import type {
21
+ DefaultError,
22
+ OmitKeyof,
23
+ QueriesObserverOptions,
24
+ QueriesPlaceholderDataFunction,
25
+ QueryFunction,
26
+ QueryKey,
27
+ QueryObserverOptions,
28
+ ThrowOnError,
29
+ } from '@tanstack/query-core'
30
+ import type {
31
+ CreateQueryOptions,
32
+ CreateQueryResult,
33
+ DefinedCreateQueryResult,
34
+ } from './types'
35
+ import type { Signal } from '@angular/core'
36
+
37
+ // This defines the `CreateQueryOptions` that are accepted in `QueriesOptions` & `GetOptions`.
38
+ // `placeholderData` function always gets undefined passed
39
+ type QueryObserverOptionsForCreateQueries<
40
+ TQueryFnData = unknown,
41
+ TError = DefaultError,
42
+ TData = TQueryFnData,
43
+ TQueryKey extends QueryKey = QueryKey,
44
+ > = OmitKeyof<
45
+ CreateQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
46
+ 'placeholderData'
47
+ > & {
48
+ placeholderData?: TQueryFnData | QueriesPlaceholderDataFunction<TQueryFnData>
49
+ }
50
+
51
+ // Avoid TS depth-limit error in case of large array literal
52
+ type MAXIMUM_DEPTH = 20
53
+
54
+ // Widen the type of the symbol to enable type inference even if skipToken is not immutable.
55
+ type SkipTokenForCreateQueries = symbol
56
+
57
+ type GetCreateQueryOptionsForCreateQueries<T> =
58
+ // Part 1: responsible for applying explicit type parameter to function arguments, if object { queryFnData: TQueryFnData, error: TError, data: TData }
59
+ T extends {
60
+ queryFnData: infer TQueryFnData
61
+ error?: infer TError
62
+ data: infer TData
63
+ }
64
+ ? QueryObserverOptionsForCreateQueries<TQueryFnData, TError, TData>
65
+ : T extends { queryFnData: infer TQueryFnData; error?: infer TError }
66
+ ? QueryObserverOptionsForCreateQueries<TQueryFnData, TError>
67
+ : T extends { data: infer TData; error?: infer TError }
68
+ ? QueryObserverOptionsForCreateQueries<unknown, TError, TData>
69
+ : // Part 2: responsible for applying explicit type parameter to function arguments, if tuple [TQueryFnData, TError, TData]
70
+ T extends [infer TQueryFnData, infer TError, infer TData]
71
+ ? QueryObserverOptionsForCreateQueries<TQueryFnData, TError, TData>
72
+ : T extends [infer TQueryFnData, infer TError]
73
+ ? QueryObserverOptionsForCreateQueries<TQueryFnData, TError>
74
+ : T extends [infer TQueryFnData]
75
+ ? QueryObserverOptionsForCreateQueries<TQueryFnData>
76
+ : // Part 3: responsible for inferring and enforcing type if no explicit parameter was provided
77
+ T extends {
78
+ queryFn?:
79
+ | QueryFunction<infer TQueryFnData, infer TQueryKey>
80
+ | SkipTokenForCreateQueries
81
+ select?: (data: any) => infer TData
82
+ throwOnError?: ThrowOnError<any, infer TError, any, any>
83
+ }
84
+ ? QueryObserverOptionsForCreateQueries<
85
+ TQueryFnData,
86
+ unknown extends TError ? DefaultError : TError,
87
+ unknown extends TData ? TQueryFnData : TData,
88
+ TQueryKey
89
+ >
90
+ : // Fallback
91
+ QueryObserverOptionsForCreateQueries
92
+
93
+ // A defined initialData setting should return a DefinedCreateQueryResult rather than CreateQueryResult
94
+ type GetDefinedOrUndefinedQueryResult<T, TData, TError = unknown> = T extends {
95
+ initialData?: infer TInitialData
96
+ }
97
+ ? unknown extends TInitialData
98
+ ? CreateQueryResult<TData, TError>
99
+ : TInitialData extends TData
100
+ ? DefinedCreateQueryResult<TData, TError>
101
+ : TInitialData extends () => infer TInitialDataResult
102
+ ? unknown extends TInitialDataResult
103
+ ? CreateQueryResult<TData, TError>
104
+ : TInitialDataResult extends TData
105
+ ? DefinedCreateQueryResult<TData, TError>
106
+ : CreateQueryResult<TData, TError>
107
+ : CreateQueryResult<TData, TError>
108
+ : CreateQueryResult<TData, TError>
109
+
110
+ type GetCreateQueryResult<T> =
111
+ // Part 1: responsible for mapping explicit type parameter to function result, if object
112
+ T extends { queryFnData: any; error?: infer TError; data: infer TData }
113
+ ? GetDefinedOrUndefinedQueryResult<T, TData, TError>
114
+ : T extends { queryFnData: infer TQueryFnData; error?: infer TError }
115
+ ? GetDefinedOrUndefinedQueryResult<T, TQueryFnData, TError>
116
+ : T extends { data: infer TData; error?: infer TError }
117
+ ? GetDefinedOrUndefinedQueryResult<T, TData, TError>
118
+ : // Part 2: responsible for mapping explicit type parameter to function result, if tuple
119
+ T extends [any, infer TError, infer TData]
120
+ ? GetDefinedOrUndefinedQueryResult<T, TData, TError>
121
+ : T extends [infer TQueryFnData, infer TError]
122
+ ? GetDefinedOrUndefinedQueryResult<T, TQueryFnData, TError>
123
+ : T extends [infer TQueryFnData]
124
+ ? GetDefinedOrUndefinedQueryResult<T, TQueryFnData>
125
+ : // Part 3: responsible for mapping inferred type to results, if no explicit parameter was provided
126
+ T extends {
127
+ queryFn?:
128
+ | QueryFunction<infer TQueryFnData, any>
129
+ | SkipTokenForCreateQueries
130
+ select?: (data: any) => infer TData
131
+ throwOnError?: ThrowOnError<any, infer TError, any, any>
132
+ }
133
+ ? GetDefinedOrUndefinedQueryResult<
134
+ T,
135
+ unknown extends TData ? TQueryFnData : TData,
136
+ unknown extends TError ? DefaultError : TError
137
+ >
138
+ : // Fallback
139
+ CreateQueryResult
140
+
141
+ /**
142
+ * The `queries` array accepted by `injectQueries`. Recursively unwraps each tuple element so every entry's
143
+ * `queryFn`/`select`/`throwOnError` are inferred individually, up to 20 elements — past that, tuple
144
+ * recursion falls back to a single homogeneous options type. An opaque array (e.g. `unknown[]`) is returned
145
+ * as-is; a non-tuple array of a known element type is mapped to that element type instead, with no such
146
+ * limit.
147
+ *
148
+ * @template T - The type of the `queries` array as written at the call site.
149
+ * @template TResults - The internal accumulator that this type builds during recursion. It is not meant to
150
+ * be set explicitly.
151
+ * @template TDepth - The internal recursion-depth counter, checked against the 20-element limit. It is not
152
+ * meant to be set explicitly.
153
+ */
154
+ export type QueriesOptions<
155
+ T extends Array<any>,
156
+ TResults extends Array<any> = [],
157
+ TDepth extends ReadonlyArray<number> = [],
158
+ > = TDepth['length'] extends MAXIMUM_DEPTH
159
+ ? Array<QueryObserverOptionsForCreateQueries>
160
+ : T extends []
161
+ ? []
162
+ : T extends [infer Head]
163
+ ? [...TResults, GetCreateQueryOptionsForCreateQueries<Head>]
164
+ : T extends [infer Head, ...infer Tails]
165
+ ? QueriesOptions<
166
+ [...Tails],
167
+ [...TResults, GetCreateQueryOptionsForCreateQueries<Head>],
168
+ [...TDepth, 1]
169
+ >
170
+ : ReadonlyArray<unknown> extends T
171
+ ? T
172
+ : // If T is *some* array but we couldn't assign unknown[] to it, then it must hold some known/homogeneous type!
173
+ // use this to infer the param types in the case of Array.map() argument
174
+ T extends Array<
175
+ QueryObserverOptionsForCreateQueries<
176
+ infer TQueryFnData,
177
+ infer TError,
178
+ infer TData,
179
+ infer TQueryKey
180
+ >
181
+ >
182
+ ? Array<
183
+ QueryObserverOptionsForCreateQueries<
184
+ TQueryFnData,
185
+ TError,
186
+ TData,
187
+ TQueryKey
188
+ >
189
+ >
190
+ : // Fallback
191
+ Array<QueryObserverOptionsForCreateQueries>
192
+
193
+ /**
194
+ * The result type returned by `injectQueries`, when no `combine` is provided. Mirrors {@link QueriesOptions}:
195
+ * each tuple element's result type is inferred individually, up to 20 elements — past that, tuple recursion
196
+ * falls back to a single homogeneous {@link CreateQueryResult} type. A non-tuple array is mapped per-element
197
+ * instead, with no such limit — every entry keeps its individually inferred type regardless of array length.
198
+ *
199
+ * @template T - The type of the `queries` array, as inferred by {@link QueriesOptions}.
200
+ * @template TResults - The internal accumulator that this type builds during recursion. It is not meant to
201
+ * be set explicitly.
202
+ * @template TDepth - The internal recursion-depth counter, checked against the 20-element limit. It is not
203
+ * meant to be set explicitly.
204
+ */
205
+ export type QueriesResults<
206
+ T extends Array<any>,
207
+ TResults extends Array<any> = [],
208
+ TDepth extends ReadonlyArray<number> = [],
209
+ > = TDepth['length'] extends MAXIMUM_DEPTH
210
+ ? Array<CreateQueryResult>
211
+ : T extends []
212
+ ? []
213
+ : T extends [infer Head]
214
+ ? [...TResults, GetCreateQueryResult<Head>]
215
+ : T extends [infer Head, ...infer Tails]
216
+ ? QueriesResults<
217
+ [...Tails],
218
+ [...TResults, GetCreateQueryResult<Head>],
219
+ [...TDepth, 1]
220
+ >
221
+ : { [K in keyof T]: GetCreateQueryResult<T[K]> }
222
+
223
+ export interface InjectQueriesOptions<
224
+ T extends Array<any>,
225
+ TCombinedResult = QueriesResults<T>,
226
+ > {
227
+ queries:
228
+ | readonly [...QueriesOptions<T>]
229
+ | readonly [
230
+ ...{ [K in keyof T]: GetCreateQueryOptionsForCreateQueries<T[K]> },
231
+ ]
232
+ combine?: (result: QueriesResults<T>) => TCombinedResult
233
+ }
234
+
235
+ /**
236
+ * Injects a signal to fetch a variable number of queries.
237
+ *
238
+ * The `queries` key accepts an array with query option objects mostly identical to `injectQuery`'s. Having
239
+ * the same query key more than once in the array of query objects may cause some data to be shared between
240
+ * queries. To avoid this, consider de-duplicating the queries and map the results back to the desired
241
+ * structure.
242
+ *
243
+ * The `combine` option can be used to combine the results of the queries into a single value. The result
244
+ * will be structurally shared to be as referentially stable as possible.
245
+ *
246
+ * @remarks Unlike `injectQuery`, `injectQueries` cannot infer the `data` argument of an _inline_ `select`
247
+ * from its sibling `queryFn`. Because `injectQueries` infers the type of the whole `queries` array at once,
248
+ * the `select` parameter of a query object written inline cannot be contextually typed from that same
249
+ * object's `queryFn`, so it falls back to `unknown` — a
250
+ * [known TypeScript limitation](https://github.com/TanStack/query/issues/6556). Annotate the `select`
251
+ * parameter explicitly, or define the query with {@link queryOptions}, which resolves its types in a single
252
+ * object _before_ it reaches `injectQueries`, to work around this — see the example below.
253
+ * @param optionsFn - A function returning the queries' options — an array of query option objects under
254
+ * `queries`, and an optional `combine`. Similar to `computed` from Angular, this function runs in the
255
+ * reactive context, so signals read inside it (e.g. to build the `queries` array) drive the queries.
256
+ * @param injector - The `Injector` in which to create the queries. If this is not provided, the current
257
+ * injection context will be used instead (via `inject`).
258
+ * @returns A `Signal` with the combined result. Without `combine`, this is an array with all the query
259
+ * results, in the same order as the input. When `combine` is provided, this is the value returned by
260
+ * `combine` instead.
261
+ *
262
+ * @example
263
+ * ```angular-ts
264
+ * @Component({
265
+ * selector: 'posts',
266
+ * template: `
267
+ * <ul>
268
+ * @for (query of postQueries(); track $index) {
269
+ * @if (query.isPending()) {
270
+ * <li>Loading...</li>
271
+ * } @else if (query.isError()) {
272
+ * <li>Error: {{ query.error()?.message }}</li>
273
+ * } @else {
274
+ * <li>{{ query.data().title }}</li>
275
+ * }
276
+ * }
277
+ * </ul>
278
+ * `,
279
+ * })
280
+ * export class Posts {
281
+ * readonly ids = signal([1, 2, 3])
282
+ *
283
+ * readonly postQueries = injectQueries(() => ({
284
+ * queries: this.ids().map((id) => ({
285
+ * queryKey: ['post', id],
286
+ * queryFn: () => fetchPost(id),
287
+ * staleTime: Infinity,
288
+ * })),
289
+ * }))
290
+ * }
291
+ * ```
292
+ *
293
+ * @example
294
+ * Combining results into a single value:
295
+ * ```angular-ts
296
+ * @Component({
297
+ * selector: 'posts',
298
+ * template: `
299
+ * @if (combined().isPending) {
300
+ * Loading...
301
+ * } @else if (combined().isError) {
302
+ * Error loading posts
303
+ * } @else {
304
+ * <ul>
305
+ * @for (post of combined().data; track post?.id) {
306
+ * <li>{{ post?.title }}</li>
307
+ * }
308
+ * </ul>
309
+ * }
310
+ * `,
311
+ * })
312
+ * export class Posts {
313
+ * readonly ids = signal([1, 2, 3])
314
+ *
315
+ * readonly combined = injectQueries(() => ({
316
+ * queries: this.ids().map((id) => ({
317
+ * queryKey: ['post', id],
318
+ * queryFn: () => fetchPost(id),
319
+ * })),
320
+ * combine: (postQueries) => ({
321
+ * data: postQueries.map((query) => query.data),
322
+ * isPending: postQueries.some((query) => query.isPending),
323
+ * isError: postQueries.some((query) => query.isError),
324
+ * }),
325
+ * }))
326
+ * }
327
+ * ```
328
+ *
329
+ * @example
330
+ * Typing `select` via {@link queryOptions}. Note that spreading a `queryOptions` result and overriding
331
+ * `select` inline still falls back to `unknown` — wrap the spread in `queryOptions` again so the override is
332
+ * resolved before it reaches `injectQueries`:
333
+ * ```angular-ts
334
+ * const postOptions = (id: number) =>
335
+ * queryOptions({
336
+ * queryKey: ['post', id],
337
+ * queryFn: () => fetchPost(id),
338
+ * })
339
+ *
340
+ * @Component({
341
+ * selector: 'post-title',
342
+ * template: `<h1>{{ fixed()[0].data() }}</h1>`,
343
+ * })
344
+ * export class PostTitle {
345
+ * readonly id = signal(1)
346
+ *
347
+ * readonly broken = injectQueries(() => ({
348
+ * queries: [
349
+ * {
350
+ * ...postOptions(this.id()),
351
+ * // ❌ `data` is `unknown` here
352
+ * select: (data) => data.title,
353
+ * },
354
+ * ],
355
+ * }))
356
+ *
357
+ * readonly fixed = injectQueries(() => ({
358
+ * queries: [
359
+ * queryOptions({
360
+ * ...postOptions(this.id()),
361
+ * // ✅ `data` is `Post`
362
+ * select: (data) => data.title,
363
+ * }),
364
+ * ],
365
+ * }))
366
+ * }
367
+ * ```
368
+ */
369
+ export function injectQueries<
370
+ T extends Array<any>,
371
+ TCombinedResult = QueriesResults<T>,
372
+ >(
373
+ optionsFn: () => InjectQueriesOptions<T, TCombinedResult>,
374
+ injector?: Injector,
375
+ ): Signal<TCombinedResult> {
376
+ !injector && assertInInjectionContext(injectQueries)
377
+ return runInInjectionContext(injector ?? inject(Injector), () => {
378
+ const destroyRef = inject(DestroyRef)
379
+ const ngZone = inject(NgZone)
380
+ const queryClient = inject(QueryClient)
381
+ const isRestoring = injectIsRestoring()
382
+
383
+ /**
384
+ * Signal that has the default options from query client applied
385
+ * computed() is used so signals can be inserted into the options
386
+ * making it reactive. Wrapping options in a function ensures embedded expressions
387
+ * are preserved and can keep being applied after signal changes
388
+ */
389
+ const optionsSignal = computed(() => {
390
+ return optionsFn()
391
+ })
392
+
393
+ const defaultedQueries = computed(() => {
394
+ return optionsSignal().queries.map((opts) => {
395
+ const defaultedOptions = queryClient.defaultQueryOptions(
396
+ opts as QueryObserverOptions,
397
+ )
398
+ // Make sure the results are already in fetching state before subscribing or updating options
399
+ defaultedOptions._optimisticResults = isRestoring()
400
+ ? 'isRestoring'
401
+ : 'optimistic'
402
+
403
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
404
+ return defaultedOptions as QueryObserverOptions
405
+ })
406
+ })
407
+
408
+ const observerSignal = (() => {
409
+ let instance: QueriesObserver<TCombinedResult> | null = null
410
+
411
+ return computed(() => {
412
+ return (instance ||= new QueriesObserver<TCombinedResult>(
413
+ queryClient,
414
+ defaultedQueries(),
415
+ optionsSignal() as QueriesObserverOptions<TCombinedResult>,
416
+ ))
417
+ })
418
+ })()
419
+
420
+ const optimisticResultSignal = computed(() =>
421
+ observerSignal().getOptimisticResult(
422
+ defaultedQueries(),
423
+ (optionsSignal() as QueriesObserverOptions<TCombinedResult>).combine,
424
+ ),
425
+ )
426
+
427
+ // Do not notify on updates because of changes in the options because
428
+ // these changes should already be reflected in the optimistic result.
429
+ effect(() => {
430
+ observerSignal().setQueries(
431
+ defaultedQueries(),
432
+ optionsSignal() as QueriesObserverOptions<TCombinedResult>,
433
+ )
434
+ })
435
+
436
+ const optimisticCombinedResultSignal = computed(() => {
437
+ const [_optimisticResult, getCombinedResult, trackResult] =
438
+ optimisticResultSignal()
439
+ return getCombinedResult(trackResult())
440
+ })
441
+
442
+ const resultFromSubscriberSignal = signal<TCombinedResult | null>(null)
443
+
444
+ effect(() => {
445
+ const observer = observerSignal()
446
+ const [_optimisticResult, getCombinedResult] = optimisticResultSignal()
447
+
448
+ untracked(() => {
449
+ const unsubscribe = isRestoring()
450
+ ? () => undefined
451
+ : ngZone.runOutsideAngular(() =>
452
+ observer.subscribe(
453
+ notifyManager.batchCalls((state) => {
454
+ resultFromSubscriberSignal.set(getCombinedResult(state))
455
+ }),
456
+ ),
457
+ )
458
+
459
+ destroyRef.onDestroy(unsubscribe)
460
+ })
461
+ })
462
+
463
+ const resultSignal = computed(() => {
464
+ const subscriberResult = resultFromSubscriberSignal()
465
+ const optimisticResult = optimisticCombinedResultSignal()
466
+ return subscriberResult ?? optimisticResult
467
+ })
468
+
469
+ return computed(() => {
470
+ const result = resultSignal()
471
+ const { combine } = optionsSignal()
472
+
473
+ return combine
474
+ ? result
475
+ : (result as QueriesResults<T>).map((query) =>
476
+ signalProxy(signal(query)),
477
+ )
478
+ })
479
+ }) as unknown as Signal<TCombinedResult>
480
+ }
@@ -0,0 +1,22 @@
1
+ import { Injector, inject } from '@angular/core'
2
+ import { QueryClient } from '@tanstack/query-core'
3
+ import type { InjectOptions } from '@angular/core'
4
+
5
+ /**
6
+ * Injects a `QueryClient` instance and allows passing a custom injector.
7
+ * @param injectOptions - Type of the options argument to inject and optionally a custom injector.
8
+ * @returns The `QueryClient` instance.
9
+ * @deprecated Use `inject(QueryClient)` instead.
10
+ * If you need to get a `QueryClient` from a custom injector, use `injector.get(QueryClient)`.
11
+ *
12
+ *
13
+ * **Example**
14
+ * ```ts
15
+ * const queryClient = injectQueryClient();
16
+ * ```
17
+ */
18
+ export function injectQueryClient(
19
+ injectOptions: InjectOptions & { injector?: Injector } = {},
20
+ ) {
21
+ return (injectOptions.injector ?? inject(Injector)).get(QueryClient)
22
+ }