@raf-query/core 0.0.18 → 0.1.0

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.
@@ -1,19 +0,0 @@
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/client.d.mts DELETED
@@ -1,123 +0,0 @@
1
- import { QueryKey } from "./key.mjs";
2
- import { DataOf, EnsuredQueryHandle, InputArgs, InputOf, LoadOptions, QueryBehavior, QueryFactory, QueryHandle } from "./options.mjs";
3
- import { Query, QueryState } from "./query.mjs";
4
- import { Effect, Stream } from "effect";
5
- //#region src/client.d.ts
6
- export interface QueryClientConfig {
7
- /** Defaults for every query; a factory's own options win. */
8
- readonly defaults?: QueryBehavior;
9
- /**
10
- * Whether this client runs on the server. Detected from `window`. Server
11
- * clients don't retry, never garbage-collect, skip stale timers, and reuse
12
- * cached data in `prefetch` / `ensure` / `fetch` instead of revalidating
13
- * it, unless `invalidate` marked it.
14
- */
15
- readonly isServer?: boolean;
16
- /** Called with every failed request, including failed prefetches. */
17
- onError?(error: unknown, query: Query): void;
18
- }
19
- /** Emitted by {@link QueryClient.subscribe} for every cache change. */
20
- export type CacheEvent = {
21
- readonly query: Query;
22
- /** `observers`: a reader subscribed or unsubscribed. */
23
- readonly type: "added" | "observers" | "removed" | "updated";
24
- } | {
25
- /** A request failed (after retries), with this error. */
26
- readonly error: unknown;
27
- readonly query: Query;
28
- readonly type: "failed";
29
- };
30
- /** Matches cache entries by key prefix, or by exact key with `exact`. */
31
- export interface QueryFilter {
32
- readonly exact?: boolean;
33
- readonly queryKey: QueryKey;
34
- }
35
- /** `ensure` / `fetch` arguments: the input, then load options for paged factories. */
36
- export type LoadArgs<TFactory extends QueryFactory> = InputArgs<TFactory> | [input: InputOf<TFactory>, options: LoadOptions<TFactory>];
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
- */
41
- export type Updater<TData> = TData | ((previous: TData | undefined) => TData);
42
- /** Effect-returning twins of the client's async methods. */
43
- export interface QueryClientEffect {
44
- ensure<TFactory extends QueryFactory>(factory: TFactory, ...args: LoadArgs<TFactory>): Effect.Effect<EnsuredQueryHandle<TFactory>, unknown>;
45
- fetch<TFactory extends QueryFactory>(factory: TFactory, ...args: LoadArgs<TFactory>): Effect.Effect<DataOf<TFactory>, unknown>;
46
- invalidate<TFactory extends QueryFactory>(factory: TFactory, ...args: InputArgs<TFactory>): Effect.Effect<void>;
47
- invalidate(filter: QueryFilter): Effect.Effect<void>;
48
- /**
49
- * The entry's current state, then every change. Counts as a reader while
50
- * the stream runs, so invalidation and focus refetch it.
51
- */
52
- watch<TFactory extends QueryFactory>(factory: TFactory, ...args: InputArgs<TFactory>): Stream.Stream<QueryState<DataOf<TFactory>>>;
53
- }
54
- /**
55
- * The query cache. Create one per request on the server and one per browser
56
- * tab.
57
- */
58
- export declare class QueryClient {
59
- #private;
60
- /** Effect-returning versions of `ensure`, `fetch`, `invalidate`, plus `watch`. */
61
- readonly effect: QueryClientEffect;
62
- readonly isServer: boolean;
63
- constructor(config?: QueryClientConfig);
64
- /**
65
- * The cache entry for a factory call, created if missing. Doesn't fetch.
66
- * For bindings; apps use the verbs.
67
- */
68
- getQuery<TFactory extends QueryFactory>(factory: TFactory, ...args: InputArgs<TFactory>): Query<DataOf<TFactory>, TFactory>;
69
- /**
70
- * Start a query without waiting and return its handle. Fresh data is
71
- * reused; stale data refetches in the background (in the browser only, see
72
- * {@link QueryClientConfig.isServer}). Errors surface where the data is
73
- * read, never here.
74
- */
75
- prefetch<TFactory extends QueryFactory>(factory: TFactory, ...args: InputArgs<TFactory>): QueryHandle<TFactory>;
76
- /**
77
- * Start a query and wait for it on a cache miss. Rejects if that request
78
- * fails. Stale cached data resolves immediately and revalidates in the
79
- * browser. The handle is marked `ensured`, so `useQuery` returns its data
80
- * directly.
81
- */
82
- ensure<TFactory extends QueryFactory>(factory: TFactory, ...args: LoadArgs<TFactory>): Promise<EnsuredQueryHandle<TFactory>>;
83
- /** Like `ensure`, but resolves to the data. */
84
- fetch<TFactory extends QueryFactory>(factory: TFactory, ...args: LoadArgs<TFactory>): Promise<DataOf<TFactory>>;
85
- /**
86
- * Mark queries stale and refetch the ones being read. Resolves once those
87
- * refetches settle, so an action stays pending until fresh data is on
88
- * screen.
89
- */
90
- invalidate<TFactory extends QueryFactory>(factory: TFactory, ...args: InputArgs<TFactory>): Promise<void>;
91
- invalidate(filter: QueryFilter): Promise<void>;
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<TFactory extends QueryFactory>(factory: TFactory, ...args: InputArgs<TFactory>): Promise<void>;
97
- reset(filter: QueryFilter): Promise<void>;
98
- /**
99
- * Remove queries from the cache and abort their requests. Readers keep the
100
- * removed entry until they render again.
101
- */
102
- remove<TFactory extends QueryFactory>(factory: TFactory, ...args: InputArgs<TFactory>): void;
103
- remove(filter: QueryFilter): void;
104
- /** Write data for a factory call. Readers re-render without suspending. */
105
- setData<TFactory extends QueryFactory>(factory: TFactory, input: InputOf<TFactory>, updater: Updater<DataOf<TFactory>>): void;
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
- */
110
- getData<TFactory extends QueryFactory>(factory: TFactory, ...args: InputArgs<TFactory>): DataOf<TFactory> | undefined;
111
- /** Every entry, for devtools and integrations. */
112
- getAll(): ReadonlyArray<Query>;
113
- /** Listen to entries being added, updated, and removed. */
114
- subscribe(listener: (event: CacheEvent) => void): () => void;
115
- /** Drop every entry and abort in-flight requests. */
116
- clear(): void;
117
- /**
118
- * Start refetching read, stale queries on window focus and reconnect.
119
- * Returns the matching unmount. Providers call this; mounts are counted.
120
- */
121
- mount(): () => void;
122
- }
123
- //#endregion
package/dist/client.mjs DELETED
@@ -1,266 +0,0 @@
1
- import { unsafeCast } from "./cast.mjs";
2
- import { hashKey, matchesKey } from "./key.mjs";
3
- import { optionsFor } from "./options.mjs";
4
- import { QueryEntry } from "./query.mjs";
5
- import { Effect, Predicate, Queue, Stream } from "effect";
6
- //#region src/client.ts
7
- const caches = /* @__PURE__ */ new WeakMap();
8
- function cacheOf(client) {
9
- const cache = caches.get(client);
10
- if (!cache) throw new Error("Not a QueryClient");
11
- return cache;
12
- }
13
- const noop = () => {};
14
- const identity = (value) => value;
15
- function isUpdateFunction(updater) {
16
- return Predicate.isFunction(updater);
17
- }
18
- function typed(entry) {
19
- return unsafeCast(entry);
20
- }
21
- function handleOf(entry) {
22
- return entry.handle;
23
- }
24
- function ensuredHandleOf(entry) {
25
- return {
26
- ...handleOf(entry),
27
- ensured: true
28
- };
29
- }
30
- /**
31
- * The query cache. Create one per request on the server and one per browser
32
- * tab.
33
- */
34
- var QueryClient = class {
35
- /** Effect-returning versions of `ensure`, `fetch`, `invalidate`, plus `watch`. */
36
- effect;
37
- isServer;
38
- #cache = /* @__PURE__ */ new Map();
39
- #host;
40
- #listeners = /* @__PURE__ */ new Set();
41
- #mounts = 0;
42
- #unmountWindow = noop;
43
- constructor(config = {}) {
44
- this.isServer = config.isServer ?? !("window" in globalThis);
45
- const emit = (event) => {
46
- for (const listener of this.#listeners) listener(event);
47
- };
48
- this.#host = {
49
- changed: (query) => emit({
50
- query,
51
- type: "updated"
52
- }),
53
- defaults: config.defaults ?? {},
54
- isServer: this.isServer,
55
- observersChanged: (query) => emit({
56
- query,
57
- type: "observers"
58
- }),
59
- remove: (query) => {
60
- if (this.#cache.get(query.hash) === query) {
61
- this.#cache.delete(query.hash);
62
- query.destroy();
63
- emit({
64
- query,
65
- type: "removed"
66
- });
67
- }
68
- },
69
- reportError: (error, query) => {
70
- config.onError?.(error, query);
71
- emit({
72
- error,
73
- query,
74
- type: "failed"
75
- });
76
- }
77
- };
78
- caches.set(this, {
79
- all: () => [...this.#cache.values()],
80
- entryFor: (key) => this.#entryFor(key, hashKey(key))
81
- });
82
- this.effect = this.#effectApi();
83
- }
84
- /**
85
- * The cache entry for a factory call, created if missing. Doesn't fetch.
86
- * For bindings; apps use the verbs.
87
- */
88
- getQuery(factory, ...args) {
89
- return typed(this.#build(factory, args[0]));
90
- }
91
- /**
92
- * Start a query without waiting and return its handle. Fresh data is
93
- * reused; stale data refetches in the background (in the browser only, see
94
- * {@link QueryClientConfig.isServer}). Errors surface where the data is
95
- * read, never here.
96
- */
97
- prefetch(factory, ...args) {
98
- const entry = this.#build(factory, args[0]);
99
- if (entry.getState().hasData) this.#revalidate(entry);
100
- else entry.fetch().catch(noop);
101
- return handleOf(entry);
102
- }
103
- /**
104
- * Start a query and wait for it on a cache miss. Rejects if that request
105
- * fails. Stale cached data resolves immediately and revalidates in the
106
- * browser. The handle is marked `ensured`, so `useQuery` returns its data
107
- * directly.
108
- */
109
- async ensure(factory, ...args) {
110
- const entry = this.#build(factory, args[0]);
111
- await this.#load(entry, args[1]?.pages);
112
- return ensuredHandleOf(entry);
113
- }
114
- /** Like `ensure`, but resolves to the data. */
115
- async fetch(factory, ...args) {
116
- const entry = this.#build(factory, args[0]);
117
- await this.#load(entry, args[1]?.pages);
118
- const state = entry.getState();
119
- if (!state.hasData) throw new Error(`Query ${entry.hash} has no data`);
120
- return unsafeCast(state.data);
121
- }
122
- async invalidate(target, input) {
123
- await Promise.all(this.#match(target, input).map((entry) => entry.invalidate()));
124
- }
125
- async reset(target, input) {
126
- await Promise.all(this.#match(target, input).map((entry) => entry.reset()));
127
- }
128
- remove(target, input) {
129
- for (const entry of this.#match(target, input)) this.#host.remove(entry);
130
- }
131
- /** Write data for a factory call. Readers re-render without suspending. */
132
- setData(factory, input, updater) {
133
- const entry = this.#build(factory, input);
134
- const next = isUpdateFunction(updater) ? updater(typed(entry).getState().data) : updater;
135
- entry.setData(next);
136
- }
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
- */
141
- getData(factory, ...args) {
142
- const options = optionsFor(factory, args[0]);
143
- const entry = this.#cache.get(hashKey(options.queryKey));
144
- return entry ? typed(entry).getState().data : void 0;
145
- }
146
- /** Every entry, for devtools and integrations. */
147
- getAll() {
148
- return [...this.#cache.values()];
149
- }
150
- /** Listen to entries being added, updated, and removed. */
151
- subscribe(listener) {
152
- this.#listeners.add(listener);
153
- return () => {
154
- this.#listeners.delete(listener);
155
- };
156
- }
157
- /** Drop every entry and abort in-flight requests. */
158
- clear() {
159
- const entries = [...this.#cache.values()];
160
- this.#cache.clear();
161
- for (const entry of entries) {
162
- entry.destroy();
163
- for (const listener of this.#listeners) listener({
164
- query: entry,
165
- type: "removed"
166
- });
167
- }
168
- }
169
- /**
170
- * Start refetching read, stale queries on window focus and reconnect.
171
- * Returns the matching unmount. Providers call this; mounts are counted.
172
- */
173
- mount() {
174
- this.#mounts++;
175
- if (this.#mounts === 1 && !this.isServer) this.#unmountWindow = this.#listenToWindow();
176
- let mounted = true;
177
- return () => {
178
- if (!mounted) return;
179
- mounted = false;
180
- this.#mounts--;
181
- if (this.#mounts === 0) {
182
- this.#unmountWindow();
183
- this.#unmountWindow = noop;
184
- }
185
- };
186
- }
187
- #entryFor(key, hash) {
188
- const existing = this.#cache.get(hash);
189
- if (existing) return existing;
190
- const entry = new QueryEntry(this.#host, key, hash);
191
- this.#cache.set(hash, entry);
192
- for (const listener of this.#listeners) listener({
193
- query: entry,
194
- type: "added"
195
- });
196
- return entry;
197
- }
198
- #build(factory, input) {
199
- const options = optionsFor(factory, input);
200
- const entry = this.#entryFor(options.queryKey, hashKey(options.queryKey));
201
- entry.setOptions(options, input);
202
- return entry;
203
- }
204
- #load(entry, pages) {
205
- const state = entry.getState();
206
- if (!state.hasData) return entry.fetch({ pages });
207
- this.#revalidate(entry);
208
- return Promise.resolve(state.data);
209
- }
210
- /**
211
- * Background refetch of cached data a verb reuses. A server client renders
212
- * once, so a refetch there could only duplicate a request whose result is
213
- * never shown; it refetches only what `invalidate` marked.
214
- */
215
- #revalidate(entry) {
216
- const state = entry.getState();
217
- const wanted = this.isServer ? state.isInvalidated : entry.isStaleNow();
218
- if (!state.isFetching && wanted) entry.fetch().catch(noop);
219
- }
220
- #match(target, input) {
221
- if (Predicate.isFunction(target)) {
222
- const entry = this.#cache.get(hashKey(optionsFor(target, input).queryKey));
223
- return entry ? [entry] : [];
224
- }
225
- const hash = hashKey(target.queryKey);
226
- return [...this.#cache.values()].filter((entry) => target.exact ? entry.hash === hash : matchesKey(entry.key, target.queryKey));
227
- }
228
- #listenToWindow() {
229
- const each = (event) => {
230
- for (const entry of this.#cache.values()) if (entry.isObserved) entry.onWindowEvent(event);
231
- };
232
- const onVisibilityChange = () => {
233
- if (document.visibilityState === "visible") each("focus");
234
- };
235
- const onOnline = () => each("reconnect");
236
- window.addEventListener("visibilitychange", onVisibilityChange);
237
- window.addEventListener("online", onOnline);
238
- return () => {
239
- window.removeEventListener("visibilitychange", onVisibilityChange);
240
- window.removeEventListener("online", onOnline);
241
- };
242
- }
243
- #effectApi() {
244
- const invalidate = (target, input) => Effect.promise(() => Promise.all(this.#match(target, input).map((entry) => entry.invalidate()))).pipe(Effect.asVoid);
245
- return {
246
- ensure: (factory, ...args) => Effect.tryPromise({
247
- catch: identity,
248
- try: () => this.ensure(factory, ...args)
249
- }),
250
- fetch: (factory, ...args) => Effect.tryPromise({
251
- catch: identity,
252
- try: () => this.fetch(factory, ...args)
253
- }),
254
- invalidate,
255
- watch: (factory, ...args) => Stream.callback((queue) => {
256
- const query = this.getQuery(factory, ...args);
257
- return Effect.acquireRelease(Effect.sync(() => {
258
- Queue.offerUnsafe(queue, query.getState());
259
- return query.subscribe(() => Queue.offerUnsafe(queue, query.getState()));
260
- }), (unsubscribe) => Effect.sync(unsubscribe));
261
- })
262
- };
263
- }
264
- };
265
- //#endregion
266
- export { QueryClient, cacheOf };
package/dist/online.mjs DELETED
@@ -1,16 +0,0 @@
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 };