@effector-tanstack-query/react 0.1.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/README.md +1 -0
- package/dist/index.cjs +16 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -2
- package/dist/index.d.ts +34 -2
- package/dist/index.js +16 -6
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +53 -7
package/README.md
CHANGED
|
@@ -6,6 +6,7 @@ React hooks for [`@effector-tanstack-query/core`](https://www.npmjs.com/package/
|
|
|
6
6
|
- `useMutation(mutation)` — returns `{ data, mutate, mutateWith, reset, … }`
|
|
7
7
|
- `useInfiniteQuery(query)` — paginated variant
|
|
8
8
|
- `useSuspenseQuery(query)` / `useSuspenseInfiniteQuery(query)` — Suspense-friendly with non-nullable `data`
|
|
9
|
+
- `<HydrationBoundary state={...}>` — merges a server-prefetched `DehydratedState` into the scope's `QueryClient` (SSR companion to `EffectorNext` / `fork({ values })`)
|
|
9
10
|
|
|
10
11
|
```bash
|
|
11
12
|
npm install @effector-tanstack-query/core @effector-tanstack-query/react \
|
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
'use strict';
|
|
2
3
|
|
|
3
4
|
var React = require('react');
|
|
4
5
|
var effectorReact = require('effector-react');
|
|
6
|
+
var queryCore = require('@tanstack/query-core');
|
|
7
|
+
var core = require('@effector-tanstack-query/core');
|
|
5
8
|
|
|
6
9
|
function _interopNamespace(e) {
|
|
7
10
|
if (e && e.__esModule) return e;
|
|
@@ -23,7 +26,17 @@ function _interopNamespace(e) {
|
|
|
23
26
|
|
|
24
27
|
var React__namespace = /*#__PURE__*/_interopNamespace(React);
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
function HydrationBoundary({
|
|
30
|
+
state,
|
|
31
|
+
options,
|
|
32
|
+
children
|
|
33
|
+
}) {
|
|
34
|
+
const queryClient = effectorReact.useUnit(core.$queryClient);
|
|
35
|
+
React__namespace.useMemo(() => {
|
|
36
|
+
if (queryClient && state) queryCore.hydrate(queryClient, state, options);
|
|
37
|
+
}, [queryClient, state, options]);
|
|
38
|
+
return React__namespace.createElement(React__namespace.Fragment, null, children);
|
|
39
|
+
}
|
|
27
40
|
function useQuery(query) {
|
|
28
41
|
const state = effectorReact.useUnit({
|
|
29
42
|
data: query.$data,
|
|
@@ -99,10 +112,7 @@ function useInfiniteQuery(query) {
|
|
|
99
112
|
}
|
|
100
113
|
function useObserverRerender(observer) {
|
|
101
114
|
const [, forceRender] = React__namespace.useReducer((x) => x + 1, 0);
|
|
102
|
-
React__namespace.useEffect(() =>
|
|
103
|
-
if (!observer) return;
|
|
104
|
-
return observer.subscribe(forceRender);
|
|
105
|
-
}, [observer]);
|
|
115
|
+
React__namespace.useEffect(() => observer.subscribe(forceRender), [observer]);
|
|
106
116
|
}
|
|
107
117
|
function useSuspenseQuery(query) {
|
|
108
118
|
const mount = effectorReact.useUnit(query.mounted);
|
|
@@ -194,6 +204,7 @@ function useSuspenseObserver(query) {
|
|
|
194
204
|
return observer;
|
|
195
205
|
}
|
|
196
206
|
|
|
207
|
+
exports.HydrationBoundary = HydrationBoundary;
|
|
197
208
|
exports.useInfiniteQuery = useInfiniteQuery;
|
|
198
209
|
exports.useMutation = useMutation;
|
|
199
210
|
exports.useQuery = useQuery;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":["useUnit","React"],"mappings":"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","file":"index.cjs","sourcesContent":["import * as React from 'react'\nimport { useUnit } from 'effector-react'\nimport type {\n FetchStatus,\n MutateOptions,\n QueryStatus,\n} from '@tanstack/query-core'\nimport type {\n InfiniteQueryResult,\n MutationResult,\n MutationStatus,\n QueryResult,\n} from '@effector-tanstack-query/core'\n\nexport interface UseQueryResult<TData, TError = Error> {\n data: TData | undefined\n error: TError | null\n status: QueryStatus\n isPending: boolean\n isFetching: boolean\n isSuccess: boolean\n isError: boolean\n isPlaceholderData: boolean\n fetchStatus: FetchStatus\n refresh: () => void\n}\n\n/**\n * Subscribes a React component to a query, automatically calling\n * `mounted()` on mount and `unmounted()` on cleanup.\n */\nexport function useQuery<TData, TError = Error>(\n query: QueryResult<TData, TError>,\n): UseQueryResult<TData, TError> {\n const state = useUnit({\n data: query.$data,\n error: query.$error,\n status: query.$status,\n isPending: query.$isPending,\n isFetching: query.$isFetching,\n isSuccess: query.$isSuccess,\n isError: query.$isError,\n isPlaceholderData: query.$isPlaceholderData,\n fetchStatus: query.$fetchStatus,\n })\n\n const mount = useUnit(query.mounted)\n const unmount = useUnit(query.unmounted)\n const refresh = useUnit(query.refresh)\n\n React.useEffect(() => {\n mount()\n return () => unmount()\n }, [mount, unmount])\n\n return { ...state, refresh }\n}\n\nexport interface UseMutationResult<TData, TError, TVariables> {\n data: TData | undefined\n error: TError | null\n status: MutationStatus\n variables: TVariables | undefined\n isPaused: boolean\n isPending: boolean\n isSuccess: boolean\n isError: boolean\n isIdle: boolean\n mutate: (variables: TVariables) => void\n /**\n * Trigger the mutation with per-call callbacks layered on top of the\n * observer-level ones (`onSuccess` / `onError` / `onSettled` in\n * `createMutation` options). Use this for component-local reactions\n * that don't fit module-level `sample` wiring — navigation after success,\n * one-shot toasts, etc.\n */\n mutateWith: (args: {\n variables: TVariables\n onSuccess?: MutateOptions<TData, TError, TVariables>['onSuccess']\n onError?: MutateOptions<TData, TError, TVariables>['onError']\n onSettled?: MutateOptions<TData, TError, TVariables>['onSettled']\n }) => void\n reset: () => void\n}\n\n/**\n * Subscribes a React component to a mutation, automatically calling\n * `start()` on mount and `unmounted()` on cleanup so the queryClient can\n * garbage-collect the mutation entry once no observers remain.\n */\nexport function useMutation<TData = unknown, TError = Error, TVariables = void>(\n mutation: MutationResult<TData, TError, TVariables>,\n): UseMutationResult<TData, TError, TVariables> {\n const state = useUnit({\n data: mutation.$data,\n error: mutation.$error,\n status: mutation.$status,\n variables: mutation.$variables,\n isPaused: mutation.$isPaused,\n isPending: mutation.$isPending,\n isSuccess: mutation.$isSuccess,\n isError: mutation.$isError,\n isIdle: mutation.$isIdle,\n })\n\n const start = useUnit(mutation.start)\n const unmount = useUnit(mutation.unmounted)\n const mutate = useUnit(mutation.mutate)\n const mutateWith = useUnit(mutation.mutateWith)\n const reset = useUnit(mutation.reset)\n\n React.useEffect(() => {\n start()\n return () => unmount()\n }, [start, unmount])\n\n return { ...state, mutate, mutateWith, reset }\n}\n\nexport interface UseInfiniteQueryResult<TData, TError> {\n data: TData | undefined\n error: TError | null\n status: QueryStatus\n isPending: boolean\n isFetching: boolean\n isSuccess: boolean\n isError: boolean\n isPlaceholderData: boolean\n fetchStatus: FetchStatus\n hasNextPage: boolean\n hasPreviousPage: boolean\n isFetchingNextPage: boolean\n isFetchingPreviousPage: boolean\n isFetchNextPageError: boolean\n isFetchPreviousPageError: boolean\n refresh: () => void\n fetchNextPage: () => void\n fetchPreviousPage: () => void\n}\n\n/**\n * Subscribes a React component to an infinite query, with auto mount/unmount\n * lifecycle and bound `fetchNextPage` / `fetchPreviousPage` callbacks.\n */\nexport function useInfiniteQuery<TData, TError = Error, TPageParam = unknown>(\n query: InfiniteQueryResult<TData, TError, TPageParam>,\n): UseInfiniteQueryResult<TData, TError> {\n const state = useUnit({\n data: query.$data,\n error: query.$error,\n status: query.$status,\n isPending: query.$isPending,\n isFetching: query.$isFetching,\n isSuccess: query.$isSuccess,\n isError: query.$isError,\n isPlaceholderData: query.$isPlaceholderData,\n fetchStatus: query.$fetchStatus,\n hasNextPage: query.$hasNextPage,\n hasPreviousPage: query.$hasPreviousPage,\n isFetchingNextPage: query.$isFetchingNextPage,\n isFetchingPreviousPage: query.$isFetchingPreviousPage,\n isFetchNextPageError: query.$isFetchNextPageError,\n isFetchPreviousPageError: query.$isFetchPreviousPageError,\n })\n\n const mount = useUnit(query.mounted)\n const unmount = useUnit(query.unmounted)\n const refresh = useUnit(query.refresh)\n const fetchNextPage = useUnit(query.fetchNextPage)\n const fetchPreviousPage = useUnit(query.fetchPreviousPage)\n\n React.useEffect(() => {\n mount()\n return () => unmount()\n }, [mount, unmount])\n\n return { ...state, refresh, fetchNextPage, fetchPreviousPage }\n}\n\n// Suspense data path: read from a per-scope observer.\n//\n// The scope mount chain runs in useEffect, which is skipped while a component\n// is suspended — so on the very first render the scope's `$observer` may be\n// null. To get synchronous access to the observer's promise during suspense,\n// we construct a transient observer via the factory's hidden\n// `__createObserver(qc, { queryKey, enabled })` helper. The transient observer\n// reads from / writes to the same queryClient cache as the eventual scope\n// observer (which is created when mountFx runs after useEffect commits).\n//\n// The mount/unmount effect is still wired up so that other consumers reading\n// the same query through `useUnit` / `useQuery` see updates in scope state.\n\nfunction useObserverRerender(\n observer: { subscribe: (cb: () => void) => () => void } | null,\n): void {\n const [, forceRender] = React.useReducer((x: number) => x + 1, 0)\n React.useEffect(() => {\n if (!observer) return\n return observer.subscribe(forceRender)\n }, [observer])\n}\n\ninterface SuspenseFactory<TObserver> {\n __createObserver(\n qc: import('@tanstack/query-core').QueryClient,\n init: { queryKey: unknown; enabled: boolean },\n ): TObserver\n __resolvedKey: import('effector').Store<unknown>\n __enabled: import('effector').Store<boolean>\n}\n\nexport interface UseSuspenseQueryResult<TData, TError = Error> {\n /** Resolved query data — non-nullable inside the rendered subtree (Suspense\n * absorbed the pending state). */\n data: TData\n /** Always `null` past the Suspense gate; errors are thrown to the nearest\n * `<ErrorBoundary>`. Typed as `TError | null` for consistency with\n * `useQuery` so the same destructure works in both. */\n error: TError | null\n status: 'success'\n isPending: false\n isSuccess: true\n isError: false\n /** `true` while a background refetch is running. Use for refresh spinners. */\n isFetching: boolean\n isPlaceholderData: boolean\n fetchStatus: FetchStatus\n refresh: () => void\n}\n\n/**\n * Reads a query for use inside a `<Suspense>` boundary. While the query is\n * pending, throws an inflight promise (queryClient-deduplicated). On error,\n * throws the error — catch with `<ErrorBoundary>`. Returns the same shape as\n * `useQuery`, but with `data` narrowed to non-nullable `TData` since the\n * pending state is impossible past the Suspense gate.\n */\nexport function useSuspenseQuery<TData, TError = Error>(\n query: QueryResult<TData, TError>,\n): UseSuspenseQueryResult<TData, TError> {\n // Auto-mount lifecycle so concurrent consumers (useUnit / useQuery) reading\n // the same query through the effector scope stay in sync.\n const mount = useUnit(query.mounted)\n const unmount = useUnit(query.unmounted)\n const refresh = useUnit(query.refresh)\n React.useEffect(() => {\n mount()\n return () => unmount()\n }, [mount, unmount])\n\n const observer = useSuspenseObserver(query)\n\n useObserverRerender(observer)\n\n const result = observer.getOptimisticResult(observer.options as any)\n\n if (result.status === 'error') throw result.error\n if (result.status === 'pending') {\n throw observer.fetchOptimistic(observer.options as any)\n }\n\n // Read all secondary fields from the observer result, not the effector\n // stores: stores are only populated after mountFx fires from useEffect,\n // which on the very first successful render hasn't run yet. The observer\n // result is always live and consistent.\n return {\n data: result.data as TData,\n error: result.error as TError | null,\n status: 'success',\n isPending: false,\n isSuccess: true,\n isError: false,\n isFetching: result.isFetching,\n isPlaceholderData: result.isPlaceholderData,\n fetchStatus: result.fetchStatus,\n refresh,\n }\n}\n\nexport interface UseSuspenseInfiniteQueryResult<TData, TError = Error> {\n data: TData\n error: TError | null\n status: 'success'\n isPending: false\n isSuccess: true\n isError: false\n isFetching: boolean\n isPlaceholderData: boolean\n fetchStatus: FetchStatus\n hasNextPage: boolean\n hasPreviousPage: boolean\n isFetchingNextPage: boolean\n isFetchingPreviousPage: boolean\n isFetchNextPageError: boolean\n isFetchPreviousPageError: boolean\n refresh: () => void\n fetchNextPage: () => void\n fetchPreviousPage: () => void\n}\n\n/**\n * Suspense variant of {@link useInfiniteQuery}. Same shape as `useInfiniteQuery`,\n * with `data` narrowed to non-nullable.\n */\nexport function useSuspenseInfiniteQuery<\n TData,\n TError = Error,\n TPageParam = unknown,\n>(\n query: InfiniteQueryResult<TData, TError, TPageParam>,\n): UseSuspenseInfiniteQueryResult<TData, TError> {\n const mount = useUnit(query.mounted)\n const unmount = useUnit(query.unmounted)\n const refresh = useUnit(query.refresh)\n const fetchNextPage = useUnit(query.fetchNextPage)\n const fetchPreviousPage = useUnit(query.fetchPreviousPage)\n React.useEffect(() => {\n mount()\n return () => unmount()\n }, [mount, unmount])\n\n const observer = useSuspenseObserver(query)\n\n useObserverRerender(observer)\n\n const result = observer.getOptimisticResult(observer.options as any)\n\n if (result.status === 'error') throw result.error\n if (result.status === 'pending') {\n throw (\n observer as unknown as {\n fetchOptimistic: (\n options: typeof observer.options,\n ) => Promise<unknown>\n }\n ).fetchOptimistic(observer.options)\n }\n\n const r = result as typeof result & {\n hasNextPage: boolean\n hasPreviousPage: boolean\n isFetchingNextPage: boolean\n isFetchingPreviousPage: boolean\n isFetchNextPageError: boolean\n isFetchPreviousPageError: boolean\n }\n\n return {\n data: r.data as TData,\n error: r.error as TError | null,\n status: 'success',\n isPending: false,\n isSuccess: true,\n isError: false,\n isFetching: r.isFetching,\n isPlaceholderData: r.isPlaceholderData,\n fetchStatus: r.fetchStatus,\n hasNextPage: r.hasNextPage,\n hasPreviousPage: r.hasPreviousPage,\n isFetchingNextPage: r.isFetchingNextPage,\n isFetchingPreviousPage: r.isFetchingPreviousPage,\n isFetchNextPageError: r.isFetchNextPageError,\n isFetchPreviousPageError: r.isFetchPreviousPageError,\n refresh,\n fetchNextPage,\n fetchPreviousPage,\n }\n}\n\n/**\n * Resolves a per-scope observer for suspense usage. Prefers the scope's\n * `$observer` (set by mountFx); falls back to a transient observer\n * constructed via `__createObserver` so that the very first render — before\n * useEffect has fired — has a working observer. Both flavors read/write the\n * same queryClient cache, so the transient observer is a thin wrapper.\n */\nfunction useSuspenseObserver<\n TQuery extends {\n $observer: import('effector').Store<TObserver | null>\n $queryClient: import('effector').Store<\n import('@tanstack/query-core').QueryClient | null\n >\n },\n TObserver extends {\n options: { queryKey: unknown }\n setOptions(options: any): void\n subscribe(cb: () => void): () => void\n getOptimisticResult(options: any): {\n status: 'pending' | 'success' | 'error'\n data: unknown\n error: unknown\n isFetching: boolean\n isPlaceholderData: boolean\n fetchStatus: FetchStatus\n // Infinite-query result fields — present at runtime when the underlying\n // observer is an InfiniteQueryObserver; the suspense hooks narrow as\n // needed. Typed as `any` here to keep the constraint loose.\n hasNextPage?: any\n hasPreviousPage?: any\n isFetchingNextPage?: any\n isFetchingPreviousPage?: any\n isFetchNextPageError?: any\n isFetchPreviousPageError?: any\n }\n fetchOptimistic(options: any): Promise<unknown>\n },\n>(query: TQuery): TObserver {\n const factory = query as unknown as TQuery & SuspenseFactory<TObserver>\n const observerInScope = useUnit(query.$observer) as TObserver | null\n const qc = useUnit(query.$queryClient)\n const queryKey = useUnit(factory.__resolvedKey)\n const enabled = useUnit(factory.__enabled)\n\n // Memoize a transient observer keyed by qc, so it survives across renders\n // while the scope observer is null. Once observerInScope appears, we\n // switch — the transient one is unsubscribed and abandoned (it never\n // subscribed to queryCache, so there is nothing to leak).\n const transient = React.useMemo(() => {\n if (observerInScope || !qc) return null\n return factory.__createObserver(qc, { queryKey, enabled })\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [observerInScope, qc, factory])\n\n // Keep the transient observer's options in sync with reactive key/enabled,\n // so re-suspending on key changes still works through it.\n React.useEffect(() => {\n if (!transient) return\n transient.setOptions({ ...transient.options, queryKey, enabled })\n }, [transient, queryKey, enabled])\n\n const observer = observerInScope ?? transient\n if (!observer) {\n throw new Error(\n '[@effector-tanstack-query/react] useSuspenseQuery: no QueryClient is set. ' +\n 'Call setQueryClient(qc) or pass it to fork({ values: [[$queryClient, qc]] }).',\n )\n }\n return observer\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["useUnit","$queryClient","React","hydrate"],"mappings":"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","file":"index.cjs","sourcesContent":["'use client'\n\nimport * as React from 'react'\nimport { useUnit } from 'effector-react'\nimport { hydrate } from '@tanstack/query-core'\nimport type {\n DehydratedState,\n FetchStatus,\n HydrateOptions,\n MutateOptions,\n QueryStatus,\n} from '@tanstack/query-core'\nimport { $queryClient } from '@effector-tanstack-query/core'\nimport type {\n InfiniteQueryResult,\n MutationResult,\n MutationStatus,\n QueryResult,\n} from '@effector-tanstack-query/core'\n\nexport interface HydrationBoundaryProps {\n /**\n * Snapshot produced by `dehydrate(queryClient)` on the server. Re-applied\n * to the scope's `QueryClient` cache so observers mounted under this tree\n * read prefetched data instead of triggering fresh network requests.\n */\n state?: DehydratedState\n /** Forwarded to `hydrate(...)` — see `@tanstack/query-core` docs. */\n options?: HydrateOptions\n children?: React.ReactNode\n}\n\n/**\n * Merges a server-prefetched `DehydratedState` into the scope's\n * `QueryClient` cache.\n *\n * Mirrors `<HydrationBoundary>` from `@tanstack/react-query`: hydration\n * runs in `useMemo` so the merge happens during the render phase (children\n * see a populated cache on their first render, no flash). The hook\n * resolves the QueryClient via `useUnit($queryClient)` instead of\n * `useQueryClient()` — meaning each fork scope can have its own client\n * without an additional `<QueryClientProvider>` in the tree.\n *\n * `hydrate` is idempotent: re-rendering with the same `state` reference\n * is a no-op. Pass new `state` references on navigation to merge fresh\n * snapshots.\n *\n * Note: this only handles the QueryClient cache layer. Effector store\n * snapshots (e.g. `serialize(scope)`) flow through your existing\n * `<Provider>` / `<EffectorNext values>` layer — orthogonal concerns.\n */\nexport function HydrationBoundary({\n state,\n options,\n children,\n}: HydrationBoundaryProps): React.ReactElement {\n const queryClient = useUnit($queryClient)\n React.useMemo(() => {\n if (queryClient && state) hydrate(queryClient, state, options)\n }, [queryClient, state, options])\n return React.createElement(React.Fragment, null, children)\n}\n\nexport interface UseQueryResult<TData, TError = Error> {\n data: TData | undefined\n error: TError | null\n status: QueryStatus\n isPending: boolean\n isFetching: boolean\n isSuccess: boolean\n isError: boolean\n isPlaceholderData: boolean\n fetchStatus: FetchStatus\n refresh: () => void\n}\n\n/**\n * Subscribes a React component to a query, automatically calling\n * `mounted()` on mount and `unmounted()` on cleanup.\n */\nexport function useQuery<TData, TError = Error>(\n query: QueryResult<TData, TError>,\n): UseQueryResult<TData, TError> {\n const state = useUnit({\n data: query.$data,\n error: query.$error,\n status: query.$status,\n isPending: query.$isPending,\n isFetching: query.$isFetching,\n isSuccess: query.$isSuccess,\n isError: query.$isError,\n isPlaceholderData: query.$isPlaceholderData,\n fetchStatus: query.$fetchStatus,\n })\n\n const mount = useUnit(query.mounted)\n const unmount = useUnit(query.unmounted)\n const refresh = useUnit(query.refresh)\n\n React.useEffect(() => {\n mount()\n return () => unmount()\n }, [mount, unmount])\n\n return { ...state, refresh }\n}\n\nexport interface UseMutationResult<TData, TError, TVariables> {\n data: TData | undefined\n error: TError | null\n status: MutationStatus\n variables: TVariables | undefined\n isPaused: boolean\n isPending: boolean\n isSuccess: boolean\n isError: boolean\n isIdle: boolean\n mutate: (variables: TVariables) => void\n /**\n * Trigger the mutation with per-call callbacks layered on top of the\n * observer-level ones (`onSuccess` / `onError` / `onSettled` in\n * `createMutation` options). Use this for component-local reactions\n * that don't fit module-level `sample` wiring — navigation after success,\n * one-shot toasts, etc.\n */\n mutateWith: (args: {\n variables: TVariables\n onSuccess?: MutateOptions<TData, TError, TVariables>['onSuccess']\n onError?: MutateOptions<TData, TError, TVariables>['onError']\n onSettled?: MutateOptions<TData, TError, TVariables>['onSettled']\n }) => void\n reset: () => void\n}\n\n/**\n * Subscribes a React component to a mutation, automatically calling\n * `start()` on mount and `unmounted()` on cleanup so the queryClient can\n * garbage-collect the mutation entry once no observers remain.\n */\nexport function useMutation<TData = unknown, TError = Error, TVariables = void>(\n mutation: MutationResult<TData, TError, TVariables>,\n): UseMutationResult<TData, TError, TVariables> {\n const state = useUnit({\n data: mutation.$data,\n error: mutation.$error,\n status: mutation.$status,\n variables: mutation.$variables,\n isPaused: mutation.$isPaused,\n isPending: mutation.$isPending,\n isSuccess: mutation.$isSuccess,\n isError: mutation.$isError,\n isIdle: mutation.$isIdle,\n })\n\n const start = useUnit(mutation.start)\n const unmount = useUnit(mutation.unmounted)\n const mutate = useUnit(mutation.mutate)\n const mutateWith = useUnit(mutation.mutateWith)\n const reset = useUnit(mutation.reset)\n\n React.useEffect(() => {\n start()\n return () => unmount()\n }, [start, unmount])\n\n return { ...state, mutate, mutateWith, reset }\n}\n\nexport interface UseInfiniteQueryResult<TData, TError> {\n data: TData | undefined\n error: TError | null\n status: QueryStatus\n isPending: boolean\n isFetching: boolean\n isSuccess: boolean\n isError: boolean\n isPlaceholderData: boolean\n fetchStatus: FetchStatus\n hasNextPage: boolean\n hasPreviousPage: boolean\n isFetchingNextPage: boolean\n isFetchingPreviousPage: boolean\n isFetchNextPageError: boolean\n isFetchPreviousPageError: boolean\n refresh: () => void\n fetchNextPage: () => void\n fetchPreviousPage: () => void\n}\n\n/**\n * Subscribes a React component to an infinite query, with auto mount/unmount\n * lifecycle and bound `fetchNextPage` / `fetchPreviousPage` callbacks.\n */\nexport function useInfiniteQuery<TData, TError = Error, TPageParam = unknown>(\n query: InfiniteQueryResult<TData, TError, TPageParam>,\n): UseInfiniteQueryResult<TData, TError> {\n const state = useUnit({\n data: query.$data,\n error: query.$error,\n status: query.$status,\n isPending: query.$isPending,\n isFetching: query.$isFetching,\n isSuccess: query.$isSuccess,\n isError: query.$isError,\n isPlaceholderData: query.$isPlaceholderData,\n fetchStatus: query.$fetchStatus,\n hasNextPage: query.$hasNextPage,\n hasPreviousPage: query.$hasPreviousPage,\n isFetchingNextPage: query.$isFetchingNextPage,\n isFetchingPreviousPage: query.$isFetchingPreviousPage,\n isFetchNextPageError: query.$isFetchNextPageError,\n isFetchPreviousPageError: query.$isFetchPreviousPageError,\n })\n\n const mount = useUnit(query.mounted)\n const unmount = useUnit(query.unmounted)\n const refresh = useUnit(query.refresh)\n const fetchNextPage = useUnit(query.fetchNextPage)\n const fetchPreviousPage = useUnit(query.fetchPreviousPage)\n\n React.useEffect(() => {\n mount()\n return () => unmount()\n }, [mount, unmount])\n\n return { ...state, refresh, fetchNextPage, fetchPreviousPage }\n}\n\n// Suspense data path: read from a per-scope observer.\n//\n// The scope mount chain runs in useEffect, which is skipped while a component\n// is suspended — so on the very first render the scope's `$observer` may be\n// null. To get synchronous access to the observer's promise during suspense,\n// we construct a transient observer via the factory's hidden\n// `__createObserver(qc, { queryKey, enabled })` helper. The transient observer\n// reads from / writes to the same queryClient cache as the eventual scope\n// observer (which is created when mountFx runs after useEffect commits).\n//\n// The mount/unmount effect is still wired up so that other consumers reading\n// the same query through `useUnit` / `useQuery` see updates in scope state.\n\nfunction useObserverRerender(observer: {\n subscribe: (cb: () => void) => () => void\n}): void {\n const [, forceRender] = React.useReducer((x: number) => x + 1, 0)\n React.useEffect(() => observer.subscribe(forceRender), [observer])\n}\n\ninterface SuspenseFactory<TObserver> {\n __createObserver(\n qc: import('@tanstack/query-core').QueryClient,\n init: { queryKey: unknown; enabled: boolean },\n ): TObserver\n __resolvedKey: import('effector').Store<unknown>\n __enabled: import('effector').Store<boolean>\n}\n\nexport interface UseSuspenseQueryResult<TData, TError = Error> {\n /** Resolved query data — non-nullable inside the rendered subtree (Suspense\n * absorbed the pending state). */\n data: TData\n /** Always `null` past the Suspense gate; errors are thrown to the nearest\n * `<ErrorBoundary>`. Typed as `TError | null` for consistency with\n * `useQuery` so the same destructure works in both. */\n error: TError | null\n status: 'success'\n isPending: false\n isSuccess: true\n isError: false\n /** `true` while a background refetch is running. Use for refresh spinners. */\n isFetching: boolean\n isPlaceholderData: boolean\n fetchStatus: FetchStatus\n refresh: () => void\n}\n\n/**\n * Reads a query for use inside a `<Suspense>` boundary. While the query is\n * pending, throws an inflight promise (queryClient-deduplicated). On error,\n * throws the error — catch with `<ErrorBoundary>`. Returns the same shape as\n * `useQuery`, but with `data` narrowed to non-nullable `TData` since the\n * pending state is impossible past the Suspense gate.\n */\nexport function useSuspenseQuery<TData, TError = Error>(\n query: QueryResult<TData, TError>,\n): UseSuspenseQueryResult<TData, TError> {\n // Auto-mount lifecycle so concurrent consumers (useUnit / useQuery) reading\n // the same query through the effector scope stay in sync.\n const mount = useUnit(query.mounted)\n const unmount = useUnit(query.unmounted)\n const refresh = useUnit(query.refresh)\n React.useEffect(() => {\n mount()\n return () => unmount()\n }, [mount, unmount])\n\n const observer = useSuspenseObserver(query)\n\n useObserverRerender(observer)\n\n const result = observer.getOptimisticResult(observer.options as any)\n\n if (result.status === 'error') throw result.error\n if (result.status === 'pending') {\n throw observer.fetchOptimistic(observer.options as any)\n }\n\n // Read all secondary fields from the observer result, not the effector\n // stores: stores are only populated after mountFx fires from useEffect,\n // which on the very first successful render hasn't run yet. The observer\n // result is always live and consistent.\n return {\n data: result.data as TData,\n error: result.error as TError | null,\n status: 'success',\n isPending: false,\n isSuccess: true,\n isError: false,\n isFetching: result.isFetching,\n isPlaceholderData: result.isPlaceholderData,\n fetchStatus: result.fetchStatus,\n refresh,\n }\n}\n\nexport interface UseSuspenseInfiniteQueryResult<TData, TError = Error> {\n data: TData\n error: TError | null\n status: 'success'\n isPending: false\n isSuccess: true\n isError: false\n isFetching: boolean\n isPlaceholderData: boolean\n fetchStatus: FetchStatus\n hasNextPage: boolean\n hasPreviousPage: boolean\n isFetchingNextPage: boolean\n isFetchingPreviousPage: boolean\n isFetchNextPageError: boolean\n isFetchPreviousPageError: boolean\n refresh: () => void\n fetchNextPage: () => void\n fetchPreviousPage: () => void\n}\n\n/**\n * Suspense variant of {@link useInfiniteQuery}. Same shape as `useInfiniteQuery`,\n * with `data` narrowed to non-nullable.\n */\nexport function useSuspenseInfiniteQuery<\n TData,\n TError = Error,\n TPageParam = unknown,\n>(\n query: InfiniteQueryResult<TData, TError, TPageParam>,\n): UseSuspenseInfiniteQueryResult<TData, TError> {\n const mount = useUnit(query.mounted)\n const unmount = useUnit(query.unmounted)\n const refresh = useUnit(query.refresh)\n const fetchNextPage = useUnit(query.fetchNextPage)\n const fetchPreviousPage = useUnit(query.fetchPreviousPage)\n React.useEffect(() => {\n mount()\n return () => unmount()\n }, [mount, unmount])\n\n const observer = useSuspenseObserver(query)\n\n useObserverRerender(observer)\n\n const result = observer.getOptimisticResult(observer.options as any)\n\n if (result.status === 'error') throw result.error\n if (result.status === 'pending') {\n throw (\n observer as unknown as {\n fetchOptimistic: (\n options: typeof observer.options,\n ) => Promise<unknown>\n }\n ).fetchOptimistic(observer.options)\n }\n\n const r = result as typeof result & {\n hasNextPage: boolean\n hasPreviousPage: boolean\n isFetchingNextPage: boolean\n isFetchingPreviousPage: boolean\n isFetchNextPageError: boolean\n isFetchPreviousPageError: boolean\n }\n\n return {\n data: r.data as TData,\n error: r.error as TError | null,\n status: 'success',\n isPending: false,\n isSuccess: true,\n isError: false,\n isFetching: r.isFetching,\n isPlaceholderData: r.isPlaceholderData,\n fetchStatus: r.fetchStatus,\n hasNextPage: r.hasNextPage,\n hasPreviousPage: r.hasPreviousPage,\n isFetchingNextPage: r.isFetchingNextPage,\n isFetchingPreviousPage: r.isFetchingPreviousPage,\n isFetchNextPageError: r.isFetchNextPageError,\n isFetchPreviousPageError: r.isFetchPreviousPageError,\n refresh,\n fetchNextPage,\n fetchPreviousPage,\n }\n}\n\n/**\n * Resolves a per-scope observer for suspense usage. Prefers the scope's\n * `$observer` (set by mountFx); falls back to a transient observer\n * constructed via `__createObserver` so that the very first render — before\n * useEffect has fired — has a working observer. Both flavors read/write the\n * same queryClient cache, so the transient observer is a thin wrapper.\n */\nfunction useSuspenseObserver<\n TQuery extends {\n $observer: import('effector').Store<TObserver | null>\n $queryClient: import('effector').Store<\n import('@tanstack/query-core').QueryClient | null\n >\n },\n TObserver extends {\n options: { queryKey: unknown }\n setOptions(options: any): void\n subscribe(cb: () => void): () => void\n getOptimisticResult(options: any): {\n status: 'pending' | 'success' | 'error'\n data: unknown\n error: unknown\n isFetching: boolean\n isPlaceholderData: boolean\n fetchStatus: FetchStatus\n // Infinite-query result fields — present at runtime when the underlying\n // observer is an InfiniteQueryObserver; the suspense hooks narrow as\n // needed. Typed as `any` here to keep the constraint loose.\n hasNextPage?: any\n hasPreviousPage?: any\n isFetchingNextPage?: any\n isFetchingPreviousPage?: any\n isFetchNextPageError?: any\n isFetchPreviousPageError?: any\n }\n fetchOptimistic(options: any): Promise<unknown>\n },\n>(query: TQuery): TObserver {\n const factory = query as unknown as TQuery & SuspenseFactory<TObserver>\n const observerInScope = useUnit(query.$observer) as TObserver | null\n const qc = useUnit(query.$queryClient)\n const queryKey = useUnit(factory.__resolvedKey)\n const enabled = useUnit(factory.__enabled)\n\n // Memoize a transient observer keyed by qc, so it survives across renders\n // while the scope observer is null. Once observerInScope appears, we\n // switch — the transient one is unsubscribed and abandoned (it never\n // subscribed to queryCache, so there is nothing to leak).\n const transient = React.useMemo(() => {\n if (observerInScope || !qc) return null\n return factory.__createObserver(qc, { queryKey, enabled })\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [observerInScope, qc, factory])\n\n // Keep the transient observer's options in sync with reactive key/enabled,\n // so re-suspending on key changes still works through it.\n React.useEffect(() => {\n if (!transient) return\n transient.setOptions({ ...transient.options, queryKey, enabled })\n }, [transient, queryKey, enabled])\n\n const observer = observerInScope ?? transient\n if (!observer) {\n throw new Error(\n '[@effector-tanstack-query/react] useSuspenseQuery: no QueryClient is set. ' +\n 'Call setQueryClient(qc) or pass it to fork({ values: [[$queryClient, qc]] }).',\n )\n }\n return observer\n}\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,38 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { DehydratedState, HydrateOptions, QueryStatus, FetchStatus, MutateOptions } from '@tanstack/query-core';
|
|
2
3
|
import { MutationStatus, InfiniteQueryResult, MutationResult, QueryResult } from '@effector-tanstack-query/core';
|
|
3
4
|
|
|
5
|
+
interface HydrationBoundaryProps {
|
|
6
|
+
/**
|
|
7
|
+
* Snapshot produced by `dehydrate(queryClient)` on the server. Re-applied
|
|
8
|
+
* to the scope's `QueryClient` cache so observers mounted under this tree
|
|
9
|
+
* read prefetched data instead of triggering fresh network requests.
|
|
10
|
+
*/
|
|
11
|
+
state?: DehydratedState;
|
|
12
|
+
/** Forwarded to `hydrate(...)` — see `@tanstack/query-core` docs. */
|
|
13
|
+
options?: HydrateOptions;
|
|
14
|
+
children?: React.ReactNode;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Merges a server-prefetched `DehydratedState` into the scope's
|
|
18
|
+
* `QueryClient` cache.
|
|
19
|
+
*
|
|
20
|
+
* Mirrors `<HydrationBoundary>` from `@tanstack/react-query`: hydration
|
|
21
|
+
* runs in `useMemo` so the merge happens during the render phase (children
|
|
22
|
+
* see a populated cache on their first render, no flash). The hook
|
|
23
|
+
* resolves the QueryClient via `useUnit($queryClient)` instead of
|
|
24
|
+
* `useQueryClient()` — meaning each fork scope can have its own client
|
|
25
|
+
* without an additional `<QueryClientProvider>` in the tree.
|
|
26
|
+
*
|
|
27
|
+
* `hydrate` is idempotent: re-rendering with the same `state` reference
|
|
28
|
+
* is a no-op. Pass new `state` references on navigation to merge fresh
|
|
29
|
+
* snapshots.
|
|
30
|
+
*
|
|
31
|
+
* Note: this only handles the QueryClient cache layer. Effector store
|
|
32
|
+
* snapshots (e.g. `serialize(scope)`) flow through your existing
|
|
33
|
+
* `<Provider>` / `<EffectorNext values>` layer — orthogonal concerns.
|
|
34
|
+
*/
|
|
35
|
+
declare function HydrationBoundary({ state, options, children, }: HydrationBoundaryProps): React.ReactElement;
|
|
4
36
|
interface UseQueryResult<TData, TError = Error> {
|
|
5
37
|
data: TData | undefined;
|
|
6
38
|
error: TError | null;
|
|
@@ -127,4 +159,4 @@ interface UseSuspenseInfiniteQueryResult<TData, TError = Error> {
|
|
|
127
159
|
*/
|
|
128
160
|
declare function useSuspenseInfiniteQuery<TData, TError = Error, TPageParam = unknown>(query: InfiniteQueryResult<TData, TError, TPageParam>): UseSuspenseInfiniteQueryResult<TData, TError>;
|
|
129
161
|
|
|
130
|
-
export { type UseInfiniteQueryResult, type UseMutationResult, type UseQueryResult, type UseSuspenseInfiniteQueryResult, type UseSuspenseQueryResult, useInfiniteQuery, useMutation, useQuery, useSuspenseInfiniteQuery, useSuspenseQuery };
|
|
162
|
+
export { HydrationBoundary, type HydrationBoundaryProps, type UseInfiniteQueryResult, type UseMutationResult, type UseQueryResult, type UseSuspenseInfiniteQueryResult, type UseSuspenseQueryResult, useInfiniteQuery, useMutation, useQuery, useSuspenseInfiniteQuery, useSuspenseQuery };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,38 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { DehydratedState, HydrateOptions, QueryStatus, FetchStatus, MutateOptions } from '@tanstack/query-core';
|
|
2
3
|
import { MutationStatus, InfiniteQueryResult, MutationResult, QueryResult } from '@effector-tanstack-query/core';
|
|
3
4
|
|
|
5
|
+
interface HydrationBoundaryProps {
|
|
6
|
+
/**
|
|
7
|
+
* Snapshot produced by `dehydrate(queryClient)` on the server. Re-applied
|
|
8
|
+
* to the scope's `QueryClient` cache so observers mounted under this tree
|
|
9
|
+
* read prefetched data instead of triggering fresh network requests.
|
|
10
|
+
*/
|
|
11
|
+
state?: DehydratedState;
|
|
12
|
+
/** Forwarded to `hydrate(...)` — see `@tanstack/query-core` docs. */
|
|
13
|
+
options?: HydrateOptions;
|
|
14
|
+
children?: React.ReactNode;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Merges a server-prefetched `DehydratedState` into the scope's
|
|
18
|
+
* `QueryClient` cache.
|
|
19
|
+
*
|
|
20
|
+
* Mirrors `<HydrationBoundary>` from `@tanstack/react-query`: hydration
|
|
21
|
+
* runs in `useMemo` so the merge happens during the render phase (children
|
|
22
|
+
* see a populated cache on their first render, no flash). The hook
|
|
23
|
+
* resolves the QueryClient via `useUnit($queryClient)` instead of
|
|
24
|
+
* `useQueryClient()` — meaning each fork scope can have its own client
|
|
25
|
+
* without an additional `<QueryClientProvider>` in the tree.
|
|
26
|
+
*
|
|
27
|
+
* `hydrate` is idempotent: re-rendering with the same `state` reference
|
|
28
|
+
* is a no-op. Pass new `state` references on navigation to merge fresh
|
|
29
|
+
* snapshots.
|
|
30
|
+
*
|
|
31
|
+
* Note: this only handles the QueryClient cache layer. Effector store
|
|
32
|
+
* snapshots (e.g. `serialize(scope)`) flow through your existing
|
|
33
|
+
* `<Provider>` / `<EffectorNext values>` layer — orthogonal concerns.
|
|
34
|
+
*/
|
|
35
|
+
declare function HydrationBoundary({ state, options, children, }: HydrationBoundaryProps): React.ReactElement;
|
|
4
36
|
interface UseQueryResult<TData, TError = Error> {
|
|
5
37
|
data: TData | undefined;
|
|
6
38
|
error: TError | null;
|
|
@@ -127,4 +159,4 @@ interface UseSuspenseInfiniteQueryResult<TData, TError = Error> {
|
|
|
127
159
|
*/
|
|
128
160
|
declare function useSuspenseInfiniteQuery<TData, TError = Error, TPageParam = unknown>(query: InfiniteQueryResult<TData, TError, TPageParam>): UseSuspenseInfiniteQueryResult<TData, TError>;
|
|
129
161
|
|
|
130
|
-
export { type UseInfiniteQueryResult, type UseMutationResult, type UseQueryResult, type UseSuspenseInfiniteQueryResult, type UseSuspenseQueryResult, useInfiniteQuery, useMutation, useQuery, useSuspenseInfiniteQuery, useSuspenseQuery };
|
|
162
|
+
export { HydrationBoundary, type HydrationBoundaryProps, type UseInfiniteQueryResult, type UseMutationResult, type UseQueryResult, type UseSuspenseInfiniteQueryResult, type UseSuspenseQueryResult, useInfiniteQuery, useMutation, useQuery, useSuspenseInfiniteQuery, useSuspenseQuery };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,20 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import * as React from 'react';
|
|
2
3
|
import { useUnit } from 'effector-react';
|
|
4
|
+
import { hydrate } from '@tanstack/query-core';
|
|
5
|
+
import { $queryClient } from '@effector-tanstack-query/core';
|
|
3
6
|
|
|
4
|
-
|
|
7
|
+
function HydrationBoundary({
|
|
8
|
+
state,
|
|
9
|
+
options,
|
|
10
|
+
children
|
|
11
|
+
}) {
|
|
12
|
+
const queryClient = useUnit($queryClient);
|
|
13
|
+
React.useMemo(() => {
|
|
14
|
+
if (queryClient && state) hydrate(queryClient, state, options);
|
|
15
|
+
}, [queryClient, state, options]);
|
|
16
|
+
return React.createElement(React.Fragment, null, children);
|
|
17
|
+
}
|
|
5
18
|
function useQuery(query) {
|
|
6
19
|
const state = useUnit({
|
|
7
20
|
data: query.$data,
|
|
@@ -77,10 +90,7 @@ function useInfiniteQuery(query) {
|
|
|
77
90
|
}
|
|
78
91
|
function useObserverRerender(observer) {
|
|
79
92
|
const [, forceRender] = React.useReducer((x) => x + 1, 0);
|
|
80
|
-
React.useEffect(() =>
|
|
81
|
-
if (!observer) return;
|
|
82
|
-
return observer.subscribe(forceRender);
|
|
83
|
-
}, [observer]);
|
|
93
|
+
React.useEffect(() => observer.subscribe(forceRender), [observer]);
|
|
84
94
|
}
|
|
85
95
|
function useSuspenseQuery(query) {
|
|
86
96
|
const mount = useUnit(query.mounted);
|
|
@@ -172,6 +182,6 @@ function useSuspenseObserver(query) {
|
|
|
172
182
|
return observer;
|
|
173
183
|
}
|
|
174
184
|
|
|
175
|
-
export { useInfiniteQuery, useMutation, useQuery, useSuspenseInfiniteQuery, useSuspenseQuery };
|
|
185
|
+
export { HydrationBoundary, useInfiniteQuery, useMutation, useQuery, useSuspenseInfiniteQuery, useSuspenseQuery };
|
|
176
186
|
//# sourceMappingURL=index.js.map
|
|
177
187
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";;;;AA+BO,SAAS,SACd,KAAA,EAC+B;AAC/B,EAAA,MAAM,QAAQ,OAAA,CAAQ;AAAA,IACpB,MAAM,KAAA,CAAM,KAAA;AAAA,IACZ,OAAO,KAAA,CAAM,MAAA;AAAA,IACb,QAAQ,KAAA,CAAM,OAAA;AAAA,IACd,WAAW,KAAA,CAAM,UAAA;AAAA,IACjB,YAAY,KAAA,CAAM,WAAA;AAAA,IAClB,WAAW,KAAA,CAAM,UAAA;AAAA,IACjB,SAAS,KAAA,CAAM,QAAA;AAAA,IACf,mBAAmB,KAAA,CAAM,kBAAA;AAAA,IACzB,aAAa,KAAA,CAAM;AAAA,GACpB,CAAA;AAED,EAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,KAAA,CAAM,OAAO,CAAA;AACnC,EAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,KAAA,CAAM,SAAS,CAAA;AACvC,EAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,KAAA,CAAM,OAAO,CAAA;AAErC,EAAM,gBAAU,MAAM;AACpB,IAAA,KAAA,EAAM;AACN,IAAA,OAAO,MAAM,OAAA,EAAQ;AAAA,EACvB,CAAA,EAAG,CAAC,KAAA,EAAO,OAAO,CAAC,CAAA;AAEnB,EAAA,OAAO,EAAE,GAAG,KAAA,EAAO,OAAA,EAAQ;AAC7B;AAkCO,SAAS,YACd,QAAA,EAC8C;AAC9C,EAAA,MAAM,QAAQ,OAAA,CAAQ;AAAA,IACpB,MAAM,QAAA,CAAS,KAAA;AAAA,IACf,OAAO,QAAA,CAAS,MAAA;AAAA,IAChB,QAAQ,QAAA,CAAS,OAAA;AAAA,IACjB,WAAW,QAAA,CAAS,UAAA;AAAA,IACpB,UAAU,QAAA,CAAS,SAAA;AAAA,IACnB,WAAW,QAAA,CAAS,UAAA;AAAA,IACpB,WAAW,QAAA,CAAS,UAAA;AAAA,IACpB,SAAS,QAAA,CAAS,QAAA;AAAA,IAClB,QAAQ,QAAA,CAAS;AAAA,GAClB,CAAA;AAED,EAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,QAAA,CAAS,KAAK,CAAA;AACpC,EAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,QAAA,CAAS,SAAS,CAAA;AAC1C,EAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,QAAA,CAAS,MAAM,CAAA;AACtC,EAAA,MAAM,UAAA,GAAa,OAAA,CAAQ,QAAA,CAAS,UAAU,CAAA;AAC9C,EAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,QAAA,CAAS,KAAK,CAAA;AAEpC,EAAM,gBAAU,MAAM;AACpB,IAAA,KAAA,EAAM;AACN,IAAA,OAAO,MAAM,OAAA,EAAQ;AAAA,EACvB,CAAA,EAAG,CAAC,KAAA,EAAO,OAAO,CAAC,CAAA;AAEnB,EAAA,OAAO,EAAE,GAAG,KAAA,EAAO,MAAA,EAAQ,YAAY,KAAA,EAAM;AAC/C;AA2BO,SAAS,iBACd,KAAA,EACuC;AACvC,EAAA,MAAM,QAAQ,OAAA,CAAQ;AAAA,IACpB,MAAM,KAAA,CAAM,KAAA;AAAA,IACZ,OAAO,KAAA,CAAM,MAAA;AAAA,IACb,QAAQ,KAAA,CAAM,OAAA;AAAA,IACd,WAAW,KAAA,CAAM,UAAA;AAAA,IACjB,YAAY,KAAA,CAAM,WAAA;AAAA,IAClB,WAAW,KAAA,CAAM,UAAA;AAAA,IACjB,SAAS,KAAA,CAAM,QAAA;AAAA,IACf,mBAAmB,KAAA,CAAM,kBAAA;AAAA,IACzB,aAAa,KAAA,CAAM,YAAA;AAAA,IACnB,aAAa,KAAA,CAAM,YAAA;AAAA,IACnB,iBAAiB,KAAA,CAAM,gBAAA;AAAA,IACvB,oBAAoB,KAAA,CAAM,mBAAA;AAAA,IAC1B,wBAAwB,KAAA,CAAM,uBAAA;AAAA,IAC9B,sBAAsB,KAAA,CAAM,qBAAA;AAAA,IAC5B,0BAA0B,KAAA,CAAM;AAAA,GACjC,CAAA;AAED,EAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,KAAA,CAAM,OAAO,CAAA;AACnC,EAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,KAAA,CAAM,SAAS,CAAA;AACvC,EAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,KAAA,CAAM,OAAO,CAAA;AACrC,EAAA,MAAM,aAAA,GAAgB,OAAA,CAAQ,KAAA,CAAM,aAAa,CAAA;AACjD,EAAA,MAAM,iBAAA,GAAoB,OAAA,CAAQ,KAAA,CAAM,iBAAiB,CAAA;AAEzD,EAAM,gBAAU,MAAM;AACpB,IAAA,KAAA,EAAM;AACN,IAAA,OAAO,MAAM,OAAA,EAAQ;AAAA,EACvB,CAAA,EAAG,CAAC,KAAA,EAAO,OAAO,CAAC,CAAA;AAEnB,EAAA,OAAO,EAAE,GAAG,KAAA,EAAO,OAAA,EAAS,eAAe,iBAAA,EAAkB;AAC/D;AAeA,SAAS,oBACP,QAAA,EACM;AACN,EAAA,MAAM,GAAG,WAAW,CAAA,GAAU,iBAAW,CAAC,CAAA,KAAc,CAAA,GAAI,CAAA,EAAG,CAAC,CAAA;AAChE,EAAM,gBAAU,MAAM;AACpB,IAAA,IAAI,CAAC,QAAA,EAAU;AACf,IAAA,OAAO,QAAA,CAAS,UAAU,WAAW,CAAA;AAAA,EACvC,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AACf;AAqCO,SAAS,iBACd,KAAA,EACuC;AAGvC,EAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,KAAA,CAAM,OAAO,CAAA;AACnC,EAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,KAAA,CAAM,SAAS,CAAA;AACvC,EAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,KAAA,CAAM,OAAO,CAAA;AACrC,EAAM,gBAAU,MAAM;AACpB,IAAA,KAAA,EAAM;AACN,IAAA,OAAO,MAAM,OAAA,EAAQ;AAAA,EACvB,CAAA,EAAG,CAAC,KAAA,EAAO,OAAO,CAAC,CAAA;AAEnB,EAAA,MAAM,QAAA,GAAW,oBAAoB,KAAK,CAAA;AAE1C,EAAA,mBAAA,CAAoB,QAAQ,CAAA;AAE5B,EAAA,MAAM,MAAA,GAAS,QAAA,CAAS,mBAAA,CAAoB,QAAA,CAAS,OAAc,CAAA;AAEnE,EAAA,IAAI,MAAA,CAAO,MAAA,KAAW,OAAA,EAAS,MAAM,MAAA,CAAO,KAAA;AAC5C,EAAA,IAAI,MAAA,CAAO,WAAW,SAAA,EAAW;AAC/B,IAAA,MAAM,QAAA,CAAS,eAAA,CAAgB,QAAA,CAAS,OAAc,CAAA;AAAA,EACxD;AAMA,EAAA,OAAO;AAAA,IACL,MAAM,MAAA,CAAO,IAAA;AAAA,IACb,OAAO,MAAA,CAAO,KAAA;AAAA,IACd,MAAA,EAAQ,SAAA;AAAA,IACR,SAAA,EAAW,KAAA;AAAA,IACX,SAAA,EAAW,IAAA;AAAA,IACX,OAAA,EAAS,KAAA;AAAA,IACT,YAAY,MAAA,CAAO,UAAA;AAAA,IACnB,mBAAmB,MAAA,CAAO,iBAAA;AAAA,IAC1B,aAAa,MAAA,CAAO,WAAA;AAAA,IACpB;AAAA,GACF;AACF;AA2BO,SAAS,yBAKd,KAAA,EAC+C;AAC/C,EAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,KAAA,CAAM,OAAO,CAAA;AACnC,EAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,KAAA,CAAM,SAAS,CAAA;AACvC,EAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,KAAA,CAAM,OAAO,CAAA;AACrC,EAAA,MAAM,aAAA,GAAgB,OAAA,CAAQ,KAAA,CAAM,aAAa,CAAA;AACjD,EAAA,MAAM,iBAAA,GAAoB,OAAA,CAAQ,KAAA,CAAM,iBAAiB,CAAA;AACzD,EAAM,gBAAU,MAAM;AACpB,IAAA,KAAA,EAAM;AACN,IAAA,OAAO,MAAM,OAAA,EAAQ;AAAA,EACvB,CAAA,EAAG,CAAC,KAAA,EAAO,OAAO,CAAC,CAAA;AAEnB,EAAA,MAAM,QAAA,GAAW,oBAAoB,KAAK,CAAA;AAE1C,EAAA,mBAAA,CAAoB,QAAQ,CAAA;AAE5B,EAAA,MAAM,MAAA,GAAS,QAAA,CAAS,mBAAA,CAAoB,QAAA,CAAS,OAAc,CAAA;AAEnE,EAAA,IAAI,MAAA,CAAO,MAAA,KAAW,OAAA,EAAS,MAAM,MAAA,CAAO,KAAA;AAC5C,EAAA,IAAI,MAAA,CAAO,WAAW,SAAA,EAAW;AAC/B,IAAA,MACE,QAAA,CAKA,eAAA,CAAgB,QAAA,CAAS,OAAO,CAAA;AAAA,EACpC;AAEA,EAAA,MAAM,CAAA,GAAI,MAAA;AASV,EAAA,OAAO;AAAA,IACL,MAAM,CAAA,CAAE,IAAA;AAAA,IACR,OAAO,CAAA,CAAE,KAAA;AAAA,IACT,MAAA,EAAQ,SAAA;AAAA,IACR,SAAA,EAAW,KAAA;AAAA,IACX,SAAA,EAAW,IAAA;AAAA,IACX,OAAA,EAAS,KAAA;AAAA,IACT,YAAY,CAAA,CAAE,UAAA;AAAA,IACd,mBAAmB,CAAA,CAAE,iBAAA;AAAA,IACrB,aAAa,CAAA,CAAE,WAAA;AAAA,IACf,aAAa,CAAA,CAAE,WAAA;AAAA,IACf,iBAAiB,CAAA,CAAE,eAAA;AAAA,IACnB,oBAAoB,CAAA,CAAE,kBAAA;AAAA,IACtB,wBAAwB,CAAA,CAAE,sBAAA;AAAA,IAC1B,sBAAsB,CAAA,CAAE,oBAAA;AAAA,IACxB,0BAA0B,CAAA,CAAE,wBAAA;AAAA,IAC5B,OAAA;AAAA,IACA,aAAA;AAAA,IACA;AAAA,GACF;AACF;AASA,SAAS,oBA8BP,KAAA,EAA0B;AAC1B,EAAA,MAAM,OAAA,GAAU,KAAA;AAChB,EAAA,MAAM,eAAA,GAAkB,OAAA,CAAQ,KAAA,CAAM,SAAS,CAAA;AAC/C,EAAA,MAAM,EAAA,GAAK,OAAA,CAAQ,KAAA,CAAM,YAAY,CAAA;AACrC,EAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,OAAA,CAAQ,aAAa,CAAA;AAC9C,EAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,OAAA,CAAQ,SAAS,CAAA;AAMzC,EAAA,MAAM,SAAA,GAAkB,cAAQ,MAAM;AACpC,IAAA,IAAI,eAAA,IAAmB,CAAC,EAAA,EAAI,OAAO,IAAA;AACnC,IAAA,OAAO,QAAQ,gBAAA,CAAiB,EAAA,EAAI,EAAE,QAAA,EAAU,SAAS,CAAA;AAAA,EAE3D,CAAA,EAAG,CAAC,eAAA,EAAiB,EAAA,EAAI,OAAO,CAAC,CAAA;AAIjC,EAAM,gBAAU,MAAM;AACpB,IAAA,IAAI,CAAC,SAAA,EAAW;AAChB,IAAA,SAAA,CAAU,WAAW,EAAE,GAAG,UAAU,OAAA,EAAS,QAAA,EAAU,SAAS,CAAA;AAAA,EAClE,CAAA,EAAG,CAAC,SAAA,EAAW,QAAA,EAAU,OAAO,CAAC,CAAA;AAEjC,EAAA,MAAM,WAAW,eAAA,IAAmB,SAAA;AACpC,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KAEF;AAAA,EACF;AACA,EAAA,OAAO,QAAA;AACT","file":"index.js","sourcesContent":["import * as React from 'react'\nimport { useUnit } from 'effector-react'\nimport type {\n FetchStatus,\n MutateOptions,\n QueryStatus,\n} from '@tanstack/query-core'\nimport type {\n InfiniteQueryResult,\n MutationResult,\n MutationStatus,\n QueryResult,\n} from '@effector-tanstack-query/core'\n\nexport interface UseQueryResult<TData, TError = Error> {\n data: TData | undefined\n error: TError | null\n status: QueryStatus\n isPending: boolean\n isFetching: boolean\n isSuccess: boolean\n isError: boolean\n isPlaceholderData: boolean\n fetchStatus: FetchStatus\n refresh: () => void\n}\n\n/**\n * Subscribes a React component to a query, automatically calling\n * `mounted()` on mount and `unmounted()` on cleanup.\n */\nexport function useQuery<TData, TError = Error>(\n query: QueryResult<TData, TError>,\n): UseQueryResult<TData, TError> {\n const state = useUnit({\n data: query.$data,\n error: query.$error,\n status: query.$status,\n isPending: query.$isPending,\n isFetching: query.$isFetching,\n isSuccess: query.$isSuccess,\n isError: query.$isError,\n isPlaceholderData: query.$isPlaceholderData,\n fetchStatus: query.$fetchStatus,\n })\n\n const mount = useUnit(query.mounted)\n const unmount = useUnit(query.unmounted)\n const refresh = useUnit(query.refresh)\n\n React.useEffect(() => {\n mount()\n return () => unmount()\n }, [mount, unmount])\n\n return { ...state, refresh }\n}\n\nexport interface UseMutationResult<TData, TError, TVariables> {\n data: TData | undefined\n error: TError | null\n status: MutationStatus\n variables: TVariables | undefined\n isPaused: boolean\n isPending: boolean\n isSuccess: boolean\n isError: boolean\n isIdle: boolean\n mutate: (variables: TVariables) => void\n /**\n * Trigger the mutation with per-call callbacks layered on top of the\n * observer-level ones (`onSuccess` / `onError` / `onSettled` in\n * `createMutation` options). Use this for component-local reactions\n * that don't fit module-level `sample` wiring — navigation after success,\n * one-shot toasts, etc.\n */\n mutateWith: (args: {\n variables: TVariables\n onSuccess?: MutateOptions<TData, TError, TVariables>['onSuccess']\n onError?: MutateOptions<TData, TError, TVariables>['onError']\n onSettled?: MutateOptions<TData, TError, TVariables>['onSettled']\n }) => void\n reset: () => void\n}\n\n/**\n * Subscribes a React component to a mutation, automatically calling\n * `start()` on mount and `unmounted()` on cleanup so the queryClient can\n * garbage-collect the mutation entry once no observers remain.\n */\nexport function useMutation<TData = unknown, TError = Error, TVariables = void>(\n mutation: MutationResult<TData, TError, TVariables>,\n): UseMutationResult<TData, TError, TVariables> {\n const state = useUnit({\n data: mutation.$data,\n error: mutation.$error,\n status: mutation.$status,\n variables: mutation.$variables,\n isPaused: mutation.$isPaused,\n isPending: mutation.$isPending,\n isSuccess: mutation.$isSuccess,\n isError: mutation.$isError,\n isIdle: mutation.$isIdle,\n })\n\n const start = useUnit(mutation.start)\n const unmount = useUnit(mutation.unmounted)\n const mutate = useUnit(mutation.mutate)\n const mutateWith = useUnit(mutation.mutateWith)\n const reset = useUnit(mutation.reset)\n\n React.useEffect(() => {\n start()\n return () => unmount()\n }, [start, unmount])\n\n return { ...state, mutate, mutateWith, reset }\n}\n\nexport interface UseInfiniteQueryResult<TData, TError> {\n data: TData | undefined\n error: TError | null\n status: QueryStatus\n isPending: boolean\n isFetching: boolean\n isSuccess: boolean\n isError: boolean\n isPlaceholderData: boolean\n fetchStatus: FetchStatus\n hasNextPage: boolean\n hasPreviousPage: boolean\n isFetchingNextPage: boolean\n isFetchingPreviousPage: boolean\n isFetchNextPageError: boolean\n isFetchPreviousPageError: boolean\n refresh: () => void\n fetchNextPage: () => void\n fetchPreviousPage: () => void\n}\n\n/**\n * Subscribes a React component to an infinite query, with auto mount/unmount\n * lifecycle and bound `fetchNextPage` / `fetchPreviousPage` callbacks.\n */\nexport function useInfiniteQuery<TData, TError = Error, TPageParam = unknown>(\n query: InfiniteQueryResult<TData, TError, TPageParam>,\n): UseInfiniteQueryResult<TData, TError> {\n const state = useUnit({\n data: query.$data,\n error: query.$error,\n status: query.$status,\n isPending: query.$isPending,\n isFetching: query.$isFetching,\n isSuccess: query.$isSuccess,\n isError: query.$isError,\n isPlaceholderData: query.$isPlaceholderData,\n fetchStatus: query.$fetchStatus,\n hasNextPage: query.$hasNextPage,\n hasPreviousPage: query.$hasPreviousPage,\n isFetchingNextPage: query.$isFetchingNextPage,\n isFetchingPreviousPage: query.$isFetchingPreviousPage,\n isFetchNextPageError: query.$isFetchNextPageError,\n isFetchPreviousPageError: query.$isFetchPreviousPageError,\n })\n\n const mount = useUnit(query.mounted)\n const unmount = useUnit(query.unmounted)\n const refresh = useUnit(query.refresh)\n const fetchNextPage = useUnit(query.fetchNextPage)\n const fetchPreviousPage = useUnit(query.fetchPreviousPage)\n\n React.useEffect(() => {\n mount()\n return () => unmount()\n }, [mount, unmount])\n\n return { ...state, refresh, fetchNextPage, fetchPreviousPage }\n}\n\n// Suspense data path: read from a per-scope observer.\n//\n// The scope mount chain runs in useEffect, which is skipped while a component\n// is suspended — so on the very first render the scope's `$observer` may be\n// null. To get synchronous access to the observer's promise during suspense,\n// we construct a transient observer via the factory's hidden\n// `__createObserver(qc, { queryKey, enabled })` helper. The transient observer\n// reads from / writes to the same queryClient cache as the eventual scope\n// observer (which is created when mountFx runs after useEffect commits).\n//\n// The mount/unmount effect is still wired up so that other consumers reading\n// the same query through `useUnit` / `useQuery` see updates in scope state.\n\nfunction useObserverRerender(\n observer: { subscribe: (cb: () => void) => () => void } | null,\n): void {\n const [, forceRender] = React.useReducer((x: number) => x + 1, 0)\n React.useEffect(() => {\n if (!observer) return\n return observer.subscribe(forceRender)\n }, [observer])\n}\n\ninterface SuspenseFactory<TObserver> {\n __createObserver(\n qc: import('@tanstack/query-core').QueryClient,\n init: { queryKey: unknown; enabled: boolean },\n ): TObserver\n __resolvedKey: import('effector').Store<unknown>\n __enabled: import('effector').Store<boolean>\n}\n\nexport interface UseSuspenseQueryResult<TData, TError = Error> {\n /** Resolved query data — non-nullable inside the rendered subtree (Suspense\n * absorbed the pending state). */\n data: TData\n /** Always `null` past the Suspense gate; errors are thrown to the nearest\n * `<ErrorBoundary>`. Typed as `TError | null` for consistency with\n * `useQuery` so the same destructure works in both. */\n error: TError | null\n status: 'success'\n isPending: false\n isSuccess: true\n isError: false\n /** `true` while a background refetch is running. Use for refresh spinners. */\n isFetching: boolean\n isPlaceholderData: boolean\n fetchStatus: FetchStatus\n refresh: () => void\n}\n\n/**\n * Reads a query for use inside a `<Suspense>` boundary. While the query is\n * pending, throws an inflight promise (queryClient-deduplicated). On error,\n * throws the error — catch with `<ErrorBoundary>`. Returns the same shape as\n * `useQuery`, but with `data` narrowed to non-nullable `TData` since the\n * pending state is impossible past the Suspense gate.\n */\nexport function useSuspenseQuery<TData, TError = Error>(\n query: QueryResult<TData, TError>,\n): UseSuspenseQueryResult<TData, TError> {\n // Auto-mount lifecycle so concurrent consumers (useUnit / useQuery) reading\n // the same query through the effector scope stay in sync.\n const mount = useUnit(query.mounted)\n const unmount = useUnit(query.unmounted)\n const refresh = useUnit(query.refresh)\n React.useEffect(() => {\n mount()\n return () => unmount()\n }, [mount, unmount])\n\n const observer = useSuspenseObserver(query)\n\n useObserverRerender(observer)\n\n const result = observer.getOptimisticResult(observer.options as any)\n\n if (result.status === 'error') throw result.error\n if (result.status === 'pending') {\n throw observer.fetchOptimistic(observer.options as any)\n }\n\n // Read all secondary fields from the observer result, not the effector\n // stores: stores are only populated after mountFx fires from useEffect,\n // which on the very first successful render hasn't run yet. The observer\n // result is always live and consistent.\n return {\n data: result.data as TData,\n error: result.error as TError | null,\n status: 'success',\n isPending: false,\n isSuccess: true,\n isError: false,\n isFetching: result.isFetching,\n isPlaceholderData: result.isPlaceholderData,\n fetchStatus: result.fetchStatus,\n refresh,\n }\n}\n\nexport interface UseSuspenseInfiniteQueryResult<TData, TError = Error> {\n data: TData\n error: TError | null\n status: 'success'\n isPending: false\n isSuccess: true\n isError: false\n isFetching: boolean\n isPlaceholderData: boolean\n fetchStatus: FetchStatus\n hasNextPage: boolean\n hasPreviousPage: boolean\n isFetchingNextPage: boolean\n isFetchingPreviousPage: boolean\n isFetchNextPageError: boolean\n isFetchPreviousPageError: boolean\n refresh: () => void\n fetchNextPage: () => void\n fetchPreviousPage: () => void\n}\n\n/**\n * Suspense variant of {@link useInfiniteQuery}. Same shape as `useInfiniteQuery`,\n * with `data` narrowed to non-nullable.\n */\nexport function useSuspenseInfiniteQuery<\n TData,\n TError = Error,\n TPageParam = unknown,\n>(\n query: InfiniteQueryResult<TData, TError, TPageParam>,\n): UseSuspenseInfiniteQueryResult<TData, TError> {\n const mount = useUnit(query.mounted)\n const unmount = useUnit(query.unmounted)\n const refresh = useUnit(query.refresh)\n const fetchNextPage = useUnit(query.fetchNextPage)\n const fetchPreviousPage = useUnit(query.fetchPreviousPage)\n React.useEffect(() => {\n mount()\n return () => unmount()\n }, [mount, unmount])\n\n const observer = useSuspenseObserver(query)\n\n useObserverRerender(observer)\n\n const result = observer.getOptimisticResult(observer.options as any)\n\n if (result.status === 'error') throw result.error\n if (result.status === 'pending') {\n throw (\n observer as unknown as {\n fetchOptimistic: (\n options: typeof observer.options,\n ) => Promise<unknown>\n }\n ).fetchOptimistic(observer.options)\n }\n\n const r = result as typeof result & {\n hasNextPage: boolean\n hasPreviousPage: boolean\n isFetchingNextPage: boolean\n isFetchingPreviousPage: boolean\n isFetchNextPageError: boolean\n isFetchPreviousPageError: boolean\n }\n\n return {\n data: r.data as TData,\n error: r.error as TError | null,\n status: 'success',\n isPending: false,\n isSuccess: true,\n isError: false,\n isFetching: r.isFetching,\n isPlaceholderData: r.isPlaceholderData,\n fetchStatus: r.fetchStatus,\n hasNextPage: r.hasNextPage,\n hasPreviousPage: r.hasPreviousPage,\n isFetchingNextPage: r.isFetchingNextPage,\n isFetchingPreviousPage: r.isFetchingPreviousPage,\n isFetchNextPageError: r.isFetchNextPageError,\n isFetchPreviousPageError: r.isFetchPreviousPageError,\n refresh,\n fetchNextPage,\n fetchPreviousPage,\n }\n}\n\n/**\n * Resolves a per-scope observer for suspense usage. Prefers the scope's\n * `$observer` (set by mountFx); falls back to a transient observer\n * constructed via `__createObserver` so that the very first render — before\n * useEffect has fired — has a working observer. Both flavors read/write the\n * same queryClient cache, so the transient observer is a thin wrapper.\n */\nfunction useSuspenseObserver<\n TQuery extends {\n $observer: import('effector').Store<TObserver | null>\n $queryClient: import('effector').Store<\n import('@tanstack/query-core').QueryClient | null\n >\n },\n TObserver extends {\n options: { queryKey: unknown }\n setOptions(options: any): void\n subscribe(cb: () => void): () => void\n getOptimisticResult(options: any): {\n status: 'pending' | 'success' | 'error'\n data: unknown\n error: unknown\n isFetching: boolean\n isPlaceholderData: boolean\n fetchStatus: FetchStatus\n // Infinite-query result fields — present at runtime when the underlying\n // observer is an InfiniteQueryObserver; the suspense hooks narrow as\n // needed. Typed as `any` here to keep the constraint loose.\n hasNextPage?: any\n hasPreviousPage?: any\n isFetchingNextPage?: any\n isFetchingPreviousPage?: any\n isFetchNextPageError?: any\n isFetchPreviousPageError?: any\n }\n fetchOptimistic(options: any): Promise<unknown>\n },\n>(query: TQuery): TObserver {\n const factory = query as unknown as TQuery & SuspenseFactory<TObserver>\n const observerInScope = useUnit(query.$observer) as TObserver | null\n const qc = useUnit(query.$queryClient)\n const queryKey = useUnit(factory.__resolvedKey)\n const enabled = useUnit(factory.__enabled)\n\n // Memoize a transient observer keyed by qc, so it survives across renders\n // while the scope observer is null. Once observerInScope appears, we\n // switch — the transient one is unsubscribed and abandoned (it never\n // subscribed to queryCache, so there is nothing to leak).\n const transient = React.useMemo(() => {\n if (observerInScope || !qc) return null\n return factory.__createObserver(qc, { queryKey, enabled })\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [observerInScope, qc, factory])\n\n // Keep the transient observer's options in sync with reactive key/enabled,\n // so re-suspending on key changes still works through it.\n React.useEffect(() => {\n if (!transient) return\n transient.setOptions({ ...transient.options, queryKey, enabled })\n }, [transient, queryKey, enabled])\n\n const observer = observerInScope ?? transient\n if (!observer) {\n throw new Error(\n '[@effector-tanstack-query/react] useSuspenseQuery: no QueryClient is set. ' +\n 'Call setQueryClient(qc) or pass it to fork({ values: [[$queryClient, qc]] }).',\n )\n }\n return observer\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["'use client'\n\nimport * as React from 'react'\nimport { useUnit } from 'effector-react'\nimport { hydrate } from '@tanstack/query-core'\nimport type {\n DehydratedState,\n FetchStatus,\n HydrateOptions,\n MutateOptions,\n QueryStatus,\n} from '@tanstack/query-core'\nimport { $queryClient } from '@effector-tanstack-query/core'\nimport type {\n InfiniteQueryResult,\n MutationResult,\n MutationStatus,\n QueryResult,\n} from '@effector-tanstack-query/core'\n\nexport interface HydrationBoundaryProps {\n /**\n * Snapshot produced by `dehydrate(queryClient)` on the server. Re-applied\n * to the scope's `QueryClient` cache so observers mounted under this tree\n * read prefetched data instead of triggering fresh network requests.\n */\n state?: DehydratedState\n /** Forwarded to `hydrate(...)` — see `@tanstack/query-core` docs. */\n options?: HydrateOptions\n children?: React.ReactNode\n}\n\n/**\n * Merges a server-prefetched `DehydratedState` into the scope's\n * `QueryClient` cache.\n *\n * Mirrors `<HydrationBoundary>` from `@tanstack/react-query`: hydration\n * runs in `useMemo` so the merge happens during the render phase (children\n * see a populated cache on their first render, no flash). The hook\n * resolves the QueryClient via `useUnit($queryClient)` instead of\n * `useQueryClient()` — meaning each fork scope can have its own client\n * without an additional `<QueryClientProvider>` in the tree.\n *\n * `hydrate` is idempotent: re-rendering with the same `state` reference\n * is a no-op. Pass new `state` references on navigation to merge fresh\n * snapshots.\n *\n * Note: this only handles the QueryClient cache layer. Effector store\n * snapshots (e.g. `serialize(scope)`) flow through your existing\n * `<Provider>` / `<EffectorNext values>` layer — orthogonal concerns.\n */\nexport function HydrationBoundary({\n state,\n options,\n children,\n}: HydrationBoundaryProps): React.ReactElement {\n const queryClient = useUnit($queryClient)\n React.useMemo(() => {\n if (queryClient && state) hydrate(queryClient, state, options)\n }, [queryClient, state, options])\n return React.createElement(React.Fragment, null, children)\n}\n\nexport interface UseQueryResult<TData, TError = Error> {\n data: TData | undefined\n error: TError | null\n status: QueryStatus\n isPending: boolean\n isFetching: boolean\n isSuccess: boolean\n isError: boolean\n isPlaceholderData: boolean\n fetchStatus: FetchStatus\n refresh: () => void\n}\n\n/**\n * Subscribes a React component to a query, automatically calling\n * `mounted()` on mount and `unmounted()` on cleanup.\n */\nexport function useQuery<TData, TError = Error>(\n query: QueryResult<TData, TError>,\n): UseQueryResult<TData, TError> {\n const state = useUnit({\n data: query.$data,\n error: query.$error,\n status: query.$status,\n isPending: query.$isPending,\n isFetching: query.$isFetching,\n isSuccess: query.$isSuccess,\n isError: query.$isError,\n isPlaceholderData: query.$isPlaceholderData,\n fetchStatus: query.$fetchStatus,\n })\n\n const mount = useUnit(query.mounted)\n const unmount = useUnit(query.unmounted)\n const refresh = useUnit(query.refresh)\n\n React.useEffect(() => {\n mount()\n return () => unmount()\n }, [mount, unmount])\n\n return { ...state, refresh }\n}\n\nexport interface UseMutationResult<TData, TError, TVariables> {\n data: TData | undefined\n error: TError | null\n status: MutationStatus\n variables: TVariables | undefined\n isPaused: boolean\n isPending: boolean\n isSuccess: boolean\n isError: boolean\n isIdle: boolean\n mutate: (variables: TVariables) => void\n /**\n * Trigger the mutation with per-call callbacks layered on top of the\n * observer-level ones (`onSuccess` / `onError` / `onSettled` in\n * `createMutation` options). Use this for component-local reactions\n * that don't fit module-level `sample` wiring — navigation after success,\n * one-shot toasts, etc.\n */\n mutateWith: (args: {\n variables: TVariables\n onSuccess?: MutateOptions<TData, TError, TVariables>['onSuccess']\n onError?: MutateOptions<TData, TError, TVariables>['onError']\n onSettled?: MutateOptions<TData, TError, TVariables>['onSettled']\n }) => void\n reset: () => void\n}\n\n/**\n * Subscribes a React component to a mutation, automatically calling\n * `start()` on mount and `unmounted()` on cleanup so the queryClient can\n * garbage-collect the mutation entry once no observers remain.\n */\nexport function useMutation<TData = unknown, TError = Error, TVariables = void>(\n mutation: MutationResult<TData, TError, TVariables>,\n): UseMutationResult<TData, TError, TVariables> {\n const state = useUnit({\n data: mutation.$data,\n error: mutation.$error,\n status: mutation.$status,\n variables: mutation.$variables,\n isPaused: mutation.$isPaused,\n isPending: mutation.$isPending,\n isSuccess: mutation.$isSuccess,\n isError: mutation.$isError,\n isIdle: mutation.$isIdle,\n })\n\n const start = useUnit(mutation.start)\n const unmount = useUnit(mutation.unmounted)\n const mutate = useUnit(mutation.mutate)\n const mutateWith = useUnit(mutation.mutateWith)\n const reset = useUnit(mutation.reset)\n\n React.useEffect(() => {\n start()\n return () => unmount()\n }, [start, unmount])\n\n return { ...state, mutate, mutateWith, reset }\n}\n\nexport interface UseInfiniteQueryResult<TData, TError> {\n data: TData | undefined\n error: TError | null\n status: QueryStatus\n isPending: boolean\n isFetching: boolean\n isSuccess: boolean\n isError: boolean\n isPlaceholderData: boolean\n fetchStatus: FetchStatus\n hasNextPage: boolean\n hasPreviousPage: boolean\n isFetchingNextPage: boolean\n isFetchingPreviousPage: boolean\n isFetchNextPageError: boolean\n isFetchPreviousPageError: boolean\n refresh: () => void\n fetchNextPage: () => void\n fetchPreviousPage: () => void\n}\n\n/**\n * Subscribes a React component to an infinite query, with auto mount/unmount\n * lifecycle and bound `fetchNextPage` / `fetchPreviousPage` callbacks.\n */\nexport function useInfiniteQuery<TData, TError = Error, TPageParam = unknown>(\n query: InfiniteQueryResult<TData, TError, TPageParam>,\n): UseInfiniteQueryResult<TData, TError> {\n const state = useUnit({\n data: query.$data,\n error: query.$error,\n status: query.$status,\n isPending: query.$isPending,\n isFetching: query.$isFetching,\n isSuccess: query.$isSuccess,\n isError: query.$isError,\n isPlaceholderData: query.$isPlaceholderData,\n fetchStatus: query.$fetchStatus,\n hasNextPage: query.$hasNextPage,\n hasPreviousPage: query.$hasPreviousPage,\n isFetchingNextPage: query.$isFetchingNextPage,\n isFetchingPreviousPage: query.$isFetchingPreviousPage,\n isFetchNextPageError: query.$isFetchNextPageError,\n isFetchPreviousPageError: query.$isFetchPreviousPageError,\n })\n\n const mount = useUnit(query.mounted)\n const unmount = useUnit(query.unmounted)\n const refresh = useUnit(query.refresh)\n const fetchNextPage = useUnit(query.fetchNextPage)\n const fetchPreviousPage = useUnit(query.fetchPreviousPage)\n\n React.useEffect(() => {\n mount()\n return () => unmount()\n }, [mount, unmount])\n\n return { ...state, refresh, fetchNextPage, fetchPreviousPage }\n}\n\n// Suspense data path: read from a per-scope observer.\n//\n// The scope mount chain runs in useEffect, which is skipped while a component\n// is suspended — so on the very first render the scope's `$observer` may be\n// null. To get synchronous access to the observer's promise during suspense,\n// we construct a transient observer via the factory's hidden\n// `__createObserver(qc, { queryKey, enabled })` helper. The transient observer\n// reads from / writes to the same queryClient cache as the eventual scope\n// observer (which is created when mountFx runs after useEffect commits).\n//\n// The mount/unmount effect is still wired up so that other consumers reading\n// the same query through `useUnit` / `useQuery` see updates in scope state.\n\nfunction useObserverRerender(observer: {\n subscribe: (cb: () => void) => () => void\n}): void {\n const [, forceRender] = React.useReducer((x: number) => x + 1, 0)\n React.useEffect(() => observer.subscribe(forceRender), [observer])\n}\n\ninterface SuspenseFactory<TObserver> {\n __createObserver(\n qc: import('@tanstack/query-core').QueryClient,\n init: { queryKey: unknown; enabled: boolean },\n ): TObserver\n __resolvedKey: import('effector').Store<unknown>\n __enabled: import('effector').Store<boolean>\n}\n\nexport interface UseSuspenseQueryResult<TData, TError = Error> {\n /** Resolved query data — non-nullable inside the rendered subtree (Suspense\n * absorbed the pending state). */\n data: TData\n /** Always `null` past the Suspense gate; errors are thrown to the nearest\n * `<ErrorBoundary>`. Typed as `TError | null` for consistency with\n * `useQuery` so the same destructure works in both. */\n error: TError | null\n status: 'success'\n isPending: false\n isSuccess: true\n isError: false\n /** `true` while a background refetch is running. Use for refresh spinners. */\n isFetching: boolean\n isPlaceholderData: boolean\n fetchStatus: FetchStatus\n refresh: () => void\n}\n\n/**\n * Reads a query for use inside a `<Suspense>` boundary. While the query is\n * pending, throws an inflight promise (queryClient-deduplicated). On error,\n * throws the error — catch with `<ErrorBoundary>`. Returns the same shape as\n * `useQuery`, but with `data` narrowed to non-nullable `TData` since the\n * pending state is impossible past the Suspense gate.\n */\nexport function useSuspenseQuery<TData, TError = Error>(\n query: QueryResult<TData, TError>,\n): UseSuspenseQueryResult<TData, TError> {\n // Auto-mount lifecycle so concurrent consumers (useUnit / useQuery) reading\n // the same query through the effector scope stay in sync.\n const mount = useUnit(query.mounted)\n const unmount = useUnit(query.unmounted)\n const refresh = useUnit(query.refresh)\n React.useEffect(() => {\n mount()\n return () => unmount()\n }, [mount, unmount])\n\n const observer = useSuspenseObserver(query)\n\n useObserverRerender(observer)\n\n const result = observer.getOptimisticResult(observer.options as any)\n\n if (result.status === 'error') throw result.error\n if (result.status === 'pending') {\n throw observer.fetchOptimistic(observer.options as any)\n }\n\n // Read all secondary fields from the observer result, not the effector\n // stores: stores are only populated after mountFx fires from useEffect,\n // which on the very first successful render hasn't run yet. The observer\n // result is always live and consistent.\n return {\n data: result.data as TData,\n error: result.error as TError | null,\n status: 'success',\n isPending: false,\n isSuccess: true,\n isError: false,\n isFetching: result.isFetching,\n isPlaceholderData: result.isPlaceholderData,\n fetchStatus: result.fetchStatus,\n refresh,\n }\n}\n\nexport interface UseSuspenseInfiniteQueryResult<TData, TError = Error> {\n data: TData\n error: TError | null\n status: 'success'\n isPending: false\n isSuccess: true\n isError: false\n isFetching: boolean\n isPlaceholderData: boolean\n fetchStatus: FetchStatus\n hasNextPage: boolean\n hasPreviousPage: boolean\n isFetchingNextPage: boolean\n isFetchingPreviousPage: boolean\n isFetchNextPageError: boolean\n isFetchPreviousPageError: boolean\n refresh: () => void\n fetchNextPage: () => void\n fetchPreviousPage: () => void\n}\n\n/**\n * Suspense variant of {@link useInfiniteQuery}. Same shape as `useInfiniteQuery`,\n * with `data` narrowed to non-nullable.\n */\nexport function useSuspenseInfiniteQuery<\n TData,\n TError = Error,\n TPageParam = unknown,\n>(\n query: InfiniteQueryResult<TData, TError, TPageParam>,\n): UseSuspenseInfiniteQueryResult<TData, TError> {\n const mount = useUnit(query.mounted)\n const unmount = useUnit(query.unmounted)\n const refresh = useUnit(query.refresh)\n const fetchNextPage = useUnit(query.fetchNextPage)\n const fetchPreviousPage = useUnit(query.fetchPreviousPage)\n React.useEffect(() => {\n mount()\n return () => unmount()\n }, [mount, unmount])\n\n const observer = useSuspenseObserver(query)\n\n useObserverRerender(observer)\n\n const result = observer.getOptimisticResult(observer.options as any)\n\n if (result.status === 'error') throw result.error\n if (result.status === 'pending') {\n throw (\n observer as unknown as {\n fetchOptimistic: (\n options: typeof observer.options,\n ) => Promise<unknown>\n }\n ).fetchOptimistic(observer.options)\n }\n\n const r = result as typeof result & {\n hasNextPage: boolean\n hasPreviousPage: boolean\n isFetchingNextPage: boolean\n isFetchingPreviousPage: boolean\n isFetchNextPageError: boolean\n isFetchPreviousPageError: boolean\n }\n\n return {\n data: r.data as TData,\n error: r.error as TError | null,\n status: 'success',\n isPending: false,\n isSuccess: true,\n isError: false,\n isFetching: r.isFetching,\n isPlaceholderData: r.isPlaceholderData,\n fetchStatus: r.fetchStatus,\n hasNextPage: r.hasNextPage,\n hasPreviousPage: r.hasPreviousPage,\n isFetchingNextPage: r.isFetchingNextPage,\n isFetchingPreviousPage: r.isFetchingPreviousPage,\n isFetchNextPageError: r.isFetchNextPageError,\n isFetchPreviousPageError: r.isFetchPreviousPageError,\n refresh,\n fetchNextPage,\n fetchPreviousPage,\n }\n}\n\n/**\n * Resolves a per-scope observer for suspense usage. Prefers the scope's\n * `$observer` (set by mountFx); falls back to a transient observer\n * constructed via `__createObserver` so that the very first render — before\n * useEffect has fired — has a working observer. Both flavors read/write the\n * same queryClient cache, so the transient observer is a thin wrapper.\n */\nfunction useSuspenseObserver<\n TQuery extends {\n $observer: import('effector').Store<TObserver | null>\n $queryClient: import('effector').Store<\n import('@tanstack/query-core').QueryClient | null\n >\n },\n TObserver extends {\n options: { queryKey: unknown }\n setOptions(options: any): void\n subscribe(cb: () => void): () => void\n getOptimisticResult(options: any): {\n status: 'pending' | 'success' | 'error'\n data: unknown\n error: unknown\n isFetching: boolean\n isPlaceholderData: boolean\n fetchStatus: FetchStatus\n // Infinite-query result fields — present at runtime when the underlying\n // observer is an InfiniteQueryObserver; the suspense hooks narrow as\n // needed. Typed as `any` here to keep the constraint loose.\n hasNextPage?: any\n hasPreviousPage?: any\n isFetchingNextPage?: any\n isFetchingPreviousPage?: any\n isFetchNextPageError?: any\n isFetchPreviousPageError?: any\n }\n fetchOptimistic(options: any): Promise<unknown>\n },\n>(query: TQuery): TObserver {\n const factory = query as unknown as TQuery & SuspenseFactory<TObserver>\n const observerInScope = useUnit(query.$observer) as TObserver | null\n const qc = useUnit(query.$queryClient)\n const queryKey = useUnit(factory.__resolvedKey)\n const enabled = useUnit(factory.__enabled)\n\n // Memoize a transient observer keyed by qc, so it survives across renders\n // while the scope observer is null. Once observerInScope appears, we\n // switch — the transient one is unsubscribed and abandoned (it never\n // subscribed to queryCache, so there is nothing to leak).\n const transient = React.useMemo(() => {\n if (observerInScope || !qc) return null\n return factory.__createObserver(qc, { queryKey, enabled })\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [observerInScope, qc, factory])\n\n // Keep the transient observer's options in sync with reactive key/enabled,\n // so re-suspending on key changes still works through it.\n React.useEffect(() => {\n if (!transient) return\n transient.setOptions({ ...transient.options, queryKey, enabled })\n }, [transient, queryKey, enabled])\n\n const observer = observerInScope ?? transient\n if (!observer) {\n throw new Error(\n '[@effector-tanstack-query/react] useSuspenseQuery: no QueryClient is set. ' +\n 'Call setQueryClient(qc) or pass it to fork({ values: [[$queryClient, qc]] }).',\n )\n }\n return observer\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effector-tanstack-query/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "React hooks for @effector-tanstack-query/core — useQuery, useMutation, useInfiniteQuery, useSuspenseQuery, useSuspenseInfiniteQuery",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ilya Agarkov <ilya.al.ag@gmail.com>",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
],
|
|
46
46
|
"sideEffects": false,
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@effector-tanstack-query/core": "0.
|
|
48
|
+
"@effector-tanstack-query/core": "0.2.0"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"@tanstack/query-core": "^5.0.0",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"react": "^18.0.0 || ^19.0.0"
|
|
55
55
|
},
|
|
56
56
|
"scripts": {
|
|
57
|
-
"build": "tsup",
|
|
57
|
+
"build": "tsup && node ./scripts/inject-use-client.mjs",
|
|
58
58
|
"build:watch": "tsup --watch",
|
|
59
59
|
"clean": "rm -rf ./dist ./coverage",
|
|
60
60
|
"test": "vitest --run",
|
package/src/index.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
1
3
|
import * as React from 'react'
|
|
2
4
|
import { useUnit } from 'effector-react'
|
|
5
|
+
import { hydrate } from '@tanstack/query-core'
|
|
3
6
|
import type {
|
|
7
|
+
DehydratedState,
|
|
4
8
|
FetchStatus,
|
|
9
|
+
HydrateOptions,
|
|
5
10
|
MutateOptions,
|
|
6
11
|
QueryStatus,
|
|
7
12
|
} from '@tanstack/query-core'
|
|
13
|
+
import { $queryClient } from '@effector-tanstack-query/core'
|
|
8
14
|
import type {
|
|
9
15
|
InfiniteQueryResult,
|
|
10
16
|
MutationResult,
|
|
@@ -12,6 +18,49 @@ import type {
|
|
|
12
18
|
QueryResult,
|
|
13
19
|
} from '@effector-tanstack-query/core'
|
|
14
20
|
|
|
21
|
+
export interface HydrationBoundaryProps {
|
|
22
|
+
/**
|
|
23
|
+
* Snapshot produced by `dehydrate(queryClient)` on the server. Re-applied
|
|
24
|
+
* to the scope's `QueryClient` cache so observers mounted under this tree
|
|
25
|
+
* read prefetched data instead of triggering fresh network requests.
|
|
26
|
+
*/
|
|
27
|
+
state?: DehydratedState
|
|
28
|
+
/** Forwarded to `hydrate(...)` — see `@tanstack/query-core` docs. */
|
|
29
|
+
options?: HydrateOptions
|
|
30
|
+
children?: React.ReactNode
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Merges a server-prefetched `DehydratedState` into the scope's
|
|
35
|
+
* `QueryClient` cache.
|
|
36
|
+
*
|
|
37
|
+
* Mirrors `<HydrationBoundary>` from `@tanstack/react-query`: hydration
|
|
38
|
+
* runs in `useMemo` so the merge happens during the render phase (children
|
|
39
|
+
* see a populated cache on their first render, no flash). The hook
|
|
40
|
+
* resolves the QueryClient via `useUnit($queryClient)` instead of
|
|
41
|
+
* `useQueryClient()` — meaning each fork scope can have its own client
|
|
42
|
+
* without an additional `<QueryClientProvider>` in the tree.
|
|
43
|
+
*
|
|
44
|
+
* `hydrate` is idempotent: re-rendering with the same `state` reference
|
|
45
|
+
* is a no-op. Pass new `state` references on navigation to merge fresh
|
|
46
|
+
* snapshots.
|
|
47
|
+
*
|
|
48
|
+
* Note: this only handles the QueryClient cache layer. Effector store
|
|
49
|
+
* snapshots (e.g. `serialize(scope)`) flow through your existing
|
|
50
|
+
* `<Provider>` / `<EffectorNext values>` layer — orthogonal concerns.
|
|
51
|
+
*/
|
|
52
|
+
export function HydrationBoundary({
|
|
53
|
+
state,
|
|
54
|
+
options,
|
|
55
|
+
children,
|
|
56
|
+
}: HydrationBoundaryProps): React.ReactElement {
|
|
57
|
+
const queryClient = useUnit($queryClient)
|
|
58
|
+
React.useMemo(() => {
|
|
59
|
+
if (queryClient && state) hydrate(queryClient, state, options)
|
|
60
|
+
}, [queryClient, state, options])
|
|
61
|
+
return React.createElement(React.Fragment, null, children)
|
|
62
|
+
}
|
|
63
|
+
|
|
15
64
|
export interface UseQueryResult<TData, TError = Error> {
|
|
16
65
|
data: TData | undefined
|
|
17
66
|
error: TError | null
|
|
@@ -190,14 +239,11 @@ export function useInfiniteQuery<TData, TError = Error, TPageParam = unknown>(
|
|
|
190
239
|
// The mount/unmount effect is still wired up so that other consumers reading
|
|
191
240
|
// the same query through `useUnit` / `useQuery` see updates in scope state.
|
|
192
241
|
|
|
193
|
-
function useObserverRerender(
|
|
194
|
-
|
|
195
|
-
): void {
|
|
242
|
+
function useObserverRerender(observer: {
|
|
243
|
+
subscribe: (cb: () => void) => () => void
|
|
244
|
+
}): void {
|
|
196
245
|
const [, forceRender] = React.useReducer((x: number) => x + 1, 0)
|
|
197
|
-
React.useEffect(() =>
|
|
198
|
-
if (!observer) return
|
|
199
|
-
return observer.subscribe(forceRender)
|
|
200
|
-
}, [observer])
|
|
246
|
+
React.useEffect(() => observer.subscribe(forceRender), [observer])
|
|
201
247
|
}
|
|
202
248
|
|
|
203
249
|
interface SuspenseFactory<TObserver> {
|