@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 +21 -0
- package/README.md +236 -0
- package/lib/analysis/index.js.html +5406 -0
- package/lib/index.js +489 -0
- package/lib/index.js.map +1 -0
- package/lib/types/index.d.ts +497 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index2.d.ts +298 -0
- package/lib/types/index2.d.ts.map +1 -0
- package/package.json +55 -0
- package/src/index.ts +69 -0
- package/src/query-client.ts +59 -0
- package/src/tests/query.test.ts +1768 -0
- package/src/use-infinite-query.ts +138 -0
- package/src/use-is-fetching.ts +44 -0
- package/src/use-mutation.ts +117 -0
- package/src/use-queries.ts +61 -0
- package/src/use-query-error-reset-boundary.ts +95 -0
- package/src/use-query.ts +106 -0
- package/src/use-suspense-query.ts +282 -0
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`.
|