@raf-query/react 0.7.8 → 0.7.10
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 +21 -4
- package/dist/client.d.mts +19 -1
- package/dist/client.mjs +21 -1
- package/dist/index.d.mts +2 -2
- package/dist/use-query.d.mts +47 -6
- package/dist/use-query.mjs +30 -2
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,14 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.7.10](https://github.com/KATT/raf-query/compare/0.7.9...0.7.10) (2026-10-14)
|
|
4
|
+
|
|
5
|
+
### Bug Fixes
|
|
6
|
+
|
|
7
|
+
* **examples:** fit the example apps on a phone screen ([#116](https://github.com/KATT/raf-query/issues/116)) ([1580e00](https://github.com/KATT/raf-query/commit/1580e00f79f4e47e82ddba64e0d22723b9ffb4ba)), references [#112](https://github.com/KATT/raf-query/issues/112)
|
|
8
|
+
* **examples:** open issue pages with prefetch skeletons ([#106](https://github.com/KATT/raf-query/issues/106)) ([db6b5bc](https://github.com/KATT/raf-query/commit/db6b5bcd162dd34073d36c74eaf7618f5012dc31))
|
|
9
|
+
* **examples:** remount the issue page when next or previous changes the id ([#112](https://github.com/KATT/raf-query/issues/112)) ([e4b709b](https://github.com/KATT/raf-query/commit/e4b709b455e4e7bbd85746afe46ecca269a728ee))
|
|
10
|
+
* **react:** type useQuery's promise as Promise ([#118](https://github.com/KATT/raf-query/issues/118)) ([f1b4efe](https://github.com/KATT/raf-query/commit/f1b4efefc7624719b00f0c6e308dd7a72c18c132))
|
|
11
|
+
|
|
12
|
+
## [0.7.9](https://github.com/KATT/raf-query/compare/0.7.8...0.7.9) (2026-10-14)
|
|
13
|
+
|
|
14
|
+
- add a Links table to the pull request template ([#108](https://github.com/KATT/raf-query/issues/108)) ([91247e4](https://github.com/KATT/raf-query/commit/91247e47d4d1b4a0b6e5eb2068602629c3fbce9e))
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
- QueryClient.runPromise, typed Effect errors, and the Effect RPC example ([#103](https://github.com/KATT/raf-query/issues/103)) ([98bd182](https://github.com/KATT/raf-query/commit/98bd1821081313feb1846531feac0dfbe7215d31)), references [#96](https://github.com/KATT/raf-query/issues/96) [#97](https://github.com/KATT/raf-query/issues/97)
|
|
19
|
+
|
|
3
20
|
## [0.7.8](https://github.com/KATT/raf-query/compare/0.7.7...0.7.8) (2026-10-14)
|
|
4
21
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
22
|
+
- inline type assertions next to their SAFETY comments ([#104](https://github.com/KATT/raf-query/issues/104)) ([53fdbdb](https://github.com/KATT/raf-query/commit/53fdbdbfdcb146799b145faadb579bbb45e19139))
|
|
23
|
+
- **release:** skip npm publish when packages did not change ([#101](https://github.com/KATT/raf-query/issues/101)) ([4c6c315](https://github.com/KATT/raf-query/commit/4c6c3156224cfb74a62f6c6e5505c30da7400289)), references [#80](https://github.com/KATT/raf-query/issues/80)
|
|
24
|
+
- say the route waits for the post and comments stream in after ([#102](https://github.com/KATT/raf-query/issues/102)) ([3d0c5f4](https://github.com/KATT/raf-query/commit/3d0c5f4c089921fc178b374179dc23bafdbe899c))
|
|
8
25
|
|
|
9
26
|
### Bug Fixes
|
|
10
27
|
|
|
11
|
-
|
|
28
|
+
- **examples:** start prefetch off and link whole issue rows ([#105](https://github.com/KATT/raf-query/issues/105)) ([cf83077](https://github.com/KATT/raf-query/commit/cf8307701abc6e6ff50304b7245e3bc2af929f3a))
|
|
12
29
|
|
|
13
30
|
## [0.7.7](https://github.com/KATT/raf-query/compare/0.7.6...0.7.7) (2026-10-14)
|
|
14
31
|
|
package/dist/client.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DataOf, DehydrateOptions, DehydratedState, EnsuredQueryHandle, HydrateOptions, InputArgs, InputOf, LoadOptions, PagedQueryFactory, Query, QueryCache, QueryFactory, QueryHandle } from "@raf-query/core";
|
|
2
|
-
import { Layer } from "effect";
|
|
2
|
+
import { Effect, Layer } from "effect";
|
|
3
3
|
//#region src/client.d.ts
|
|
4
4
|
export interface QueryClientConfig<TServices = never> extends QueryCache.Config {
|
|
5
5
|
/**
|
|
@@ -10,6 +10,13 @@ export interface QueryClientConfig<TServices = never> extends QueryCache.Config
|
|
|
10
10
|
readonly layer?: Layer.Layer<TServices>;
|
|
11
11
|
/** Called with every failed request, including failed prefetches. */
|
|
12
12
|
onError?(error: unknown, query: Query.Query): void;
|
|
13
|
+
/**
|
|
14
|
+
* When `false`, `useQuery` for an Effect `queryFn` (a typed `ErrorOf`)
|
|
15
|
+
* returns a `Result` in the first tuple slot instead of a promise that
|
|
16
|
+
* rejects. Defaults to `false` when `layer` is set, `true` otherwise.
|
|
17
|
+
* Promise `queryFn`s always throw; this flag does not apply to them.
|
|
18
|
+
*/
|
|
19
|
+
readonly throwOnError?: boolean;
|
|
13
20
|
}
|
|
14
21
|
/**
|
|
15
22
|
* The query cache, with Promise methods. Create one per request on the
|
|
@@ -22,6 +29,11 @@ export declare class QueryClient<TServices = never> {
|
|
|
22
29
|
/** The Effect-native cache behind this client. */
|
|
23
30
|
readonly effect: QueryCache.QueryCache;
|
|
24
31
|
readonly isServer: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Default for Effect `useQuery` reads. See
|
|
34
|
+
* {@link QueryClientConfig.throwOnError}.
|
|
35
|
+
*/
|
|
36
|
+
readonly throwOnError: boolean;
|
|
25
37
|
constructor(config?: QueryClientConfig<TServices>);
|
|
26
38
|
/**
|
|
27
39
|
* The cache entry for a factory call, created if missing. Doesn't fetch.
|
|
@@ -91,6 +103,12 @@ export declare class QueryClient<TServices = never> {
|
|
|
91
103
|
* Returns the matching unmount. Providers call this; mounts are counted.
|
|
92
104
|
*/
|
|
93
105
|
mount(): () => void;
|
|
106
|
+
/**
|
|
107
|
+
* Run an Effect with the cache (`QueryCache`) and the services of
|
|
108
|
+
* `config.layer`, as `Effect.runPromise` does: it rejects with the
|
|
109
|
+
* Effect's failure, or its defect. For an app's loaders and actions.
|
|
110
|
+
*/
|
|
111
|
+
runPromise<A, E>(effect: Effect.Effect<A, E, QueryCache.QueryCache | TServices>): Promise<A>;
|
|
94
112
|
/**
|
|
95
113
|
* Close the client: interrupt every request and timer, and release the
|
|
96
114
|
* layer's services. Resolves once the layer's finalizers have run.
|
package/dist/client.mjs
CHANGED
|
@@ -34,15 +34,27 @@ var QueryClient = class {
|
|
|
34
34
|
effect;
|
|
35
35
|
isServer;
|
|
36
36
|
/**
|
|
37
|
+
* Default for Effect `useQuery` reads. See
|
|
38
|
+
* {@link QueryClientConfig.throwOnError}.
|
|
39
|
+
*/
|
|
40
|
+
throwOnError;
|
|
41
|
+
/**
|
|
42
|
+
* The cache and the layer's services. Typed `never` so clients of any
|
|
43
|
+
* `TServices` stay assignable to each other; `runPromise` restores it.
|
|
44
|
+
*/
|
|
45
|
+
#context;
|
|
46
|
+
/**
|
|
37
47
|
* Owns the cache's fibers, mounts and the layer's services; lives until
|
|
38
48
|
* `dispose()`.
|
|
39
49
|
*/
|
|
40
50
|
#scope = Scope.makeUnsafe();
|
|
41
51
|
constructor(config = {}) {
|
|
42
52
|
const cache = QueryCache.layer(config);
|
|
43
|
-
const services = Effect.runSync(Layer.buildWithScope(config.layer ? Layer.
|
|
53
|
+
const services = Effect.runSync(Layer.buildWithScope(config.layer ? Layer.provideMerge(cache, config.layer) : cache, this.#scope));
|
|
54
|
+
this.#context = services;
|
|
44
55
|
this.effect = Context.get(services, QueryCache.QueryCache);
|
|
45
56
|
this.isServer = this.effect.isServer;
|
|
57
|
+
this.throwOnError = config.throwOnError ?? config.layer === void 0;
|
|
46
58
|
const onError = config.onError;
|
|
47
59
|
if (onError) this.effect.subscribeUnsafe((event) => {
|
|
48
60
|
if (event.type === "failed") onError(event.error, event.query);
|
|
@@ -134,6 +146,14 @@ var QueryClient = class {
|
|
|
134
146
|
};
|
|
135
147
|
}
|
|
136
148
|
/**
|
|
149
|
+
* Run an Effect with the cache (`QueryCache`) and the services of
|
|
150
|
+
* `config.layer`, as `Effect.runPromise` does: it rejects with the
|
|
151
|
+
* Effect's failure, or its defect. For an app's loaders and actions.
|
|
152
|
+
*/
|
|
153
|
+
runPromise(effect) {
|
|
154
|
+
return Effect.runPromiseWith(this.#context)(effect);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
137
157
|
* Close the client: interrupt every request and timer, and release the
|
|
138
158
|
* layer's services. Resolves once the layer's finalizers have run.
|
|
139
159
|
*/
|
package/dist/index.d.mts
CHANGED
|
@@ -2,7 +2,7 @@ import { QueryClient, QueryClientConfig, dehydrate, hydrate } from "./client.mjs
|
|
|
2
2
|
import { Settled, allKeyed } from "./all-keyed.mjs";
|
|
3
3
|
import { QueryClientProvider, useQueryClient } from "./provider.mjs";
|
|
4
4
|
import { useIsFetching } from "./use-is-fetching.mjs";
|
|
5
|
-
import { PagingControls, QueryControls, UseEnsuredQueryResult, UseQueryControls, UseQueryOptions, UseQueryResult, useQuery } from "./use-query.mjs";
|
|
5
|
+
import { HasTypedError, PagingControls, QueryControls, ThrowsOnError, UseEnsuredQueryResult, UseQueryControls, UseQueryOptions, UseQueryResult, UseQueryThrowOptions, useQuery } from "./use-query.mjs";
|
|
6
6
|
import { useWaterfall } from "./use-waterfall.mjs";
|
|
7
7
|
export * from "@raf-query/core";
|
|
8
|
-
export { type PagingControls, QueryClient, type QueryClientConfig, QueryClientProvider, type QueryControls, type Settled, type UseEnsuredQueryResult, type UseQueryControls, type UseQueryOptions, type UseQueryResult, allKeyed, dehydrate, hydrate, useIsFetching, useQuery, useQueryClient, useWaterfall };
|
|
8
|
+
export { type HasTypedError, type PagingControls, QueryClient, type QueryClientConfig, QueryClientProvider, type QueryControls, type Settled, type ThrowsOnError, type UseEnsuredQueryResult, type UseQueryControls, type UseQueryOptions, type UseQueryResult, type UseQueryThrowOptions, allKeyed, dehydrate, hydrate, useIsFetching, useQuery, useQueryClient, useWaterfall };
|
package/dist/use-query.d.mts
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
import "./client.mjs";
|
|
2
|
-
import { DataOf, EnsuredQueryHandle, InputOf, IsPaged, Query, QueryFactory, QueryHandle, QueryKey, QueryObserver
|
|
2
|
+
import { DataOf, EnsuredQueryHandle, ErrorOf, InputOf, IsPaged, Query, QueryFactory, QueryHandle, QueryKey, QueryObserver } from "@raf-query/core";
|
|
3
|
+
import { Result } from "effect";
|
|
3
4
|
//#region src/use-query.d.ts
|
|
4
5
|
/** `refetchInterval`, `refetchOnWindowFocus`, `refetchOnReconnect`, `whilePending`. */
|
|
5
6
|
export type UseQueryOptions = QueryObserver.Options;
|
|
7
|
+
/**
|
|
8
|
+
* `throwOnError` only when {@link ErrorOf} is typed (Effect `queryFn`).
|
|
9
|
+
* Promise factories always throw; the option is not part of their options.
|
|
10
|
+
*/
|
|
11
|
+
export type UseQueryThrowOptions<TFactory extends QueryFactory> = HasTypedError<TFactory> extends true ? {
|
|
12
|
+
/**
|
|
13
|
+
* When `false`, the first tuple slot is a `Result` instead of a
|
|
14
|
+
* rejecting promise. Omit to use the client's default (`false`
|
|
15
|
+
* with a `layer`, `true` without).
|
|
16
|
+
*/
|
|
17
|
+
readonly throwOnError?: boolean;
|
|
18
|
+
} : {
|
|
19
|
+
readonly throwOnError?: never;
|
|
20
|
+
};
|
|
6
21
|
/** The second element of {@link useQuery}'s result. */
|
|
7
22
|
export interface QueryControls<TFactory extends QueryFactory> {
|
|
8
23
|
/** Error of the last failed request. Existing data stays readable. */
|
|
@@ -36,9 +51,21 @@ export interface PagingControls {
|
|
|
36
51
|
readonly isFetchingPreviousPage: boolean;
|
|
37
52
|
}
|
|
38
53
|
export type UseQueryControls<TFactory extends QueryFactory> = IsPaged<TFactory> extends true ? QueryControls<TFactory> & PagingControls : QueryControls<TFactory>;
|
|
39
|
-
|
|
54
|
+
/**
|
|
55
|
+
* `true` when the factory's error channel is typed (Effect `queryFn`), not
|
|
56
|
+
* `unknown` (promise / plain value).
|
|
57
|
+
*/
|
|
58
|
+
export type HasTypedError<TFactory extends QueryFactory> = unknown extends ErrorOf<TFactory> ? false : true;
|
|
59
|
+
/**
|
|
60
|
+
* Whether this read throws on failure. Untyped errors always throw. Typed
|
|
61
|
+
* errors follow `options.throwOnError`, else the client's default (`false`
|
|
62
|
+
* with a `layer`, `true` without). Types treat an omitted flag as `false`
|
|
63
|
+
* (Result), matching a client with a `layer`.
|
|
64
|
+
*/
|
|
65
|
+
export type ThrowsOnError<TFactory extends QueryFactory, TThrow extends boolean | undefined> = HasTypedError<TFactory> extends false ? true : TThrow extends boolean ? TThrow : false;
|
|
66
|
+
export type UseQueryResult<TFactory extends QueryFactory, TThrow extends boolean | undefined = undefined> = ThrowsOnError<TFactory, TThrow> extends true ? readonly [promise: Promise<DataOf<TFactory>>, controls: UseQueryControls<TFactory>] : readonly [promise: Promise<Result.Result<DataOf<TFactory>, ErrorOf<TFactory>>>, controls: UseQueryControls<TFactory>];
|
|
40
67
|
/** {@link useQuery}'s result for a handle from `ensure`. */
|
|
41
|
-
export type UseEnsuredQueryResult<TFactory extends QueryFactory> = readonly [data: DataOf<TFactory>, controls: UseQueryControls<TFactory>];
|
|
68
|
+
export type UseEnsuredQueryResult<TFactory extends QueryFactory, TThrow extends boolean | undefined = undefined> = ThrowsOnError<TFactory, TThrow> extends true ? readonly [data: DataOf<TFactory>, controls: UseQueryControls<TFactory>] : readonly [result: Result.Result<DataOf<TFactory>, ErrorOf<TFactory>>, controls: UseQueryControls<TFactory>];
|
|
42
69
|
/**
|
|
43
70
|
* Read the query a handle points at. Returns a promise to unwrap with
|
|
44
71
|
* `use()` and the query's controls:
|
|
@@ -48,13 +75,23 @@ export type UseEnsuredQueryResult<TFactory extends QueryFactory> = readonly [dat
|
|
|
48
75
|
* const post = use(postPromise);
|
|
49
76
|
* ```
|
|
50
77
|
*
|
|
78
|
+
* For an Effect `queryFn` (typed {@link ErrorOf}), with the client's
|
|
79
|
+
* `throwOnError: false` (the default when it has a `layer`), the first slot
|
|
80
|
+
* is a `Result` instead — `use()` never throws:
|
|
81
|
+
*
|
|
82
|
+
* ```tsx
|
|
83
|
+
* const [issueResult, issueState] = useQuery(issueQuery, loaderData.issue);
|
|
84
|
+
* const issue = use(issueResult);
|
|
85
|
+
* Result.match(issue, { onFailure: …, onSuccess: … });
|
|
86
|
+
* ```
|
|
87
|
+
*
|
|
51
88
|
* The promise is stable per data version and already fulfilled once the
|
|
52
89
|
* cache has data, so `use()` only suspends while there is nothing to show.
|
|
53
90
|
* The component re-renders when the promise changes, and when a control it
|
|
54
91
|
* has read changes; changes to controls it never reads are skipped.
|
|
55
92
|
*
|
|
56
93
|
* A handle from `ensure` had its data loaded before the route rendered, so
|
|
57
|
-
* the hook returns the data itself:
|
|
94
|
+
* the hook returns the data (or `Result`) itself:
|
|
58
95
|
*
|
|
59
96
|
* ```tsx
|
|
60
97
|
* const [post, postState] = useQuery(postQuery, loaderData.post);
|
|
@@ -63,6 +100,10 @@ export type UseEnsuredQueryResult<TFactory extends QueryFactory> = readonly [dat
|
|
|
63
100
|
* If that entry has lost its data since (garbage-collected, `reset`), the
|
|
64
101
|
* hook suspends until it is refetched.
|
|
65
102
|
*/
|
|
66
|
-
export declare function useQuery<TFactory extends QueryFactory>(factory: TFactory, handle: EnsuredQueryHandle<TFactory>, options?: UseQueryOptions
|
|
67
|
-
|
|
103
|
+
export declare function useQuery<TFactory extends QueryFactory, const TThrow extends boolean | undefined = undefined>(factory: TFactory, handle: EnsuredQueryHandle<TFactory>, options?: UseQueryOptions & UseQueryThrowOptions<TFactory> & {
|
|
104
|
+
readonly throwOnError?: TThrow;
|
|
105
|
+
}): UseEnsuredQueryResult<TFactory, TThrow>;
|
|
106
|
+
export declare function useQuery<TFactory extends QueryFactory, const TThrow extends boolean | undefined = undefined>(factory: TFactory, handle: QueryHandle<TFactory>, options?: UseQueryOptions & UseQueryThrowOptions<TFactory> & {
|
|
107
|
+
readonly throwOnError?: TThrow;
|
|
108
|
+
}): UseQueryResult<TFactory, TThrow>;
|
|
68
109
|
//#endregion
|
package/dist/use-query.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useQueryClient } from "./provider.mjs";
|
|
3
3
|
import { assertSent } from "./browser-requests.mjs";
|
|
4
|
-
import { Query, QueryObserver } from "@raf-query/core";
|
|
5
|
-
import { Effect } from "effect";
|
|
4
|
+
import { Query, QueryObserver, fulfilled, pending } from "@raf-query/core";
|
|
5
|
+
import { Effect, Result } from "effect";
|
|
6
6
|
import { use, useEffect, useState, useSyncExternalStore } from "react";
|
|
7
7
|
//#region src/use-query.ts
|
|
8
8
|
function queryFor(client, factory, input) {
|
|
@@ -34,6 +34,29 @@ function storeFor(query, options) {
|
|
|
34
34
|
subscribe: (onChange) => QueryObserver.subscribeUnsafe(observer, onChange)
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* One `Result` thenable per data thenable, so `use()` sees a stable identity
|
|
39
|
+
* across re-renders the same way it does for the data promise.
|
|
40
|
+
*/
|
|
41
|
+
const resultThenables = /* @__PURE__ */ new WeakMap();
|
|
42
|
+
function asResultThenable(promise) {
|
|
43
|
+
const cached = resultThenables.get(promise);
|
|
44
|
+
if (cached) return cached;
|
|
45
|
+
let next;
|
|
46
|
+
if (promise.status === "fulfilled") next = fulfilled(Result.succeed(promise.value));
|
|
47
|
+
else if (promise.status === "rejected") next = fulfilled(Result.fail(promise.reason));
|
|
48
|
+
else {
|
|
49
|
+
const deferred = pending();
|
|
50
|
+
promise.then((value) => {
|
|
51
|
+
deferred.resolve(Result.succeed(value));
|
|
52
|
+
}, (error) => {
|
|
53
|
+
deferred.resolve(Result.fail(error));
|
|
54
|
+
});
|
|
55
|
+
next = deferred.promise;
|
|
56
|
+
}
|
|
57
|
+
resultThenables.set(promise, next);
|
|
58
|
+
return next;
|
|
59
|
+
}
|
|
37
60
|
function useQuery(factory, handle, options = {}) {
|
|
38
61
|
const client = useQueryClient();
|
|
39
62
|
const query = queryFor(client, factory, handle.input);
|
|
@@ -64,6 +87,11 @@ function useQuery(factory, handle, options = {}) {
|
|
|
64
87
|
]);
|
|
65
88
|
QueryObserver.trackProp(observer, "promise");
|
|
66
89
|
const controls = withPromiseCommands(QueryObserver.trackResult(observer, result));
|
|
90
|
+
if (!(options.throwOnError ?? client.throwOnError)) {
|
|
91
|
+
const resultPromise = asResultThenable(result.promise);
|
|
92
|
+
if (handle.ensured === true) return [use(resultPromise), controls];
|
|
93
|
+
return [resultPromise, controls];
|
|
94
|
+
}
|
|
67
95
|
if (handle.ensured === true) return [use(result.promise), controls];
|
|
68
96
|
return [result.promise, controls];
|
|
69
97
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@raf-query/react",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.10",
|
|
4
4
|
"description": "React bindings for raf-query, a render-as-you-fetch query library",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"query",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
}
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@raf-query/core": "0.7.
|
|
49
|
+
"@raf-query/core": "0.7.10"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"@tanstack/react-router": "^1.170.0",
|