@raf-query/core 0.10.0 → 0.10.2
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 +22 -5
- package/README.md +1 -1
- package/dist/all-keyed.d.mts +18 -0
- package/dist/all-keyed.mjs +19 -0
- package/dist/cache.d.mts +1 -1
- package/dist/cache.mjs +1 -1
- package/dist/client.d.mts +127 -0
- package/dist/client.mjs +176 -0
- package/dist/controls.d.mts +85 -0
- package/dist/controls.mjs +59 -0
- package/dist/entry.d.mts +2 -2
- package/dist/entry.mjs +1 -1
- package/dist/hydration.d.mts +2 -2
- package/dist/index.d.mts +7 -4
- package/dist/index.mjs +5 -2
- package/dist/observer.d.mts +17 -9
- package/dist/observer.mjs +68 -16
- package/dist/query.d.mts +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,20 +1,37 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.10.2](https://github.com/KATT/raf-query/compare/0.10.1...0.10.2) (2026-10-16)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
* **react:** re-render useQuery readers of the thenable's status when the first load settles (2/3) ([#198](https://github.com/KATT/raf-query/issues/198)) ([436d83c](https://github.com/KATT/raf-query/commit/436d83cfd6a3944265eb56d5597c322342c5a6c6))
|
|
8
|
+
* **react:** type useQuery's promise as a Thenable (1/3) ([#200](https://github.com/KATT/raf-query/issues/200)) ([47e5309](https://github.com/KATT/raf-query/commit/47e530957565a2bd345642ae7a4be5d3c501ffb2))
|
|
9
|
+
|
|
10
|
+
## [0.10.1](https://github.com/KATT/raf-query/compare/0.10.0...0.10.1) (2026-10-16)
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
- **core:** export QueryClient, dehydrate, hydrate, allKeyed and the useQuery controls from @raf-query/core ([#193](https://github.com/KATT/raf-query/issues/193)) ([54492ab](https://github.com/KATT/raf-query/commit/54492abbe2966a0645e8a4c77f9c5ab31d6e3a20)), references [#194](https://github.com/KATT/raf-query/issues/194)
|
|
15
|
+
|
|
16
|
+
### Documentation
|
|
17
|
+
|
|
18
|
+
- React Router client-only and server sections with one server client per request, Effect under Integrations, official docs links, and ensure waits like fetch in server-only code ([#192](https://github.com/KATT/raf-query/issues/192)) ([841ffcf](https://github.com/KATT/raf-query/commit/841ffcf4d0fa0a40dfc58dc65bfcfadca8948874)), references [#190](https://github.com/KATT/raf-query/issues/190)
|
|
19
|
+
|
|
3
20
|
## [0.10.0](https://github.com/KATT/raf-query/compare/0.9.2...0.10.0) (2026-10-16)
|
|
4
21
|
|
|
5
22
|
### ⚠ BREAKING CHANGES
|
|
6
23
|
|
|
7
|
-
|
|
8
|
-
|
|
24
|
+
- **react:** import HydrationBoundary from @raf-query/react in RSC and React Router ([#175](https://github.com/KATT/raf-query/issues/175))
|
|
25
|
+
- **react:** import QueryProvider from @raf-query/react ([#177](https://github.com/KATT/raf-query/issues/177))
|
|
9
26
|
|
|
10
27
|
### Features
|
|
11
28
|
|
|
12
|
-
|
|
13
|
-
|
|
29
|
+
- **react:** import HydrationBoundary from @raf-query/react in RSC and React Router ([#175](https://github.com/KATT/raf-query/issues/175)) ([244705c](https://github.com/KATT/raf-query/commit/244705c74841d280a596195dec5cf5e5d5617fe5))
|
|
30
|
+
- **react:** import QueryProvider from @raf-query/react ([#177](https://github.com/KATT/raf-query/issues/177)) ([fb4fceb](https://github.com/KATT/raf-query/commit/fb4fcebb404906b4a9d5716a1d45cb33c82da2fb)), references [#175](https://github.com/KATT/raf-query/issues/175) [#175](https://github.com/KATT/raf-query/issues/175) [#175](https://github.com/KATT/raf-query/issues/175) [#175](https://github.com/KATT/raf-query/issues/175)
|
|
14
31
|
|
|
15
32
|
### Code Refactoring
|
|
16
33
|
|
|
17
|
-
|
|
34
|
+
- **react:** group the HydrationBoundary and QueryProvider files in folders ([#186](https://github.com/KATT/raf-query/issues/186)) ([ad07bb4](https://github.com/KATT/raf-query/commit/ad07bb45ca77fc797cd6b8432685e58f43907258)), references [#175](https://github.com/KATT/raf-query/issues/175)
|
|
18
35
|
|
|
19
36
|
## [0.9.2](https://github.com/KATT/raf-query/compare/0.9.1...0.9.2) (2026-10-15)
|
|
20
37
|
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @raf-query/core
|
|
2
2
|
|
|
3
|
-
The framework-agnostic cache behind [raf-query](https://raf-query.katt.dev), written in and for [Effect](https://effect.website): every verb is an Effect with typed errors and services, and entries can be watched as Streams.
|
|
3
|
+
The framework-agnostic cache behind [raf-query](https://raf-query.katt.dev), written in and for [Effect](https://effect.website): every verb is an Effect with typed errors and services, and entries can be watched as Streams. It also holds `QueryClient`, the same cache with Promise methods, which the framework bindings re-export.
|
|
4
4
|
|
|
5
5
|
**[Documentation](https://raf-query.katt.dev/docs/effect)** · [API reference](https://raf-query.katt.dev/docs/api-reference#raf-querycore) · [GitHub](https://github.com/KATT/raf-query)
|
|
6
6
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/all-keyed.d.ts
|
|
2
|
+
/** The resolved form of a record passed to {@link allKeyed}. */
|
|
3
|
+
export type Settled<TRecord> = { -readonly [TKey in keyof TRecord]: Awaited<TRecord[TKey]>; };
|
|
4
|
+
/**
|
|
5
|
+
* Ponyfill of the TC39 `Promise.allKeyed` proposal: a record of promises in,
|
|
6
|
+
* a promise of the same record out, rejecting on the first failure.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* const data = await allKeyed({
|
|
10
|
+
* post: qc.ensure(postQuery, { slug }),
|
|
11
|
+
* author: qc.ensure(authorQuery, { slug }),
|
|
12
|
+
* });
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export declare function allKeyed<TRecord extends {
|
|
16
|
+
readonly [key: string]: unknown;
|
|
17
|
+
}>(record: TRecord): Promise<Settled<TRecord>>;
|
|
18
|
+
//#endregion
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/all-keyed.ts
|
|
2
|
+
/**
|
|
3
|
+
* Ponyfill of the TC39 `Promise.allKeyed` proposal: a record of promises in,
|
|
4
|
+
* a promise of the same record out, rejecting on the first failure.
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* const data = await allKeyed({
|
|
8
|
+
* post: qc.ensure(postQuery, { slug }),
|
|
9
|
+
* author: qc.ensure(authorQuery, { slug }),
|
|
10
|
+
* });
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
async function allKeyed(record) {
|
|
14
|
+
const keys = Object.keys(record);
|
|
15
|
+
const values = await Promise.all(keys.map((key) => record[key]));
|
|
16
|
+
return Object.fromEntries(keys.map((key, index) => [key, values[index]]));
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
export { allKeyed };
|
package/dist/cache.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { QueryKey } from "./key.mjs";
|
|
2
2
|
import { DataOf, ErrorOf, InputArgs, InputOf, LoadOptions, PagedQueryFactory, QueryBehavior, QueryFactory, QueryHandle, QueryHandleWithData, ServicesOf } from "./options.mjs";
|
|
3
|
-
import { DehydrateOptions, DehydratedState, DehydratedStream, HydrateOptions } from "./hydration.mjs";
|
|
4
3
|
import { CancelledError, Query, State, query_d_exports } from "./query.mjs";
|
|
4
|
+
import { DehydrateOptions, DehydratedState, DehydratedStream, HydrateOptions } from "./hydration.mjs";
|
|
5
5
|
import { Context, Effect, Layer, Option, Scope, Stream } from "effect";
|
|
6
6
|
declare namespace cache_d_exports {
|
|
7
7
|
export { Config, Event, Filter, QueryCache, Target, TypeId, Updater, isQueryCache, layer, make };
|
package/dist/cache.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { __exportAll } from "./_virtual/_rolldown/runtime.mjs";
|
|
2
|
-
import { Platform } from "./platform.mjs";
|
|
3
2
|
import { hashKey, matchesKey } from "./key.mjs";
|
|
4
3
|
import { listen } from "./listen.mjs";
|
|
4
|
+
import { Platform } from "./platform.mjs";
|
|
5
5
|
import { changes, emit, fetch, invalidate, isObserved, make as make$1, needsRefetch, onPlatformEvent, provide, remove, reset, revalidate, setData, setOptions, start } from "./entry.mjs";
|
|
6
6
|
import { dehydrateEntries, dehydrateStream, hydrateEntries } from "./hydration.mjs";
|
|
7
7
|
import { optionsFor } from "./options.mjs";
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { DataOf, InputArgs, InputOf, LoadOptions, PagedQueryFactory, QueryFactory, QueryHandle, QueryHandleWithData } from "./options.mjs";
|
|
2
|
+
import { Query, query_d_exports } from "./query.mjs";
|
|
3
|
+
import { DehydrateOptions, DehydratedState, HydrateOptions } from "./hydration.mjs";
|
|
4
|
+
import { Config, Event, Filter, QueryCache, Target, Updater, cache_d_exports } from "./cache.mjs";
|
|
5
|
+
import { Effect, Layer } from "effect";
|
|
6
|
+
//#region src/client.d.ts
|
|
7
|
+
export interface QueryClientConfig<TServices = never> extends Config {
|
|
8
|
+
/**
|
|
9
|
+
* Services for queries whose `queryFn` returns an Effect. Built with the
|
|
10
|
+
* client, so it must build synchronously; `dispose()` runs its
|
|
11
|
+
* finalizers.
|
|
12
|
+
*/
|
|
13
|
+
readonly layer?: Layer.Layer<TServices>;
|
|
14
|
+
/** Called with every failed request, including failed prefetches. */
|
|
15
|
+
onError?(error: unknown, query: Query): void;
|
|
16
|
+
/**
|
|
17
|
+
* When `false`, `useQuery` for an Effect `queryFn` (a typed `ErrorOf`)
|
|
18
|
+
* returns a `Result` in the first tuple slot instead of a promise that
|
|
19
|
+
* rejects. Defaults to `false` when `layer` is set, `true` otherwise.
|
|
20
|
+
* Promise `queryFn`s always throw; this flag does not apply to them.
|
|
21
|
+
*/
|
|
22
|
+
readonly throwOnError?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The query cache, with Promise methods. Create one per request on the
|
|
26
|
+
* server and one per browser tab. `effect` is the same cache's Effect API.
|
|
27
|
+
* Its verbs take factories whose `queryFn` needs at most `TServices`, the
|
|
28
|
+
* services of `config.layer`.
|
|
29
|
+
*/
|
|
30
|
+
export declare class QueryClient<TServices = never> {
|
|
31
|
+
#private;
|
|
32
|
+
/** The Effect-native cache behind this client. */
|
|
33
|
+
readonly effect: QueryCache;
|
|
34
|
+
readonly isServer: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Default for Effect `useQuery` reads. See
|
|
37
|
+
* {@link QueryClientConfig.throwOnError}.
|
|
38
|
+
*/
|
|
39
|
+
readonly throwOnError: boolean;
|
|
40
|
+
constructor(config?: QueryClientConfig<TServices>);
|
|
41
|
+
/**
|
|
42
|
+
* The cache entry for a factory call, created if missing. Doesn't fetch.
|
|
43
|
+
* For bindings; apps use the verbs.
|
|
44
|
+
*/
|
|
45
|
+
getQuery<TFactory extends QueryFactory<TServices>>(factory: TFactory, ...args: InputArgs<TFactory>): Query<TFactory>;
|
|
46
|
+
/**
|
|
47
|
+
* Start a query without waiting and return its handle. Fresh data is
|
|
48
|
+
* reused; stale data refetches in the background (in the browser only).
|
|
49
|
+
* Errors surface where the data is read, never here.
|
|
50
|
+
*/
|
|
51
|
+
prefetch<TFactory extends QueryFactory<TServices>>(factory: TFactory, ...args: InputArgs<TFactory>): QueryHandle<TFactory>;
|
|
52
|
+
/**
|
|
53
|
+
* {@link fetch} without waiting on stale data: start a query and wait for
|
|
54
|
+
* it on a cache miss. Stale cached data resolves immediately and
|
|
55
|
+
* revalidates in the browser. Rejects if that request fails.
|
|
56
|
+
*/
|
|
57
|
+
ensure<TFactory extends QueryFactory<TServices>>(factory: TFactory, ...args: InputArgs<TFactory>): Promise<QueryHandleWithData<TFactory>>;
|
|
58
|
+
/** {@link ensure}, loading `options.pages` pages of a paged query. */
|
|
59
|
+
ensure<TFactory extends PagedQueryFactory<TServices>>(factory: TFactory, input: InputOf<TFactory>, options: LoadOptions): Promise<QueryHandleWithData<TFactory>>;
|
|
60
|
+
/**
|
|
61
|
+
* Start a query and resolve to a handle holding its data. Waits on a cache
|
|
62
|
+
* miss and on stale data (on the server, only invalidated data), so the
|
|
63
|
+
* data is fresh. Rejects if that request fails.
|
|
64
|
+
*/
|
|
65
|
+
fetch<TFactory extends QueryFactory<TServices>>(factory: TFactory, ...args: InputArgs<TFactory>): Promise<QueryHandleWithData<TFactory>>;
|
|
66
|
+
/** {@link fetch}, loading `options.pages` pages of a paged query. */
|
|
67
|
+
fetch<TFactory extends PagedQueryFactory<TServices>>(factory: TFactory, input: InputOf<TFactory>, options: LoadOptions): Promise<QueryHandleWithData<TFactory>>;
|
|
68
|
+
/**
|
|
69
|
+
* Mark queries stale and refetch the ones being read. Resolves once those
|
|
70
|
+
* refetches settle, so an action stays pending until fresh data is on
|
|
71
|
+
* screen. Takes a factory and its input, or a {@link QueryCache.Filter}.
|
|
72
|
+
*/
|
|
73
|
+
invalidate<TFactory extends QueryFactory<TServices>>(...target: Target<TFactory>): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Drop queries' data and errors, as if they were never fetched. Read ones
|
|
76
|
+
* suspend and refetch; resolves once those refetches settle.
|
|
77
|
+
*/
|
|
78
|
+
reset<TFactory extends QueryFactory<TServices>>(...target: Target<TFactory>): Promise<void>;
|
|
79
|
+
/**
|
|
80
|
+
* Remove queries from the cache and abort their requests. Readers keep the
|
|
81
|
+
* removed entry until they render again.
|
|
82
|
+
*/
|
|
83
|
+
remove<TFactory extends QueryFactory<TServices>>(...target: Target<TFactory>): void;
|
|
84
|
+
/** Write data for a factory call. Readers re-render without suspending. */
|
|
85
|
+
setData<TFactory extends QueryFactory<TServices>>(factory: TFactory, input: InputOf<TFactory>, updater: Updater<DataOf<TFactory>>): void;
|
|
86
|
+
/**
|
|
87
|
+
* Cached data for a factory call, or `undefined` if there is none. Data can
|
|
88
|
+
* itself be `undefined`; `Query.getUnsafe(getQuery(...)).hasData` tells
|
|
89
|
+
* them apart.
|
|
90
|
+
*/
|
|
91
|
+
getData<TFactory extends QueryFactory<TServices>>(factory: TFactory, ...args: InputArgs<TFactory>): DataOf<TFactory> | undefined;
|
|
92
|
+
/** Every entry, for devtools and integrations. */
|
|
93
|
+
getAll(): ReadonlyArray<Query>;
|
|
94
|
+
/**
|
|
95
|
+
* How many entries have a request in flight: all of them, or those
|
|
96
|
+
* matching `filter`. For global loading indicators.
|
|
97
|
+
*/
|
|
98
|
+
isFetching(filter?: Filter): number;
|
|
99
|
+
/** Listen to entries being added, updated, and removed. */
|
|
100
|
+
subscribe(listener: (event: Event) => void): () => void;
|
|
101
|
+
/** Drop every entry and abort in-flight requests. */
|
|
102
|
+
clear(): void;
|
|
103
|
+
/**
|
|
104
|
+
* Start refetching read, stale queries on window focus and reconnect.
|
|
105
|
+
* Returns the matching unmount. Providers call this; mounts are counted.
|
|
106
|
+
*/
|
|
107
|
+
mount(): () => void;
|
|
108
|
+
/**
|
|
109
|
+
* Run an Effect with the cache (`QueryCache`) and the services of
|
|
110
|
+
* `config.layer`, as `Effect.runPromise` does: it rejects with the
|
|
111
|
+
* Effect's failure, or its defect. For an app's loaders and actions.
|
|
112
|
+
*/
|
|
113
|
+
runPromise<A, E>(effect: Effect.Effect<A, E, QueryCache | TServices>): Promise<A>;
|
|
114
|
+
/**
|
|
115
|
+
* Close the client: interrupt every request and timer, and release the
|
|
116
|
+
* layer's services. Resolves once the layer's finalizers have run.
|
|
117
|
+
*/
|
|
118
|
+
dispose(): Promise<void>;
|
|
119
|
+
}
|
|
120
|
+
/** Snapshot the cache for transfer to another client (server → browser). */
|
|
121
|
+
export declare function dehydrate(client: QueryClient, options?: DehydrateOptions): DehydratedState;
|
|
122
|
+
/**
|
|
123
|
+
* Write dehydrated entries into a client. Existing entries only take newer
|
|
124
|
+
* data; pending entries resume the sender's request.
|
|
125
|
+
*/
|
|
126
|
+
export declare function hydrate(client: QueryClient, state: DehydratedState, options?: HydrateOptions): void;
|
|
127
|
+
//#endregion
|
package/dist/client.mjs
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { QueryCache, layer } from "./cache.mjs";
|
|
2
|
+
import { Context, Effect, Exit, Layer, Option, Predicate, Scope } from "effect";
|
|
3
|
+
//#region src/client.ts
|
|
4
|
+
/**
|
|
5
|
+
* A verb's Effect, run by a client. Its requirement is the factory's
|
|
6
|
+
* `ServicesOf`, which the client's verbs only accept when its `layer`
|
|
7
|
+
* provides them.
|
|
8
|
+
*/
|
|
9
|
+
function provided(effect) {
|
|
10
|
+
return effect;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Run a verb that waits on a request. A cancelled request rejects with an
|
|
14
|
+
* `AbortError`, as an aborted `fetch` does, not with the Effect API's
|
|
15
|
+
* `Query.CancelledError`.
|
|
16
|
+
*/
|
|
17
|
+
function runLoad(effect) {
|
|
18
|
+
return Effect.runPromise(effect).catch((error) => {
|
|
19
|
+
throw Predicate.isTagged(error, "CancelledError") ? new DOMException("The query was cancelled", "AbortError") : error;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/** `ensure` / `fetch` arguments, forwarded to the cache's same verb. */
|
|
23
|
+
function loadArgs(input, options) {
|
|
24
|
+
return [input, options];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The query cache, with Promise methods. Create one per request on the
|
|
28
|
+
* server and one per browser tab. `effect` is the same cache's Effect API.
|
|
29
|
+
* Its verbs take factories whose `queryFn` needs at most `TServices`, the
|
|
30
|
+
* services of `config.layer`.
|
|
31
|
+
*/
|
|
32
|
+
var QueryClient = class {
|
|
33
|
+
/** The Effect-native cache behind this client. */
|
|
34
|
+
effect;
|
|
35
|
+
isServer;
|
|
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
|
+
/**
|
|
47
|
+
* Owns the cache's fibers, mounts and the layer's services; lives until
|
|
48
|
+
* `dispose()`.
|
|
49
|
+
*/
|
|
50
|
+
#scope = Scope.makeUnsafe();
|
|
51
|
+
constructor(config = {}) {
|
|
52
|
+
const cache = layer(config);
|
|
53
|
+
const services = Effect.runSync(Layer.buildWithScope(config.layer ? Layer.provideMerge(cache, config.layer) : cache, this.#scope));
|
|
54
|
+
this.#context = services;
|
|
55
|
+
this.effect = Context.get(services, QueryCache);
|
|
56
|
+
this.isServer = this.effect.isServer;
|
|
57
|
+
this.throwOnError = config.throwOnError ?? config.layer === void 0;
|
|
58
|
+
const onError = config.onError;
|
|
59
|
+
if (onError) this.effect.subscribeUnsafe((event) => {
|
|
60
|
+
if (event.type === "failed") onError(event.error, event.query);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The cache entry for a factory call, created if missing. Doesn't fetch.
|
|
65
|
+
* For bindings; apps use the verbs.
|
|
66
|
+
*/
|
|
67
|
+
getQuery(factory, ...args) {
|
|
68
|
+
return this.effect.getQueryUnsafe(factory, ...args);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Start a query without waiting and return its handle. Fresh data is
|
|
72
|
+
* reused; stale data refetches in the background (in the browser only).
|
|
73
|
+
* Errors surface where the data is read, never here.
|
|
74
|
+
*/
|
|
75
|
+
prefetch(factory, ...args) {
|
|
76
|
+
return Effect.runSync(provided(this.effect.prefetch(factory, ...args)));
|
|
77
|
+
}
|
|
78
|
+
ensure(factory, input, options) {
|
|
79
|
+
return runLoad(provided(this.effect.ensure(factory, ...loadArgs(input, options))));
|
|
80
|
+
}
|
|
81
|
+
fetch(factory, input, options) {
|
|
82
|
+
return runLoad(provided(this.effect.fetch(factory, ...loadArgs(input, options))));
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Mark queries stale and refetch the ones being read. Resolves once those
|
|
86
|
+
* refetches settle, so an action stays pending until fresh data is on
|
|
87
|
+
* screen. Takes a factory and its input, or a {@link QueryCache.Filter}.
|
|
88
|
+
*/
|
|
89
|
+
invalidate(...target) {
|
|
90
|
+
return Effect.runPromise(this.effect.invalidate(...target));
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Drop queries' data and errors, as if they were never fetched. Read ones
|
|
94
|
+
* suspend and refetch; resolves once those refetches settle.
|
|
95
|
+
*/
|
|
96
|
+
reset(...target) {
|
|
97
|
+
return Effect.runPromise(this.effect.reset(...target));
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Remove queries from the cache and abort their requests. Readers keep the
|
|
101
|
+
* removed entry until they render again.
|
|
102
|
+
*/
|
|
103
|
+
remove(...target) {
|
|
104
|
+
Effect.runSync(this.effect.remove(...target));
|
|
105
|
+
}
|
|
106
|
+
/** Write data for a factory call. Readers re-render without suspending. */
|
|
107
|
+
setData(factory, input, updater) {
|
|
108
|
+
Effect.runSync(this.effect.setData(factory, input, updater));
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Cached data for a factory call, or `undefined` if there is none. Data can
|
|
112
|
+
* itself be `undefined`; `Query.getUnsafe(getQuery(...)).hasData` tells
|
|
113
|
+
* them apart.
|
|
114
|
+
*/
|
|
115
|
+
getData(factory, ...args) {
|
|
116
|
+
return Option.getOrUndefined(Effect.runSync(this.effect.getData(factory, ...args)));
|
|
117
|
+
}
|
|
118
|
+
/** Every entry, for devtools and integrations. */
|
|
119
|
+
getAll() {
|
|
120
|
+
return Effect.runSync(this.effect.all);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* How many entries have a request in flight: all of them, or those
|
|
124
|
+
* matching `filter`. For global loading indicators.
|
|
125
|
+
*/
|
|
126
|
+
isFetching(filter) {
|
|
127
|
+
return this.effect.isFetchingUnsafe(filter);
|
|
128
|
+
}
|
|
129
|
+
/** Listen to entries being added, updated, and removed. */
|
|
130
|
+
subscribe(listener) {
|
|
131
|
+
return this.effect.subscribeUnsafe(listener);
|
|
132
|
+
}
|
|
133
|
+
/** Drop every entry and abort in-flight requests. */
|
|
134
|
+
clear() {
|
|
135
|
+
Effect.runSync(this.effect.clear);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Start refetching read, stale queries on window focus and reconnect.
|
|
139
|
+
* Returns the matching unmount. Providers call this; mounts are counted.
|
|
140
|
+
*/
|
|
141
|
+
mount() {
|
|
142
|
+
const scope = Scope.forkUnsafe(this.#scope);
|
|
143
|
+
Effect.runSync(this.effect.mount.pipe(Scope.provide(scope)));
|
|
144
|
+
return () => {
|
|
145
|
+
Effect.runSync(Scope.close(scope, Exit.void));
|
|
146
|
+
};
|
|
147
|
+
}
|
|
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
|
+
/**
|
|
157
|
+
* Close the client: interrupt every request and timer, and release the
|
|
158
|
+
* layer's services. Resolves once the layer's finalizers have run.
|
|
159
|
+
*/
|
|
160
|
+
dispose() {
|
|
161
|
+
return Effect.runPromise(Scope.close(this.#scope, Exit.void));
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
/** Snapshot the cache for transfer to another client (server → browser). */
|
|
165
|
+
function dehydrate(client, options) {
|
|
166
|
+
return Effect.runSync(client.effect.dehydrate(options));
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Write dehydrated entries into a client. Existing entries only take newer
|
|
170
|
+
* data; pending entries resume the sender's request.
|
|
171
|
+
*/
|
|
172
|
+
function hydrate(client, state, options) {
|
|
173
|
+
Effect.runSync(client.effect.hydrate(state, options));
|
|
174
|
+
}
|
|
175
|
+
//#endregion
|
|
176
|
+
export { QueryClient, dehydrate, hydrate };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { QueryKey } from "./key.mjs";
|
|
2
|
+
import { DataOf, ErrorOf, InputOf, IsPaged, QueryFactory } from "./options.mjs";
|
|
3
|
+
import { Thenable } from "./thenable.mjs";
|
|
4
|
+
import { Options, Result as Result$1, observer_d_exports } from "./observer.mjs";
|
|
5
|
+
import { Result } from "effect";
|
|
6
|
+
//#region src/controls.d.ts
|
|
7
|
+
/** `refetchInterval`, `refetchOnWindowFocus`, `refetchOnReconnect`, `whilePending`. */
|
|
8
|
+
export type UseQueryOptions = Options;
|
|
9
|
+
/**
|
|
10
|
+
* `throwOnError` only when {@link ErrorOf} is typed (Effect `queryFn`).
|
|
11
|
+
* Promise factories always throw; the option is not part of their options.
|
|
12
|
+
*/
|
|
13
|
+
export type UseQueryThrowOptions<TFactory extends QueryFactory> = HasTypedError<TFactory> extends true ? {
|
|
14
|
+
/**
|
|
15
|
+
* When `false`, the data is a `Result` instead of a rejecting
|
|
16
|
+
* promise. Omit to use the client's default (`false` with a
|
|
17
|
+
* `layer`, `true` without).
|
|
18
|
+
*/
|
|
19
|
+
readonly throwOnError?: boolean;
|
|
20
|
+
} : {
|
|
21
|
+
readonly throwOnError?: never;
|
|
22
|
+
};
|
|
23
|
+
/** What `useQuery` returns next to the data. */
|
|
24
|
+
export interface QueryControls<TFactory extends QueryFactory> {
|
|
25
|
+
/** Error of the last failed request. Existing data stays readable. */
|
|
26
|
+
readonly error: unknown;
|
|
27
|
+
/** Stable string form of the handle's key, for `key` props. */
|
|
28
|
+
readonly hash: string;
|
|
29
|
+
/** Input of the data in the promise; differs from the handle's while `isPrevious`. */
|
|
30
|
+
readonly input: InputOf<TFactory>;
|
|
31
|
+
/** A request is in flight: first load, new input, or background refetch. */
|
|
32
|
+
readonly isFetching: boolean;
|
|
33
|
+
/** No data yet for the handle's input. */
|
|
34
|
+
readonly isPending: boolean;
|
|
35
|
+
/** The promise holds a previous input's data (`whilePending: "previous"`). */
|
|
36
|
+
readonly isPrevious: boolean;
|
|
37
|
+
/** The data is older than `staleTime`, or was invalidated. */
|
|
38
|
+
readonly isStale: boolean;
|
|
39
|
+
/** Key of the data in the promise; differs from the handle's while `isPrevious`. */
|
|
40
|
+
readonly key: QueryKey;
|
|
41
|
+
/** Refetch now. Resolves when the request settles; never rejects. */
|
|
42
|
+
refetch(): Promise<void>;
|
|
43
|
+
/** When the data in the promise was fetched (ms since epoch); `0` before that. */
|
|
44
|
+
readonly updatedAt: number;
|
|
45
|
+
}
|
|
46
|
+
/** Extra controls for factories that return `infiniteQueryOptions(...)`. */
|
|
47
|
+
export interface PagingControls {
|
|
48
|
+
fetchNextPage(): Promise<void>;
|
|
49
|
+
fetchPreviousPage(): Promise<void>;
|
|
50
|
+
readonly hasNextPage: boolean;
|
|
51
|
+
readonly hasPreviousPage: boolean;
|
|
52
|
+
readonly isFetchingNextPage: boolean;
|
|
53
|
+
readonly isFetchingPreviousPage: boolean;
|
|
54
|
+
}
|
|
55
|
+
export type UseQueryControls<TFactory extends QueryFactory> = IsPaged<TFactory> extends true ? QueryControls<TFactory> & PagingControls : QueryControls<TFactory>;
|
|
56
|
+
/**
|
|
57
|
+
* `true` when the factory's error channel is typed (Effect `queryFn`), not
|
|
58
|
+
* `unknown` (promise / plain value).
|
|
59
|
+
*/
|
|
60
|
+
export type HasTypedError<TFactory extends QueryFactory> = unknown extends ErrorOf<TFactory> ? false : true;
|
|
61
|
+
/**
|
|
62
|
+
* Whether this read throws on failure. Untyped errors always throw. Typed
|
|
63
|
+
* errors follow `options.throwOnError`, else the client's default (`false`
|
|
64
|
+
* with a `layer`, `true` without). Types treat an omitted flag as `false`
|
|
65
|
+
* (Result), matching a client with a `layer`.
|
|
66
|
+
*/
|
|
67
|
+
export type ThrowsOnError<TFactory extends QueryFactory, TThrow extends boolean | undefined> = HasTypedError<TFactory> extends false ? true : TThrow extends boolean ? TThrow : false;
|
|
68
|
+
/** What a read settles with: the data, or a `Result` when it doesn't throw. */
|
|
69
|
+
export type ReadValue<TFactory extends QueryFactory, TThrow extends boolean | undefined = undefined> = ThrowsOnError<TFactory, TThrow> extends true ? DataOf<TFactory> : Result.Result<DataOf<TFactory>, ErrorOf<TFactory>>;
|
|
70
|
+
type Command = "fetchNextPage" | "fetchPreviousPage" | "refetch";
|
|
71
|
+
/** An observer result whose command Effects are Promise functions. */
|
|
72
|
+
export type PromiseControls = Omit<Result$1, Command> & Record<Command, () => Promise<void>>;
|
|
73
|
+
/**
|
|
74
|
+
* `result`, with the observer's command Effects as Promise functions. Reads
|
|
75
|
+
* of every other property go through to `result`, so a tracked result
|
|
76
|
+
* (`QueryObserver.trackResult`) stays tracked. For bindings.
|
|
77
|
+
*/
|
|
78
|
+
export declare function promiseControls(result: Result$1): PromiseControls;
|
|
79
|
+
/**
|
|
80
|
+
* A thenable that fulfills with a `Result` when `promise` settles, for reads
|
|
81
|
+
* that don't throw (`throwOnError: false`). Settled already when `promise`
|
|
82
|
+
* is, and the same thenable for the same `promise`. For bindings.
|
|
83
|
+
*/
|
|
84
|
+
export declare function resultThenable<A, E>(promise: Thenable<A>): Thenable<Result.Result<A, E>>;
|
|
85
|
+
//#endregion
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { fulfilled, pending } from "./thenable.mjs";
|
|
2
|
+
import { Effect, Result } from "effect";
|
|
3
|
+
//#region src/controls.ts
|
|
4
|
+
/**
|
|
5
|
+
* What a framework binding's `useQuery` returns next to the data: the
|
|
6
|
+
* observer's result with Promise commands, and the types every binding
|
|
7
|
+
* shares. The React, Svelte and Vue packages build their reads on this.
|
|
8
|
+
*/
|
|
9
|
+
const commands = /* @__PURE__ */ new WeakMap();
|
|
10
|
+
/** One stable Promise function per command Effect, so identities track the query's. */
|
|
11
|
+
function commandFor(effect) {
|
|
12
|
+
let command = commands.get(effect);
|
|
13
|
+
if (!command) {
|
|
14
|
+
command = () => Effect.runPromise(effect);
|
|
15
|
+
commands.set(effect, command);
|
|
16
|
+
}
|
|
17
|
+
return command;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* `result`, with the observer's command Effects as Promise functions. Reads
|
|
21
|
+
* of every other property go through to `result`, so a tracked result
|
|
22
|
+
* (`QueryObserver.trackResult`) stays tracked. For bindings.
|
|
23
|
+
*/
|
|
24
|
+
function promiseControls(result) {
|
|
25
|
+
return new Proxy(result, { get: (target, name, receiver) => {
|
|
26
|
+
if (name === "fetchNextPage" || name === "fetchPreviousPage" || name === "refetch") return commandFor(target[name]);
|
|
27
|
+
return Reflect.get(target, name, receiver);
|
|
28
|
+
} });
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* One `Result` thenable per data thenable, so readers see a stable identity
|
|
32
|
+
* across re-renders the same way they do for the data promise.
|
|
33
|
+
*/
|
|
34
|
+
const resultThenables = /* @__PURE__ */ new WeakMap();
|
|
35
|
+
/**
|
|
36
|
+
* A thenable that fulfills with a `Result` when `promise` settles, for reads
|
|
37
|
+
* that don't throw (`throwOnError: false`). Settled already when `promise`
|
|
38
|
+
* is, and the same thenable for the same `promise`. For bindings.
|
|
39
|
+
*/
|
|
40
|
+
function resultThenable(promise) {
|
|
41
|
+
const cached = resultThenables.get(promise);
|
|
42
|
+
if (cached) return cached;
|
|
43
|
+
let next;
|
|
44
|
+
if (promise.status === "fulfilled") next = fulfilled(Result.succeed(promise.value));
|
|
45
|
+
else if (promise.status === "rejected") next = fulfilled(Result.fail(promise.reason));
|
|
46
|
+
else {
|
|
47
|
+
const deferred = pending();
|
|
48
|
+
promise.then((value) => {
|
|
49
|
+
deferred.resolve(Result.succeed(value));
|
|
50
|
+
}, (error) => {
|
|
51
|
+
deferred.resolve(Result.fail(error));
|
|
52
|
+
});
|
|
53
|
+
next = deferred.promise;
|
|
54
|
+
}
|
|
55
|
+
resultThenables.set(promise, next);
|
|
56
|
+
return next;
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
export { promiseControls, resultThenable };
|
package/dist/entry.d.mts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { Platform, PlatformEvent } from "./platform.mjs";
|
|
2
1
|
import { QueryKey } from "./key.mjs";
|
|
3
2
|
import { AnyQueryOptions, QueryBehavior, QueryFactory, QueryHandle, QueryMeta } from "./options.mjs";
|
|
4
|
-
import {
|
|
3
|
+
import { Platform, PlatformEvent } from "./platform.mjs";
|
|
5
4
|
import { Pending, Thenable } from "./thenable.mjs";
|
|
6
5
|
import { FetchRequest, Query, State, SubscribeOptions } from "./query.mjs";
|
|
6
|
+
import { Event } from "./cache.mjs";
|
|
7
7
|
import { Cause, Clock, Context, Deferred, Effect, Scope, Stream } from "effect";
|
|
8
8
|
declare namespace entry_d_exports {
|
|
9
9
|
export { BrowserOnlyError, CacheState, CancelledError, Entry, TypeId, changes, emit, entryOf, fetch, hasNextPageUnsafe, hasPreviousPageUnsafe, invalidate, isBrowserOnly, isObserved, isQuery, isStaleUnsafe, make, metaOf, needsRefetch, onPlatformEvent, provide, readUnsafe, remove, reset, resume, revalidate, setData, setOptions, start, subscribeUnsafe };
|
package/dist/entry.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { whenOnline } from "./platform.mjs";
|
|
2
1
|
import { replaceEqualDeep } from "./share.mjs";
|
|
3
2
|
import { fetchPages, isInfiniteData, nextPageParam, previousPageParam } from "./infinite.mjs";
|
|
4
3
|
import { listen } from "./listen.mjs";
|
|
5
4
|
import { scheduleNotify } from "./notify.mjs";
|
|
5
|
+
import { whenOnline } from "./platform.mjs";
|
|
6
6
|
import { fromQueryFn, requestServices, runRequest } from "./request.mjs";
|
|
7
7
|
import { retrySchedule } from "./retry.mjs";
|
|
8
8
|
import { fulfilled, pending } from "./thenable.mjs";
|
package/dist/hydration.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { QueryKey } from "./key.mjs";
|
|
2
2
|
import { QueryFactory, QueryMeta } from "./options.mjs";
|
|
3
|
-
import "./cache.mjs";
|
|
4
|
-
import "./entry.mjs";
|
|
5
3
|
import { Query, query_d_exports } from "./query.mjs";
|
|
4
|
+
import "./entry.mjs";
|
|
5
|
+
import "./cache.mjs";
|
|
6
6
|
import { Cause, Effect, Scope, Stream } from "effect";
|
|
7
7
|
//#region src/hydration.d.ts
|
|
8
8
|
/** What a dehydrated pending query's promise resolves to. */
|
package/dist/index.d.mts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Settled, allKeyed } from "./all-keyed.mjs";
|
|
2
2
|
import { QueryKey, hashKey, matchesKey } from "./key.mjs";
|
|
3
3
|
import { AnyQueryOptions, DataOf, ErrorOf, InfiniteData, InfiniteQueryOptions, InfiniteQueryTypes, InputArgs, InputOf, IsPaged, LoadOptions, PageFunctionContext, PageOf, PagedQueryFactory, QueryBehavior, QueryFactory, QueryFnResult, QueryFunctionContext, QueryHandle, QueryHandleWithData, QueryMeta, QueryOptions, QueryTypes, Register, RetryPolicy, ServicesOf, infiniteQueryOptions, queryOptions } from "./options.mjs";
|
|
4
|
-
import {
|
|
5
|
-
import { cache_d_exports } from "./cache.mjs";
|
|
4
|
+
import { platform_d_exports } from "./platform.mjs";
|
|
6
5
|
import { Pending, Thenable, fulfilled, pending } from "./thenable.mjs";
|
|
7
6
|
import { query_d_exports } from "./query.mjs";
|
|
7
|
+
import { DehydrateOptions, DehydratedQuery, DehydratedResult, DehydratedState, DehydratedStream, HydrateOptions, RedactedError, canDehydrate, dehydrateQuery } from "./hydration.mjs";
|
|
8
|
+
import { cache_d_exports } from "./cache.mjs";
|
|
9
|
+
import { QueryClient, QueryClientConfig, dehydrate, hydrate } from "./client.mjs";
|
|
8
10
|
import { observer_d_exports } from "./observer.mjs";
|
|
9
|
-
|
|
11
|
+
import { HasTypedError, PagingControls, PromiseControls, QueryControls, ReadValue, ThrowsOnError, UseQueryControls, UseQueryOptions, UseQueryThrowOptions, promiseControls, resultThenable } from "./controls.mjs";
|
|
12
|
+
export { type AnyQueryOptions, type DataOf, type DehydrateOptions, type DehydratedQuery, type DehydratedResult, type DehydratedState, type DehydratedStream, type ErrorOf, type HasTypedError, type HydrateOptions, type InfiniteData, type InfiniteQueryOptions, type InfiniteQueryTypes, type InputArgs, type InputOf, type IsPaged, type LoadOptions, type PageFunctionContext, type PageOf, type PagedQueryFactory, type PagingControls, type Pending, platform_d_exports as Platform, type PromiseControls, query_d_exports as Query, type QueryBehavior, cache_d_exports as QueryCache, QueryClient, type QueryClientConfig, type QueryControls, type QueryFactory, type QueryFnResult, type QueryFunctionContext, type QueryHandle, type QueryHandleWithData, type QueryKey, type QueryMeta, observer_d_exports as QueryObserver, type QueryOptions, type QueryTypes, type ReadValue, RedactedError, type Register, type RetryPolicy, type ServicesOf, type Settled, type Thenable, type ThrowsOnError, type UseQueryControls, type UseQueryOptions, type UseQueryThrowOptions, allKeyed, canDehydrate, dehydrate, dehydrateQuery, fulfilled, hashKey, hydrate, infiniteQueryOptions, matchesKey, pending, promiseControls, queryOptions, resultThenable };
|
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { allKeyed } from "./all-keyed.mjs";
|
|
2
2
|
import { hashKey, matchesKey } from "./key.mjs";
|
|
3
|
+
import { platform_exports } from "./platform.mjs";
|
|
3
4
|
import { fulfilled, pending } from "./thenable.mjs";
|
|
4
5
|
import { query_exports } from "./query.mjs";
|
|
5
6
|
import { RedactedError, canDehydrate, dehydrateQuery } from "./hydration.mjs";
|
|
6
7
|
import { infiniteQueryOptions, queryOptions } from "./options.mjs";
|
|
7
8
|
import { cache_exports } from "./cache.mjs";
|
|
9
|
+
import { QueryClient, dehydrate, hydrate } from "./client.mjs";
|
|
10
|
+
import { promiseControls, resultThenable } from "./controls.mjs";
|
|
8
11
|
import { observer_exports } from "./observer.mjs";
|
|
9
|
-
export { platform_exports as Platform, query_exports as Query, cache_exports as QueryCache, observer_exports as QueryObserver, RedactedError, canDehydrate, dehydrateQuery, fulfilled, hashKey, infiniteQueryOptions, matchesKey, pending, queryOptions };
|
|
12
|
+
export { platform_exports as Platform, query_exports as Query, cache_exports as QueryCache, QueryClient, observer_exports as QueryObserver, RedactedError, allKeyed, canDehydrate, dehydrate, dehydrateQuery, fulfilled, hashKey, hydrate, infiniteQueryOptions, matchesKey, pending, promiseControls, queryOptions, resultThenable };
|
package/dist/observer.d.mts
CHANGED
|
@@ -4,7 +4,7 @@ import { Thenable } from "./thenable.mjs";
|
|
|
4
4
|
import { Query, query_d_exports } from "./query.mjs";
|
|
5
5
|
import { Effect, Pipeable, Stream } from "effect";
|
|
6
6
|
declare namespace observer_d_exports {
|
|
7
|
-
export { Options, QueryObserver, Result, ResultKey, TypeId, changes, get, getOptimisticResultUnsafe, getUnsafe, isQueryObserver, make, makeUnsafe, setOptionsUnsafe, subscribeUnsafe, trackProp, trackResult };
|
|
7
|
+
export { Options, QueryObserver, Result$1 as Result, ResultKey, TypeId, changes, get, getOptimisticResultUnsafe, getUnsafe, isQueryObserver, make, makeUnsafe, setOptionsUnsafe, subscribeUnsafe, trackProp, trackResult, trackThenable };
|
|
8
8
|
}
|
|
9
9
|
export declare const TypeId = "~@raf-query/core/QueryObserver";
|
|
10
10
|
export type TypeId = typeof TypeId;
|
|
@@ -24,7 +24,7 @@ export interface Options {
|
|
|
24
24
|
readonly whilePending?: "previous" | "suspend";
|
|
25
25
|
}
|
|
26
26
|
/** What a reader sees of its query. Replaced, never mutated, on every change. */
|
|
27
|
-
|
|
27
|
+
interface Result$1<TData = unknown> {
|
|
28
28
|
/** Error of the last failed request. Existing data stays readable. */
|
|
29
29
|
readonly error: unknown;
|
|
30
30
|
/** The query's `fetchNextPage`. */
|
|
@@ -56,7 +56,7 @@ export interface Result<TData = unknown> {
|
|
|
56
56
|
/** When the data in `promise` was fetched (ms since epoch); `0` before that. */
|
|
57
57
|
readonly updatedAt: number;
|
|
58
58
|
}
|
|
59
|
-
export type ResultKey = keyof Result;
|
|
59
|
+
export type ResultKey = keyof Result$1;
|
|
60
60
|
/** An observer of a factory's queries; its results hold the factory's {@link DataOf}. */
|
|
61
61
|
export interface QueryObserver<TFactory extends QueryFactory = QueryFactory> extends Pipeable.Pipeable {
|
|
62
62
|
/** The options set by the latest `make` or {@link setOptionsUnsafe}. */
|
|
@@ -73,14 +73,14 @@ export declare function makeUnsafe<TFactory extends QueryFactory>(query: Query<T
|
|
|
73
73
|
/** {@link makeUnsafe}, as an Effect. */
|
|
74
74
|
export declare function make<TFactory extends QueryFactory>(query: Query<TFactory>, options?: Options): Effect.Effect<QueryObserver<TFactory>>;
|
|
75
75
|
/** The current result. */
|
|
76
|
-
export declare function getUnsafe<TFactory extends QueryFactory>(self: QueryObserver<TFactory>): Result<DataOf<TFactory>>;
|
|
76
|
+
export declare function getUnsafe<TFactory extends QueryFactory>(self: QueryObserver<TFactory>): Result$1<DataOf<TFactory>>;
|
|
77
77
|
/** {@link getUnsafe}, as an Effect. */
|
|
78
|
-
export declare function get<TFactory extends QueryFactory>(self: QueryObserver<TFactory>): Effect.Effect<Result<DataOf<TFactory>>>;
|
|
78
|
+
export declare function get<TFactory extends QueryFactory>(self: QueryObserver<TFactory>): Effect.Effect<Result$1<DataOf<TFactory>>>;
|
|
79
79
|
/**
|
|
80
80
|
* The result for `query` with `options` right now, without switching to
|
|
81
81
|
* them. Bindings call this during render, before `setOptionsUnsafe` commits.
|
|
82
82
|
*/
|
|
83
|
-
export declare function getOptimisticResultUnsafe<TFactory extends QueryFactory>(self: QueryObserver<TFactory>, query: Query<TFactory>, options: Options): Result<DataOf<TFactory>>;
|
|
83
|
+
export declare function getOptimisticResultUnsafe<TFactory extends QueryFactory>(self: QueryObserver<TFactory>, query: Query<TFactory>, options: Options): Result$1<DataOf<TFactory>>;
|
|
84
84
|
/** Observe `query` with `options` from now on. */
|
|
85
85
|
export declare function setOptionsUnsafe<TFactory extends QueryFactory>(self: QueryObserver<TFactory>, query: Query<TFactory>, options: Options): void;
|
|
86
86
|
/** Listen for result changes. The first listener subscribes to the query. */
|
|
@@ -89,14 +89,22 @@ export declare function subscribeUnsafe<TFactory extends QueryFactory>(self: Que
|
|
|
89
89
|
* The current result, then every change listeners are notified of. Counts
|
|
90
90
|
* as a listener while the stream runs, so the query counts as read.
|
|
91
91
|
*/
|
|
92
|
-
export declare function changes<TFactory extends QueryFactory>(self: QueryObserver<TFactory>): Stream.Stream<Result<DataOf<TFactory>>>;
|
|
92
|
+
export declare function changes<TFactory extends QueryFactory>(self: QueryObserver<TFactory>): Stream.Stream<Result$1<DataOf<TFactory>>>;
|
|
93
93
|
/**
|
|
94
94
|
* Wrap `result` so that reading a property marks it tracked. Once any
|
|
95
95
|
* property is tracked, listeners are only notified when a tracked one
|
|
96
96
|
* changes.
|
|
97
97
|
*/
|
|
98
|
-
export declare function trackResult<TFactory extends QueryFactory, TResult extends Result<DataOf<TFactory>>>(self: QueryObserver<TFactory>, result: TResult): TResult;
|
|
98
|
+
export declare function trackResult<TFactory extends QueryFactory, TResult extends Result$1<DataOf<TFactory>>>(self: QueryObserver<TFactory>, result: TResult): TResult;
|
|
99
|
+
/**
|
|
100
|
+
* Wrap `promise`, the result's or one derived from it, so that reading its
|
|
101
|
+
* `status`, `value` or `reason` marks its settling as tracked. A first load
|
|
102
|
+
* settles its promise in place, without a new promise, so only readers of
|
|
103
|
+
* those fields need to hear about it. The mark is the wrapper's, shared by
|
|
104
|
+
* the observers that hand it out.
|
|
105
|
+
*/
|
|
106
|
+
export declare function trackThenable<TFactory extends QueryFactory, TThenable extends Thenable<unknown>>(self: QueryObserver<TFactory>, promise: TThenable): TThenable;
|
|
99
107
|
/** Mark `name` as read, for bindings that track access themselves. */
|
|
100
108
|
export declare function trackProp<TFactory extends QueryFactory>(self: QueryObserver<TFactory>, name: ResultKey): void;
|
|
101
109
|
//#endregion
|
|
102
|
-
export { observer_d_exports };
|
|
110
|
+
export { Result$1 as Result, observer_d_exports };
|
package/dist/observer.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { __exportAll } from "./_virtual/_rolldown/runtime.mjs";
|
|
2
|
-
import { Platform } from "./platform.mjs";
|
|
3
2
|
import { listen } from "./listen.mjs";
|
|
3
|
+
import { Platform } from "./platform.mjs";
|
|
4
4
|
import { entryOf } from "./entry.mjs";
|
|
5
5
|
import { fetchNextPage, fetchPreviousPage, getUnsafe as getUnsafe$1, hasNextPageUnsafe, hasPreviousPageUnsafe, refetch, subscribeUnsafe as subscribeUnsafe$1 } from "./query.mjs";
|
|
6
6
|
import { Effect, Exit, FiberHandle, Pipeable, Predicate, Scope } from "effect";
|
|
@@ -10,7 +10,8 @@ import { Effect, Exit, FiberHandle, Pipeable, Predicate, Scope } from "effect";
|
|
|
10
10
|
* Query's `QueryObserver`: it derives the reader's result from the query's
|
|
11
11
|
* state, keeps a previous query's data for `whilePending: "previous"`, runs
|
|
12
12
|
* `refetchInterval`, and notifies its listeners only when a result property
|
|
13
|
-
* they read changed (see {@link trackResult})
|
|
13
|
+
* they read changed (see {@link trackResult}), or a promise whose `status`
|
|
14
|
+
* they read settled (see {@link trackThenable}). Bindings make one per hook.
|
|
14
15
|
*
|
|
15
16
|
* The `*Unsafe` functions read and update synchronously, for renders and
|
|
16
17
|
* external stores. While it has listeners, the observer holds a `Scope` (a
|
|
@@ -30,7 +31,8 @@ var observer_exports = /* @__PURE__ */ __exportAll({
|
|
|
30
31
|
setOptionsUnsafe: () => setOptionsUnsafe,
|
|
31
32
|
subscribeUnsafe: () => subscribeUnsafe,
|
|
32
33
|
trackProp: () => trackProp,
|
|
33
|
-
trackResult: () => trackResult
|
|
34
|
+
trackResult: () => trackResult,
|
|
35
|
+
trackThenable: () => trackThenable
|
|
34
36
|
});
|
|
35
37
|
const TypeId = "~@raf-query/core/QueryObserver";
|
|
36
38
|
const Proto = {
|
|
@@ -65,9 +67,16 @@ function implOf(self) {
|
|
|
65
67
|
function isResultKey(name) {
|
|
66
68
|
return Predicate.isString(name) && Object.hasOwn(resultKeys, name);
|
|
67
69
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
return
|
|
70
|
+
/** Whether `next.promise` has a different status than it had in the current result. */
|
|
71
|
+
function settled(self, next) {
|
|
72
|
+
return self.currentStatus !== next.promise.status;
|
|
73
|
+
}
|
|
74
|
+
function anyChanged(self, next) {
|
|
75
|
+
return settled(self, next) || Object.keys(resultKeys).some((name) => isResultKey(name) && self.current[name] !== next[name]);
|
|
76
|
+
}
|
|
77
|
+
function setCurrent(self, next) {
|
|
78
|
+
self.current = next;
|
|
79
|
+
self.currentStatus = next.promise.status;
|
|
71
80
|
}
|
|
72
81
|
/** The result for `query` with `options`, given the last query that had data. */
|
|
73
82
|
function resultOf(query, options, lastWithData) {
|
|
@@ -97,13 +106,16 @@ function resultOf(query, options, lastWithData) {
|
|
|
97
106
|
}
|
|
98
107
|
/** Observe `query`. Doesn't subscribe to it until the observer has a listener. */
|
|
99
108
|
function makeUnsafe(query, options = {}) {
|
|
109
|
+
const current = resultOf(query, options, void 0);
|
|
100
110
|
return Object.assign(Object.create(Proto), {
|
|
101
|
-
current
|
|
111
|
+
current,
|
|
112
|
+
currentStatus: current.promise.status,
|
|
102
113
|
lastWithData: getUnsafe$1(query).hasData ? query : void 0,
|
|
103
114
|
listeners: /* @__PURE__ */ new Set(),
|
|
104
115
|
options,
|
|
105
116
|
query,
|
|
106
117
|
subscription: void 0,
|
|
118
|
+
tracked: void 0,
|
|
107
119
|
trackedProps: /* @__PURE__ */ new Set()
|
|
108
120
|
});
|
|
109
121
|
}
|
|
@@ -126,7 +138,7 @@ function get(self) {
|
|
|
126
138
|
function getOptimisticResultUnsafe(self, query, options) {
|
|
127
139
|
const impl = implOf(self);
|
|
128
140
|
const result = resultOf(query, options, impl.lastWithData);
|
|
129
|
-
if (
|
|
141
|
+
if (anyChanged(impl, result)) setCurrent(impl, result);
|
|
130
142
|
return result;
|
|
131
143
|
}
|
|
132
144
|
/** Observe `query` with `options` from now on. */
|
|
@@ -185,22 +197,62 @@ function trackResult(self, result) {
|
|
|
185
197
|
return Reflect.get(target, name, receiver);
|
|
186
198
|
} });
|
|
187
199
|
}
|
|
200
|
+
const settledFields = /* @__PURE__ */ new Set([
|
|
201
|
+
"reason",
|
|
202
|
+
"status",
|
|
203
|
+
"value"
|
|
204
|
+
]);
|
|
205
|
+
/**
|
|
206
|
+
* One wrapper per promise, shared by every observer, so its identity is as
|
|
207
|
+
* stable as the promise's: an observer made by a render that suspended
|
|
208
|
+
* before committing is replaced on the next attempt, and `use()` expects
|
|
209
|
+
* the same promise again.
|
|
210
|
+
*/
|
|
211
|
+
const wrappers = /* @__PURE__ */ new WeakMap();
|
|
212
|
+
/** Wrappers whose `status`, `value` or `reason` a reader has read. */
|
|
213
|
+
const settledReads = /* @__PURE__ */ new WeakSet();
|
|
214
|
+
/**
|
|
215
|
+
* Wrap `promise`, the result's or one derived from it, so that reading its
|
|
216
|
+
* `status`, `value` or `reason` marks its settling as tracked. A first load
|
|
217
|
+
* settles its promise in place, without a new promise, so only readers of
|
|
218
|
+
* those fields need to hear about it. The mark is the wrapper's, shared by
|
|
219
|
+
* the observers that hand it out.
|
|
220
|
+
*/
|
|
221
|
+
function trackThenable(self, promise) {
|
|
222
|
+
let wrapper = wrappers.get(promise);
|
|
223
|
+
if (!wrapper) {
|
|
224
|
+
const created = new Proxy(promise, { get: (target, name) => {
|
|
225
|
+
if (settledFields.has(name)) settledReads.add(created);
|
|
226
|
+
const value = Reflect.get(target, name, target);
|
|
227
|
+
return Predicate.isFunction(value) ? value.bind(target) : value;
|
|
228
|
+
} });
|
|
229
|
+
wrappers.set(promise, created);
|
|
230
|
+
wrapper = created;
|
|
231
|
+
}
|
|
232
|
+
implOf(self).tracked = wrapper;
|
|
233
|
+
return wrapper;
|
|
234
|
+
}
|
|
235
|
+
function readsSettled(self) {
|
|
236
|
+
return self.tracked !== void 0 && settledReads.has(self.tracked);
|
|
237
|
+
}
|
|
188
238
|
/** Mark `name` as read, for bindings that track access themselves. */
|
|
189
239
|
function trackProp(self, name) {
|
|
190
240
|
implOf(self).trackedProps.add(name);
|
|
191
241
|
}
|
|
192
242
|
/** Recompute the result, and notify listeners if a tracked property changed. */
|
|
193
243
|
function updateResult(self) {
|
|
194
|
-
const previous = self.current;
|
|
195
244
|
const next = resultOf(self.query, self.options, self.lastWithData);
|
|
196
245
|
if (getUnsafe$1(self.query).hasData) self.lastWithData = self.query;
|
|
197
|
-
if (
|
|
198
|
-
|
|
199
|
-
|
|
246
|
+
if (!anyChanged(self, next)) return;
|
|
247
|
+
const notify = shouldNotify(self, next);
|
|
248
|
+
setCurrent(self, next);
|
|
249
|
+
if (notify) for (const listener of self.listeners) listener();
|
|
200
250
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
251
|
+
/** Whether something tracked differs in `next` from the current result. */
|
|
252
|
+
function shouldNotify(self, next) {
|
|
253
|
+
if (self.trackedProps.size === 0 && !readsSettled(self)) return true;
|
|
254
|
+
if (readsSettled(self) && settled(self, next)) return true;
|
|
255
|
+
for (const name of self.trackedProps) if (self.current[name] !== next[name]) return true;
|
|
204
256
|
return false;
|
|
205
257
|
}
|
|
206
258
|
/**
|
|
@@ -245,4 +297,4 @@ function poll(query, interval) {
|
|
|
245
297
|
}).pipe(Effect.delay(interval), Effect.forever);
|
|
246
298
|
}
|
|
247
299
|
//#endregion
|
|
248
|
-
export { TypeId, changes, get, getOptimisticResultUnsafe, getUnsafe, isQueryObserver, make, makeUnsafe, observer_exports, setOptionsUnsafe, subscribeUnsafe, trackProp, trackResult };
|
|
300
|
+
export { TypeId, changes, get, getOptimisticResultUnsafe, getUnsafe, isQueryObserver, make, makeUnsafe, observer_exports, setOptionsUnsafe, subscribeUnsafe, trackProp, trackResult, trackThenable };
|
package/dist/query.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { QueryKey } from "./key.mjs";
|
|
2
2
|
import { AnyQueryOptions, DataOf, ErrorOf, QueryFactory, QueryHandle, QueryMeta, ServicesOf } from "./options.mjs";
|
|
3
|
+
import { Direction } from "./infinite.mjs";
|
|
3
4
|
import { Thenable } from "./thenable.mjs";
|
|
4
5
|
import { BrowserOnlyError as BrowserOnlyError$1, CancelledError as CancelledError$1, TypeId as TypeId$1, entry_d_exports } from "./entry.mjs";
|
|
5
|
-
import { Direction } from "./infinite.mjs";
|
|
6
6
|
import { Effect, Pipeable, Stream } from "effect";
|
|
7
7
|
declare namespace query_d_exports {
|
|
8
8
|
export { BrowserOnlyError, CancelledError, FetchRequest, Query, State, SubscribeOptions, TypeId, changes, fetch, fetchNextPage, fetchPreviousPage, get, getUnsafe, hasNextPageUnsafe, hasPreviousPageUnsafe, invalidate, isQuery, isStaleUnsafe, metaUnsafe, observerCountUnsafe, readUnsafe, refetch, reset, revalidateUnsafe, subscribeUnsafe };
|