@effector-tanstack-query/core 0.1.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/README.md +40 -0
- package/dist/createBaseQuery.cjs +208 -0
- package/dist/createBaseQuery.cjs.map +1 -0
- package/dist/createBaseQuery.d.cts +114 -0
- package/dist/createBaseQuery.d.ts +114 -0
- package/dist/createBaseQuery.js +204 -0
- package/dist/createBaseQuery.js.map +1 -0
- package/dist/createInfiniteQuery.cjs +193 -0
- package/dist/createInfiniteQuery.cjs.map +1 -0
- package/dist/createInfiniteQuery.d.cts +8 -0
- package/dist/createInfiniteQuery.d.ts +8 -0
- package/dist/createInfiniteQuery.js +191 -0
- package/dist/createInfiniteQuery.js.map +1 -0
- package/dist/createInvalidate.cjs +37 -0
- package/dist/createInvalidate.cjs.map +1 -0
- package/dist/createInvalidate.d.cts +50 -0
- package/dist/createInvalidate.d.ts +50 -0
- package/dist/createInvalidate.js +35 -0
- package/dist/createInvalidate.js.map +1 -0
- package/dist/createMutation.cjs +177 -0
- package/dist/createMutation.cjs.map +1 -0
- package/dist/createMutation.d.cts +7 -0
- package/dist/createMutation.d.ts +7 -0
- package/dist/createMutation.js +175 -0
- package/dist/createMutation.js.map +1 -0
- package/dist/createQuery.cjs +98 -0
- package/dist/createQuery.cjs.map +1 -0
- package/dist/createQuery.d.cts +8 -0
- package/dist/createQuery.d.ts +8 -0
- package/dist/createQuery.js +96 -0
- package/dist/createQuery.js.map +1 -0
- package/dist/index.cjs +36 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/queryClient.cjs +20 -0
- package/dist/queryClient.cjs.map +1 -0
- package/dist/queryClient.d.cts +15 -0
- package/dist/queryClient.d.ts +15 -0
- package/dist/queryClient.js +17 -0
- package/dist/queryClient.js.map +1 -0
- package/dist/resolve.cjs +37 -0
- package/dist/resolve.cjs.map +1 -0
- package/dist/resolve.d.cts +17 -0
- package/dist/resolve.d.ts +17 -0
- package/dist/resolve.js +33 -0
- package/dist/resolve.js.map +1 -0
- package/dist/types.cjs +4 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.cts +209 -0
- package/dist/types.d.ts +209 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +60 -0
- package/src/createBaseQuery.ts +428 -0
- package/src/createInfiniteQuery.ts +291 -0
- package/src/createInvalidate.ts +104 -0
- package/src/createMutation.ts +271 -0
- package/src/createQuery.ts +155 -0
- package/src/index.ts +17 -0
- package/src/queryClient.ts +23 -0
- package/src/resolve.ts +50 -0
- package/src/types.ts +270 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
import {
|
|
2
|
+
attach,
|
|
3
|
+
combine,
|
|
4
|
+
createEvent,
|
|
5
|
+
createStore,
|
|
6
|
+
sample,
|
|
7
|
+
scopeBind,
|
|
8
|
+
} from 'effector'
|
|
9
|
+
import type { EventCallable, Store } from 'effector'
|
|
10
|
+
import type {
|
|
11
|
+
FetchStatus,
|
|
12
|
+
QueryClient,
|
|
13
|
+
QueryKey,
|
|
14
|
+
QueryStatus,
|
|
15
|
+
} from '@tanstack/query-core'
|
|
16
|
+
import { $queryClient } from './queryClient'
|
|
17
|
+
import { resolveEnabled, resolveKey } from './resolve'
|
|
18
|
+
import type { EffectorQueryKey, StoreOrValue } from './types'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The minimal shape of an observer that createBaseQuery knows how to drive.
|
|
22
|
+
* Both QueryObserver and InfiniteQueryObserver satisfy this.
|
|
23
|
+
*/
|
|
24
|
+
export interface BaseObserverLike<TResult> {
|
|
25
|
+
options: { queryKey: QueryKey; _defaulted?: boolean; queryHash?: string }
|
|
26
|
+
setOptions(options: any): void
|
|
27
|
+
subscribe(listener: (result: TResult) => void): () => void
|
|
28
|
+
getCurrentResult(): TResult
|
|
29
|
+
destroy(): void
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The subset of observer result fields that createBaseQuery wires up
|
|
34
|
+
* into stores common to all query flavors.
|
|
35
|
+
*/
|
|
36
|
+
export interface BaseObserverResult<TData, TError> {
|
|
37
|
+
data: TData | undefined
|
|
38
|
+
error: TError | null
|
|
39
|
+
status: QueryStatus
|
|
40
|
+
isFetching: boolean
|
|
41
|
+
fetchStatus: FetchStatus
|
|
42
|
+
isPlaceholderData: boolean
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface BaseQueryStores<TData, TError, TObserver> {
|
|
46
|
+
$data: Store<TData | undefined>
|
|
47
|
+
$error: Store<TError | null>
|
|
48
|
+
$status: Store<QueryStatus>
|
|
49
|
+
$isPending: Store<boolean>
|
|
50
|
+
$isFetching: Store<boolean>
|
|
51
|
+
$isSuccess: Store<boolean>
|
|
52
|
+
$isError: Store<boolean>
|
|
53
|
+
$isPlaceholderData: Store<boolean>
|
|
54
|
+
$fetchStatus: Store<FetchStatus>
|
|
55
|
+
/**
|
|
56
|
+
* Per-scope observer. Populated on first `mounted()` via attach over
|
|
57
|
+
* `$queryClient` — every fork scope has its own Observer instance bound
|
|
58
|
+
* to the scope's QueryClient. Read scope-aware via `useUnit($observer)`.
|
|
59
|
+
*/
|
|
60
|
+
$observer: Store<TObserver | null>
|
|
61
|
+
/**
|
|
62
|
+
* Resolved QueryClient store. If the factory was called with an explicit
|
|
63
|
+
* client, this is a frozen store of that client. Otherwise it's the global
|
|
64
|
+
* `$queryClient`, which honors `fork({ values })` overrides.
|
|
65
|
+
*/
|
|
66
|
+
$queryClient: Store<QueryClient | null>
|
|
67
|
+
/** Internal — used by the React suspense hooks. */
|
|
68
|
+
$resolvedKey: Store<QueryKey>
|
|
69
|
+
/** Internal — used by the React suspense hooks. */
|
|
70
|
+
$enabled: Store<boolean>
|
|
71
|
+
refresh: EventCallable<void>
|
|
72
|
+
mounted: EventCallable<void>
|
|
73
|
+
unmounted: EventCallable<void>
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface BaseQueryOptions {
|
|
77
|
+
queryKey: EffectorQueryKey
|
|
78
|
+
enabled?: StoreOrValue<boolean>
|
|
79
|
+
/**
|
|
80
|
+
* Pre-resolved reactive `refetchInterval`. The per-flavor factory extracts
|
|
81
|
+
* the original option, and — if it's a Store — passes the Store here while
|
|
82
|
+
* stripping the value from the observer constructor options. Static values
|
|
83
|
+
* and function forms continue to flow through `restOptions` to the
|
|
84
|
+
* observer.
|
|
85
|
+
*/
|
|
86
|
+
reactiveRefetchInterval?: Store<number | false | undefined>
|
|
87
|
+
name?: string
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const SID_PREFIX = '@tanstack/query-effector'
|
|
91
|
+
|
|
92
|
+
const warnedNames = new Set<string>()
|
|
93
|
+
|
|
94
|
+
export function warnMissingName(role: string): void {
|
|
95
|
+
if (typeof process === 'undefined' || process.env.NODE_ENV === 'production') {
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
if (warnedNames.has(role)) return
|
|
99
|
+
warnedNames.add(role)
|
|
100
|
+
// eslint-disable-next-line no-console
|
|
101
|
+
console.warn(
|
|
102
|
+
`[@tanstack/query-effector] ${role} created without a "name" — internal stores will be excluded from serialize(scope). ` +
|
|
103
|
+
`Pass a unique "name" to enable SSR via fork({ values: serialize(scope) }).`,
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function sidConfig(
|
|
108
|
+
name: string | undefined,
|
|
109
|
+
role: string,
|
|
110
|
+
): { sid: string; name: string } | {} {
|
|
111
|
+
if (!name) return {}
|
|
112
|
+
return {
|
|
113
|
+
sid: `${SID_PREFIX}.${name}.${role}`,
|
|
114
|
+
name: `${name}.${role}`,
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface ExtrasSetup<TResult, TObserver, TExtraStores> {
|
|
119
|
+
/** Extra stores/events merged into the final result object. */
|
|
120
|
+
stores: TExtraStores
|
|
121
|
+
/**
|
|
122
|
+
* Invoked inside the mount effect. Must scope-bind any extra events
|
|
123
|
+
* and return a function that dispatches extra fields from the observer
|
|
124
|
+
* result. The returned dispatcher is called on every subscription
|
|
125
|
+
* notification alongside the base dispatcher.
|
|
126
|
+
*/
|
|
127
|
+
bindDispatcher: () => (result: TResult) => void
|
|
128
|
+
/**
|
|
129
|
+
* Lets a flavor wire its own per-observer effects (e.g.
|
|
130
|
+
* fetchNextPage / fetchPreviousPage for infinite queries). Receives the
|
|
131
|
+
* per-scope `$observer` store so the flavor can build attach-based
|
|
132
|
+
* effects that resolve the observer from the current scope.
|
|
133
|
+
*/
|
|
134
|
+
setupEffects?: (params: { $observer: Store<TObserver | null> }) => void
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface CreateBaseQueryConfig<
|
|
138
|
+
TData,
|
|
139
|
+
TError,
|
|
140
|
+
TResult extends BaseObserverResult<TData, TError>,
|
|
141
|
+
TObserver extends BaseObserverLike<TResult>,
|
|
142
|
+
TExtraStores,
|
|
143
|
+
> {
|
|
144
|
+
/** Build the observer for the current scope. Receives the resolved client. */
|
|
145
|
+
createObserver: (
|
|
146
|
+
queryClient: QueryClient,
|
|
147
|
+
initial: { queryKey: QueryKey; enabled: boolean },
|
|
148
|
+
) => TObserver
|
|
149
|
+
/**
|
|
150
|
+
* Hook for query flavors that need additional stores/events (e.g. infinite
|
|
151
|
+
* query's hasNextPage, fetchNextPage). Called once at factory time.
|
|
152
|
+
*/
|
|
153
|
+
setupExtras?: () => ExtrasSetup<TResult, TObserver, TExtraStores>
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function createBaseQuery<
|
|
157
|
+
TData,
|
|
158
|
+
TError,
|
|
159
|
+
TResult extends BaseObserverResult<TData, TError>,
|
|
160
|
+
TObserver extends BaseObserverLike<TResult>,
|
|
161
|
+
TExtraStores = {},
|
|
162
|
+
>(
|
|
163
|
+
explicitClient: QueryClient | null,
|
|
164
|
+
options: BaseQueryOptions,
|
|
165
|
+
config: CreateBaseQueryConfig<TData, TError, TResult, TObserver, TExtraStores>,
|
|
166
|
+
): BaseQueryStores<TData, TError, TObserver> & TExtraStores {
|
|
167
|
+
const { name, reactiveRefetchInterval: $reactiveRefetchInterval } = options
|
|
168
|
+
const $resolvedKey = resolveKey(options.queryKey)
|
|
169
|
+
const $enabled = resolveEnabled(options.enabled)
|
|
170
|
+
|
|
171
|
+
// If an explicit client is passed, the factory is locked to it. fork()
|
|
172
|
+
// values cannot override the captured value because $effectiveClient is a
|
|
173
|
+
// brand-new store (not the global one). When no explicit client is passed,
|
|
174
|
+
// we route through $queryClient — which respects fork({ values }) for
|
|
175
|
+
// per-scope isolation.
|
|
176
|
+
const $effectiveClient: Store<QueryClient | null> = explicitClient
|
|
177
|
+
? createStore(explicitClient as QueryClient | null, {
|
|
178
|
+
serialize: 'ignore',
|
|
179
|
+
})
|
|
180
|
+
: $queryClient
|
|
181
|
+
|
|
182
|
+
const dataUpdated = createEvent<TData | undefined>()
|
|
183
|
+
const errorUpdated = createEvent<TError | null>()
|
|
184
|
+
const statusUpdated = createEvent<QueryStatus>()
|
|
185
|
+
const isFetchingUpdated = createEvent<boolean>()
|
|
186
|
+
const fetchStatusUpdated = createEvent<FetchStatus>()
|
|
187
|
+
const isPlaceholderDataUpdated = createEvent<boolean>()
|
|
188
|
+
|
|
189
|
+
const $data = createStore<TData | undefined>(undefined, {
|
|
190
|
+
skipVoid: false,
|
|
191
|
+
...sidConfig(name, '$data'),
|
|
192
|
+
}).on(dataUpdated, (_, v) => v)
|
|
193
|
+
const $error = createStore<TError | null>(null, {
|
|
194
|
+
skipVoid: false,
|
|
195
|
+
...sidConfig(name, '$error'),
|
|
196
|
+
}).on(errorUpdated, (_, v) => v)
|
|
197
|
+
const $status = createStore<QueryStatus>('pending', {
|
|
198
|
+
...sidConfig(name, '$status'),
|
|
199
|
+
}).on(statusUpdated, (_, v) => v)
|
|
200
|
+
const $isFetching = createStore(false, {
|
|
201
|
+
...sidConfig(name, '$isFetching'),
|
|
202
|
+
}).on(isFetchingUpdated, (_, v) => v)
|
|
203
|
+
const $fetchStatus = createStore<FetchStatus>('idle', {
|
|
204
|
+
...sidConfig(name, '$fetchStatus'),
|
|
205
|
+
}).on(fetchStatusUpdated, (_, v) => v)
|
|
206
|
+
const $isPlaceholderData = createStore(false, {
|
|
207
|
+
...sidConfig(name, '$isPlaceholderData'),
|
|
208
|
+
}).on(isPlaceholderDataUpdated, (_, v) => v)
|
|
209
|
+
|
|
210
|
+
// Derived stores via .map don't accept sid in their config — effector's
|
|
211
|
+
// serialize() captures source-store values, and derived stores recompute
|
|
212
|
+
// automatically on the client after fork({ values }).
|
|
213
|
+
const $isPending = $status.map((s) => s === 'pending')
|
|
214
|
+
const $isSuccess = $status.map((s) => s === 'success')
|
|
215
|
+
const $isError = $status.map((s) => s === 'error')
|
|
216
|
+
|
|
217
|
+
// Per-scope observer storage. Carries runtime-only references
|
|
218
|
+
// (subscriptions, callbacks) — must never participate in serialization.
|
|
219
|
+
const $observer = createStore<TObserver | null>(null, {
|
|
220
|
+
serialize: 'ignore',
|
|
221
|
+
})
|
|
222
|
+
const observerCreated = createEvent<TObserver>()
|
|
223
|
+
$observer.on(observerCreated, (_, obs) => obs)
|
|
224
|
+
|
|
225
|
+
// Per-observer unsubscribe handles. WeakMap so abandoned observers (e.g.
|
|
226
|
+
// a scope that was discarded without unmount) are GC'able.
|
|
227
|
+
const observerSubscriptions = new WeakMap<TObserver, () => void>()
|
|
228
|
+
|
|
229
|
+
const extras = config.setupExtras?.()
|
|
230
|
+
extras?.setupEffects?.({ $observer })
|
|
231
|
+
|
|
232
|
+
// Runs once per mount. Creates the observer for the current scope (if not
|
|
233
|
+
// yet created) and attaches the subscription. scopeBind({ safe: true })
|
|
234
|
+
// reliably captures the fork scope here because this effect is triggered
|
|
235
|
+
// directly from allSettled(mounted). Bound dispatchers are captured in the
|
|
236
|
+
// observer callback's closure and reused for all subsequent notifications
|
|
237
|
+
// (including after key/enabled changes).
|
|
238
|
+
const mountFx = attach({
|
|
239
|
+
source: { qc: $effectiveClient, observer: $observer },
|
|
240
|
+
effect: (
|
|
241
|
+
{ qc, observer: existingObserver },
|
|
242
|
+
{
|
|
243
|
+
key,
|
|
244
|
+
enabled,
|
|
245
|
+
refetchInterval,
|
|
246
|
+
}: {
|
|
247
|
+
key: QueryKey
|
|
248
|
+
enabled: boolean
|
|
249
|
+
refetchInterval: number | false | undefined
|
|
250
|
+
},
|
|
251
|
+
) => {
|
|
252
|
+
if (!qc) {
|
|
253
|
+
throw new Error(
|
|
254
|
+
'[@tanstack/query-effector] No QueryClient is set. Call setQueryClient(qc) before mounting, ' +
|
|
255
|
+
'pass it to fork({ values: [[$queryClient, qc]] }), or pass it explicitly to the factory.',
|
|
256
|
+
)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const observer =
|
|
260
|
+
existingObserver ??
|
|
261
|
+
config.createObserver(qc, { queryKey: key, enabled })
|
|
262
|
+
|
|
263
|
+
const dispatchData = scopeBind(dataUpdated, { safe: true })
|
|
264
|
+
const dispatchError = scopeBind(errorUpdated, { safe: true })
|
|
265
|
+
const dispatchStatus = scopeBind(statusUpdated, { safe: true })
|
|
266
|
+
const dispatchIsFetching = scopeBind(isFetchingUpdated, { safe: true })
|
|
267
|
+
const dispatchFetchStatus = scopeBind(fetchStatusUpdated, { safe: true })
|
|
268
|
+
const dispatchIsPlaceholderData = scopeBind(isPlaceholderDataUpdated, {
|
|
269
|
+
safe: true,
|
|
270
|
+
})
|
|
271
|
+
const dispatchExtras = extras?.bindDispatcher()
|
|
272
|
+
|
|
273
|
+
observerSubscriptions.get(observer)?.()
|
|
274
|
+
observer.setOptions({
|
|
275
|
+
...observer.options,
|
|
276
|
+
queryKey: key,
|
|
277
|
+
enabled,
|
|
278
|
+
// Only override refetchInterval when the user provided a reactive
|
|
279
|
+
// Store — otherwise the static value (or function) from the observer
|
|
280
|
+
// constructor wins.
|
|
281
|
+
...($reactiveRefetchInterval ? { refetchInterval } : {}),
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
const dispatch = (result: TResult) => {
|
|
285
|
+
dispatchData(result.data)
|
|
286
|
+
dispatchError(result.error)
|
|
287
|
+
dispatchStatus(result.status)
|
|
288
|
+
dispatchIsFetching(result.isFetching)
|
|
289
|
+
dispatchFetchStatus(result.fetchStatus)
|
|
290
|
+
dispatchIsPlaceholderData(result.isPlaceholderData)
|
|
291
|
+
dispatchExtras?.(result)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const unsubscribe = observer.subscribe(dispatch)
|
|
295
|
+
observerSubscriptions.set(observer, unsubscribe)
|
|
296
|
+
|
|
297
|
+
// Emit the current state immediately — observer.subscribe() may not
|
|
298
|
+
// fire the callback synchronously when cached data already matches
|
|
299
|
+
// the observer's initial result (e.g. staleTime + setQueryData).
|
|
300
|
+
// This mirrors react-query's getOptimisticResult() on mount.
|
|
301
|
+
dispatch(observer.getCurrentResult())
|
|
302
|
+
|
|
303
|
+
return observer
|
|
304
|
+
},
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
sample({ clock: mountFx.doneData, target: observerCreated })
|
|
308
|
+
|
|
309
|
+
// Runs when key / enabled / reactive refetchInterval change after mount.
|
|
310
|
+
// Only updates observer options — subscription + dispatchers were already
|
|
311
|
+
// wired in mountFx.
|
|
312
|
+
const updateObserverFx = attach({
|
|
313
|
+
source: $observer,
|
|
314
|
+
effect: (
|
|
315
|
+
observer,
|
|
316
|
+
{
|
|
317
|
+
key,
|
|
318
|
+
enabled,
|
|
319
|
+
refetchInterval,
|
|
320
|
+
}: {
|
|
321
|
+
key: QueryKey
|
|
322
|
+
enabled: boolean
|
|
323
|
+
refetchInterval: number | false | undefined
|
|
324
|
+
},
|
|
325
|
+
) => {
|
|
326
|
+
if (!observer) return
|
|
327
|
+
// Strip _defaulted and queryHash so defaultQueryOptions() recomputes
|
|
328
|
+
// the hash for the new key. Without this, the old hash is preserved and
|
|
329
|
+
// QueryObserver#updateQuery() finds the old query — no key switch, no fetch.
|
|
330
|
+
const {
|
|
331
|
+
_defaulted: _d,
|
|
332
|
+
queryHash: _h,
|
|
333
|
+
...baseOptions
|
|
334
|
+
} = observer.options as typeof observer.options & {
|
|
335
|
+
_defaulted?: boolean
|
|
336
|
+
queryHash?: string
|
|
337
|
+
}
|
|
338
|
+
observer.setOptions({
|
|
339
|
+
...baseOptions,
|
|
340
|
+
queryKey: key,
|
|
341
|
+
enabled,
|
|
342
|
+
...($reactiveRefetchInterval ? { refetchInterval } : {}),
|
|
343
|
+
})
|
|
344
|
+
},
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
const mounted = createEvent<void>()
|
|
348
|
+
const unmounted = createEvent<void>()
|
|
349
|
+
const $isMounted = createStore(false, {
|
|
350
|
+
...sidConfig(name, '$isMounted'),
|
|
351
|
+
})
|
|
352
|
+
.on(mounted, () => true)
|
|
353
|
+
.on(unmounted, () => false)
|
|
354
|
+
|
|
355
|
+
// Combine of all reactive options that drive observer.setOptions. Built once
|
|
356
|
+
// so mountFx and updateObserverFx see the same shape. When the user didn't
|
|
357
|
+
// pass a reactive `refetchInterval`, we fall back to a static-`false` store
|
|
358
|
+
// (its emitted value is never read — the spread is guarded by the original
|
|
359
|
+
// `$reactiveRefetchInterval` reference).
|
|
360
|
+
const $observerOptions = combine({
|
|
361
|
+
key: $resolvedKey,
|
|
362
|
+
enabled: $enabled,
|
|
363
|
+
refetchInterval:
|
|
364
|
+
$reactiveRefetchInterval ??
|
|
365
|
+
createStore<number | false | undefined>(false),
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
sample({
|
|
369
|
+
clock: mounted,
|
|
370
|
+
source: $observerOptions,
|
|
371
|
+
target: mountFx,
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
sample({
|
|
375
|
+
clock: $observerOptions,
|
|
376
|
+
source: $observerOptions,
|
|
377
|
+
filter: $isMounted,
|
|
378
|
+
target: updateObserverFx,
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
// Single effect: tear down subscription + destroy + clear $observer.
|
|
382
|
+
// Doing all three in one effect avoids ordering ambiguity vs. separate
|
|
383
|
+
// events that all sample from `unmounted`.
|
|
384
|
+
const observerDestroyed = createEvent<void>()
|
|
385
|
+
$observer.on(observerDestroyed, () => null)
|
|
386
|
+
|
|
387
|
+
const unmountFx = attach({
|
|
388
|
+
source: $observer,
|
|
389
|
+
effect: (observer) => {
|
|
390
|
+
if (!observer) return
|
|
391
|
+
observerSubscriptions.get(observer)?.()
|
|
392
|
+
observerSubscriptions.delete(observer)
|
|
393
|
+
observer.destroy()
|
|
394
|
+
},
|
|
395
|
+
})
|
|
396
|
+
sample({ clock: unmounted, target: unmountFx })
|
|
397
|
+
sample({ clock: unmountFx.finally, target: observerDestroyed })
|
|
398
|
+
|
|
399
|
+
const refresh = createEvent<void>()
|
|
400
|
+
const refreshFx = attach({
|
|
401
|
+
source: { qc: $effectiveClient, key: $resolvedKey },
|
|
402
|
+
effect: ({ qc, key }) => {
|
|
403
|
+
if (!qc) return
|
|
404
|
+
return qc.invalidateQueries({ queryKey: key })
|
|
405
|
+
},
|
|
406
|
+
})
|
|
407
|
+
sample({ clock: refresh, target: refreshFx })
|
|
408
|
+
|
|
409
|
+
return {
|
|
410
|
+
$data,
|
|
411
|
+
$error,
|
|
412
|
+
$status,
|
|
413
|
+
$isPending,
|
|
414
|
+
$isFetching,
|
|
415
|
+
$isSuccess,
|
|
416
|
+
$isError,
|
|
417
|
+
$isPlaceholderData,
|
|
418
|
+
$fetchStatus,
|
|
419
|
+
$observer,
|
|
420
|
+
$queryClient: $effectiveClient,
|
|
421
|
+
$resolvedKey,
|
|
422
|
+
$enabled,
|
|
423
|
+
refresh,
|
|
424
|
+
mounted,
|
|
425
|
+
unmounted,
|
|
426
|
+
...(extras?.stores ?? ({} as TExtraStores)),
|
|
427
|
+
}
|
|
428
|
+
}
|