@raf-query/react 0.0.6 → 0.0.7
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 +7 -1
- package/README.md +56 -12
- package/dist/devtools-panel.d.mts +38 -0
- package/dist/devtools-panel.mjs +878 -0
- package/dist/devtools-production.d.mts +2 -0
- package/dist/devtools-production.mjs +3 -0
- package/dist/devtools-store.mjs +103 -0
- package/dist/devtools.d.mts +8 -0
- package/dist/devtools.mjs +12 -0
- package/dist/provider.mjs +5 -1
- package/dist/tanstack-router.mjs +1 -1
- package/package.json +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.0.7](https://github.com/KATT/raf-query/compare/0.0.6...0.0.7) (2026-10-12)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
* **react:** add @raf-query/react/devtools ([#7](https://github.com/KATT/raf-query/issues/7)) ([e325d76](https://github.com/KATT/raf-query/commit/e325d76305e29f935228a2dbad6834b0309229e4))
|
|
8
|
+
|
|
3
9
|
## [0.0.6](https://github.com/KATT/raf-query/compare/0.0.5...0.0.6) (2026-10-12)
|
|
4
10
|
|
|
5
|
-
|
|
11
|
+
- experimental @raf-query/tanstack-react-query on TanStack Query ([#8](https://github.com/KATT/raf-query/issues/8)) ([9d909c4](https://github.com/KATT/raf-query/commit/9d909c46ca030fd465d7af0fcd9591818356dd80))
|
|
6
12
|
|
|
7
13
|
## [0.0.5](https://github.com/KATT/raf-query/compare/0.0.4...0.0.5) (2026-10-12)
|
|
8
14
|
|
package/README.md
CHANGED
|
@@ -17,13 +17,14 @@ Early. Both packages are on npm at `0.0.x`, and the API can still change between
|
|
|
17
17
|
| `@raf-query/core` | Framework-agnostic: `QueryClient`, cache, `queryOptions` / `infiniteQueryOptions`, handles, dehydrate / hydrate stream, `allKeyed` |
|
|
18
18
|
| `@raf-query/react` | Hooks and provider. Re-exports everything from `@raf-query/core`, so apps install and import only this package |
|
|
19
19
|
|
|
20
|
-
`@raf-query/react` subpath entries for framework integrations:
|
|
20
|
+
`@raf-query/react` subpath entries for framework integrations and tooling:
|
|
21
21
|
|
|
22
22
|
| Entry | Use |
|
|
23
23
|
| ---------------------------------- | ---------------------------------------------------------------------- |
|
|
24
24
|
| `@raf-query/react/tanstack-router` | Streams the router's query cache to the client during SSR |
|
|
25
25
|
| `@raf-query/react/rsc` | `HydrationBoundary` for passing server-component queries to the client |
|
|
26
26
|
| `@raf-query/react/next` | `QueryProvider` for Next.js App Router |
|
|
27
|
+
| `@raf-query/react/devtools` | [Devtools](#devtools) panel for inspecting the cache in development |
|
|
27
28
|
|
|
28
29
|
## Principles
|
|
29
30
|
|
|
@@ -470,21 +471,64 @@ const program = Effect.gen(function* () {
|
|
|
470
471
|
|
|
471
472
|
`watch` counts as a reader while it runs, so invalidation and window focus refetch the entry.
|
|
472
473
|
|
|
474
|
+
## Devtools
|
|
475
|
+
|
|
476
|
+
`@raf-query/react/devtools` shows the browser's query cache as it changes. Render `QueryDevtools` anywhere under the provider:
|
|
477
|
+
|
|
478
|
+
```tsx
|
|
479
|
+
import { QueryDevtools } from "@raf-query/react/devtools";
|
|
480
|
+
|
|
481
|
+
<QueryClientProvider client={queryClient}>
|
|
482
|
+
{props.children}
|
|
483
|
+
<QueryDevtools />
|
|
484
|
+
</QueryClientProvider>;
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
In Next.js, put it inside `QueryProvider` in the root layout. It's a client component, so a server-component layout can render it.
|
|
488
|
+
|
|
489
|
+
A button in the corner opens a panel docked to the edge of the viewport. The panel lists every entry with its observer count (mounted readers) and status:
|
|
490
|
+
|
|
491
|
+
| Status | Meaning |
|
|
492
|
+
| ---------- | ------------------------------------------ |
|
|
493
|
+
| `fetching` | A request is in flight |
|
|
494
|
+
| `error` | The last request failed |
|
|
495
|
+
| `stale` | Read, and older than `staleTime` |
|
|
496
|
+
| `fresh` | Read, and within `staleTime` |
|
|
497
|
+
| `inactive` | Nothing reads it; it's removed at `gcTime` |
|
|
498
|
+
|
|
499
|
+
Select an entry to see its key, input, data, error, and last update, and to **Refetch**, **Invalidate**, **Reset** (drop its data, so readers suspend and refetch) or **Remove** it. The header filters entries by key, sorts them, and has **Invalidate all** and **Clear cache**. The panel keeps its open state and size in `localStorage`.
|
|
500
|
+
|
|
501
|
+
`QueryDevtools` props, all optional:
|
|
502
|
+
|
|
503
|
+
| Prop | Values (default first) |
|
|
504
|
+
| ---------------- | -------------------------------------------------------------- |
|
|
505
|
+
| `client` | The nearest `QueryClientProvider`'s client, or another one |
|
|
506
|
+
| `initialIsOpen` | `false`, `true`. Applies until the user opens or closes it |
|
|
507
|
+
| `buttonPosition` | `"bottom-right"`, `"bottom-left"`, `"top-right"`, `"top-left"` |
|
|
508
|
+
| `position` | `"bottom"`, `"top"`, `"left"`, `"right"`: the docked edge |
|
|
509
|
+
| `theme` | `"system"`, `"dark"`, `"light"` |
|
|
510
|
+
|
|
511
|
+
`QueryDevtoolsPanel` is the panel alone, to place in your own layout. It takes `client`, `theme`, `style`, and `onClose`, which adds a close button.
|
|
512
|
+
|
|
513
|
+
Both render nothing on the server, since the server's cache isn't the browser's. They also render nothing when `process.env.NODE_ENV` is `"production"`, and bundlers drop the panel's code from production builds. To ship the panel in production, for example lazily behind a flag, import from `@raf-query/react/devtools/production`.
|
|
514
|
+
|
|
473
515
|
## API reference
|
|
474
516
|
|
|
475
517
|
`QueryClient`:
|
|
476
518
|
|
|
477
|
-
| Member
|
|
478
|
-
|
|
|
479
|
-
| `new QueryClient(config?)`
|
|
480
|
-
| `prefetch`, `ensure`, `fetch`
|
|
481
|
-
| `invalidate`, `setData`, `getData`
|
|
482
|
-
| `
|
|
483
|
-
| `
|
|
484
|
-
| `
|
|
485
|
-
| `
|
|
486
|
-
| `
|
|
487
|
-
| `
|
|
519
|
+
| Member | Use |
|
|
520
|
+
| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
521
|
+
| `new QueryClient(config?)` | `config.defaults` (query options below), `config.onError`, `config.isServer` |
|
|
522
|
+
| `prefetch`, `ensure`, `fetch` | [Start queries](#starting-queries) |
|
|
523
|
+
| `invalidate`, `setData`, `getData` | [Write and read the cache](#mutations) |
|
|
524
|
+
| `reset(factory, input)` / `reset(filter)` | Drop data and errors; read queries suspend and refetch. Resolves when they settle |
|
|
525
|
+
| `remove(factory, input)` / `remove(filter)` | Remove entries and abort their requests |
|
|
526
|
+
| `effect` | [Effect versions](#effect) of `ensure`, `fetch`, `invalidate`, plus `watch` |
|
|
527
|
+
| `subscribe(listener)` | Cache events: `added`, `updated`, `observers` (a reader subscribed or left), `removed`, and `failed` (with the `error`) |
|
|
528
|
+
| `getAll()`, `getQuery(factory, input?)` | Cache entries, for devtools and integrations |
|
|
529
|
+
| `clear()` | Drop every entry and abort in-flight requests |
|
|
530
|
+
| `mount()` | Start window-focus and reconnect refetching. The providers call it |
|
|
531
|
+
| `isServer` | Detected from `window`. Server clients don't retry, never gc, skip stale timers |
|
|
488
532
|
|
|
489
533
|
Query options, accepted by `queryOptions`, `infiniteQueryOptions` and `config.defaults`:
|
|
490
534
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { QueryClient } from "@raf-query/core";
|
|
2
|
+
import { CSSProperties } from "react";
|
|
3
|
+
//#region src/devtools-panel.d.ts
|
|
4
|
+
export type DevtoolsTheme = "dark" | "light" | "system";
|
|
5
|
+
export interface QueryDevtoolsPanelProps {
|
|
6
|
+
/** The client to inspect. Default: the nearest `QueryClientProvider`'s. */
|
|
7
|
+
readonly client?: QueryClient;
|
|
8
|
+
/** Shows a close button that calls this. */
|
|
9
|
+
readonly onClose?: () => void;
|
|
10
|
+
/** Styles for the panel's root element, e.g. its height. */
|
|
11
|
+
readonly style?: CSSProperties;
|
|
12
|
+
/** Default: `"system"`. */
|
|
13
|
+
readonly theme?: DevtoolsTheme;
|
|
14
|
+
}
|
|
15
|
+
export interface QueryDevtoolsProps {
|
|
16
|
+
/** Corner of the toggle button. Default: `"bottom-right"`. */
|
|
17
|
+
readonly buttonPosition?: "bottom-left" | "bottom-right" | "top-left" | "top-right";
|
|
18
|
+
/** The client to inspect. Default: the nearest `QueryClientProvider`'s. */
|
|
19
|
+
readonly client?: QueryClient;
|
|
20
|
+
/** Open on the first visit. Later visits remember the last open state. */
|
|
21
|
+
readonly initialIsOpen?: boolean;
|
|
22
|
+
/** Edge of the viewport the panel docks to. Default: `"bottom"`. */
|
|
23
|
+
readonly position?: "bottom" | "left" | "right" | "top";
|
|
24
|
+
/** Default: `"system"`. */
|
|
25
|
+
readonly theme?: DevtoolsTheme;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The devtools panel on its own, to place anywhere in the page. Renders
|
|
29
|
+
* nothing on the server, since the server's cache isn't the browser's.
|
|
30
|
+
*/
|
|
31
|
+
export declare function QueryDevtoolsPanel(props: QueryDevtoolsPanelProps): import("react").JSX.Element | null;
|
|
32
|
+
/**
|
|
33
|
+
* A toggle button that opens the devtools panel docked to an edge of the
|
|
34
|
+
* viewport. Renders nothing on the server. Open state and size are kept in
|
|
35
|
+
* `localStorage`.
|
|
36
|
+
*/
|
|
37
|
+
export declare function QueryDevtools(props: QueryDevtoolsProps): import("react").JSX.Element | null;
|
|
38
|
+
//#endregion
|
|
@@ -0,0 +1,878 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useOptionalQueryClient } from "./provider.mjs";
|
|
3
|
+
import { countByStatus, sortOrders, statuses, useQueryRows, visibleRows } from "./devtools-store.mjs";
|
|
4
|
+
import { useState, useSyncExternalStore } from "react";
|
|
5
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { Predicate } from "effect";
|
|
7
|
+
//#region src/devtools-panel.tsx
|
|
8
|
+
const palettes = {
|
|
9
|
+
dark: {
|
|
10
|
+
accent: "#38bdf8",
|
|
11
|
+
background: "#0f172a",
|
|
12
|
+
boolean: "#c084fc",
|
|
13
|
+
border: "#334155",
|
|
14
|
+
muted: "#94a3b8",
|
|
15
|
+
number: "#fbbf24",
|
|
16
|
+
raised: "#1e293b",
|
|
17
|
+
selected: "#1e3a5f",
|
|
18
|
+
string: "#4ade80",
|
|
19
|
+
text: "#e2e8f0"
|
|
20
|
+
},
|
|
21
|
+
light: {
|
|
22
|
+
accent: "#0369a1",
|
|
23
|
+
background: "#ffffff",
|
|
24
|
+
boolean: "#7e22ce",
|
|
25
|
+
border: "#cbd5e1",
|
|
26
|
+
muted: "#64748b",
|
|
27
|
+
number: "#b45309",
|
|
28
|
+
raised: "#f1f5f9",
|
|
29
|
+
selected: "#dbeafe",
|
|
30
|
+
string: "#15803d",
|
|
31
|
+
text: "#0f172a"
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
const statusColors = {
|
|
35
|
+
error: "#dc2626",
|
|
36
|
+
fetching: "#2563eb",
|
|
37
|
+
fresh: "#16a34a",
|
|
38
|
+
inactive: "#64748b",
|
|
39
|
+
stale: "#ca8a04"
|
|
40
|
+
};
|
|
41
|
+
const sortLabels = {
|
|
42
|
+
hash: "Key",
|
|
43
|
+
status: "Status",
|
|
44
|
+
updated: "Last updated"
|
|
45
|
+
};
|
|
46
|
+
const fontFamily = "ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif";
|
|
47
|
+
const monoFamily = "ui-monospace, SFMono-Regular, Menlo, Consolas, \"Liberation Mono\", monospace";
|
|
48
|
+
/** Entries an expanded object, array, map, or set lists before "… more". */
|
|
49
|
+
const maxChildren = 100;
|
|
50
|
+
const minSize = 120;
|
|
51
|
+
const defaultSize = 320;
|
|
52
|
+
const storageKeys = {
|
|
53
|
+
open: "raf-query-devtools:open",
|
|
54
|
+
size: "raf-query-devtools:size"
|
|
55
|
+
};
|
|
56
|
+
const noop = () => {};
|
|
57
|
+
const subscribeNever = () => noop;
|
|
58
|
+
/** `false` on the server and while hydrating, then `true`. */
|
|
59
|
+
function useIsClient() {
|
|
60
|
+
return useSyncExternalStore(subscribeNever, () => true, () => false);
|
|
61
|
+
}
|
|
62
|
+
function subscribeColorScheme(onChange) {
|
|
63
|
+
const media = window.matchMedia("(prefers-color-scheme: dark)");
|
|
64
|
+
media.addEventListener("change", onChange);
|
|
65
|
+
return () => media.removeEventListener("change", onChange);
|
|
66
|
+
}
|
|
67
|
+
function prefersDark() {
|
|
68
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
69
|
+
}
|
|
70
|
+
function usePalette(theme = "system") {
|
|
71
|
+
const systemDark = useSyncExternalStore(subscribeColorScheme, prefersDark, () => true);
|
|
72
|
+
if (theme === "system") return systemDark ? palettes.dark : palettes.light;
|
|
73
|
+
return palettes[theme];
|
|
74
|
+
}
|
|
75
|
+
function useDevtoolsClient(client) {
|
|
76
|
+
const contextClient = useOptionalQueryClient();
|
|
77
|
+
const resolved = client ?? contextClient;
|
|
78
|
+
if (!resolved) throw new Error("raf-query devtools: pass `client` or render inside a QueryClientProvider");
|
|
79
|
+
return resolved;
|
|
80
|
+
}
|
|
81
|
+
function readStored(key) {
|
|
82
|
+
try {
|
|
83
|
+
return window.localStorage.getItem(key);
|
|
84
|
+
} catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function writeStored(key, value) {
|
|
89
|
+
try {
|
|
90
|
+
window.localStorage.setItem(key, value);
|
|
91
|
+
} catch {}
|
|
92
|
+
}
|
|
93
|
+
function formatTime(updatedAt) {
|
|
94
|
+
return updatedAt === 0 ? "never" : new Date(updatedAt).toLocaleTimeString();
|
|
95
|
+
}
|
|
96
|
+
/** One-line form of a value, for leaves and collapsed nodes. */
|
|
97
|
+
function preview(value) {
|
|
98
|
+
if (Predicate.isString(value)) return JSON.stringify(value);
|
|
99
|
+
if (Predicate.isBigInt(value)) return `${value}n`;
|
|
100
|
+
if (Predicate.isSymbol(value)) return value.toString();
|
|
101
|
+
if (Predicate.isFunction(value)) return `ƒ ${value.name || "anonymous"}()`;
|
|
102
|
+
if (Predicate.isDate(value)) return Number.isNaN(value.getTime()) ? "Invalid Date" : value.toISOString();
|
|
103
|
+
if (Predicate.isError(value)) return `${value.name}: ${value.message}`;
|
|
104
|
+
if (Predicate.isRegExp(value)) return String(value);
|
|
105
|
+
if (Predicate.isPromiseLike(value)) return "Promise";
|
|
106
|
+
if (Array.isArray(value)) return `Array(${value.length})`;
|
|
107
|
+
if (Predicate.isMap(value)) return `Map(${value.size})`;
|
|
108
|
+
if (Predicate.isSet(value)) return `Set(${value.size})`;
|
|
109
|
+
if (Predicate.isObject(value)) {
|
|
110
|
+
const size = Object.keys(value).length;
|
|
111
|
+
return size === 1 ? "{1 key}" : `{${size} keys}`;
|
|
112
|
+
}
|
|
113
|
+
return String(value);
|
|
114
|
+
}
|
|
115
|
+
/** Labelled children of a value the explorer can expand, else `undefined`. */
|
|
116
|
+
function childrenOf(value) {
|
|
117
|
+
if (Array.isArray(value)) return value.map((item, index) => [String(index), item]);
|
|
118
|
+
if (Predicate.isMap(value)) return [...value].map(([key, item]) => [preview(key), item]);
|
|
119
|
+
if (Predicate.isSet(value)) return [...value].map((item, index) => [String(index), item]);
|
|
120
|
+
if (!Predicate.isObject(value) || Predicate.isDate(value) || Predicate.isError(value) || Predicate.isRegExp(value) || Predicate.isPromiseLike(value)) return;
|
|
121
|
+
return Object.entries(value);
|
|
122
|
+
}
|
|
123
|
+
function colorOf(value, palette) {
|
|
124
|
+
if (Predicate.isString(value)) return palette.string;
|
|
125
|
+
if (Predicate.isNumber(value) || Predicate.isBigInt(value)) return palette.number;
|
|
126
|
+
if (Predicate.isBoolean(value)) return palette.boolean;
|
|
127
|
+
if (Predicate.isNullish(value)) return palette.muted;
|
|
128
|
+
return palette.text;
|
|
129
|
+
}
|
|
130
|
+
function copyText(value) {
|
|
131
|
+
try {
|
|
132
|
+
return JSON.stringify(value, null, 2) ?? String(value);
|
|
133
|
+
} catch {
|
|
134
|
+
return preview(value);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function Explorer(props) {
|
|
138
|
+
const [expanded, setExpanded] = useState(props.depth < 1);
|
|
139
|
+
const children = childrenOf(props.value);
|
|
140
|
+
const palette = props.palette;
|
|
141
|
+
const label = props.label === void 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
142
|
+
style: { color: palette.accent },
|
|
143
|
+
children: `${props.label}: `
|
|
144
|
+
});
|
|
145
|
+
if (children === void 0) return /* @__PURE__ */ jsxs("div", {
|
|
146
|
+
style: { paddingLeft: 14 },
|
|
147
|
+
children: [label, /* @__PURE__ */ jsx("span", {
|
|
148
|
+
style: { color: colorOf(props.value, palette) },
|
|
149
|
+
children: preview(props.value)
|
|
150
|
+
})]
|
|
151
|
+
});
|
|
152
|
+
return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("button", {
|
|
153
|
+
"aria-expanded": expanded,
|
|
154
|
+
onClick: () => setExpanded(!expanded),
|
|
155
|
+
style: {
|
|
156
|
+
background: "none",
|
|
157
|
+
border: "none",
|
|
158
|
+
color: palette.text,
|
|
159
|
+
cursor: "pointer",
|
|
160
|
+
font: "inherit",
|
|
161
|
+
padding: 0,
|
|
162
|
+
textAlign: "left"
|
|
163
|
+
},
|
|
164
|
+
type: "button",
|
|
165
|
+
children: [
|
|
166
|
+
/* @__PURE__ */ jsx("span", {
|
|
167
|
+
"aria-hidden": true,
|
|
168
|
+
style: {
|
|
169
|
+
display: "inline-block",
|
|
170
|
+
width: 14
|
|
171
|
+
},
|
|
172
|
+
children: expanded ? "▾" : "▸"
|
|
173
|
+
}),
|
|
174
|
+
label,
|
|
175
|
+
/* @__PURE__ */ jsx("span", {
|
|
176
|
+
style: { color: palette.muted },
|
|
177
|
+
children: preview(props.value)
|
|
178
|
+
})
|
|
179
|
+
]
|
|
180
|
+
}), expanded ? /* @__PURE__ */ jsxs("div", {
|
|
181
|
+
style: { paddingLeft: 14 },
|
|
182
|
+
children: [children.slice(0, maxChildren).map(([key, item]) => /* @__PURE__ */ jsx(Explorer, {
|
|
183
|
+
depth: props.depth + 1,
|
|
184
|
+
label: key,
|
|
185
|
+
palette,
|
|
186
|
+
value: item
|
|
187
|
+
}, key)), children.length > maxChildren ? /* @__PURE__ */ jsx("div", {
|
|
188
|
+
style: {
|
|
189
|
+
color: palette.muted,
|
|
190
|
+
paddingLeft: 14
|
|
191
|
+
},
|
|
192
|
+
children: `… ${children.length - maxChildren} more`
|
|
193
|
+
}) : null]
|
|
194
|
+
}) : null] });
|
|
195
|
+
}
|
|
196
|
+
function StatusChip(props) {
|
|
197
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
198
|
+
"data-status": props.status,
|
|
199
|
+
style: {
|
|
200
|
+
alignItems: "center",
|
|
201
|
+
background: props.palette.raised,
|
|
202
|
+
border: `1px solid ${props.palette.border}`,
|
|
203
|
+
borderRadius: 999,
|
|
204
|
+
display: "inline-flex",
|
|
205
|
+
gap: 6,
|
|
206
|
+
padding: "2px 8px"
|
|
207
|
+
},
|
|
208
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
209
|
+
"aria-hidden": true,
|
|
210
|
+
style: {
|
|
211
|
+
background: statusColors[props.status],
|
|
212
|
+
borderRadius: 999,
|
|
213
|
+
height: 8,
|
|
214
|
+
width: 8
|
|
215
|
+
}
|
|
216
|
+
}), `${props.status} ${props.count}`]
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
function ActionButton(props) {
|
|
220
|
+
return /* @__PURE__ */ jsx("button", {
|
|
221
|
+
disabled: props.disabled,
|
|
222
|
+
onClick: props.onClick,
|
|
223
|
+
style: {
|
|
224
|
+
background: props.color,
|
|
225
|
+
border: "none",
|
|
226
|
+
borderRadius: 4,
|
|
227
|
+
color: "#ffffff",
|
|
228
|
+
cursor: props.disabled ? "not-allowed" : "pointer",
|
|
229
|
+
font: "inherit",
|
|
230
|
+
opacity: props.disabled ? .5 : 1,
|
|
231
|
+
padding: "4px 10px"
|
|
232
|
+
},
|
|
233
|
+
type: "button",
|
|
234
|
+
children: props.label
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
function Heading(props) {
|
|
238
|
+
return /* @__PURE__ */ jsx("h3", {
|
|
239
|
+
style: {
|
|
240
|
+
background: props.palette.raised,
|
|
241
|
+
borderBottom: `1px solid ${props.palette.border}`,
|
|
242
|
+
borderTop: `1px solid ${props.palette.border}`,
|
|
243
|
+
fontSize: 12,
|
|
244
|
+
fontWeight: 600,
|
|
245
|
+
margin: 0,
|
|
246
|
+
padding: "4px 8px"
|
|
247
|
+
},
|
|
248
|
+
children: props.title
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
function Field(props) {
|
|
252
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
253
|
+
style: {
|
|
254
|
+
display: "flex",
|
|
255
|
+
gap: 8,
|
|
256
|
+
justifyContent: "space-between"
|
|
257
|
+
},
|
|
258
|
+
children: [/* @__PURE__ */ jsx("dt", {
|
|
259
|
+
style: { color: props.palette.muted },
|
|
260
|
+
children: props.label
|
|
261
|
+
}), /* @__PURE__ */ jsx("dd", {
|
|
262
|
+
style: {
|
|
263
|
+
margin: 0,
|
|
264
|
+
textAlign: "right"
|
|
265
|
+
},
|
|
266
|
+
children: props.children
|
|
267
|
+
})]
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
function QueryDetails(props) {
|
|
271
|
+
const palette = props.palette;
|
|
272
|
+
const query = props.row.query;
|
|
273
|
+
const state = props.row.state;
|
|
274
|
+
const options = query.options;
|
|
275
|
+
const section = { padding: 8 };
|
|
276
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
277
|
+
"aria-label": "Query details",
|
|
278
|
+
style: {
|
|
279
|
+
flex: 1,
|
|
280
|
+
minWidth: 0,
|
|
281
|
+
overflow: "auto"
|
|
282
|
+
},
|
|
283
|
+
children: [
|
|
284
|
+
/* @__PURE__ */ jsx(Heading, {
|
|
285
|
+
palette,
|
|
286
|
+
title: "Query details"
|
|
287
|
+
}),
|
|
288
|
+
/* @__PURE__ */ jsxs("div", {
|
|
289
|
+
style: section,
|
|
290
|
+
children: [/* @__PURE__ */ jsx("pre", {
|
|
291
|
+
style: {
|
|
292
|
+
fontFamily: monoFamily,
|
|
293
|
+
margin: "0 0 8px",
|
|
294
|
+
whiteSpace: "pre-wrap",
|
|
295
|
+
wordBreak: "break-all"
|
|
296
|
+
},
|
|
297
|
+
children: JSON.stringify(query.key, null, 2)
|
|
298
|
+
}), /* @__PURE__ */ jsxs("dl", {
|
|
299
|
+
style: {
|
|
300
|
+
display: "grid",
|
|
301
|
+
gap: 4,
|
|
302
|
+
margin: 0
|
|
303
|
+
},
|
|
304
|
+
children: [
|
|
305
|
+
/* @__PURE__ */ jsx(Field, {
|
|
306
|
+
label: "Status",
|
|
307
|
+
palette,
|
|
308
|
+
children: props.row.status
|
|
309
|
+
}),
|
|
310
|
+
/* @__PURE__ */ jsx(Field, {
|
|
311
|
+
label: "Observers",
|
|
312
|
+
palette,
|
|
313
|
+
children: String(props.row.observers)
|
|
314
|
+
}),
|
|
315
|
+
/* @__PURE__ */ jsx(Field, {
|
|
316
|
+
label: "Last updated",
|
|
317
|
+
palette,
|
|
318
|
+
children: formatTime(state.updatedAt)
|
|
319
|
+
}),
|
|
320
|
+
/* @__PURE__ */ jsx(Field, {
|
|
321
|
+
label: "Kind",
|
|
322
|
+
palette,
|
|
323
|
+
children: options === void 0 ? "hydrated" : options.kind
|
|
324
|
+
}),
|
|
325
|
+
/* @__PURE__ */ jsx(Field, {
|
|
326
|
+
label: "Failures",
|
|
327
|
+
palette,
|
|
328
|
+
children: String(state.failureCount)
|
|
329
|
+
})
|
|
330
|
+
]
|
|
331
|
+
})]
|
|
332
|
+
}),
|
|
333
|
+
/* @__PURE__ */ jsx(Heading, {
|
|
334
|
+
palette,
|
|
335
|
+
title: "Actions"
|
|
336
|
+
}),
|
|
337
|
+
/* @__PURE__ */ jsxs("div", {
|
|
338
|
+
style: {
|
|
339
|
+
...section,
|
|
340
|
+
display: "flex",
|
|
341
|
+
flexWrap: "wrap",
|
|
342
|
+
gap: 6
|
|
343
|
+
},
|
|
344
|
+
children: [
|
|
345
|
+
/* @__PURE__ */ jsx(ActionButton, {
|
|
346
|
+
color: statusColors.fetching,
|
|
347
|
+
disabled: options === void 0 || state.isFetching,
|
|
348
|
+
label: "Refetch",
|
|
349
|
+
onClick: () => void query.refetch()
|
|
350
|
+
}),
|
|
351
|
+
/* @__PURE__ */ jsx(ActionButton, {
|
|
352
|
+
color: statusColors.stale,
|
|
353
|
+
label: "Invalidate",
|
|
354
|
+
onClick: () => void query.invalidate()
|
|
355
|
+
}),
|
|
356
|
+
/* @__PURE__ */ jsx(ActionButton, {
|
|
357
|
+
color: statusColors.inactive,
|
|
358
|
+
label: "Reset",
|
|
359
|
+
onClick: () => void query.reset()
|
|
360
|
+
}),
|
|
361
|
+
/* @__PURE__ */ jsx(ActionButton, {
|
|
362
|
+
color: statusColors.error,
|
|
363
|
+
label: "Remove",
|
|
364
|
+
onClick: () => props.client.remove({
|
|
365
|
+
exact: true,
|
|
366
|
+
queryKey: query.key
|
|
367
|
+
})
|
|
368
|
+
})
|
|
369
|
+
]
|
|
370
|
+
}),
|
|
371
|
+
state.error == null ? null : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Heading, {
|
|
372
|
+
palette,
|
|
373
|
+
title: "Error"
|
|
374
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
375
|
+
style: {
|
|
376
|
+
...section,
|
|
377
|
+
color: statusColors.error
|
|
378
|
+
},
|
|
379
|
+
children: [/* @__PURE__ */ jsx("div", { children: preview(state.error) }), Predicate.isError(state.error) && state.error.stack ? /* @__PURE__ */ jsx("pre", {
|
|
380
|
+
style: {
|
|
381
|
+
fontFamily: monoFamily,
|
|
382
|
+
fontSize: 11,
|
|
383
|
+
margin: "6px 0 0",
|
|
384
|
+
whiteSpace: "pre-wrap"
|
|
385
|
+
},
|
|
386
|
+
children: state.error.stack
|
|
387
|
+
}) : null]
|
|
388
|
+
})] }),
|
|
389
|
+
/* @__PURE__ */ jsx(Heading, {
|
|
390
|
+
palette,
|
|
391
|
+
title: "Data"
|
|
392
|
+
}),
|
|
393
|
+
/* @__PURE__ */ jsxs("div", {
|
|
394
|
+
style: {
|
|
395
|
+
...section,
|
|
396
|
+
fontFamily: monoFamily
|
|
397
|
+
},
|
|
398
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
399
|
+
onClick: () => void navigator.clipboard?.writeText(copyText(state.data)),
|
|
400
|
+
style: {
|
|
401
|
+
background: "none",
|
|
402
|
+
border: `1px solid ${palette.border}`,
|
|
403
|
+
borderRadius: 4,
|
|
404
|
+
color: palette.text,
|
|
405
|
+
cursor: "pointer",
|
|
406
|
+
float: "right",
|
|
407
|
+
font: "inherit",
|
|
408
|
+
fontFamily,
|
|
409
|
+
padding: "2px 8px"
|
|
410
|
+
},
|
|
411
|
+
type: "button",
|
|
412
|
+
children: "Copy"
|
|
413
|
+
}), /* @__PURE__ */ jsx(Explorer, {
|
|
414
|
+
depth: 0,
|
|
415
|
+
palette,
|
|
416
|
+
value: state.data
|
|
417
|
+
})]
|
|
418
|
+
}),
|
|
419
|
+
/* @__PURE__ */ jsx(Heading, {
|
|
420
|
+
palette,
|
|
421
|
+
title: "Input"
|
|
422
|
+
}),
|
|
423
|
+
/* @__PURE__ */ jsx("div", {
|
|
424
|
+
style: {
|
|
425
|
+
...section,
|
|
426
|
+
fontFamily: monoFamily
|
|
427
|
+
},
|
|
428
|
+
children: /* @__PURE__ */ jsx(Explorer, {
|
|
429
|
+
depth: 0,
|
|
430
|
+
palette,
|
|
431
|
+
value: query.handle.input
|
|
432
|
+
})
|
|
433
|
+
})
|
|
434
|
+
]
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
function QueryList(props) {
|
|
438
|
+
const palette = props.palette;
|
|
439
|
+
if (props.rows.length === 0) return /* @__PURE__ */ jsx("p", {
|
|
440
|
+
style: {
|
|
441
|
+
color: palette.muted,
|
|
442
|
+
margin: 0,
|
|
443
|
+
padding: 12
|
|
444
|
+
},
|
|
445
|
+
children: "No queries"
|
|
446
|
+
});
|
|
447
|
+
return /* @__PURE__ */ jsx("ul", {
|
|
448
|
+
style: {
|
|
449
|
+
listStyle: "none",
|
|
450
|
+
margin: 0,
|
|
451
|
+
padding: 0
|
|
452
|
+
},
|
|
453
|
+
children: props.rows.map((row) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("button", {
|
|
454
|
+
"aria-pressed": row.hash === props.selectedHash,
|
|
455
|
+
"data-status": row.status,
|
|
456
|
+
onClick: () => props.onSelect(row.hash),
|
|
457
|
+
style: {
|
|
458
|
+
alignItems: "center",
|
|
459
|
+
background: row.hash === props.selectedHash ? palette.selected : "transparent",
|
|
460
|
+
border: "none",
|
|
461
|
+
borderBottom: `1px solid ${palette.border}`,
|
|
462
|
+
color: palette.text,
|
|
463
|
+
cursor: "pointer",
|
|
464
|
+
display: "flex",
|
|
465
|
+
font: "inherit",
|
|
466
|
+
gap: 8,
|
|
467
|
+
padding: "4px 8px",
|
|
468
|
+
textAlign: "left",
|
|
469
|
+
width: "100%"
|
|
470
|
+
},
|
|
471
|
+
title: row.status,
|
|
472
|
+
type: "button",
|
|
473
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
474
|
+
"aria-label": `${row.observers} observers`,
|
|
475
|
+
style: {
|
|
476
|
+
background: statusColors[row.status],
|
|
477
|
+
borderRadius: 4,
|
|
478
|
+
color: "#ffffff",
|
|
479
|
+
flexShrink: 0,
|
|
480
|
+
fontWeight: 600,
|
|
481
|
+
minWidth: 22,
|
|
482
|
+
padding: "1px 4px",
|
|
483
|
+
textAlign: "center"
|
|
484
|
+
},
|
|
485
|
+
children: row.observers
|
|
486
|
+
}), /* @__PURE__ */ jsx("code", {
|
|
487
|
+
style: {
|
|
488
|
+
fontFamily: monoFamily,
|
|
489
|
+
overflow: "hidden",
|
|
490
|
+
textOverflow: "ellipsis",
|
|
491
|
+
whiteSpace: "nowrap"
|
|
492
|
+
},
|
|
493
|
+
children: row.hash
|
|
494
|
+
})]
|
|
495
|
+
}) }, row.hash))
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
function isSortBy(value) {
|
|
499
|
+
return sortOrders.some((order) => order === value);
|
|
500
|
+
}
|
|
501
|
+
function Panel(props) {
|
|
502
|
+
const client = useDevtoolsClient(props.client);
|
|
503
|
+
const palette = usePalette(props.theme);
|
|
504
|
+
const rows = useQueryRows(client);
|
|
505
|
+
const [filter, setFilter] = useState("");
|
|
506
|
+
const [sortBy, setSortBy] = useState("status");
|
|
507
|
+
const [selectedHash, setSelectedHash] = useState(void 0);
|
|
508
|
+
const counts = countByStatus(rows);
|
|
509
|
+
const shown = visibleRows(rows, filter, sortBy);
|
|
510
|
+
const selected = rows.find((row) => row.hash === selectedHash);
|
|
511
|
+
const control = {
|
|
512
|
+
background: palette.raised,
|
|
513
|
+
border: `1px solid ${palette.border}`,
|
|
514
|
+
borderRadius: 4,
|
|
515
|
+
color: palette.text,
|
|
516
|
+
font: "inherit",
|
|
517
|
+
padding: "3px 6px"
|
|
518
|
+
};
|
|
519
|
+
const onClose = props.onClose;
|
|
520
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
521
|
+
"data-raf-query-devtools": "",
|
|
522
|
+
style: {
|
|
523
|
+
background: palette.background,
|
|
524
|
+
boxSizing: "border-box",
|
|
525
|
+
color: palette.text,
|
|
526
|
+
colorScheme: palette === palettes.dark ? "dark" : "light",
|
|
527
|
+
display: "flex",
|
|
528
|
+
flexDirection: "column",
|
|
529
|
+
fontFamily,
|
|
530
|
+
fontSize: 12,
|
|
531
|
+
lineHeight: 1.4,
|
|
532
|
+
minHeight: 0,
|
|
533
|
+
overflow: "hidden",
|
|
534
|
+
...props.style
|
|
535
|
+
},
|
|
536
|
+
children: [/* @__PURE__ */ jsxs("header", {
|
|
537
|
+
style: {
|
|
538
|
+
alignItems: "center",
|
|
539
|
+
borderBottom: `1px solid ${palette.border}`,
|
|
540
|
+
display: "flex",
|
|
541
|
+
flexWrap: "wrap",
|
|
542
|
+
gap: 8,
|
|
543
|
+
padding: 8
|
|
544
|
+
},
|
|
545
|
+
children: [
|
|
546
|
+
/* @__PURE__ */ jsxs("strong", {
|
|
547
|
+
style: { fontSize: 13 },
|
|
548
|
+
children: ["raf-query ", /* @__PURE__ */ jsx("span", {
|
|
549
|
+
style: { color: palette.accent },
|
|
550
|
+
children: "devtools"
|
|
551
|
+
})]
|
|
552
|
+
}),
|
|
553
|
+
/* @__PURE__ */ jsx("div", {
|
|
554
|
+
style: {
|
|
555
|
+
display: "flex",
|
|
556
|
+
flexWrap: "wrap",
|
|
557
|
+
gap: 4
|
|
558
|
+
},
|
|
559
|
+
children: statuses.map((status) => /* @__PURE__ */ jsx(StatusChip, {
|
|
560
|
+
count: counts[status],
|
|
561
|
+
palette,
|
|
562
|
+
status
|
|
563
|
+
}, status))
|
|
564
|
+
}),
|
|
565
|
+
/* @__PURE__ */ jsxs("div", {
|
|
566
|
+
style: {
|
|
567
|
+
display: "flex",
|
|
568
|
+
gap: 6,
|
|
569
|
+
marginLeft: "auto"
|
|
570
|
+
},
|
|
571
|
+
children: [
|
|
572
|
+
/* @__PURE__ */ jsx("input", {
|
|
573
|
+
"aria-label": "Filter queries",
|
|
574
|
+
onChange: (event) => setFilter(event.target.value),
|
|
575
|
+
placeholder: "Filter",
|
|
576
|
+
style: control,
|
|
577
|
+
type: "search",
|
|
578
|
+
value: filter
|
|
579
|
+
}),
|
|
580
|
+
/* @__PURE__ */ jsx("select", {
|
|
581
|
+
"aria-label": "Sort queries",
|
|
582
|
+
onChange: (event) => {
|
|
583
|
+
const value = event.target.value;
|
|
584
|
+
if (isSortBy(value)) setSortBy(value);
|
|
585
|
+
},
|
|
586
|
+
style: control,
|
|
587
|
+
value: sortBy,
|
|
588
|
+
children: sortOrders.map((order) => /* @__PURE__ */ jsx("option", {
|
|
589
|
+
value: order,
|
|
590
|
+
children: sortLabels[order]
|
|
591
|
+
}, order))
|
|
592
|
+
}),
|
|
593
|
+
/* @__PURE__ */ jsx("button", {
|
|
594
|
+
onClick: () => void client.invalidate({ queryKey: [] }),
|
|
595
|
+
style: {
|
|
596
|
+
...control,
|
|
597
|
+
cursor: "pointer"
|
|
598
|
+
},
|
|
599
|
+
type: "button",
|
|
600
|
+
children: "Invalidate all"
|
|
601
|
+
}),
|
|
602
|
+
/* @__PURE__ */ jsx("button", {
|
|
603
|
+
onClick: () => client.clear(),
|
|
604
|
+
style: {
|
|
605
|
+
...control,
|
|
606
|
+
cursor: "pointer"
|
|
607
|
+
},
|
|
608
|
+
type: "button",
|
|
609
|
+
children: "Clear cache"
|
|
610
|
+
}),
|
|
611
|
+
onClose ? /* @__PURE__ */ jsx("button", {
|
|
612
|
+
"aria-label": "Close raf-query devtools",
|
|
613
|
+
onClick: () => onClose(),
|
|
614
|
+
style: {
|
|
615
|
+
...control,
|
|
616
|
+
cursor: "pointer"
|
|
617
|
+
},
|
|
618
|
+
type: "button",
|
|
619
|
+
children: "✕"
|
|
620
|
+
}) : null
|
|
621
|
+
]
|
|
622
|
+
})
|
|
623
|
+
]
|
|
624
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
625
|
+
style: {
|
|
626
|
+
display: "flex",
|
|
627
|
+
flex: 1,
|
|
628
|
+
minHeight: 0
|
|
629
|
+
},
|
|
630
|
+
children: [/* @__PURE__ */ jsx("nav", {
|
|
631
|
+
"aria-label": "Queries",
|
|
632
|
+
style: {
|
|
633
|
+
borderRight: `1px solid ${palette.border}`,
|
|
634
|
+
flex: 1,
|
|
635
|
+
minWidth: 0,
|
|
636
|
+
overflow: "auto"
|
|
637
|
+
},
|
|
638
|
+
children: /* @__PURE__ */ jsx(QueryList, {
|
|
639
|
+
onSelect: setSelectedHash,
|
|
640
|
+
palette,
|
|
641
|
+
rows: shown,
|
|
642
|
+
selectedHash
|
|
643
|
+
})
|
|
644
|
+
}), selected ? /* @__PURE__ */ jsx(QueryDetails, {
|
|
645
|
+
client,
|
|
646
|
+
palette,
|
|
647
|
+
row: selected
|
|
648
|
+
}) : /* @__PURE__ */ jsx("p", {
|
|
649
|
+
style: {
|
|
650
|
+
color: palette.muted,
|
|
651
|
+
flex: 1,
|
|
652
|
+
margin: 0,
|
|
653
|
+
padding: 12
|
|
654
|
+
},
|
|
655
|
+
children: "Select a query to inspect it"
|
|
656
|
+
})]
|
|
657
|
+
})]
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* The devtools panel on its own, to place anywhere in the page. Renders
|
|
662
|
+
* nothing on the server, since the server's cache isn't the browser's.
|
|
663
|
+
*/
|
|
664
|
+
function QueryDevtoolsPanel(props) {
|
|
665
|
+
return useIsClient() ? /* @__PURE__ */ jsx(Panel, {
|
|
666
|
+
client: props.client,
|
|
667
|
+
onClose: props.onClose,
|
|
668
|
+
style: props.style,
|
|
669
|
+
theme: props.theme
|
|
670
|
+
}) : null;
|
|
671
|
+
}
|
|
672
|
+
const corners = {
|
|
673
|
+
"bottom-left": {
|
|
674
|
+
bottom: 12,
|
|
675
|
+
left: 12
|
|
676
|
+
},
|
|
677
|
+
"bottom-right": {
|
|
678
|
+
bottom: 12,
|
|
679
|
+
right: 12
|
|
680
|
+
},
|
|
681
|
+
"top-left": {
|
|
682
|
+
left: 12,
|
|
683
|
+
top: 12
|
|
684
|
+
},
|
|
685
|
+
"top-right": {
|
|
686
|
+
right: 12,
|
|
687
|
+
top: 12
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
function clampSize(size, max) {
|
|
691
|
+
return Math.min(Math.max(size, minSize), Math.max(max - 40, minSize));
|
|
692
|
+
}
|
|
693
|
+
function DockedDevtools(props) {
|
|
694
|
+
const position = props.position ?? "bottom";
|
|
695
|
+
const buttonPosition = props.buttonPosition ?? "bottom-right";
|
|
696
|
+
const horizontal = position === "left" || position === "right";
|
|
697
|
+
const [isOpen, setIsOpen] = useState(() => {
|
|
698
|
+
const stored = readStored(storageKeys.open);
|
|
699
|
+
return stored === null ? props.initialIsOpen ?? false : stored === "true";
|
|
700
|
+
});
|
|
701
|
+
const [size, setSize] = useState(() => Number(readStored(storageKeys.size)) || defaultSize);
|
|
702
|
+
const palette = usePalette(props.theme);
|
|
703
|
+
const setOpen = (open) => {
|
|
704
|
+
setIsOpen(open);
|
|
705
|
+
writeStored(storageKeys.open, String(open));
|
|
706
|
+
};
|
|
707
|
+
const startResize = (event) => {
|
|
708
|
+
event.preventDefault();
|
|
709
|
+
let latest = size;
|
|
710
|
+
const onMove = (move) => {
|
|
711
|
+
const next = {
|
|
712
|
+
bottom: window.innerHeight - move.clientY,
|
|
713
|
+
left: move.clientX,
|
|
714
|
+
right: window.innerWidth - move.clientX,
|
|
715
|
+
top: move.clientY
|
|
716
|
+
}[position];
|
|
717
|
+
latest = clampSize(next, horizontal ? window.innerWidth : window.innerHeight);
|
|
718
|
+
setSize(latest);
|
|
719
|
+
};
|
|
720
|
+
const onUp = () => {
|
|
721
|
+
window.removeEventListener("pointermove", onMove);
|
|
722
|
+
window.removeEventListener("pointerup", onUp);
|
|
723
|
+
writeStored(storageKeys.size, String(latest));
|
|
724
|
+
};
|
|
725
|
+
window.addEventListener("pointermove", onMove);
|
|
726
|
+
window.addEventListener("pointerup", onUp);
|
|
727
|
+
};
|
|
728
|
+
if (!isOpen) return /* @__PURE__ */ jsxs("button", {
|
|
729
|
+
"aria-label": "Open raf-query devtools",
|
|
730
|
+
onClick: () => setOpen(true),
|
|
731
|
+
style: {
|
|
732
|
+
...corners[buttonPosition],
|
|
733
|
+
alignItems: "center",
|
|
734
|
+
background: palette.background,
|
|
735
|
+
border: `1px solid ${palette.border}`,
|
|
736
|
+
borderRadius: 999,
|
|
737
|
+
boxShadow: "0 2px 8px rgb(0 0 0 / 0.25)",
|
|
738
|
+
color: palette.text,
|
|
739
|
+
cursor: "pointer",
|
|
740
|
+
display: "flex",
|
|
741
|
+
fontFamily,
|
|
742
|
+
fontSize: 12,
|
|
743
|
+
fontWeight: 600,
|
|
744
|
+
gap: 6,
|
|
745
|
+
padding: "6px 12px",
|
|
746
|
+
position: "fixed",
|
|
747
|
+
zIndex: 99999
|
|
748
|
+
},
|
|
749
|
+
type: "button",
|
|
750
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
751
|
+
"aria-hidden": true,
|
|
752
|
+
style: {
|
|
753
|
+
background: palette.accent,
|
|
754
|
+
borderRadius: 999,
|
|
755
|
+
height: 8,
|
|
756
|
+
width: 8
|
|
757
|
+
}
|
|
758
|
+
}), "raf-query"]
|
|
759
|
+
});
|
|
760
|
+
const edge = {
|
|
761
|
+
bottom: {
|
|
762
|
+
bottom: 0,
|
|
763
|
+
height: size,
|
|
764
|
+
left: 0,
|
|
765
|
+
right: 0
|
|
766
|
+
},
|
|
767
|
+
left: {
|
|
768
|
+
bottom: 0,
|
|
769
|
+
left: 0,
|
|
770
|
+
top: 0,
|
|
771
|
+
width: size
|
|
772
|
+
},
|
|
773
|
+
right: {
|
|
774
|
+
bottom: 0,
|
|
775
|
+
right: 0,
|
|
776
|
+
top: 0,
|
|
777
|
+
width: size
|
|
778
|
+
},
|
|
779
|
+
top: {
|
|
780
|
+
height: size,
|
|
781
|
+
left: 0,
|
|
782
|
+
right: 0,
|
|
783
|
+
top: 0
|
|
784
|
+
}
|
|
785
|
+
}[position];
|
|
786
|
+
const handleEdge = {
|
|
787
|
+
bottom: {
|
|
788
|
+
height: 10,
|
|
789
|
+
left: 0,
|
|
790
|
+
right: 0,
|
|
791
|
+
top: -5
|
|
792
|
+
},
|
|
793
|
+
left: {
|
|
794
|
+
bottom: 0,
|
|
795
|
+
right: -5,
|
|
796
|
+
top: 0,
|
|
797
|
+
width: 10
|
|
798
|
+
},
|
|
799
|
+
right: {
|
|
800
|
+
bottom: 0,
|
|
801
|
+
left: -5,
|
|
802
|
+
top: 0,
|
|
803
|
+
width: 10
|
|
804
|
+
},
|
|
805
|
+
top: {
|
|
806
|
+
bottom: -5,
|
|
807
|
+
height: 10,
|
|
808
|
+
left: 0,
|
|
809
|
+
right: 0
|
|
810
|
+
}
|
|
811
|
+
}[position];
|
|
812
|
+
return /* @__PURE__ */ jsxs("aside", {
|
|
813
|
+
"aria-label": "raf-query devtools",
|
|
814
|
+
style: {
|
|
815
|
+
...edge,
|
|
816
|
+
borderColor: palette.border,
|
|
817
|
+
borderStyle: "solid",
|
|
818
|
+
borderWidth: {
|
|
819
|
+
bottom: "1px 0 0",
|
|
820
|
+
left: "0 1px 0 0",
|
|
821
|
+
right: "0 0 0 1px",
|
|
822
|
+
top: "0 0 1px"
|
|
823
|
+
}[position],
|
|
824
|
+
boxShadow: "0 0 16px rgb(0 0 0 / 0.3)",
|
|
825
|
+
position: "fixed",
|
|
826
|
+
zIndex: 99999
|
|
827
|
+
},
|
|
828
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
829
|
+
"aria-label": "Resize raf-query devtools",
|
|
830
|
+
"aria-orientation": horizontal ? "vertical" : "horizontal",
|
|
831
|
+
onPointerDown: startResize,
|
|
832
|
+
role: "separator",
|
|
833
|
+
style: {
|
|
834
|
+
...handleEdge,
|
|
835
|
+
alignItems: "center",
|
|
836
|
+
cursor: horizontal ? "ew-resize" : "ns-resize",
|
|
837
|
+
display: "flex",
|
|
838
|
+
justifyContent: "center",
|
|
839
|
+
position: "absolute",
|
|
840
|
+
touchAction: "none",
|
|
841
|
+
zIndex: 1
|
|
842
|
+
},
|
|
843
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
844
|
+
"aria-hidden": true,
|
|
845
|
+
style: {
|
|
846
|
+
background: palette.border,
|
|
847
|
+
borderRadius: 999,
|
|
848
|
+
height: horizontal ? 32 : 4,
|
|
849
|
+
width: horizontal ? 4 : 32
|
|
850
|
+
}
|
|
851
|
+
})
|
|
852
|
+
}), /* @__PURE__ */ jsx(QueryDevtoolsPanel, {
|
|
853
|
+
client: props.client,
|
|
854
|
+
onClose: () => setOpen(false),
|
|
855
|
+
style: {
|
|
856
|
+
height: "100%",
|
|
857
|
+
width: "100%"
|
|
858
|
+
},
|
|
859
|
+
theme: props.theme
|
|
860
|
+
})]
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* A toggle button that opens the devtools panel docked to an edge of the
|
|
865
|
+
* viewport. Renders nothing on the server. Open state and size are kept in
|
|
866
|
+
* `localStorage`.
|
|
867
|
+
*/
|
|
868
|
+
function QueryDevtools(props) {
|
|
869
|
+
return useIsClient() ? /* @__PURE__ */ jsx(DockedDevtools, {
|
|
870
|
+
buttonPosition: props.buttonPosition,
|
|
871
|
+
client: props.client,
|
|
872
|
+
initialIsOpen: props.initialIsOpen,
|
|
873
|
+
position: props.position,
|
|
874
|
+
theme: props.theme
|
|
875
|
+
}) : null;
|
|
876
|
+
}
|
|
877
|
+
//#endregion
|
|
878
|
+
export { QueryDevtools, QueryDevtoolsPanel, preview };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { useSyncExternalStore } from "react";
|
|
2
|
+
//#region src/devtools-store.ts
|
|
3
|
+
const statuses = [
|
|
4
|
+
"fetching",
|
|
5
|
+
"error",
|
|
6
|
+
"stale",
|
|
7
|
+
"fresh",
|
|
8
|
+
"inactive"
|
|
9
|
+
];
|
|
10
|
+
const sortOrders = [
|
|
11
|
+
"status",
|
|
12
|
+
"hash",
|
|
13
|
+
"updated"
|
|
14
|
+
];
|
|
15
|
+
const stores = /* @__PURE__ */ new WeakMap();
|
|
16
|
+
function statusOf(query) {
|
|
17
|
+
const state = query.getState();
|
|
18
|
+
if (state.isFetching) return "fetching";
|
|
19
|
+
if (state.status === "error") return "error";
|
|
20
|
+
if (query.observerCount === 0) return "inactive";
|
|
21
|
+
return query.isStaleNow() ? "stale" : "fresh";
|
|
22
|
+
}
|
|
23
|
+
function rowOf(query) {
|
|
24
|
+
return {
|
|
25
|
+
hash: query.hash,
|
|
26
|
+
observers: query.observerCount,
|
|
27
|
+
query,
|
|
28
|
+
state: query.getState(),
|
|
29
|
+
status: statusOf(query)
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function createStore(client) {
|
|
33
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
34
|
+
let version = 0;
|
|
35
|
+
let snapshotVersion = -1;
|
|
36
|
+
let snapshot = [];
|
|
37
|
+
let scheduled = false;
|
|
38
|
+
client.subscribe(() => {
|
|
39
|
+
version++;
|
|
40
|
+
if (scheduled || listeners.size === 0) return;
|
|
41
|
+
scheduled = true;
|
|
42
|
+
queueMicrotask(() => {
|
|
43
|
+
scheduled = false;
|
|
44
|
+
for (const listener of listeners) listener();
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
return {
|
|
48
|
+
getSnapshot: () => {
|
|
49
|
+
if (snapshotVersion !== version) {
|
|
50
|
+
snapshotVersion = version;
|
|
51
|
+
snapshot = client.getAll().map(rowOf);
|
|
52
|
+
}
|
|
53
|
+
return snapshot;
|
|
54
|
+
},
|
|
55
|
+
subscribe: (onChange) => {
|
|
56
|
+
listeners.add(onChange);
|
|
57
|
+
return () => {
|
|
58
|
+
listeners.delete(onChange);
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function storeFor(client) {
|
|
64
|
+
let store = stores.get(client);
|
|
65
|
+
if (!store) {
|
|
66
|
+
store = createStore(client);
|
|
67
|
+
stores.set(client, store);
|
|
68
|
+
}
|
|
69
|
+
return store;
|
|
70
|
+
}
|
|
71
|
+
/** Every cache entry, re-rendering on each batch of cache events. */
|
|
72
|
+
function useQueryRows(client) {
|
|
73
|
+
const store = storeFor(client);
|
|
74
|
+
return useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
75
|
+
}
|
|
76
|
+
function countByStatus(rows) {
|
|
77
|
+
const counts = {
|
|
78
|
+
error: 0,
|
|
79
|
+
fetching: 0,
|
|
80
|
+
fresh: 0,
|
|
81
|
+
inactive: 0,
|
|
82
|
+
stale: 0
|
|
83
|
+
};
|
|
84
|
+
for (const row of rows) counts[row.status]++;
|
|
85
|
+
return counts;
|
|
86
|
+
}
|
|
87
|
+
function compareRows(sortBy) {
|
|
88
|
+
return (a, b) => {
|
|
89
|
+
const primary = {
|
|
90
|
+
hash: 0,
|
|
91
|
+
status: statuses.indexOf(a.status) - statuses.indexOf(b.status),
|
|
92
|
+
updated: b.state.updatedAt - a.state.updatedAt
|
|
93
|
+
}[sortBy];
|
|
94
|
+
return primary === 0 ? a.hash.localeCompare(b.hash) : primary;
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/** Rows whose hash contains `filter` (case-insensitive), sorted. */
|
|
98
|
+
function visibleRows(rows, filter, sortBy) {
|
|
99
|
+
const needle = filter.trim().toLowerCase();
|
|
100
|
+
return rows.filter((row) => row.hash.toLowerCase().includes(needle)).toSorted(compareRows(sortBy));
|
|
101
|
+
}
|
|
102
|
+
//#endregion
|
|
103
|
+
export { countByStatus, sortOrders, statusOf, statuses, useQueryRows, visibleRows };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { DevtoolsTheme, QueryDevtools as QueryDevtools$1, QueryDevtoolsPanel as QueryDevtoolsPanel$1, QueryDevtoolsPanelProps, QueryDevtoolsProps } from "./devtools-panel.mjs";
|
|
2
|
+
//#region src/devtools.d.ts
|
|
3
|
+
/** Toggle button plus docked panel. Renders nothing in production builds. */
|
|
4
|
+
export declare const QueryDevtools: typeof QueryDevtools$1;
|
|
5
|
+
/** The panel alone, to embed anywhere. Renders nothing in production builds. */
|
|
6
|
+
export declare const QueryDevtoolsPanel: typeof QueryDevtoolsPanel$1;
|
|
7
|
+
//#endregion
|
|
8
|
+
export type { DevtoolsTheme, QueryDevtoolsPanelProps, QueryDevtoolsProps };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { QueryDevtools as QueryDevtools$1, QueryDevtoolsPanel as QueryDevtoolsPanel$1 } from "./devtools-panel.mjs";
|
|
3
|
+
//#region src/devtools.tsx
|
|
4
|
+
function Nothing() {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
/** Toggle button plus docked panel. Renders nothing in production builds. */
|
|
8
|
+
const QueryDevtools = process.env.NODE_ENV === "production" ? Nothing : QueryDevtools$1;
|
|
9
|
+
/** The panel alone, to embed anywhere. Renders nothing in production builds. */
|
|
10
|
+
const QueryDevtoolsPanel = process.env.NODE_ENV === "production" ? Nothing : QueryDevtoolsPanel$1;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { QueryDevtools, QueryDevtoolsPanel };
|
package/dist/provider.mjs
CHANGED
|
@@ -15,6 +15,10 @@ function QueryClientProvider(props) {
|
|
|
15
15
|
children: props.children
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
|
+
/** The nearest provider's client, or `null` outside one. */
|
|
19
|
+
function useOptionalQueryClient() {
|
|
20
|
+
return use(QueryClientContext);
|
|
21
|
+
}
|
|
18
22
|
/** The client from the nearest {@link QueryClientProvider}. */
|
|
19
23
|
function useQueryClient() {
|
|
20
24
|
const client = use(QueryClientContext);
|
|
@@ -22,4 +26,4 @@ function useQueryClient() {
|
|
|
22
26
|
return client;
|
|
23
27
|
}
|
|
24
28
|
//#endregion
|
|
25
|
-
export { QueryClientProvider, useQueryClient };
|
|
29
|
+
export { QueryClientProvider, useOptionalQueryClient, useQueryClient };
|
package/dist/tanstack-router.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { canDehydrate, dehydrateQuery, hydrate } from "@raf-query/core";
|
|
2
|
-
import { isRedirect } from "@tanstack/react-router";
|
|
3
2
|
import { Predicate } from "effect";
|
|
3
|
+
import { isRedirect } from "@tanstack/react-router";
|
|
4
4
|
//#region src/tanstack-router.ts
|
|
5
5
|
const noop = () => {};
|
|
6
6
|
function setupServer(options) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@raf-query/react",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "React bindings for raf-query, a render-as-you-fetch query library",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"query",
|
|
@@ -33,12 +33,14 @@
|
|
|
33
33
|
"sideEffects": false,
|
|
34
34
|
"exports": {
|
|
35
35
|
".": "./dist/index.mjs",
|
|
36
|
+
"./devtools": "./dist/devtools.mjs",
|
|
37
|
+
"./devtools/production": "./dist/devtools-production.mjs",
|
|
36
38
|
"./next": "./dist/next.mjs",
|
|
37
39
|
"./rsc": "./dist/rsc.mjs",
|
|
38
40
|
"./tanstack-router": "./dist/tanstack-router.mjs"
|
|
39
41
|
},
|
|
40
42
|
"dependencies": {
|
|
41
|
-
"@raf-query/core": "0.0.
|
|
43
|
+
"@raf-query/core": "0.0.7",
|
|
42
44
|
"effect": "^4.0.1"
|
|
43
45
|
},
|
|
44
46
|
"peerDependencies": {
|