@raf-query/core 0.0.5 → 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 CHANGED
@@ -1,12 +1,22 @@
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
+
9
+ ## [0.0.6](https://github.com/KATT/raf-query/compare/0.0.5...0.0.6) (2026-10-12)
10
+
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))
12
+
3
13
  ## [0.0.5](https://github.com/KATT/raf-query/compare/0.0.4...0.0.5) (2026-10-12)
4
14
 
5
- * **examples:** use Zod instead of Effect Schema ([#6](https://github.com/KATT/raf-query/issues/6)) ([7068cbe](https://github.com/KATT/raf-query/commit/7068cbe95e693787f22908b0ed678f6b7b05a550))
15
+ - **examples:** use Zod instead of Effect Schema ([#6](https://github.com/KATT/raf-query/issues/6)) ([7068cbe](https://github.com/KATT/raf-query/commit/7068cbe95e693787f22908b0ed678f6b7b05a550))
6
16
 
7
17
  ### Bug Fixes
8
18
 
9
- * **react:** HydrationBoundary defers newer data for entries already on screen ([#5](https://github.com/KATT/raf-query/issues/5)) ([881a535](https://github.com/KATT/raf-query/commit/881a53544f65e2ffe858a0d010d7109427b93d55))
19
+ - **react:** HydrationBoundary defers newer data for entries already on screen ([#5](https://github.com/KATT/raf-query/issues/5)) ([881a535](https://github.com/KATT/raf-query/commit/881a53544f65e2ffe858a0d010d7109427b93d55))
10
20
 
11
21
  ## [0.0.4](https://github.com/KATT/raf-query/compare/0.0.3...0.0.4) (2026-10-12)
12
22
 
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 | Use |
478
- | --------------------------------------- | ------------------------------------------------------------------------------- |
479
- | `new QueryClient(config?)` | `config.defaults` (query options below), `config.onError`, `config.isServer` |
480
- | `prefetch`, `ensure`, `fetch` | [Start queries](#starting-queries) |
481
- | `invalidate`, `setData`, `getData` | [Write and read the cache](#mutations) |
482
- | `effect` | [Effect versions](#effect) of `ensure`, `fetch`, `invalidate`, plus `watch` |
483
- | `subscribe(listener)` | Cache events: `added`, `updated`, `removed`, and `failed` (with the `error`) |
484
- | `getAll()`, `getQuery(factory, input?)` | Cache entries, for devtools and integrations |
485
- | `clear()` | Drop every entry and abort in-flight requests |
486
- | `mount()` | Start window-focus and reconnect refetching. The providers call it |
487
- | `isServer` | Detected from `window`. Server clients don't retry, never gc, skip stale timers |
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
- readonly type: "added" | "removed" | "updated";
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raf-query/core",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "Framework-agnostic core of raf-query, a render-as-you-fetch query library",
5
5
  "keywords": [
6
6
  "query",