@raf-query/core 0.10.1 → 0.10.3
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 +15 -2
- package/dist/binding.d.mts +92 -0
- package/dist/binding.mjs +186 -0
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +2 -1
- package/dist/observer.d.mts +9 -1
- package/dist/observer.mjs +67 -15
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,14 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.10.3](https://github.com/KATT/raf-query/compare/0.10.2...0.10.3) (2026-10-16)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
* **core:** add QueryBinding, a framework-free useQuery for bindings ([#197](https://github.com/KATT/raf-query/issues/197)) ([4865802](https://github.com/KATT/raf-query/commit/48658024f4d21d43ce8274de379a19c8898b6173)), references [#194](https://github.com/KATT/raf-query/issues/194)
|
|
8
|
+
|
|
9
|
+
## [0.10.2](https://github.com/KATT/raf-query/compare/0.10.1...0.10.2) (2026-10-16)
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
- **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))
|
|
14
|
+
- **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))
|
|
15
|
+
|
|
3
16
|
## [0.10.1](https://github.com/KATT/raf-query/compare/0.10.0...0.10.1) (2026-10-16)
|
|
4
17
|
|
|
5
18
|
### Features
|
|
6
19
|
|
|
7
|
-
|
|
20
|
+
- **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)
|
|
8
21
|
|
|
9
22
|
### Documentation
|
|
10
23
|
|
|
11
|
-
|
|
24
|
+
- 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)
|
|
12
25
|
|
|
13
26
|
## [0.10.0](https://github.com/KATT/raf-query/compare/0.9.2...0.10.0) (2026-10-16)
|
|
14
27
|
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { QueryFactory, QueryHandle } from "./options.mjs";
|
|
2
|
+
import { Query, query_d_exports } from "./query.mjs";
|
|
3
|
+
import { QueryClient } from "./client.mjs";
|
|
4
|
+
import { Options, QueryObserver, observer_d_exports } from "./observer.mjs";
|
|
5
|
+
import { ReadValue, UseQueryControls, UseQueryOptions, UseQueryThrowOptions } from "./controls.mjs";
|
|
6
|
+
declare namespace binding_d_exports {
|
|
7
|
+
export { Hooks, QueryBinding, ReadOptions, Result, ResultWithData, Source, makeUnsafe, observerOptions, queryFor, subscribeUnsafe, syncUnsafe };
|
|
8
|
+
}
|
|
9
|
+
/** A read's options: the observer's, and `throwOnError` for typed errors. */
|
|
10
|
+
export type ReadOptions<TFactory extends QueryFactory, TThrow extends boolean | undefined = undefined> = UseQueryOptions & UseQueryThrowOptions<TFactory> & {
|
|
11
|
+
readonly throwOnError?: TThrow;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* What a binding's `useQuery` returns: the query's controls, the `promise`
|
|
15
|
+
* of the data to show, and that `data` once it's there. Each property is a
|
|
16
|
+
* getter that calls {@link Hooks.track}, so destructuring reads it once.
|
|
17
|
+
*/
|
|
18
|
+
export type Result<TFactory extends QueryFactory, TThrow extends boolean | undefined = undefined> = UseQueryControls<TFactory> & {
|
|
19
|
+
/** The promise's data once it has settled, else `undefined`. */
|
|
20
|
+
readonly data: ReadValue<TFactory, TThrow> | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Settles with the data to show. A new promise per data version, already
|
|
23
|
+
* settled once the cache has data.
|
|
24
|
+
*/
|
|
25
|
+
readonly promise: Promise<ReadValue<TFactory, TThrow>>;
|
|
26
|
+
};
|
|
27
|
+
/** {@link Result} for a handle from `ensure` or `fetch`. */
|
|
28
|
+
export type ResultWithData<TFactory extends QueryFactory, TThrow extends boolean | undefined = undefined> = Result<TFactory, TThrow> & {
|
|
29
|
+
/** The query's data. The handle's `data` until the cache has data again. */
|
|
30
|
+
readonly data: ReadValue<TFactory, TThrow>;
|
|
31
|
+
};
|
|
32
|
+
/** How a framework plugs into a binding. */
|
|
33
|
+
export interface Hooks {
|
|
34
|
+
/**
|
|
35
|
+
* Runs when the result changed: a property something read, or a pending
|
|
36
|
+
* promise something read settling in place. Typically bumps the version.
|
|
37
|
+
*/
|
|
38
|
+
readonly notify: () => void;
|
|
39
|
+
/**
|
|
40
|
+
* Runs on every read of the result, before the read, so the framework
|
|
41
|
+
* re-runs whatever read it after {@link Hooks.notify}. Typically reads a
|
|
42
|
+
* version signal.
|
|
43
|
+
*/
|
|
44
|
+
readonly track: () => void;
|
|
45
|
+
/**
|
|
46
|
+
* Runs cache writes (starting a read can notify observers synchronously)
|
|
47
|
+
* outside the framework's tracking. Default: runs `f` as is.
|
|
48
|
+
*/
|
|
49
|
+
readonly untracked?: <A>(f: () => A) => A;
|
|
50
|
+
}
|
|
51
|
+
/** Where a binding reads its handle and options from. */
|
|
52
|
+
export interface Source<TFactory extends QueryFactory> {
|
|
53
|
+
readonly handle: () => QueryHandle<TFactory>;
|
|
54
|
+
readonly options: () => UseQueryOptions & {
|
|
55
|
+
readonly throwOnError?: boolean;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** One component's read of a query. */
|
|
59
|
+
export interface QueryBinding<TFactory extends QueryFactory = QueryFactory> {
|
|
60
|
+
readonly client: QueryClient<unknown>;
|
|
61
|
+
readonly observer: QueryObserver<TFactory>;
|
|
62
|
+
/** The result, the same object for the binding's life. */
|
|
63
|
+
readonly result: Result<TFactory, boolean>;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The query a handle points at. Calls the factory, so callers keep it for
|
|
67
|
+
* as long as the handle is the same.
|
|
68
|
+
*/
|
|
69
|
+
export declare function queryFor<TFactory extends QueryFactory>(client: QueryClient<unknown>, factory: TFactory, handle: QueryHandle<TFactory>): Query<TFactory>;
|
|
70
|
+
/** The observer's part of a read's options. */
|
|
71
|
+
export declare function observerOptions(options: UseQueryOptions): Options;
|
|
72
|
+
/**
|
|
73
|
+
* Bind `factory` to the handle and options `source` returns. Reads the
|
|
74
|
+
* query and observes it, but doesn't subscribe: call {@link subscribeUnsafe}
|
|
75
|
+
* where the framework mounts, in the browser.
|
|
76
|
+
*/
|
|
77
|
+
export declare function makeUnsafe<TFactory extends QueryFactory>(client: QueryClient<unknown>, factory: TFactory, source: Source<TFactory>, hooks: Hooks): QueryBinding<TFactory>;
|
|
78
|
+
/**
|
|
79
|
+
* Follow the source: switch to a new handle's query, or new options. Reads
|
|
80
|
+
* call it themselves, so markup that reads before the framework's effects
|
|
81
|
+
* run still sees the new query. Bindings also call it from an effect, so a
|
|
82
|
+
* new handle is observed even when nothing reads the result.
|
|
83
|
+
*/
|
|
84
|
+
export declare function syncUnsafe<TFactory extends QueryFactory>(self: QueryBinding<TFactory>): void;
|
|
85
|
+
/**
|
|
86
|
+
* Notify {@link Hooks.notify} on result changes from now on. A server
|
|
87
|
+
* render reads once and never unmounts, so bindings call it in the browser
|
|
88
|
+
* only. Returns the unsubscribe.
|
|
89
|
+
*/
|
|
90
|
+
export declare function subscribeUnsafe<TFactory extends QueryFactory>(self: QueryBinding<TFactory>): () => void;
|
|
91
|
+
//#endregion
|
|
92
|
+
export { binding_d_exports };
|
package/dist/binding.mjs
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { __exportAll } from "./_virtual/_rolldown/runtime.mjs";
|
|
2
|
+
import { hashKey } from "./key.mjs";
|
|
3
|
+
import { readUnsafe } from "./query.mjs";
|
|
4
|
+
import { promiseControls, resultThenable } from "./controls.mjs";
|
|
5
|
+
import { getOptimisticResultUnsafe, getUnsafe, makeUnsafe as makeUnsafe$1, setOptionsUnsafe, subscribeUnsafe as subscribeUnsafe$1, trackProp } from "./observer.mjs";
|
|
6
|
+
import { Result } from "effect";
|
|
7
|
+
//#region src/binding.ts
|
|
8
|
+
/**
|
|
9
|
+
* What a framework's `useQuery` does, without the framework: one observer
|
|
10
|
+
* per component, following the handle and options it reads through
|
|
11
|
+
* getters, and a result of getters over the observer. A binding supplies
|
|
12
|
+
* {@link Hooks}: how a read is tracked, how a change is announced, and how
|
|
13
|
+
* cache writes run outside the framework's tracking. The Vue and Svelte
|
|
14
|
+
* packages are this plus their reactivity and lifecycle.
|
|
15
|
+
*
|
|
16
|
+
* Synchronous throughout, for renders.
|
|
17
|
+
*/
|
|
18
|
+
var binding_exports = /* @__PURE__ */ __exportAll({
|
|
19
|
+
makeUnsafe: () => makeUnsafe,
|
|
20
|
+
observerOptions: () => observerOptions,
|
|
21
|
+
queryFor: () => queryFor,
|
|
22
|
+
subscribeUnsafe: () => subscribeUnsafe,
|
|
23
|
+
syncUnsafe: () => syncUnsafe
|
|
24
|
+
});
|
|
25
|
+
/**
|
|
26
|
+
* The query a handle points at. Calls the factory, so callers keep it for
|
|
27
|
+
* as long as the handle is the same.
|
|
28
|
+
*/
|
|
29
|
+
function queryFor(client, factory, handle) {
|
|
30
|
+
const args = [handle.input];
|
|
31
|
+
return client.getQuery(factory, ...args);
|
|
32
|
+
}
|
|
33
|
+
/** The observer's part of a read's options. */
|
|
34
|
+
function observerOptions(options) {
|
|
35
|
+
return {
|
|
36
|
+
refetchInterval: options.refetchInterval,
|
|
37
|
+
refetchOnReconnect: options.refetchOnReconnect,
|
|
38
|
+
refetchOnWindowFocus: options.refetchOnWindowFocus,
|
|
39
|
+
whilePending: options.whilePending
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function sameObserverOptions(a, b) {
|
|
43
|
+
return a.refetchInterval === b.refetchInterval && a.refetchOnReconnect === b.refetchOnReconnect && a.refetchOnWindowFocus === b.refetchOnWindowFocus && a.whilePending === b.whilePending;
|
|
44
|
+
}
|
|
45
|
+
function stateOf(self) {
|
|
46
|
+
return self;
|
|
47
|
+
}
|
|
48
|
+
const runNow = (f) => f();
|
|
49
|
+
/**
|
|
50
|
+
* Bind `factory` to the handle and options `source` returns. Reads the
|
|
51
|
+
* query and observes it, but doesn't subscribe: call {@link subscribeUnsafe}
|
|
52
|
+
* where the framework mounts, in the browser.
|
|
53
|
+
*/
|
|
54
|
+
function makeUnsafe(client, factory, source, hooks) {
|
|
55
|
+
return (hooks.untracked ?? runNow)(() => {
|
|
56
|
+
const handle = source.handle();
|
|
57
|
+
const query = queryFor(client, factory, handle);
|
|
58
|
+
const options = observerOptions(source.options());
|
|
59
|
+
readUnsafe(query);
|
|
60
|
+
const state = {
|
|
61
|
+
awaited: /* @__PURE__ */ new WeakSet(),
|
|
62
|
+
client,
|
|
63
|
+
factory,
|
|
64
|
+
handle,
|
|
65
|
+
hooks,
|
|
66
|
+
observer: makeUnsafe$1(query, options),
|
|
67
|
+
options,
|
|
68
|
+
query,
|
|
69
|
+
source
|
|
70
|
+
};
|
|
71
|
+
return Object.assign(state, { result: resultOf(state) });
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Follow the source: switch to a new handle's query, or new options. Reads
|
|
76
|
+
* call it themselves, so markup that reads before the framework's effects
|
|
77
|
+
* run still sees the new query. Bindings also call it from an effect, so a
|
|
78
|
+
* new handle is observed even when nothing reads the result.
|
|
79
|
+
*/
|
|
80
|
+
function syncUnsafe(self) {
|
|
81
|
+
sync(stateOf(self));
|
|
82
|
+
}
|
|
83
|
+
function sync(impl) {
|
|
84
|
+
const handle = impl.source.handle();
|
|
85
|
+
const options = observerOptions(impl.source.options());
|
|
86
|
+
const newQuery = handle !== impl.handle && hashKey(handle.key) !== impl.query.hash;
|
|
87
|
+
impl.handle = handle;
|
|
88
|
+
if (!newQuery && sameObserverOptions(options, impl.options)) return;
|
|
89
|
+
(impl.hooks.untracked ?? runNow)(() => {
|
|
90
|
+
if (newQuery) {
|
|
91
|
+
impl.query = queryFor(impl.client, impl.factory, handle);
|
|
92
|
+
readUnsafe(impl.query);
|
|
93
|
+
}
|
|
94
|
+
impl.options = options;
|
|
95
|
+
setOptionsUnsafe(impl.observer, impl.query, options);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Notify {@link Hooks.notify} on result changes from now on. A server
|
|
100
|
+
* render reads once and never unmounts, so bindings call it in the browser
|
|
101
|
+
* only. Returns the unsubscribe.
|
|
102
|
+
*/
|
|
103
|
+
function subscribeUnsafe(self) {
|
|
104
|
+
const impl = stateOf(self);
|
|
105
|
+
return subscribeUnsafe$1(impl.observer, impl.hooks.notify);
|
|
106
|
+
}
|
|
107
|
+
function resultOf(impl) {
|
|
108
|
+
const { hooks, observer } = impl;
|
|
109
|
+
function read(name) {
|
|
110
|
+
sync(impl);
|
|
111
|
+
hooks.track();
|
|
112
|
+
trackProp(observer, name);
|
|
113
|
+
return impl.client.isServer ? getOptimisticResultUnsafe(observer, impl.query, impl.options) : getUnsafe(observer);
|
|
114
|
+
}
|
|
115
|
+
function throwOnError() {
|
|
116
|
+
return impl.source.options().throwOnError ?? impl.client.throwOnError;
|
|
117
|
+
}
|
|
118
|
+
function shown() {
|
|
119
|
+
const promise = read("promise").promise;
|
|
120
|
+
if (promise.status === "pending" && !impl.awaited.has(promise)) {
|
|
121
|
+
impl.awaited.add(promise);
|
|
122
|
+
promise.then(hooks.notify, hooks.notify);
|
|
123
|
+
}
|
|
124
|
+
return throwOnError() ? promise : resultThenable(promise);
|
|
125
|
+
}
|
|
126
|
+
function controls() {
|
|
127
|
+
return promiseControls(getUnsafe(observer));
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
get data() {
|
|
131
|
+
const promise = shown();
|
|
132
|
+
if (promise.status === "fulfilled") return promise.value;
|
|
133
|
+
const handle = impl.handle;
|
|
134
|
+
if (!("data" in handle)) return;
|
|
135
|
+
const data = handle.data;
|
|
136
|
+
return throwOnError() ? data : Result.succeed(data);
|
|
137
|
+
},
|
|
138
|
+
get error() {
|
|
139
|
+
return read("error").error;
|
|
140
|
+
},
|
|
141
|
+
fetchNextPage: () => controls().fetchNextPage(),
|
|
142
|
+
fetchPreviousPage: () => controls().fetchPreviousPage(),
|
|
143
|
+
get hash() {
|
|
144
|
+
return read("hash").hash;
|
|
145
|
+
},
|
|
146
|
+
get hasNextPage() {
|
|
147
|
+
return read("hasNextPage").hasNextPage;
|
|
148
|
+
},
|
|
149
|
+
get hasPreviousPage() {
|
|
150
|
+
return read("hasPreviousPage").hasPreviousPage;
|
|
151
|
+
},
|
|
152
|
+
get input() {
|
|
153
|
+
return read("input").input;
|
|
154
|
+
},
|
|
155
|
+
get isFetching() {
|
|
156
|
+
return read("isFetching").isFetching;
|
|
157
|
+
},
|
|
158
|
+
get isFetchingNextPage() {
|
|
159
|
+
return read("isFetchingNextPage").isFetchingNextPage;
|
|
160
|
+
},
|
|
161
|
+
get isFetchingPreviousPage() {
|
|
162
|
+
return read("isFetchingPreviousPage").isFetchingPreviousPage;
|
|
163
|
+
},
|
|
164
|
+
get isPending() {
|
|
165
|
+
return read("isPending").isPending;
|
|
166
|
+
},
|
|
167
|
+
get isPrevious() {
|
|
168
|
+
return read("isPrevious").isPrevious;
|
|
169
|
+
},
|
|
170
|
+
get isStale() {
|
|
171
|
+
return read("isStale").isStale;
|
|
172
|
+
},
|
|
173
|
+
get key() {
|
|
174
|
+
return read("key").key;
|
|
175
|
+
},
|
|
176
|
+
get promise() {
|
|
177
|
+
return shown();
|
|
178
|
+
},
|
|
179
|
+
refetch: () => controls().refetch(),
|
|
180
|
+
get updatedAt() {
|
|
181
|
+
return read("updatedAt").updatedAt;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
//#endregion
|
|
186
|
+
export { binding_exports, makeUnsafe, observerOptions, queryFor, subscribeUnsafe, syncUnsafe };
|
package/dist/index.d.mts
CHANGED
|
@@ -9,4 +9,5 @@ import { cache_d_exports } from "./cache.mjs";
|
|
|
9
9
|
import { QueryClient, QueryClientConfig, dehydrate, hydrate } from "./client.mjs";
|
|
10
10
|
import { observer_d_exports } from "./observer.mjs";
|
|
11
11
|
import { HasTypedError, PagingControls, PromiseControls, QueryControls, ReadValue, ThrowsOnError, UseQueryControls, UseQueryOptions, UseQueryThrowOptions, promiseControls, resultThenable } from "./controls.mjs";
|
|
12
|
-
|
|
12
|
+
import { binding_d_exports } from "./binding.mjs";
|
|
13
|
+
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, binding_d_exports as QueryBinding, 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
|
@@ -9,4 +9,5 @@ import { cache_exports } from "./cache.mjs";
|
|
|
9
9
|
import { QueryClient, dehydrate, hydrate } from "./client.mjs";
|
|
10
10
|
import { promiseControls, resultThenable } from "./controls.mjs";
|
|
11
11
|
import { observer_exports } from "./observer.mjs";
|
|
12
|
-
|
|
12
|
+
import { binding_exports } from "./binding.mjs";
|
|
13
|
+
export { platform_exports as Platform, query_exports as Query, binding_exports as QueryBinding, 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$1 as 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;
|
|
@@ -96,6 +96,14 @@ export declare function changes<TFactory extends QueryFactory>(self: QueryObserv
|
|
|
96
96
|
* changes.
|
|
97
97
|
*/
|
|
98
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
|
package/dist/observer.mjs
CHANGED
|
@@ -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 };
|