@raf-query/core 0.11.1 → 0.13.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.
Files changed (73) hide show
  1. package/CHANGELOG.md +39 -1
  2. package/README.md +28 -18
  3. package/dist/client.d.mts +163 -119
  4. package/dist/client.mjs +90 -162
  5. package/dist/entry.d.mts +73 -147
  6. package/dist/entry.mjs +314 -471
  7. package/dist/features/focus-refetch/focus-refetch.d.mts +17 -0
  8. package/dist/features/focus-refetch/focus-refetch.mjs +20 -0
  9. package/dist/features/polling/polling.d.mts +16 -0
  10. package/dist/features/polling/polling.mjs +39 -0
  11. package/dist/features/reconnect-refetch/reconnect-refetch.d.mts +17 -0
  12. package/dist/features/reconnect-refetch/reconnect-refetch.mjs +16 -0
  13. package/dist/features/shared.mjs +13 -0
  14. package/dist/guards.mjs +23 -0
  15. package/dist/hydration/hydration.d.mts +108 -0
  16. package/dist/hydration/hydration.mjs +142 -0
  17. package/dist/index.d.mts +19 -13
  18. package/dist/index.mjs +16 -13
  19. package/dist/infinite/infinite.d.mts +83 -0
  20. package/dist/infinite/infinite.mjs +120 -0
  21. package/dist/infinite/paging.d.mts +71 -0
  22. package/dist/infinite/paging.mjs +62 -0
  23. package/dist/internal.d.mts +13 -0
  24. package/dist/internal.mjs +7 -0
  25. package/dist/keys/keys.d.mts +54 -0
  26. package/dist/keys/keys.mjs +53 -0
  27. package/dist/keys/targets.d.mts +62 -0
  28. package/dist/keys/targets.mjs +85 -0
  29. package/dist/links/retry/retry.d.mts +33 -0
  30. package/dist/links/retry/retry.mjs +53 -0
  31. package/dist/links/timeout/timeout.d.mts +17 -0
  32. package/dist/links/timeout/timeout.mjs +35 -0
  33. package/dist/load.d.mts +149 -0
  34. package/dist/load.mjs +179 -0
  35. package/dist/options.d.mts +161 -332
  36. package/dist/options.mjs +28 -40
  37. package/dist/presets.d.mts +35 -0
  38. package/dist/presets.mjs +27 -0
  39. package/dist/root.d.mts +86 -0
  40. package/dist/structural-sharing/structural-sharing.mjs +32 -0
  41. package/dist/subscribe.d.mts +15 -0
  42. package/dist/subscribe.mjs +29 -0
  43. package/dist/thenable.d.mts +15 -17
  44. package/dist/thenable.mjs +25 -6
  45. package/dist/verbs.d.mts +150 -0
  46. package/dist/verbs.mjs +115 -0
  47. package/package.json +13 -9
  48. package/dist/_virtual/_rolldown/runtime.mjs +0 -13
  49. package/dist/all-keyed.d.mts +0 -18
  50. package/dist/all-keyed.mjs +0 -19
  51. package/dist/binding.d.mts +0 -92
  52. package/dist/binding.mjs +0 -186
  53. package/dist/cache.d.mts +0 -164
  54. package/dist/cache.mjs +0 -184
  55. package/dist/controls.d.mts +0 -95
  56. package/dist/controls.mjs +0 -70
  57. package/dist/hydration.d.mts +0 -84
  58. package/dist/hydration.mjs +0 -148
  59. package/dist/infinite.d.mts +0 -5
  60. package/dist/infinite.mjs +0 -103
  61. package/dist/key.d.mts +0 -18
  62. package/dist/key.mjs +0 -36
  63. package/dist/listen.mjs +0 -19
  64. package/dist/notify.mjs +0 -21
  65. package/dist/observer.d.mts +0 -110
  66. package/dist/observer.mjs +0 -300
  67. package/dist/platform.d.mts +0 -27
  68. package/dist/platform.mjs +0 -44
  69. package/dist/query.d.mts +0 -137
  70. package/dist/query.mjs +0 -147
  71. package/dist/request.mjs +0 -47
  72. package/dist/retry.mjs +0 -25
  73. package/dist/share.mjs +0 -40
