@raf-query/core 0.11.0 → 0.12.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.
- package/CHANGELOG.md +18 -2
- package/README.md +28 -18
- package/dist/client.d.mts +132 -119
- package/dist/client.mjs +59 -162
- package/dist/entry.d.mts +67 -148
- package/dist/entry.mjs +303 -474
- package/dist/features/focus-refetch/focus-refetch.d.mts +15 -0
- package/dist/features/focus-refetch/focus-refetch.mjs +18 -0
- package/dist/features/polling/polling.d.mts +12 -0
- package/dist/features/polling/polling.mjs +35 -0
- package/dist/features/reconnect-refetch/reconnect-refetch.d.mts +15 -0
- package/dist/features/reconnect-refetch/reconnect-refetch.mjs +14 -0
- package/dist/features/shared.mjs +13 -0
- package/dist/guards.mjs +23 -0
- package/dist/hydration/hydration.d.mts +73 -0
- package/dist/hydration/hydration.mjs +128 -0
- package/dist/index.d.mts +18 -13
- package/dist/index.mjs +15 -13
- package/dist/infinite/infinite.d.mts +71 -0
- package/dist/infinite/infinite.mjs +88 -0
- package/dist/internal.d.mts +13 -0
- package/dist/internal.mjs +7 -0
- package/dist/keys/keys.d.mts +46 -0
- package/dist/keys/keys.mjs +53 -0
- package/dist/keys/targets.d.mts +56 -0
- package/dist/keys/targets.mjs +85 -0
- package/dist/links/retry/retry.d.mts +26 -0
- package/dist/links/retry/retry.mjs +51 -0
- package/dist/links/timeout/timeout.d.mts +9 -0
- package/dist/links/timeout/timeout.mjs +27 -0
- package/dist/load.d.mts +124 -0
- package/dist/load.mjs +165 -0
- package/dist/options.d.mts +112 -338
- package/dist/options.mjs +25 -36
- package/dist/presets.d.mts +29 -0
- package/dist/presets.mjs +25 -0
- package/dist/root.d.mts +82 -0
- package/dist/structural-sharing/structural-sharing.mjs +32 -0
- package/dist/subscribe.d.mts +15 -0
- package/dist/subscribe.mjs +29 -0
- package/dist/thenable.d.mts +13 -17
- package/dist/thenable.mjs +25 -6
- package/dist/verbs.d.mts +109 -0
- package/dist/verbs.mjs +87 -0
- package/package.json +13 -9
- package/dist/_virtual/_rolldown/runtime.mjs +0 -13
- package/dist/all-keyed.d.mts +0 -18
- package/dist/all-keyed.mjs +0 -19
- package/dist/binding.d.mts +0 -92
- package/dist/binding.mjs +0 -186
- package/dist/cache.d.mts +0 -164
- package/dist/cache.mjs +0 -184
- package/dist/controls.d.mts +0 -93
- package/dist/controls.mjs +0 -69
- package/dist/hydration.d.mts +0 -84
- package/dist/hydration.mjs +0 -148
- package/dist/infinite.d.mts +0 -5
- package/dist/infinite.mjs +0 -103
- package/dist/key.d.mts +0 -18
- package/dist/key.mjs +0 -36
- package/dist/listen.mjs +0 -19
- package/dist/notify.mjs +0 -21
- package/dist/observer.d.mts +0 -110
- package/dist/observer.mjs +0 -300
- package/dist/platform.d.mts +0 -27
- package/dist/platform.mjs +0 -44
- package/dist/query.d.mts +0 -137
- package/dist/query.mjs +0 -147
- package/dist/request.mjs +0 -47
- package/dist/retry.mjs +0 -25
- package/dist/share.mjs +0 -40
package/CHANGELOG.md
CHANGED
|
@@ -1,14 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.12.0](https://github.com/KATT/raf-query/compare/0.11.1...0.12.0) (2026-11-08)
|
|
4
|
+
|
|
5
|
+
### ⚠ BREAKING CHANGES
|
|
6
|
+
|
|
7
|
+
* greenfield rewrite on a promise kernel, with load() handles and opt-in behaviors ([#230](https://github.com/KATT/raf-query/issues/230))
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* greenfield rewrite on a promise kernel, with load() handles and opt-in behaviors ([#230](https://github.com/KATT/raf-query/issues/230)) ([e2a6f85](https://github.com/KATT/raf-query/commit/e2a6f85297992dc8a5cdd3f871a54452393ae978))
|
|
12
|
+
|
|
13
|
+
## [0.11.1](https://github.com/KATT/raf-query/compare/0.11.0...0.11.1) (2026-10-17)
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
- **core:** take throwOnError from the query's options (2/2) ([#214](https://github.com/KATT/raf-query/issues/214)) ([ef1f0fd](https://github.com/KATT/raf-query/commit/ef1f0fd8eda2a73f4c4c268e8a3a5cf91a0ba0d8)), references [#448](https://github.com/KATT/raf-query/issues/448)
|
|
18
|
+
|
|
3
19
|
## [0.11.0](https://github.com/KATT/raf-query/compare/0.10.4...0.11.0) (2026-10-16)
|
|
4
20
|
|
|
5
21
|
### ⚠ BREAKING CHANGES
|
|
6
22
|
|
|
7
|
-
|
|
23
|
+
- **core:** enabled replaces ssr, as a boolean like TanStack Query's ([#204](https://github.com/KATT/raf-query/issues/204))
|
|
8
24
|
|
|
9
25
|
### Features
|
|
10
26
|
|
|
11
|
-
|
|
27
|
+
- **core:** enabled replaces ssr, as a boolean like TanStack Query's ([#204](https://github.com/KATT/raf-query/issues/204)) ([b7e543b](https://github.com/KATT/raf-query/commit/b7e543b9d1d34e02061740f749f2ddabcdee2ad2))
|
|
12
28
|
|
|
13
29
|
## [0.10.4](https://github.com/KATT/raf-query/compare/0.10.3...0.10.4) (2026-10-16)
|
|
14
30
|
|
package/README.md
CHANGED
|
@@ -1,47 +1,57 @@
|
|
|
1
1
|
# @raf-query/core
|
|
2
2
|
|
|
3
|
-
The framework-agnostic
|
|
3
|
+
The framework-agnostic query client behind [raf-query](https://raf-query.katt.dev): a small cache that dedupes requests, keeps entries fresh, and hands out serializable handles. Retries, timeouts, refetch on focus or reconnect, polling and hydration are opt-in links, features and exports, so a client bundles only what it turns on.
|
|
4
4
|
|
|
5
|
-
**[Documentation](https://raf-query.katt.dev
|
|
5
|
+
**[Documentation](https://raf-query.katt.dev)** · [API reference](https://raf-query.katt.dev/docs/api-reference#raf-querycore) · [GitHub](https://github.com/KATT/raf-query)
|
|
6
6
|
|
|
7
7
|
> [!NOTE]
|
|
8
8
|
> raf-query is early. The API can still change between minor releases, and you use it at your own risk.
|
|
9
9
|
|
|
10
|
-
React apps install [`@raf-query/react`](https://www.npmjs.com/package/@raf-query/react) instead: it re-exports this package, so you never import core directly. Install core on its own for
|
|
10
|
+
React apps install [`@raf-query/react`](https://www.npmjs.com/package/@raf-query/react) instead: it re-exports this package, so you never import core directly. Install core on its own for scripts, tests and bindings to other frameworks.
|
|
11
11
|
|
|
12
12
|
## Install
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
npm install @raf-query/core
|
|
15
|
+
npm install @raf-query/core
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
## Usage
|
|
19
19
|
|
|
20
20
|
```ts
|
|
21
|
-
import {
|
|
22
|
-
|
|
21
|
+
import {
|
|
22
|
+
createQueryClient,
|
|
23
|
+
group,
|
|
24
|
+
invalidateOne,
|
|
25
|
+
load,
|
|
26
|
+
queryOptions,
|
|
27
|
+
retry,
|
|
28
|
+
revalidate,
|
|
29
|
+
} from "@raf-query/core";
|
|
30
|
+
|
|
31
|
+
const issues = group("issues");
|
|
23
32
|
|
|
24
33
|
export function issueQuery(input: { id: number }) {
|
|
25
34
|
return queryOptions({
|
|
26
|
-
queryKey:
|
|
27
|
-
queryFn: () =>
|
|
28
|
-
retry: Schedule.exponential("200 millis").pipe(Schedule.upTo({ times: 3 })),
|
|
35
|
+
queryKey: issues("byId", input.id),
|
|
36
|
+
queryFn: () => getIssue(input.id),
|
|
29
37
|
});
|
|
30
38
|
}
|
|
31
39
|
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
Stream.map((state) => state.data?.title),
|
|
37
|
-
Stream.runForEach((title) => Effect.log(title)),
|
|
38
|
-
);
|
|
40
|
+
const queryClient = createQueryClient({ links: [retry()], staleTime: 5000 });
|
|
41
|
+
|
|
42
|
+
const [, data] = await load(queryClient, {
|
|
43
|
+
issue: revalidate(issueQuery, { id: 1 }),
|
|
39
44
|
});
|
|
45
|
+
console.log(data.issue.title);
|
|
40
46
|
|
|
41
|
-
|
|
47
|
+
await invalidateOne(queryClient, issueQuery, { id: 1 });
|
|
42
48
|
```
|
|
43
49
|
|
|
44
|
-
|
|
50
|
+
## Entry points
|
|
51
|
+
|
|
52
|
+
| Import | Use |
|
|
53
|
+
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
54
|
+
| `@raf-query/core` | `createQueryClient`, query factories, `load`, the verbs, the built-in links and features, `group`, `dehydrate` and `hydrate` |
|
|
45
55
|
|
|
46
56
|
## License
|
|
47
57
|
|
package/dist/client.d.mts
CHANGED
|
@@ -1,128 +1,141 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import { QueryKey } from "./keys/keys.mjs";
|
|
2
|
+
import { AnyPart, AnyRoot, Decl, Declares, MetaOf, MetaOfDecl, RootTypes } from "./root.mjs";
|
|
3
|
+
import { Stamp } from "./keys/targets.mjs";
|
|
4
|
+
import { QueryOptions } from "./options.mjs";
|
|
5
|
+
import { Listeners, SubscribeOptions } from "./subscribe.mjs";
|
|
6
|
+
import { Entry } from "./entry.mjs";
|
|
6
7
|
//#region src/client.d.ts
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
8
|
+
type LinkFunction = (op: Operation, next: (op: Operation) => Promise<unknown>) => Promise<unknown>;
|
|
9
|
+
type FeatureFunction = (client: QueryClient, signal: AbortSignal) => void;
|
|
10
|
+
/** Any link, whatever `meta` it reads. */
|
|
11
|
+
export type AnyLink = LinkFunction & AnyPart;
|
|
12
|
+
/** Any feature, whatever `meta` it reads. */
|
|
13
|
+
export type AnyFeature = FeatureFunction & AnyPart;
|
|
14
|
+
/** Time and timers, injectable so tests control them without patching globals. */
|
|
15
|
+
export interface Clock {
|
|
16
|
+
clearTimeout(handle: unknown): void;
|
|
17
|
+
now(): number;
|
|
18
|
+
setTimeout(callback: () => void, ms: number): unknown;
|
|
19
|
+
}
|
|
20
|
+
export declare const systemClock: Clock;
|
|
21
|
+
/** One request, as links see it. `TMeta` is what the link declares. */
|
|
22
|
+
export interface Operation<TMeta extends object = object> {
|
|
23
|
+
readonly client: QueryClient;
|
|
24
|
+
/** The entry's data when the request started (paged queries extend it). */
|
|
25
|
+
readonly data: unknown;
|
|
26
|
+
/** For paged queries: which page to add. */
|
|
27
|
+
readonly direction: "next" | "previous" | undefined;
|
|
28
|
+
readonly hasData: boolean;
|
|
29
|
+
readonly hash: string;
|
|
30
|
+
readonly input: unknown;
|
|
31
|
+
/** The query's `meta`, `{}` when it has none. */
|
|
32
|
+
readonly meta: TMeta;
|
|
33
|
+
readonly options: QueryOptions;
|
|
34
|
+
/** For paged queries: how many pages a first load fetches. */
|
|
35
|
+
readonly pages: number | undefined;
|
|
36
|
+
readonly queryKey: QueryKey;
|
|
37
|
+
readonly signal: AbortSignal;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Wraps every request, like a tRPC link. Call `next(op)` to run the rest of
|
|
41
|
+
* the chain; the last link is the query's own `queryFn`. `TDecl` is the
|
|
42
|
+
* `meta` it reads: see {@link link}.
|
|
43
|
+
*/
|
|
44
|
+
export type Link<TDecl extends Decl = Decl> = LinkFunction & Declares<TDecl>;
|
|
45
|
+
/**
|
|
46
|
+
* A cache-wide behavior, started when a provider mounts the client and
|
|
47
|
+
* stopped by `signal`. `TDecl` is the `meta` it reads: see {@link feature}.
|
|
48
|
+
*/
|
|
49
|
+
export type Feature<TDecl extends Decl = Decl> = FeatureFunction & Declares<TDecl>;
|
|
50
|
+
/**
|
|
51
|
+
* A link that declares the `meta` fields it reads. A root's queries take
|
|
52
|
+
* those fields only when the root has the link.
|
|
53
|
+
*
|
|
54
|
+
* ```ts
|
|
55
|
+
* const traced = link<{ meta: { traceId?: string } }>((op, next) =>
|
|
56
|
+
* withTrace(op.meta.traceId, () => next(op)),
|
|
57
|
+
* );
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export declare function link<TDecl extends Decl = Decl>(run: (op: Operation<MetaOfDecl<TDecl> & object>, next: (op: Operation) => Promise<unknown>) => Promise<unknown>): Link<TDecl>;
|
|
61
|
+
/**
|
|
62
|
+
* A feature that declares the `meta` fields it reads. Without `setup`, it
|
|
63
|
+
* only declares them: a root's own fields, such as an `owner` its targets
|
|
64
|
+
* match on.
|
|
65
|
+
*
|
|
66
|
+
* ```ts
|
|
67
|
+
* const ownership = feature<{ meta: { owner?: "posts-team" | "users-team" } }>();
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export declare function feature<TDecl extends Decl = Decl>(setup?: FeatureFunction): Feature<TDecl>;
|
|
71
|
+
export type ClientEvent = {
|
|
72
|
+
readonly entry: Entry;
|
|
73
|
+
readonly type: "added" | "removed" | "updated";
|
|
74
|
+
} | {
|
|
75
|
+
readonly entry: Entry;
|
|
76
|
+
readonly type: "readers";
|
|
77
|
+
};
|
|
78
|
+
export interface QueryClientConfig {
|
|
79
|
+
readonly clock?: Clock;
|
|
80
|
+
/** Behaviors started while a provider has the client mounted. */
|
|
81
|
+
readonly features?: ReadonlyArray<AnyFeature>;
|
|
82
|
+
/** Default `gcTime`: 5 minutes. Server clients never gc. */
|
|
83
|
+
readonly gcTime?: number;
|
|
84
|
+
/** Default: no `window`. Server clients never gc and never revalidate cached data. */
|
|
85
|
+
readonly isServer?: boolean;
|
|
86
|
+
/** Wrap every request, outermost first. */
|
|
87
|
+
readonly links?: ReadonlyArray<AnyLink>;
|
|
88
|
+
/** Default `staleTime`: `0`. */
|
|
89
|
+
readonly staleTime?: number;
|
|
90
|
+
/** Default `structuralSharing`: `true`. */
|
|
91
|
+
readonly structuralSharing?: boolean;
|
|
24
92
|
}
|
|
25
93
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* services of `config.layer`.
|
|
94
|
+
* A query cache. `TTypes` is its root's types, which factories built with
|
|
95
|
+
* `bindQueryOptions<typeof makeQueryClient>()` are checked against. The bare
|
|
96
|
+
* `QueryClient` type takes a client of any root.
|
|
30
97
|
*/
|
|
31
|
-
export
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
readonly
|
|
98
|
+
export interface QueryClient<TTypes extends RootTypes = AnyRoot> {
|
|
99
|
+
/** Entries changed since the last notification. */
|
|
100
|
+
readonly "~changed": Set<Entry>;
|
|
101
|
+
readonly "~entries": Map<string, Entry>;
|
|
102
|
+
readonly "~events": Listeners<ClientEvent>;
|
|
103
|
+
/** Orders requests and invalidations. */
|
|
104
|
+
"~seq": number;
|
|
105
|
+
/** Invalidations not yet older than every entry's data. */
|
|
106
|
+
"~stamps": Array<Stamp>;
|
|
107
|
+
/** Phantom: the root's types, invariant. `undefined` at runtime. */
|
|
108
|
+
readonly "~types"?: {
|
|
109
|
+
readonly root: (types: TTypes) => TTypes;
|
|
110
|
+
};
|
|
111
|
+
readonly clock: Clock;
|
|
112
|
+
readonly config: QueryClientConfig;
|
|
35
113
|
readonly isServer: boolean;
|
|
36
|
-
/**
|
|
37
|
-
|
|
38
|
-
* {@link QueryClientConfig.throwOnError}.
|
|
39
|
-
*/
|
|
40
|
-
readonly throwOnError: boolean;
|
|
41
|
-
constructor(config?: QueryClientConfig<TServices>);
|
|
42
|
-
/**
|
|
43
|
-
* The cache entry for a factory call, created if missing. Doesn't fetch.
|
|
44
|
-
* For bindings; apps use the verbs.
|
|
45
|
-
*/
|
|
46
|
-
getQuery<TFactory extends QueryFactory<TServices>>(factory: TFactory, ...args: InputArgs<TFactory>): Query<TFactory>;
|
|
47
|
-
/**
|
|
48
|
-
* Start a query without waiting and return its handle. Fresh data is
|
|
49
|
-
* reused; stale data refetches in the background (in the browser only).
|
|
50
|
-
* Errors surface where the data is read, never here.
|
|
51
|
-
*/
|
|
52
|
-
prefetch<TFactory extends QueryFactory<TServices>>(factory: TFactory, ...args: InputArgs<TFactory>): QueryHandle<TFactory>;
|
|
53
|
-
/**
|
|
54
|
-
* {@link fetch} without waiting on stale data: start a query and wait for
|
|
55
|
-
* it on a cache miss. Stale cached data resolves immediately and
|
|
56
|
-
* revalidates in the browser. Rejects if that request fails.
|
|
57
|
-
*/
|
|
58
|
-
ensure<TFactory extends QueryFactory<TServices>>(factory: TFactory, ...args: InputArgs<TFactory>): Promise<QueryHandleWithData<TFactory>>;
|
|
59
|
-
/** {@link ensure}, loading `options.pages` pages of a paged query. */
|
|
60
|
-
ensure<TFactory extends PagedQueryFactory<TServices>>(factory: TFactory, input: InputOf<TFactory>, options: LoadOptions): Promise<QueryHandleWithData<TFactory>>;
|
|
61
|
-
/**
|
|
62
|
-
* Start a query and resolve to a handle holding its data. Waits on a cache
|
|
63
|
-
* miss and on stale data (on the server, only invalidated data), so the
|
|
64
|
-
* data is fresh. Rejects if that request fails.
|
|
65
|
-
*/
|
|
66
|
-
fetch<TFactory extends QueryFactory<TServices>>(factory: TFactory, ...args: InputArgs<TFactory>): Promise<QueryHandleWithData<TFactory>>;
|
|
67
|
-
/** {@link fetch}, loading `options.pages` pages of a paged query. */
|
|
68
|
-
fetch<TFactory extends PagedQueryFactory<TServices>>(factory: TFactory, input: InputOf<TFactory>, options: LoadOptions): Promise<QueryHandleWithData<TFactory>>;
|
|
69
|
-
/**
|
|
70
|
-
* Mark queries stale and refetch the ones being read. Resolves once those
|
|
71
|
-
* refetches settle, so an action stays pending until fresh data is on
|
|
72
|
-
* screen. Takes a factory and its input, or a {@link QueryCache.Filter}.
|
|
73
|
-
*/
|
|
74
|
-
invalidate<TFactory extends QueryFactory<TServices>>(...target: Target<TFactory>): Promise<void>;
|
|
75
|
-
/**
|
|
76
|
-
* Drop queries' data and errors, as if they were never fetched. Read ones
|
|
77
|
-
* suspend and refetch; resolves once those refetches settle.
|
|
78
|
-
*/
|
|
79
|
-
reset<TFactory extends QueryFactory<TServices>>(...target: Target<TFactory>): Promise<void>;
|
|
80
|
-
/**
|
|
81
|
-
* Remove queries from the cache and abort their requests. Readers keep the
|
|
82
|
-
* removed entry until they render again.
|
|
83
|
-
*/
|
|
84
|
-
remove<TFactory extends QueryFactory<TServices>>(...target: Target<TFactory>): void;
|
|
85
|
-
/** Write data for a factory call. Readers re-render without suspending. */
|
|
86
|
-
setData<TFactory extends QueryFactory<TServices>>(factory: TFactory, input: InputOf<TFactory>, updater: Updater<DataOf<TFactory>>): void;
|
|
87
|
-
/**
|
|
88
|
-
* Cached data for a factory call, or `undefined` if there is none. Data can
|
|
89
|
-
* itself be `undefined`; `Query.getUnsafe(getQuery(...)).hasData` tells
|
|
90
|
-
* them apart.
|
|
91
|
-
*/
|
|
92
|
-
getData<TFactory extends QueryFactory<TServices>>(factory: TFactory, ...args: InputArgs<TFactory>): DataOf<TFactory> | undefined;
|
|
93
|
-
/** Every entry, for devtools and integrations. */
|
|
94
|
-
getAll(): ReadonlyArray<Query>;
|
|
95
|
-
/**
|
|
96
|
-
* How many entries have a request in flight: all of them, or those
|
|
97
|
-
* matching `filter`. For global loading indicators.
|
|
98
|
-
*/
|
|
99
|
-
isFetching(filter?: Filter): number;
|
|
100
|
-
/** Listen to entries being added, updated, and removed. */
|
|
101
|
-
subscribe(listener: (event: Event) => void): () => void;
|
|
102
|
-
/** Drop every entry and abort in-flight requests. */
|
|
103
|
-
clear(): void;
|
|
104
|
-
/**
|
|
105
|
-
* Start refetching read, stale queries on window focus and reconnect.
|
|
106
|
-
* Returns the matching unmount. Providers call this; mounts are counted.
|
|
107
|
-
*/
|
|
108
|
-
mount(): () => void;
|
|
109
|
-
/**
|
|
110
|
-
* Run an Effect with the cache (`QueryCache`) and the services of
|
|
111
|
-
* `config.layer`, as `Effect.runPromise` does: it rejects with the
|
|
112
|
-
* Effect's failure, or its defect. For an app's loaders and actions.
|
|
113
|
-
*/
|
|
114
|
-
runPromise<A, E>(effect: Effect.Effect<A, E, QueryCache | TServices>): Promise<A>;
|
|
115
|
-
/**
|
|
116
|
-
* Close the client: interrupt every request and timer, and release the
|
|
117
|
-
* layer's services. Resolves once the layer's finalizers have run.
|
|
118
|
-
*/
|
|
119
|
-
dispose(): Promise<void>;
|
|
114
|
+
/** Cache events: entries added, updated, removed, or gaining and losing readers. */
|
|
115
|
+
subscribe(listener: (event: ClientEvent) => void, options?: SubscribeOptions): () => void;
|
|
120
116
|
}
|
|
121
|
-
/** Snapshot the cache for transfer to another client (server → browser). */
|
|
122
|
-
export declare function dehydrate(client: QueryClient, options?: DehydrateOptions): DehydratedState;
|
|
123
117
|
/**
|
|
124
|
-
*
|
|
125
|
-
*
|
|
118
|
+
* Create a query client. Behaviors are opt-in: pass `links` and `features`,
|
|
119
|
+
* or spread `recommended()`. The client's `meta` type is what they declare.
|
|
120
|
+
*
|
|
121
|
+
* ```ts
|
|
122
|
+
* const ownership = feature<{ meta: { owner?: "posts-team" | "users-team" } }>();
|
|
123
|
+
* export const makeQueryClient = () =>
|
|
124
|
+
* createQueryClient({ links: [retry()], features: [ownership], staleTime: 30_000 });
|
|
125
|
+
* ```
|
|
126
|
+
*
|
|
127
|
+
* Make one per request on the server (in a loader or a request-scoped
|
|
128
|
+
* cache), never at module level.
|
|
129
|
+
*/
|
|
130
|
+
export declare function createQueryClient<const TLinks extends ReadonlyArray<AnyLink> = [], const TFeatures extends ReadonlyArray<AnyFeature> = []>(config?: QueryClientConfig & {
|
|
131
|
+
readonly features?: TFeatures;
|
|
132
|
+
readonly links?: TLinks;
|
|
133
|
+
}): QueryClient<{
|
|
134
|
+
readonly meta: MetaOf<[...TLinks, ...TFeatures]>;
|
|
135
|
+
}>;
|
|
136
|
+
/**
|
|
137
|
+
* Start the client's features. Providers call it while mounted; the
|
|
138
|
+
* returned function stops them.
|
|
126
139
|
*/
|
|
127
|
-
export declare function
|
|
140
|
+
export declare function mount(client: QueryClient): () => void;
|
|
128
141
|
//#endregion
|
package/dist/client.mjs
CHANGED
|
@@ -1,176 +1,73 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Context, Effect, Exit, Layer, Option, Predicate, Scope } from "effect";
|
|
1
|
+
import { listeners } from "./subscribe.mjs";
|
|
3
2
|
//#region src/client.ts
|
|
3
|
+
const systemClock = {
|
|
4
|
+
clearTimeout: (handle) => {
|
|
5
|
+
clearTimeout(handle);
|
|
6
|
+
},
|
|
7
|
+
now: () => Date.now(),
|
|
8
|
+
setTimeout: (callback, ms) => setTimeout(callback, ms)
|
|
9
|
+
};
|
|
4
10
|
/**
|
|
5
|
-
* A
|
|
6
|
-
*
|
|
7
|
-
*
|
|
11
|
+
* A link that declares the `meta` fields it reads. A root's queries take
|
|
12
|
+
* those fields only when the root has the link.
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* const traced = link<{ meta: { traceId?: string } }>((op, next) =>
|
|
16
|
+
* withTrace(op.meta.traceId, () => next(op)),
|
|
17
|
+
* );
|
|
18
|
+
* ```
|
|
8
19
|
*/
|
|
9
|
-
function
|
|
10
|
-
return
|
|
20
|
+
function link(run) {
|
|
21
|
+
return run;
|
|
11
22
|
}
|
|
23
|
+
const noop = () => {};
|
|
12
24
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
25
|
+
* A feature that declares the `meta` fields it reads. Without `setup`, it
|
|
26
|
+
* only declares them: a root's own fields, such as an `owner` its targets
|
|
27
|
+
* match on.
|
|
28
|
+
*
|
|
29
|
+
* ```ts
|
|
30
|
+
* const ownership = feature<{ meta: { owner?: "posts-team" | "users-team" } }>();
|
|
31
|
+
* ```
|
|
16
32
|
*/
|
|
17
|
-
function
|
|
18
|
-
return
|
|
19
|
-
throw Predicate.isTagged(error, "CancelledError") ? new DOMException("The query was cancelled", "AbortError") : error;
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
/** `ensure` / `fetch` arguments, forwarded to the cache's same verb. */
|
|
23
|
-
function loadArgs(input, options) {
|
|
24
|
-
return [input, options];
|
|
33
|
+
function feature(setup = noop) {
|
|
34
|
+
return setup;
|
|
25
35
|
}
|
|
26
36
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
37
|
+
* Create a query client. Behaviors are opt-in: pass `links` and `features`,
|
|
38
|
+
* or spread `recommended()`. The client's `meta` type is what they declare.
|
|
39
|
+
*
|
|
40
|
+
* ```ts
|
|
41
|
+
* const ownership = feature<{ meta: { owner?: "posts-team" | "users-team" } }>();
|
|
42
|
+
* export const makeQueryClient = () =>
|
|
43
|
+
* createQueryClient({ links: [retry()], features: [ownership], staleTime: 30_000 });
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* Make one per request on the server (in a loader or a request-scoped
|
|
47
|
+
* cache), never at module level.
|
|
31
48
|
*/
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
#context;
|
|
46
|
-
/**
|
|
47
|
-
* Owns the cache's fibers, mounts and the layer's services; lives until
|
|
48
|
-
* `dispose()`.
|
|
49
|
-
*/
|
|
50
|
-
#scope = Scope.makeUnsafe();
|
|
51
|
-
constructor(config = {}) {
|
|
52
|
-
const cache = layer(config);
|
|
53
|
-
const services = Effect.runSync(Layer.buildWithScope(config.layer ? Layer.provideMerge(cache, config.layer) : cache, this.#scope));
|
|
54
|
-
this.#context = services;
|
|
55
|
-
this.effect = Context.get(services, QueryCache);
|
|
56
|
-
this.isServer = this.effect.isServer;
|
|
57
|
-
this.throwOnError = config.throwOnError ?? config.layer === void 0;
|
|
58
|
-
const onError = config.onError;
|
|
59
|
-
if (onError) this.effect.subscribeUnsafe((event) => {
|
|
60
|
-
if (event.type === "failed") onError(event.error, event.query);
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* The cache entry for a factory call, created if missing. Doesn't fetch.
|
|
65
|
-
* For bindings; apps use the verbs.
|
|
66
|
-
*/
|
|
67
|
-
getQuery(factory, ...args) {
|
|
68
|
-
return this.effect.getQueryUnsafe(factory, ...args);
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Start a query without waiting and return its handle. Fresh data is
|
|
72
|
-
* reused; stale data refetches in the background (in the browser only).
|
|
73
|
-
* Errors surface where the data is read, never here.
|
|
74
|
-
*/
|
|
75
|
-
prefetch(factory, ...args) {
|
|
76
|
-
return Effect.runSync(provided(this.effect.prefetch(factory, ...args)));
|
|
77
|
-
}
|
|
78
|
-
ensure(factory, input, options) {
|
|
79
|
-
return runLoad(provided(this.effect.ensure(factory, ...loadArgs(input, options))));
|
|
80
|
-
}
|
|
81
|
-
fetch(factory, input, options) {
|
|
82
|
-
return runLoad(provided(this.effect.fetch(factory, ...loadArgs(input, options))));
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Mark queries stale and refetch the ones being read. Resolves once those
|
|
86
|
-
* refetches settle, so an action stays pending until fresh data is on
|
|
87
|
-
* screen. Takes a factory and its input, or a {@link QueryCache.Filter}.
|
|
88
|
-
*/
|
|
89
|
-
invalidate(...target) {
|
|
90
|
-
return Effect.runPromise(this.effect.invalidate(...target));
|
|
91
|
-
}
|
|
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(...target) {
|
|
97
|
-
return Effect.runPromise(this.effect.reset(...target));
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Remove queries from the cache and abort their requests. Readers keep the
|
|
101
|
-
* removed entry until they render again.
|
|
102
|
-
*/
|
|
103
|
-
remove(...target) {
|
|
104
|
-
Effect.runSync(this.effect.remove(...target));
|
|
105
|
-
}
|
|
106
|
-
/** Write data for a factory call. Readers re-render without suspending. */
|
|
107
|
-
setData(factory, input, updater) {
|
|
108
|
-
Effect.runSync(this.effect.setData(factory, input, updater));
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* Cached data for a factory call, or `undefined` if there is none. Data can
|
|
112
|
-
* itself be `undefined`; `Query.getUnsafe(getQuery(...)).hasData` tells
|
|
113
|
-
* them apart.
|
|
114
|
-
*/
|
|
115
|
-
getData(factory, ...args) {
|
|
116
|
-
return Option.getOrUndefined(Effect.runSync(this.effect.getData(factory, ...args)));
|
|
117
|
-
}
|
|
118
|
-
/** Every entry, for devtools and integrations. */
|
|
119
|
-
getAll() {
|
|
120
|
-
return Effect.runSync(this.effect.all);
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* How many entries have a request in flight: all of them, or those
|
|
124
|
-
* matching `filter`. For global loading indicators.
|
|
125
|
-
*/
|
|
126
|
-
isFetching(filter) {
|
|
127
|
-
return this.effect.isFetchingUnsafe(filter);
|
|
128
|
-
}
|
|
129
|
-
/** Listen to entries being added, updated, and removed. */
|
|
130
|
-
subscribe(listener) {
|
|
131
|
-
return this.effect.subscribeUnsafe(listener);
|
|
132
|
-
}
|
|
133
|
-
/** Drop every entry and abort in-flight requests. */
|
|
134
|
-
clear() {
|
|
135
|
-
Effect.runSync(this.effect.clear);
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Start refetching read, stale queries on window focus and reconnect.
|
|
139
|
-
* Returns the matching unmount. Providers call this; mounts are counted.
|
|
140
|
-
*/
|
|
141
|
-
mount() {
|
|
142
|
-
const scope = Scope.forkUnsafe(this.#scope);
|
|
143
|
-
Effect.runSync(this.effect.mount.pipe(Scope.provide(scope)));
|
|
144
|
-
return () => {
|
|
145
|
-
Effect.runSync(Scope.close(scope, Exit.void));
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* Run an Effect with the cache (`QueryCache`) and the services of
|
|
150
|
-
* `config.layer`, as `Effect.runPromise` does: it rejects with the
|
|
151
|
-
* Effect's failure, or its defect. For an app's loaders and actions.
|
|
152
|
-
*/
|
|
153
|
-
runPromise(effect) {
|
|
154
|
-
return Effect.runPromiseWith(this.#context)(effect);
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Close the client: interrupt every request and timer, and release the
|
|
158
|
-
* layer's services. Resolves once the layer's finalizers have run.
|
|
159
|
-
*/
|
|
160
|
-
dispose() {
|
|
161
|
-
return Effect.runPromise(Scope.close(this.#scope, Exit.void));
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
/** Snapshot the cache for transfer to another client (server → browser). */
|
|
165
|
-
function dehydrate(client, options) {
|
|
166
|
-
return Effect.runSync(client.effect.dehydrate(options));
|
|
49
|
+
function createQueryClient(config = {}) {
|
|
50
|
+
const events = listeners();
|
|
51
|
+
return {
|
|
52
|
+
"~changed": /* @__PURE__ */ new Set(),
|
|
53
|
+
"~entries": /* @__PURE__ */ new Map(),
|
|
54
|
+
"~events": events,
|
|
55
|
+
"~seq": 0,
|
|
56
|
+
"~stamps": [],
|
|
57
|
+
clock: config.clock ?? systemClock,
|
|
58
|
+
config,
|
|
59
|
+
isServer: config.isServer ?? !("window" in globalThis),
|
|
60
|
+
subscribe: (listener, options) => events.subscribe(listener, options)
|
|
61
|
+
};
|
|
167
62
|
}
|
|
168
63
|
/**
|
|
169
|
-
*
|
|
170
|
-
*
|
|
64
|
+
* Start the client's features. Providers call it while mounted; the
|
|
65
|
+
* returned function stops them.
|
|
171
66
|
*/
|
|
172
|
-
function
|
|
173
|
-
|
|
67
|
+
function mount(client) {
|
|
68
|
+
const controller = new AbortController();
|
|
69
|
+
for (const start of client.config.features ?? []) start(client, controller.signal);
|
|
70
|
+
return () => controller.abort();
|
|
174
71
|
}
|
|
175
72
|
//#endregion
|
|
176
|
-
export {
|
|
73
|
+
export { createQueryClient, feature, link, mount, systemClock };
|