@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.
Files changed (65) hide show
  1. package/README.md +40 -0
  2. package/dist/createBaseQuery.cjs +208 -0
  3. package/dist/createBaseQuery.cjs.map +1 -0
  4. package/dist/createBaseQuery.d.cts +114 -0
  5. package/dist/createBaseQuery.d.ts +114 -0
  6. package/dist/createBaseQuery.js +204 -0
  7. package/dist/createBaseQuery.js.map +1 -0
  8. package/dist/createInfiniteQuery.cjs +193 -0
  9. package/dist/createInfiniteQuery.cjs.map +1 -0
  10. package/dist/createInfiniteQuery.d.cts +8 -0
  11. package/dist/createInfiniteQuery.d.ts +8 -0
  12. package/dist/createInfiniteQuery.js +191 -0
  13. package/dist/createInfiniteQuery.js.map +1 -0
  14. package/dist/createInvalidate.cjs +37 -0
  15. package/dist/createInvalidate.cjs.map +1 -0
  16. package/dist/createInvalidate.d.cts +50 -0
  17. package/dist/createInvalidate.d.ts +50 -0
  18. package/dist/createInvalidate.js +35 -0
  19. package/dist/createInvalidate.js.map +1 -0
  20. package/dist/createMutation.cjs +177 -0
  21. package/dist/createMutation.cjs.map +1 -0
  22. package/dist/createMutation.d.cts +7 -0
  23. package/dist/createMutation.d.ts +7 -0
  24. package/dist/createMutation.js +175 -0
  25. package/dist/createMutation.js.map +1 -0
  26. package/dist/createQuery.cjs +98 -0
  27. package/dist/createQuery.cjs.map +1 -0
  28. package/dist/createQuery.d.cts +8 -0
  29. package/dist/createQuery.d.ts +8 -0
  30. package/dist/createQuery.js +96 -0
  31. package/dist/createQuery.js.map +1 -0
  32. package/dist/index.cjs +36 -0
  33. package/dist/index.cjs.map +1 -0
  34. package/dist/index.d.cts +8 -0
  35. package/dist/index.d.ts +8 -0
  36. package/dist/index.js +7 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/queryClient.cjs +20 -0
  39. package/dist/queryClient.cjs.map +1 -0
  40. package/dist/queryClient.d.cts +15 -0
  41. package/dist/queryClient.d.ts +15 -0
  42. package/dist/queryClient.js +17 -0
  43. package/dist/queryClient.js.map +1 -0
  44. package/dist/resolve.cjs +37 -0
  45. package/dist/resolve.cjs.map +1 -0
  46. package/dist/resolve.d.cts +17 -0
  47. package/dist/resolve.d.ts +17 -0
  48. package/dist/resolve.js +33 -0
  49. package/dist/resolve.js.map +1 -0
  50. package/dist/types.cjs +4 -0
  51. package/dist/types.cjs.map +1 -0
  52. package/dist/types.d.cts +209 -0
  53. package/dist/types.d.ts +209 -0
  54. package/dist/types.js +3 -0
  55. package/dist/types.js.map +1 -0
  56. package/package.json +60 -0
  57. package/src/createBaseQuery.ts +428 -0
  58. package/src/createInfiniteQuery.ts +291 -0
  59. package/src/createInvalidate.ts +104 -0
  60. package/src/createMutation.ts +271 -0
  61. package/src/createQuery.ts +155 -0
  62. package/src/index.ts +17 -0
  63. package/src/queryClient.ts +23 -0
  64. package/src/resolve.ts +50 -0
  65. package/src/types.ts +270 -0
