@raf-query/react 0.11.1 → 0.13.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/CHANGELOG.md +39 -1
- package/README.md +32 -59
- package/dist/bind.d.mts +21 -0
- package/dist/bind.mjs +23 -0
- package/dist/browser-render.mjs +46 -0
- package/dist/devtools.d.mts +42 -2
- package/dist/devtools.mjs +244 -1
- package/dist/hydration-boundary/hydrate.mjs +10 -13
- package/dist/hydration-boundary/hydration-boundary-react-server.d.mts +6 -0
- package/dist/hydration-boundary/hydration-boundary-react-server.mjs +20 -9
- package/dist/hydration-boundary/hydration-boundary.d.mts +15 -28
- package/dist/hydration-boundary/hydration-boundary.mjs +7 -26
- package/dist/index-react-server.d.mts +7 -6
- package/dist/index-react-server.mjs +4 -6
- package/dist/index-shared.d.mts +5 -6
- package/dist/index-shared.mjs +3 -6
- package/dist/index.d.mts +7 -6
- package/dist/index.mjs +5 -4
- package/dist/provider.d.mts +11 -5
- package/dist/provider.mjs +12 -9
- package/dist/react-server-hooks.d.mts +43 -0
- package/dist/react-server-hooks.mjs +45 -0
- package/dist/tanstack-router-browser.d.mts +2 -0
- package/dist/tanstack-router-browser.mjs +2 -0
- package/dist/tanstack-router-shared.d.mts +9 -7
- package/dist/tanstack-router-shared.mjs +12 -22
- package/dist/tanstack-router.d.mts +4 -2
- package/dist/tanstack-router.mjs +12 -14
- package/dist/tracked.d.mts +1 -0
- package/dist/tracked.mjs +65 -0
- package/dist/use-infinite-query.d.mts +23 -0
- package/dist/use-infinite-query.mjs +30 -0
- package/dist/use-is-fetching.d.mts +7 -15
- package/dist/use-is-fetching.mjs +12 -30
- package/dist/use-query.d.mts +52 -50
- package/dist/use-query.mjs +112 -47
- package/package.json +10 -9
- package/dist/devtools-panel.d.mts +0 -38
- package/dist/devtools-panel.mjs +0 -896
- package/dist/devtools-store.mjs +0 -104
- package/dist/hydration-boundary/sent.mjs +0 -16
- package/dist/query-provider/browser-requests.mjs +0 -38
- package/dist/query-provider/query-provider.d.mts +0 -21
- package/dist/query-provider/query-provider.mjs +0 -33
- package/dist/use-waterfall.d.mts +0 -18
- package/dist/use-waterfall.mjs +0 -41
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,48 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.13.0](https://github.com/KATT/raf-query/compare/0.12.0...0.13.0) (2026-11-10)
|
|
4
|
+
|
|
5
|
+
### ⚠ BREAKING CHANGES
|
|
6
|
+
|
|
7
|
+
* **core:** paging lives in core; useInfiniteQuery reads core's PageControls ([#246](https://github.com/KATT/raf-query/issues/246))
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **core:** paging lives in core; useInfiniteQuery reads core's PageControls ([#246](https://github.com/KATT/raf-query/issues/246)) ([f6b4ed2](https://github.com/KATT/raf-query/commit/f6b4ed210340b4b25bf3316ef08a9c8ceb9ed112))
|
|
12
|
+
* **core:** pass the client making the request to queryFn ([#241](https://github.com/KATT/raf-query/issues/241)) ([8c85746](https://github.com/KATT/raf-query/commit/8c85746c230bc3d8a426b32a130a260fa7e8ca62))
|
|
13
|
+
* **core:** replay cached entries to a new client subscriber ([#243](https://github.com/KATT/raf-query/issues/243)) ([1455eed](https://github.com/KATT/raf-query/commit/1455eed2612fe69f0f10bfec08cfd552ef90c786))
|
|
14
|
+
* **core:** ssr: false for queries only a browser can run ([#240](https://github.com/KATT/raf-query/issues/240)) ([077de62](https://github.com/KATT/raf-query/commit/077de6223a92b8bd8d35f7df043d1efe147ca403))
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
* **core:** infer factories built inline by a generic maker in setData ([#244](https://github.com/KATT/raf-query/issues/244)) ([da87c07](https://github.com/KATT/raf-query/commit/da87c07a3463a919c154ac3ac5fba3d546b3231e))
|
|
19
|
+
* **core:** keep data written while a request ran ([#239](https://github.com/KATT/raf-query/issues/239)) ([4cb7cc0](https://github.com/KATT/raf-query/commit/4cb7cc0605a2d4637591eb365153c5e955eb41a8))
|
|
20
|
+
* **core:** start features once for providers that share a client ([#242](https://github.com/KATT/raf-query/issues/242)) ([d5968fb](https://github.com/KATT/raf-query/commit/d5968fb140429a1b2e7fb94662c266a52e877cc0))
|
|
21
|
+
|
|
22
|
+
### Documentation
|
|
23
|
+
|
|
24
|
+
* every example is the blog's posts, comments and authors ([#236](https://github.com/KATT/raf-query/issues/236)) ([2551e97](https://github.com/KATT/raf-query/commit/2551e9710aa7b149be333a1675dae31d9d1f5efd)), references [#230](https://github.com/KATT/raf-query/issues/230)
|
|
25
|
+
|
|
26
|
+
### Miscellaneous Chores
|
|
27
|
+
|
|
28
|
+
* **lint:** require a docs link on every public export ([#237](https://github.com/KATT/raf-query/issues/237)) ([b5c8133](https://github.com/KATT/raf-query/commit/b5c8133c129e0412e82cd2f24da6a2516b4a2012))
|
|
29
|
+
* unblock CI: [@see](https://github.com/see) links and the RSC test's build ([#245](https://github.com/KATT/raf-query/issues/245)) ([84927e4](https://github.com/KATT/raf-query/commit/84927e4a900f7608e1e17b022e28afadbe00e4ae)), references [#243](https://github.com/KATT/raf-query/issues/243) [#246](https://github.com/KATT/raf-query/issues/246) [#240](https://github.com/KATT/raf-query/issues/240) [#238](https://github.com/KATT/raf-query/issues/238)
|
|
30
|
+
|
|
31
|
+
## [0.12.0](https://github.com/KATT/raf-query/compare/0.11.1...0.12.0) (2026-11-08)
|
|
32
|
+
|
|
33
|
+
### ⚠ BREAKING CHANGES
|
|
34
|
+
|
|
35
|
+
- greenfield rewrite on a promise kernel, with load() handles and opt-in behaviors ([#230](https://github.com/KATT/raf-query/issues/230))
|
|
36
|
+
|
|
37
|
+
### Features
|
|
38
|
+
|
|
39
|
+
- greenfield rewrite on a promise kernel, with load() handles and opt-in behaviors ([#230](https://github.com/KATT/raf-query/issues/230)) ([e2a6f85](https://github.com/KATT/raf-query/commit/e2a6f85297992dc8a5cdd3f871a54452393ae978))
|
|
40
|
+
|
|
3
41
|
## [0.11.1](https://github.com/KATT/raf-query/compare/0.11.0...0.11.1) (2026-10-17)
|
|
4
42
|
|
|
5
43
|
### Features
|
|
6
44
|
|
|
7
|
-
|
|
45
|
+
- **core:** take throwOnError from the query's options (2/2) ([#214](https://github.com/KATT/raf-query/issues/214)) ([ef1f0fd](https://github.com/KATT/raf-query/commit/ef1f0fd8eda2a73f4c4c268e8a3a5cf91a0ba0d8)), references [#448](https://github.com/KATT/raf-query/issues/448)
|
|
8
46
|
|
|
9
47
|
## [0.11.0](https://github.com/KATT/raf-query/compare/0.10.4...0.11.0) (2026-10-16)
|
|
10
48
|
|
package/README.md
CHANGED
|
@@ -10,28 +10,31 @@ Render-as-you-fetch data fetching for React: start queries in route loaders and
|
|
|
10
10
|
## Install
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
|
-
npm install @raf-query/react
|
|
13
|
+
npm install @raf-query/react
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Requires React 19.2 or newer.
|
|
17
17
|
|
|
18
18
|
## Usage
|
|
19
19
|
|
|
20
20
|
A query is a factory. The loader and the component call the same one. A paged list, such as comments, uses `infiniteQueryOptions`.
|
|
21
21
|
|
|
22
22
|
```tsx
|
|
23
|
-
import { infiniteQueryOptions, queryOptions } from "@raf-query/react";
|
|
23
|
+
import { group, infiniteQueryOptions, queryOptions } from "@raf-query/react";
|
|
24
|
+
|
|
25
|
+
const posts = group("posts");
|
|
26
|
+
const comments = group(posts, "comments");
|
|
24
27
|
|
|
25
28
|
export function postQuery(input: { slug: string }) {
|
|
26
29
|
return queryOptions({
|
|
27
|
-
queryKey:
|
|
30
|
+
queryKey: posts("bySlug", input.slug),
|
|
28
31
|
queryFn: () => getPost(input.slug),
|
|
29
32
|
});
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
export function commentsQuery(input: { slug: string }) {
|
|
33
36
|
return infiniteQueryOptions({
|
|
34
|
-
queryKey:
|
|
37
|
+
queryKey: comments(input.slug),
|
|
35
38
|
queryFn: (context) =>
|
|
36
39
|
listComments({ cursor: context.pageParam, slug: input.slug }),
|
|
37
40
|
initialPageParam: 0,
|
|
@@ -40,28 +43,36 @@ export function commentsQuery(input: { slug: string }) {
|
|
|
40
43
|
}
|
|
41
44
|
```
|
|
42
45
|
|
|
43
|
-
**`prefetch`** starts
|
|
46
|
+
A loader names its queries in one `load` call. **`prefetch`** starts a request without waiting for it: the page renders now, and `use()` suspends whatever is still loading under its Suspense fallback. **`swr`** holds the navigation until the data is cached, so `use()` returns it without suspending. `useQuery` returns a thenable for every handle, because the cache can drop a query between the loader and the render.
|
|
44
47
|
|
|
45
48
|
```tsx
|
|
46
|
-
import {
|
|
49
|
+
import {
|
|
50
|
+
load,
|
|
51
|
+
prefetch,
|
|
52
|
+
type QueryHandle,
|
|
53
|
+
swr,
|
|
54
|
+
useInfiniteQuery,
|
|
55
|
+
useQuery,
|
|
56
|
+
} from "@raf-query/react";
|
|
47
57
|
import { Suspense, use } from "react";
|
|
48
58
|
|
|
49
59
|
export const Route = createFileRoute("/posts/$slug")({
|
|
50
|
-
loader: (args) => {
|
|
51
|
-
const qc = args.context.queryClient;
|
|
60
|
+
loader: async (args) => {
|
|
52
61
|
const { slug } = args.params;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
};
|
|
62
|
+
const [handles] = await load(args.context.queryClient, {
|
|
63
|
+
post: swr(postQuery, { slug }),
|
|
64
|
+
comments: prefetch(commentsQuery, { slug }),
|
|
65
|
+
});
|
|
66
|
+
return handles;
|
|
57
67
|
},
|
|
58
68
|
component: PostPage,
|
|
59
69
|
});
|
|
60
70
|
|
|
61
71
|
function PostPage() {
|
|
62
72
|
const data = Route.useLoaderData();
|
|
63
|
-
const [
|
|
64
|
-
|
|
73
|
+
const [postThenable] = useQuery(postQuery, data.post);
|
|
74
|
+
// The loader waited for it, so this doesn't suspend.
|
|
75
|
+
const post = use(postThenable);
|
|
65
76
|
return (
|
|
66
77
|
<article>
|
|
67
78
|
<h1>{post.title}</h1>
|
|
@@ -73,8 +84,8 @@ function PostPage() {
|
|
|
73
84
|
}
|
|
74
85
|
|
|
75
86
|
function Comments(props: { handle: QueryHandle<typeof commentsQuery> }) {
|
|
76
|
-
const [
|
|
77
|
-
const pages = use(
|
|
87
|
+
const [pagesThenable] = useInfiniteQuery(commentsQuery, props.handle);
|
|
88
|
+
const pages = use(pagesThenable);
|
|
78
89
|
return pages.pages
|
|
79
90
|
.flatMap((page) => page.items)
|
|
80
91
|
.map((comment) => (
|
|
@@ -85,50 +96,12 @@ function Comments(props: { handle: QueryHandle<typeof commentsQuery> }) {
|
|
|
85
96
|
}
|
|
86
97
|
```
|
|
87
98
|
|
|
88
|
-
**`ensure`** waits until the data is cached, so the navigation holds. `useQuery` returns the data itself for that handle, not a promise. Await the post and its comments together with [`allKeyed`](https://raf-query.katt.dev/docs/starting-queries#awaiting-several-queries) — a record of promises in, the same record out — so one wait never delays the other.
|
|
89
|
-
|
|
90
|
-
```tsx
|
|
91
|
-
import { allKeyed, useQuery } from "@raf-query/react";
|
|
92
|
-
|
|
93
|
-
export const Route = createFileRoute("/posts/$slug")({
|
|
94
|
-
loader: (args) => {
|
|
95
|
-
const qc = args.context.queryClient;
|
|
96
|
-
const { slug } = args.params;
|
|
97
|
-
return allKeyed({
|
|
98
|
-
comments: qc.ensure(commentsQuery, { slug }),
|
|
99
|
-
post: qc.ensure(postQuery, { slug }),
|
|
100
|
-
});
|
|
101
|
-
},
|
|
102
|
-
component: PostPage,
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
function PostPage() {
|
|
106
|
-
const data = Route.useLoaderData();
|
|
107
|
-
// Both already waited, so these are the data itself.
|
|
108
|
-
const [post] = useQuery(postQuery, data.post);
|
|
109
|
-
const [pages] = useQuery(commentsQuery, data.comments);
|
|
110
|
-
return (
|
|
111
|
-
<article>
|
|
112
|
-
<h1>{post.title}</h1>
|
|
113
|
-
{pages.pages
|
|
114
|
-
.flatMap((page) => page.items)
|
|
115
|
-
.map((comment) => (
|
|
116
|
-
<p key={comment.id}>
|
|
117
|
-
<strong>{comment.author}</strong> {comment.body}
|
|
118
|
-
</p>
|
|
119
|
-
))}
|
|
120
|
-
</article>
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
```
|
|
124
|
-
|
|
125
99
|
## Entry points
|
|
126
100
|
|
|
127
|
-
| Import | Use
|
|
128
|
-
| ---------------------------------- |
|
|
129
|
-
| `@raf-query/react` | `QueryClientProvider`, `
|
|
130
|
-
| `@raf-query/react/tanstack-router` | [TanStack Start and Router](https://raf-query.katt.dev/docs/tanstack-start) SSR streaming
|
|
131
|
-
| `@raf-query/react/devtools` | The [devtools](https://raf-query.katt.dev/docs/devtools) panel; omit the element to tree-shake it |
|
|
101
|
+
| Import | Use |
|
|
102
|
+
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
103
|
+
| `@raf-query/react` | `QueryClientProvider`, `useQuery`, `useInfiniteQuery`, `useIsFetching`, `HydrationBoundary`, `QueryDevtools`, and all of core (`group`, `dehydrate`, `hydrate`); what an app doesn't import stays out of its bundle |
|
|
104
|
+
| `@raf-query/react/tanstack-router` | [TanStack Start and Router](https://raf-query.katt.dev/docs/tanstack-start) SSR streaming |
|
|
132
105
|
|
|
133
106
|
## License
|
|
134
107
|
|
package/dist/bind.d.mts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { QueryClient } from "@raf-query/core";
|
|
2
|
+
import { RootOf } from "@raf-query/core/internal";
|
|
3
|
+
//#region src/bind.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* `useQueryClient` typed with a root, so verbs called with the client it
|
|
6
|
+
* returns check factories and targets against that root. `T` is the
|
|
7
|
+
* client's type, or the type of a function that makes it. Bind once per app:
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* // raf.ts
|
|
11
|
+
* export const useQueryClient = bindUseQueryClient<typeof makeQueryClient>();
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* It returns `useQueryClient` itself; only the types change. It isn't a
|
|
15
|
+
* hook, so call it at module level. This module has no `"use client"`, so a
|
|
16
|
+
* server module can import the file that binds it.
|
|
17
|
+
*
|
|
18
|
+
* @see https://raf-query.katt.dev/docs/links-and-features#typing-meta-by-its-root
|
|
19
|
+
*/
|
|
20
|
+
export declare function bindUseQueryClient<T>(): () => QueryClient<RootOf<T>>;
|
|
21
|
+
//#endregion
|
package/dist/bind.mjs
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useQueryClient } from "./provider.mjs";
|
|
2
|
+
//#region src/bind.ts
|
|
3
|
+
/**
|
|
4
|
+
* `useQueryClient` typed with a root, so verbs called with the client it
|
|
5
|
+
* returns check factories and targets against that root. `T` is the
|
|
6
|
+
* client's type, or the type of a function that makes it. Bind once per app:
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* // raf.ts
|
|
10
|
+
* export const useQueryClient = bindUseQueryClient<typeof makeQueryClient>();
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* It returns `useQueryClient` itself; only the types change. It isn't a
|
|
14
|
+
* hook, so call it at module level. This module has no `"use client"`, so a
|
|
15
|
+
* server module can import the file that binds it.
|
|
16
|
+
*
|
|
17
|
+
* @see https://raf-query.katt.dev/docs/links-and-features#typing-meta-by-its-root
|
|
18
|
+
*/
|
|
19
|
+
function bindUseQueryClient() {
|
|
20
|
+
return useQueryClient;
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { bindUseQueryClient };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { rejected } from "@raf-query/core/internal";
|
|
2
|
+
//#region src/browser-render.ts
|
|
3
|
+
/**
|
|
4
|
+
* The digest Next.js gives a boundary that renders only in the browser
|
|
5
|
+
* (`next/dynamic` with `ssr: false`); it doesn't report those errors.
|
|
6
|
+
*/
|
|
7
|
+
const clientRendering = "BAILOUT_TO_CLIENT_SIDE_RENDERING";
|
|
8
|
+
/**
|
|
9
|
+
* What a server render's read rejects with when nothing on the server will
|
|
10
|
+
* settle the query. `use()` throws it, React renders the nearest Suspense
|
|
11
|
+
* fallback, and the browser renders the content.
|
|
12
|
+
*/
|
|
13
|
+
var BrowserRenderError = class extends Error {
|
|
14
|
+
digest = clientRendering;
|
|
15
|
+
name = "BrowserRenderError";
|
|
16
|
+
};
|
|
17
|
+
/** Server clients a server components' `HydrationBoundary` feeds. */
|
|
18
|
+
const fedByServerComponents = /* @__PURE__ */ new WeakSet();
|
|
19
|
+
/**
|
|
20
|
+
* Mark a server render's client as fed by server components: from now on,
|
|
21
|
+
* its renders leave a query nothing started to the browser instead of
|
|
22
|
+
* fetching it, since nothing would send that request's data down.
|
|
23
|
+
*/
|
|
24
|
+
function markFedByServerComponents(client) {
|
|
25
|
+
if (client.isServer) fedByServerComponents.add(client);
|
|
26
|
+
}
|
|
27
|
+
/** Whether a render on `client` leaves a query nothing started to the browser. */
|
|
28
|
+
function leavesUnstartedToBrowser(client) {
|
|
29
|
+
return fedByServerComponents.has(client);
|
|
30
|
+
}
|
|
31
|
+
const reads = /* @__PURE__ */ new WeakMap();
|
|
32
|
+
/**
|
|
33
|
+
* The read of an entry a server render won't settle: no data, no error and
|
|
34
|
+
* no request in flight. It rejects, and the boundary that `use()`s it renders
|
|
35
|
+
* in the browser.
|
|
36
|
+
*/
|
|
37
|
+
function browserRender(entry) {
|
|
38
|
+
let read = reads.get(entry);
|
|
39
|
+
if (!read) {
|
|
40
|
+
read = rejected(new BrowserRenderError(`Query ${entry.hash} loads in the browser: nothing started it on the server`));
|
|
41
|
+
reads.set(entry, read);
|
|
42
|
+
}
|
|
43
|
+
return read;
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
export { browserRender, leavesUnstartedToBrowser, markFedByServerComponents };
|
package/dist/devtools.d.mts
CHANGED
|
@@ -1,2 +1,42 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { QueryClient } from "@raf-query/core";
|
|
2
|
+
import { CSSProperties } from "react";
|
|
3
|
+
//#region src/devtools.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Props of {@link QueryDevtoolsPanel}.
|
|
6
|
+
*
|
|
7
|
+
* @see https://raf-query.katt.dev/docs/devtools#props
|
|
8
|
+
*/
|
|
9
|
+
export interface QueryDevtoolsPanelProps {
|
|
10
|
+
/** The client to inspect. Default: the nearest `QueryClientProvider`'s. */
|
|
11
|
+
readonly client?: QueryClient;
|
|
12
|
+
/** Shows a close button that calls this. */
|
|
13
|
+
readonly onClose?: () => void;
|
|
14
|
+
/** Styles for the panel's root element, e.g. its height. */
|
|
15
|
+
readonly style?: CSSProperties;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Props of {@link QueryDevtools}.
|
|
19
|
+
*
|
|
20
|
+
* @see https://raf-query.katt.dev/docs/devtools
|
|
21
|
+
*/
|
|
22
|
+
export interface QueryDevtoolsProps {
|
|
23
|
+
/** Corner of the toggle button. Default: `"bottom-right"`. */
|
|
24
|
+
readonly buttonPosition?: "bottom-left" | "bottom-right" | "top-left" | "top-right";
|
|
25
|
+
/** The client to inspect. Default: the nearest `QueryClientProvider`'s. */
|
|
26
|
+
readonly client?: QueryClient;
|
|
27
|
+
/** Open on the first render. */
|
|
28
|
+
readonly initialIsOpen?: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The devtools panel, to place in your own layout.
|
|
32
|
+
*
|
|
33
|
+
* @see https://raf-query.katt.dev/docs/devtools#embedding-the-panel
|
|
34
|
+
*/
|
|
35
|
+
export declare function QueryDevtoolsPanel(props: QueryDevtoolsPanelProps): import("react").JSX.Element;
|
|
36
|
+
/**
|
|
37
|
+
* A toggle button that opens the devtools panel over the page.
|
|
38
|
+
*
|
|
39
|
+
* @see https://raf-query.katt.dev/docs/devtools
|
|
40
|
+
*/
|
|
41
|
+
export declare function QueryDevtools(props: QueryDevtoolsProps): import("react").JSX.Element;
|
|
42
|
+
//#endregion
|
package/dist/devtools.mjs
CHANGED
|
@@ -1,3 +1,246 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
2
|
+
import { useOptionalQueryClient } from "./provider.mjs";
|
|
3
|
+
import { fetchEntry, loadsOf, remove, reset } from "@raf-query/core/internal";
|
|
4
|
+
import { useMemo, useState, useSyncExternalStore } from "react";
|
|
5
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
//#region src/devtools.tsx
|
|
7
|
+
function statusOf(entry) {
|
|
8
|
+
const state = entry.state;
|
|
9
|
+
if (state.isFetching) return "fetching";
|
|
10
|
+
if (state.status === "error") return "error";
|
|
11
|
+
return state.hasData ? entry.readers.size > 0 ? "active" : "inactive" : "pending";
|
|
12
|
+
}
|
|
13
|
+
function snapshotOf(client) {
|
|
14
|
+
return {
|
|
15
|
+
loads: loadsOf(client),
|
|
16
|
+
rows: [...client["~entries"].values()].map((entry) => ({
|
|
17
|
+
entry,
|
|
18
|
+
readers: entry.readers.size,
|
|
19
|
+
state: entry.state,
|
|
20
|
+
status: statusOf(entry),
|
|
21
|
+
updatedAt: entry.state.updatedAt
|
|
22
|
+
})).toSorted((a, b) => a.entry.hash.localeCompare(b.entry.hash))
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function snapshotStore(client) {
|
|
26
|
+
let changed = false;
|
|
27
|
+
let snapshot = snapshotOf(client);
|
|
28
|
+
return {
|
|
29
|
+
read: () => {
|
|
30
|
+
if (changed) {
|
|
31
|
+
changed = false;
|
|
32
|
+
snapshot = snapshotOf(client);
|
|
33
|
+
}
|
|
34
|
+
return snapshot;
|
|
35
|
+
},
|
|
36
|
+
subscribe: (onChange) => {
|
|
37
|
+
changed = true;
|
|
38
|
+
return client.subscribe(() => {
|
|
39
|
+
changed = true;
|
|
40
|
+
onChange();
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function useSnapshot(client) {
|
|
46
|
+
const store = useMemo(() => snapshotStore(client), [client]);
|
|
47
|
+
return useSyncExternalStore(store.subscribe, store.read, store.read);
|
|
48
|
+
}
|
|
49
|
+
const colors = {
|
|
50
|
+
active: "#16a34a",
|
|
51
|
+
error: "#dc2626",
|
|
52
|
+
fetching: "#2563eb",
|
|
53
|
+
inactive: "#6b7280",
|
|
54
|
+
pending: "#ca8a04"
|
|
55
|
+
};
|
|
56
|
+
function Panel(props) {
|
|
57
|
+
const snapshot = useSnapshot(props.client);
|
|
58
|
+
const [selected, setSelected] = useState();
|
|
59
|
+
const row = snapshot.rows.find((candidate) => candidate.entry.hash === selected);
|
|
60
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
61
|
+
style: {
|
|
62
|
+
background: "#0b0d12",
|
|
63
|
+
color: "#e5e7eb",
|
|
64
|
+
display: "grid",
|
|
65
|
+
font: "12px/1.4 ui-monospace, monospace",
|
|
66
|
+
gridTemplateColumns: "minmax(0, 1fr) minmax(0, 1fr)",
|
|
67
|
+
height: 320,
|
|
68
|
+
...props.style
|
|
69
|
+
},
|
|
70
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
71
|
+
style: {
|
|
72
|
+
borderRight: "1px solid #1f2937",
|
|
73
|
+
overflow: "auto"
|
|
74
|
+
},
|
|
75
|
+
children: [
|
|
76
|
+
/* @__PURE__ */ jsxs("div", {
|
|
77
|
+
style: {
|
|
78
|
+
display: "flex",
|
|
79
|
+
gap: 8,
|
|
80
|
+
padding: 8
|
|
81
|
+
},
|
|
82
|
+
children: [/* @__PURE__ */ jsxs("strong", { children: [
|
|
83
|
+
"Queries (",
|
|
84
|
+
snapshot.rows.length,
|
|
85
|
+
")"
|
|
86
|
+
] }), props.onClose ? /* @__PURE__ */ jsx("button", {
|
|
87
|
+
onClick: props.onClose,
|
|
88
|
+
style: { marginLeft: "auto" },
|
|
89
|
+
type: "button",
|
|
90
|
+
children: "Close"
|
|
91
|
+
}) : null]
|
|
92
|
+
}),
|
|
93
|
+
snapshot.rows.map((item) => /* @__PURE__ */ jsxs("button", {
|
|
94
|
+
onClick: () => setSelected(item.entry.hash),
|
|
95
|
+
style: {
|
|
96
|
+
background: item.entry.hash === selected ? "#1f2937" : "transparent",
|
|
97
|
+
border: 0,
|
|
98
|
+
color: "inherit",
|
|
99
|
+
display: "flex",
|
|
100
|
+
font: "inherit",
|
|
101
|
+
gap: 8,
|
|
102
|
+
padding: "4px 8px",
|
|
103
|
+
textAlign: "left",
|
|
104
|
+
width: "100%"
|
|
105
|
+
},
|
|
106
|
+
type: "button",
|
|
107
|
+
children: [
|
|
108
|
+
/* @__PURE__ */ jsx("span", {
|
|
109
|
+
style: {
|
|
110
|
+
color: colors[item.status],
|
|
111
|
+
minWidth: 64
|
|
112
|
+
},
|
|
113
|
+
children: item.status
|
|
114
|
+
}),
|
|
115
|
+
/* @__PURE__ */ jsx("span", {
|
|
116
|
+
style: { minWidth: 16 },
|
|
117
|
+
children: item.readers
|
|
118
|
+
}),
|
|
119
|
+
/* @__PURE__ */ jsx("span", {
|
|
120
|
+
style: {
|
|
121
|
+
overflow: "hidden",
|
|
122
|
+
textOverflow: "ellipsis"
|
|
123
|
+
},
|
|
124
|
+
children: item.entry.hash
|
|
125
|
+
})
|
|
126
|
+
]
|
|
127
|
+
}, item.entry.hash)),
|
|
128
|
+
/* @__PURE__ */ jsxs("div", {
|
|
129
|
+
style: {
|
|
130
|
+
borderTop: "1px solid #1f2937",
|
|
131
|
+
padding: 8
|
|
132
|
+
},
|
|
133
|
+
children: [/* @__PURE__ */ jsx("strong", { children: "Loads" }), snapshot.loads.toReversed().map((record) => /* @__PURE__ */ jsx("div", { children: record.entries.map((one) => `${one.name} (${one.kind})`).join(", ") }, record.startedAt + record.entries.map((one) => one.hash).join()))]
|
|
134
|
+
})
|
|
135
|
+
]
|
|
136
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
137
|
+
style: {
|
|
138
|
+
overflow: "auto",
|
|
139
|
+
padding: 8
|
|
140
|
+
},
|
|
141
|
+
children: row ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
142
|
+
/* @__PURE__ */ jsxs("div", {
|
|
143
|
+
style: {
|
|
144
|
+
display: "flex",
|
|
145
|
+
gap: 8,
|
|
146
|
+
marginBottom: 8
|
|
147
|
+
},
|
|
148
|
+
children: [
|
|
149
|
+
/* @__PURE__ */ jsx("button", {
|
|
150
|
+
onClick: () => void fetchEntry(row.entry, { restart: true }),
|
|
151
|
+
type: "button",
|
|
152
|
+
children: "Refetch"
|
|
153
|
+
}),
|
|
154
|
+
/* @__PURE__ */ jsx("button", {
|
|
155
|
+
onClick: () => void reset(row.entry),
|
|
156
|
+
type: "button",
|
|
157
|
+
children: "Reset"
|
|
158
|
+
}),
|
|
159
|
+
/* @__PURE__ */ jsx("button", {
|
|
160
|
+
onClick: () => remove(row.entry),
|
|
161
|
+
type: "button",
|
|
162
|
+
children: "Remove"
|
|
163
|
+
})
|
|
164
|
+
]
|
|
165
|
+
}),
|
|
166
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
167
|
+
"Updated:",
|
|
168
|
+
" ",
|
|
169
|
+
row.updatedAt ? new Date(row.updatedAt).toLocaleTimeString() : "never"
|
|
170
|
+
] }),
|
|
171
|
+
/* @__PURE__ */ jsx("pre", {
|
|
172
|
+
style: { whiteSpace: "pre-wrap" },
|
|
173
|
+
children: JSON.stringify(row.state.error ?? row.state.data, null, 2)
|
|
174
|
+
})
|
|
175
|
+
] }) : /* @__PURE__ */ jsx("div", {
|
|
176
|
+
style: { color: "#6b7280" },
|
|
177
|
+
children: "Select a query"
|
|
178
|
+
})
|
|
179
|
+
})]
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* The devtools panel, to place in your own layout.
|
|
184
|
+
*
|
|
185
|
+
* @see https://raf-query.katt.dev/docs/devtools#embedding-the-panel
|
|
186
|
+
*/
|
|
187
|
+
function QueryDevtoolsPanel(props) {
|
|
188
|
+
const nearest = useOptionalQueryClient();
|
|
189
|
+
const client = props.client ?? nearest;
|
|
190
|
+
if (!client) throw new Error("QueryDevtoolsPanel: pass `client`, or render it in a QueryClientProvider");
|
|
191
|
+
return /* @__PURE__ */ jsx(Panel, {
|
|
192
|
+
...props,
|
|
193
|
+
client
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
const corners = {
|
|
197
|
+
"bottom-left": {
|
|
198
|
+
bottom: 12,
|
|
199
|
+
left: 12
|
|
200
|
+
},
|
|
201
|
+
"bottom-right": {
|
|
202
|
+
bottom: 12,
|
|
203
|
+
right: 12
|
|
204
|
+
},
|
|
205
|
+
"top-left": {
|
|
206
|
+
left: 12,
|
|
207
|
+
top: 12
|
|
208
|
+
},
|
|
209
|
+
"top-right": {
|
|
210
|
+
right: 12,
|
|
211
|
+
top: 12
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
/**
|
|
215
|
+
* A toggle button that opens the devtools panel over the page.
|
|
216
|
+
*
|
|
217
|
+
* @see https://raf-query.katt.dev/docs/devtools
|
|
218
|
+
*/
|
|
219
|
+
function QueryDevtools(props) {
|
|
220
|
+
const [open, setOpen] = useState(props.initialIsOpen ?? false);
|
|
221
|
+
const corner = corners[props.buttonPosition ?? "bottom-right"];
|
|
222
|
+
return /* @__PURE__ */ jsx(Fragment, { children: open ? /* @__PURE__ */ jsx("div", {
|
|
223
|
+
style: {
|
|
224
|
+
bottom: 0,
|
|
225
|
+
left: 0,
|
|
226
|
+
position: "fixed",
|
|
227
|
+
right: 0,
|
|
228
|
+
zIndex: 99999
|
|
229
|
+
},
|
|
230
|
+
children: /* @__PURE__ */ jsx(QueryDevtoolsPanel, {
|
|
231
|
+
client: props.client,
|
|
232
|
+
onClose: () => setOpen(false)
|
|
233
|
+
})
|
|
234
|
+
}) : /* @__PURE__ */ jsx("button", {
|
|
235
|
+
onClick: () => setOpen(true),
|
|
236
|
+
style: {
|
|
237
|
+
...corner,
|
|
238
|
+
position: "fixed",
|
|
239
|
+
zIndex: 99999
|
|
240
|
+
},
|
|
241
|
+
type: "button",
|
|
242
|
+
children: "raf-query"
|
|
243
|
+
}) });
|
|
244
|
+
}
|
|
245
|
+
//#endregion
|
|
3
246
|
export { QueryDevtools, QueryDevtoolsPanel };
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useQueryClient } from "../provider.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { markFedByServerComponents } from "../browser-render.mjs";
|
|
4
|
+
import { hydrate } from "@raf-query/core";
|
|
5
|
+
import { hashKey } from "@raf-query/core/internal";
|
|
4
6
|
import { useEffect, useState } from "react";
|
|
5
7
|
//#region src/hydration-boundary/hydrate.tsx
|
|
6
8
|
/**
|
|
@@ -8,11 +10,10 @@ import { useEffect, useState } from "react";
|
|
|
8
10
|
* render, and entries it already shows, which must wait for a commit.
|
|
9
11
|
*/
|
|
10
12
|
function split(client, state) {
|
|
11
|
-
const
|
|
12
|
-
const isShown = (query) => shown.has(hashKey(query.key));
|
|
13
|
+
const isShown = (sent) => client["~entries"].get(hashKey(sent.queryKey))?.state.hasData === true;
|
|
13
14
|
return {
|
|
14
|
-
deferred: {
|
|
15
|
-
now: {
|
|
15
|
+
deferred: { entries: state.entries.filter(isShown) },
|
|
16
|
+
now: { entries: state.entries.filter((sent) => !isShown(sent)) }
|
|
16
17
|
};
|
|
17
18
|
}
|
|
18
19
|
/**
|
|
@@ -25,12 +26,12 @@ function split(client, state) {
|
|
|
25
26
|
*/
|
|
26
27
|
function Hydrate(props) {
|
|
27
28
|
const client = useQueryClient();
|
|
29
|
+
if (props.fromServerComponents) markFedByServerComponents(client);
|
|
28
30
|
const state = props.state;
|
|
29
|
-
const options = props.options;
|
|
30
31
|
const [hydrated, setHydrated] = useState();
|
|
31
32
|
if (hydrated?.state !== state) {
|
|
32
33
|
const parts = split(client, state);
|
|
33
|
-
hydrate(client, parts.now
|
|
34
|
+
hydrate(client, parts.now);
|
|
34
35
|
setHydrated({
|
|
35
36
|
deferred: parts.deferred,
|
|
36
37
|
state
|
|
@@ -38,12 +39,8 @@ function Hydrate(props) {
|
|
|
38
39
|
}
|
|
39
40
|
const deferred = hydrated?.deferred;
|
|
40
41
|
useEffect(() => {
|
|
41
|
-
if (deferred && deferred.
|
|
42
|
-
}, [
|
|
43
|
-
client,
|
|
44
|
-
deferred,
|
|
45
|
-
options
|
|
46
|
-
]);
|
|
42
|
+
if (deferred && deferred.entries.length > 0) hydrate(client, deferred);
|
|
43
|
+
}, [client, deferred]);
|
|
47
44
|
return props.children;
|
|
48
45
|
}
|
|
49
46
|
//#endregion
|
|
@@ -6,6 +6,12 @@ import { HydrationBoundaryProps } from "./hydration-boundary.mjs";
|
|
|
6
6
|
* browser cache: pending entries cross as promises and stream in as they
|
|
7
7
|
* settle, and entries an earlier boundary already sent in this request are
|
|
8
8
|
* skipped. With `state`, writes that state, as the browser version does.
|
|
9
|
+
*
|
|
10
|
+
* It also marks the client components' server render: from there on, a read
|
|
11
|
+
* of a query nothing sent renders its Suspense fallback on the server and
|
|
12
|
+
* loads in the browser, instead of fetching where nothing would send it.
|
|
13
|
+
*
|
|
14
|
+
* @see https://raf-query.katt.dev/docs/react-server-components#setup
|
|
9
15
|
*/
|
|
10
16
|
export declare function HydrationBoundary(props: HydrationBoundaryProps): import("react").JSX.Element;
|
|
11
17
|
//#endregion
|