@raf-query/core 0.0.19 → 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,15 +1,25 @@
1
- import { cacheOf } from "./client.mjs";
1
+ import { resume, setData } from "./entry.mjs";
2
+ import { getUnsafe } from "./query.mjs";
3
+ import { Data, Effect, Queue, Stream } from "effect";
2
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
+ };
3
14
  const noop = () => {};
4
15
  const identity = (value) => value;
5
16
  /** The default dehydrate filter: entries with data or a request in flight. */
6
17
  function canDehydrate(query) {
7
- const state = query.getState();
18
+ const state = getUnsafe(query);
8
19
  return state.hasData || state.isFetching;
9
20
  }
10
- /** Snapshot one entry for transfer. */
11
- function dehydrateQuery(query, options = {}) {
12
- const state = query.getState();
21
+ function dehydrateUnsafe(query, options) {
22
+ const state = getUnsafe(query);
13
23
  const serialize = options.serializeData ?? identity;
14
24
  if (state.hasData) return {
15
25
  data: serialize(state.data),
@@ -17,7 +27,7 @@ function dehydrateQuery(query, options = {}) {
17
27
  updatedAt: state.updatedAt
18
28
  };
19
29
  const promise = state.promise.then(serialize, (error) => {
20
- throw options.keepErrors ? error : /* @__PURE__ */ new Error(`Query ${query.hash} failed`);
30
+ throw options.keepErrors ? error : new RedactedError({ hash: query.hash });
21
31
  });
22
32
  promise.catch(noop);
23
33
  return {
@@ -26,25 +36,76 @@ function dehydrateQuery(query, options = {}) {
26
36
  updatedAt: 0
27
37
  };
28
38
  }
29
- /** Snapshot the cache for transfer to another client (server → browser). */
30
- function dehydrate(client, options = {}) {
39
+ /** Snapshot one entry for transfer. */
40
+ function dehydrateQuery(query, options = {}) {
41
+ return Effect.sync(() => dehydrateUnsafe(query, options));
42
+ }
43
+ /** Dehydrate the entries `options` selects, recording them in `options.sent`. */
44
+ function dehydrateEntries(queries, options) {
31
45
  const filter = options.filter ?? canDehydrate;
32
- return { queries: client.getAll().filter(filter).map((query) => dehydrateQuery(query, options)) };
46
+ const sent = options.sent;
47
+ const dehydrated = [];
48
+ for (const query of queries) if (!sent?.has(query.hash) && filter(query)) {
49
+ sent?.add(query.hash);
50
+ dehydrated.push(dehydrateUnsafe(query, options));
51
+ }
52
+ return { queries: dehydrated };
33
53
  }
34
54
  /**
35
- * Write dehydrated entries into a client. Existing entries only take newer
55
+ * Snapshots `queries()` and subscribes in the same synchronous step, so no
56
+ * change falls between the two. Changed entries are collected for a
57
+ * microtask, so entries that settle together share a batch, and the batch is
58
+ * handed to the reader before that microtask ends: ahead of the Suspense
59
+ * boundaries React resumes for those entries.
60
+ */
61
+ const dehydrateStream = Effect.fnUntraced(function* (queries, subscribe, options) {
62
+ const sent = options.sent ?? /* @__PURE__ */ new Set();
63
+ const send = {
64
+ ...options,
65
+ sent
66
+ };
67
+ const queue = yield* Queue.unbounded();
68
+ let batch;
69
+ const flush = () => {
70
+ const changed = batch;
71
+ batch = void 0;
72
+ if (!changed) return;
73
+ const state = dehydrateEntries(changed.values(), send);
74
+ if (state.queries.length > 0) {
75
+ Queue.offerUnsafe(queue, state);
76
+ Queue.flushUnsafe(queue);
77
+ }
78
+ };
79
+ yield* Effect.acquireRelease(Effect.sync(() => subscribe((event) => {
80
+ if (event.type === "removed" || sent.has(event.query.hash)) return;
81
+ if (!batch) {
82
+ batch = /* @__PURE__ */ new Map();
83
+ queueMicrotask(flush);
84
+ }
85
+ batch.set(event.query.hash, event.query);
86
+ })), (unsubscribe) => Effect.sync(() => {
87
+ flush();
88
+ unsubscribe();
89
+ Queue.endUnsafe(queue);
90
+ }));
91
+ return {
92
+ initial: dehydrateEntries(queries(), send),
93
+ updates: Stream.fromQueue(queue)
94
+ };
95
+ });
96
+ /**
97
+ * Write dehydrated entries into a cache. Existing entries only take newer
36
98
  * data; pending entries resume the sender's request.
37
99
  */
38
- function hydrate(client, state, options = {}) {
100
+ function hydrateEntries(entryFor, state, options) {
39
101
  const deserialize = options.deserializeData ?? identity;
40
- const cache = cacheOf(client);
41
102
  for (const query of state.queries) {
42
- const entry = cache.entryFor(query.key);
43
- const current = entry.getState();
103
+ const entry = entryFor(query.key);
104
+ const current = entry.state;
44
105
  if (query.updatedAt > 0) {
45
- if (!current.hasData || current.updatedAt < query.updatedAt) entry.setData(deserialize(query.data), query.updatedAt);
46
- } else if (query.promise && !current.hasData) entry.resume(Promise.resolve(query.promise).then(deserialize));
106
+ if (!current.hasData || current.updatedAt < query.updatedAt) setData(entry, deserialize(query.data), query.updatedAt);
107
+ } else if (query.promise && !current.hasData) resume(entry, Promise.resolve(query.promise).then(deserialize));
47
108
  }
48
109
  }
49
110
  //#endregion
50
- export { canDehydrate, dehydrate, dehydrateQuery, hydrate };
111
+ export { RedactedError, canDehydrate, dehydrateEntries, dehydrateQuery, dehydrateStream, dehydrateUnsafe, hydrateEntries };
package/dist/index.d.mts CHANGED
@@ -1,9 +1,9 @@
1
- import { Settled, allKeyed } from "./all-keyed.mjs";
1
+ import { platform_d_exports } from "./platform.mjs";
2
2
  import { QueryKey, hashKey, matchesKey } from "./key.mjs";
3
- import { AnyQueryOptions, DataOf, EnsuredQueryHandle, InfiniteData, InfiniteQueryOptions, InputArgs, InputOf, IsPaged, LoadOptions, PageFunctionContext, PageOf, QueryBehavior, QueryFactory, QueryFnResult, QueryFunctionContext, QueryHandle, QueryOptions, RetryPolicy, infiniteQueryOptions, queryOptions } from "./options.mjs";
3
+ import { AnyQueryOptions, DataOf, EnsuredQueryHandle, ErrorOf, InfiniteData, InfiniteQueryOptions, InputArgs, InputOf, IsPaged, LoadOptions, PageFunctionContext, PageOf, QueryBehavior, QueryFactory, QueryFnResult, QueryFunctionContext, QueryHandle, QueryOptions, RetryPolicy, ServicesOf, infiniteQueryOptions, queryOptions } from "./options.mjs";
4
+ import { DehydrateOptions, DehydratedQuery, DehydratedState, DehydratedStream, HydrateOptions, RedactedError, canDehydrate, dehydrateQuery } from "./hydration.mjs";
5
+ import { cache_d_exports } from "./cache.mjs";
4
6
  import { Thenable } from "./thenable.mjs";
5
- import { CancelledError, FetchRequest, Query, QueryState, SubscribeOptions } from "./query.mjs";
6
- import { CacheEvent, LoadArgs, QueryClient, QueryClientConfig, QueryClientEffect, QueryFilter, Updater } from "./client.mjs";
7
- import { DehydrateOptions, DehydratedQuery, DehydratedState, HydrateOptions, canDehydrate, dehydrate, dehydrateQuery, hydrate } from "./hydration.mjs";
8
- import { QueryObserver, QueryObserverOptions, QueryObserverResult } from "./observer.mjs";
9
- export { type AnyQueryOptions, type CacheEvent, CancelledError, type DataOf, type DehydrateOptions, type DehydratedQuery, type DehydratedState, type EnsuredQueryHandle, type FetchRequest, type HydrateOptions, type InfiniteData, type InfiniteQueryOptions, type InputArgs, type InputOf, type IsPaged, type LoadArgs, type LoadOptions, type PageFunctionContext, type PageOf, type Query, type QueryBehavior, QueryClient, type QueryClientConfig, type QueryClientEffect, type QueryFactory, type QueryFilter, type QueryFnResult, type QueryFunctionContext, type QueryHandle, type QueryKey, QueryObserver, type QueryObserverOptions, type QueryObserverResult, type QueryOptions, type QueryState, type RetryPolicy, type Settled, type SubscribeOptions, type Thenable, type Updater, allKeyed, canDehydrate, dehydrate, dehydrateQuery, hashKey, hydrate, infiniteQueryOptions, matchesKey, queryOptions };
7
+ import { query_d_exports } from "./query.mjs";
8
+ import { observer_d_exports } from "./observer.mjs";
9
+ export { type AnyQueryOptions, type DataOf, type DehydrateOptions, type DehydratedQuery, type DehydratedState, type DehydratedStream, type EnsuredQueryHandle, type ErrorOf, type HydrateOptions, type InfiniteData, type InfiniteQueryOptions, type InputArgs, type InputOf, type IsPaged, type LoadOptions, type PageFunctionContext, type PageOf, platform_d_exports as Platform, query_d_exports as Query, type QueryBehavior, cache_d_exports as QueryCache, type QueryFactory, type QueryFnResult, type QueryFunctionContext, type QueryHandle, type QueryKey, observer_d_exports as QueryObserver, type QueryOptions, RedactedError, type RetryPolicy, type ServicesOf, type Thenable, canDehydrate, dehydrateQuery, hashKey, infiniteQueryOptions, matchesKey, queryOptions };
package/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
- import { allKeyed } from "./all-keyed.mjs";
1
+ import { platform_exports } from "./platform.mjs";
2
2
  import { hashKey, matchesKey } from "./key.mjs";
3
+ import { query_exports } from "./query.mjs";
4
+ import { RedactedError, canDehydrate, dehydrateQuery } from "./hydration.mjs";
3
5
  import { infiniteQueryOptions, queryOptions } from "./options.mjs";
4
- import { CancelledError } from "./query.mjs";
5
- import { QueryClient } from "./client.mjs";
6
- import { canDehydrate, dehydrate, dehydrateQuery, hydrate } from "./hydration.mjs";
7
- import { QueryObserver } from "./observer.mjs";
8
- export { CancelledError, QueryClient, QueryObserver, allKeyed, canDehydrate, dehydrate, dehydrateQuery, hashKey, hydrate, infiniteQueryOptions, matchesKey, queryOptions };
6
+ import { cache_exports } from "./cache.mjs";
7
+ import { observer_exports } from "./observer.mjs";
8
+ export { platform_exports as Platform, query_exports as Query, cache_exports as QueryCache, observer_exports as QueryObserver, RedactedError, canDehydrate, dehydrateQuery, hashKey, infiniteQueryOptions, matchesKey, queryOptions };
@@ -0,0 +1,5 @@
1
+ import "./options.mjs";
2
+ import { Effect } from "effect";
3
+ //#region src/infinite.d.ts
4
+ export type Direction = "backward" | "forward";
5
+ //#endregion
@@ -0,0 +1,59 @@
1
+ import { Effect, Predicate } from "effect";
2
+ //#region src/infinite.ts
3
+ function isInfiniteData(data) {
4
+ return Predicate.hasProperty(data, "pages") && Predicate.hasProperty(data, "pageParams") && Array.isArray(data.pages) && Array.isArray(data.pageParams);
5
+ }
6
+ function nextPageParam(options, data) {
7
+ if (data.pages.length === 0) return;
8
+ return options.getNextPageParam(data.pages.at(-1), data.pages, data.pageParams.at(-1), data.pageParams);
9
+ }
10
+ function previousPageParam(options, data) {
11
+ if (data.pages.length === 0 || !options.getPreviousPageParam) return;
12
+ return options.getPreviousPageParam(data.pages[0], data.pages, data.pageParams[0], data.pageParams);
13
+ }
14
+ function addPage(data, page, pageParam, direction, maxPages) {
15
+ const forward = direction === "forward";
16
+ const pages = forward ? [...data.pages, page] : [page, ...data.pages];
17
+ const pageParams = forward ? [...data.pageParams, pageParam] : [pageParam, ...data.pageParams];
18
+ if (maxPages === void 0 || pages.length <= maxPages) return {
19
+ pageParams,
20
+ pages
21
+ };
22
+ return forward ? {
23
+ pageParams: pageParams.slice(-maxPages),
24
+ pages: pages.slice(-maxPages)
25
+ } : {
26
+ pageParams: pageParams.slice(0, maxPages),
27
+ pages: pages.slice(0, maxPages)
28
+ };
29
+ }
30
+ /**
31
+ * A paged query's request. With a `direction`, adds one page at that end of
32
+ * `current`. Otherwise loads from the first page param again: as many pages
33
+ * as `current` has, or `pages` if that's more, stopping at the last page.
34
+ */
35
+ const fetchPages = Effect.fnUntraced(function* (options, current, request, fetchPage) {
36
+ if (request.direction && current) {
37
+ const pageParam = request.direction === "forward" ? nextPageParam(options, current) : previousPageParam(options, current);
38
+ if (pageParam == null) return current;
39
+ return addPage(current, yield* fetchPage(pageParam, request.direction), pageParam, request.direction, options.maxPages);
40
+ }
41
+ const count = Math.max(request.pages ?? 1, current?.pages.length ?? 0);
42
+ let data = {
43
+ pageParams: [],
44
+ pages: []
45
+ };
46
+ let pageParam = current && current.pageParams.length > 0 ? current.pageParams[0] : options.initialPageParam;
47
+ for (let index = 0; index < count; index++) {
48
+ if (index > 0) {
49
+ const next = nextPageParam(options, data);
50
+ if (next == null) break;
51
+ pageParam = next;
52
+ }
53
+ const page = yield* fetchPage(pageParam, "forward");
54
+ data = addPage(data, page, pageParam, "forward", options.maxPages);
55
+ }
56
+ return data;
57
+ });
58
+ //#endregion
59
+ export { fetchPages, isInfiniteData, nextPageParam, previousPageParam };
@@ -0,0 +1,19 @@
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 };
@@ -1,10 +1,15 @@
1
1
  import { QueryKey } from "./key.mjs";
2
2
  import { QueryFactory } from "./options.mjs";
3
3
  import { Thenable } from "./thenable.mjs";
4
- import { Query } from "./query.mjs";
5
- //#region src/observer.d.ts
4
+ import { Query, query_d_exports } from "./query.mjs";
5
+ import { Effect, Pipeable, Stream } from "effect";
6
+ declare namespace observer_d_exports {
7
+ export { Options, QueryObserver, Result, ResultKey, TypeId, changes, get, getOptimisticResultUnsafe, getUnsafe, isQueryObserver, make, makeUnsafe, setOptionsUnsafe, subscribeUnsafe, trackProp, trackResult };
8
+ }
9
+ export declare const TypeId = "~@raf-query/core/QueryObserver";
10
+ export type TypeId = typeof TypeId;
6
11
  /** How one reader observes its query. */
7
- export interface QueryObserverOptions {
12
+ export interface Options {
8
13
  /** Refetch on this interval (ms) while subscribed and the page is visible. */
9
14
  readonly refetchInterval?: number;
10
15
  /** Refetch on reconnect while stale. Default: the query's option, else `true`. */
@@ -19,11 +24,13 @@ export interface QueryObserverOptions {
19
24
  readonly whilePending?: "previous" | "suspend";
20
25
  }
21
26
  /** What a reader sees of its query. Replaced, never mutated, on every change. */
22
- export interface QueryObserverResult<TData = unknown> {
27
+ export interface Result<TData = unknown> {
23
28
  /** Error of the last failed request. Existing data stays readable. */
24
29
  readonly error: unknown;
25
- fetchNextPage(): Promise<void>;
26
- fetchPreviousPage(): Promise<void>;
30
+ /** The query's `fetchNextPage`. */
31
+ readonly fetchNextPage: Effect.Effect<void>;
32
+ /** The query's `fetchPreviousPage`. */
33
+ readonly fetchPreviousPage: Effect.Effect<void>;
27
34
  /** Stable string form of the observed query's key. */
28
35
  readonly hash: string;
29
36
  readonly hasNextPage: boolean;
@@ -42,42 +49,53 @@ export interface QueryObserverResult<TData = unknown> {
42
49
  readonly isStale: boolean;
43
50
  /** Key of the data in `promise`; differs from the query's while `isPrevious`. */
44
51
  readonly key: QueryKey;
45
- /** Settles with the data to show; see `QueryState.promise`. */
52
+ /** Settles with the data to show; see `Query.State.promise`. */
46
53
  readonly promise: Thenable<TData>;
47
- refetch(): Promise<void>;
54
+ /** The query's `refetch`. */
55
+ readonly refetch: Effect.Effect<void>;
48
56
  /** When the data in `promise` was fetched (ms since epoch); `0` before that. */
49
57
  readonly updatedAt: number;
50
58
  }
51
- type ResultKey = keyof QueryObserverResult;
59
+ export type ResultKey = keyof Result;
60
+ export interface QueryObserver<TData = unknown, TFactory extends QueryFactory = QueryFactory> extends Pipeable.Pipeable {
61
+ /** The options set by the latest `make` or {@link setOptionsUnsafe}. */
62
+ readonly options: Options;
63
+ /** The observed query. */
64
+ readonly query: Query<TData, TFactory>;
65
+ readonly [TypeId]: {
66
+ readonly _A: (_: never) => TData;
67
+ };
68
+ }
69
+ export declare function isQueryObserver(value: unknown): value is QueryObserver;
70
+ /** Observe `query`. Doesn't subscribe to it until the observer has a listener. */
71
+ export declare function makeUnsafe<TData, TFactory extends QueryFactory>(query: Query<TData, TFactory>, options?: Options): QueryObserver<TData, TFactory>;
72
+ /** {@link makeUnsafe}, as an Effect. */
73
+ export declare function make<TData, TFactory extends QueryFactory>(query: Query<TData, TFactory>, options?: Options): Effect.Effect<QueryObserver<TData, TFactory>>;
74
+ /** The current result. */
75
+ export declare function getUnsafe<TData, TFactory extends QueryFactory>(self: QueryObserver<TData, TFactory>): Result<TData>;
76
+ /** {@link getUnsafe}, as an Effect. */
77
+ export declare function get<TData, TFactory extends QueryFactory>(self: QueryObserver<TData, TFactory>): Effect.Effect<Result<TData>>;
52
78
  /**
53
- * One reader of one query at a time, like TanStack Query's `QueryObserver`:
54
- * it derives the reader's result from the query's state, keeps a previous
55
- * query's data for `whilePending: "previous"`, runs `refetchInterval`, and
56
- * notifies its listeners only when a result property they read changed
57
- * (see {@link QueryObserver.trackResult}). Bindings create one per hook.
79
+ * The result for `query` with `options` right now, without switching to
80
+ * them. Bindings call this during render, before `setOptionsUnsafe` commits.
58
81
  */
59
- export declare class QueryObserver<TData = unknown, TFactory extends QueryFactory = QueryFactory> {
60
- #private;
61
- constructor(query: Query<TData, TFactory>, options?: QueryObserverOptions);
62
- /**
63
- * The result for `query` with `options` right now, without switching to
64
- * it. Bindings call this during render, before `setOptions` commits.
65
- */
66
- getOptimisticResult(query: Query<TData, TFactory>, options: QueryObserverOptions): QueryObserverResult<TData>;
67
- getCurrentResult: () => QueryObserverResult<TData>;
68
- /** Observe `query` with `options` from now on. */
69
- setOptions(query: Query<TData, TFactory>, options: QueryObserverOptions): void;
70
- /** Listen for result changes. The first listener subscribes to the query. */
71
- subscribe: (listener: () => void) => (() => void);
72
- /**
73
- * Wrap `result` so that reading a property marks it tracked. Once any
74
- * property is tracked, listeners are only notified when a tracked one
75
- * changes.
76
- */
77
- trackResult<TResult extends QueryObserverResult<TData>>(result: TResult): TResult;
78
- /** Mark `name` as read, for bindings that track access themselves. */
79
- trackProp(name: ResultKey): void;
80
- /** Recompute the result, and notify listeners if a tracked property changed. */
81
- updateResult(): void;
82
- }
83
- //#endregion
82
+ export declare function getOptimisticResultUnsafe<TData, TFactory extends QueryFactory>(self: QueryObserver<TData, TFactory>, query: Query<TData, TFactory>, options: Options): Result<TData>;
83
+ /** Observe `query` with `options` from now on. */
84
+ export declare function setOptionsUnsafe<TData, TFactory extends QueryFactory>(self: QueryObserver<TData, TFactory>, query: Query<TData, TFactory>, options: Options): void;
85
+ /** Listen for result changes. The first listener subscribes to the query. */
86
+ export declare function subscribeUnsafe<TData, TFactory extends QueryFactory>(self: QueryObserver<TData, TFactory>, listener: () => void): () => void;
87
+ /**
88
+ * The current result, then every change listeners are notified of. Counts
89
+ * as a listener while the stream runs, so the query counts as read.
90
+ */
91
+ export declare function changes<TData, TFactory extends QueryFactory>(self: QueryObserver<TData, TFactory>): Stream.Stream<Result<TData>>;
92
+ /**
93
+ * Wrap `result` so that reading a property marks it tracked. Once any
94
+ * property is tracked, listeners are only notified when a tracked one
95
+ * changes.
96
+ */
97
+ export declare function trackResult<TData, TFactory extends QueryFactory, TResult extends Result<TData>>(self: QueryObserver<TData, TFactory>, result: TResult): TResult;
98
+ /** Mark `name` as read, for bindings that track access themselves. */
99
+ export declare function trackProp<TData, TFactory extends QueryFactory>(self: QueryObserver<TData, TFactory>, name: ResultKey): void;
100
+ //#endregion
101
+ export { observer_d_exports };