@pyreon/query 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-present Vit Bokisch
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,236 @@
1
+ # @pyreon/query
2
+
3
+ Pyreon adapter for TanStack Query. Reactive `useQuery`, `useMutation`, `useInfiniteQuery`, and Suspense integration with fine-grained signal updates.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ bun add @pyreon/query @tanstack/query-core
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```tsx
14
+ import { QueryClient, QueryClientProvider, useQuery } from "@pyreon/query"
15
+
16
+ const queryClient = new QueryClient()
17
+
18
+ function UserProfile(props: { id: string }) {
19
+ const query = useQuery(() => ({
20
+ queryKey: ["user", props.id],
21
+ queryFn: () => fetch(`/api/users/${props.id}`).then(r => r.json()),
22
+ }))
23
+
24
+ return () => {
25
+ if (query.isLoading()) return <p>Loading...</p>
26
+ if (query.isError()) return <p>Error</p>
27
+ return <h1>{query.data()?.name}</h1>
28
+ }
29
+ }
30
+
31
+ const App = () => (
32
+ <QueryClientProvider client={queryClient}>
33
+ <UserProfile id="1" />
34
+ </QueryClientProvider>
35
+ )
36
+ ```
37
+
38
+ ## API
39
+
40
+ ### `QueryClientProvider`
41
+
42
+ Provide a `QueryClient` to the component tree.
43
+
44
+ | Parameter | Type | Description |
45
+ | --- | --- | --- |
46
+ | `client` | `QueryClient` | TanStack Query client instance |
47
+
48
+ ### `useQueryClient()`
49
+
50
+ Access the `QueryClient` from the nearest `QueryClientProvider`.
51
+
52
+ **Returns:** `QueryClient`
53
+
54
+ ### `useQuery(options)`
55
+
56
+ Subscribe to a query with fine-grained reactive signals. Options are passed as a function so reactive values (e.g. signal-based query keys) trigger automatic refetches.
57
+
58
+ | Parameter | Type | Description |
59
+ | --- | --- | --- |
60
+ | `options` | `() => QueryObserverOptions` | Function returning query options |
61
+
62
+ **Returns:** `UseQueryResult<TData, TError>` with:
63
+
64
+ | Property | Type | Description |
65
+ | --- | --- | --- |
66
+ | `result` | `Signal<QueryObserverResult>` | Full observer result |
67
+ | `data` | `Signal<TData \| undefined>` | Query data |
68
+ | `error` | `Signal<TError \| null>` | Query error |
69
+ | `status` | `Signal<"pending" \| "error" \| "success">` | Query status |
70
+ | `isPending` | `Signal<boolean>` | No data yet |
71
+ | `isLoading` | `Signal<boolean>` | First fetch in progress |
72
+ | `isFetching` | `Signal<boolean>` | Any fetch in progress |
73
+ | `isError` | `Signal<boolean>` | Query errored |
74
+ | `isSuccess` | `Signal<boolean>` | Query succeeded |
75
+ | `refetch()` | `() => Promise<QueryObserverResult>` | Trigger manual refetch |
76
+
77
+ ```ts
78
+ const userId = signal(1)
79
+ const query = useQuery(() => ({
80
+ queryKey: ["user", userId()],
81
+ queryFn: () => fetchUser(userId()),
82
+ }))
83
+ // Changing userId triggers automatic refetch
84
+ ```
85
+
86
+ ### `useMutation(options)`
87
+
88
+ Run a mutation with reactive status signals.
89
+
90
+ | Parameter | Type | Description |
91
+ | --- | --- | --- |
92
+ | `options` | `MutationObserverOptions` | Mutation config |
93
+
94
+ **Returns:** `UseMutationResult<TData, TError, TVariables, TContext>` with:
95
+
96
+ | Property | Type | Description |
97
+ | --- | --- | --- |
98
+ | `data` | `Signal<TData \| undefined>` | Mutation result |
99
+ | `error` | `Signal<TError \| null>` | Mutation error |
100
+ | `status` | `Signal<"idle" \| "pending" \| "success" \| "error">` | Status |
101
+ | `isPending` | `Signal<boolean>` | Mutation in progress |
102
+ | `isSuccess` | `Signal<boolean>` | Mutation succeeded |
103
+ | `isError` | `Signal<boolean>` | Mutation errored |
104
+ | `isIdle` | `Signal<boolean>` | Not yet fired |
105
+ | `mutate(vars, opts?)` | `Function` | Fire-and-forget mutation |
106
+ | `mutateAsync(vars, opts?)` | `Function` | Promise-returning mutation |
107
+ | `reset()` | `() => void` | Reset to idle state |
108
+
109
+ ```ts
110
+ const mutation = useMutation({
111
+ mutationFn: (data: { title: string }) =>
112
+ fetch("/api/posts", { method: "POST", body: JSON.stringify(data) }).then(r => r.json()),
113
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ["posts"] }),
114
+ })
115
+
116
+ mutation.mutate({ title: "New Post" })
117
+ ```
118
+
119
+ ### `useInfiniteQuery(options)`
120
+
121
+ Paginated/infinite query with the same fine-grained signal pattern as `useQuery`.
122
+
123
+ | Parameter | Type | Description |
124
+ | --- | --- | --- |
125
+ | `options` | `() => InfiniteQueryObserverOptions` | Function returning options |
126
+
127
+ **Returns:** `UseInfiniteQueryResult<TData, TError>` — same shape as `UseQueryResult`.
128
+
129
+ ### `useQueries(options)`
130
+
131
+ Run multiple queries in parallel.
132
+
133
+ | Parameter | Type | Description |
134
+ | --- | --- | --- |
135
+ | `options` | `UseQueriesOptions` | Array of query configs |
136
+
137
+ **Returns:** Array of `UseQueryResult` objects.
138
+
139
+ ### `useSuspenseQuery(options)` / `useSuspenseInfiniteQuery(options)`
140
+
141
+ Suspense-enabled queries. Data is guaranteed non-undefined after the suspense boundary resolves.
142
+
143
+ | Property | Type | Description |
144
+ | --- | --- | --- |
145
+ | `data` | `Signal<TData>` | Always defined (non-undefined) |
146
+
147
+ ```tsx
148
+ function UserList() {
149
+ const query = useSuspenseQuery({
150
+ queryKey: ["users"],
151
+ queryFn: fetchUsers,
152
+ })
153
+ return () => (
154
+ <ul>
155
+ {query.data().map(u => <li>{u.name}</li>)}
156
+ </ul>
157
+ )
158
+ }
159
+ ```
160
+
161
+ ### `QuerySuspense`
162
+
163
+ Suspense wrapper component with built-in error handling.
164
+
165
+ | Parameter | Type | Description |
166
+ | --- | --- | --- |
167
+ | `fallback` | `VNodeChild` | Loading fallback |
168
+ | `children` | `VNodeChild` | Content |
169
+
170
+ ```tsx
171
+ <QuerySuspense fallback={<p>Loading...</p>}>
172
+ <UserList />
173
+ </QuerySuspense>
174
+ ```
175
+
176
+ ### `QueryErrorResetBoundary` / `useQueryErrorResetBoundary()`
177
+
178
+ Error boundary for resetting query errors on retry.
179
+
180
+ ### `useIsFetching(filters?)` / `useIsMutating(filters?)`
181
+
182
+ Global counters of active queries/mutations as reactive signals.
183
+
184
+ | Parameter | Type | Description |
185
+ | --- | --- | --- |
186
+ | `filters` | `QueryFilters` / `MutationFilters` | Optional filters to narrow scope |
187
+
188
+ **Returns:** `Signal<number>`
189
+
190
+ ```ts
191
+ const fetching = useIsFetching()
192
+ // fetching() => number of active queries
193
+ ```
194
+
195
+ ## Patterns
196
+
197
+ ### SSR Dehydration
198
+
199
+ ```ts
200
+ import { QueryClient, dehydrate, hydrate } from "@pyreon/query"
201
+
202
+ // Server: prefetch and serialize
203
+ const queryClient = new QueryClient()
204
+ await queryClient.prefetchQuery({ queryKey: ["users"], queryFn: fetchUsers })
205
+ const dehydratedState = dehydrate(queryClient)
206
+
207
+ // Client: restore cache
208
+ hydrate(queryClient, dehydratedState)
209
+ ```
210
+
211
+ ### Reactive Query Keys
212
+
213
+ Options are a function, so reading signals inside auto-tracks dependencies.
214
+
215
+ ```ts
216
+ const filter = signal("active")
217
+ const query = useQuery(() => ({
218
+ queryKey: ["todos", filter()],
219
+ queryFn: () => fetchTodos(filter()),
220
+ }))
221
+ // Changing filter() triggers a new fetch
222
+ ```
223
+
224
+ ## Re-exports from `@tanstack/query-core`
225
+
226
+ **Runtime:** `QueryClient`, `QueryCache`, `MutationCache`, `dehydrate`, `hydrate`, `keepPreviousData`, `hashKey`, `isCancelledError`, `CancelledError`, `defaultShouldDehydrateQuery`, `defaultShouldDehydrateMutation`
227
+
228
+ **Types:** `QueryKey`, `QueryFilters`, `MutationFilters`, `DehydratedState`, `FetchQueryOptions`, `InvalidateQueryFilters`, `InvalidateOptions`, `RefetchQueryFilters`, `RefetchOptions`, `QueryClientConfig`
229
+
230
+ ## Gotchas
231
+
232
+ - Each field on `UseQueryResult` is an independent signal. Reading `query.data()` does not re-run when `isFetching` changes, and vice versa.
233
+ - `useQuery` options must be a function `() => opts`, not a plain object. This is required for reactive option tracking.
234
+ - `useMutation` options are a plain object (not a function) since mutations are imperative.
235
+ - `mutate()` swallows errors into the `error` signal. Use `mutateAsync()` if you need try/catch.
236
+ - Observer subscriptions are cleaned up automatically on component unmount via `onUnmount`.