@@ -0,0 +1,155 @@
1
+ import { attach, createEvent, sample } from 'effector'
2
+ import { QueryObserver } from '@tanstack/query-core'
3
+ import type { QueryClient } from '@tanstack/query-core'
4
+ import { createBaseQuery, warnMissingName } from './createBaseQuery'
5
+ import { resolveReactiveRefetchInterval } from './resolve'
6
+ import type { CreateQueryOptions, QueryResult } from './types'
7
+
8
+ export function createQuery<
9
+ TQueryFnData = unknown,
10
+ TError = Error,
11
+ TData = TQueryFnData,
12
+ >(
13
+ options: CreateQueryOptions<TQueryFnData, TError, TData>,
14
+ ): QueryResult<TData, TError>
15
+ export function createQuery<
16
+ TQueryFnData = unknown,
17
+ TError = Error,
18
+ TData = TQueryFnData,
19
+ >(
20
+ queryClient: QueryClient,
21
+ options: CreateQueryOptions<TQueryFnData, TError, TData>,
22
+ ): QueryResult<TData, TError>
23
+ export function createQuery<
24
+ TQueryFnData = unknown,
25
+ TError = Error,
26
+ TData = TQueryFnData,
27
+ >(
28
+ arg1:
29
+ | QueryClient
30
+ | CreateQueryOptions<TQueryFnData, TError, TData>,
31
+ arg2?: CreateQueryOptions<TQueryFnData, TError, TData>,
32
+ ): QueryResult<TData, TError> {
33
+ const [explicitClient, options] = parseQueryArgs<TQueryFnData, TError, TData>(
34
+ arg1,
35
+ arg2,
36
+ )
37
+ const { queryKey, enabled, name, ...restOptions } = options
38
+
39
+ if (!name) warnMissingName('createQuery')
40
+
41
+ // If `refetchInterval` is a Store, pull it out for reactive wiring in
42
+ // createBaseQuery — otherwise leave it in restOptions for the observer
43
+ // constructor (handles plain values and function forms unchanged).
44
+ const reactiveRefetchInterval = resolveReactiveRefetchInterval(
45
+ (restOptions as { refetchInterval?: unknown }).refetchInterval,
46
+ )
47
+ if (reactiveRefetchInterval) {
48
+ delete (restOptions as { refetchInterval?: unknown }).refetchInterval
49
+ }
50
+
51
+ const base = createBaseQuery<
52
+ TData,
53
+ TError,
54
+ ReturnType<QueryObserver<TQueryFnData, TError, TData>['getCurrentResult']>,
55
+ QueryObserver<TQueryFnData, TError, TData>
56
+ >(
57
+ explicitClient,
58
+ { queryKey, enabled, name, reactiveRefetchInterval },
59
+ {
60
+ createObserver: (qc, { queryKey: key, enabled: isEnabled }) =>
61
+ // Cast: restOptions's `refetchInterval` may still type as
62
+ // `Store | number | false | fn`; the Store form is deleted at runtime
63
+ // above, but TS can't narrow that here.
64
+ new QueryObserver<TQueryFnData, TError, TData>(qc, {
65
+ ...restOptions,
66
+ queryKey: key,
67
+ enabled: isEnabled,
68
+ } as any),
69
+ },
70
+ )
71
+
72
+ // Prefetch event: drives `queryClient.fetchQuery` directly (no Observer)
73
+ // and **awaits** the result, so `allSettled(query.prefetch, { scope })` on
74
+ // the server returns only after the cache has the data. Unlike `mounted`,
75
+ // which kicks off a background subscription and resolves immediately, this
76
+ // is the right primitive for SSR / route loaders. The current resolved key
77
+ // + enabled is read from the scope via attach — reactive keys work.
78
+ const prefetch = createEvent<void>()
79
+ const prefetchFx = attach({
80
+ source: {
81
+ qc: base.$queryClient,
82
+ key: base.$resolvedKey,
83
+ enabled: base.$enabled,
84
+ },
85
+ effect: ({ qc, key, enabled }) => {
86
+ if (!qc || !enabled) return
87
+ return qc.fetchQuery({
88
+ ...restOptions,
89
+ queryKey: key,
90
+ } as any)
91
+ },
92
+ })
93
+ sample({ clock: prefetch, target: prefetchFx })
94
+
95
+ // Lazy `observer` field for backward compatibility — returns the
96
+ // default-scope observer (non-fork). Tests and advanced consumers that read
97
+ // `query.observer` after `query.mounted()` see the live observer. For
98
+ // fork-aware consumers, use `query.$observer` via `useUnit`.
99
+ const result: QueryResult<TData, TError> = {
100
+ $data: base.$data,
101
+ $error: base.$error,
102
+ $status: base.$status,
103
+ $isPending: base.$isPending,
104
+ $isFetching: base.$isFetching,
105
+ $isSuccess: base.$isSuccess,
106
+ $isError: base.$isError,
107
+ $isPlaceholderData: base.$isPlaceholderData,
108
+ $fetchStatus: base.$fetchStatus,
109
+ $observer: base.$observer as unknown as QueryResult<
110
+ TData,
111
+ TError
112
+ >['$observer'],
113
+ $queryClient: base.$queryClient,
114
+ refresh: base.refresh,
115
+ prefetch,
116
+ mounted: base.mounted,
117
+ unmounted: base.unmounted,
118
+ }
119
+
120
+ // Internal: used by useSuspenseQuery to construct a transient observer
121
+ // when the suspense hook renders before mountFx has populated the scope's
122
+ // $observer (mountFx runs from useEffect, which is skipped while
123
+ // suspended). Not part of the public API; not in TS types.
124
+ Object.defineProperty(result, '__createObserver', {
125
+ enumerable: false,
126
+ value: (qc: QueryClient, init: { queryKey: any; enabled: boolean }) =>
127
+ new QueryObserver<TQueryFnData, TError, TData>(qc, {
128
+ ...restOptions,
129
+ queryKey: init.queryKey,
130
+ enabled: init.enabled,
131
+ } as any),
132
+ })
133
+ Object.defineProperty(result, '__resolvedKey', {
134
+ enumerable: false,
135
+ value: base.$resolvedKey,
136
+ })
137
+ Object.defineProperty(result, '__enabled', {
138
+ enumerable: false,
139
+ value: base.$enabled,
140
+ })
141
+
142
+ return result
143
+ }
144
+
145
+ function parseQueryArgs<TQueryFnData, TError, TData>(
146
+ arg1:
147
+ | QueryClient
148
+ | CreateQueryOptions<TQueryFnData, TError, TData>,
149
+ arg2?: CreateQueryOptions<TQueryFnData, TError, TData>,
150
+ ): [QueryClient | null, CreateQueryOptions<TQueryFnData, TError, TData>] {
151
+ if (arg2 !== undefined) {
152
+ return [arg1 as QueryClient, arg2]
153
+ }
154
+ return [null, arg1 as CreateQueryOptions<TQueryFnData, TError, TData>]
155
+ }
package/src/index.ts ADDED
@@ -0,0 +1,17 @@
1
+ export { createQuery } from './createQuery'
2
+ export { createInfiniteQuery } from './createInfiniteQuery'
3
+ export { createMutation } from './createMutation'
4
+ export { createInvalidate } from './createInvalidate'
5
+ export type { CreateInvalidateOptions } from './createInvalidate'
6
+ export { $queryClient, setQueryClient } from './queryClient'
7
+ export type {
8
+ CreateInfiniteQueryOptions,
9
+ CreateMutationOptions,
10
+ CreateQueryOptions,
11
+ EffectorQueryKey,
12
+ InfiniteQueryResult,
13
+ MutationResult,
14
+ MutationStatus,
15
+ QueryResult,
16
+ StoreOrValue,
17
+ } from './types'
@@ -0,0 +1,23 @@
1
+ import { createEvent, createStore } from 'effector'
2
+ import type { QueryClient } from '@tanstack/query-core'
3
+
4
+ /**
5
+ * Holds the default `QueryClient` used when a factory (`createQuery`,
6
+ * `createInfiniteQuery`, `createMutation`) is called without an explicit one.
7
+ *
8
+ * Per-scope isolation: under `fork({ values: [[$queryClient, qc]] })`, every
9
+ * factory in that scope uses its own client. Observers are created lazily on
10
+ * mount, reading this store via `attach`, so each scope gets its own observer.
11
+ */
12
+ export const $queryClient = createStore<QueryClient | null>(null, {
13
+ name: '@tanstack/query-effector.$queryClient',
14
+ sid: '@tanstack/query-effector.$queryClient',
15
+ // Carries a runtime-only object (`QueryClient` instance) — must not
16
+ // round-trip through `serialize(scope)`. Per-scope injection happens
17
+ // via `fork({ values: [[$queryClient, qc]] })` instead.
18
+ serialize: 'ignore',
19
+ })
20
+
21
+ export const setQueryClient = createEvent<QueryClient>()
22
+
23
+ $queryClient.on(setQueryClient, (_, qc) => qc)
package/src/resolve.ts ADDED
@@ -0,0 +1,50 @@
1
+ import { combine, createStore, is } from 'effector'
2
+ import type { Store } from 'effector'
3
+ import type { QueryKey } from '@tanstack/query-core'
4
+ import type { EffectorQueryKey, StoreOrValue } from './types'
5
+
6
+ export function resolveKey(key: EffectorQueryKey): Store<QueryKey> {
7
+ const storePositions: Array<number> = []
8
+ const stores: Array<Store<unknown>> = []
9
+
10
+ key.forEach((item, i) => {
11
+ if (is.store(item)) {
12
+ storePositions.push(i)
13
+ stores.push(item as Store<unknown>)
14
+ }
15
+ })
16
+
17
+ if (stores.length === 0) {
18
+ return createStore(key as QueryKey)
19
+ }
20
+
21
+ return combine(stores).map((values) =>
22
+ key.map((item, i) => {
23
+ const storeIdx = storePositions.indexOf(i)
24
+ return storeIdx >= 0 ? values[storeIdx] : item
25
+ }),
26
+ ) as Store<QueryKey>
27
+ }
28
+
29
+ export function resolveEnabled(
30
+ enabled: StoreOrValue<boolean> | undefined,
31
+ ): Store<boolean> {
32
+ if (is.store(enabled)) return enabled
33
+ return createStore(enabled ?? true)
34
+ }
35
+
36
+ /**
37
+ * `refetchInterval` accepts a static value, a function `(query) => …`, or an
38
+ * effector `Store<number | false>`. Only the Store form is "reactive" — the
39
+ * function form is evaluated by the observer on every tick and stays in the
40
+ * observer's options. Returns the store if one was passed, `undefined`
41
+ * otherwise (signaling the per-flavor factory to keep the value in
42
+ * `restOptions` for the observer constructor).
43
+ */
44
+ export function resolveReactiveRefetchInterval(
45
+ value: unknown,
46
+ ): Store<number | false | undefined> | undefined {
47
+ return is.store(value)
48
+ ? (value as Store<number | false | undefined>)
49
+ : undefined
50
+ }
package/src/types.ts ADDED
@@ -0,0 +1,270 @@
1
+ import type { Event, EventCallable, Store } from 'effector'
2
+ import type {
3
+ FetchStatus,
4
+ InfiniteData,
5
+ InfiniteQueryObserver,
6
+ InfiniteQueryObserverOptions,
7
+ MutateOptions,
8
+ MutationObserver,
9
+ MutationObserverOptions,
10
+ QueryClient,
11
+ QueryKey,
12
+ QueryObserver,
13
+ QueryObserverOptions,
14
+ QueryStatus,
15
+ } from '@tanstack/query-core'
16
+
17
+ export type StoreOrValue<T> = Store<T> | T
18
+
19
+ /**
20
+ * A query key where each element can be a plain value or an effector Store.
21
+ * When any Store changes, the query is automatically re-executed with the new key.
22
+ *
23
+ * @example
24
+ * const $userId = createStore(1)
25
+ * queryKey: ['user', $userId, 'details']
26
+ */
27
+ export type EffectorQueryKey = ReadonlyArray<
28
+ StoreOrValue<string | number | bigint | boolean | null | undefined | object>
29
+ >
30
+
31
+ export interface CreateQueryOptions<
32
+ TQueryFnData = unknown,
33
+ TError = Error,
34
+ TData = TQueryFnData,
35
+ > extends Omit<
36
+ QueryObserverOptions<TQueryFnData, TError, TData>,
37
+ 'queryKey' | 'enabled' | 'refetchInterval'
38
+ > {
39
+ queryKey: EffectorQueryKey
40
+ enabled?: StoreOrValue<boolean>
41
+ /**
42
+ * Polling interval in milliseconds, `false` to disable, or a Store for
43
+ * runtime toggling — `Store<number | false>`. When a Store is passed, the
44
+ * observer's `refetchInterval` is automatically kept in sync via
45
+ * `setOptions` on every store change. The function form (`(query) => …`)
46
+ * from TanStack Query is also still supported.
47
+ */
48
+ refetchInterval?:
49
+ | QueryObserverOptions<TQueryFnData, TError, TData>['refetchInterval']
50
+ | Store<number | false | undefined>
51
+ /**
52
+ * Stable name used to derive SIDs for the internal effector stores so that
53
+ * `serialize(scope)` / `fork({ values })` round-trip works for SSR. Without
54
+ * a name, the queryClient's `dehydrate`/`hydrate` path still works, but
55
+ * scope-only serialization will silently drop these stores.
56
+ */
57
+ name?: string
58
+ }
59
+
60
+ export interface QueryResult<TData, TError = Error> {
61
+ /** The resolved query data, or `undefined` while loading */
62
+ $data: Store<TData | undefined>
63
+ /** The query error, or `null` if there is none */
64
+ $error: Store<TError | null>
65
+ /** The query status: `'pending'` | `'success'` | `'error'` */
66
+ $status: Store<QueryStatus>
67
+ /** `true` while there is no cached data and the query is fetching */
68
+ $isPending: Store<boolean>
69
+ /** `true` while the query is fetching in the background */
70
+ $isFetching: Store<boolean>
71
+ /** `true` when the query has successfully fetched data */
72
+ $isSuccess: Store<boolean>
73
+ /** `true` when the query has failed */
74
+ $isError: Store<boolean>
75
+ /** `true` when the displayed data is placeholder data (not yet fetched for current key) */
76
+ $isPlaceholderData: Store<boolean>
77
+ /** The fetch status: `'fetching'` | `'paused'` | `'idle'` */
78
+ $fetchStatus: Store<FetchStatus>
79
+ /** Invalidates the query and triggers a background refetch */
80
+ refresh: EventCallable<void>
81
+ /**
82
+ * Fetches the query via `queryClient.fetchQuery` and **awaits** the result.
83
+ * Unlike `mounted`, this is meant for server-side prefetching / route
84
+ * loaders where you need the cache populated before responding to the
85
+ * request — `await allSettled(query.prefetch, { scope })` returns only
86
+ * after the queryFn has resolved. Skips automatically when `enabled` is
87
+ * `false`.
88
+ *
89
+ * @example
90
+ * await allSettled(query.prefetch, { scope })
91
+ * // queryClient cache + scope are now ready to be dehydrated/serialized.
92
+ */
93
+ prefetch: EventCallable<void>
94
+ /**
95
+ * Initializes the query subscription. Must be called (or used with allSettled)
96
+ * before the query starts fetching.
97
+ *
98
+ * @example Without fork
99
+ * query.mounted()
100
+ *
101
+ * @example With fork (test isolation)
102
+ * const scope = fork()
103
+ * await allSettled(query.mounted, { scope })
104
+ */
105
+ mounted: EventCallable<void>
106
+ /**
107
+ * Tears down the query subscription and cancels any in-flight request.
108
+ * Call this when the consumer is destroyed (e.g. component unmount).
109
+ */
110
+ unmounted: EventCallable<void>
111
+ /**
112
+ * Per-scope observer store. Each fork scope has its own Observer instance,
113
+ * created lazily on `mounted()` and bound to that scope's QueryClient.
114
+ * Read scope-aware via `useUnit($observer)`. For tests, prefer
115
+ * `scope.getState($observer)` over reading the default scope state.
116
+ */
117
+ $observer: Store<QueryObserver<TData, TError> | null>
118
+ /**
119
+ * The QueryClient store this query is bound to. Frozen if a client was
120
+ * passed explicitly to the factory; otherwise points at the global
121
+ * `$queryClient` and honors `fork({ values: [[$queryClient, qc]] })`.
122
+ */
123
+ $queryClient: Store<QueryClient | null>
124
+ }
125
+
126
+ export interface CreateInfiniteQueryOptions<
127
+ TQueryFnData = unknown,
128
+ TError = Error,
129
+ TPageParam = unknown,
130
+ TData = InfiniteData<TQueryFnData, TPageParam>,
131
+ > extends Omit<
132
+ InfiniteQueryObserverOptions<
133
+ TQueryFnData,
134
+ TError,
135
+ TData,
136
+ ReadonlyArray<unknown>,
137
+ TPageParam
138
+ >,
139
+ 'queryKey' | 'enabled' | 'refetchInterval'
140
+ > {
141
+ queryKey: EffectorQueryKey
142
+ enabled?: StoreOrValue<boolean>
143
+ /** See {@link CreateQueryOptions.refetchInterval}. */
144
+ refetchInterval?:
145
+ | InfiniteQueryObserverOptions<
146
+ TQueryFnData,
147
+ TError,
148
+ TData,
149
+ ReadonlyArray<unknown>,
150
+ TPageParam
151
+ >['refetchInterval']
152
+ | Store<number | false | undefined>
153
+ /** See {@link CreateQueryOptions.name}. */
154
+ name?: string
155
+ }
156
+
157
+ export interface InfiniteQueryResult<
158
+ TData,
159
+ TError = Error,
160
+ TPageParam = unknown,
161
+ > {
162
+ /**
163
+ * The selected/displayed data. Defaults to `InfiniteData<TQueryFnData, TPageParam>`
164
+ * but narrows to whatever `select` returns when provided.
165
+ */
166
+ $data: Store<TData | undefined>
167
+ $error: Store<TError | null>
168
+ $status: Store<QueryStatus>
169
+ $isPending: Store<boolean>
170
+ $isFetching: Store<boolean>
171
+ $isSuccess: Store<boolean>
172
+ $isError: Store<boolean>
173
+ $isPlaceholderData: Store<boolean>
174
+ $fetchStatus: Store<FetchStatus>
175
+ $hasNextPage: Store<boolean>
176
+ $hasPreviousPage: Store<boolean>
177
+ $isFetchingNextPage: Store<boolean>
178
+ $isFetchingPreviousPage: Store<boolean>
179
+ $isFetchNextPageError: Store<boolean>
180
+ $isFetchPreviousPageError: Store<boolean>
181
+ fetchNextPage: EventCallable<void>
182
+ fetchPreviousPage: EventCallable<void>
183
+ refresh: EventCallable<void>
184
+ /** See {@link QueryResult.prefetch}. Uses `fetchInfiniteQuery` under the hood. */
185
+ prefetch: EventCallable<void>
186
+ mounted: EventCallable<void>
187
+ unmounted: EventCallable<void>
188
+ /** See {@link QueryResult.$observer}. */
189
+ $observer: Store<
190
+ InfiniteQueryObserver<any, TError, TData, QueryKey, TPageParam> | null
191
+ >
192
+ /** See {@link QueryResult.$queryClient}. */
193
+ $queryClient: Store<QueryClient | null>
194
+ }
195
+
196
+ export type CreateMutationOptions<
197
+ TData = unknown,
198
+ TError = Error,
199
+ TVariables = void,
200
+ TOnMutateResult = unknown,
201
+ > = MutationObserverOptions<TData, TError, TVariables, TOnMutateResult> & {
202
+ /** See {@link CreateQueryOptions.name}. */
203
+ name?: string
204
+ }
205
+
206
+ export type MutationStatus = 'idle' | 'pending' | 'success' | 'error'
207
+
208
+ export interface MutationResult<
209
+ TData = unknown,
210
+ TError = Error,
211
+ TVariables = void,
212
+ > {
213
+ /** The mutation result data, or `undefined` before success */
214
+ $data: Store<TData | undefined>
215
+ /** The mutation error, or `null` if there is none */
216
+ $error: Store<TError | null>
217
+ /** The mutation status: `'idle'` | `'pending'` | `'success'` | `'error'` */
218
+ $status: Store<MutationStatus>
219
+ /** The variables passed to the last `mutate` call */
220
+ $variables: Store<TVariables | undefined>
221
+ /** `true` while the mutation is paused (e.g. offline) and cannot run */
222
+ $isPaused: Store<boolean>
223
+ /** `true` while the mutation is executing */
224
+ $isPending: Store<boolean>
225
+ /** `true` when the mutation has succeeded */
226
+ $isSuccess: Store<boolean>
227
+ /** `true` when the mutation has failed */
228
+ $isError: Store<boolean>
229
+ /** `true` when the mutation has not yet been triggered */
230
+ $isIdle: Store<boolean>
231
+ /** Per-scope MutationObserver. Created on `start()`. See {@link QueryResult.$observer}. */
232
+ $observer: Store<MutationObserver<TData, TError, TVariables, any> | null>
233
+ /** See {@link QueryResult.$queryClient}. */
234
+ $queryClient: Store<QueryClient | null>
235
+ /** Triggers the mutation with the given variables */
236
+ mutate: EventCallable<TVariables>
237
+ /**
238
+ * Triggers the mutation with per-call callbacks layered on top of the
239
+ * observer-level ones. Use when you need component-local reactions
240
+ * (e.g. navigate after success) without module-level `sample` wiring.
241
+ */
242
+ mutateWith: EventCallable<{
243
+ variables: TVariables
244
+ onSuccess?: MutateOptions<TData, TError, TVariables>['onSuccess']
245
+ onError?: MutateOptions<TData, TError, TVariables>['onError']
246
+ onSettled?: MutateOptions<TData, TError, TVariables>['onSettled']
247
+ }>
248
+ /** Resets the mutation state back to idle */
249
+ reset: EventCallable<void>
250
+ /**
251
+ * Initializes the mutation observer subscription.
252
+ * Must be called before mutate to ensure stores receive updates.
253
+ */
254
+ start: EventCallable<void>
255
+ /**
256
+ * Tears down the observer subscription. Call this when the consumer is
257
+ * destroyed (e.g. component unmount) so the queryClient can gc the
258
+ * mutation entry.
259
+ */
260
+ unmounted: EventCallable<void>
261
+ /**
262
+ * Sample-friendly events for module-level reactions to mutation outcome.
263
+ * Payloads include both the original `params` and the `result` / `error`,
264
+ * matching effector effect `done` / `fail` shape.
265
+ */
266
+ finished: {
267
+ success: Event<{ params: TVariables; result: TData }>
268
+ failure: Event<{ params: TVariables; error: TError }>
269
+ }
270
+ }