@raf-query/core 0.1.2 → 0.1.4
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 +13 -1
- package/README.md +58 -18
- package/dist/entry.d.mts +15 -2
- package/dist/entry.mjs +72 -13
- package/dist/hydration.d.mts +3 -1
- package/dist/hydration.mjs +9 -3
- package/dist/index.d.mts +2 -2
- package/dist/options.d.mts +29 -0
- package/dist/query.d.mts +7 -2
- package/dist/query.mjs +10 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.4](https://github.com/KATT/raf-query/compare/0.1.3...0.1.4) (2026-10-12)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
* **core:** add per-query meta ([#26](https://github.com/KATT/raf-query/issues/26)) ([863c915](https://github.com/KATT/raf-query/commit/863c915d884bdfa3bcb3053da348d51aa0e697cf))
|
|
8
|
+
|
|
9
|
+
## [0.1.3](https://github.com/KATT/raf-query/compare/0.1.2...0.1.3) (2026-10-12)
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
- **core:** cancel requests nobody reads anymore ([#27](https://github.com/KATT/raf-query/issues/27)) ([c3c1e4b](https://github.com/KATT/raf-query/commit/c3c1e4bb9250164b6df17d7d4b7bd4043f908961))
|
|
14
|
+
|
|
3
15
|
## [0.1.2](https://github.com/KATT/raf-query/compare/0.1.1...0.1.2) (2026-10-12)
|
|
4
16
|
|
|
5
17
|
### Features
|
|
6
18
|
|
|
7
|
-
|
|
19
|
+
- add isFetching(filter?) and useIsFetching(filter?) ([#25](https://github.com/KATT/raf-query/issues/25)) ([70c3b98](https://github.com/KATT/raf-query/commit/70c3b981027f717b1d619d5a2b23822f685c032a))
|
|
8
20
|
|
|
9
21
|
## [0.1.1](https://github.com/KATT/raf-query/compare/0.1.0...0.1.1) (2026-10-12)
|
|
10
22
|
|
package/README.md
CHANGED
|
@@ -66,10 +66,46 @@ export function commentsQuery(input: { slug: string }) {
|
|
|
66
66
|
|
|
67
67
|
Factories must take at most one argument; the type rejects two or more. The single argument is the query's **input** and must be serializable, because it is what a handle carries.
|
|
68
68
|
|
|
69
|
-
A `queryFn` returns a value, a promise, or an [Effect](#effect). It receives `queryKey` and an abort `signal`; paged ones also get `pageParam` and `direction`. TypeScript infers the page type from `queryFn`, so if `getNextPageParam` comes first in the object literal (sorted keys), pass the types instead: `infiniteQueryOptions<CommentPage, number>({ ... })`.
|
|
69
|
+
A `queryFn` returns a value, a promise, or an [Effect](#effect). It receives `queryKey`, the query's [`meta`](#query-meta) and an abort `signal`; paged ones also get `pageParam` and `direction`. TypeScript infers the page type from `queryFn`, so if `getNextPageParam` comes first in the object literal (sorted keys), pass the types instead: `infiniteQueryOptions<CommentPage, number>({ ... })`.
|
|
70
70
|
|
|
71
71
|
The factory is the only place a query is declared paged. Every other API — `prefetch`, `ensure`, `fetch`, `useQuery`, `useWaterfall`, `invalidate`, `setData` — takes either kind and infers the rest from the factory's type.
|
|
72
72
|
|
|
73
|
+
### Query meta
|
|
74
|
+
|
|
75
|
+
`meta` attaches anything you want to know about a query elsewhere. It reaches the `queryFn` context, `Query.metaUnsafe(query)` (from the latest factory call) and the devtools. A common use is a per-query message for one global error handler:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import { Query, QueryClient, queryOptions } from "@raf-query/react";
|
|
79
|
+
|
|
80
|
+
export function issuesQuery() {
|
|
81
|
+
return queryOptions({
|
|
82
|
+
queryKey: ["issues"],
|
|
83
|
+
queryFn: () => listIssues(),
|
|
84
|
+
meta: { errorMessage: "Couldn't load issues" },
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const queryClient = new QueryClient({
|
|
89
|
+
onError: (error, query) => {
|
|
90
|
+
toast.error(
|
|
91
|
+
Query.metaUnsafe(query)?.errorMessage ?? "Something went wrong",
|
|
92
|
+
);
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`meta` is `Record<string, unknown>` by default. Type it for the whole app by augmenting `Register` in the package you import from (`@raf-query/react` re-exports it from `@raf-query/core`, so either works):
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
declare module "@raf-query/react" {
|
|
101
|
+
interface Register {
|
|
102
|
+
queryMeta: { errorMessage?: string };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`config.defaults` takes `meta` too, for queries that set none; a query's own `meta` replaces the default as a whole. `meta` is sent with dehydrated state, so keep it serializable: a hydrated entry reports the sender's `meta` until the receiving side calls its factory.
|
|
108
|
+
|
|
73
109
|
## Starting queries
|
|
74
110
|
|
|
75
111
|
All three verbs live on the query client and accept a factory plus its input.
|
|
@@ -593,7 +629,7 @@ A button in the corner opens a panel docked to the edge of the viewport. The pan
|
|
|
593
629
|
| `fresh` | Read, and within `staleTime` |
|
|
594
630
|
| `inactive` | Nothing reads it; it's removed at `gcTime` |
|
|
595
631
|
|
|
596
|
-
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`.
|
|
632
|
+
Select an entry to see its key, input, meta, 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`.
|
|
597
633
|
|
|
598
634
|
`QueryDevtools` props, all optional:
|
|
599
635
|
|
|
@@ -639,29 +675,33 @@ Query options, accepted by `queryOptions`, `infiniteQueryOptions` and `config.de
|
|
|
639
675
|
| `retryDelay` (ms, or a function of the failure count) | Doubling from 1 s, at most 30 s |
|
|
640
676
|
| `refetchOnWindowFocus`, `refetchOnReconnect` | `true` (only read, stale queries refetch) |
|
|
641
677
|
| `structuralSharing` (keep deeply equal parts of data) | `true` |
|
|
678
|
+
| [`meta`](#query-meta) | None |
|
|
642
679
|
|
|
643
680
|
`infiniteQueryOptions` also takes `getPreviousPageParam` and `maxPages`.
|
|
644
681
|
|
|
645
682
|
In the browser, retries pause while `navigator.onLine` is `false`: after the retry delay, the next attempt waits for the window's `online` event. The request stays fetching meanwhile, and cancelling it (`remove`, `clear`, a restart) stops the wait. The first attempt runs regardless.
|
|
646
683
|
|
|
684
|
+
When the last reader of a query unsubscribes during a background refetch, a `queryFn` that read its `signal` (or returned an Effect) is aborted: the entry keeps its data and goes back to how it was before the request, and `fetch` callers waiting on it get the cached data. Other requests, including every first load (a render may be suspended on it), finish so their result is cached, but don't retry unless a reader comes back. Requests nothing has subscribed to yet, like a loader's `prefetch`, keep running.
|
|
685
|
+
|
|
647
686
|
`@raf-query/core`:
|
|
648
687
|
|
|
649
|
-
| Export | Use
|
|
650
|
-
| ------------------------------------------------------------ |
|
|
651
|
-
| `QueryCache` | The cache service (`qc.effect`): `make`, `layer`, and the verbs as Effects
|
|
652
|
-
| `Query` | Cache entries: `Query.fetch`, `Query.changes`, `Query.getUnsafe`, …
|
|
653
|
-
| `Platform` | The page the cache watches (focus, network); swap it in tests
|
|
654
|
-
| `queryOptions(options)` | Typed options for a query factory
|
|
655
|
-
| `infiniteQueryOptions(options)` | Typed options for a paged query factory
|
|
656
|
-
| `hashKey(key)`, `matchesKey(key, prefix)` | Key helpers
|
|
657
|
-
| `QueryObserver` | One reader's view of a query, with tracked results; for bindings
|
|
658
|
-
| `Query.CancelledError` | Failure of a request whose entry was removed or cleared before it settled
|
|
659
|
-
| `RedactedError` | What a streamed pending query rejects with when its server request failed
|
|
660
|
-
| `QueryHandle<TFactory>` | Handle type for props
|
|
661
|
-
| `EnsuredQueryHandle<TFactory>` | Handle type for props fed by `ensure`
|
|
662
|
-
| `Thenable<T>` | Promise with `status` / `value` / `reason`, read by `use()`
|
|
663
|
-
| `DataOf<TFactory>`, `InputOf<TFactory>`, `ErrorOf<TFactory>` | A factory's data, input and error types
|
|
664
|
-
| `ServicesOf<TFactory>` | The services a factory's `queryFn` Effect needs; `never` for promises
|
|
688
|
+
| Export | Use |
|
|
689
|
+
| ------------------------------------------------------------ | --------------------------------------------------------------------------------------- |
|
|
690
|
+
| `QueryCache` | The cache service (`qc.effect`): `make`, `layer`, and the verbs as Effects |
|
|
691
|
+
| `Query` | Cache entries: `Query.fetch`, `Query.changes`, `Query.getUnsafe`, `Query.metaUnsafe`, … |
|
|
692
|
+
| `Platform` | The page the cache watches (focus, network); swap it in tests |
|
|
693
|
+
| `queryOptions(options)` | Typed options for a query factory |
|
|
694
|
+
| `infiniteQueryOptions(options)` | Typed options for a paged query factory |
|
|
695
|
+
| `hashKey(key)`, `matchesKey(key, prefix)` | Key helpers |
|
|
696
|
+
| `QueryObserver` | One reader's view of a query, with tracked results; for bindings |
|
|
697
|
+
| `Query.CancelledError` | Failure of a request whose entry was removed or cleared before it settled |
|
|
698
|
+
| `RedactedError` | What a streamed pending query rejects with when its server request failed |
|
|
699
|
+
| `QueryHandle<TFactory>` | Handle type for props |
|
|
700
|
+
| `EnsuredQueryHandle<TFactory>` | Handle type for props fed by `ensure` |
|
|
701
|
+
| `Thenable<T>` | Promise with `status` / `value` / `reason`, read by `use()` |
|
|
702
|
+
| `DataOf<TFactory>`, `InputOf<TFactory>`, `ErrorOf<TFactory>` | A factory's data, input and error types |
|
|
703
|
+
| `ServicesOf<TFactory>` | The services a factory's `queryFn` Effect needs; `never` for promises |
|
|
704
|
+
| `Register`, `QueryMeta` | Augment `Register` with `queryMeta` to type [`meta`](#query-meta) |
|
|
665
705
|
|
|
666
706
|
`@raf-query/react` (plus all of the above):
|
|
667
707
|
|
package/dist/entry.d.mts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Platform, PlatformEvent } from "./platform.mjs";
|
|
2
2
|
import { QueryKey } from "./key.mjs";
|
|
3
|
-
import { AnyQueryOptions, QueryBehavior, QueryFactory, QueryHandle } from "./options.mjs";
|
|
3
|
+
import { AnyQueryOptions, QueryBehavior, QueryFactory, QueryHandle, QueryMeta } from "./options.mjs";
|
|
4
4
|
import { Event } from "./cache.mjs";
|
|
5
5
|
import { Pending, Thenable } from "./thenable.mjs";
|
|
6
6
|
import { FetchRequest, Query, State, SubscribeOptions } from "./query.mjs";
|
|
7
7
|
import { Cause, Clock, Context, Deferred, Effect, Scope, Stream } from "effect";
|
|
8
8
|
declare namespace entry_d_exports {
|
|
9
|
-
export { CacheState, CancelledError, Entry, TypeId, changes, emit, entryOf, fetch, hasNextPageUnsafe, hasPreviousPageUnsafe, invalidate, isObserved, isQuery, isStaleUnsafe, make, now, onPlatformEvent, provide, readUnsafe, remove, reset, resume, setData, setOptions, start, subscribeUnsafe };
|
|
9
|
+
export { CacheState, CancelledError, Entry, TypeId, changes, emit, entryOf, fetch, hasNextPageUnsafe, hasPreviousPageUnsafe, invalidate, isObserved, isQuery, isStaleUnsafe, make, metaOf, now, onPlatformEvent, provide, readUnsafe, remove, reset, resume, setData, setOptions, start, subscribeUnsafe };
|
|
10
10
|
}
|
|
11
11
|
export declare const TypeId = "~@raf-query/core/Query";
|
|
12
12
|
declare const CancelledError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => Cause.YieldableError & {
|
|
@@ -54,13 +54,19 @@ export interface Entry extends Query {
|
|
|
54
54
|
readonly commands: Commands;
|
|
55
55
|
/** A failed first load was shown; the next read retries it. */
|
|
56
56
|
errorShown: boolean;
|
|
57
|
+
/** `failureCount` before the request in flight, restored if it's cancelled. */
|
|
58
|
+
failureCountBefore: number;
|
|
57
59
|
handle: QueryHandle;
|
|
60
|
+
/** `meta` sent with dehydrated state, for an entry no factory call has reached. */
|
|
61
|
+
hydratedMeta: QueryMeta | undefined;
|
|
58
62
|
/** Awaited by every caller of the request in flight; outlives restarts. */
|
|
59
63
|
inFlight: Deferred.Deferred<unknown, unknown> | undefined;
|
|
60
64
|
readonly listeners: Map<() => void, SubscribeOptions>;
|
|
61
65
|
options: AnyQueryOptions | undefined;
|
|
62
66
|
/** The first load's promise, until it settles. */
|
|
63
67
|
pending: Pending<unknown> | undefined;
|
|
68
|
+
/** The last reader left during the request in flight: it retries no more. */
|
|
69
|
+
retriesStopped: boolean;
|
|
64
70
|
/** Open while the entry is cached; owns its fibers. */
|
|
65
71
|
readonly scope: Scope.Closeable;
|
|
66
72
|
/**
|
|
@@ -68,6 +74,11 @@ export interface Entry extends Query {
|
|
|
68
74
|
* latest verb that called for this entry ({@link provide}).
|
|
69
75
|
*/
|
|
70
76
|
services: Context.Context<never>;
|
|
77
|
+
/**
|
|
78
|
+
* The request in flight read its `signal`, or returned an Effect, so
|
|
79
|
+
* interrupting it cancels its work (TanStack's `abortSignalConsumed`).
|
|
80
|
+
*/
|
|
81
|
+
signalRead: boolean;
|
|
71
82
|
state: State<unknown>;
|
|
72
83
|
readonly tasks: Tasks;
|
|
73
84
|
}
|
|
@@ -78,6 +89,8 @@ export declare function make(cache: CacheState, key: QueryKey, hash: string): En
|
|
|
78
89
|
export declare function now(entry: Entry): number;
|
|
79
90
|
/** Readers are subscribed; `invalidate` and focus refetch only what is read. */
|
|
80
91
|
export declare function isObserved(entry: Entry): boolean;
|
|
92
|
+
/** Until a factory call attaches options, `meta` comes from hydration. */
|
|
93
|
+
export declare function metaOf(entry: Entry): QueryMeta | undefined;
|
|
81
94
|
export declare function isStaleUnsafe(entry: Entry): boolean;
|
|
82
95
|
export declare function hasNextPageUnsafe(entry: Entry): boolean;
|
|
83
96
|
export declare function hasPreviousPageUnsafe(entry: Entry): boolean;
|
package/dist/entry.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { fromQueryFn, requestServices, runRequest } from "./request.mjs";
|
|
|
7
7
|
import { retrySchedule } from "./retry.mjs";
|
|
8
8
|
import { replaceEqualDeep } from "./share.mjs";
|
|
9
9
|
import { fulfilled, pending } from "./thenable.mjs";
|
|
10
|
-
import { Cause, Data, Deferred, Effect, Exit, FiberMap, Pipeable, Predicate, Scope } from "effect";
|
|
10
|
+
import { Cause, Data, Deferred, Effect, Exit, FiberMap, Pipeable, Predicate, Schedule, Scope } from "effect";
|
|
11
11
|
//#region src/entry.ts
|
|
12
12
|
const TypeId = "~@raf-query/core/Query";
|
|
13
13
|
/**
|
|
@@ -55,18 +55,22 @@ function make(cache, key, hash) {
|
|
|
55
55
|
const entry = Object.assign(Object.create(Proto), {
|
|
56
56
|
cache,
|
|
57
57
|
errorShown: false,
|
|
58
|
+
failureCountBefore: 0,
|
|
58
59
|
handle: {
|
|
59
60
|
input: void 0,
|
|
60
61
|
key
|
|
61
62
|
},
|
|
62
63
|
hash,
|
|
64
|
+
hydratedMeta: void 0,
|
|
63
65
|
inFlight: void 0,
|
|
64
66
|
key,
|
|
65
67
|
listeners: /* @__PURE__ */ new Map(),
|
|
66
68
|
options: void 0,
|
|
67
69
|
pending: first,
|
|
70
|
+
retriesStopped: false,
|
|
68
71
|
scope,
|
|
69
72
|
services: cache.services,
|
|
73
|
+
signalRead: false,
|
|
70
74
|
state: {
|
|
71
75
|
data: void 0,
|
|
72
76
|
error: null,
|
|
@@ -101,6 +105,10 @@ function isObserved(entry) {
|
|
|
101
105
|
function behavior(entry, name) {
|
|
102
106
|
return entry.options?.[name] ?? entry.cache.defaults[name];
|
|
103
107
|
}
|
|
108
|
+
/** Until a factory call attaches options, `meta` comes from hydration. */
|
|
109
|
+
function metaOf(entry) {
|
|
110
|
+
return entry.options ? behavior(entry, "meta") : entry.hydratedMeta ?? entry.cache.defaults.meta;
|
|
111
|
+
}
|
|
104
112
|
function computeStale(entry, state) {
|
|
105
113
|
if (state.isInvalidated || !state.hasData) return true;
|
|
106
114
|
const staleTime = behavior(entry, "staleTime") ?? 0;
|
|
@@ -121,6 +129,7 @@ function hasPreviousPageUnsafe(entry) {
|
|
|
121
129
|
}
|
|
122
130
|
function subscribeUnsafe(entry, listener, options = defaultSubscribeOptions) {
|
|
123
131
|
entry.listeners.set(listener, options);
|
|
132
|
+
entry.retriesStopped = false;
|
|
124
133
|
entry.tasks.cancel("gc");
|
|
125
134
|
scheduleStale(entry);
|
|
126
135
|
emit(entry.cache, {
|
|
@@ -132,6 +141,8 @@ function subscribeUnsafe(entry, listener, options = defaultSubscribeOptions) {
|
|
|
132
141
|
if (entry.listeners.size === 0) {
|
|
133
142
|
entry.tasks.cancel("stale");
|
|
134
143
|
scheduleGc(entry);
|
|
144
|
+
const inFlight = entry.inFlight;
|
|
145
|
+
if (inFlight) scheduleNotify(() => unobserved(entry, inFlight));
|
|
135
146
|
}
|
|
136
147
|
emit(entry.cache, {
|
|
137
148
|
query: entry,
|
|
@@ -273,8 +284,11 @@ function set(entry, patch) {
|
|
|
273
284
|
}
|
|
274
285
|
/** Run `request` as the entry's request fiber, settling into its awaiter. */
|
|
275
286
|
function startRequest(entry, request, direction) {
|
|
287
|
+
if (!entry.inFlight) entry.failureCountBefore = entry.state.failureCount;
|
|
276
288
|
const awaiter = entry.inFlight ?? Deferred.makeUnsafe();
|
|
277
289
|
entry.inFlight = awaiter;
|
|
290
|
+
entry.retriesStopped = false;
|
|
291
|
+
entry.signalRead = false;
|
|
278
292
|
entry.tasks.cancel("gc");
|
|
279
293
|
if (!entry.state.hasData && entry.state.status === "error") {
|
|
280
294
|
entry.pending = pending();
|
|
@@ -312,6 +326,29 @@ function settle(entry, awaiter, exit) {
|
|
|
312
326
|
}
|
|
313
327
|
scheduleGc(entry);
|
|
314
328
|
}
|
|
329
|
+
/**
|
|
330
|
+
* The last reader left while `awaiter`'s request was in flight. A refetch
|
|
331
|
+
* whose work an interrupt cancels (`signalRead`) is interrupted, and the
|
|
332
|
+
* entry goes back to its state before the request; its awaiters get the
|
|
333
|
+
* cached data. Any other request finishes, so its result is cached, but
|
|
334
|
+
* runs no more retries.
|
|
335
|
+
*/
|
|
336
|
+
function unobserved(entry, awaiter) {
|
|
337
|
+
if (isObserved(entry) || entry.inFlight !== awaiter) return;
|
|
338
|
+
if (!entry.signalRead || !entry.state.hasData) {
|
|
339
|
+
entry.retriesStopped = true;
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
entry.inFlight = void 0;
|
|
343
|
+
entry.tasks.cancel("request");
|
|
344
|
+
set(entry, {
|
|
345
|
+
failureCount: entry.failureCountBefore,
|
|
346
|
+
fetchDirection: void 0,
|
|
347
|
+
isFetching: false
|
|
348
|
+
});
|
|
349
|
+
Deferred.doneUnsafe(awaiter, Exit.succeed(entry.state.data));
|
|
350
|
+
scheduleGc(entry);
|
|
351
|
+
}
|
|
315
352
|
function succeed(entry, next, updatedAt) {
|
|
316
353
|
const previous = entry.state;
|
|
317
354
|
const data = previous.hasData && behavior(entry, "structuralSharing") !== false ? replaceEqualDeep(previous.data, next) : next;
|
|
@@ -364,25 +401,41 @@ function markErrorShown(entry) {
|
|
|
364
401
|
}))), { onlyIfMissing: true });
|
|
365
402
|
}
|
|
366
403
|
function requestEffect(entry, options, request) {
|
|
404
|
+
const meta = metaOf(entry);
|
|
367
405
|
const queryKey = options.queryKey;
|
|
368
|
-
if (options.kind === "query") return attempt(entry, (
|
|
406
|
+
if (options.kind === "query") return attempt(entry, (readSignal) => options.queryFn({
|
|
407
|
+
meta,
|
|
369
408
|
queryKey,
|
|
370
|
-
signal
|
|
409
|
+
get signal() {
|
|
410
|
+
return readSignal();
|
|
411
|
+
}
|
|
371
412
|
}));
|
|
372
413
|
const current = isInfiniteData(entry.state.data) ? entry.state.data : void 0;
|
|
373
|
-
return fetchPages(options, current, request, (pageParam, direction) => attempt(entry, (
|
|
414
|
+
return fetchPages(options, current, request, (pageParam, direction) => attempt(entry, (readSignal) => options.queryFn({
|
|
374
415
|
direction,
|
|
416
|
+
meta,
|
|
375
417
|
pageParam,
|
|
376
418
|
queryKey,
|
|
377
|
-
signal
|
|
419
|
+
get signal() {
|
|
420
|
+
return readSignal();
|
|
421
|
+
}
|
|
378
422
|
})));
|
|
379
423
|
}
|
|
380
424
|
/**
|
|
381
425
|
* One `queryFn` call, retried per the query's policy. Every failure counts.
|
|
382
|
-
*
|
|
426
|
+
* `call` reads the signal through `readSignal`, which marks the entry's
|
|
427
|
+
* `signalRead`. Its services are erased with its data type; `runRequest`
|
|
428
|
+
* provides them.
|
|
383
429
|
*/
|
|
384
430
|
function attempt(entry, call) {
|
|
385
|
-
const once = fromQueryFn(
|
|
431
|
+
const once = fromQueryFn((signal) => {
|
|
432
|
+
const result = call(() => {
|
|
433
|
+
entry.signalRead = true;
|
|
434
|
+
return signal;
|
|
435
|
+
});
|
|
436
|
+
if (Effect.isEffect(result)) entry.signalRead = true;
|
|
437
|
+
return result;
|
|
438
|
+
}).pipe(Effect.tapError(() => Effect.sync(() => set(entry, { failureCount: entry.state.failureCount + 1 }))));
|
|
386
439
|
const schedule = retrySchedule({
|
|
387
440
|
retry: behavior(entry, "retry"),
|
|
388
441
|
retryDelay: behavior(entry, "retryDelay")
|
|
@@ -391,12 +444,18 @@ function attempt(entry, call) {
|
|
|
391
444
|
const online = entry.cache.isServer ? Effect.void : whenOnline;
|
|
392
445
|
return Effect.suspend(() => {
|
|
393
446
|
let retrying = false;
|
|
447
|
+
let lastError;
|
|
448
|
+
const unlessStopped = (next) => Effect.suspend(() => entry.retriesStopped ? Effect.fail(lastError) : next);
|
|
394
449
|
const gated = Effect.suspend(() => {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
450
|
+
if (!retrying) {
|
|
451
|
+
retrying = true;
|
|
452
|
+
return once;
|
|
453
|
+
}
|
|
454
|
+
return unlessStopped(Effect.andThen(online, unlessStopped(once)));
|
|
455
|
+
}).pipe(Effect.tapError((error) => Effect.sync(() => {
|
|
456
|
+
lastError = error;
|
|
457
|
+
})));
|
|
458
|
+
return Effect.retry(gated, Schedule.while(schedule, () => !entry.retriesStopped));
|
|
400
459
|
});
|
|
401
460
|
}
|
|
402
461
|
function scheduleGc(entry) {
|
|
@@ -418,4 +477,4 @@ function scheduleStale(entry) {
|
|
|
418
477
|
entry.tasks.fork("stale", Effect.sleep(Math.max(remaining, 0) + 1).pipe(Effect.andThen(Effect.sync(() => set(entry, {})))));
|
|
419
478
|
}
|
|
420
479
|
//#endregion
|
|
421
|
-
export { CancelledError, TypeId, changes, emit, entryOf, fetch, hasNextPageUnsafe, hasPreviousPageUnsafe, invalidate, isObserved, isQuery, isStaleUnsafe, make, now, onPlatformEvent, provide, readUnsafe, remove, reset, resume, setData, setOptions, start, subscribeUnsafe };
|
|
480
|
+
export { CancelledError, TypeId, changes, emit, entryOf, fetch, hasNextPageUnsafe, hasPreviousPageUnsafe, invalidate, isObserved, isQuery, isStaleUnsafe, make, metaOf, now, onPlatformEvent, provide, readUnsafe, remove, reset, resume, setData, setOptions, start, subscribeUnsafe };
|
package/dist/hydration.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { QueryKey } from "./key.mjs";
|
|
2
|
-
import { QueryFactory } from "./options.mjs";
|
|
2
|
+
import { QueryFactory, QueryMeta } from "./options.mjs";
|
|
3
3
|
import "./cache.mjs";
|
|
4
4
|
import "./entry.mjs";
|
|
5
5
|
import { Query, query_d_exports } from "./query.mjs";
|
|
@@ -13,6 +13,8 @@ import { Cause, Effect, Scope, Stream } from "effect";
|
|
|
13
13
|
export interface DehydratedQuery {
|
|
14
14
|
readonly data?: unknown;
|
|
15
15
|
readonly key: QueryKey;
|
|
16
|
+
/** The query's `meta`, for receivers that haven't called its factory. */
|
|
17
|
+
readonly meta?: QueryMeta;
|
|
16
18
|
readonly promise?: Promise<unknown>;
|
|
17
19
|
/** When `data` was fetched; `0` while pending. */
|
|
18
20
|
readonly updatedAt: number;
|
package/dist/hydration.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resume, setData } from "./entry.mjs";
|
|
2
|
-
import { getUnsafe } from "./query.mjs";
|
|
2
|
+
import { getUnsafe, metaUnsafe } from "./query.mjs";
|
|
3
3
|
import { Data, Effect, Queue, Stream } from "effect";
|
|
4
4
|
//#region src/hydration.ts
|
|
5
5
|
/**
|
|
@@ -21,9 +21,14 @@ function canDehydrate(query) {
|
|
|
21
21
|
function dehydrateUnsafe(query, options) {
|
|
22
22
|
const state = getUnsafe(query);
|
|
23
23
|
const serialize = options.serializeData ?? identity;
|
|
24
|
+
const meta = metaUnsafe(query);
|
|
25
|
+
const sent = meta === void 0 ? { key: query.key } : {
|
|
26
|
+
key: query.key,
|
|
27
|
+
meta
|
|
28
|
+
};
|
|
24
29
|
if (state.hasData) return {
|
|
30
|
+
...sent,
|
|
25
31
|
data: serialize(state.data),
|
|
26
|
-
key: query.key,
|
|
27
32
|
updatedAt: state.updatedAt
|
|
28
33
|
};
|
|
29
34
|
const promise = state.promise.then(serialize, (error) => {
|
|
@@ -31,7 +36,7 @@ function dehydrateUnsafe(query, options) {
|
|
|
31
36
|
});
|
|
32
37
|
promise.catch(noop);
|
|
33
38
|
return {
|
|
34
|
-
|
|
39
|
+
...sent,
|
|
35
40
|
promise,
|
|
36
41
|
updatedAt: 0
|
|
37
42
|
};
|
|
@@ -101,6 +106,7 @@ function hydrateEntries(entryFor, state, options) {
|
|
|
101
106
|
const deserialize = options.deserializeData ?? identity;
|
|
102
107
|
for (const query of state.queries) {
|
|
103
108
|
const entry = entryFor(query.key);
|
|
109
|
+
if (!entry.options && query.meta !== void 0) entry.hydratedMeta = query.meta;
|
|
104
110
|
const current = entry.state;
|
|
105
111
|
if (query.updatedAt > 0) {
|
|
106
112
|
if (!current.hasData || current.updatedAt < query.updatedAt) setData(entry, deserialize(query.data), query.updatedAt);
|
package/dist/index.d.mts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { platform_d_exports } from "./platform.mjs";
|
|
2
2
|
import { QueryKey, hashKey, matchesKey } from "./key.mjs";
|
|
3
|
-
import { AnyQueryOptions, DataOf, EnsuredQueryHandle, ErrorOf, InfiniteData, InfiniteQueryOptions, InputArgs, InputOf, IsPaged, LoadOptions, PageFunctionContext, PageOf, QueryBehavior, QueryFactory, QueryFnResult, QueryFunctionContext, QueryHandle, QueryOptions, RetryPolicy, ServicesOf, infiniteQueryOptions, queryOptions } from "./options.mjs";
|
|
3
|
+
import { AnyQueryOptions, DataOf, EnsuredQueryHandle, ErrorOf, InfiniteData, InfiniteQueryOptions, InputArgs, InputOf, IsPaged, LoadOptions, PageFunctionContext, PageOf, QueryBehavior, QueryFactory, QueryFnResult, QueryFunctionContext, QueryHandle, QueryMeta, QueryOptions, Register, RetryPolicy, ServicesOf, infiniteQueryOptions, queryOptions } from "./options.mjs";
|
|
4
4
|
import { DehydrateOptions, DehydratedQuery, DehydratedState, DehydratedStream, HydrateOptions, RedactedError, canDehydrate, dehydrateQuery } from "./hydration.mjs";
|
|
5
5
|
import { cache_d_exports } from "./cache.mjs";
|
|
6
6
|
import { Thenable } from "./thenable.mjs";
|
|
7
7
|
import { query_d_exports } from "./query.mjs";
|
|
8
8
|
import { observer_d_exports } from "./observer.mjs";
|
|
9
|
-
export { type AnyQueryOptions, type DataOf, type DehydrateOptions, type DehydratedQuery, type DehydratedState, type DehydratedStream, type EnsuredQueryHandle, type ErrorOf, type HydrateOptions, type InfiniteData, type InfiniteQueryOptions, type InputArgs, type InputOf, type IsPaged, type LoadOptions, type PageFunctionContext, type PageOf, platform_d_exports as Platform, query_d_exports as Query, type QueryBehavior, cache_d_exports as QueryCache, type QueryFactory, type QueryFnResult, type QueryFunctionContext, type QueryHandle, type QueryKey, observer_d_exports as QueryObserver, type QueryOptions, RedactedError, type RetryPolicy, type ServicesOf, type Thenable, canDehydrate, dehydrateQuery, hashKey, infiniteQueryOptions, matchesKey, queryOptions };
|
|
9
|
+
export { type AnyQueryOptions, type DataOf, type DehydrateOptions, type DehydratedQuery, type DehydratedState, type DehydratedStream, type EnsuredQueryHandle, type ErrorOf, type HydrateOptions, type InfiniteData, type InfiniteQueryOptions, type InputArgs, type InputOf, type IsPaged, type LoadOptions, type PageFunctionContext, type PageOf, platform_d_exports as Platform, query_d_exports as Query, type QueryBehavior, cache_d_exports as QueryCache, type QueryFactory, type QueryFnResult, type QueryFunctionContext, type QueryHandle, type QueryKey, type QueryMeta, observer_d_exports as QueryObserver, type QueryOptions, RedactedError, type Register, type RetryPolicy, type ServicesOf, type Thenable, canDehydrate, dehydrateQuery, hashKey, infiniteQueryOptions, matchesKey, queryOptions };
|
package/dist/options.d.mts
CHANGED
|
@@ -7,7 +7,30 @@ import { Effect, Schedule } from "effect";
|
|
|
7
7
|
* and the services it needs become requirements of the verbs that start it.
|
|
8
8
|
*/
|
|
9
9
|
export type QueryFnResult<TData, TError = unknown, TServices = never> = TData | PromiseLike<TData> | Effect.Effect<TData, TError, TServices>;
|
|
10
|
+
/**
|
|
11
|
+
* App-wide types, filled in by module augmentation. Declare `queryMeta` to
|
|
12
|
+
* type every query's `meta`. Augment the package you import from;
|
|
13
|
+
* `@raf-query/react` re-exports this interface, so either works:
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* declare module "@raf-query/react" {
|
|
17
|
+
* interface Register {
|
|
18
|
+
* queryMeta: { errorMessage?: string };
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export interface Register {}
|
|
24
|
+
/**
|
|
25
|
+
* Type of a query's `meta`. `Record<string, unknown>` unless `queryMeta` is
|
|
26
|
+
* declared on {@link Register}.
|
|
27
|
+
*/
|
|
28
|
+
export type QueryMeta = Register extends {
|
|
29
|
+
queryMeta: infer TQueryMeta;
|
|
30
|
+
} ? TQueryMeta : Record<string, unknown>;
|
|
10
31
|
export interface QueryFunctionContext {
|
|
32
|
+
/** The query's `meta` when this request started ({@link QueryBehavior.meta}). */
|
|
33
|
+
readonly meta: QueryMeta | undefined;
|
|
11
34
|
/** The query's key, as returned by the factory. */
|
|
12
35
|
readonly queryKey: QueryKey;
|
|
13
36
|
/** Aborted when the request is cancelled or superseded. */
|
|
@@ -28,6 +51,12 @@ export type RetryPolicy = boolean | number | Schedule.Schedule<unknown, unknown>
|
|
|
28
51
|
export interface QueryBehavior {
|
|
29
52
|
/** How long an unread entry stays cached, in ms. `Infinity` keeps it. */
|
|
30
53
|
readonly gcTime?: number;
|
|
54
|
+
/**
|
|
55
|
+
* Anything to know about the query elsewhere: the `queryFn` context,
|
|
56
|
+
* `Query.metaUnsafe(query)` (e.g. in the client's `onError`), devtools. A
|
|
57
|
+
* query's own `meta` replaces the default's as a whole.
|
|
58
|
+
*/
|
|
59
|
+
readonly meta?: QueryMeta;
|
|
31
60
|
/** Refetch read, stale queries when the browser comes back online. */
|
|
32
61
|
readonly refetchOnReconnect?: boolean;
|
|
33
62
|
/** Refetch read, stale queries when the window regains focus. */
|
package/dist/query.d.mts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { QueryKey } from "./key.mjs";
|
|
2
|
-
import { AnyQueryOptions, DataOf, ErrorOf, QueryFactory, QueryHandle, ServicesOf } from "./options.mjs";
|
|
2
|
+
import { AnyQueryOptions, DataOf, ErrorOf, QueryFactory, QueryHandle, QueryMeta, ServicesOf } from "./options.mjs";
|
|
3
3
|
import { Thenable } from "./thenable.mjs";
|
|
4
4
|
import { CancelledError as CancelledError$1, TypeId as TypeId$1, entry_d_exports } from "./entry.mjs";
|
|
5
5
|
import { Direction } from "./infinite.mjs";
|
|
6
6
|
import { Effect, Pipeable, Stream } from "effect";
|
|
7
7
|
declare namespace query_d_exports {
|
|
8
|
-
export { CancelledError, FetchRequest, Of, Query, State, SubscribeOptions, TypeId, changes, fetch, fetchNextPage, fetchPreviousPage, get, getUnsafe, hasNextPageUnsafe, hasPreviousPageUnsafe, invalidate, isQuery, isStaleUnsafe, observerCountUnsafe, readUnsafe, refetch, reset, subscribeUnsafe };
|
|
8
|
+
export { CancelledError, FetchRequest, Of, Query, State, SubscribeOptions, TypeId, changes, fetch, fetchNextPage, fetchPreviousPage, get, getUnsafe, hasNextPageUnsafe, hasPreviousPageUnsafe, invalidate, isQuery, isStaleUnsafe, metaUnsafe, observerCountUnsafe, readUnsafe, refetch, reset, subscribeUnsafe };
|
|
9
9
|
}
|
|
10
10
|
export declare const TypeId: typeof TypeId$1;
|
|
11
11
|
export type TypeId = typeof TypeId;
|
|
@@ -89,6 +89,11 @@ export declare function invalidate<TData, TFactory extends QueryFactory, TError>
|
|
|
89
89
|
* and refetch; succeeds when that refetch settles.
|
|
90
90
|
*/
|
|
91
91
|
export declare function reset<TData, TFactory extends QueryFactory, TError>(self: Query<TData, TFactory, TError>): Effect.Effect<void>;
|
|
92
|
+
/**
|
|
93
|
+
* The query's `meta`: the latest factory call's (the one it was hydrated
|
|
94
|
+
* with, before any), else the cache default's.
|
|
95
|
+
*/
|
|
96
|
+
export declare function metaUnsafe<TData, TFactory extends QueryFactory, TError>(self: Query<TData, TFactory, TError>): QueryMeta | undefined;
|
|
92
97
|
/** The entry's current state. */
|
|
93
98
|
export declare function get<TData, TFactory extends QueryFactory, TError>(self: Query<TData, TFactory, TError>): Effect.Effect<State<TData>>;
|
|
94
99
|
/** {@link get}, synchronously. */
|
package/dist/query.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { __exportAll } from "./_virtual/_rolldown/runtime.mjs";
|
|
2
2
|
import { unsafeCast } from "./cast.mjs";
|
|
3
|
-
import { CancelledError as CancelledError$1, TypeId as TypeId$1, changes as changes$1, entryOf, fetch as fetch$1, hasNextPageUnsafe as hasNextPageUnsafe$1, hasPreviousPageUnsafe as hasPreviousPageUnsafe$1, invalidate as invalidate$1, isQuery as isQuery$1, isStaleUnsafe as isStaleUnsafe$1, provide, readUnsafe as readUnsafe$1, reset as reset$1, subscribeUnsafe as subscribeUnsafe$1 } from "./entry.mjs";
|
|
3
|
+
import { CancelledError as CancelledError$1, TypeId as TypeId$1, changes as changes$1, entryOf, fetch as fetch$1, hasNextPageUnsafe as hasNextPageUnsafe$1, hasPreviousPageUnsafe as hasPreviousPageUnsafe$1, invalidate as invalidate$1, isQuery as isQuery$1, isStaleUnsafe as isStaleUnsafe$1, metaOf, provide, readUnsafe as readUnsafe$1, reset as reset$1, subscribeUnsafe as subscribeUnsafe$1 } from "./entry.mjs";
|
|
4
4
|
import { Effect } from "effect";
|
|
5
5
|
import { dual } from "effect/Function";
|
|
6
6
|
//#region src/query.ts
|
|
@@ -26,6 +26,7 @@ var query_exports = /* @__PURE__ */ __exportAll({
|
|
|
26
26
|
invalidate: () => invalidate,
|
|
27
27
|
isQuery: () => isQuery,
|
|
28
28
|
isStaleUnsafe: () => isStaleUnsafe,
|
|
29
|
+
metaUnsafe: () => metaUnsafe,
|
|
29
30
|
observerCountUnsafe: () => observerCountUnsafe,
|
|
30
31
|
readUnsafe: () => readUnsafe,
|
|
31
32
|
refetch: () => refetch,
|
|
@@ -75,6 +76,13 @@ function invalidate(self) {
|
|
|
75
76
|
function reset(self) {
|
|
76
77
|
return reset$1(entryOf(self));
|
|
77
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* The query's `meta`: the latest factory call's (the one it was hydrated
|
|
81
|
+
* with, before any), else the cache default's.
|
|
82
|
+
*/
|
|
83
|
+
function metaUnsafe(self) {
|
|
84
|
+
return metaOf(entryOf(self));
|
|
85
|
+
}
|
|
78
86
|
/** The entry's current state. */
|
|
79
87
|
function get(self) {
|
|
80
88
|
return Effect.sync(() => getUnsafe(self));
|
|
@@ -116,4 +124,4 @@ function observerCountUnsafe(self) {
|
|
|
116
124
|
return entryOf(self).listeners.size;
|
|
117
125
|
}
|
|
118
126
|
//#endregion
|
|
119
|
-
export { CancelledError, TypeId, changes, fetch, fetchNextPage, fetchPreviousPage, get, getUnsafe, hasNextPageUnsafe, hasPreviousPageUnsafe, invalidate, isQuery, isStaleUnsafe, observerCountUnsafe, query_exports, readUnsafe, refetch, reset, subscribeUnsafe };
|
|
127
|
+
export { CancelledError, TypeId, changes, fetch, fetchNextPage, fetchPreviousPage, get, getUnsafe, hasNextPageUnsafe, hasPreviousPageUnsafe, invalidate, isQuery, isStaleUnsafe, metaUnsafe, observerCountUnsafe, query_exports, readUnsafe, refetch, reset, subscribeUnsafe };
|