@firsthandjs/data 0.5.0 → 0.6.1
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/README.md +63 -52
- package/dist/cache.d.ts +86 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/http.d.ts +59 -32
- package/dist/http.d.ts.map +1 -1
- package/dist/index.d.ts +14 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.dev.js +191 -30
- package/dist/index.js +2 -2
- package/dist/resource.d.ts.map +1 -1
- package/dist/store.d.ts +42 -10
- package/dist/store.d.ts.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
# @firsthandjs/data
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
state, and when it has to be loaded again.
|
|
3
|
+
Data that comes from outside the reactive graph, brought in as reactive state:
|
|
4
|
+
what is loaded, what state that is in, and when it has to be loaded again.
|
|
5
5
|
|
|
6
|
-
**Documentation:** [guide](https://github.com/firsthandjs/firsthand/blob/main/docs/guide/09-data.md) · [API reference](https://github.com/firsthandjs/firsthand/blob/main/docs/reference/data.md) · [ADR-0022](https://github.com/firsthandjs/firsthand/blob/main/docs/adr/0022-resources-not-a-cache.md)
|
|
6
|
+
**Documentation:** [guide](https://github.com/firsthandjs/firsthand/blob/main/docs/guide/09-data.md) · [API reference](https://github.com/firsthandjs/firsthand/blob/main/docs/reference/data.md) · [ADR-0022](https://github.com/firsthandjs/firsthand/blob/main/docs/adr/0022-resources-not-a-cache.md) · [ADR-0023](https://github.com/firsthandjs/firsthand/blob/main/docs/adr/0023-one-cache-at-the-transport-edge.md)
|
|
7
7
|
|
|
8
8
|
```
|
|
9
9
|
npm install @firsthandjs/data
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
3.65 kB gzip. It depends on `@firsthandjs/core` and `@firsthandjs/dom`.
|
|
13
13
|
|
|
14
14
|
```tsx
|
|
15
|
-
const
|
|
15
|
+
const api = createFetchClient({ baseUrl: '/api' });
|
|
16
|
+
|
|
17
|
+
const user = useResource(({ request, tags }) => {
|
|
16
18
|
tags(tag('user', { id: props.id }));
|
|
17
|
-
return
|
|
19
|
+
return api.get<User>(`/users/${props.id}`)(request);
|
|
18
20
|
});
|
|
19
21
|
|
|
20
22
|
return <h1>{user.data.value?.name ?? '…'}</h1>;
|
|
@@ -24,65 +26,74 @@ No key, no name, no variables object. `props.id` is read inside the loader, so
|
|
|
24
26
|
changing it runs the loader again and aborts what was in flight — exactly as an
|
|
25
27
|
`effect` behaves, and for the same reason.
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
A server is the common case, not the only one: a worker, IndexedDB, a
|
|
30
|
+
WebSocket, a computation too expensive to repeat are all the same shape.
|
|
28
31
|
|
|
29
|
-
|
|
30
|
-
finding the first one's result, and that needs identity: a key, a name,
|
|
31
|
-
something two callers agree on. Every form of that is a thing to forget or
|
|
32
|
-
collide on. A resource belongs to its call site instead, and two call sites are
|
|
33
|
-
two resources whatever their tags say.
|
|
32
|
+
## Two layers
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
**Reactivity** — `useResource`, `useAction`, tags — knows who is watching what,
|
|
35
|
+
what state it is in, and when it must run again. **Transport** — a client —
|
|
36
|
+
knows how to send one request. Between them is one object:
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
| Request cache | not asking twice | those clients, the HTTP cache |
|
|
43
|
-
| **Resources** | **reactive state, status, invalidation** | **this package** |
|
|
38
|
+
```ts
|
|
39
|
+
type Loader<T> = (request: { signal: AbortSignal; force: boolean }) => Promise<T>;
|
|
40
|
+
```
|
|
44
41
|
|
|
45
|
-
|
|
42
|
+
`signal` ends a request nobody wants. `force` says this run exists _because_
|
|
43
|
+
something was invalidated, so a cache may not answer it. That is the whole
|
|
44
|
+
contract, which is why a loader can be any function at all.
|
|
46
45
|
|
|
47
|
-
|
|
48
|
-
That is the feature — and it is safe, because they are not identity.
|
|
46
|
+
## The cache is the transport's
|
|
49
47
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
48
|
+
A cache answers "have I got this already?", which needs to know when two things
|
|
49
|
+
are the same thing. A resource belongs to its call site, so that knowledge does
|
|
50
|
+
not exist in the reactivity layer — but at the transport it is right there in
|
|
51
|
+
the request.
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
const cache = createCacheClient({ ttl: 30_000 });
|
|
55
|
+
|
|
56
|
+
// In front of a client…
|
|
57
|
+
const api = createFetchClient({ baseUrl: '/api', cache });
|
|
58
|
+
|
|
59
|
+
// …or in front of anything else, which is the other half of what it is for.
|
|
60
|
+
const report = useResource(({ request }) =>
|
|
61
|
+
cache.read(`report:${month.value}`, () => buildReport(month.value))(request),
|
|
62
|
+
);
|
|
61
63
|
```
|
|
62
64
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
One cache, both jobs. With no `ttl` it still shares what is in flight — ten
|
|
66
|
+
components asking at once make one request, which is waste removed rather than
|
|
67
|
+
staleness introduced. `force` drops the entry, which is how an invalidation
|
|
68
|
+
reaches all the way down.
|
|
66
69
|
|
|
67
|
-
##
|
|
70
|
+
## Tags are for invalidation
|
|
68
71
|
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
They may be coarse, they may overlap, and two unrelated sources may share one.
|
|
73
|
+
Nothing is ever looked up by them, which is what makes that safe.
|
|
71
74
|
|
|
72
75
|
```tsx
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
);
|
|
76
|
+
const rename = useAction((name: string, { request, invalidates }) => {
|
|
77
|
+
invalidates(tag('user', { id: props.id }), tag('users'));
|
|
78
|
+
return api.patch<User>(`/users/${props.id}`, { json: { name } })(request);
|
|
79
|
+
});
|
|
76
80
|
```
|
|
77
81
|
|
|
78
|
-
`
|
|
79
|
-
|
|
82
|
+
`tags()` **replaces**, so a run says what it is about rather than accumulating
|
|
83
|
+
what it used to be about — and it may be called after the answer, for the case
|
|
84
|
+
where only the server knows which user `/users/me` was.
|
|
80
85
|
|
|
81
86
|
## Bringing a client
|
|
82
87
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
`createFetchClient` is a small REST client on the browser's own `fetch`: a base
|
|
89
|
+
URL, headers read per request so a token may change, a failed status thrown,
|
|
90
|
+
the abort signal wired through, and the cache.
|
|
91
|
+
|
|
92
|
+
For anything else, [`@firsthandjs/data-axios`](https://www.npmjs.com/package/@firsthandjs/data-axios),
|
|
93
|
+
[`-urql`](https://www.npmjs.com/package/@firsthandjs/data-urql) and
|
|
94
|
+
[`-apollo`](https://www.npmjs.com/package/@firsthandjs/data-apollo) bind an
|
|
95
|
+
instance **you** built: your interceptors, your links, your authentication.
|
|
96
|
+
None of them depends on the client it binds, so none has a version to follow.
|
|
86
97
|
|
|
87
98
|
For GraphQL, tags come out of the `.gql` file as `@tag` / `@invalidates`
|
|
88
99
|
directives, read at build time by `@firsthandjs/data/vite` and typed by
|
|
@@ -91,9 +102,9 @@ object with the directives already removed.
|
|
|
91
102
|
|
|
92
103
|
## What it deliberately does not do
|
|
93
104
|
|
|
94
|
-
Interceptors, retries, backoff, token refresh,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
105
|
+
Interceptors, retries, backoff, token refresh, progress events, XSRF,
|
|
106
|
+
normalisation, optimistic cache surgery, pagination helpers. Each belongs to a
|
|
107
|
+
transport or to an application's own policy — and a loader takes any client,
|
|
108
|
+
because it takes any function.
|
|
98
109
|
|
|
99
110
|
MIT licensed.
|
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The cache — one, and the transport layer's, not the store's.
|
|
3
|
+
*
|
|
4
|
+
* ADR-0022 says resources are not a cache, and they are not: a resource
|
|
5
|
+
* belongs to its call site, and two call sites are two resources. What that
|
|
6
|
+
* argument leaves open is that the *transport* wants one, and an application
|
|
7
|
+
* that brings no client has nowhere to put it. This is that place.
|
|
8
|
+
*
|
|
9
|
+
* It is deliberately one cache for two jobs. `createFetchClient` keeps its
|
|
10
|
+
* responses here, and an algorithm of your own — an expensive computation, a
|
|
11
|
+
* worker round trip, a read out of IndexedDB — keeps its results here through
|
|
12
|
+
* exactly the same `read`. There is no second, hidden implementation inside
|
|
13
|
+
* the fetch client, because two caches with two sets of rules is the thing
|
|
14
|
+
* this project spent an ADR refusing.
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* const cache = createCacheClient({ ttl: 30_000 });
|
|
18
|
+
*
|
|
19
|
+
* const primes = useResource((context) =>
|
|
20
|
+
* cache.read(`primes:${limit.value}`, () => sieve(limit.value))(context.request),
|
|
21
|
+
* );
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* What it does, and the whole of it:
|
|
25
|
+
*
|
|
26
|
+
* - **Serves a fresh entry** without running the producer. Freshness is `ttl`,
|
|
27
|
+
* which is 0 by default: nothing is reused, but see the next point.
|
|
28
|
+
* - **Deduplicates what is in flight.** Two callers asking for one key while
|
|
29
|
+
* the request is out get one request and both get its answer — at any `ttl`,
|
|
30
|
+
* because two identical requests overlapping in time is waste rather than
|
|
31
|
+
* staleness.
|
|
32
|
+
* - **Honours `force`.** An invalidated resource reaches through: the entry is
|
|
33
|
+
* dropped, the producer runs, and the answer replaces what was there. This
|
|
34
|
+
* is the point where the store's invalidation and the transport's memory
|
|
35
|
+
* meet, and without it an invalidation would be answered out of the cache it
|
|
36
|
+
* was meant to defeat.
|
|
37
|
+
* - **Forgets.** `forget(key)`, `forget()` for all of it, and the oldest entry
|
|
38
|
+
* goes when `max` is reached.
|
|
39
|
+
*/
|
|
40
|
+
import type { Loader } from './store.js';
|
|
41
|
+
export interface CacheOptions {
|
|
42
|
+
/**
|
|
43
|
+
* How long an answer is served again without running the producer, in ms.
|
|
44
|
+
*
|
|
45
|
+
* Default 0: every run asks again, and only what is *in flight* is shared.
|
|
46
|
+
* That is the safe default — an application that has not thought about
|
|
47
|
+
* staleness does not silently get some.
|
|
48
|
+
*/
|
|
49
|
+
readonly ttl?: number;
|
|
50
|
+
/**
|
|
51
|
+
* How many entries to keep. The least recently read goes first. Default 100.
|
|
52
|
+
*
|
|
53
|
+
* A cache without a bound is a leak with a plan, and the number is here
|
|
54
|
+
* rather than in a comment because only the application knows what its
|
|
55
|
+
* entries weigh.
|
|
56
|
+
*/
|
|
57
|
+
readonly max?: number;
|
|
58
|
+
/** For tests: what `Date.now()` should be. */
|
|
59
|
+
readonly now?: () => number;
|
|
60
|
+
}
|
|
61
|
+
export interface CacheClient {
|
|
62
|
+
/**
|
|
63
|
+
* Wraps a producer so its answer is kept under `key`.
|
|
64
|
+
*
|
|
65
|
+
* The producer is given a request of the cache's own: its signal is aborted
|
|
66
|
+
* when *every* waiter has gone, so one component leaving does not cancel a
|
|
67
|
+
* request another is still waiting for.
|
|
68
|
+
*/
|
|
69
|
+
read<T>(key: string, produce: Loader<T>): Loader<T>;
|
|
70
|
+
/**
|
|
71
|
+
* Puts a value in directly — a push from a socket, a known first page.
|
|
72
|
+
*
|
|
73
|
+
* A value you hand over is not an answer that might already be stale, so
|
|
74
|
+
* with no `ttl` configured it stays until it is forgotten rather than
|
|
75
|
+
* expiring immediately.
|
|
76
|
+
*/
|
|
77
|
+
write(key: string, value: unknown): void;
|
|
78
|
+
/** Reads what is there without running anything. `undefined` if stale. */
|
|
79
|
+
peek(key: string): unknown;
|
|
80
|
+
/** Forgets one key, or everything. */
|
|
81
|
+
forget(key?: string): void;
|
|
82
|
+
/** How many entries are held, in flight included. */
|
|
83
|
+
readonly size: number;
|
|
84
|
+
}
|
|
85
|
+
export declare function createCacheClient(options?: CacheOptions): CacheClient;
|
|
86
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,OAAO,KAAK,EAAe,MAAM,EAAE,MAAM,YAAY,CAAC;AAEtD,MAAM,WAAW,YAAY;IAC3B;;;;;;OAMG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;OAMG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,8CAA8C;IAC9C,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,WAAW;IAC1B;;;;;;OAMG;IACH,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACpD;;;;;;OAMG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IACzC,0EAA0E;IAC1E,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,sCAAsC;IACtC,MAAM,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,qDAAqD;IACrD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAaD,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,YAAiB,GAAG,WAAW,CAkHzE"}
|
package/dist/http.d.ts
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* `fetch` is already the API; what a cache needs from it is an abort signal
|
|
5
|
-
* wired up and a failed status turned into a thrown error, because a promise
|
|
6
|
-
* that resolves with a 500 makes every caller write the same four lines.
|
|
7
|
-
*/
|
|
8
|
-
import type { LoadContext } from './store.js';
|
|
1
|
+
import { type CacheClient, type CacheOptions } from './cache.js';
|
|
2
|
+
import type { Loader } from './store.js';
|
|
9
3
|
/**
|
|
10
4
|
* Thrown for any response outside 2xx.
|
|
11
5
|
*
|
|
@@ -22,7 +16,7 @@ export declare class FirsthandHttpError extends Error {
|
|
|
22
16
|
body: unknown);
|
|
23
17
|
}
|
|
24
18
|
/**
|
|
25
|
-
* What
|
|
19
|
+
* What a call takes: `RequestInit`, plus the one thing the platform lacks.
|
|
26
20
|
*
|
|
27
21
|
* `method`, `credentials`, `mode`, `headers` and the rest are passed straight
|
|
28
22
|
* through. So is `body`: a `FormData`, a `URLSearchParams` or a `Blob` is
|
|
@@ -37,28 +31,61 @@ export interface JsonRequest extends Omit<RequestInit, 'body'> {
|
|
|
37
31
|
readonly body?: BodyInit | null;
|
|
38
32
|
/** Sent as JSON, with the content type the platform will not set for you. */
|
|
39
33
|
readonly json?: unknown;
|
|
34
|
+
/**
|
|
35
|
+
* Where this call's answer is kept: a key of your own, or `false` for
|
|
36
|
+
* nowhere.
|
|
37
|
+
*
|
|
38
|
+
* A key is for when the URL is not the identity — a POST that reads, say.
|
|
39
|
+
* `false` is for the read that must never be served from memory. The
|
|
40
|
+
* platform's own `cache` option is untouched and still passed to `fetch`,
|
|
41
|
+
* because that one is the HTTP cache and a different thing entirely.
|
|
42
|
+
*
|
|
43
|
+
* A *lifetime* belongs to the cache rather than to a call, so it is set once
|
|
44
|
+
* in `createFetchClient({ cache: { ttl } })`, or on a variation of the client
|
|
45
|
+
* made with `.with({ cache: { ttl } })`.
|
|
46
|
+
*/
|
|
47
|
+
readonly cacheKey?: string | false;
|
|
40
48
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
49
|
+
export interface FetchClientOptions {
|
|
50
|
+
/** Prefixed to every relative path. A path beginning with `http` is left alone. */
|
|
51
|
+
readonly baseUrl?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Headers for every request. A function is called **per request and
|
|
54
|
+
* untracked**, which is what lets a token change without making every
|
|
55
|
+
* resource depend on it.
|
|
56
|
+
*/
|
|
57
|
+
readonly headers?: HeadersInit | (() => HeadersInit);
|
|
58
|
+
/**
|
|
59
|
+
* Where answers are kept. `false` for none (the default), options for a
|
|
60
|
+
* cache of this client's own, or a `CacheClient` shared with the rest of the
|
|
61
|
+
* application.
|
|
62
|
+
*/
|
|
63
|
+
readonly cache?: false | CacheOptions | CacheClient;
|
|
64
|
+
/** Applied to every request: `credentials`, `mode`, `referrerPolicy`, … */
|
|
65
|
+
readonly init?: RequestInit;
|
|
66
|
+
/**
|
|
67
|
+
* The seam. Wrap `fetch` to log, to retry, or to end a session on a 401 —
|
|
68
|
+
* and keep that in one place rather than in a plugin system.
|
|
69
|
+
*/
|
|
70
|
+
readonly fetch?: typeof globalThis.fetch;
|
|
71
|
+
}
|
|
72
|
+
export interface FetchClient {
|
|
73
|
+
/** Any method: the general form the others are named shortcuts for. */
|
|
74
|
+
request<T>(url: string, init?: JsonRequest): Loader<T>;
|
|
75
|
+
get<T>(url: string, init?: JsonRequest): Loader<T>;
|
|
76
|
+
post<T>(url: string, init?: JsonRequest): Loader<T>;
|
|
77
|
+
put<T>(url: string, init?: JsonRequest): Loader<T>;
|
|
78
|
+
patch<T>(url: string, init?: JsonRequest): Loader<T>;
|
|
79
|
+
/** `delete` is a keyword in enough places to be worth avoiding. */
|
|
80
|
+
remove<T>(url: string, init?: JsonRequest): Loader<T>;
|
|
81
|
+
/**
|
|
82
|
+
* A copy with some options replaced: another base URL, other headers, no
|
|
83
|
+
* cache. The cache is shared unless this call replaces it, so a specialised
|
|
84
|
+
* client is a variation rather than a second application.
|
|
85
|
+
*/
|
|
86
|
+
with(options: FetchClientOptions): FetchClient;
|
|
87
|
+
/** The cache this client keeps its answers in, if it has one. */
|
|
88
|
+
readonly cache: CacheClient | undefined;
|
|
89
|
+
}
|
|
90
|
+
export declare function createFetchClient(options?: FetchClientOptions): FetchClient;
|
|
64
91
|
//# sourceMappingURL=http.d.ts.map
|
package/dist/http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AA8BA,OAAO,EAAqB,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AACpF,OAAO,KAAK,EAAe,MAAM,EAAE,MAAM,YAAY,CAAC;AAEtD;;;;;GAKG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAEzC,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM;IACpB,yCAAyC;IACzC,QAAQ,CAAC,IAAI,EAAE,OAAO;gBAHb,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM;IACpB,yCAAyC;IAChC,IAAI,EAAE,OAAO;CAKzB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,WAAY,SAAQ,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC;IAC5D,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;IAChC,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;CACpC;AAED,MAAM,WAAW,kBAAkB;IACjC,mFAAmF;IACnF,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,CAAC,MAAM,WAAW,CAAC,CAAC;IACrD;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,YAAY,GAAG,WAAW,CAAC;IACpD,2EAA2E;IAC3E,QAAQ,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC;IAC5B;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CAC1C;AAED,MAAM,WAAW,WAAW;IAC1B,uEAAuE;IACvE,OAAO,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACvD,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACnD,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACpD,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACnD,KAAK,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACrD,mEAAmE;IACnE,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACtD;;;;OAIG;IACH,IAAI,CAAC,OAAO,EAAE,kBAAkB,GAAG,WAAW,CAAC;IAC/C,iEAAiE;IACjE,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,CAAC;CACzC;AAmDD,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,kBAAuB,GAAG,WAAW,CAmD/E"}
|
package/dist/index.d.ts
CHANGED
|
@@ -9,18 +9,24 @@
|
|
|
9
9
|
export { createData, DataContext, useData, useInvalidate } from './resource.js';
|
|
10
10
|
export { useResource, useAction, fromObservable, fromPromise } from './resource.js';
|
|
11
11
|
export type { Action, ObservableLike, BridgeOptions, ResourceOptions } from './resource.js';
|
|
12
|
-
export type { ActionContext, DataOptions, DataStore, LoadContext, Resource, Status, Storage, } from './store.js';
|
|
12
|
+
export type { ActionContext, DataOptions, DataRequest, DataStore, LoadContext, Loader, Resource, Status, Storage, } from './store.js';
|
|
13
|
+
/**
|
|
14
|
+
* The cache — one of them, and the transport's.
|
|
15
|
+
*
|
|
16
|
+
* `createFetchClient` keeps its answers here, and so does an algorithm of your
|
|
17
|
+
* own: the same `read`, the same lifetimes, and the same `force` reaching
|
|
18
|
+
* through when a resource is invalidated (ADR-0023).
|
|
19
|
+
*/
|
|
20
|
+
export { createCacheClient } from './cache.js';
|
|
21
|
+
export type { CacheClient, CacheOptions } from './cache.js';
|
|
13
22
|
export { tag, tagMatches, anyTagMatches } from './tags.js';
|
|
14
23
|
export type { Tag, TagVars, Variables } from './tags.js';
|
|
15
24
|
export { parseGraphQL, resolveTags, FirsthandDirectiveError } from './document.js';
|
|
16
25
|
export type { DocumentArguments, GraphQLDocument, TagTemplate, TagValue } from './document.js';
|
|
17
26
|
/**
|
|
18
|
-
* `fetch`,
|
|
19
|
-
*
|
|
20
|
-
* The platform, not a vendor: no base URLs, no instances, no interceptors, no
|
|
21
|
-
* retry. Those belong to a client, and a loader takes any client because it
|
|
22
|
-
* takes any function.
|
|
27
|
+
* `fetch`, as a client configured once: a base URL, headers read per request
|
|
28
|
+
* so a token may change, a failed status thrown, and the shared cache.
|
|
23
29
|
*/
|
|
24
|
-
export {
|
|
25
|
-
export type { JsonRequest } from './http.js';
|
|
30
|
+
export { createFetchClient, FirsthandHttpError } from './http.js';
|
|
31
|
+
export type { FetchClient, FetchClientOptions, JsonRequest } from './http.js';
|
|
26
32
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAChF,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACpF,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAC5F,YAAY,EACV,aAAa,EACb,WAAW,EACX,SAAS,EACT,WAAW,EACX,QAAQ,EACR,MAAM,EACN,OAAO,GACR,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC3D,YAAY,EAAE,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEzD,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AACnF,YAAY,EAAE,iBAAiB,EAAE,eAAe,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE/F
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAChF,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACpF,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAC5F,YAAY,EACV,aAAa,EACb,WAAW,EACX,WAAW,EACX,SAAS,EACT,WAAW,EACX,MAAM,EACN,QAAQ,EACR,MAAM,EACN,OAAO,GACR,MAAM,YAAY,CAAC;AAEpB;;;;;;GAMG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE5D,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC3D,YAAY,EAAE,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEzD,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AACnF,YAAY,EAAE,iBAAiB,EAAE,eAAe,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE/F;;;GAGG;AACH,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAClE,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/index.dev.js
CHANGED
|
@@ -164,16 +164,14 @@ function useResource(load, options = {}) {
|
|
|
164
164
|
if (entry.data.peek() === void 0) {
|
|
165
165
|
entry.status.value = "loading";
|
|
166
166
|
}
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
entry.tags = next;
|
|
172
|
-
if (entry.pending.length > 0 && anyTagMatches(entry.pending, next)) {
|
|
173
|
-
entry.superseded = true;
|
|
174
|
-
}
|
|
167
|
+
const declare = (...next) => {
|
|
168
|
+
entry.tags = next;
|
|
169
|
+
if (entry.pending.length > 0 && anyTagMatches(entry.pending, next)) {
|
|
170
|
+
entry.superseded = true;
|
|
175
171
|
}
|
|
176
172
|
};
|
|
173
|
+
const request = { signal: controller.signal, force, tags: declare };
|
|
174
|
+
const context = { ...request, request, tags: declare };
|
|
177
175
|
return load(context).then(async (value) => {
|
|
178
176
|
if (controller.signal.aborted || entry.disposed) {
|
|
179
177
|
return void 0;
|
|
@@ -246,12 +244,19 @@ function useAction(run) {
|
|
|
246
244
|
entry.loading.value = true;
|
|
247
245
|
entry.status.value = "loading";
|
|
248
246
|
try {
|
|
247
|
+
const invalidates = (...tags) => {
|
|
248
|
+
invalidating = tags;
|
|
249
|
+
};
|
|
249
250
|
const result = await untrack(
|
|
250
251
|
() => run(input, {
|
|
251
252
|
signal: current.signal,
|
|
252
|
-
invalidates
|
|
253
|
-
|
|
254
|
-
|
|
253
|
+
invalidates,
|
|
254
|
+
// An action changes something, so nothing it sends may be answered
|
|
255
|
+
// out of a cache: `force` is not a choice here. `tags` is the
|
|
256
|
+
// store's invalidation, so a client that knows what a mutation
|
|
257
|
+
// changed — a document with `@invalidates` — reports it without
|
|
258
|
+
// the call site repeating it.
|
|
259
|
+
request: { signal: current.signal, force: true, tags: invalidates }
|
|
255
260
|
})
|
|
256
261
|
);
|
|
257
262
|
if (current.signal.aborted || entry.disposed) {
|
|
@@ -310,6 +315,118 @@ async function void_(promise) {
|
|
|
310
315
|
return void 0;
|
|
311
316
|
}
|
|
312
317
|
|
|
318
|
+
// packages/data/src/cache.ts
|
|
319
|
+
function createCacheClient(options = {}) {
|
|
320
|
+
const ttl = options.ttl ?? 0;
|
|
321
|
+
const max = options.max ?? 100;
|
|
322
|
+
const now = options.now ?? (() => Date.now());
|
|
323
|
+
const entries = /* @__PURE__ */ new Map();
|
|
324
|
+
const drop = (key) => {
|
|
325
|
+
const entry = entries.get(key);
|
|
326
|
+
entry?.controller?.abort();
|
|
327
|
+
entries.delete(key);
|
|
328
|
+
};
|
|
329
|
+
const keep = (key, entry) => {
|
|
330
|
+
entries.delete(key);
|
|
331
|
+
entries.set(key, entry);
|
|
332
|
+
if (entries.size > max) {
|
|
333
|
+
for (const oldest of entries.keys()) {
|
|
334
|
+
drop(oldest);
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
return {
|
|
340
|
+
get size() {
|
|
341
|
+
return entries.size;
|
|
342
|
+
},
|
|
343
|
+
peek: (key) => {
|
|
344
|
+
const entry = entries.get(key);
|
|
345
|
+
if (entry === void 0 || entry.expires <= now()) {
|
|
346
|
+
return void 0;
|
|
347
|
+
}
|
|
348
|
+
return entry.value;
|
|
349
|
+
},
|
|
350
|
+
write: (key, value) => {
|
|
351
|
+
keep(key, {
|
|
352
|
+
value,
|
|
353
|
+
expires: ttl === 0 ? Infinity : now() + ttl,
|
|
354
|
+
inflight: null,
|
|
355
|
+
controller: null,
|
|
356
|
+
waiting: 0
|
|
357
|
+
});
|
|
358
|
+
},
|
|
359
|
+
forget: (key) => {
|
|
360
|
+
if (key === void 0) {
|
|
361
|
+
for (const held of [...entries.keys()]) {
|
|
362
|
+
drop(held);
|
|
363
|
+
}
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
drop(key);
|
|
367
|
+
},
|
|
368
|
+
read: (key, produce) => async (request) => {
|
|
369
|
+
const held = entries.get(key);
|
|
370
|
+
if (request.force) {
|
|
371
|
+
drop(key);
|
|
372
|
+
} else if (held !== void 0) {
|
|
373
|
+
if (held.inflight !== null) {
|
|
374
|
+
return await share(held, request);
|
|
375
|
+
}
|
|
376
|
+
if (held.expires > now()) {
|
|
377
|
+
keep(key, held);
|
|
378
|
+
return held.value;
|
|
379
|
+
}
|
|
380
|
+
entries.delete(key);
|
|
381
|
+
}
|
|
382
|
+
const controller = new AbortController();
|
|
383
|
+
const entry = {
|
|
384
|
+
value: void 0,
|
|
385
|
+
expires: 0,
|
|
386
|
+
inflight: null,
|
|
387
|
+
controller,
|
|
388
|
+
waiting: 0
|
|
389
|
+
};
|
|
390
|
+
const run = produce({ signal: controller.signal, force: request.force }).then((value) => {
|
|
391
|
+
if (entries.get(key) === entry) {
|
|
392
|
+
if (ttl === 0) {
|
|
393
|
+
entries.delete(key);
|
|
394
|
+
} else {
|
|
395
|
+
entry.value = value;
|
|
396
|
+
entry.expires = now() + ttl;
|
|
397
|
+
entry.inflight = null;
|
|
398
|
+
entry.controller = null;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return value;
|
|
402
|
+
}).catch((error) => {
|
|
403
|
+
if (entries.get(key) === entry) {
|
|
404
|
+
entries.delete(key);
|
|
405
|
+
}
|
|
406
|
+
throw error;
|
|
407
|
+
});
|
|
408
|
+
entry.inflight = run;
|
|
409
|
+
keep(key, entry);
|
|
410
|
+
return await share(entry, request);
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
async function share(entry, request) {
|
|
415
|
+
entry.waiting += 1;
|
|
416
|
+
const stop = () => {
|
|
417
|
+
if (entry.waiting <= 1 && entry.inflight !== null) {
|
|
418
|
+
entry.controller?.abort();
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
request.signal.addEventListener("abort", stop, { once: true });
|
|
422
|
+
try {
|
|
423
|
+
return await entry.inflight;
|
|
424
|
+
} finally {
|
|
425
|
+
request.signal.removeEventListener("abort", stop);
|
|
426
|
+
entry.waiting -= 1;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
313
430
|
// packages/data/src/document.ts
|
|
314
431
|
var DIRECTIVE = /^@(tag|invalidates)\b/;
|
|
315
432
|
var OPERATION = /\b(query|mutation|subscription)\b[^\S\n]*([A-Za-z_]\w*)?/;
|
|
@@ -509,6 +626,7 @@ function resolveTags(templates, variables) {
|
|
|
509
626
|
}
|
|
510
627
|
|
|
511
628
|
// packages/data/src/http.ts
|
|
629
|
+
import { untrack as untrack2 } from "@firsthandjs/core";
|
|
512
630
|
var FirsthandHttpError = class extends Error {
|
|
513
631
|
constructor(status, url, body) {
|
|
514
632
|
super(`HTTP ${String(status)} for ${url}`);
|
|
@@ -521,36 +639,79 @@ var FirsthandHttpError = class extends Error {
|
|
|
521
639
|
url;
|
|
522
640
|
body;
|
|
523
641
|
};
|
|
524
|
-
function
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
if (!
|
|
540
|
-
|
|
642
|
+
function resolve(options, url, init) {
|
|
643
|
+
const target = /^[a-z]+:\/\//i.test(url) ? url : `${options.baseUrl ?? ""}${url}`;
|
|
644
|
+
const { json: payload, cacheKey: _key, ...rest } = init;
|
|
645
|
+
const merged = { ...options.init, ...rest };
|
|
646
|
+
const headers = new Headers(
|
|
647
|
+
// Untracked: a header function reads a token, and a token is not something
|
|
648
|
+
// a resource may depend on — writing it would re-send every request that
|
|
649
|
+
// built a header from it, including on the way out of a sign-out.
|
|
650
|
+
typeof options.headers === "function" ? untrack2(options.headers) : options.headers
|
|
651
|
+
);
|
|
652
|
+
for (const [name, value] of new Headers(init.headers ?? {})) {
|
|
653
|
+
headers.set(name, value);
|
|
654
|
+
}
|
|
655
|
+
if (payload !== void 0) {
|
|
656
|
+
merged.body = JSON.stringify(payload);
|
|
657
|
+
if (!headers.has("content-type")) {
|
|
658
|
+
headers.set("content-type", "application/json");
|
|
541
659
|
}
|
|
542
|
-
|
|
660
|
+
}
|
|
661
|
+
merged.headers = headers;
|
|
662
|
+
return [target, merged];
|
|
663
|
+
}
|
|
664
|
+
async function send(options, url, init, request) {
|
|
665
|
+
const call = options.fetch ?? globalThis.fetch;
|
|
666
|
+
const response = await call(url, { ...init, signal: request.signal });
|
|
667
|
+
const text = await response.text();
|
|
668
|
+
const parsed = text === "" ? void 0 : JSON.parse(text);
|
|
669
|
+
if (!response.ok) {
|
|
670
|
+
throw new FirsthandHttpError(response.status, url, parsed);
|
|
671
|
+
}
|
|
672
|
+
return parsed;
|
|
673
|
+
}
|
|
674
|
+
function createFetchClient(options = {}) {
|
|
675
|
+
const cache = options.cache === void 0 || options.cache === false ? void 0 : "read" in options.cache ? options.cache : createCacheClient(options.cache);
|
|
676
|
+
const client = {
|
|
677
|
+
cache,
|
|
678
|
+
request: (url, init = {}) => async (request) => {
|
|
679
|
+
const [target, merged] = resolve(options, url, init);
|
|
680
|
+
const method = (merged.method ?? "GET").toUpperCase();
|
|
681
|
+
const key = typeof init.cacheKey === "string" ? init.cacheKey : method === "GET" || method === "HEAD" ? `${method} ${target}` : void 0;
|
|
682
|
+
if (cache === void 0 || init.cacheKey === false || key === void 0) {
|
|
683
|
+
return await send(options, target, merged, request);
|
|
684
|
+
}
|
|
685
|
+
return await cache.read(key, (shared) => send(options, target, merged, shared))(
|
|
686
|
+
request
|
|
687
|
+
);
|
|
688
|
+
},
|
|
689
|
+
get: (url, init = {}) => client.request(url, init),
|
|
690
|
+
post: (url, init = {}) => client.request(url, { method: "POST", ...init }),
|
|
691
|
+
put: (url, init = {}) => client.request(url, { method: "PUT", ...init }),
|
|
692
|
+
patch: (url, init = {}) => client.request(url, { method: "PATCH", ...init }),
|
|
693
|
+
remove: (url, init = {}) => client.request(url, { method: "DELETE", ...init }),
|
|
694
|
+
with: (overrides) => createFetchClient({
|
|
695
|
+
...options,
|
|
696
|
+
...overrides,
|
|
697
|
+
// The cache is the one option that is shared rather than rebuilt: a
|
|
698
|
+
// specialised client is a variation of this one, not a second
|
|
699
|
+
// application with its own memory.
|
|
700
|
+
cache: overrides.cache ?? cache ?? false
|
|
701
|
+
})
|
|
543
702
|
};
|
|
703
|
+
return client;
|
|
544
704
|
}
|
|
545
705
|
export {
|
|
546
706
|
DataContext,
|
|
547
707
|
FirsthandDirectiveError,
|
|
548
708
|
FirsthandHttpError,
|
|
549
709
|
anyTagMatches,
|
|
710
|
+
createCacheClient,
|
|
550
711
|
createData,
|
|
712
|
+
createFetchClient,
|
|
551
713
|
fromObservable,
|
|
552
714
|
fromPromise,
|
|
553
|
-
json,
|
|
554
715
|
parseGraphQL,
|
|
555
716
|
resolveTags,
|
|
556
717
|
tag,
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{createContext as
|
|
2
|
-
`,
|
|
1
|
+
import{createContext as J,effect as j,onCleanup as D,untrack as z,useContext as N}from"@firsthandjs/core";import{batch as L,signal as b}from"@firsthandjs/core";var F=Object.freeze({});function R(e,n=F){return{name:e,vars:n}}function O(e,n){if(e.name!==n.name)return!1;for(let a of Object.keys(e.vars))if(!Object.is(e.vars[a],n.vars[a]))return!1;return!0}function m(e,n){for(let a of e)for(let t of n)if(O(a,t))return!0;return!1}function S(e={}){let n=new Set;return{storage:e.storage,hold:a=>(n.add(a),()=>n.delete(a)),invalidate:async(...a)=>{let t=[];for(let r of[...n])r.controller!==null&&r.pending.push(...a),m(a,r.tags)&&(r.name,r.tags,t.push(r.run(!0)));await Promise.all(t)},clear:()=>{for(let a of[...n])a.controller?.abort(),n.delete(a);try{e.storage?.clear?.()}catch{}},get size(){return n.size}}}function x(e,n){return{data:b(void 0),error:b(void 0),status:b("idle"),loading:b(!1),tags:[],controller:null,pending:[],superseded:!1,disposed:!1,name:n}}function h(e,n){L(()=>{e.data.value=n,e.error.value=void 0,e.status.value="success",e.loading.value=!1})}function v(e,n){L(()=>{e.error.value=n,e.status.value="error",e.loading.value=!1})}function C(e,n){return{data:e.data,error:e.error,status:e.status,loading:e.loading,reload:()=>e.run(!0),dispose:()=>{e.disposed=!0,e.controller?.abort(),n()}}}var P=J();function y(){return N(P).value}function Q(){let e=y();return(...n)=>e.invalidate(...n)}function V(e,n={}){let a=y(),t=x(a,n.persist),r=a.hold(t);n.persist,t.run=o=>{if(t.disposed)return Promise.resolve(void 0);t.controller?.abort();let i=new AbortController;t.controller=i,t.pending=[],t.superseded=!1,t.loading.value=!0,t.data.peek()===void 0&&(t.status.value="loading");let d=(...u)=>{t.tags=u,t.pending.length>0&&m(t.pending,u)&&(t.superseded=!0)},c={signal:i.signal,force:o,tags:d},p={...c,request:c,tags:d};return e(p).then(async u=>{if(!(i.signal.aborted||t.disposed)){if(t.controller=null,h(t,u),n.persist!==void 0)try{a.storage?.write?.(n.persist,u)}catch{}return t.superseded?(t.superseded=!1,t.run(!0)):u}}).catch(u=>{i.signal.aborted||t.disposed||(t.controller=null,v(t,u))})};let s=n.persist,l=a.storage;return s!==void 0&&l?.read!==void 0&&(async()=>{try{let o=await l.read?.(s);o!==void 0&&t.data.peek()===void 0&&!t.disposed&&(h(t,o),t.loading.value=!0)}catch{}})(),j(()=>{t.run(!1)}),D(()=>{t.disposed=!0,t.controller?.abort(),n.persist,t.tags,r()}),C(t,r)}function _(e){let n=y(),a=x(n,void 0),t=null;return D(()=>{a.disposed=!0,t?.abort()}),{data:a.data,error:a.error,status:a.status,running:a.loading,run:async r=>{t?.abort(),t=new AbortController;let s=t,l=[];a.loading.value=!0,a.status.value="loading";try{let o=(...d)=>{l=d},i=await z(()=>e(r,{signal:s.signal,invalidates:o,request:{signal:s.signal,force:!0,tags:o}}));return s.signal.aborted||a.disposed?void 0:(h(a,i),l.length>0&&await n.invalidate(...l),i)}catch(o){if(s.signal.aborted||a.disposed)return;v(a,o);return}}}}function G(e,n={}){let a=y(),t=x(a,void 0),r=a.hold(t);t.run=()=>n.reload===void 0?Promise.resolve(void 0):K(n.reload()),t.loading.value=!0,t.status.value="loading";let s=e.subscribe({next:o=>{h(t,o)},error:o=>{v(t,o)}}),l=typeof s=="function"?s:()=>{s.unsubscribe()};return D(()=>{t.disposed=!0,l(),r()}),C(t,()=>{l(),r()})}function M(e){return V(()=>e())}async function K(e){await e}function k(e={}){let n=e.ttl??0,a=e.max??100,t=e.now??(()=>Date.now()),r=new Map,s=o=>{r.get(o)?.controller?.abort(),r.delete(o)},l=(o,i)=>{if(r.delete(o),r.set(o,i),r.size>a)for(let d of r.keys()){s(d);break}};return{get size(){return r.size},peek:o=>{let i=r.get(o);if(!(i===void 0||i.expires<=t()))return i.value},write:(o,i)=>{l(o,{value:i,expires:n===0?1/0:t()+n,inflight:null,controller:null,waiting:0})},forget:o=>{if(o===void 0){for(let i of[...r.keys()])s(i);return}s(o)},read:(o,i)=>async d=>{let c=r.get(o);if(d.force)s(o);else if(c!==void 0){if(c.inflight!==null)return await A(c,d);if(c.expires>t())return l(o,c),c.value;r.delete(o)}let p=new AbortController,u={value:void 0,expires:0,inflight:null,controller:p,waiting:0},T=i({signal:p.signal,force:d.force}).then(f=>(r.get(o)===u&&(n===0?r.delete(o):(u.value=f,u.expires=t()+n,u.inflight=null,u.controller=null)),f)).catch(f=>{throw r.get(o)===u&&r.delete(o),f});return u.inflight=T,l(o,u),await A(u,d)}}}async function A(e,n){e.waiting+=1;let a=()=>{e.waiting<=1&&e.inflight!==null&&e.controller?.abort()};n.signal.addEventListener("abort",a,{once:!0});try{return await e.inflight}finally{n.signal.removeEventListener("abort",a),e.waiting-=1}}var B=/^@(tag|invalidates)\b/,U=/\b(query|mutation|subscription)\b[^\S\n]*([A-Za-z_]\w*)?/,E=/^[_A-Za-z][_0-9A-Za-z]*/,g=class extends Error{constructor(n){super(n),this.name="FirsthandDirectiveError"}};function q(e,n){if(e.startsWith('"""',n)){let t=e.indexOf('"""',n+3);return t===-1?e.length:t+3}let a=n+1;for(;a<e.length&&e[a]!=='"';)a+=e[a]==="\\"?2:1;return a+1}function Z(e,n){let a=0,t=n;for(;t<e.length;){let r=e[t];if(r==='"'){t=q(e,t);continue}if(r==="("||r==="["||r==="{")a++;else if((r===")"||r==="]"||r==="}")&&(a--,a===0))return t+1;t++}throw new g(`unclosed arguments in ${e.slice(n,n+40)}`)}function W(e,n){try{return JSON.parse(e)}catch{throw new g(`@${n}: ${e} is not a valid string`)}}function X(e,n){let a={},t=0,r=()=>{for(;t<e.length&&/[\s,]/.test(e[t]);)t++};for(r();t<e.length;){let s=E.exec(e.slice(t));if(s===null)throw new g(`@${n}: expected an argument name at "${e.slice(t)}"`);if(t+=s[0].length,r(),e[t]!==":")throw new g(`@${n}: argument ${s[0]} has no value`);t++,r();let l=Y(e,n,t);a[s[0]]=l.value,t=l.next,r()}return a}function Y(e,n,a){let t=e[a];if(t==="$"){let i=E.exec(e.slice(a+1));if(i===null)throw new g(`@${n}: expected a variable name after $`);return{value:{variable:i[0]},next:a+1+i[0].length}}if(t==='"'){let i=q(e,a),d=e.slice(a,i);return{value:{literal:d.startsWith('"""')?d.slice(3,-3).trim():W(d,n)},next:i}}if(t==="["||t==="{")throw new g(`@${n}: a tag variable must be a scalar, not a list or object`);let r=/^[^\s,)]+/.exec(e.slice(a));if(r===null)throw new g(`@${n}: expected a value`);let s=r[0],l=a+s.length;if(s==="true"||s==="false")return{value:{literal:s==="true"},next:l};if(s==="null")return{value:{literal:null},next:l};let o=Number(s);return{value:{literal:Number.isNaN(o)?s:o},next:l}}function ee(e){let n=[],a=[],t="",r=0,s=0;for(;r<e.length;){let l=e[r];if(l==='"'){r=q(e,r);continue}if(l==="#"){let f=e.indexOf(`
|
|
2
|
+
`,r);r=f===-1?e.length:f;continue}if(l!=="@"){r++;continue}let o=B.exec(e.slice(r));if(o===null){r++;continue}let i=r+o[0].length,d="",c=i;for(;c<e.length&&/\s/.test(e[c]);)c++;if(e[c]==="("){let f=Z(e,c);d=e.slice(c+1,f-1),i=f}let p=X(d,o[1]),u=p.name;if(u===void 0||!("literal"in u)||typeof u.literal!="string")throw new g(`@${o[1]} needs a literal name, as in @${o[1]}(name: "user", id: $id)`);delete p.name,(o[1]==="tag"?n:a).push({name:u.literal,vars:p});let T=r;for(;T>s&&/\s/.test(e[T-1]);)T--;t+=e.slice(s,T),s=i,r=i}return{tags:n,invalidates:a,stripped:t+e.slice(s)}}function te(e){let{tags:n,invalidates:a,stripped:t}=ee(e),r=U.exec(t.replace(/#[^\n]*/g,""));return{source:t,operation:r?.[2]??"",kind:r?.[1]??"query",tags:n,invalidates:a}}function ne(e,n){return e.map(a=>{let t={};for(let[r,s]of Object.entries(a.vars)){if("literal"in s){t[r]=s.literal;continue}let l=n[s.variable];l!=null&&(t[r]=typeof l=="object"?JSON.stringify(l):l)}return R(a.name,t)})}import{untrack as re}from"@firsthandjs/core";var w=class extends Error{constructor(a,t,r){super(`HTTP ${String(a)} for ${t}`);this.status=a;this.url=t;this.body=r;this.name="FirsthandHttpError"}status;url;body};function ae(e,n,a){let t=/^[a-z]+:\/\//i.test(n)?n:`${e.baseUrl??""}${n}`,{json:r,cacheKey:s,...l}=a,o={...e.init,...l},i=new Headers(typeof e.headers=="function"?re(e.headers):e.headers);for(let[d,c]of new Headers(a.headers??{}))i.set(d,c);return r!==void 0&&(o.body=JSON.stringify(r),i.has("content-type")||i.set("content-type","application/json")),o.headers=i,[t,o]}async function I(e,n,a,t){let s=await(e.fetch??globalThis.fetch)(n,{...a,signal:t.signal}),l=await s.text(),o=l===""?void 0:JSON.parse(l);if(!s.ok)throw new w(s.status,n,o);return o}function $(e={}){let n=e.cache===void 0||e.cache===!1?void 0:"read"in e.cache?e.cache:k(e.cache),a={cache:n,request:(t,r={})=>async s=>{let[l,o]=ae(e,t,r),i=(o.method??"GET").toUpperCase(),d=typeof r.cacheKey=="string"?r.cacheKey:i==="GET"||i==="HEAD"?`${i} ${l}`:void 0;return n===void 0||r.cacheKey===!1||d===void 0?await I(e,l,o,s):await n.read(d,c=>I(e,l,o,c))(s)},get:(t,r={})=>a.request(t,r),post:(t,r={})=>a.request(t,{method:"POST",...r}),put:(t,r={})=>a.request(t,{method:"PUT",...r}),patch:(t,r={})=>a.request(t,{method:"PATCH",...r}),remove:(t,r={})=>a.request(t,{method:"DELETE",...r}),with:t=>$({...e,...t,cache:t.cache??n??!1})};return a}export{P as DataContext,g as FirsthandDirectiveError,w as FirsthandHttpError,m as anyTagMatches,k as createCacheClient,S as createData,$ as createFetchClient,G as fromObservable,M as fromPromise,te as parseGraphQL,ne as resolveTags,R as tag,O as tagMatches,_ as useAction,y as useData,Q as useInvalidate,V as useResource};
|
package/dist/resource.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAKL,KAAK,aAAa,
|
|
1
|
+
{"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAKL,KAAK,aAAa,EAElB,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,QAAQ,EACb,KAAK,MAAM,EACZ,MAAM,YAAY,CAAC;AACpB,OAAO,EAAiB,KAAK,GAAG,EAAE,MAAM,WAAW,CAAC;AAGpD,eAAO,MAAM,WAAW,gDAA6B,CAAC;AAEtD,wBAAgB,OAAO,IAAI,SAAS,CAEnC;AAED,6DAA6D;AAC7D,wBAAgB,aAAa,IAAI,CAAC,GAAG,QAAQ,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAGrE;AAED,MAAM,WAAW,eAAe;IAC9B;;;;;;;OAOG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAC3B,IAAI,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,EAC1C,OAAO,GAAE,eAAoB,GAC5B,QAAQ,CAAC,CAAC,CAAC,CAqGb;AAED,MAAM,WAAW,MAAM,CAAC,CAAC,EAAE,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IACzC,mEAAmE;IACnE,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;CACvC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,CAAC,EAC5B,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,aAAa,KAAK,OAAO,CAAC,CAAC,CAAC,GACpD,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CA0Dd;AAED,MAAM,WAAW,cAAc,CAAC,CAAC;IAC/B,SAAS,CAAC,QAAQ,EAAE;QAClB,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;QAC1B,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;KAClC,GAAG;QAAE,WAAW,EAAE,MAAM,IAAI,CAAA;KAAE,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC;CAChD;AAED,MAAM,WAAW,aAAa;IAC5B,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAC9B,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,EACzB,OAAO,GAAE,aAAkB,GAC1B,QAAQ,CAAC,CAAC,CAAC,CAoCb;AAED,8EAA8E;AAC9E,wBAAgB,WAAW,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAErE;AAQD,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC"}
|
package/dist/store.d.ts
CHANGED
|
@@ -19,10 +19,40 @@
|
|
|
19
19
|
import { type ReadonlyCell, type Signal } from '@firsthandjs/core';
|
|
20
20
|
import { type Tag } from './tags.js';
|
|
21
21
|
export type Status = 'idle' | 'loading' | 'success' | 'error';
|
|
22
|
-
/**
|
|
23
|
-
|
|
22
|
+
/**
|
|
23
|
+
* What a loader hands to a transport: everything a request needs to be sent
|
|
24
|
+
* once, aborted, and — when it matters — sent *again*.
|
|
25
|
+
*
|
|
26
|
+
* The two travel together because they answer the same question. `signal` ends
|
|
27
|
+
* a request that is no longer wanted; `force` says this run exists *because*
|
|
28
|
+
* something was invalidated, so a transport cache that answers from its own
|
|
29
|
+
* memory would make that invalidation silently pointless. Every client in this
|
|
30
|
+
* project takes this object and honours both.
|
|
31
|
+
*/
|
|
32
|
+
export interface DataRequest {
|
|
24
33
|
/** Aborted when this run is superseded, or the resource goes away. */
|
|
25
34
|
readonly signal: AbortSignal;
|
|
35
|
+
/**
|
|
36
|
+
* True when this run was caused by an invalidation or by `reload()`.
|
|
37
|
+
*
|
|
38
|
+
* A client must then skip whatever it has cached and replace it with the
|
|
39
|
+
* answer — which is what makes an invalidation reach all the way down.
|
|
40
|
+
*/
|
|
41
|
+
readonly force: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Where a client reports what this request is about, when it knows.
|
|
44
|
+
*
|
|
45
|
+
* A GraphQL document carries its own `@tag` and `@invalidates` directives,
|
|
46
|
+
* so a client can declare them without the call site repeating them. In a
|
|
47
|
+
* resource this is the resource's `tags`; in an action it is the store's
|
|
48
|
+
* `invalidates` — the same hole, filled with whichever of the two the
|
|
49
|
+
* request belongs to. Optional, because a request written by hand may have
|
|
50
|
+
* nothing to say.
|
|
51
|
+
*/
|
|
52
|
+
readonly tags?: (...tags: Tag[]) => void;
|
|
53
|
+
}
|
|
54
|
+
/** What a loader is given. */
|
|
55
|
+
export interface LoadContext extends DataRequest {
|
|
26
56
|
/**
|
|
27
57
|
* Declares what this resource is about. **Replaces**: call it before the
|
|
28
58
|
* first `await` when you know, again after it when only the server does, and
|
|
@@ -30,15 +60,10 @@ export interface LoadContext {
|
|
|
30
60
|
*/
|
|
31
61
|
readonly tags: (...tags: Tag[]) => void;
|
|
32
62
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* It is the one place the layers touch. A transport cache would otherwise
|
|
36
|
-
* hand back the answer that was just invalidated, and the invalidation would
|
|
37
|
-
* be silently pointless — so pass it on: `cache: force ? 'reload' : 'default'`
|
|
38
|
-
* for `fetch`, `fetchPolicy: force ? 'network-only' : 'cache-first'` for
|
|
39
|
-
* Apollo, `requestPolicy` for urql.
|
|
63
|
+
* The two above as one object, to hand to a client:
|
|
64
|
+
* `api.get<User>('/users/7')(request)`.
|
|
40
65
|
*/
|
|
41
|
-
readonly
|
|
66
|
+
readonly request: DataRequest;
|
|
42
67
|
}
|
|
43
68
|
/** What an action is given. */
|
|
44
69
|
export interface ActionContext {
|
|
@@ -49,7 +74,14 @@ export interface ActionContext {
|
|
|
49
74
|
* is the case where only the server knows what was touched.
|
|
50
75
|
*/
|
|
51
76
|
readonly invalidates: (...tags: Tag[]) => void;
|
|
77
|
+
/**
|
|
78
|
+
* The request to hand to a client. `force` is always true here: an action
|
|
79
|
+
* changes something, so nothing it sends may be answered from a cache.
|
|
80
|
+
*/
|
|
81
|
+
readonly request: DataRequest;
|
|
52
82
|
}
|
|
83
|
+
/** What a client hands back: a request waiting for the context to send it. */
|
|
84
|
+
export type Loader<T> = (request: DataRequest) => Promise<T>;
|
|
53
85
|
export interface Resource<T> {
|
|
54
86
|
readonly data: ReadonlyCell<T | undefined>;
|
|
55
87
|
readonly error: ReadonlyCell<unknown>;
|
package/dist/store.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAiB,KAAK,YAAY,EAAE,KAAK,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAClF,OAAO,EAAiB,KAAK,GAAG,EAAE,MAAM,WAAW,CAAC;AAGpD,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAE9D
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@firsthandjs/data",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Resources, actions and invalidation for Firsthand: reactive async state, with the cache one layer down.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"LICENSE"
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@firsthandjs/core": "0.
|
|
32
|
-
"@firsthandjs/dom": "0.
|
|
31
|
+
"@firsthandjs/core": "0.6.1",
|
|
32
|
+
"@firsthandjs/dom": "0.6.1"
|
|
33
33
|
},
|
|
34
34
|
"engines": {
|
|
35
35
|
"node": ">=20.11.0"
|