@pyreon/query 0.2.0 → 0.4.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/lib/analysis/index.js.html +1 -1
- package/lib/index.js +270 -139
- package/lib/index.js.map +1 -1
- package/lib/types/index.d.ts +278 -147
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index2.d.ts +162 -104
- package/lib/types/index2.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +42 -44
- package/src/query-client.ts +3 -4
- package/src/tests/{query.test.ts → query.test.tsx} +254 -353
- package/src/tests/subscription.test.tsx +581 -0
- package/src/use-infinite-query.ts +2 -2
- package/src/use-is-fetching.ts +1 -1
- package/src/use-mutation.ts +2 -2
- package/src/use-queries.ts +2 -2
- package/src/use-query-error-reset-boundary.ts +3 -4
- package/src/use-query.ts +2 -2
- package/src/use-subscription.ts +226 -0
- package/src/use-suspense-query.ts +3 -3
|
@@ -5386,7 +5386,7 @@ var drawChart = (function (exports) {
|
|
|
5386
5386
|
</script>
|
|
5387
5387
|
<script>
|
|
5388
5388
|
/*<!--*/
|
|
5389
|
-
const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"
|
|
5389
|
+
const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"56f0aa60-1","name":"query-client.ts"},{"uid":"56f0aa60-3","name":"use-infinite-query.ts"},{"uid":"56f0aa60-5","name":"use-is-fetching.ts"},{"uid":"56f0aa60-7","name":"use-mutation.ts"},{"uid":"56f0aa60-9","name":"use-queries.ts"},{"uid":"56f0aa60-11","name":"use-query.ts"},{"uid":"56f0aa60-13","name":"use-query-error-reset-boundary.ts"},{"uid":"56f0aa60-15","name":"use-subscription.ts"},{"uid":"56f0aa60-17","name":"use-suspense-query.ts"},{"uid":"56f0aa60-19","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"56f0aa60-1":{"renderedLength":989,"gzipLength":502,"brotliLength":0,"metaUid":"56f0aa60-0"},"56f0aa60-3":{"renderedLength":2354,"gzipLength":813,"brotliLength":0,"metaUid":"56f0aa60-2"},"56f0aa60-5":{"renderedLength":1002,"gzipLength":397,"brotliLength":0,"metaUid":"56f0aa60-4"},"56f0aa60-7":{"renderedLength":1705,"gzipLength":734,"brotliLength":0,"metaUid":"56f0aa60-6"},"56f0aa60-9":{"renderedLength":929,"gzipLength":504,"brotliLength":0,"metaUid":"56f0aa60-8"},"56f0aa60-11":{"renderedLength":1734,"gzipLength":732,"brotliLength":0,"metaUid":"56f0aa60-10"},"56f0aa60-13":{"renderedLength":1681,"gzipLength":672,"brotliLength":0,"metaUid":"56f0aa60-12"},"56f0aa60-15":{"renderedLength":3571,"gzipLength":1163,"brotliLength":0,"metaUid":"56f0aa60-14"},"56f0aa60-17":{"renderedLength":4400,"gzipLength":1231,"brotliLength":0,"metaUid":"56f0aa60-16"},"56f0aa60-19":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"56f0aa60-18"}},"nodeMetas":{"56f0aa60-0":{"id":"/src/query-client.ts","moduleParts":{"index.js":"56f0aa60-1"},"imported":[{"uid":"56f0aa60-21"}],"importedBy":[{"uid":"56f0aa60-18"},{"uid":"56f0aa60-2"},{"uid":"56f0aa60-4"},{"uid":"56f0aa60-6"},{"uid":"56f0aa60-8"},{"uid":"56f0aa60-10"},{"uid":"56f0aa60-12"},{"uid":"56f0aa60-14"},{"uid":"56f0aa60-16"}]},"56f0aa60-2":{"id":"/src/use-infinite-query.ts","moduleParts":{"index.js":"56f0aa60-3"},"imported":[{"uid":"56f0aa60-21"},{"uid":"56f0aa60-22"},{"uid":"56f0aa60-20"},{"uid":"56f0aa60-0"}],"importedBy":[{"uid":"56f0aa60-18"}]},"56f0aa60-4":{"id":"/src/use-is-fetching.ts","moduleParts":{"index.js":"56f0aa60-5"},"imported":[{"uid":"56f0aa60-21"},{"uid":"56f0aa60-22"},{"uid":"56f0aa60-0"}],"importedBy":[{"uid":"56f0aa60-18"}]},"56f0aa60-6":{"id":"/src/use-mutation.ts","moduleParts":{"index.js":"56f0aa60-7"},"imported":[{"uid":"56f0aa60-21"},{"uid":"56f0aa60-22"},{"uid":"56f0aa60-20"},{"uid":"56f0aa60-0"}],"importedBy":[{"uid":"56f0aa60-18"}]},"56f0aa60-8":{"id":"/src/use-queries.ts","moduleParts":{"index.js":"56f0aa60-9"},"imported":[{"uid":"56f0aa60-21"},{"uid":"56f0aa60-22"},{"uid":"56f0aa60-20"},{"uid":"56f0aa60-0"}],"importedBy":[{"uid":"56f0aa60-18"}]},"56f0aa60-10":{"id":"/src/use-query.ts","moduleParts":{"index.js":"56f0aa60-11"},"imported":[{"uid":"56f0aa60-21"},{"uid":"56f0aa60-22"},{"uid":"56f0aa60-20"},{"uid":"56f0aa60-0"}],"importedBy":[{"uid":"56f0aa60-18"}]},"56f0aa60-12":{"id":"/src/use-query-error-reset-boundary.ts","moduleParts":{"index.js":"56f0aa60-13"},"imported":[{"uid":"56f0aa60-21"},{"uid":"56f0aa60-0"}],"importedBy":[{"uid":"56f0aa60-18"}]},"56f0aa60-14":{"id":"/src/use-subscription.ts","moduleParts":{"index.js":"56f0aa60-15"},"imported":[{"uid":"56f0aa60-21"},{"uid":"56f0aa60-22"},{"uid":"56f0aa60-0"}],"importedBy":[{"uid":"56f0aa60-18"}]},"56f0aa60-16":{"id":"/src/use-suspense-query.ts","moduleParts":{"index.js":"56f0aa60-17"},"imported":[{"uid":"56f0aa60-21"},{"uid":"56f0aa60-22"},{"uid":"56f0aa60-20"},{"uid":"56f0aa60-0"}],"importedBy":[{"uid":"56f0aa60-18"}]},"56f0aa60-18":{"id":"/src/index.ts","moduleParts":{"index.js":"56f0aa60-19"},"imported":[{"uid":"56f0aa60-20"},{"uid":"56f0aa60-0"},{"uid":"56f0aa60-2"},{"uid":"56f0aa60-4"},{"uid":"56f0aa60-6"},{"uid":"56f0aa60-8"},{"uid":"56f0aa60-10"},{"uid":"56f0aa60-12"},{"uid":"56f0aa60-14"},{"uid":"56f0aa60-16"}],"importedBy":[],"isEntry":true},"56f0aa60-20":{"id":"@tanstack/query-core","moduleParts":{},"imported":[],"importedBy":[{"uid":"56f0aa60-18"},{"uid":"56f0aa60-2"},{"uid":"56f0aa60-6"},{"uid":"56f0aa60-8"},{"uid":"56f0aa60-10"},{"uid":"56f0aa60-16"}]},"56f0aa60-21":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"56f0aa60-0"},{"uid":"56f0aa60-2"},{"uid":"56f0aa60-4"},{"uid":"56f0aa60-6"},{"uid":"56f0aa60-8"},{"uid":"56f0aa60-10"},{"uid":"56f0aa60-12"},{"uid":"56f0aa60-14"},{"uid":"56f0aa60-16"}]},"56f0aa60-22":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"56f0aa60-2"},{"uid":"56f0aa60-4"},{"uid":"56f0aa60-6"},{"uid":"56f0aa60-8"},{"uid":"56f0aa60-10"},{"uid":"56f0aa60-14"},{"uid":"56f0aa60-16"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
|
|
5390
5390
|
|
|
5391
5391
|
const run = () => {
|
|
5392
5392
|
const width = window.innerWidth;
|
package/lib/index.js
CHANGED
|
@@ -33,24 +33,24 @@ function useQueryClient() {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
//#endregion
|
|
36
|
-
//#region src/use-query.ts
|
|
36
|
+
//#region src/use-infinite-query.ts
|
|
37
37
|
/**
|
|
38
|
-
* Subscribe to a
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* `options` is a function so it can read Pyreon signals — when a signal changes
|
|
42
|
-
* (e.g. a reactive query key), the observer is updated and refetches automatically.
|
|
38
|
+
* Subscribe to a paginated / infinite-scroll query.
|
|
39
|
+
* Returns fine-grained reactive signals plus `fetchNextPage`, `fetchPreviousPage`,
|
|
40
|
+
* `hasNextPage` and `hasPreviousPage`.
|
|
43
41
|
*
|
|
44
42
|
* @example
|
|
45
|
-
* const
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
43
|
+
* const query = useInfiniteQuery(() => ({
|
|
44
|
+
* queryKey: ['posts'],
|
|
45
|
+
* queryFn: ({ pageParam }) => fetchPosts(pageParam as number),
|
|
46
|
+
* initialPageParam: 0,
|
|
47
|
+
* getNextPageParam: (lastPage) => lastPage.nextCursor,
|
|
49
48
|
* }))
|
|
50
|
-
* //
|
|
49
|
+
* // query.data()?.pages — array of pages
|
|
50
|
+
* // h('button', { onClick: () => query.fetchNextPage() }, 'Load more')
|
|
51
51
|
*/
|
|
52
|
-
function
|
|
53
|
-
const observer = new
|
|
52
|
+
function useInfiniteQuery(options) {
|
|
53
|
+
const observer = new InfiniteQueryObserver(useQueryClient(), options());
|
|
54
54
|
const initial = observer.getCurrentResult();
|
|
55
55
|
const resultSig = signal(initial);
|
|
56
56
|
const dataSig = signal(initial.data);
|
|
@@ -59,8 +59,12 @@ function useQuery(options) {
|
|
|
59
59
|
const isPending = signal(initial.isPending);
|
|
60
60
|
const isLoading = signal(initial.isLoading);
|
|
61
61
|
const isFetching = signal(initial.isFetching);
|
|
62
|
+
const isFetchingNextPage = signal(initial.isFetchingNextPage);
|
|
63
|
+
const isFetchingPreviousPage = signal(initial.isFetchingPreviousPage);
|
|
62
64
|
const isError = signal(initial.isError);
|
|
63
65
|
const isSuccess = signal(initial.isSuccess);
|
|
66
|
+
const hasNextPage = signal(initial.hasNextPage);
|
|
67
|
+
const hasPreviousPage = signal(initial.hasPreviousPage);
|
|
64
68
|
const unsub = observer.subscribe((r) => {
|
|
65
69
|
batch(() => {
|
|
66
70
|
resultSig.set(r);
|
|
@@ -70,8 +74,12 @@ function useQuery(options) {
|
|
|
70
74
|
isPending.set(r.isPending);
|
|
71
75
|
isLoading.set(r.isLoading);
|
|
72
76
|
isFetching.set(r.isFetching);
|
|
77
|
+
isFetchingNextPage.set(r.isFetchingNextPage);
|
|
78
|
+
isFetchingPreviousPage.set(r.isFetchingPreviousPage);
|
|
73
79
|
isError.set(r.isError);
|
|
74
80
|
isSuccess.set(r.isSuccess);
|
|
81
|
+
hasNextPage.set(r.hasNextPage);
|
|
82
|
+
hasPreviousPage.set(r.hasPreviousPage);
|
|
75
83
|
});
|
|
76
84
|
});
|
|
77
85
|
effect(() => {
|
|
@@ -86,12 +94,54 @@ function useQuery(options) {
|
|
|
86
94
|
isPending,
|
|
87
95
|
isLoading,
|
|
88
96
|
isFetching,
|
|
97
|
+
isFetchingNextPage,
|
|
98
|
+
isFetchingPreviousPage,
|
|
89
99
|
isError,
|
|
90
100
|
isSuccess,
|
|
101
|
+
hasNextPage,
|
|
102
|
+
hasPreviousPage,
|
|
103
|
+
fetchNextPage: () => observer.fetchNextPage(),
|
|
104
|
+
fetchPreviousPage: () => observer.fetchPreviousPage(),
|
|
91
105
|
refetch: () => observer.refetch()
|
|
92
106
|
};
|
|
93
107
|
}
|
|
94
108
|
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region src/use-is-fetching.ts
|
|
111
|
+
/**
|
|
112
|
+
* Returns a signal that tracks how many queries are currently in-flight.
|
|
113
|
+
* Useful for global loading indicators.
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* const fetching = useIsFetching()
|
|
117
|
+
* // h('span', null, () => fetching() > 0 ? 'Loading…' : '')
|
|
118
|
+
*/
|
|
119
|
+
function useIsFetching(filters) {
|
|
120
|
+
const client = useQueryClient();
|
|
121
|
+
const count = signal(client.isFetching(filters));
|
|
122
|
+
const unsub = client.getQueryCache().subscribe(() => {
|
|
123
|
+
count.set(client.isFetching(filters));
|
|
124
|
+
});
|
|
125
|
+
onUnmount(() => unsub());
|
|
126
|
+
return count;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Returns a signal that tracks how many mutations are currently in-flight.
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* const mutating = useIsMutating()
|
|
133
|
+
* // h('span', null, () => mutating() > 0 ? 'Saving…' : '')
|
|
134
|
+
*/
|
|
135
|
+
function useIsMutating(filters) {
|
|
136
|
+
const client = useQueryClient();
|
|
137
|
+
const count = signal(client.isMutating(filters));
|
|
138
|
+
const unsub = client.getMutationCache().subscribe(() => {
|
|
139
|
+
count.set(client.isMutating(filters));
|
|
140
|
+
});
|
|
141
|
+
onUnmount(() => unsub());
|
|
142
|
+
return count;
|
|
143
|
+
}
|
|
144
|
+
|
|
95
145
|
//#endregion
|
|
96
146
|
//#region src/use-mutation.ts
|
|
97
147
|
/**
|
|
@@ -148,24 +198,60 @@ function useMutation(options) {
|
|
|
148
198
|
}
|
|
149
199
|
|
|
150
200
|
//#endregion
|
|
151
|
-
//#region src/use-
|
|
201
|
+
//#region src/use-queries.ts
|
|
152
202
|
/**
|
|
153
|
-
* Subscribe to a
|
|
154
|
-
*
|
|
155
|
-
*
|
|
203
|
+
* Subscribe to multiple queries in parallel. Returns a single signal containing
|
|
204
|
+
* the array of results — index-aligned with the `queries` array.
|
|
205
|
+
*
|
|
206
|
+
* `queries` is a reactive function so signal-based keys trigger re-evaluation
|
|
207
|
+
* automatically.
|
|
156
208
|
*
|
|
157
209
|
* @example
|
|
158
|
-
* const
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
210
|
+
* const userIds = signal([1, 2, 3])
|
|
211
|
+
* const results = useQueries(() =>
|
|
212
|
+
* userIds().map(id => ({
|
|
213
|
+
* queryKey: ['user', id],
|
|
214
|
+
* queryFn: () => fetchUser(id),
|
|
215
|
+
* }))
|
|
216
|
+
* )
|
|
217
|
+
* // results() — QueryObserverResult[]
|
|
218
|
+
* // results()[0].data — first user
|
|
219
|
+
*/
|
|
220
|
+
function useQueries(queries) {
|
|
221
|
+
const observer = new QueriesObserver(useQueryClient(), queries());
|
|
222
|
+
const resultSig = signal(observer.getCurrentResult());
|
|
223
|
+
const unsub = observer.subscribe((results) => {
|
|
224
|
+
resultSig.set(results);
|
|
225
|
+
});
|
|
226
|
+
effect(() => {
|
|
227
|
+
observer.setQueries(queries());
|
|
228
|
+
});
|
|
229
|
+
onUnmount(() => {
|
|
230
|
+
unsub();
|
|
231
|
+
observer.destroy();
|
|
232
|
+
});
|
|
233
|
+
return resultSig;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
//#endregion
|
|
237
|
+
//#region src/use-query.ts
|
|
238
|
+
/**
|
|
239
|
+
* Subscribe to a query. Returns fine-grained reactive signals for data,
|
|
240
|
+
* error and status — each signal only notifies effects that depend on it.
|
|
241
|
+
*
|
|
242
|
+
* `options` is a function so it can read Pyreon signals — when a signal changes
|
|
243
|
+
* (e.g. a reactive query key), the observer is updated and refetches automatically.
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* const userId = signal(1)
|
|
247
|
+
* const query = useQuery(() => ({
|
|
248
|
+
* queryKey: ['user', userId()],
|
|
249
|
+
* queryFn: () => fetch(`/api/users/${userId()}`).then(r => r.json()),
|
|
163
250
|
* }))
|
|
164
|
-
* // query.data()?.
|
|
165
|
-
* // h('button', { onClick: () => query.fetchNextPage() }, 'Load more')
|
|
251
|
+
* // In template: () => query.data()?.name
|
|
166
252
|
*/
|
|
167
|
-
function
|
|
168
|
-
const observer = new
|
|
253
|
+
function useQuery(options) {
|
|
254
|
+
const observer = new QueryObserver(useQueryClient(), options());
|
|
169
255
|
const initial = observer.getCurrentResult();
|
|
170
256
|
const resultSig = signal(initial);
|
|
171
257
|
const dataSig = signal(initial.data);
|
|
@@ -174,12 +260,8 @@ function useInfiniteQuery(options) {
|
|
|
174
260
|
const isPending = signal(initial.isPending);
|
|
175
261
|
const isLoading = signal(initial.isLoading);
|
|
176
262
|
const isFetching = signal(initial.isFetching);
|
|
177
|
-
const isFetchingNextPage = signal(initial.isFetchingNextPage);
|
|
178
|
-
const isFetchingPreviousPage = signal(initial.isFetchingPreviousPage);
|
|
179
263
|
const isError = signal(initial.isError);
|
|
180
264
|
const isSuccess = signal(initial.isSuccess);
|
|
181
|
-
const hasNextPage = signal(initial.hasNextPage);
|
|
182
|
-
const hasPreviousPage = signal(initial.hasPreviousPage);
|
|
183
265
|
const unsub = observer.subscribe((r) => {
|
|
184
266
|
batch(() => {
|
|
185
267
|
resultSig.set(r);
|
|
@@ -189,12 +271,8 @@ function useInfiniteQuery(options) {
|
|
|
189
271
|
isPending.set(r.isPending);
|
|
190
272
|
isLoading.set(r.isLoading);
|
|
191
273
|
isFetching.set(r.isFetching);
|
|
192
|
-
isFetchingNextPage.set(r.isFetchingNextPage);
|
|
193
|
-
isFetchingPreviousPage.set(r.isFetchingPreviousPage);
|
|
194
274
|
isError.set(r.isError);
|
|
195
275
|
isSuccess.set(r.isSuccess);
|
|
196
|
-
hasNextPage.set(r.hasNextPage);
|
|
197
|
-
hasPreviousPage.set(r.hasPreviousPage);
|
|
198
276
|
});
|
|
199
277
|
});
|
|
200
278
|
effect(() => {
|
|
@@ -209,88 +287,191 @@ function useInfiniteQuery(options) {
|
|
|
209
287
|
isPending,
|
|
210
288
|
isLoading,
|
|
211
289
|
isFetching,
|
|
212
|
-
isFetchingNextPage,
|
|
213
|
-
isFetchingPreviousPage,
|
|
214
290
|
isError,
|
|
215
291
|
isSuccess,
|
|
216
|
-
hasNextPage,
|
|
217
|
-
hasPreviousPage,
|
|
218
|
-
fetchNextPage: () => observer.fetchNextPage(),
|
|
219
|
-
fetchPreviousPage: () => observer.fetchPreviousPage(),
|
|
220
292
|
refetch: () => observer.refetch()
|
|
221
293
|
};
|
|
222
294
|
}
|
|
223
295
|
|
|
224
296
|
//#endregion
|
|
225
|
-
//#region src/use-
|
|
297
|
+
//#region src/use-query-error-reset-boundary.ts
|
|
298
|
+
const QueryErrorResetBoundaryContext = createContext(null);
|
|
226
299
|
/**
|
|
227
|
-
*
|
|
228
|
-
*
|
|
300
|
+
* Wraps a subtree so that `useQueryErrorResetBoundary()` descendants can reset
|
|
301
|
+
* all errored queries within this boundary.
|
|
302
|
+
*
|
|
303
|
+
* Pair with Pyreon's `ErrorBoundary` to retry failed queries when the user
|
|
304
|
+
* dismisses the error fallback:
|
|
229
305
|
*
|
|
230
306
|
* @example
|
|
231
|
-
*
|
|
232
|
-
*
|
|
307
|
+
* h(QueryErrorResetBoundary, null,
|
|
308
|
+
* h(ErrorBoundary, {
|
|
309
|
+
* fallback: (err, boundaryReset) => {
|
|
310
|
+
* const { reset } = useQueryErrorResetBoundary()
|
|
311
|
+
* return h('button', {
|
|
312
|
+
* onClick: () => { reset(); boundaryReset() },
|
|
313
|
+
* }, 'Retry')
|
|
314
|
+
* },
|
|
315
|
+
* }, h(MyComponent, null)),
|
|
316
|
+
* )
|
|
233
317
|
*/
|
|
234
|
-
function
|
|
318
|
+
function QueryErrorResetBoundary(props) {
|
|
235
319
|
const client = useQueryClient();
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
return
|
|
320
|
+
pushContext(new Map([[QueryErrorResetBoundaryContext.id, { reset: () => {
|
|
321
|
+
client.refetchQueries({ predicate: (query) => query.state.status === "error" });
|
|
322
|
+
} }]]));
|
|
323
|
+
onUnmount(() => popContext());
|
|
324
|
+
const ch = props.children;
|
|
325
|
+
return typeof ch === "function" ? ch() : ch;
|
|
242
326
|
}
|
|
243
327
|
/**
|
|
244
|
-
* Returns
|
|
328
|
+
* Returns the `reset` function provided by the nearest `QueryErrorResetBoundary`.
|
|
329
|
+
* If called outside a boundary, falls back to resetting all errored queries
|
|
330
|
+
* on the current `QueryClient`.
|
|
245
331
|
*
|
|
246
332
|
* @example
|
|
247
|
-
*
|
|
248
|
-
*
|
|
333
|
+
* // Inside an ErrorBoundary fallback:
|
|
334
|
+
* const { reset } = useQueryErrorResetBoundary()
|
|
335
|
+
* h('button', { onClick: () => { reset(); boundaryReset() } }, 'Retry')
|
|
249
336
|
*/
|
|
250
|
-
function
|
|
337
|
+
function useQueryErrorResetBoundary() {
|
|
338
|
+
const boundary = useContext(QueryErrorResetBoundaryContext);
|
|
251
339
|
const client = useQueryClient();
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
onUnmount(() => unsub());
|
|
257
|
-
return count;
|
|
340
|
+
if (boundary) return boundary;
|
|
341
|
+
return { reset: () => {
|
|
342
|
+
client.refetchQueries({ predicate: (query) => query.state.status === "error" });
|
|
343
|
+
} };
|
|
258
344
|
}
|
|
259
345
|
|
|
260
346
|
//#endregion
|
|
261
|
-
//#region src/use-
|
|
347
|
+
//#region src/use-subscription.ts
|
|
262
348
|
/**
|
|
263
|
-
*
|
|
264
|
-
*
|
|
349
|
+
* Reactive WebSocket subscription that integrates with TanStack Query.
|
|
350
|
+
* Automatically manages connection lifecycle, reconnection, and cleanup.
|
|
265
351
|
*
|
|
266
|
-
* `
|
|
267
|
-
*
|
|
352
|
+
* Use the `onMessage` callback to invalidate or update query cache
|
|
353
|
+
* when the server pushes data.
|
|
268
354
|
*
|
|
269
355
|
* @example
|
|
270
|
-
*
|
|
271
|
-
* const
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
* )
|
|
277
|
-
*
|
|
278
|
-
*
|
|
356
|
+
* ```ts
|
|
357
|
+
* const sub = useSubscription({
|
|
358
|
+
* url: 'wss://api.example.com/ws',
|
|
359
|
+
* onMessage: (event, queryClient) => {
|
|
360
|
+
* const data = JSON.parse(event.data)
|
|
361
|
+
* if (data.type === 'order-updated') {
|
|
362
|
+
* queryClient.invalidateQueries({ queryKey: ['orders'] })
|
|
363
|
+
* }
|
|
364
|
+
* },
|
|
365
|
+
* })
|
|
366
|
+
* // sub.status() — 'connecting' | 'connected' | 'disconnected' | 'error'
|
|
367
|
+
* // sub.send(JSON.stringify({ type: 'subscribe', channel: 'orders' }))
|
|
368
|
+
* ```
|
|
279
369
|
*/
|
|
280
|
-
function
|
|
281
|
-
const
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
370
|
+
function useSubscription(options) {
|
|
371
|
+
const queryClient = useQueryClient();
|
|
372
|
+
const status = signal("disconnected");
|
|
373
|
+
let ws = null;
|
|
374
|
+
let reconnectAttempts = 0;
|
|
375
|
+
let reconnectTimer = null;
|
|
376
|
+
let intentionalClose = false;
|
|
377
|
+
const reconnectEnabled = options.reconnect !== false;
|
|
378
|
+
const baseDelay = options.reconnectDelay ?? 1e3;
|
|
379
|
+
const maxAttempts = options.maxReconnectAttempts ?? 10;
|
|
380
|
+
function getUrl() {
|
|
381
|
+
return typeof options.url === "function" ? options.url() : options.url;
|
|
382
|
+
}
|
|
383
|
+
function isEnabled() {
|
|
384
|
+
if (options.enabled === void 0) return true;
|
|
385
|
+
return typeof options.enabled === "function" ? options.enabled() : options.enabled;
|
|
386
|
+
}
|
|
387
|
+
function connect() {
|
|
388
|
+
if (ws) {
|
|
389
|
+
ws.onopen = null;
|
|
390
|
+
ws.onmessage = null;
|
|
391
|
+
ws.onclose = null;
|
|
392
|
+
ws.onerror = null;
|
|
393
|
+
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) ws.close();
|
|
394
|
+
}
|
|
395
|
+
if (!isEnabled()) {
|
|
396
|
+
status.set("disconnected");
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
status.set("connecting");
|
|
400
|
+
try {
|
|
401
|
+
ws = options.protocols ? new WebSocket(getUrl(), options.protocols) : new WebSocket(getUrl());
|
|
402
|
+
} catch {
|
|
403
|
+
status.set("error");
|
|
404
|
+
scheduleReconnect();
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
ws.onopen = (event) => {
|
|
408
|
+
batch(() => {
|
|
409
|
+
status.set("connected");
|
|
410
|
+
reconnectAttempts = 0;
|
|
411
|
+
});
|
|
412
|
+
options.onOpen?.(event);
|
|
413
|
+
};
|
|
414
|
+
ws.onmessage = (event) => {
|
|
415
|
+
options.onMessage(event, queryClient);
|
|
416
|
+
};
|
|
417
|
+
ws.onclose = (event) => {
|
|
418
|
+
status.set("disconnected");
|
|
419
|
+
options.onClose?.(event);
|
|
420
|
+
if (!intentionalClose && reconnectEnabled) scheduleReconnect();
|
|
421
|
+
};
|
|
422
|
+
ws.onerror = (event) => {
|
|
423
|
+
status.set("error");
|
|
424
|
+
options.onError?.(event);
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
function scheduleReconnect() {
|
|
428
|
+
if (!reconnectEnabled) return;
|
|
429
|
+
if (maxAttempts > 0 && reconnectAttempts >= maxAttempts) return;
|
|
430
|
+
const delay = baseDelay * 2 ** reconnectAttempts;
|
|
431
|
+
reconnectAttempts++;
|
|
432
|
+
reconnectTimer = setTimeout(() => {
|
|
433
|
+
reconnectTimer = null;
|
|
434
|
+
if (!intentionalClose && isEnabled()) connect();
|
|
435
|
+
}, delay);
|
|
436
|
+
}
|
|
437
|
+
function send(data) {
|
|
438
|
+
if (ws?.readyState === WebSocket.OPEN) ws.send(data);
|
|
439
|
+
}
|
|
440
|
+
function close() {
|
|
441
|
+
intentionalClose = true;
|
|
442
|
+
if (reconnectTimer !== null) {
|
|
443
|
+
clearTimeout(reconnectTimer);
|
|
444
|
+
reconnectTimer = null;
|
|
445
|
+
}
|
|
446
|
+
if (ws) {
|
|
447
|
+
ws.onopen = null;
|
|
448
|
+
ws.onmessage = null;
|
|
449
|
+
ws.onclose = null;
|
|
450
|
+
ws.onerror = null;
|
|
451
|
+
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) ws.close();
|
|
452
|
+
ws = null;
|
|
453
|
+
}
|
|
454
|
+
status.set("disconnected");
|
|
455
|
+
}
|
|
456
|
+
function manualReconnect() {
|
|
457
|
+
intentionalClose = false;
|
|
458
|
+
reconnectAttempts = 0;
|
|
459
|
+
connect();
|
|
460
|
+
}
|
|
286
461
|
effect(() => {
|
|
287
|
-
|
|
462
|
+
if (typeof options.url === "function") options.url();
|
|
463
|
+
if (typeof options.enabled === "function") options.enabled();
|
|
464
|
+
intentionalClose = false;
|
|
465
|
+
reconnectAttempts = 0;
|
|
466
|
+
connect();
|
|
288
467
|
});
|
|
289
|
-
onUnmount(() =>
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
468
|
+
onUnmount(() => close());
|
|
469
|
+
return {
|
|
470
|
+
status,
|
|
471
|
+
send,
|
|
472
|
+
close,
|
|
473
|
+
reconnect: manualReconnect
|
|
474
|
+
};
|
|
294
475
|
}
|
|
295
476
|
|
|
296
477
|
//#endregion
|
|
@@ -435,55 +616,5 @@ function useSuspenseInfiniteQuery(options) {
|
|
|
435
616
|
}
|
|
436
617
|
|
|
437
618
|
//#endregion
|
|
438
|
-
|
|
439
|
-
const QueryErrorResetBoundaryContext = createContext(null);
|
|
440
|
-
/**
|
|
441
|
-
* Wraps a subtree so that `useQueryErrorResetBoundary()` descendants can reset
|
|
442
|
-
* all errored queries within this boundary.
|
|
443
|
-
*
|
|
444
|
-
* Pair with Pyreon's `ErrorBoundary` to retry failed queries when the user
|
|
445
|
-
* dismisses the error fallback:
|
|
446
|
-
*
|
|
447
|
-
* @example
|
|
448
|
-
* h(QueryErrorResetBoundary, null,
|
|
449
|
-
* h(ErrorBoundary, {
|
|
450
|
-
* fallback: (err, boundaryReset) => {
|
|
451
|
-
* const { reset } = useQueryErrorResetBoundary()
|
|
452
|
-
* return h('button', {
|
|
453
|
-
* onClick: () => { reset(); boundaryReset() },
|
|
454
|
-
* }, 'Retry')
|
|
455
|
-
* },
|
|
456
|
-
* }, h(MyComponent, null)),
|
|
457
|
-
* )
|
|
458
|
-
*/
|
|
459
|
-
function QueryErrorResetBoundary(props) {
|
|
460
|
-
const client = useQueryClient();
|
|
461
|
-
pushContext(new Map([[QueryErrorResetBoundaryContext.id, { reset: () => {
|
|
462
|
-
client.refetchQueries({ predicate: (query) => query.state.status === "error" });
|
|
463
|
-
} }]]));
|
|
464
|
-
onUnmount(() => popContext());
|
|
465
|
-
const ch = props.children;
|
|
466
|
-
return typeof ch === "function" ? ch() : ch;
|
|
467
|
-
}
|
|
468
|
-
/**
|
|
469
|
-
* Returns the `reset` function provided by the nearest `QueryErrorResetBoundary`.
|
|
470
|
-
* If called outside a boundary, falls back to resetting all errored queries
|
|
471
|
-
* on the current `QueryClient`.
|
|
472
|
-
*
|
|
473
|
-
* @example
|
|
474
|
-
* // Inside an ErrorBoundary fallback:
|
|
475
|
-
* const { reset } = useQueryErrorResetBoundary()
|
|
476
|
-
* h('button', { onClick: () => { reset(); boundaryReset() } }, 'Retry')
|
|
477
|
-
*/
|
|
478
|
-
function useQueryErrorResetBoundary() {
|
|
479
|
-
const boundary = useContext(QueryErrorResetBoundaryContext);
|
|
480
|
-
const client = useQueryClient();
|
|
481
|
-
if (boundary) return boundary;
|
|
482
|
-
return { reset: () => {
|
|
483
|
-
client.refetchQueries({ predicate: (query) => query.state.status === "error" });
|
|
484
|
-
} };
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
//#endregion
|
|
488
|
-
export { CancelledError, MutationCache, QueryCache, QueryClient, QueryClientContext, QueryClientProvider, QueryErrorResetBoundary, QuerySuspense, defaultShouldDehydrateMutation, defaultShouldDehydrateQuery, dehydrate, hashKey, hydrate, isCancelledError, keepPreviousData, useInfiniteQuery, useIsFetching, useIsMutating, useMutation, useQueries, useQuery, useQueryClient, useQueryErrorResetBoundary, useSuspenseInfiniteQuery, useSuspenseQuery };
|
|
619
|
+
export { CancelledError, MutationCache, QueryCache, QueryClient, QueryClientContext, QueryClientProvider, QueryErrorResetBoundary, QuerySuspense, defaultShouldDehydrateMutation, defaultShouldDehydrateQuery, dehydrate, hashKey, hydrate, isCancelledError, keepPreviousData, useInfiniteQuery, useIsFetching, useIsMutating, useMutation, useQueries, useQuery, useQueryClient, useQueryErrorResetBoundary, useSubscription, useSuspenseInfiniteQuery, useSuspenseQuery };
|
|
489
620
|
//# sourceMappingURL=index.js.map
|