@@ -0,0 +1,149 @@
1
+ import { DataOf, InputArgs, InputOf, KeyOf, QueryFactory } from "./options.mjs";
2
+ import { Entry } from "./entry.mjs";
3
+ import { QueryClient } from "./client.mjs";
4
+ //#region src/load.d.ts
5
+ /**
6
+ * A small serializable reference to a query: its key and its factory's
7
+ * input. It carries no data; readers get that from the cache. Handles of
8
+ * different factories have different types.
9
+ *
10
+ * @see https://raf-query.katt.dev/docs/reading-queries#handles
11
+ */
12
+ export interface QueryHandle<TFactory extends QueryFactory = QueryFactory> {
13
+ readonly input: InputOf<TFactory>;
14
+ readonly queryKey: KeyOf<TFactory>;
15
+ }
16
+ type LoadKind = "prefetch" | "revalidate" | "swr";
17
+ /**
18
+ * A query for `load` to start. Building one starts nothing.
19
+ *
20
+ * @see https://raf-query.katt.dev/docs/starting-queries
21
+ */
22
+ export interface LoadEntry<TFactory extends QueryFactory = QueryFactory, TKind extends LoadKind = LoadKind> {
23
+ readonly factory: TFactory;
24
+ readonly input: InputOf<TFactory>;
25
+ readonly kind: TKind;
26
+ /** For paged factories: how many pages a first load fetches. */
27
+ readonly pages?: number;
28
+ }
29
+ /**
30
+ * Options for a `load` entry.
31
+ *
32
+ * @see https://raf-query.katt.dev/docs/infinite-queries#loading-several-pages-up-front
33
+ */
34
+ export interface LoadEntryOptions {
35
+ /** For paged factories: how many pages a first load fetches. Default 1. */
36
+ readonly pages?: number;
37
+ }
38
+ type LoadEntryArgs<TFactory extends QueryFactory> = [input: InputOf<TFactory>, options?: LoadEntryOptions] | InputArgs<TFactory>;
39
+ /**
40
+ * Start the query if the cache has no fresh data, and don't wait for it.
41
+ * It never makes the load reject: errors show up where a component reads
42
+ * the handle.
43
+ *
44
+ * @see https://raf-query.katt.dev/docs/starting-queries#prefetch-or-swr
45
+ */
46
+ export declare function prefetch<TFactory extends QueryFactory>(factory: TFactory, ...args: LoadEntryArgs<TFactory>): LoadEntry<TFactory, "prefetch">;
47
+ /**
48
+ * Use cached data even when it's stale, and wait only when the cache has
49
+ * none. Stale data resolves at once and refetches in the background, as
50
+ * HTTP's
51
+ * [`stale-while-revalidate`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#stale-while-revalidate)
52
+ * does. The load rejects if the fetch it waits for fails.
53
+ *
54
+ * @see https://raf-query.katt.dev/docs/starting-queries#prefetch-or-swr
55
+ */
56
+ export declare function swr<TFactory extends QueryFactory>(factory: TFactory, ...args: LoadEntryArgs<TFactory>): LoadEntry<TFactory, "swr">;
57
+ /**
58
+ * Wait for fresh data, as HTTP's
59
+ * [`must-revalidate`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#must-revalidate)
60
+ * does. Fresh cached data resolves at once without a refetch (`staleTime`
61
+ * is the max-age); with stale data or none it waits for the fetch. On a
62
+ * server client, data counts as fresh until a write invalidates it. The
63
+ * load rejects if the fetch fails.
64
+ *
65
+ * @see https://raf-query.katt.dev/docs/starting-queries#the-three-entries
66
+ */
67
+ export declare function revalidate<TFactory extends QueryFactory>(factory: TFactory, ...args: LoadEntryArgs<TFactory>): LoadEntry<TFactory, "revalidate">;
68
+ /**
69
+ * In place of a `load` entry: skip the query. Its handle is `undefined` and
70
+ * `data` has no key for it, so a component renders its reader only when
71
+ * there's a handle.
72
+ *
73
+ * ```ts
74
+ * const [handles] = await load(queryClient, {
75
+ * drafts: user.isAdmin ? prefetch(draftsQuery, { slug }) : skipToken,
76
+ * });
77
+ * ```
78
+ *
79
+ * @see https://raf-query.katt.dev/docs/starting-queries#conditional-queries
80
+ */
81
+ export declare const skipToken: unique symbol;
82
+ /** What `load` takes per name: an entry, or `skipToken`. */
83
+ type LoadEntries = Record<string, LoadEntry | typeof skipToken>;
84
+ type HandleOf<TEntry> = TEntry extends LoadEntry ? QueryHandle<TEntry["factory"]> : undefined;
85
+ type DataOfEntry<TEntry> = TEntry extends LoadEntry ? DataOf<TEntry["factory"]> : never;
86
+ type LoadedData<TEntries extends LoadEntries> = { [TName in keyof TEntries as TEntries[TName] extends LoadEntry<QueryFactory, "revalidate" | "swr"> ? TName : never]: DataOfEntry<TEntries[TName]>; };
87
+ /**
88
+ * What `load` resolves to: a handle per entry (`undefined` for `skipToken`),
89
+ * and the data of `swr` and `revalidate` entries.
90
+ *
91
+ * @see https://raf-query.katt.dev/docs/starting-queries
92
+ */
93
+ export type Loaded<TEntries extends LoadEntries> = readonly [handles: { [TName in keyof TEntries]: HandleOf<TEntries[TName]>; }, data: LoadedData<TEntries>];
94
+ /** A `load` call as devtools show it. */
95
+ export interface LoadRecord {
96
+ readonly entries: ReadonlyArray<{
97
+ readonly hash: string;
98
+ readonly kind: LoadKind;
99
+ readonly name: string;
100
+ }>;
101
+ readonly startedAt: number;
102
+ }
103
+ /** The latest `load` calls on a client, newest last. Recorded in development only. */
104
+ export declare function loadsOf(client: QueryClient): ReadonlyArray<LoadRecord>;
105
+ /** Read an entry's data the way its readers see it: through `select`. */
106
+ export declare function readData(entry: Entry): unknown;
107
+ /** Start the entry's request if its data isn't fresh. Never waits. */
108
+ export declare function startIfStale(entry: Entry): void;
109
+ /**
110
+ * Start a loader's queries and return a serializable handle for each. It
111
+ * starts every entry before it waits for any, so the order of the entries
112
+ * can't create a waterfall. Resolves with `[handles, data]` once every
113
+ * `swr` and `revalidate` entry has data, and rejects with the error as soon
114
+ * as one fails.
115
+ * `prefetch` entries never make it reject. A `skipToken` in place of an
116
+ * entry starts nothing and gets no handle.
117
+ *
118
+ * ```ts
119
+ * const [handles, data] = await load(queryClient, {
120
+ * comments: prefetch(commentsQuery, { slug }),
121
+ * post: swr(postQuery, { slug }),
122
+ * });
123
+ * ```
124
+ *
125
+ * @see https://raf-query.katt.dev/docs/starting-queries
126
+ */
127
+ export declare function load<const TEntries extends LoadEntries>(client: QueryClient, entries: TEntries): Promise<Loaded<TEntries>>;
128
+ /** Whether a handle came from `waterfallHandle()`: reading it is expected to fetch. */
129
+ export declare function isWaterfallHandle(handle: Pick<QueryHandle, "queryKey">): boolean;
130
+ /**
131
+ * The handle of a query whose input is only known while rendering, like an
132
+ * id in another query's data. It starts nothing: the component that reads
133
+ * it fetches when it renders, which makes it a request waterfall, and the
134
+ * name keeps that visible. Prefer starting queries in a loader with `load`.
135
+ *
136
+ * With a handle and an updater, it derives a handle from that one: the
137
+ * updater gets the handle's input and returns the new one (say, with a
138
+ * search term being typed). While the new input maps to the same query, it
139
+ * returns the handle as is, so readers keep it.
140
+ *
141
+ * ```ts
142
+ * const searchHandle = waterfallHandle(searchQuery, props.search, (input) => ({ ...input, q }));
143
+ * ```
144
+ *
145
+ * @see https://raf-query.katt.dev/docs/request-waterfalls#render-time-inputs-waterfallhandle
146
+ */
147
+ export declare function waterfallHandle<TFactory extends QueryFactory>(factory: TFactory, ...input: InputArgs<TFactory>): QueryHandle<TFactory>;
148
+ export declare function waterfallHandle<TFactory extends QueryFactory>(factory: TFactory, handle: QueryHandle<TFactory>, update: (input: InputOf<TFactory>) => InputOf<TFactory>): QueryHandle<TFactory>;
149
+ //#endregion
package/dist/load.mjs ADDED
@@ -0,0 +1,179 @@
1
+ import { hashKey } from "./keys/keys.mjs";
2
+ import { optionsOf } from "./options.mjs";
3
+ import { entryOf, fetchEntry, isBrowserOnly, isInvalidated, isStale } from "./entry.mjs";
4
+ //#region src/load.ts
5
+ function loadEntry(kind, factory, args) {
6
+ return {
7
+ factory,
8
+ input: args[0],
9
+ kind,
10
+ pages: args[1]?.pages
11
+ };
12
+ }
13
+ /**
14
+ * Start the query if the cache has no fresh data, and don't wait for it.
15
+ * It never makes the load reject: errors show up where a component reads
16
+ * the handle.
17
+ *
18
+ * @see https://raf-query.katt.dev/docs/starting-queries#prefetch-or-swr
19
+ */
20
+ function prefetch(factory, ...args) {
21
+ return loadEntry("prefetch", factory, args);
22
+ }
23
+ /**
24
+ * Use cached data even when it's stale, and wait only when the cache has
25
+ * none. Stale data resolves at once and refetches in the background, as
26
+ * HTTP's
27
+ * [`stale-while-revalidate`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#stale-while-revalidate)
28
+ * does. The load rejects if the fetch it waits for fails.
29
+ *
30
+ * @see https://raf-query.katt.dev/docs/starting-queries#prefetch-or-swr
31
+ */
32
+ function swr(factory, ...args) {
33
+ return loadEntry("swr", factory, args);
34
+ }
35
+ /**
36
+ * Wait for fresh data, as HTTP's
37
+ * [`must-revalidate`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#must-revalidate)
38
+ * does. Fresh cached data resolves at once without a refetch (`staleTime`
39
+ * is the max-age); with stale data or none it waits for the fetch. On a
40
+ * server client, data counts as fresh until a write invalidates it. The
41
+ * load rejects if the fetch fails.
42
+ *
43
+ * @see https://raf-query.katt.dev/docs/starting-queries#the-three-entries
44
+ */
45
+ function revalidate(factory, ...args) {
46
+ return loadEntry("revalidate", factory, args);
47
+ }
48
+ /**
49
+ * In place of a `load` entry: skip the query. Its handle is `undefined` and
50
+ * `data` has no key for it, so a component renders its reader only when
51
+ * there's a handle.
52
+ *
53
+ * ```ts
54
+ * const [handles] = await load(queryClient, {
55
+ * drafts: user.isAdmin ? prefetch(draftsQuery, { slug }) : skipToken,
56
+ * });
57
+ * ```
58
+ *
59
+ * @see https://raf-query.katt.dev/docs/starting-queries#conditional-queries
60
+ */
61
+ const skipToken = Symbol("skipToken");
62
+ const loadRecords = /* @__PURE__ */ new WeakMap();
63
+ /** The latest `load` calls on a client, newest last. Recorded in development only. */
64
+ function loadsOf(client) {
65
+ return loadRecords.get(client) ?? [];
66
+ }
67
+ /** Read an entry's data the way its readers see it: through `select`. */
68
+ function readData(entry) {
69
+ const select = entry.options?.select;
70
+ return select ? select(entry.state.data) : entry.state.data;
71
+ }
72
+ /** Start the entry's request if its data isn't fresh. Never waits. */
73
+ function startIfStale(entry) {
74
+ if (isStale(entry) && !(entry.client.isServer && entry.state.hasData)) fetchEntry(entry);
75
+ }
76
+ /**
77
+ * Whether `revalidate` can use the entry's data. A server client belongs to
78
+ * one request, so what it fetched is fresh for that request until a write
79
+ * invalidates it.
80
+ */
81
+ function isFresh(entry) {
82
+ return entry.client.isServer ? entry.state.hasData && !isInvalidated(entry) : !isStale(entry);
83
+ }
84
+ function waitFresh(entry, pages) {
85
+ if (isFresh(entry)) return Promise.resolve({ ok: true });
86
+ return fetchEntry(entry, { pages });
87
+ }
88
+ function waitAny(entry, pages) {
89
+ if (!entry.state.hasData) return waitFresh(entry, pages);
90
+ startIfStale(entry);
91
+ return Promise.resolve({ ok: true });
92
+ }
93
+ /**
94
+ * Start a loader's queries and return a serializable handle for each. It
95
+ * starts every entry before it waits for any, so the order of the entries
96
+ * can't create a waterfall. Resolves with `[handles, data]` once every
97
+ * `swr` and `revalidate` entry has data, and rejects with the error as soon
98
+ * as one fails.
99
+ * `prefetch` entries never make it reject. A `skipToken` in place of an
100
+ * entry starts nothing and gets no handle.
101
+ *
102
+ * ```ts
103
+ * const [handles, data] = await load(queryClient, {
104
+ * comments: prefetch(commentsQuery, { slug }),
105
+ * post: swr(postQuery, { slug }),
106
+ * });
107
+ * ```
108
+ *
109
+ * @see https://raf-query.katt.dev/docs/starting-queries
110
+ */
111
+ async function load(client, entries) {
112
+ const started = Object.entries(entries).flatMap(([name, item]) => item === skipToken ? [] : [{
113
+ item,
114
+ name
115
+ }]).map((loaded) => {
116
+ const item = loaded.item;
117
+ const cached = entryOf(client, item.factory, item.input);
118
+ let wait;
119
+ if (item.kind === "prefetch") startIfStale(cached);
120
+ else if (isBrowserOnly(cached) && !cached.state.hasData) wait = Promise.resolve({
121
+ error: /* @__PURE__ */ new Error(`raf-query: ${cached.hash} has \`ssr: false\`, so the server can't wait for it. Load it with prefetch() instead of ${item.kind}().`),
122
+ ok: false
123
+ });
124
+ else wait = (item.kind === "revalidate" ? waitFresh : waitAny)(cached, item.pages);
125
+ return {
126
+ entry: cached,
127
+ item,
128
+ name: loaded.name,
129
+ wait
130
+ };
131
+ });
132
+ if (process.env.NODE_ENV !== "production") {
133
+ const records = loadRecords.get(client) ?? [];
134
+ records.push({
135
+ entries: started.map((one) => ({
136
+ hash: one.entry.hash,
137
+ kind: one.item.kind,
138
+ name: one.name
139
+ })),
140
+ startedAt: client.clock.now()
141
+ });
142
+ loadRecords.set(client, records.slice(-20));
143
+ }
144
+ const waits = started.map((one) => one.wait?.then((outcome) => {
145
+ if (!outcome.ok) throw outcome.error;
146
+ }));
147
+ await Promise.all(waits);
148
+ const handles = {};
149
+ const data = {};
150
+ for (const one of started) {
151
+ handles[one.name] = {
152
+ input: one.item.input,
153
+ queryKey: one.entry.key
154
+ };
155
+ if (one.wait) data[one.name] = readData(one.entry);
156
+ }
157
+ return [handles, data];
158
+ }
159
+ const waterfallHandles = /* @__PURE__ */ new WeakSet();
160
+ /** Whether a handle came from `waterfallHandle()`: reading it is expected to fetch. */
161
+ function isWaterfallHandle(handle) {
162
+ return waterfallHandles.has(handle);
163
+ }
164
+ function toHandle(factory, input) {
165
+ const handle = {
166
+ input,
167
+ queryKey: optionsOf(factory, input).queryKey
168
+ };
169
+ waterfallHandles.add(handle);
170
+ return handle;
171
+ }
172
+ function waterfallHandle(factory, ...args) {
173
+ if (args.length < 2) return toHandle(factory, args[0]);
174
+ const [handle, update] = args;
175
+ const next = toHandle(factory, update(handle.input));
176
+ return hashKey(next.queryKey) === hashKey(handle.queryKey) ? handle : next;
177
+ }
178
+ //#endregion
179
+ export { isWaterfallHandle, load, loadsOf, prefetch, readData, revalidate, skipToken, startIfStale, swr, waterfallHandle };