@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
@@ -1,95 +0,0 @@
1
- import { QueryKey } from "./key.mjs";
2
- import { DataOf, ErrorOf, InputOf, IsPaged, QueryFactory, ThrowOnErrorOf } from "./options.mjs";
3
- import { Thenable } from "./thenable.mjs";
4
- import { Query, query_d_exports } from "./query.mjs";
5
- import { Options, Result as Result$1, observer_d_exports } from "./observer.mjs";
6
- import { Result } from "effect";
7
- //#region src/controls.d.ts
8
- /** `refetchInterval`, `refetchOnWindowFocus`, `refetchOnReconnect`, `whilePending`. */
9
- export type UseQueryOptions = Options;
10
- /**
11
- * `throwOnError` only when {@link ErrorOf} is typed (Effect `queryFn`).
12
- * Promise factories always throw; the option is not part of their options.
13
- */
14
- export type UseQueryThrowOptions<TFactory extends QueryFactory> = HasTypedError<TFactory> extends true ? {
15
- /**
16
- * When `false`, the data is a `Result` instead of a rejecting
17
- * promise. Omit to use the query's `throwOnError`, else the
18
- * client's default (`false` with a `layer`, `true` without).
19
- */
20
- readonly throwOnError?: boolean;
21
- } : {
22
- readonly throwOnError?: never;
23
- };
24
- /** What `useQuery` returns next to the data. */
25
- export interface QueryControls<TFactory extends QueryFactory> {
26
- /** Error of the last failed request. Existing data stays readable. */
27
- readonly error: unknown;
28
- /** Stable string form of the handle's key, for `key` props. */
29
- readonly hash: string;
30
- /** Input of the data in the promise; differs from the handle's while `isPrevious`. */
31
- readonly input: InputOf<TFactory>;
32
- /** A request is in flight: first load, new input, or background refetch. */
33
- readonly isFetching: boolean;
34
- /** No data yet for the handle's input. */
35
- readonly isPending: boolean;
36
- /** The promise holds a previous input's data (`whilePending: "previous"`). */
37
- readonly isPrevious: boolean;
38
- /** The data is older than `staleTime`, or was invalidated. */
39
- readonly isStale: boolean;
40
- /** Key of the data in the promise; differs from the handle's while `isPrevious`. */
41
- readonly key: QueryKey;
42
- /** Refetch now. Resolves when the request settles; never rejects. */
43
- refetch(): Promise<void>;
44
- /** When the data in the promise was fetched (ms since epoch); `0` before that. */
45
- readonly updatedAt: number;
46
- }
47
- /** Extra controls for factories that return `infiniteQueryOptions(...)`. */
48
- export interface PagingControls {
49
- fetchNextPage(): Promise<void>;
50
- fetchPreviousPage(): Promise<void>;
51
- readonly hasNextPage: boolean;
52
- readonly hasPreviousPage: boolean;
53
- readonly isFetchingNextPage: boolean;
54
- readonly isFetchingPreviousPage: boolean;
55
- }
56
- export type UseQueryControls<TFactory extends QueryFactory> = IsPaged<TFactory> extends true ? QueryControls<TFactory> & PagingControls : QueryControls<TFactory>;
57
- /**
58
- * `true` when the factory's error channel is typed (Effect `queryFn`), not
59
- * `unknown` (promise / plain value).
60
- */
61
- export type HasTypedError<TFactory extends QueryFactory> = unknown extends ErrorOf<TFactory> ? false : true;
62
- /**
63
- * Whether this read throws on failure. Untyped errors always throw. Typed
64
- * errors follow the read's `throwOnError`, else the query's
65
- * ({@link ThrowOnErrorOf}), else the client's default (`false` with a
66
- * `layer`, `true` without). Types can't see the client's, so they treat it
67
- * as `false` (Result), matching a client with a `layer`.
68
- */
69
- export type ThrowsOnError<TFactory extends QueryFactory, TThrow extends boolean | undefined> = HasTypedError<TFactory> extends false ? true : TThrow extends boolean ? TThrow : ThrowOnErrorOf<TFactory> extends boolean ? ThrowOnErrorOf<TFactory> : false;
70
- /**
71
- * {@link ThrowsOnError} at runtime, for bindings: a read of `query` throws
72
- * when its `queryFn` returns a promise or plain value, whose error isn't
73
- * typed. Otherwise it follows the read's `throwOnError`, else the query's
74
- * options', else `clientDefault` (pass the client's `throwOnError`). A query
75
- * whose `queryFn` hasn't run yet follows the flags.
76
- */
77
- export declare function throwsOnError<TFactory extends QueryFactory>(query: Query<TFactory>, throwOnError: boolean | undefined, clientDefault?: boolean): boolean;
78
- /** What a read settles with: the data, or a `Result` when it doesn't throw. */
79
- export type ReadValue<TFactory extends QueryFactory, TThrow extends boolean | undefined = undefined> = ThrowsOnError<TFactory, TThrow> extends true ? DataOf<TFactory> : Result.Result<DataOf<TFactory>, ErrorOf<TFactory>>;
80
- type Command = "fetchNextPage" | "fetchPreviousPage" | "refetch";
81
- /** An observer result whose command Effects are Promise functions. */
82
- export type PromiseControls = Omit<Result$1, Command> & Record<Command, () => Promise<void>>;
83
- /**
84
- * `result`, with the observer's command Effects as Promise functions. Reads
85
- * of every other property go through to `result`, so a tracked result
86
- * (`QueryObserver.trackResult`) stays tracked. For bindings.
87
- */
88
- export declare function promiseControls(result: Result$1): PromiseControls;
89
- /**
90
- * A thenable that fulfills with a `Result` when `promise` settles, for reads
91
- * that don't throw (`throwOnError: false`). Settled already when `promise`
92
- * is, and the same thenable for the same `promise`. For bindings.
93
- */
94
- export declare function resultThenable<A, E>(promise: Thenable<A>): Thenable<Result.Result<A, E>>;
95
- //#endregion
package/dist/controls.mjs DELETED
@@ -1,70 +0,0 @@
1
- import { fulfilled, pending } from "./thenable.mjs";
2
- import { returnsEffectUnsafe } from "./query.mjs";
3
- import { Effect, Result } from "effect";
4
- //#region src/controls.ts
5
- /**
6
- * What a framework binding's `useQuery` returns next to the data: the
7
- * observer's result with Promise commands, and the types every binding
8
- * shares. The React, Svelte and Vue packages build their reads on this.
9
- */
10
- /**
11
- * {@link ThrowsOnError} at runtime, for bindings: a read of `query` throws
12
- * when its `queryFn` returns a promise or plain value, whose error isn't
13
- * typed. Otherwise it follows the read's `throwOnError`, else the query's
14
- * options', else `clientDefault` (pass the client's `throwOnError`). A query
15
- * whose `queryFn` hasn't run yet follows the flags.
16
- */
17
- function throwsOnError(query, throwOnError, clientDefault = false) {
18
- return returnsEffectUnsafe(query) === false || (throwOnError ?? query.options?.throwOnError ?? clientDefault);
19
- }
20
- const commands = /* @__PURE__ */ new WeakMap();
21
- /** One stable Promise function per command Effect, so identities track the query's. */
22
- function commandFor(effect) {
23
- let command = commands.get(effect);
24
- if (!command) {
25
- command = () => Effect.runPromise(effect);
26
- commands.set(effect, command);
27
- }
28
- return command;
29
- }
30
- /**
31
- * `result`, with the observer's command Effects as Promise functions. Reads
32
- * of every other property go through to `result`, so a tracked result
33
- * (`QueryObserver.trackResult`) stays tracked. For bindings.
34
- */
35
- function promiseControls(result) {
36
- return new Proxy(result, { get: (target, name, receiver) => {
37
- if (name === "fetchNextPage" || name === "fetchPreviousPage" || name === "refetch") return commandFor(target[name]);
38
- return Reflect.get(target, name, receiver);
39
- } });
40
- }
41
- /**
42
- * One `Result` thenable per data thenable, so readers see a stable identity
43
- * across re-renders the same way they do for the data promise.
44
- */
45
- const resultThenables = /* @__PURE__ */ new WeakMap();
46
- /**
47
- * A thenable that fulfills with a `Result` when `promise` settles, for reads
48
- * that don't throw (`throwOnError: false`). Settled already when `promise`
49
- * is, and the same thenable for the same `promise`. For bindings.
50
- */
51
- function resultThenable(promise) {
52
- const cached = resultThenables.get(promise);
53
- if (cached) return cached;
54
- let next;
55
- if (promise.status === "fulfilled") next = fulfilled(Result.succeed(promise.value));
56
- else if (promise.status === "rejected") next = fulfilled(Result.fail(promise.reason));
57
- else {
58
- const deferred = pending();
59
- promise.then((value) => {
60
- deferred.resolve(Result.succeed(value));
61
- }, (error) => {
62
- deferred.resolve(Result.fail(error));
63
- });
64
- next = deferred.promise;
65
- }
66
- resultThenables.set(promise, next);
67
- return next;
68
- }
69
- //#endregion
70
- export { promiseControls, resultThenable, throwsOnError };
@@ -1,84 +0,0 @@
1
- import { QueryKey } from "./key.mjs";
2
- import { QueryFactory, QueryMeta } from "./options.mjs";
3
- import { Query, query_d_exports } from "./query.mjs";
4
- import "./entry.mjs";
5
- import "./cache.mjs";
6
- import { Cause, Effect, Scope, Stream } from "effect";
7
- //#region src/hydration.d.ts
8
- /** What a dehydrated pending query's promise resolves to. */
9
- export interface DehydratedResult {
10
- readonly data?: unknown;
11
- /** When the sender got `data` (ms since epoch). */
12
- readonly updatedAt: number;
13
- }
14
- /**
15
- * One cache entry in transit. Settled entries carry `data`; entries still
16
- * loading their first data carry the request's `promise`, so the receiving
17
- * cache resumes it instead of fetching again.
18
- */
19
- export interface DehydratedQuery {
20
- readonly data?: unknown;
21
- /** Whether the sender's `queryFn` returned an Effect, when it ran there. */
22
- readonly effect?: boolean;
23
- readonly key: QueryKey;
24
- /** The query's `meta`, for receivers that haven't called its factory. */
25
- readonly meta?: QueryMeta;
26
- readonly promise?: Promise<DehydratedResult>;
27
- /** When `data` was fetched; `0` while pending. */
28
- readonly updatedAt: number;
29
- }
30
- export interface DehydratedState {
31
- readonly queries: ReadonlyArray<DehydratedQuery>;
32
- }
33
- /** The cache now, then entries as they start or settle. */
34
- export interface DehydratedStream {
35
- /** The entries to send first. */
36
- readonly initial: DehydratedState;
37
- /**
38
- * Later entries, in batches. Ends once the scope `dehydrateStream` ran in
39
- * closes, after a last batch of what changed before. One consumer only.
40
- */
41
- readonly updates: Stream.Stream<DehydratedState>;
42
- }
43
- export interface DehydrateOptions {
44
- /**
45
- * Which entries to send. Default: entries with data or a request in
46
- * flight ({@link canDehydrate}).
47
- */
48
- readonly filter?: (query: Query) => boolean;
49
- /**
50
- * Send a pending query's rejection as-is. By default the receiving side
51
- * gets a {@link RedactedError}, so server errors don't leak into the page;
52
- * its error boundary can still retry on the client.
53
- */
54
- readonly keepErrors?: boolean;
55
- /**
56
- * Hashes of entries sent already, shared by every dehydration of one
57
- * transfer (e.g. one request). Those entries are skipped, and the ones
58
- * sent are added.
59
- */
60
- readonly sent?: Set<string>;
61
- /** Transform data before it is serialized. */
62
- readonly serializeData?: (data: unknown) => unknown;
63
- }
64
- export interface HydrateOptions {
65
- /** Reverse `serializeData`. */
66
- readonly deserializeData?: (data: unknown) => unknown;
67
- }
68
- declare const RedactedError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => Cause.YieldableError & {
69
- readonly _tag: "RedactedError";
70
- } & Readonly<A>;
71
- /**
72
- * What a dehydrated pending query rejects with when its request failed and
73
- * `keepErrors` is off: the receiving side learns which query failed, not why.
74
- */
75
- export declare class RedactedError extends RedactedError_base<{
76
- readonly hash: string;
77
- }> {
78
- get message(): string;
79
- }
80
- /** The default dehydrate filter: entries with data or a request in flight. */
81
- export declare function canDehydrate<TFactory extends QueryFactory>(query: Query<TFactory>): boolean;
82
- /** Snapshot one entry for transfer. */
83
- export declare function dehydrateQuery<TFactory extends QueryFactory>(query: Query<TFactory>, options?: DehydrateOptions): Effect.Effect<DehydratedQuery>;
84
- //#endregion
@@ -1,148 +0,0 @@
1
- import { entryOf, isDisabled, resume, revalidate, setData } from "./entry.mjs";
2
- import { getUnsafe, metaUnsafe, returnsEffectUnsafe } from "./query.mjs";
3
- import { Data, Effect, Queue, Stream } from "effect";
4
- //#region src/hydration.ts
5
- /**
6
- * What a dehydrated pending query rejects with when its request failed and
7
- * `keepErrors` is off: the receiving side learns which query failed, not why.
8
- */
9
- var RedactedError = class extends Data.TaggedError("RedactedError") {
10
- get message() {
11
- return `Query ${this.hash} failed`;
12
- }
13
- };
14
- const noop = () => {};
15
- const identity = (value) => value;
16
- /** The default dehydrate filter: entries with data or a request in flight. */
17
- function canDehydrate(query) {
18
- const state = getUnsafe(query);
19
- return state.hasData || state.isFetching;
20
- }
21
- function dehydrateUnsafe(query, options) {
22
- const sent = dehydrateState(query, options);
23
- const effect = returnsEffectUnsafe(query);
24
- return effect === void 0 ? sent : {
25
- ...sent,
26
- effect
27
- };
28
- }
29
- /** {@link dehydrateUnsafe} without `effect`. */
30
- function dehydrateState(query, options) {
31
- const state = getUnsafe(query);
32
- const serialize = options.serializeData ?? identity;
33
- const meta = metaUnsafe(query);
34
- const sent = meta === void 0 ? { key: query.key } : {
35
- key: query.key,
36
- meta
37
- };
38
- if (state.hasData) return {
39
- ...sent,
40
- data: serialize(state.data),
41
- updatedAt: state.updatedAt
42
- };
43
- const promise = state.promise.then((data) => ({
44
- data: serialize(data),
45
- updatedAt: getUnsafe(query).updatedAt
46
- }), (error) => {
47
- throw options.keepErrors ? error : new RedactedError({ hash: query.hash });
48
- });
49
- promise.catch(noop);
50
- return {
51
- ...sent,
52
- promise,
53
- updatedAt: 0
54
- };
55
- }
56
- /** Snapshot one entry for transfer. */
57
- function dehydrateQuery(query, options = {}) {
58
- return Effect.sync(() => dehydrateUnsafe(query, options));
59
- }
60
- /** Dehydrate the entries `options` selects, recording them in `options.sent`. */
61
- function dehydrateEntries(queries, options) {
62
- const filter = options.filter ?? canDehydrate;
63
- const sent = options.sent;
64
- const dehydrated = [];
65
- for (const query of queries) if (!sent?.has(query.hash) && !isDisabled(entryOf(query)) && filter(query)) {
66
- sent?.add(query.hash);
67
- dehydrated.push(dehydrateUnsafe(query, options));
68
- }
69
- return { queries: dehydrated };
70
- }
71
- /**
72
- * Snapshots `queries()` and subscribes in the same synchronous step, so no
73
- * change falls between the two. Changed entries are collected for a
74
- * microtask, so entries that settle together share a batch, and the batch is
75
- * handed to the reader before that microtask ends: ahead of the Suspense
76
- * boundaries React resumes for those entries.
77
- */
78
- const dehydrateStream = /*#__PURE__*/ Effect.fnUntraced(function* (queries, subscribe, options) {
79
- const sent = options.sent ?? /* @__PURE__ */ new Set();
80
- const send = {
81
- ...options,
82
- sent
83
- };
84
- const queue = yield* Queue.unbounded();
85
- let batch;
86
- const flush = () => {
87
- const changed = batch;
88
- batch = void 0;
89
- if (!changed) return;
90
- const state = dehydrateEntries(changed.values(), send);
91
- if (state.queries.length > 0) {
92
- Queue.offerUnsafe(queue, state);
93
- Queue.flushUnsafe(queue);
94
- }
95
- };
96
- yield* Effect.acquireRelease(Effect.sync(() => subscribe((event) => {
97
- if (event.type === "removed" || sent.has(event.query.hash)) return;
98
- if (!batch) {
99
- batch = /* @__PURE__ */ new Map();
100
- queueMicrotask(flush);
101
- }
102
- batch.set(event.query.hash, event.query);
103
- })), (unsubscribe) => Effect.sync(() => {
104
- flush();
105
- unsubscribe();
106
- Queue.endUnsafe(queue);
107
- }));
108
- return {
109
- initial: dehydrateEntries(queries(), send),
110
- updates: Stream.fromQueue(queue)
111
- };
112
- });
113
- /**
114
- * Take the sender's data if it's newer than the entry's. An entry that keeps
115
- * its own (the sender's is older, as in a router's cached payload) was asked
116
- * for again, so it revalidates if stale, as `prefetch` would.
117
- */
118
- function takeNewer(entry, data, updatedAt) {
119
- const current = entry.state;
120
- if (!current.hasData || current.updatedAt < updatedAt) setData(entry, data, updatedAt);
121
- else revalidate(entry);
122
- }
123
- /**
124
- * Write dehydrated entries into a cache. Existing entries only take newer
125
- * data. A pending entry the cache has nothing for resumes the sender's
126
- * request. One that has data, or a request of its own, waits for the
127
- * sender's instead of fetching again, and then takes whichever is newer;
128
- * if the sender's failed, it revalidates.
129
- */
130
- function hydrateEntries(entryFor, state, options) {
131
- const deserialize = options.deserializeData ?? identity;
132
- for (const query of state.queries) {
133
- const entry = entryFor(query.key);
134
- if (!entry.options && query.meta !== void 0) entry.hydratedMeta = query.meta;
135
- entry.returnsEffect ??= query.effect;
136
- if (query.updatedAt > 0) takeNewer(entry, deserialize(query.data), query.updatedAt);
137
- else if (query.promise) {
138
- const result = Promise.resolve(query.promise).then((sent) => ({
139
- data: deserialize(sent.data),
140
- updatedAt: sent.updatedAt
141
- }));
142
- if (!entry.state.hasData && !entry.inFlight) resume(entry, result);
143
- else result.then((sent) => takeNewer(entry, sent.data, sent.updatedAt), () => revalidate(entry));
144
- } else revalidate(entry);
145
- }
146
- }
147
- //#endregion
148
- export { RedactedError, canDehydrate, dehydrateEntries, dehydrateQuery, dehydrateStream, hydrateEntries };
@@ -1,5 +0,0 @@
1
- import "./options.mjs";
2
- import { Effect } from "effect";
3
- //#region src/infinite.d.ts
4
- export type Direction = "backward" | "forward";
5
- //#endregion
package/dist/infinite.mjs DELETED
@@ -1,103 +0,0 @@
1
- import { replaceEqualDeep } from "./share.mjs";
2
- import { Effect, Option, Predicate } from "effect";
3
- //#region src/infinite.ts
4
- function isInfiniteData(data) {
5
- return Predicate.hasProperty(data, "pages") && Predicate.hasProperty(data, "pageParams") && Array.isArray(data.pages) && Array.isArray(data.pageParams);
6
- }
7
- function nextPageParam(options, data) {
8
- if (data.pages.length === 0) return;
9
- return options.getNextPageParam(data.pages.at(-1), data.pages, data.pageParams.at(-1), data.pageParams);
10
- }
11
- function previousPageParam(options, data) {
12
- if (data.pages.length === 0 || !options.getPreviousPageParam) return;
13
- return options.getPreviousPageParam(data.pages[0], data.pages, data.pageParams[0], data.pageParams);
14
- }
15
- function addPage(data, page, pageParam, direction, maxPages) {
16
- const forward = direction === "forward";
17
- const pages = forward ? [...data.pages, page] : [page, ...data.pages];
18
- const pageParams = forward ? [...data.pageParams, pageParam] : [pageParam, ...data.pageParams];
19
- if (maxPages === void 0 || pages.length <= maxPages) return {
20
- pageParams,
21
- pages
22
- };
23
- return forward ? {
24
- pageParams: pageParams.slice(-maxPages),
25
- pages: pages.slice(-maxPages)
26
- } : {
27
- pageParams: pageParams.slice(0, maxPages),
28
- pages: pages.slice(0, maxPages)
29
- };
30
- }
31
- function edgeParam(data, direction) {
32
- return direction === "forward" ? data.pageParams.at(-1) : data.pageParams[0];
33
- }
34
- /** The page `data` holds for `pageParam`, compared deeply. */
35
- function pageFor(data, pageParam) {
36
- const index = data?.pageParams.findIndex((param) => replaceEqualDeep(param, pageParam) === param) ?? -1;
37
- return index === -1 ? Option.none() : Option.some(data?.pages[index]);
38
- }
39
- /**
40
- * `data`, a full refetch's pages, with each page that `latest` replaced
41
- * after the refetch started reading it: that write is newer than the read.
42
- * A page written before its read started is read again, and the read wins.
43
- */
44
- function keepWrites(data, readFrom, latest) {
45
- let changed = false;
46
- const pages = data.pages.map((page, index) => {
47
- const pageParam = data.pageParams[index];
48
- const read = readFrom.find((entry) => replaceEqualDeep(entry.pageParam, pageParam) === entry.pageParam);
49
- const now = pageFor(latest, pageParam);
50
- if (!read || Option.isNone(now) || Option.isSome(read.seen) && read.seen.value === now.value) return page;
51
- changed = true;
52
- return now.value;
53
- });
54
- return changed ? {
55
- pageParams: data.pageParams,
56
- pages
57
- } : data;
58
- }
59
- /**
60
- * A paged query's request. With a `direction`, adds one page at that end of
61
- * the entry's data, read again with `latest` once the page loads, so data
62
- * written meanwhile (`setData`, a live subscription) stays. If that data no
63
- * longer ends at the page param the request started from, the write
64
- * replaced the list, and the page is dropped. Without a `direction`, loads
65
- * from the first page param again: as many pages as `current` has, or
66
- * `pages` if that's more, stopping at the last page. A page written while
67
- * the refetch was reading it, or after, stays as written ({@link keepWrites}).
68
- */
69
- const fetchPages = /*#__PURE__*/ Effect.fnUntraced(function* (options, current, request, fetchPage, latest) {
70
- const direction = request.direction;
71
- if (direction && current) {
72
- const pageParam = direction === "forward" ? nextPageParam(options, current) : previousPageParam(options, current);
73
- if (pageParam == null) return current;
74
- const page = yield* fetchPage(pageParam, direction);
75
- const now = latest() ?? current;
76
- const startedFrom = edgeParam(current, direction);
77
- if (replaceEqualDeep(startedFrom, edgeParam(now, direction)) !== startedFrom) return now;
78
- return addPage(now, page, pageParam, direction, options.maxPages);
79
- }
80
- const count = Math.max(request.pages ?? 1, current?.pages.length ?? 0);
81
- let data = {
82
- pageParams: [],
83
- pages: []
84
- };
85
- const readFrom = [];
86
- let pageParam = current && current.pageParams.length > 0 ? current.pageParams[0] : options.initialPageParam;
87
- for (let index = 0; index < count; index++) {
88
- if (index > 0) {
89
- const next = nextPageParam(options, data);
90
- if (next == null) break;
91
- pageParam = next;
92
- }
93
- readFrom.push({
94
- pageParam,
95
- seen: pageFor(latest(), pageParam)
96
- });
97
- const page = yield* fetchPage(pageParam, "forward");
98
- data = addPage(data, page, pageParam, "forward", options.maxPages);
99
- }
100
- return keepWrites(data, readFrom, latest());
101
- });
102
- //#endregion
103
- export { fetchPages, isInfiniteData, nextPageParam, previousPageParam };
package/dist/key.d.mts DELETED
@@ -1,18 +0,0 @@
1
- //#region src/key.d.ts
2
- /**
3
- * A query's cache key. Must be JSON-serializable (plain objects, arrays and
4
- * primitives): it travels inside handles.
5
- */
6
- export type QueryKey = ReadonlyArray<unknown>;
7
- /**
8
- * Stable string form of a key. Object keys are sorted, so `{ a, b }` and
9
- * `{ b, a }` hash the same.
10
- */
11
- export declare function hashKey(key: QueryKey): string;
12
- /**
13
- * Whether `key` starts with `prefix`. Object segments match when every field
14
- * in the prefix's segment matches, so `["posts", { sort: "new" }]` matches
15
- * `["posts", { sort: "new", page: 2 }]`.
16
- */
17
- export declare function matchesKey(key: QueryKey, prefix: QueryKey): boolean;
18
- //#endregion
package/dist/key.mjs DELETED
@@ -1,36 +0,0 @@
1
- import { Predicate } from "effect";
2
- //#region src/key.ts
3
- function isPlainRecord(value) {
4
- if (!Predicate.isObject(value) || Array.isArray(value)) return false;
5
- const prototype = Object.getPrototypeOf(value);
6
- return prototype === Object.prototype || prototype === null;
7
- }
8
- function sortKeys(_name, value) {
9
- if (!isPlainRecord(value)) return value;
10
- const sorted = {};
11
- for (const name of Object.keys(value).toSorted()) sorted[name] = value[name];
12
- return sorted;
13
- }
14
- /**
15
- * Stable string form of a key. Object keys are sorted, so `{ a, b }` and
16
- * `{ b, a }` hash the same.
17
- */
18
- function hashKey(key) {
19
- return JSON.stringify(key, sortKeys);
20
- }
21
- function partialMatch(actual, expected) {
22
- if (actual === expected) return true;
23
- if (Array.isArray(actual) && Array.isArray(expected)) return actual.length === expected.length && expected.every((part, index) => partialMatch(actual[index], part));
24
- if (isPlainRecord(actual) && isPlainRecord(expected)) return Object.keys(expected).every((name) => partialMatch(actual[name], expected[name]));
25
- return false;
26
- }
27
- /**
28
- * Whether `key` starts with `prefix`. Object segments match when every field
29
- * in the prefix's segment matches, so `["posts", { sort: "new" }]` matches
30
- * `["posts", { sort: "new", page: 2 }]`.
31
- */
32
- function matchesKey(key, prefix) {
33
- return prefix.length <= key.length && prefix.every((part, index) => partialMatch(key[index], part));
34
- }
35
- //#endregion
36
- export { hashKey, isPlainRecord, matchesKey };
package/dist/listen.mjs DELETED
@@ -1,19 +0,0 @@
1
- import { Effect, Queue, Stream } from "effect";
2
- //#region src/listen.ts
3
- /**
4
- * A stream of what `subscribe` emits. Subscribes in the consuming fiber as
5
- * soon as the stream starts, so nothing emitted after that is missed
6
- * (`Stream.callback` subscribes on a forked fiber, a scheduler tick later),
7
- * and unsubscribes when the stream ends.
8
- */
9
- function listen(subscribe) {
10
- return Stream.unwrap(Effect.gen(function* () {
11
- const queue = yield* Queue.unbounded();
12
- yield* Effect.acquireRelease(Effect.sync(() => subscribe((value) => {
13
- Queue.offerUnsafe(queue, value);
14
- })), (unsubscribe) => Effect.sync(unsubscribe));
15
- return Stream.fromQueue(queue);
16
- }));
17
- }
18
- //#endregion
19
- export { listen };
package/dist/notify.mjs DELETED
@@ -1,21 +0,0 @@
1
- //#region src/notify.ts
2
- let queued;
3
- /**
4
- * Calls `listener` in a microtask, once per batch. State changes made while
5
- * React renders (a fetch started by a read) must not notify subscribers
6
- * synchronously, and several changes in one turn should re-render once.
7
- */
8
- function scheduleNotify(listener) {
9
- if (queued) {
10
- queued.add(listener);
11
- return;
12
- }
13
- const batch = /* @__PURE__ */ new Set([listener]);
14
- queued = batch;
15
- queueMicrotask(() => {
16
- queued = void 0;
17
- for (const queuedListener of batch) queuedListener();
18
- });
19
- }
20
- //#endregion
21
- export { scheduleNotify };