@raf-query/core 0.0.10 → 0.0.11
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 +7 -1
- package/README.md +2 -1
- package/dist/key.mjs +1 -1
- package/dist/options.d.mts +6 -0
- package/dist/query.mjs +9 -6
- package/dist/share.mjs +41 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.0.11](https://github.com/KATT/raf-query/compare/0.0.10...0.0.11) (2026-10-12)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
* **core:** structurally share new data with the old (structuralSharing) ([#15](https://github.com/KATT/raf-query/issues/15)) ([bf4392a](https://github.com/KATT/raf-query/commit/bf4392a56936ead6fb8b2726981cb19dadf51304))
|
|
8
|
+
|
|
3
9
|
## [0.0.10](https://github.com/KATT/raf-query/compare/0.0.9...0.0.10) (2026-10-12)
|
|
4
10
|
|
|
5
|
-
|
|
11
|
+
- move the TanStack Query experiment into .experimental folders ([#12](https://github.com/KATT/raf-query/issues/12)) ([e9613a6](https://github.com/KATT/raf-query/commit/e9613a608f100fb17de6baf37c0f1fcfa479e909))
|
|
6
12
|
|
|
7
13
|
## [0.0.9](https://github.com/KATT/raf-query/compare/0.0.8...0.0.9) (2026-10-12)
|
|
8
14
|
|
package/README.md
CHANGED
|
@@ -179,7 +179,7 @@ function PostPage() {
|
|
|
179
179
|
| `hash` | Stable string form of the handle's key, for `key` props on Suspense boundaries |
|
|
180
180
|
| `refetch()` | Refetch now. Returns a promise, so it can be awaited inside a transition or action |
|
|
181
181
|
|
|
182
|
-
When the cache updates — refetch, invalidation, `setData` — the hook returns a new, pre-resolved promise and the component re-renders without suspending. When the **first** load fails, the promise rejects and `use()` throws to the nearest error boundary. Resetting that boundary refetches the query.
|
|
182
|
+
When the cache updates — refetch, invalidation, `setData` — the hook returns a new, pre-resolved promise and the component re-renders without suspending. New data is structurally shared with the old: objects and arrays that are deeply equal keep their identity, and a refetch that returns equal data keeps the same promise, so nothing re-renders. When the **first** load fails, the promise rejects and `use()` throws to the nearest error boundary. Resetting that boundary refetches the query.
|
|
183
183
|
|
|
184
184
|
Queries do not refetch on mount. They refetch when a loader starts them again, when invalidated, on window focus or reconnect while stale, on `refetch()`, or on `refetchInterval`.
|
|
185
185
|
|
|
@@ -539,6 +539,7 @@ Query options, accepted by `queryOptions`, `infiniteQueryOptions` and `config.de
|
|
|
539
539
|
| `retry` (count, `boolean`, predicate, `Schedule`) | `3`; `0` on the server |
|
|
540
540
|
| `retryDelay` (ms, or a function of the failure count) | Doubling from 1 s, at most 30 s |
|
|
541
541
|
| `refetchOnWindowFocus`, `refetchOnReconnect` | `true` (only read, stale queries refetch) |
|
|
542
|
+
| `structuralSharing` (keep deeply equal parts of data) | `true` |
|
|
542
543
|
|
|
543
544
|
`infiniteQueryOptions` also takes `getPreviousPageParam` and `maxPages`.
|
|
544
545
|
|
package/dist/key.mjs
CHANGED
package/dist/options.d.mts
CHANGED
|
@@ -34,6 +34,12 @@ export interface QueryBehavior {
|
|
|
34
34
|
readonly retryDelay?: number | ((failureCount: number, error: unknown) => number);
|
|
35
35
|
/** How long data counts as fresh, in ms. Fresh data is never refetched. */
|
|
36
36
|
readonly staleTime?: number;
|
|
37
|
+
/**
|
|
38
|
+
* Keep the parts of new data that are deeply equal to the old data, so
|
|
39
|
+
* unchanged objects keep their identity and unchanged data keeps its
|
|
40
|
+
* promise. Compares plain objects and arrays. Default `true`.
|
|
41
|
+
*/
|
|
42
|
+
readonly structuralSharing?: boolean;
|
|
37
43
|
}
|
|
38
44
|
export interface QueryOptions<TData> extends QueryBehavior {
|
|
39
45
|
readonly kind: "query";
|
package/dist/query.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { scheduleNotify } from "./notify.mjs";
|
|
2
|
+
import { replaceEqualDeep } from "./share.mjs";
|
|
2
3
|
import { fulfilled, pending } from "./thenable.mjs";
|
|
3
4
|
import { Cause, Effect, Exit, Predicate, Schedule } from "effect";
|
|
4
5
|
//#region src/query.ts
|
|
@@ -260,10 +261,8 @@ var QueryEntry = class {
|
|
|
260
261
|
#settle(run, exit) {
|
|
261
262
|
if (this.#inFlight !== run) return;
|
|
262
263
|
this.#inFlight = void 0;
|
|
263
|
-
if (Exit.isSuccess(exit))
|
|
264
|
-
|
|
265
|
-
run.deferred.resolve(exit.value);
|
|
266
|
-
} else if (Cause.hasInterruptsOnly(exit.cause)) {
|
|
264
|
+
if (Exit.isSuccess(exit)) run.deferred.resolve(this.#succeed(exit.value, Date.now()));
|
|
265
|
+
else if (Cause.hasInterruptsOnly(exit.cause)) {
|
|
267
266
|
this.#set({
|
|
268
267
|
fetchDirection: void 0,
|
|
269
268
|
isFetching: false
|
|
@@ -276,10 +275,13 @@ var QueryEntry = class {
|
|
|
276
275
|
}
|
|
277
276
|
this.#scheduleGc();
|
|
278
277
|
}
|
|
279
|
-
#succeed(
|
|
278
|
+
#succeed(next, updatedAt) {
|
|
279
|
+
const previous = this.#state;
|
|
280
|
+
const data = previous.hasData && this.#behavior("structuralSharing") !== false ? replaceEqualDeep(previous.data, next) : next;
|
|
280
281
|
const firstLoad = this.#pending;
|
|
281
282
|
this.#pending = void 0;
|
|
282
283
|
firstLoad?.resolve(data);
|
|
284
|
+
const unchanged = previous.hasData && data === previous.data;
|
|
283
285
|
this.#set({
|
|
284
286
|
data,
|
|
285
287
|
error: null,
|
|
@@ -288,10 +290,11 @@ var QueryEntry = class {
|
|
|
288
290
|
hasData: true,
|
|
289
291
|
isFetching: false,
|
|
290
292
|
isInvalidated: false,
|
|
291
|
-
promise: firstLoad ?
|
|
293
|
+
promise: firstLoad?.promise ?? (unchanged ? previous.promise : fulfilled(data)),
|
|
292
294
|
status: "success",
|
|
293
295
|
updatedAt
|
|
294
296
|
});
|
|
297
|
+
return data;
|
|
295
298
|
}
|
|
296
299
|
#fail(error) {
|
|
297
300
|
this.#host.reportError(error, this);
|
package/dist/share.mjs
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { unsafeCast } from "./cast.mjs";
|
|
2
|
+
import { isPlainRecord } from "./key.mjs";
|
|
3
|
+
//#region src/share.ts
|
|
4
|
+
function isPlainArray(value) {
|
|
5
|
+
return Array.isArray(value) && value.length === Object.keys(value).length;
|
|
6
|
+
}
|
|
7
|
+
function share(previous, next, depth) {
|
|
8
|
+
if (previous === next || depth > 500) return next;
|
|
9
|
+
if (isPlainArray(previous) && isPlainArray(next)) {
|
|
10
|
+
let equal = previous.length === next.length;
|
|
11
|
+
const copy = next.map((item, index) => {
|
|
12
|
+
const shared = share(previous[index], item, depth + 1);
|
|
13
|
+
equal &&= shared === previous[index];
|
|
14
|
+
return shared;
|
|
15
|
+
});
|
|
16
|
+
return equal ? previous : copy;
|
|
17
|
+
}
|
|
18
|
+
if (isPlainRecord(previous) && isPlainRecord(next)) {
|
|
19
|
+
const names = Object.keys(next);
|
|
20
|
+
let equal = names.length === Object.keys(previous).length;
|
|
21
|
+
const copy = {};
|
|
22
|
+
for (const name of names) {
|
|
23
|
+
const shared = share(previous[name], next[name], depth + 1);
|
|
24
|
+
equal &&= shared === previous[name] && Object.hasOwn(previous, name);
|
|
25
|
+
copy[name] = shared;
|
|
26
|
+
}
|
|
27
|
+
return equal ? previous : copy;
|
|
28
|
+
}
|
|
29
|
+
return next;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* `next`, reusing every part of `previous` that is deeply equal to it: all
|
|
33
|
+
* of `previous` when nothing changed, else a copy of `next` whose unchanged
|
|
34
|
+
* subtrees are `previous`'s. Only plain objects and arrays are compared;
|
|
35
|
+
* anything else is taken from `next`. TanStack Query's `replaceEqualDeep`.
|
|
36
|
+
*/
|
|
37
|
+
function replaceEqualDeep(previous, next) {
|
|
38
|
+
return unsafeCast(share(previous, next, 0));
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
export { replaceEqualDeep };
|