@raf-query/core 0.0.16 → 0.0.18
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 +4 -0
- package/dist/client.d.mts +8 -2
- package/dist/client.mjs +7 -4
- package/dist/online.mjs +16 -0
- package/dist/query.mjs +12 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.0.18](https://github.com/KATT/raf-query/compare/0.0.17...0.0.18) (2026-10-12)
|
|
4
|
+
|
|
5
|
+
### Bug Fixes
|
|
6
|
+
|
|
7
|
+
* **core:** allow undefined query data ([#22](https://github.com/KATT/raf-query/issues/22)) ([9b85e57](https://github.com/KATT/raf-query/commit/9b85e57bf2fed4413cdbc1b49f925cc60dad695f)), references [#20](https://github.com/KATT/raf-query/issues/20)
|
|
8
|
+
|
|
9
|
+
## [0.0.17](https://github.com/KATT/raf-query/compare/0.0.16...0.0.17) (2026-10-12)
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
- **core:** pause retries while the browser is offline ([#18](https://github.com/KATT/raf-query/issues/18)) ([a25c664](https://github.com/KATT/raf-query/commit/a25c664359fe7248cdc8d91a14d524e37b467c6c))
|
|
14
|
+
|
|
3
15
|
## [0.0.16](https://github.com/KATT/raf-query/compare/0.0.15...0.0.16) (2026-10-12)
|
|
4
16
|
|
|
5
17
|
### Bug Fixes
|
|
6
18
|
|
|
7
|
-
|
|
19
|
+
- **core:** don't revalidate cached data in server verbs ([#19](https://github.com/KATT/raf-query/issues/19)) ([0ed7d9f](https://github.com/KATT/raf-query/commit/0ed7d9fefd0b70a8aa9a85c8d2c309a9fed42903))
|
|
8
20
|
|
|
9
21
|
## [0.0.15](https://github.com/KATT/raf-query/compare/0.0.14...0.0.15) (2026-10-12)
|
|
10
22
|
|
package/README.md
CHANGED
|
@@ -363,6 +363,8 @@ function EditTitle(props: { post: Post }) {
|
|
|
363
363
|
| `qc.setData(factory, input, updater)` | Write data directly |
|
|
364
364
|
| `qc.getData(factory, input)` | Read cached data, or `undefined` |
|
|
365
365
|
|
|
366
|
+
Data can be `undefined` when the `queryFn`'s type allows it (`Promise<User | undefined>`); it's cached and reused like any other value, unlike in TanStack Query, where an `undefined` result is an error. So `getData` and an updater's `previous` return `undefined` both with no data and with `undefined` data; `qc.getQuery(factory, input).getState().hasData` tells them apart.
|
|
367
|
+
|
|
366
368
|
## SSR and hydration
|
|
367
369
|
|
|
368
370
|
The server's query client streams its cache to the browser's query client as entries settle. Pending queries cross as promises, so the browser resumes the server's request instead of starting a new one. Components never hydrate anything themselves; a handle only needs its key to find the entry.
|
|
@@ -556,6 +558,8 @@ Query options, accepted by `queryOptions`, `infiniteQueryOptions` and `config.de
|
|
|
556
558
|
|
|
557
559
|
`infiniteQueryOptions` also takes `getPreviousPageParam` and `maxPages`.
|
|
558
560
|
|
|
561
|
+
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.
|
|
562
|
+
|
|
559
563
|
`@raf-query/core`:
|
|
560
564
|
|
|
561
565
|
| Export | Use |
|
package/dist/client.d.mts
CHANGED
|
@@ -34,7 +34,10 @@ export interface QueryFilter {
|
|
|
34
34
|
}
|
|
35
35
|
/** `ensure` / `fetch` arguments: the input, then load options for paged factories. */
|
|
36
36
|
export type LoadArgs<TFactory extends QueryFactory> = InputArgs<TFactory> | [input: InputOf<TFactory>, options: LoadOptions<TFactory>];
|
|
37
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* A new value, or a function of the current one. `previous` is `undefined`
|
|
39
|
+
* both with no data and with `undefined` data.
|
|
40
|
+
*/
|
|
38
41
|
export type Updater<TData> = TData | ((previous: TData | undefined) => TData);
|
|
39
42
|
/** Effect-returning twins of the client's async methods. */
|
|
40
43
|
export interface QueryClientEffect {
|
|
@@ -100,7 +103,10 @@ export declare class QueryClient {
|
|
|
100
103
|
remove(filter: QueryFilter): void;
|
|
101
104
|
/** Write data for a factory call. Readers re-render without suspending. */
|
|
102
105
|
setData<TFactory extends QueryFactory>(factory: TFactory, input: InputOf<TFactory>, updater: Updater<DataOf<TFactory>>): void;
|
|
103
|
-
/**
|
|
106
|
+
/**
|
|
107
|
+
* Cached data for a factory call, or `undefined` if there is none. Data can
|
|
108
|
+
* itself be `undefined`; `getQuery(...).getState().hasData` tells them apart.
|
|
109
|
+
*/
|
|
104
110
|
getData<TFactory extends QueryFactory>(factory: TFactory, ...args: InputArgs<TFactory>): DataOf<TFactory> | undefined;
|
|
105
111
|
/** Every entry, for devtools and integrations. */
|
|
106
112
|
getAll(): ReadonlyArray<Query>;
|
package/dist/client.mjs
CHANGED
|
@@ -115,9 +115,9 @@ var QueryClient = class {
|
|
|
115
115
|
async fetch(factory, ...args) {
|
|
116
116
|
const entry = this.#build(factory, args[0]);
|
|
117
117
|
await this.#load(entry, args[1]?.pages);
|
|
118
|
-
const
|
|
119
|
-
if (
|
|
120
|
-
return data;
|
|
118
|
+
const state = entry.getState();
|
|
119
|
+
if (!state.hasData) throw new Error(`Query ${entry.hash} has no data`);
|
|
120
|
+
return unsafeCast(state.data);
|
|
121
121
|
}
|
|
122
122
|
async invalidate(target, input) {
|
|
123
123
|
await Promise.all(this.#match(target, input).map((entry) => entry.invalidate()));
|
|
@@ -134,7 +134,10 @@ var QueryClient = class {
|
|
|
134
134
|
const next = isUpdateFunction(updater) ? updater(typed(entry).getState().data) : updater;
|
|
135
135
|
entry.setData(next);
|
|
136
136
|
}
|
|
137
|
-
/**
|
|
137
|
+
/**
|
|
138
|
+
* Cached data for a factory call, or `undefined` if there is none. Data can
|
|
139
|
+
* itself be `undefined`; `getQuery(...).getState().hasData` tells them apart.
|
|
140
|
+
*/
|
|
138
141
|
getData(factory, ...args) {
|
|
139
142
|
const options = optionsFor(factory, args[0]);
|
|
140
143
|
const entry = this.#cache.get(hashKey(options.queryKey));
|
package/dist/online.mjs
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
//#region src/online.ts
|
|
3
|
+
function isOffline() {
|
|
4
|
+
return "window" in globalThis && "navigator" in globalThis && "onLine" in navigator && !navigator.onLine;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Succeeds now if the browser is online, else on the window's next `online`
|
|
8
|
+
* event. Interrupting it removes the listener.
|
|
9
|
+
*/
|
|
10
|
+
const whenOnline = Effect.suspend(() => isOffline() ? Effect.callback((resume) => {
|
|
11
|
+
const onOnline = () => resume(Effect.void);
|
|
12
|
+
window.addEventListener("online", onOnline, { once: true });
|
|
13
|
+
return Effect.sync(() => window.removeEventListener("online", onOnline));
|
|
14
|
+
}) : Effect.void);
|
|
15
|
+
//#endregion
|
|
16
|
+
export { whenOnline };
|
package/dist/query.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { scheduleNotify } from "./notify.mjs";
|
|
2
|
+
import { whenOnline } from "./online.mjs";
|
|
2
3
|
import { replaceEqualDeep } from "./share.mjs";
|
|
3
4
|
import { fulfilled, pending } from "./thenable.mjs";
|
|
4
5
|
import { Cause, Effect, Exit, Predicate, Schedule } from "effect";
|
|
@@ -382,7 +383,16 @@ var QueryEntry = class {
|
|
|
382
383
|
const retry = this.#behavior("retry") ?? (this.#host.isServer ? 0 : 3);
|
|
383
384
|
const countFailure = Effect.sync(() => this.#set({ failureCount: this.#state.failureCount + 1 }));
|
|
384
385
|
const counted = once.pipe(Effect.tapError(() => countFailure));
|
|
385
|
-
|
|
386
|
+
const online = this.#host.isServer ? Effect.void : whenOnline;
|
|
387
|
+
if (Schedule.isSchedule(retry)) return Effect.suspend(() => {
|
|
388
|
+
let first = true;
|
|
389
|
+
const each = Effect.suspend(() => {
|
|
390
|
+
const wait = first ? Effect.void : online;
|
|
391
|
+
first = false;
|
|
392
|
+
return wait.pipe(Effect.andThen(counted));
|
|
393
|
+
});
|
|
394
|
+
return Effect.retry(each, retry);
|
|
395
|
+
});
|
|
386
396
|
const retryDelay = this.#behavior("retryDelay");
|
|
387
397
|
const delayFor = (failureCount, error) => {
|
|
388
398
|
if (retryDelay === void 0) return Math.min(1e3 * 2 ** (failureCount - 1), 3e4);
|
|
@@ -391,7 +401,7 @@ var QueryEntry = class {
|
|
|
391
401
|
const attempt = counted.pipe(Effect.catch((error) => {
|
|
392
402
|
const failureCount = this.#state.failureCount;
|
|
393
403
|
if (!shouldRetry(retry, failureCount, error)) return Effect.fail(error);
|
|
394
|
-
return Effect.sleep(delayFor(failureCount, error)).pipe(Effect.andThen(Effect.suspend(() => attempt)));
|
|
404
|
+
return Effect.sleep(delayFor(failureCount, error)).pipe(Effect.andThen(online), Effect.andThen(Effect.suspend(() => attempt)));
|
|
395
405
|
}));
|
|
396
406
|
return attempt;
|
|
397
407
|
}
|