@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.
- package/LICENSE +21 -0
- package/README.md +125 -43
- package/dist/QueryClientProvider.d.ts +85 -0
- package/dist/QueryClientProvider.d.ts.map +1 -0
- package/dist/QueryClientProvider.js +137 -0
- package/dist/QueryClientProvider.js.map +1 -0
- package/dist/accessor.d.ts +32 -0
- package/dist/accessor.d.ts.map +1 -0
- package/dist/accessor.js +12 -0
- package/dist/accessor.js.map +1 -0
- package/dist/context.d.ts +59 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +100 -0
- package/dist/context.js.map +1 -0
- package/dist/controllers/BaseController.d.ts +38 -0
- package/dist/controllers/BaseController.d.ts.map +1 -0
- package/dist/controllers/BaseController.js +204 -0
- package/dist/controllers/BaseController.js.map +1 -0
- package/dist/createInfiniteQueryController.d.ts +76 -0
- package/dist/createInfiniteQueryController.d.ts.map +1 -0
- package/dist/createInfiniteQueryController.js +221 -0
- package/dist/createInfiniteQueryController.js.map +1 -0
- package/dist/createMutationController.d.ts +79 -0
- package/dist/createMutationController.d.ts.map +1 -0
- package/dist/createMutationController.js +193 -0
- package/dist/createMutationController.js.map +1 -0
- package/dist/createQueriesController.d.ts +145 -0
- package/dist/createQueriesController.d.ts.map +1 -0
- package/dist/createQueriesController.js +302 -0
- package/dist/createQueriesController.js.map +1 -0
- package/dist/createQueryController.d.ts +67 -0
- package/dist/createQueryController.d.ts.map +1 -0
- package/dist/createQueryController.js +211 -0
- package/dist/createQueryController.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/infiniteQueryOptions.d.ts +21 -0
- package/dist/infiniteQueryOptions.d.ts.map +1 -0
- package/dist/infiniteQueryOptions.js +22 -0
- package/dist/infiniteQueryOptions.js.map +1 -0
- package/dist/mutationOptions.d.ts +19 -0
- package/dist/mutationOptions.d.ts.map +1 -0
- package/dist/mutationOptions.js +20 -0
- package/dist/mutationOptions.js.map +1 -0
- package/dist/queryOptions.d.ts +62 -0
- package/dist/queryOptions.d.ts.map +1 -0
- package/dist/queryOptions.js +4 -0
- package/dist/queryOptions.js.map +1 -0
- package/dist/types.d.ts +31 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/useIsFetching.d.ts +45 -0
- package/dist/useIsFetching.d.ts.map +1 -0
- package/dist/useIsFetching.js +116 -0
- package/dist/useIsFetching.js.map +1 -0
- package/dist/useIsMutating.d.ts +45 -0
- package/dist/useIsMutating.d.ts.map +1 -0
- package/dist/useIsMutating.js +116 -0
- package/dist/useIsMutating.js.map +1 -0
- package/dist/useMutationState.d.ts +57 -0
- package/dist/useMutationState.d.ts.map +1 -0
- package/dist/useMutationState.js +132 -0
- package/dist/useMutationState.js.map +1 -0
- package/dist-cjs/QueryClientProvider.d.cts +84 -0
- package/dist-cjs/QueryClientProvider.js +141 -0
- package/dist-cjs/QueryClientProvider.js.map +1 -0
- package/dist-cjs/accessor.d.cts +31 -0
- package/dist-cjs/accessor.js +16 -0
- package/dist-cjs/accessor.js.map +1 -0
- package/dist-cjs/context.d.cts +58 -0
- package/dist-cjs/context.js +109 -0
- package/dist-cjs/context.js.map +1 -0
- package/dist-cjs/controllers/BaseController.d.cts +37 -0
- package/dist-cjs/controllers/BaseController.js +208 -0
- package/dist-cjs/controllers/BaseController.js.map +1 -0
- package/dist-cjs/createInfiniteQueryController.d.cts +75 -0
- package/dist-cjs/createInfiniteQueryController.js +224 -0
- package/dist-cjs/createInfiniteQueryController.js.map +1 -0
- package/dist-cjs/createMutationController.d.cts +78 -0
- package/dist-cjs/createMutationController.js +196 -0
- package/dist-cjs/createMutationController.js.map +1 -0
- package/dist-cjs/createQueriesController.d.cts +144 -0
- package/dist-cjs/createQueriesController.js +305 -0
- package/dist-cjs/createQueriesController.js.map +1 -0
- package/dist-cjs/createQueryController.d.cts +66 -0
- package/dist-cjs/createQueryController.js +214 -0
- package/dist-cjs/createQueryController.js.map +1 -0
- package/dist-cjs/index.d.cts +23 -0
- package/dist-cjs/index.js +49 -0
- package/dist-cjs/index.js.map +1 -0
- package/dist-cjs/infiniteQueryOptions.d.cts +20 -0
- package/dist-cjs/infiniteQueryOptions.js +25 -0
- package/dist-cjs/infiniteQueryOptions.js.map +1 -0
- package/dist-cjs/mutationOptions.d.cts +18 -0
- package/dist-cjs/mutationOptions.js +23 -0
- package/dist-cjs/mutationOptions.js.map +1 -0
- package/dist-cjs/package.json +3 -0
- package/dist-cjs/queryOptions.d.cts +61 -0
- package/dist-cjs/queryOptions.js +7 -0
- package/dist-cjs/queryOptions.js.map +1 -0
- package/dist-cjs/types.d.cts +30 -0
- package/dist-cjs/types.js +3 -0
- package/dist-cjs/types.js.map +1 -0
- package/dist-cjs/useIsFetching.d.cts +44 -0
- package/dist-cjs/useIsFetching.js +119 -0
- package/dist-cjs/useIsFetching.js.map +1 -0
- package/dist-cjs/useIsMutating.d.cts +44 -0
- package/dist-cjs/useIsMutating.js +119 -0
- package/dist-cjs/useIsMutating.js.map +1 -0
- package/dist-cjs/useMutationState.d.cts +56 -0
- package/dist-cjs/useMutationState.js +135 -0
- package/dist-cjs/useMutationState.js.map +1 -0
- package/package.json +73 -8
- package/src/QueryClientProvider.ts +174 -0
- package/src/accessor.ts +43 -0
- package/src/context.ts +120 -0
- package/src/controllers/BaseController.ts +284 -0
- package/src/createInfiniteQueryController.ts +394 -0
- package/src/createMutationController.ts +361 -0
- package/src/createQueriesController.ts +633 -0
- package/src/createQueryController.ts +342 -0
- package/src/index.ts +72 -0
- package/src/infiniteQueryOptions.ts +48 -0
- package/src/mutationOptions.ts +31 -0
- package/src/queryOptions.ts +143 -0
- package/src/types.ts +77 -0
- package/src/useIsFetching.ts +159 -0
- package/src/useIsMutating.ts +159 -0
- package/src/useMutationState.ts +201 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { ContextProvider } from '@lit/context'
|
|
2
|
+
import type { QueryClient } from '@tanstack/query-core'
|
|
3
|
+
import type { TemplateResult } from 'lit'
|
|
4
|
+
import { LitElement, html } from 'lit'
|
|
5
|
+
import {
|
|
6
|
+
createMissingQueryClientError,
|
|
7
|
+
queryClientContext,
|
|
8
|
+
registerDefaultQueryClient,
|
|
9
|
+
unregisterDefaultQueryClient,
|
|
10
|
+
} from './context.js'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Lit element that provides a `QueryClient` to descendant Lit Query
|
|
14
|
+
* controllers through Lit context.
|
|
15
|
+
*
|
|
16
|
+
* The `client` is a property, not an attribute. When rendering this element in
|
|
17
|
+
* a Lit template, bind it with property binding: `.client=${queryClient}`.
|
|
18
|
+
* The provider throws if it connects without a client, or if an already
|
|
19
|
+
* connected provider has its client cleared.
|
|
20
|
+
*
|
|
21
|
+
* This class is not registered as a custom element by the package. Applications
|
|
22
|
+
* must register either a subclass or the class itself with
|
|
23
|
+
* `customElements.define`.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* import { html, LitElement } from 'lit'
|
|
28
|
+
* import { QueryClient, QueryClientProvider } from '@tanstack/lit-query'
|
|
29
|
+
*
|
|
30
|
+
* const queryClient = new QueryClient()
|
|
31
|
+
*
|
|
32
|
+
* class AppQueryProvider extends QueryClientProvider {
|
|
33
|
+
* constructor() {
|
|
34
|
+
* super()
|
|
35
|
+
* this.client = queryClient
|
|
36
|
+
* }
|
|
37
|
+
* }
|
|
38
|
+
*
|
|
39
|
+
* customElements.define('app-query-provider', AppQueryProvider)
|
|
40
|
+
*
|
|
41
|
+
* class AppRoot extends LitElement {
|
|
42
|
+
* render() {
|
|
43
|
+
* return html`<app-query-provider><todos-view></todos-view></app-query-provider>`
|
|
44
|
+
* }
|
|
45
|
+
* }
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* import { html } from 'lit'
|
|
51
|
+
* import { QueryClient, QueryClientProvider } from '@tanstack/lit-query'
|
|
52
|
+
*
|
|
53
|
+
* const queryClient = new QueryClient()
|
|
54
|
+
*
|
|
55
|
+
* customElements.define('query-client-provider', QueryClientProvider)
|
|
56
|
+
*
|
|
57
|
+
* const view = html`
|
|
58
|
+
* <query-client-provider .client=${queryClient}>
|
|
59
|
+
* <todos-view></todos-view>
|
|
60
|
+
* </query-client-provider>
|
|
61
|
+
* `
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export class QueryClientProvider extends LitElement {
|
|
65
|
+
/** @internal */
|
|
66
|
+
static properties = {
|
|
67
|
+
client: { attribute: false },
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The `QueryClient` provided to descendant controllers and global fallback
|
|
72
|
+
* helpers while this provider is connected.
|
|
73
|
+
*
|
|
74
|
+
* Bind this as a property in Lit templates with `.client=${queryClient}`.
|
|
75
|
+
*/
|
|
76
|
+
declare client: QueryClient
|
|
77
|
+
|
|
78
|
+
private readonly contextProvider: ContextProvider<typeof queryClientContext>
|
|
79
|
+
|
|
80
|
+
private mountedClient: QueryClient | undefined
|
|
81
|
+
|
|
82
|
+
constructor() {
|
|
83
|
+
super()
|
|
84
|
+
this.contextProvider = new ContextProvider(this, {
|
|
85
|
+
context: queryClientContext,
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** @internal */
|
|
90
|
+
connectedCallback(): void {
|
|
91
|
+
super.connectedCallback()
|
|
92
|
+
const client = this.requireClient()
|
|
93
|
+
this.contextProvider.setValue(client)
|
|
94
|
+
this.mountClient(client)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** @internal */
|
|
98
|
+
disconnectedCallback(): void {
|
|
99
|
+
this.unmountClient(this.mountedClient)
|
|
100
|
+
super.disconnectedCallback()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** @internal */
|
|
104
|
+
protected willUpdate(changedProperties: Map<PropertyKey, unknown>): void {
|
|
105
|
+
if (!changedProperties.has('client')) {
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const nextClient = this.client
|
|
110
|
+
if (!nextClient) {
|
|
111
|
+
if (this.isConnected) {
|
|
112
|
+
this.unmountClient(this.mountedClient)
|
|
113
|
+
// Sentinel: notify active consumers that the provider is now unbound.
|
|
114
|
+
this.contextProvider.setValue(undefined as unknown as QueryClient)
|
|
115
|
+
throw createMissingQueryClientError()
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const previousClient = changedProperties.get('client') as
|
|
122
|
+
| QueryClient
|
|
123
|
+
| undefined
|
|
124
|
+
if (previousClient && previousClient !== nextClient && this.isConnected) {
|
|
125
|
+
this.unmountClient(previousClient)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this.contextProvider.setValue(nextClient)
|
|
129
|
+
|
|
130
|
+
if (this.isConnected) {
|
|
131
|
+
this.mountClient(nextClient)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** @internal */
|
|
136
|
+
render(): TemplateResult {
|
|
137
|
+
return html`<slot></slot>`
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private mountClient(client: QueryClient): void {
|
|
141
|
+
if (this.mountedClient === client) {
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (this.mountedClient) {
|
|
146
|
+
this.unmountClient(this.mountedClient)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
client.mount()
|
|
150
|
+
registerDefaultQueryClient(client)
|
|
151
|
+
this.mountedClient = client
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private unmountClient(client?: QueryClient): void {
|
|
155
|
+
if (!client) {
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
client.unmount()
|
|
160
|
+
unregisterDefaultQueryClient(client)
|
|
161
|
+
|
|
162
|
+
if (this.mountedClient === client) {
|
|
163
|
+
this.mountedClient = undefined
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private requireClient(): QueryClient {
|
|
168
|
+
if (!this.client) {
|
|
169
|
+
throw createMissingQueryClientError()
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return this.client
|
|
173
|
+
}
|
|
174
|
+
}
|
package/src/accessor.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A value that can be passed directly or read from a zero-argument getter.
|
|
3
|
+
*
|
|
4
|
+
* Lit Query APIs read function accessors during host updates, so the getter can
|
|
5
|
+
* depend on reactive host state.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const staticKey: Accessor<readonly unknown[]> = ['todos']
|
|
10
|
+
* const reactiveKey: Accessor<readonly unknown[]> = () => ['todos', this.userId]
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export type Accessor<T> = T | (() => T)
|
|
14
|
+
|
|
15
|
+
export function readAccessor<T>(value: Accessor<T>): T {
|
|
16
|
+
return typeof value === 'function' ? (value as () => T)() : value
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A callable accessor with a `current` property for reading the latest
|
|
21
|
+
* controller result.
|
|
22
|
+
*
|
|
23
|
+
* Controller creators and cache state helpers return this shape so render code
|
|
24
|
+
* can use either `result()` or `result.current`.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* const query = this.todos()
|
|
29
|
+
* const sameQuery = this.todos.current
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export type ValueAccessor<T> = (() => T) & {
|
|
33
|
+
readonly current: T
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function createValueAccessor<T>(getter: () => T): ValueAccessor<T> {
|
|
37
|
+
const accessor = (() => getter()) as ValueAccessor<T>
|
|
38
|
+
Object.defineProperty(accessor, 'current', {
|
|
39
|
+
get: getter,
|
|
40
|
+
enumerable: true,
|
|
41
|
+
})
|
|
42
|
+
return accessor
|
|
43
|
+
}
|
package/src/context.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { createContext } from '@lit/context'
|
|
2
|
+
import type { QueryClient } from '@tanstack/query-core'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Lit context key used by `QueryClientProvider` and host-bound APIs to share a
|
|
6
|
+
* `QueryClient` through the DOM tree.
|
|
7
|
+
*
|
|
8
|
+
* Most applications use `QueryClientProvider` instead of interacting with this
|
|
9
|
+
* context directly.
|
|
10
|
+
*/
|
|
11
|
+
export const queryClientContext = createContext<QueryClient>(
|
|
12
|
+
Symbol.for('tanstack-query-client'),
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
const missingQueryClientMessage =
|
|
16
|
+
'No QueryClient available. Pass one explicitly or render within QueryClientProvider.'
|
|
17
|
+
const ambiguousQueryClientMessage =
|
|
18
|
+
'Multiple QueryClients are mounted. Pass one explicitly instead of relying on global QueryClient helpers.'
|
|
19
|
+
|
|
20
|
+
const registeredClients = new Map<QueryClient, number>()
|
|
21
|
+
let defaultClient: QueryClient | undefined
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Registers a `QueryClient` as a process-local fallback for APIs that resolve a
|
|
25
|
+
* client without an explicit argument.
|
|
26
|
+
*
|
|
27
|
+
* `QueryClientProvider` calls this automatically while it is connected. Prefer
|
|
28
|
+
* passing an explicit client or rendering under a provider when possible.
|
|
29
|
+
*
|
|
30
|
+
* @param client - The query client to register as the current default.
|
|
31
|
+
*/
|
|
32
|
+
export function registerDefaultQueryClient(client: QueryClient): void {
|
|
33
|
+
registeredClients.set(client, (registeredClients.get(client) ?? 0) + 1)
|
|
34
|
+
defaultClient = client
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Unregisters a client previously registered with
|
|
39
|
+
* `registerDefaultQueryClient`.
|
|
40
|
+
*
|
|
41
|
+
* `QueryClientProvider` calls this automatically when it disconnects.
|
|
42
|
+
*
|
|
43
|
+
* @param client - The query client registration to release.
|
|
44
|
+
*/
|
|
45
|
+
export function unregisterDefaultQueryClient(client: QueryClient): void {
|
|
46
|
+
const count = registeredClients.get(client)
|
|
47
|
+
if (count === undefined) {
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (count > 1) {
|
|
52
|
+
registeredClients.set(client, count - 1)
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
registeredClients.delete(client)
|
|
57
|
+
if (defaultClient !== client) {
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const remaining = [...registeredClients.keys()]
|
|
62
|
+
defaultClient = remaining.at(-1)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Returns the registered default `QueryClient`, if exactly one default client is
|
|
67
|
+
* available.
|
|
68
|
+
*
|
|
69
|
+
* @returns The default query client, or `undefined` when there is no registered
|
|
70
|
+
* client or more than one registered client.
|
|
71
|
+
*/
|
|
72
|
+
export function getDefaultQueryClient(): QueryClient | undefined {
|
|
73
|
+
if (registeredClients.size > 1) {
|
|
74
|
+
return undefined
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return defaultClient
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function createMissingQueryClientError(): Error {
|
|
81
|
+
return new Error(missingQueryClientMessage)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function createAmbiguousQueryClientError(): Error {
|
|
85
|
+
return new Error(ambiguousQueryClientMessage)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Resolves the current default `QueryClient` registered by a connected
|
|
90
|
+
* `QueryClientProvider`.
|
|
91
|
+
*
|
|
92
|
+
* This helper is useful outside a Lit reactive controller when a single
|
|
93
|
+
* provider is mounted. It throws if no client is registered or if multiple
|
|
94
|
+
* clients are mounted and the default would be ambiguous.
|
|
95
|
+
*
|
|
96
|
+
* @returns The single registered query client.
|
|
97
|
+
*/
|
|
98
|
+
export function useQueryClient(): QueryClient {
|
|
99
|
+
const client = getDefaultQueryClient()
|
|
100
|
+
if (client) {
|
|
101
|
+
return client
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (registeredClients.size > 1) {
|
|
105
|
+
throw createAmbiguousQueryClientError()
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
throw createMissingQueryClientError()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Resolves an explicit `QueryClient` or falls back to `useQueryClient`.
|
|
113
|
+
*
|
|
114
|
+
* @param explicit - Optional client supplied by the caller.
|
|
115
|
+
* @returns The explicit client when provided, otherwise the current default
|
|
116
|
+
* client.
|
|
117
|
+
*/
|
|
118
|
+
export function resolveQueryClient(explicit?: QueryClient): QueryClient {
|
|
119
|
+
return explicit ?? useQueryClient()
|
|
120
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { ContextEvent } from '@lit/context'
|
|
2
|
+
import type { QueryClient } from '@tanstack/query-core'
|
|
3
|
+
import type { ReactiveController, ReactiveControllerHost } from 'lit'
|
|
4
|
+
import {
|
|
5
|
+
createMissingQueryClientError,
|
|
6
|
+
queryClientContext,
|
|
7
|
+
} from '../context.js'
|
|
8
|
+
|
|
9
|
+
type QueryClientResolutionState =
|
|
10
|
+
| 'pre-connect'
|
|
11
|
+
| 'awaiting-context'
|
|
12
|
+
| 'bound'
|
|
13
|
+
| 'missing'
|
|
14
|
+
|
|
15
|
+
export abstract class BaseController<TResult> implements ReactiveController {
|
|
16
|
+
protected result: TResult
|
|
17
|
+
|
|
18
|
+
private readonly explicitClient?: QueryClient
|
|
19
|
+
private contextClient: QueryClient | undefined
|
|
20
|
+
private contextUnsubscribe: (() => void) | undefined
|
|
21
|
+
|
|
22
|
+
private connected = false
|
|
23
|
+
private destroyed = false
|
|
24
|
+
private updateQueued = false
|
|
25
|
+
private clientChangeQueued = false
|
|
26
|
+
private connectionAttempt = 0
|
|
27
|
+
private queryClientResolutionState: QueryClientResolutionState
|
|
28
|
+
|
|
29
|
+
protected constructor(
|
|
30
|
+
protected readonly host: ReactiveControllerHost,
|
|
31
|
+
initialResult: TResult,
|
|
32
|
+
queryClient?: QueryClient,
|
|
33
|
+
) {
|
|
34
|
+
this.explicitClient = queryClient
|
|
35
|
+
this.result = initialResult
|
|
36
|
+
this.queryClientResolutionState = queryClient ? 'bound' : 'pre-connect'
|
|
37
|
+
|
|
38
|
+
host.addController(this)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
hostConnected(): void {
|
|
42
|
+
if (this.connected || this.destroyed) {
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
this.connected = true
|
|
47
|
+
let contextResolutionAttempt: number | undefined
|
|
48
|
+
|
|
49
|
+
if (this.explicitClient) {
|
|
50
|
+
this.queryClientResolutionState = 'bound'
|
|
51
|
+
} else {
|
|
52
|
+
contextResolutionAttempt = ++this.connectionAttempt
|
|
53
|
+
this.beginContextResolution()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Defer onConnected to ensure subclass constructors complete before
|
|
57
|
+
// lifecycle callbacks access subclass state. This handles the case where
|
|
58
|
+
// addController is called on an already-connected host (e.g., during
|
|
59
|
+
// willUpdate), which synchronously triggers hostConnected before
|
|
60
|
+
// subclass field initialization.
|
|
61
|
+
queueMicrotask(() => {
|
|
62
|
+
if (this.connected && !this.destroyed) {
|
|
63
|
+
this.onConnected()
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
if (contextResolutionAttempt !== undefined) {
|
|
68
|
+
// Provider-backed controllers on already-connected hosts should finish
|
|
69
|
+
// their deferred onConnected pass before a context client binds.
|
|
70
|
+
this.queueContextResolution(contextResolutionAttempt)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
hostDisconnected(): void {
|
|
75
|
+
if (!this.connected) {
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
this.connected = false
|
|
80
|
+
|
|
81
|
+
if (!this.explicitClient) {
|
|
82
|
+
this.connectionAttempt += 1
|
|
83
|
+
this.clearContextClient()
|
|
84
|
+
this.updateQueryClientResolutionState('pre-connect')
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.onDisconnected()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
hostUpdate(): void {
|
|
91
|
+
if (this.destroyed) {
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
this.onHostUpdate()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
destroy(): void {
|
|
99
|
+
if (this.destroyed) {
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
this.destroyed = true
|
|
104
|
+
this.connected = false
|
|
105
|
+
this.connectionAttempt += 1
|
|
106
|
+
this.clearContextClient()
|
|
107
|
+
this.queryClientResolutionState = this.explicitClient
|
|
108
|
+
? 'bound'
|
|
109
|
+
: 'pre-connect'
|
|
110
|
+
this.onDisconnected()
|
|
111
|
+
|
|
112
|
+
if ('removeController' in this.host) {
|
|
113
|
+
this.host.removeController(this)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
protected tryGetQueryClient(): QueryClient | undefined {
|
|
118
|
+
return this.explicitClient ?? this.contextClient
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
protected getQueryClient(): QueryClient {
|
|
122
|
+
const client = this.tryGetQueryClient()
|
|
123
|
+
if (!client) {
|
|
124
|
+
throw createMissingQueryClientError()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return client
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
protected setResult(next: TResult): void {
|
|
131
|
+
if (Object.is(this.result, next)) {
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
this.result = next
|
|
136
|
+
this.queueUpdate()
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
get current(): TResult {
|
|
140
|
+
if (this.queryClientResolutionState === 'missing') {
|
|
141
|
+
throw createMissingQueryClientError()
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return this.result
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
protected get connectedState(): boolean {
|
|
148
|
+
return this.connected
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
protected queueUpdate(): void {
|
|
152
|
+
if (this.updateQueued) {
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
this.updateQueued = true
|
|
157
|
+
queueMicrotask(() => {
|
|
158
|
+
this.updateQueued = false
|
|
159
|
+
if (!this.destroyed) {
|
|
160
|
+
this.host.requestUpdate()
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private queueQueryClientChanged(): void {
|
|
166
|
+
if (this.clientChangeQueued) {
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
this.clientChangeQueued = true
|
|
171
|
+
queueMicrotask(() => {
|
|
172
|
+
this.clientChangeQueued = false
|
|
173
|
+
if (!this.destroyed) {
|
|
174
|
+
this.onQueryClientChanged()
|
|
175
|
+
}
|
|
176
|
+
})
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private beginContextResolution(): void {
|
|
180
|
+
this.clearContextClient()
|
|
181
|
+
this.updateQueryClientResolutionState('awaiting-context')
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private queueContextResolution(attempt: number): void {
|
|
185
|
+
queueMicrotask(() => {
|
|
186
|
+
if (
|
|
187
|
+
this.destroyed ||
|
|
188
|
+
!this.connected ||
|
|
189
|
+
attempt !== this.connectionAttempt ||
|
|
190
|
+
this.queryClientResolutionState !== 'awaiting-context'
|
|
191
|
+
) {
|
|
192
|
+
return
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
this.dispatchContextRequest(attempt)
|
|
196
|
+
this.queueInitialContextResolutionCompletion(attempt)
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private dispatchContextRequest(attempt: number): void {
|
|
201
|
+
if (!('dispatchEvent' in this.host)) {
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const contextTarget = this.host as ReactiveControllerHost & EventTarget
|
|
206
|
+
contextTarget.dispatchEvent(
|
|
207
|
+
new ContextEvent(
|
|
208
|
+
queryClientContext,
|
|
209
|
+
contextTarget as unknown as Element,
|
|
210
|
+
(value, unsubscribe) => {
|
|
211
|
+
if (
|
|
212
|
+
this.destroyed ||
|
|
213
|
+
!this.connected ||
|
|
214
|
+
attempt !== this.connectionAttempt
|
|
215
|
+
) {
|
|
216
|
+
unsubscribe?.()
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (
|
|
221
|
+
this.contextUnsubscribe &&
|
|
222
|
+
this.contextUnsubscribe !== unsubscribe
|
|
223
|
+
) {
|
|
224
|
+
this.contextUnsubscribe()
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const resolutionChanged = this.updateQueryClientResolutionState(
|
|
228
|
+
value === undefined ? 'missing' : 'bound',
|
|
229
|
+
)
|
|
230
|
+
const clientChanged = this.contextClient !== value
|
|
231
|
+
|
|
232
|
+
this.contextClient = value
|
|
233
|
+
this.contextUnsubscribe = unsubscribe
|
|
234
|
+
|
|
235
|
+
if (resolutionChanged || clientChanged) {
|
|
236
|
+
this.queueUpdate()
|
|
237
|
+
this.queueQueryClientChanged()
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
true,
|
|
241
|
+
),
|
|
242
|
+
)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private queueInitialContextResolutionCompletion(attempt: number): void {
|
|
246
|
+
queueMicrotask(() => {
|
|
247
|
+
if (
|
|
248
|
+
this.destroyed ||
|
|
249
|
+
!this.connected ||
|
|
250
|
+
attempt !== this.connectionAttempt ||
|
|
251
|
+
this.queryClientResolutionState !== 'awaiting-context'
|
|
252
|
+
) {
|
|
253
|
+
return
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (this.updateQueryClientResolutionState('missing')) {
|
|
257
|
+
this.queueUpdate()
|
|
258
|
+
this.queueQueryClientChanged()
|
|
259
|
+
}
|
|
260
|
+
})
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
private clearContextClient(): void {
|
|
264
|
+
this.contextUnsubscribe?.()
|
|
265
|
+
this.contextUnsubscribe = undefined
|
|
266
|
+
this.contextClient = undefined
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private updateQueryClientResolutionState(
|
|
270
|
+
nextState: QueryClientResolutionState,
|
|
271
|
+
): boolean {
|
|
272
|
+
if (this.queryClientResolutionState === nextState) {
|
|
273
|
+
return false
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
this.queryClientResolutionState = nextState
|
|
277
|
+
return true
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
protected abstract onConnected(): void
|
|
281
|
+
protected abstract onDisconnected(): void
|
|
282
|
+
protected abstract onHostUpdate(): void
|
|
283
|
+
protected abstract onQueryClientChanged(): void
|
|
284
|
+
}
|