@tanstack/lit-query 0.0.1 → 0.2.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 (132) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +125 -43
  3. package/dist/QueryClientProvider.d.ts +85 -0
  4. package/dist/QueryClientProvider.d.ts.map +1 -0
  5. package/dist/QueryClientProvider.js +137 -0
  6. package/dist/QueryClientProvider.js.map +1 -0
  7. package/dist/accessor.d.ts +32 -0
  8. package/dist/accessor.d.ts.map +1 -0
  9. package/dist/accessor.js +12 -0
  10. package/dist/accessor.js.map +1 -0
  11. package/dist/context.d.ts +59 -0
  12. package/dist/context.d.ts.map +1 -0
  13. package/dist/context.js +100 -0
  14. package/dist/context.js.map +1 -0
  15. package/dist/controllers/BaseController.d.ts +38 -0
  16. package/dist/controllers/BaseController.d.ts.map +1 -0
  17. package/dist/controllers/BaseController.js +204 -0
  18. package/dist/controllers/BaseController.js.map +1 -0
  19. package/dist/createInfiniteQueryController.d.ts +76 -0
  20. package/dist/createInfiniteQueryController.d.ts.map +1 -0
  21. package/dist/createInfiniteQueryController.js +221 -0
  22. package/dist/createInfiniteQueryController.js.map +1 -0
  23. package/dist/createMutationController.d.ts +79 -0
  24. package/dist/createMutationController.d.ts.map +1 -0
  25. package/dist/createMutationController.js +193 -0
  26. package/dist/createMutationController.js.map +1 -0
  27. package/dist/createQueriesController.d.ts +145 -0
  28. package/dist/createQueriesController.d.ts.map +1 -0
  29. package/dist/createQueriesController.js +302 -0
  30. package/dist/createQueriesController.js.map +1 -0
  31. package/dist/createQueryController.d.ts +67 -0
  32. package/dist/createQueryController.d.ts.map +1 -0
  33. package/dist/createQueryController.js +211 -0
  34. package/dist/createQueryController.js.map +1 -0
  35. package/dist/index.d.ts +24 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +15 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/infiniteQueryOptions.d.ts +21 -0
  40. package/dist/infiniteQueryOptions.d.ts.map +1 -0
  41. package/dist/infiniteQueryOptions.js +22 -0
  42. package/dist/infiniteQueryOptions.js.map +1 -0
  43. package/dist/mutationOptions.d.ts +19 -0
  44. package/dist/mutationOptions.d.ts.map +1 -0
  45. package/dist/mutationOptions.js +20 -0
  46. package/dist/mutationOptions.js.map +1 -0
  47. package/dist/queryOptions.d.ts +62 -0
  48. package/dist/queryOptions.d.ts.map +1 -0
  49. package/dist/queryOptions.js +4 -0
  50. package/dist/queryOptions.js.map +1 -0
  51. package/dist/types.d.ts +31 -0
  52. package/dist/types.d.ts.map +1 -0
  53. package/dist/types.js +2 -0
  54. package/dist/types.js.map +1 -0
  55. package/dist/useIsFetching.d.ts +45 -0
  56. package/dist/useIsFetching.d.ts.map +1 -0
  57. package/dist/useIsFetching.js +116 -0
  58. package/dist/useIsFetching.js.map +1 -0
  59. package/dist/useIsMutating.d.ts +45 -0
  60. package/dist/useIsMutating.d.ts.map +1 -0
  61. package/dist/useIsMutating.js +116 -0
  62. package/dist/useIsMutating.js.map +1 -0
  63. package/dist/useMutationState.d.ts +57 -0
  64. package/dist/useMutationState.d.ts.map +1 -0
  65. package/dist/useMutationState.js +132 -0
  66. package/dist/useMutationState.js.map +1 -0
  67. package/dist-cjs/QueryClientProvider.d.cts +84 -0
  68. package/dist-cjs/QueryClientProvider.js +141 -0
  69. package/dist-cjs/QueryClientProvider.js.map +1 -0
  70. package/dist-cjs/accessor.d.cts +31 -0
  71. package/dist-cjs/accessor.js +16 -0
  72. package/dist-cjs/accessor.js.map +1 -0
  73. package/dist-cjs/context.d.cts +58 -0
  74. package/dist-cjs/context.js +109 -0
  75. package/dist-cjs/context.js.map +1 -0
  76. package/dist-cjs/controllers/BaseController.d.cts +37 -0
  77. package/dist-cjs/controllers/BaseController.js +208 -0
  78. package/dist-cjs/controllers/BaseController.js.map +1 -0
  79. package/dist-cjs/createInfiniteQueryController.d.cts +75 -0
  80. package/dist-cjs/createInfiniteQueryController.js +224 -0
  81. package/dist-cjs/createInfiniteQueryController.js.map +1 -0
  82. package/dist-cjs/createMutationController.d.cts +78 -0
  83. package/dist-cjs/createMutationController.js +196 -0
  84. package/dist-cjs/createMutationController.js.map +1 -0
  85. package/dist-cjs/createQueriesController.d.cts +144 -0
  86. package/dist-cjs/createQueriesController.js +305 -0
  87. package/dist-cjs/createQueriesController.js.map +1 -0
  88. package/dist-cjs/createQueryController.d.cts +66 -0
  89. package/dist-cjs/createQueryController.js +214 -0
  90. package/dist-cjs/createQueryController.js.map +1 -0
  91. package/dist-cjs/index.d.cts +23 -0
  92. package/dist-cjs/index.js +49 -0
  93. package/dist-cjs/index.js.map +1 -0
  94. package/dist-cjs/infiniteQueryOptions.d.cts +20 -0
  95. package/dist-cjs/infiniteQueryOptions.js +25 -0
  96. package/dist-cjs/infiniteQueryOptions.js.map +1 -0
  97. package/dist-cjs/mutationOptions.d.cts +18 -0
  98. package/dist-cjs/mutationOptions.js +23 -0
  99. package/dist-cjs/mutationOptions.js.map +1 -0
  100. package/dist-cjs/package.json +3 -0
  101. package/dist-cjs/queryOptions.d.cts +61 -0
  102. package/dist-cjs/queryOptions.js +7 -0
  103. package/dist-cjs/queryOptions.js.map +1 -0
  104. package/dist-cjs/types.d.cts +30 -0
  105. package/dist-cjs/types.js +3 -0
  106. package/dist-cjs/types.js.map +1 -0
  107. package/dist-cjs/useIsFetching.d.cts +44 -0
  108. package/dist-cjs/useIsFetching.js +119 -0
  109. package/dist-cjs/useIsFetching.js.map +1 -0
  110. package/dist-cjs/useIsMutating.d.cts +44 -0
  111. package/dist-cjs/useIsMutating.js +119 -0
  112. package/dist-cjs/useIsMutating.js.map +1 -0
  113. package/dist-cjs/useMutationState.d.cts +56 -0
  114. package/dist-cjs/useMutationState.js +135 -0
  115. package/dist-cjs/useMutationState.js.map +1 -0
  116. package/package.json +73 -8
  117. package/src/QueryClientProvider.ts +174 -0
  118. package/src/accessor.ts +43 -0
  119. package/src/context.ts +120 -0
  120. package/src/controllers/BaseController.ts +284 -0
  121. package/src/createInfiniteQueryController.ts +394 -0
  122. package/src/createMutationController.ts +361 -0
  123. package/src/createQueriesController.ts +633 -0
  124. package/src/createQueryController.ts +342 -0
  125. package/src/index.ts +72 -0
  126. package/src/infiniteQueryOptions.ts +48 -0
  127. package/src/mutationOptions.ts +31 -0
  128. package/src/queryOptions.ts +143 -0
  129. package/src/types.ts +77 -0
  130. package/src/useIsFetching.ts +159 -0
  131. package/src/useIsMutating.ts +159 -0
  132. package/src/useMutationState.ts +201 -0
