@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 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
- * 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))
6
- * **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)
7
- * 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))
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
- * **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))
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.provide(cache, config.layer) : cache, this.#scope));
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 };
@@ -1,8 +1,23 @@
1
1
  import "./client.mjs";
2
- import { DataOf, EnsuredQueryHandle, InputOf, IsPaged, Query, QueryFactory, QueryHandle, QueryKey, QueryObserver, Thenable } from "@raf-query/core";
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
- export type UseQueryResult<TFactory extends QueryFactory> = readonly [promise: Thenable<DataOf<TFactory>>, controls: UseQueryControls<TFactory>];
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): UseEnsuredQueryResult<TFactory>;
67
- export declare function useQuery<TFactory extends QueryFactory>(factory: TFactory, handle: QueryHandle<TFactory>, options?: UseQueryOptions): UseQueryResult<TFactory>;
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
@@ -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.8",
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.8"
49
+ "@raf-query/core": "0.7.10"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "@tanstack/react-router": "^1.170.0",