@raf-query/core 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/client.d.mts +14 -1
- package/dist/client.mjs +10 -0
- package/dist/query.d.mts +9 -0
- package/dist/query.mjs +24 -2
- package/package.json +1 -1
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
|
|
package/dist/client.d.mts
CHANGED
|
@@ -17,7 +17,8 @@ export interface QueryClientConfig {
|
|
|
17
17
|
/** Emitted by {@link QueryClient.subscribe} for every cache change. */
|
|
18
18
|
export type CacheEvent = {
|
|
19
19
|
readonly query: Query;
|
|
20
|
-
|
|
20
|
+
/** `observers`: a reader subscribed or unsubscribed. */
|
|
21
|
+
readonly type: "added" | "observers" | "removed" | "updated";
|
|
21
22
|
} | {
|
|
22
23
|
/** A request failed (after retries), with this error. */
|
|
23
24
|
readonly error: unknown;
|
|
@@ -80,6 +81,18 @@ export declare class QueryClient {
|
|
|
80
81
|
*/
|
|
81
82
|
invalidate<TFactory extends QueryFactory>(factory: TFactory, ...args: InputArgs<TFactory>): Promise<void>;
|
|
82
83
|
invalidate(filter: QueryFilter): Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* Drop queries' data and errors, as if they were never fetched. Read ones
|
|
86
|
+
* suspend and refetch; resolves once those refetches settle.
|
|
87
|
+
*/
|
|
88
|
+
reset<TFactory extends QueryFactory>(factory: TFactory, ...args: InputArgs<TFactory>): Promise<void>;
|
|
89
|
+
reset(filter: QueryFilter): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* Remove queries from the cache and abort their requests. Readers keep the
|
|
92
|
+
* removed entry until they render again.
|
|
93
|
+
*/
|
|
94
|
+
remove<TFactory extends QueryFactory>(factory: TFactory, ...args: InputArgs<TFactory>): void;
|
|
95
|
+
remove(filter: QueryFilter): void;
|
|
83
96
|
/** Write data for a factory call. Readers re-render without suspending. */
|
|
84
97
|
setData<TFactory extends QueryFactory>(factory: TFactory, input: InputOf<TFactory>, updater: Updater<DataOf<TFactory>>): void;
|
|
85
98
|
/** Cached data for a factory call, or `undefined`. */
|
package/dist/client.mjs
CHANGED
|
@@ -46,6 +46,10 @@ var QueryClient = class {
|
|
|
46
46
|
}),
|
|
47
47
|
defaults: config.defaults ?? {},
|
|
48
48
|
isServer: this.isServer,
|
|
49
|
+
observersChanged: (query) => emit({
|
|
50
|
+
query,
|
|
51
|
+
type: "observers"
|
|
52
|
+
}),
|
|
49
53
|
remove: (query) => {
|
|
50
54
|
if (this.#cache.get(query.hash) === query) {
|
|
51
55
|
this.#cache.delete(query.hash);
|
|
@@ -109,6 +113,12 @@ var QueryClient = class {
|
|
|
109
113
|
async invalidate(target, input) {
|
|
110
114
|
await Promise.all(this.#match(target, input).map((entry) => entry.invalidate()));
|
|
111
115
|
}
|
|
116
|
+
async reset(target, input) {
|
|
117
|
+
await Promise.all(this.#match(target, input).map((entry) => entry.reset()));
|
|
118
|
+
}
|
|
119
|
+
remove(target, input) {
|
|
120
|
+
for (const entry of this.#match(target, input)) this.#host.remove(entry);
|
|
121
|
+
}
|
|
112
122
|
/** Write data for a factory call. Readers re-render without suspending. */
|
|
113
123
|
setData(factory, input, updater) {
|
|
114
124
|
const entry = this.#build(factory, input);
|
package/dist/query.d.mts
CHANGED
|
@@ -57,7 +57,11 @@ export interface Query<TData = unknown, TFactory extends QueryFactory = QueryFac
|
|
|
57
57
|
hasNextPage(): boolean;
|
|
58
58
|
hasPreviousPage(): boolean;
|
|
59
59
|
invalidate(): Promise<void>;
|
|
60
|
+
/** Staleness as of now; `getState().isStale` is only kept current while read. */
|
|
61
|
+
isStaleNow(): boolean;
|
|
60
62
|
readonly key: QueryKey;
|
|
63
|
+
/** Readers currently subscribed. */
|
|
64
|
+
readonly observerCount: number;
|
|
61
65
|
/** Options from the latest factory call; `undefined` for an entry only hydrated so far. */
|
|
62
66
|
readonly options: AnyQueryOptions | undefined;
|
|
63
67
|
/**
|
|
@@ -67,6 +71,11 @@ export interface Query<TData = unknown, TFactory extends QueryFactory = QueryFac
|
|
|
67
71
|
read(): Promise<TData>;
|
|
68
72
|
/** Refetch now; resolves when the request settles. */
|
|
69
73
|
refetch(): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Drop data and errors, back to the state of a new entry. Readers suspend
|
|
76
|
+
* and refetch; resolves when that refetch settles.
|
|
77
|
+
*/
|
|
78
|
+
reset(): Promise<void>;
|
|
70
79
|
/** Listen for state changes. Subscribed entries count as read. */
|
|
71
80
|
subscribe(listener: () => void, observer?: QueryObserver): () => void;
|
|
72
81
|
}
|
package/dist/query.mjs
CHANGED
|
@@ -95,19 +95,23 @@ var QueryEntry = class {
|
|
|
95
95
|
get isObserved() {
|
|
96
96
|
return this.#listeners.size > 0;
|
|
97
97
|
}
|
|
98
|
+
get observerCount() {
|
|
99
|
+
return this.#listeners.size;
|
|
100
|
+
}
|
|
98
101
|
getState = () => this.#state;
|
|
99
|
-
/** Staleness as of now; `getState().isStale` is only refreshed for read entries. */
|
|
100
102
|
isStaleNow = () => this.#computeStale(this.#state);
|
|
101
103
|
subscribe = (listener, observer = defaultObserver) => {
|
|
102
104
|
this.#listeners.set(listener, observer);
|
|
103
105
|
this.#clearGc();
|
|
104
106
|
this.#scheduleStale();
|
|
107
|
+
this.#host.observersChanged(this);
|
|
105
108
|
return () => {
|
|
106
|
-
this.#listeners.delete(listener);
|
|
109
|
+
if (!this.#listeners.delete(listener)) return;
|
|
107
110
|
if (this.#listeners.size === 0) {
|
|
108
111
|
clearTimeout(this.#staleTimer);
|
|
109
112
|
this.#scheduleGc();
|
|
110
113
|
}
|
|
114
|
+
this.#host.observersChanged(this);
|
|
111
115
|
};
|
|
112
116
|
};
|
|
113
117
|
/** Attach the options and input of the latest factory call. */
|
|
@@ -158,6 +162,24 @@ var QueryEntry = class {
|
|
|
158
162
|
if (!this.isObserved || !this.#options) return Promise.resolve();
|
|
159
163
|
return this.fetch({ restart: true }).then(noop, noop);
|
|
160
164
|
};
|
|
165
|
+
reset = () => {
|
|
166
|
+
clearTimeout(this.#errorShownTimer);
|
|
167
|
+
this.#errorShownTimer = void 0;
|
|
168
|
+
this.#errorShown = false;
|
|
169
|
+
this.#pending ??= pending();
|
|
170
|
+
this.#set({
|
|
171
|
+
data: void 0,
|
|
172
|
+
error: null,
|
|
173
|
+
failureCount: 0,
|
|
174
|
+
hasData: false,
|
|
175
|
+
isInvalidated: false,
|
|
176
|
+
promise: this.#pending.promise,
|
|
177
|
+
status: "pending",
|
|
178
|
+
updatedAt: 0
|
|
179
|
+
});
|
|
180
|
+
if (!this.#options || !this.isObserved && !this.#inFlight) return Promise.resolve();
|
|
181
|
+
return this.fetch({ restart: true }).then(noop, noop);
|
|
182
|
+
};
|
|
161
183
|
/** Write data directly, as if a request had returned it. */
|
|
162
184
|
setData(data, updatedAt = Date.now()) {
|
|
163
185
|
this.#succeed(data, updatedAt);
|