@@ -0,0 +1,633 @@
1
+ import {
2
+ QueriesObserver,
3
+ type DefaultError,
4
+ type DefinedQueryObserverResult,
5
+ type OmitKeyof,
6
+ type QueriesObserverOptions,
7
+ type QueryFunction,
8
+ type QueryKey,
9
+ type QueryObserverOptions,
10
+ type QueryObserverResult,
11
+ type ThrowOnError,
12
+ } from '@tanstack/query-core'
13
+ import type { QueryClient } from '@tanstack/query-core'
14
+ import type { ReactiveControllerHost } from 'lit'
15
+ import {
16
+ createValueAccessor,
17
+ readAccessor,
18
+ type Accessor,
19
+ type ValueAccessor,
20
+ } from './accessor.js'
21
+ import { createMissingQueryClientError } from './context.js'
22
+ import { BaseController } from './controllers/BaseController.js'
23
+
24
+ /**
25
+ * Options for one query inside `createQueriesController`.
26
+ *
27
+ * This mirrors `QueryObserverOptions` and is used by the tuple inference that
28
+ * maps each input query to its corresponding result.
29
+ */
30
+ export type CreateQueriesInput<
31
+ TQueryFnData = unknown,
32
+ TError = DefaultError,
33
+ TData = TQueryFnData,
34
+ TQueryKey extends QueryKey = QueryKey,
35
+ > = QueryObserverOptions<TQueryFnData, TError, TData, TQueryFnData, TQueryKey>
36
+
37
+ type CreateQueriesInputForController<
38
+ TQueryFnData = unknown,
39
+ TError = DefaultError,
40
+ TData = TQueryFnData,
41
+ TQueryKey extends QueryKey = QueryKey,
42
+ > = OmitKeyof<CreateQueriesInput<TQueryFnData, TError, TData, TQueryKey>, never>
43
+
44
+ type MAXIMUM_DEPTH = 20
45
+
46
+ type SkipTokenForCreateQueries = symbol
47
+
48
+ type GetCreateQueriesInput<T> = T extends {
49
+ queryFnData: infer TQueryFnData
50
+ error?: infer TError
51
+ data: infer TData
52
+ }
53
+ ? CreateQueriesInputForController<TQueryFnData, TError, TData>
54
+ : T extends { queryFnData: infer TQueryFnData; error?: infer TError }
55
+ ? CreateQueriesInputForController<TQueryFnData, TError>
56
+ : T extends { data: infer TData; error?: infer TError }
57
+ ? CreateQueriesInputForController<unknown, TError, TData>
58
+ : T extends [infer TQueryFnData, infer TError, infer TData]
59
+ ? CreateQueriesInputForController<TQueryFnData, TError, TData>
60
+ : T extends [infer TQueryFnData, infer TError]
61
+ ? CreateQueriesInputForController<TQueryFnData, TError>
62
+ : T extends [infer TQueryFnData]
63
+ ? CreateQueriesInputForController<TQueryFnData>
64
+ : T extends {
65
+ queryFn?:
66
+ | QueryFunction<infer TQueryFnData, infer TQueryKey>
67
+ | SkipTokenForCreateQueries
68
+ select?: (data: any) => infer TData
69
+ throwOnError?: ThrowOnError<any, infer TError, any, any>
70
+ }
71
+ ? CreateQueriesInputForController<
72
+ TQueryFnData,
73
+ unknown extends TError ? DefaultError : TError,
74
+ unknown extends TData ? TQueryFnData : TData,
75
+ TQueryKey
76
+ >
77
+ : CreateQueriesInputForController
78
+
79
+ type GetDefinedOrUndefinedCreateQueriesResult<
80
+ T,
81
+ TData,
82
+ TError = unknown,
83
+ > = T extends {
84
+ initialData?: infer TInitialData
85
+ }
86
+ ? unknown extends TInitialData
87
+ ? QueryObserverResult<TData, TError>
88
+ : TInitialData extends TData
89
+ ? DefinedQueryObserverResult<TData, TError>
90
+ : TInitialData extends () => infer TInitialDataResult
91
+ ? unknown extends TInitialDataResult
92
+ ? QueryObserverResult<TData, TError>
93
+ : TInitialDataResult extends TData
94
+ ? DefinedQueryObserverResult<TData, TError>
95
+ : QueryObserverResult<TData, TError>
96
+ : QueryObserverResult<TData, TError>
97
+ : QueryObserverResult<TData, TError>
98
+
99
+ type GetCreateQueriesResult<T> = T extends {
100
+ queryFnData: any
101
+ error?: infer TError
102
+ data: infer TData
103
+ }
104
+ ? GetDefinedOrUndefinedCreateQueriesResult<T, TData, TError>
105
+ : T extends { queryFnData: infer TQueryFnData; error?: infer TError }
106
+ ? GetDefinedOrUndefinedCreateQueriesResult<T, TQueryFnData, TError>
107
+ : T extends { data: infer TData; error?: infer TError }
108
+ ? GetDefinedOrUndefinedCreateQueriesResult<T, TData, TError>
109
+ : T extends [any, infer TError, infer TData]
110
+ ? GetDefinedOrUndefinedCreateQueriesResult<T, TData, TError>
111
+ : T extends [infer TQueryFnData, infer TError]
112
+ ? GetDefinedOrUndefinedCreateQueriesResult<T, TQueryFnData, TError>
113
+ : T extends [infer TQueryFnData]
114
+ ? GetDefinedOrUndefinedCreateQueriesResult<T, TQueryFnData>
115
+ : T extends {
116
+ queryFn?:
117
+ | QueryFunction<infer TQueryFnData, any>
118
+ | SkipTokenForCreateQueries
119
+ select?: (data: any) => infer TData
120
+ throwOnError?: ThrowOnError<any, infer TError, any, any>
121
+ }
122
+ ? GetDefinedOrUndefinedCreateQueriesResult<
123
+ T,
124
+ unknown extends TData ? TQueryFnData : TData,
125
+ unknown extends TError ? DefaultError : TError
126
+ >
127
+ : QueryObserverResult
128
+
129
+ export type CreateQueriesOptions<
130
+ T extends Array<any>,
131
+ TResults extends Array<any> = [],
132
+ TDepth extends ReadonlyArray<number> = [],
133
+ > = TDepth['length'] extends MAXIMUM_DEPTH
134
+ ? Array<CreateQueriesInputForController>
135
+ : T extends []
136
+ ? []
137
+ : T extends [infer Head]
138
+ ? [...TResults, GetCreateQueriesInput<Head>]
139
+ : T extends [infer Head, ...infer Tails]
140
+ ? CreateQueriesOptions<
141
+ [...Tails],
142
+ [...TResults, GetCreateQueriesInput<Head>],
143
+ [...TDepth, 1]
144
+ >
145
+ : ReadonlyArray<unknown> extends T
146
+ ? T
147
+ : T extends Array<
148
+ CreateQueriesInputForController<
149
+ infer TQueryFnData,
150
+ infer TError,
151
+ infer TData,
152
+ infer TQueryKey
153
+ >
154
+ >
155
+ ? Array<
156
+ CreateQueriesInputForController<
157
+ TQueryFnData,
158
+ TError,
159
+ TData,
160
+ TQueryKey
161
+ >
162
+ >
163
+ : Array<CreateQueriesInputForController>
164
+
165
+ /**
166
+ * Tuple of query results inferred from the query inputs passed to
167
+ * `createQueriesController`.
168
+ */
169
+ export type CreateQueriesResults<
170
+ T extends Array<any>,
171
+ TResults extends Array<any> = [],
172
+ TDepth extends ReadonlyArray<number> = [],
173
+ > = TDepth['length'] extends MAXIMUM_DEPTH
174
+ ? Array<QueryObserverResult>
175
+ : T extends []
176
+ ? []
177
+ : T extends [infer Head]
178
+ ? [...TResults, GetCreateQueriesResult<Head>]
179
+ : T extends [infer Head, ...infer Tails]
180
+ ? CreateQueriesResults<
181
+ [...Tails],
182
+ [...TResults, GetCreateQueriesResult<Head>],
183
+ [...TDepth, 1]
184
+ >
185
+ : { [K in keyof T]: GetCreateQueriesResult<T[K]> }
186
+
187
+ /**
188
+ * Options accepted by `createQueriesController`.
189
+ *
190
+ * `queries` can be a static list or a getter that returns the current list.
191
+ * `combine` can reshape the array of query results into a single value for the
192
+ * returned accessor.
193
+ */
194
+ export type CreateQueriesControllerOptions<
195
+ TQueryOptions extends Array<any> = Array<any>,
196
+ TCombinedResult = CreateQueriesResults<TQueryOptions>,
197
+ > = {
198
+ /** Query options to observe, or a getter that returns the current options. */
199
+ queries: Accessor<
200
+ | readonly [...CreateQueriesOptions<TQueryOptions>]
201
+ | readonly [
202
+ ...{
203
+ [K in keyof TQueryOptions]: GetCreateQueriesInput<TQueryOptions[K]>
204
+ },
205
+ ]
206
+ >
207
+ /** Optional function that combines the query result array into one value. */
208
+ combine?: (result: CreateQueriesResults<TQueryOptions>) => TCombinedResult
209
+ }
210
+
211
+ /**
212
+ * Accessor returned by `createQueriesController`.
213
+ *
214
+ * Call the accessor or read its `current` property to get the latest combined
215
+ * value.
216
+ */
217
+ export type QueriesResultAccessor<TCombinedResult> =
218
+ ValueAccessor<TCombinedResult> & {
219
+ /** Removes the controller from its Lit host and unsubscribes observers. */
220
+ destroy: () => void
221
+ }
222
+
223
+ function createPendingQueryObserverResult(): QueryObserverResult {
224
+ return {
225
+ data: undefined,
226
+ dataUpdatedAt: 0,
227
+ error: null,
228
+ errorUpdatedAt: 0,
229
+ failureCount: 0,
230
+ failureReason: null,
231
+ errorUpdateCount: 0,
232
+ isError: false,
233
+ isFetched: false,
234
+ isFetchedAfterMount: false,
235
+ isFetching: false,
236
+ isInitialLoading: false,
237
+ isLoading: false,
238
+ isLoadingError: false,
239
+ isPaused: false,
240
+ isPending: true,
241
+ isPlaceholderData: false,
242
+ isRefetchError: false,
243
+ isRefetching: false,
244
+ isStale: true,
245
+ isEnabled: true,
246
+ isSuccess: false,
247
+ fetchStatus: 'idle',
248
+ status: 'pending',
249
+ refetch: (() =>
250
+ Promise.reject(
251
+ createMissingQueryClientError(),
252
+ )) as QueryObserverResult['refetch'],
253
+ promise: Promise.resolve(undefined as never),
254
+ } as unknown as QueryObserverResult
255
+ }
256
+
257
+ function createPlaceholderQueryObserverResult(
258
+ query: QueryObserverOptions,
259
+ ): QueryObserverResult {
260
+ const initialData =
261
+ typeof query.initialData === 'function'
262
+ ? query.initialData()
263
+ : query.initialData
264
+
265
+ if (initialData === undefined) {
266
+ return createPendingQueryObserverResult()
267
+ }
268
+
269
+ const data = query.select ? query.select(initialData) : initialData
270
+ const initialDataUpdatedAt =
271
+ typeof query.initialDataUpdatedAt === 'function'
272
+ ? query.initialDataUpdatedAt()
273
+ : query.initialDataUpdatedAt
274
+
275
+ return {
276
+ ...createPendingQueryObserverResult(),
277
+ data,
278
+ dataUpdatedAt: initialDataUpdatedAt ?? Date.now(),
279
+ isPending: false,
280
+ isInitialLoading: false,
281
+ isLoading: false,
282
+ isSuccess: true,
283
+ status: 'success',
284
+ promise: Promise.resolve(data as never),
285
+ } as QueryObserverResult
286
+ }
287
+
288
+ function resolveQueriesOptions<TCombinedResult>(
289
+ optionsAccessor: Accessor<
290
+ CreateQueriesControllerOptions<any, TCombinedResult>
291
+ >,
292
+ client: QueryClient,
293
+ ): {
294
+ queries: Array<QueryObserverOptions>
295
+ combine: QueriesObserverOptions<TCombinedResult>['combine']
296
+ } {
297
+ const resolvedOptions = readAccessor(optionsAccessor)
298
+ const resolvedQueries = readAccessor(resolvedOptions.queries)
299
+ const combine =
300
+ resolvedOptions.combine as QueriesObserverOptions<TCombinedResult>['combine']
301
+
302
+ return {
303
+ queries: resolvedQueries.map((query) => {
304
+ const defaulted = client.defaultQueryOptions(
305
+ query as QueryObserverOptions,
306
+ )
307
+ ;(defaulted as { _optimisticResults?: 'optimistic' })._optimisticResults =
308
+ 'optimistic'
309
+ return defaulted
310
+ }),
311
+ combine,
312
+ }
313
+ }
314
+
315
+ class QueriesController<
316
+ TQueryOptions extends Array<any>,
317
+ TCombinedResult,
318
+ > extends BaseController<TCombinedResult> {
319
+ private readonly options: Accessor<
320
+ CreateQueriesControllerOptions<TQueryOptions, TCombinedResult>
321
+ >
322
+ private observer: QueriesObserver<TCombinedResult> | undefined
323
+ private unsubscribe: (() => void) | undefined
324
+ private queryClient: QueryClient | undefined
325
+ private explicitInitializationError: unknown | undefined
326
+ private placeholderInitialized = false
327
+ private placeholderRetryableFailure = true
328
+
329
+ constructor(
330
+ host: ReactiveControllerHost,
331
+ options: Accessor<
332
+ CreateQueriesControllerOptions<TQueryOptions, TCombinedResult>
333
+ >,
334
+ queryClient?: QueryClient,
335
+ ) {
336
+ super(host, [] as unknown as TCombinedResult, queryClient)
337
+ this.options = options
338
+
339
+ queueMicrotask(() => {
340
+ this.placeholderRetryableFailure = false
341
+ })
342
+
343
+ if (!queryClient) {
344
+ return
345
+ }
346
+
347
+ if (this.shouldRefreshOnHostUpdate()) {
348
+ return
349
+ }
350
+
351
+ this.tryInitializeExplicitClient(queryClient)
352
+ }
353
+
354
+ protected onConnected(): void {
355
+ if (!this.syncClient()) {
356
+ return
357
+ }
358
+
359
+ this.refreshOptions()
360
+ this.subscribe()
361
+ }
362
+
363
+ protected onDisconnected(): void {
364
+ this.unsubscribeObserver()
365
+ this.syncClient()
366
+ }
367
+
368
+ protected onHostUpdate(): void {
369
+ if (!this.shouldRefreshOnHostUpdate()) {
370
+ return
371
+ }
372
+
373
+ if (!this.refreshOptions()) {
374
+ this.setPlaceholderResult()
375
+ }
376
+ }
377
+
378
+ protected onQueryClientChanged(): void {
379
+ if (!this.syncClient() || !this.connectedState) {
380
+ return
381
+ }
382
+
383
+ this.refreshOptions()
384
+ this.subscribe()
385
+ }
386
+
387
+ private subscribe(): void {
388
+ if (!this.observer) {
389
+ return
390
+ }
391
+
392
+ if (this.unsubscribe) {
393
+ return
394
+ }
395
+
396
+ this.unsubscribe = this.observer.subscribe((next) => {
397
+ const { combine } = this.readResolvedOptions()
398
+ this.setResult(this.computeResult(next, combine))
399
+ })
400
+ }
401
+
402
+ private tryInitializeExplicitClient(queryClient: QueryClient): boolean {
403
+ try {
404
+ const { queries, combine } = resolveQueriesOptions(
405
+ this.options,
406
+ queryClient,
407
+ )
408
+ const observer = new QueriesObserver(queryClient, queries, {
409
+ combine,
410
+ } as QueriesObserverOptions<TCombinedResult>)
411
+ this.queryClient = queryClient
412
+ this.observer = observer
413
+ this.result = this.computeResult(observer.getCurrentResult(), combine)
414
+ this.explicitInitializationError = undefined
415
+ this.placeholderInitialized = true
416
+ return true
417
+ } catch (error) {
418
+ // Retry after construction completes so late host fields used by
419
+ // static queries/combine callbacks can finish initializing first.
420
+ this.explicitInitializationError = error
421
+ this.queryClient = undefined
422
+ this.observer = undefined
423
+ return false
424
+ }
425
+ }
426
+
427
+ private retryExplicitInitializationIfNeeded(): boolean {
428
+ if (!this.explicitInitializationError || this.shouldRefreshOnHostUpdate()) {
429
+ return false
430
+ }
431
+
432
+ const explicitClient = this.tryGetQueryClient()
433
+ if (!explicitClient) {
434
+ return false
435
+ }
436
+
437
+ return this.tryInitializeExplicitClient(explicitClient)
438
+ }
439
+
440
+ private unsubscribeObserver(): void {
441
+ this.unsubscribe?.()
442
+ this.unsubscribe = undefined
443
+ }
444
+
445
+ private syncClient(): boolean {
446
+ const nextClient = this.tryGetQueryClient()
447
+ if (!nextClient) {
448
+ this.unsubscribeObserver()
449
+ this.queryClient = undefined
450
+ this.observer = undefined
451
+ this.setPlaceholderResult()
452
+ return false
453
+ }
454
+
455
+ if (nextClient === this.queryClient) {
456
+ return true
457
+ }
458
+
459
+ this.unsubscribeObserver()
460
+ this.queryClient = nextClient
461
+ const { queries, combine } = this.readResolvedOptions()
462
+ this.observer = new QueriesObserver(this.queryClient, queries, {
463
+ combine,
464
+ } as QueriesObserverOptions<TCombinedResult>)
465
+ this.setResult(
466
+ this.computeResult(this.observer.getCurrentResult(), combine),
467
+ )
468
+ this.placeholderInitialized = true
469
+ return true
470
+ }
471
+
472
+ private refreshOptions(): boolean {
473
+ if (!this.syncClient() || !this.observer) {
474
+ return false
475
+ }
476
+
477
+ const { queries, combine } = this.readResolvedOptions()
478
+
479
+ this.observer.setQueries(queries, {
480
+ combine,
481
+ } as QueriesObserverOptions<TCombinedResult>)
482
+
483
+ const [rawResult, getCombinedResult] = this.observer.getOptimisticResult(
484
+ queries,
485
+ combine,
486
+ )
487
+
488
+ this.setResult(getCombinedResult(rawResult))
489
+ return true
490
+ }
491
+
492
+ private readResolvedOptions(client = this.queryClient): {
493
+ queries: Array<QueryObserverOptions>
494
+ combine: QueriesObserverOptions<TCombinedResult>['combine']
495
+ } {
496
+ if (!client) {
497
+ throw createMissingQueryClientError()
498
+ }
499
+
500
+ return resolveQueriesOptions(
501
+ this.options as Accessor<
502
+ CreateQueriesControllerOptions<any, TCombinedResult>
503
+ >,
504
+ client,
505
+ )
506
+ }
507
+
508
+ private shouldRefreshOnHostUpdate(): boolean {
509
+ if (typeof this.options === 'function') {
510
+ return true
511
+ }
512
+
513
+ return typeof this.options.queries === 'function'
514
+ }
515
+
516
+ private computeResult(
517
+ rawResult: Array<QueryObserverResult>,
518
+ combine: QueriesObserverOptions<TCombinedResult>['combine'],
519
+ ): TCombinedResult {
520
+ return (combine ? combine(rawResult) : rawResult) as TCombinedResult
521
+ }
522
+
523
+ private static createPlaceholderResult<TCombinedResult>(
524
+ optionsAccessor: Accessor<
525
+ CreateQueriesControllerOptions<any, TCombinedResult>
526
+ >,
527
+ ): TCombinedResult {
528
+ const resolvedOptions = readAccessor(optionsAccessor)
529
+ const queries = readAccessor(resolvedOptions.queries)
530
+ const placeholders = queries.map((query) =>
531
+ createPlaceholderQueryObserverResult(query as QueryObserverOptions),
532
+ )
533
+ return (
534
+ resolvedOptions.combine
535
+ ? resolvedOptions.combine(placeholders as never)
536
+ : placeholders
537
+ ) as TCombinedResult
538
+ }
539
+
540
+ readCurrent(): TCombinedResult {
541
+ if (this.retryExplicitInitializationIfNeeded()) {
542
+ return this.current
543
+ }
544
+
545
+ if (this.explicitInitializationError && !this.placeholderRetryableFailure) {
546
+ throw this.explicitInitializationError
547
+ }
548
+
549
+ if (!this.queryClient && !this.observer && !this.placeholderInitialized) {
550
+ try {
551
+ // Early reads can happen during class-field initialization, before
552
+ // accessors referenced by queries/combine are ready. Retry normally
553
+ // after construction finishes and only surface errors after that point.
554
+ this.setPlaceholderResult()
555
+ } catch (error) {
556
+ if (!this.placeholderRetryableFailure) {
557
+ throw error
558
+ }
559
+ }
560
+ }
561
+
562
+ return this.current
563
+ }
564
+
565
+ private setPlaceholderResult(): void {
566
+ this.result = QueriesController.createPlaceholderResult(this.options)
567
+ this.placeholderInitialized = true
568
+ }
569
+ }
570
+
571
+ /**
572
+ * Creates a Lit reactive controller that subscribes the host to multiple
573
+ * queries.
574
+ *
575
+ * The returned accessor is callable and also exposes `current` and `destroy`.
576
+ * When `options` or `options.queries` is a function, it is re-read during host
577
+ * updates so the query list can follow reactive host state.
578
+ *
579
+ * If `queryClient` is omitted, the controller resolves the client from the
580
+ * nearest connected `QueryClientProvider`.
581
+ *
582
+ * @param host - The Lit reactive controller host that owns the queries
583
+ * subscription.
584
+ * @param options - Queries controller options, or a getter that returns options.
585
+ * @param queryClient - Optional explicit query client. Provide this for
586
+ * controllers that should not resolve a client from Lit context.
587
+ * @returns An accessor for the latest query results, or the value returned by
588
+ * `combine`.
589
+ *
590
+ * @example
591
+ * ```ts
592
+ * import { LitElement, html } from 'lit'
593
+ * import { createQueriesController } from '@tanstack/lit-query'
594
+ *
595
+ * class DashboardView extends LitElement {
596
+ * private readonly dashboard = createQueriesController(this, {
597
+ * queries: [
598
+ * { queryKey: ['stats'], queryFn: fetchStats },
599
+ * { queryKey: ['projects'], queryFn: fetchProjects },
600
+ * ],
601
+ * combine: ([stats, projects]) => ({
602
+ * stats: stats.data,
603
+ * projects: projects.data ?? [],
604
+ * isPending: stats.isPending || projects.isPending,
605
+ * }),
606
+ * })
607
+ *
608
+ * render() {
609
+ * const dashboard = this.dashboard()
610
+ * return html`<p>Projects: ${dashboard.projects.length}</p>`
611
+ * }
612
+ * }
613
+ * ```
614
+ */
615
+ export function createQueriesController<
616
+ TQueryOptions extends Array<any>,
617
+ TCombinedResult = CreateQueriesResults<TQueryOptions>,
618
+ >(
619
+ host: ReactiveControllerHost,
620
+ options: Accessor<
621
+ CreateQueriesControllerOptions<TQueryOptions, TCombinedResult>
622
+ >,
623
+ queryClient?: QueryClient,
624
+ ): QueriesResultAccessor<TCombinedResult> {
625
+ const controller = new QueriesController(host, options, queryClient)
626
+
627
+ return Object.assign(
628
+ createValueAccessor(() => controller.readCurrent()),
629
+ {
630
+ destroy: () => controller.destroy(),
631
+ },
632
+ )
633
+ }