@firsthandjs/data 0.6.1 → 0.6.2
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 +6 -1
- package/dist/cache.d.ts +10 -0
- package/dist/cache.d.ts.map +1 -1
- package/dist/http.d.ts +23 -0
- package/dist/http.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.dev.js +20 -1
- package/dist/index.js +2 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ what is loaded, what state that is in, and when it has to be loaded again.
|
|
|
9
9
|
npm install @firsthandjs/data
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
3.
|
|
12
|
+
3.84 kB gzip. It depends on `@firsthandjs/core` and `@firsthandjs/dom`.
|
|
13
13
|
|
|
14
14
|
```tsx
|
|
15
15
|
const api = createFetchClient({ baseUrl: '/api' });
|
|
@@ -67,6 +67,11 @@ components asking at once make one request, which is waste removed rather than
|
|
|
67
67
|
staleness introduced. `force` drops the entry, which is how an invalidation
|
|
68
68
|
reaches all the way down.
|
|
69
69
|
|
|
70
|
+
A key is an **identity** and a **request**: `GET /api/me` is the same URL for
|
|
71
|
+
everybody, so the identity — the `authorization` header by default, or a
|
|
72
|
+
`scope` function you give — is what stops one account being served the answer
|
|
73
|
+
cached for another.
|
|
74
|
+
|
|
70
75
|
## Tags are for invalidation
|
|
71
76
|
|
|
72
77
|
They may be coarse, they may overlap, and two unrelated sources may share one.
|
package/dist/cache.d.ts
CHANGED
|
@@ -38,6 +38,16 @@
|
|
|
38
38
|
* goes when `max` is reached.
|
|
39
39
|
*/
|
|
40
40
|
import type { Loader } from './store.js';
|
|
41
|
+
/**
|
|
42
|
+
* A key for a value, stable however the value was written.
|
|
43
|
+
*
|
|
44
|
+
* `{ a: 1, b: 2 }` and `{ b: 2, a: 1 }` are the same request, so they must be
|
|
45
|
+
* the same key — which `JSON.stringify` alone does not give you, because it
|
|
46
|
+
* keeps insertion order. Exported because a caller building a `cacheKey` of
|
|
47
|
+
* their own needs the same guarantee, and getting it wrong means one answer
|
|
48
|
+
* served for two different requests.
|
|
49
|
+
*/
|
|
50
|
+
export declare function stableKey(value: unknown): string;
|
|
41
51
|
export interface CacheOptions {
|
|
42
52
|
/**
|
|
43
53
|
* How long an answer is served again without running the producer, in ms.
|
package/dist/cache.d.ts.map
CHANGED
|
@@ -1 +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"}
|
|
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;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAmBhD;AAED,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
|
@@ -36,6 +36,11 @@ export interface JsonRequest extends Omit<RequestInit, 'body'> {
|
|
|
36
36
|
* nowhere.
|
|
37
37
|
*
|
|
38
38
|
* A key is for when the URL is not the identity — a POST that reads, say.
|
|
39
|
+
* It must then carry **everything that varies**, the body included:
|
|
40
|
+
* `` cacheKey: `search:${stableKey(body)}` `` rather than `'search'`, or
|
|
41
|
+
* two different searches share one answer. The client's identity scope is added
|
|
42
|
+
* for you either way.
|
|
43
|
+
*
|
|
39
44
|
* `false` is for the read that must never be served from memory. The
|
|
40
45
|
* platform's own `cache` option is untouched and still passed to `fetch`,
|
|
41
46
|
* because that one is the HTTP cache and a different thing entirely.
|
|
@@ -63,6 +68,24 @@ export interface FetchClientOptions {
|
|
|
63
68
|
readonly cache?: false | CacheOptions | CacheClient;
|
|
64
69
|
/** Applied to every request: `credentials`, `mode`, `referrerPolicy`, … */
|
|
65
70
|
readonly init?: RequestInit;
|
|
71
|
+
/**
|
|
72
|
+
* Who the cached answers belong to.
|
|
73
|
+
*
|
|
74
|
+
* A cache keyed by URL alone is a cache that can serve one account's answer
|
|
75
|
+
* to the next one, because `GET /api/me` is the same URL for everybody. So
|
|
76
|
+
* the key carries an identity, and by default that identity is the
|
|
77
|
+
* `authorization` header this request would be sent with — which means the
|
|
78
|
+
* dangerous case is handled without anybody opting in.
|
|
79
|
+
*
|
|
80
|
+
* Set this when the identity is somewhere else: a cookie session, a tenant
|
|
81
|
+
* header, an account picker. It is read per request and untracked, like
|
|
82
|
+
* `headers`. Returning `''` says every caller shares one cache.
|
|
83
|
+
*
|
|
84
|
+
* Clearing the cache on sign-out (`client.cache?.forget()`) is still worth
|
|
85
|
+
* doing — it frees the memory, and it is what `store.clear()` is beside —
|
|
86
|
+
* but forgetting to is no longer a correctness bug.
|
|
87
|
+
*/
|
|
88
|
+
readonly scope?: () => string;
|
|
66
89
|
/**
|
|
67
90
|
* The seam. Wrap `fetch` to log, to retry, or to end a session on a 401 —
|
|
68
91
|
* and keep that in one place rather than in a plugin system.
|
package/dist/http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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
|
|
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;;;;;;;;;;;;;;;;;OAiBG;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;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;IAC9B;;;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,CA6D/E"}
|
package/dist/index.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export type { ActionContext, DataOptions, DataRequest, DataStore, LoadContext, L
|
|
|
17
17
|
* own: the same `read`, the same lifetimes, and the same `force` reaching
|
|
18
18
|
* through when a resource is invalidated (ADR-0023).
|
|
19
19
|
*/
|
|
20
|
-
export { createCacheClient } from './cache.js';
|
|
20
|
+
export { createCacheClient, stableKey } from './cache.js';
|
|
21
21
|
export type { CacheClient, CacheOptions } from './cache.js';
|
|
22
22
|
export { tag, tagMatches, anyTagMatches } from './tags.js';
|
|
23
23
|
export type { Tag, TagVars, Variables } from './tags.js';
|
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,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;
|
|
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,SAAS,EAAE,MAAM,YAAY,CAAC;AAC1D,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
|
@@ -316,6 +316,22 @@ async function void_(promise) {
|
|
|
316
316
|
}
|
|
317
317
|
|
|
318
318
|
// packages/data/src/cache.ts
|
|
319
|
+
function stableKey(value) {
|
|
320
|
+
if (value === void 0) {
|
|
321
|
+
return "undefined";
|
|
322
|
+
}
|
|
323
|
+
if (value === null || typeof value !== "object") {
|
|
324
|
+
return JSON.stringify(value);
|
|
325
|
+
}
|
|
326
|
+
if (Array.isArray(value)) {
|
|
327
|
+
return `[${value.map(stableKey).join(",")}]`;
|
|
328
|
+
}
|
|
329
|
+
if (value instanceof Date) {
|
|
330
|
+
return value.toISOString();
|
|
331
|
+
}
|
|
332
|
+
const entries = Object.entries(value).filter(([, held]) => held !== void 0).sort(([one], [other]) => one < other ? -1 : 1);
|
|
333
|
+
return `{${entries.map(([name, held]) => `${name}:${stableKey(held)}`).join(",")}}`;
|
|
334
|
+
}
|
|
319
335
|
function createCacheClient(options = {}) {
|
|
320
336
|
const ttl = options.ttl ?? 0;
|
|
321
337
|
const max = options.max ?? 100;
|
|
@@ -678,7 +694,9 @@ function createFetchClient(options = {}) {
|
|
|
678
694
|
request: (url, init = {}) => async (request) => {
|
|
679
695
|
const [target, merged] = resolve(options, url, init);
|
|
680
696
|
const method = (merged.method ?? "GET").toUpperCase();
|
|
681
|
-
const
|
|
697
|
+
const scope = options.scope === void 0 ? merged.headers.get("authorization") ?? "" : untrack2(options.scope);
|
|
698
|
+
const named = typeof init.cacheKey === "string" ? init.cacheKey : method === "GET" || method === "HEAD" ? `${method} ${target}` : void 0;
|
|
699
|
+
const key = named === void 0 ? void 0 : `${scope}\0${named}`;
|
|
682
700
|
if (cache === void 0 || init.cacheKey === false || key === void 0) {
|
|
683
701
|
return await send(options, target, merged, request);
|
|
684
702
|
}
|
|
@@ -714,6 +732,7 @@ export {
|
|
|
714
732
|
fromPromise,
|
|
715
733
|
parseGraphQL,
|
|
716
734
|
resolveTags,
|
|
735
|
+
stableKey,
|
|
717
736
|
tag,
|
|
718
737
|
tagMatches,
|
|
719
738
|
useAction,
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{createContext as
|
|
2
|
-
`,
|
|
1
|
+
import{createContext as z,effect as N,onCleanup as D,untrack as Q,useContext as _}from"@firsthandjs/core";import{batch as S,signal as b}from"@firsthandjs/core";var J=Object.freeze({});function R(e,r=J){return{name:e,vars:r}}function L(e,r){if(e.name!==r.name)return!1;for(let n of Object.keys(e.vars))if(!Object.is(e.vars[n],r.vars[n]))return!1;return!0}function m(e,r){for(let n of e)for(let t of r)if(L(n,t))return!0;return!1}function P(e={}){let r=new Set;return{storage:e.storage,hold:n=>(r.add(n),()=>r.delete(n)),invalidate:async(...n)=>{let t=[];for(let a of[...r])a.controller!==null&&a.pending.push(...n),m(n,a.tags)&&(a.name,a.tags,t.push(a.run(!0)));await Promise.all(t)},clear:()=>{for(let n of[...r])n.controller?.abort(),r.delete(n);try{e.storage?.clear?.()}catch{}},get size(){return r.size}}}function x(e,r){return{data:b(void 0),error:b(void 0),status:b("idle"),loading:b(!1),tags:[],controller:null,pending:[],superseded:!1,disposed:!1,name:r}}function h(e,r){S(()=>{e.data.value=r,e.error.value=void 0,e.status.value="success",e.loading.value=!1})}function v(e,r){S(()=>{e.error.value=r,e.status.value="error",e.loading.value=!1})}function C(e,r){return{data:e.data,error:e.error,status:e.status,loading:e.loading,reload:()=>e.run(!0),dispose:()=>{e.disposed=!0,e.controller?.abort(),r()}}}var A=z();function y(){return _(A).value}function G(){let e=y();return(...r)=>e.invalidate(...r)}function V(e,r={}){let n=y(),t=x(n,r.persist),a=n.hold(t);r.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 u=(...d)=>{t.tags=d,t.pending.length>0&&m(t.pending,d)&&(t.superseded=!0)},c={signal:i.signal,force:o,tags:u},g={...c,request:c,tags:u};return e(g).then(async d=>{if(!(i.signal.aborted||t.disposed)){if(t.controller=null,h(t,d),r.persist!==void 0)try{n.storage?.write?.(r.persist,d)}catch{}return t.superseded?(t.superseded=!1,t.run(!0)):d}}).catch(d=>{i.signal.aborted||t.disposed||(t.controller=null,v(t,d))})};let s=r.persist,l=n.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{}})(),N(()=>{t.run(!1)}),D(()=>{t.disposed=!0,t.controller?.abort(),r.persist,t.tags,a()}),C(t,a)}function M(e){let r=y(),n=x(r,void 0),t=null;return D(()=>{n.disposed=!0,t?.abort()}),{data:n.data,error:n.error,status:n.status,running:n.loading,run:async a=>{t?.abort(),t=new AbortController;let s=t,l=[];n.loading.value=!0,n.status.value="loading";try{let o=(...u)=>{l=u},i=await Q(()=>e(a,{signal:s.signal,invalidates:o,request:{signal:s.signal,force:!0,tags:o}}));return s.signal.aborted||n.disposed?void 0:(h(n,i),l.length>0&&await r.invalidate(...l),i)}catch(o){if(s.signal.aborted||n.disposed)return;v(n,o);return}}}}function K(e,r={}){let n=y(),t=x(n,void 0),a=n.hold(t);t.run=()=>r.reload===void 0?Promise.resolve(void 0):U(r.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(),a()}),C(t,()=>{l(),a()})}function B(e){return V(()=>e())}async function U(e){await e}function k(e){return e===void 0?"undefined":e===null||typeof e!="object"?JSON.stringify(e):Array.isArray(e)?`[${e.map(k).join(",")}]`:e instanceof Date?e.toISOString():`{${Object.entries(e).filter(([,n])=>n!==void 0).sort(([n],[t])=>n<t?-1:1).map(([n,t])=>`${n}:${k(t)}`).join(",")}}`}function O(e={}){let r=e.ttl??0,n=e.max??100,t=e.now??(()=>Date.now()),a=new Map,s=o=>{a.get(o)?.controller?.abort(),a.delete(o)},l=(o,i)=>{if(a.delete(o),a.set(o,i),a.size>n)for(let u of a.keys()){s(u);break}};return{get size(){return a.size},peek:o=>{let i=a.get(o);if(!(i===void 0||i.expires<=t()))return i.value},write:(o,i)=>{l(o,{value:i,expires:r===0?1/0:t()+r,inflight:null,controller:null,waiting:0})},forget:o=>{if(o===void 0){for(let i of[...a.keys()])s(i);return}s(o)},read:(o,i)=>async u=>{let c=a.get(o);if(u.force)s(o);else if(c!==void 0){if(c.inflight!==null)return await $(c,u);if(c.expires>t())return l(o,c),c.value;a.delete(o)}let g=new AbortController,d={value:void 0,expires:0,inflight:null,controller:g,waiting:0},T=i({signal:g.signal,force:u.force}).then(p=>(a.get(o)===d&&(r===0?a.delete(o):(d.value=p,d.expires=t()+r,d.inflight=null,d.controller=null)),p)).catch(p=>{throw a.get(o)===d&&a.delete(o),p});return d.inflight=T,l(o,d),await $(d,u)}}}async function $(e,r){e.waiting+=1;let n=()=>{e.waiting<=1&&e.inflight!==null&&e.controller?.abort()};r.signal.addEventListener("abort",n,{once:!0});try{return await e.inflight}finally{r.signal.removeEventListener("abort",n),e.waiting-=1}}var Z=/^@(tag|invalidates)\b/,W=/\b(query|mutation|subscription)\b[^\S\n]*([A-Za-z_]\w*)?/,E=/^[_A-Za-z][_0-9A-Za-z]*/,f=class extends Error{constructor(r){super(r),this.name="FirsthandDirectiveError"}};function q(e,r){if(e.startsWith('"""',r)){let t=e.indexOf('"""',r+3);return t===-1?e.length:t+3}let n=r+1;for(;n<e.length&&e[n]!=='"';)n+=e[n]==="\\"?2:1;return n+1}function X(e,r){let n=0,t=r;for(;t<e.length;){let a=e[t];if(a==='"'){t=q(e,t);continue}if(a==="("||a==="["||a==="{")n++;else if((a===")"||a==="]"||a==="}")&&(n--,n===0))return t+1;t++}throw new f(`unclosed arguments in ${e.slice(r,r+40)}`)}function Y(e,r){try{return JSON.parse(e)}catch{throw new f(`@${r}: ${e} is not a valid string`)}}function ee(e,r){let n={},t=0,a=()=>{for(;t<e.length&&/[\s,]/.test(e[t]);)t++};for(a();t<e.length;){let s=E.exec(e.slice(t));if(s===null)throw new f(`@${r}: expected an argument name at "${e.slice(t)}"`);if(t+=s[0].length,a(),e[t]!==":")throw new f(`@${r}: argument ${s[0]} has no value`);t++,a();let l=te(e,r,t);n[s[0]]=l.value,t=l.next,a()}return n}function te(e,r,n){let t=e[n];if(t==="$"){let i=E.exec(e.slice(n+1));if(i===null)throw new f(`@${r}: expected a variable name after $`);return{value:{variable:i[0]},next:n+1+i[0].length}}if(t==='"'){let i=q(e,n),u=e.slice(n,i);return{value:{literal:u.startsWith('"""')?u.slice(3,-3).trim():Y(u,r)},next:i}}if(t==="["||t==="{")throw new f(`@${r}: a tag variable must be a scalar, not a list or object`);let a=/^[^\s,)]+/.exec(e.slice(n));if(a===null)throw new f(`@${r}: expected a value`);let s=a[0],l=n+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 ne(e){let r=[],n=[],t="",a=0,s=0;for(;a<e.length;){let l=e[a];if(l==='"'){a=q(e,a);continue}if(l==="#"){let p=e.indexOf(`
|
|
2
|
+
`,a);a=p===-1?e.length:p;continue}if(l!=="@"){a++;continue}let o=Z.exec(e.slice(a));if(o===null){a++;continue}let i=a+o[0].length,u="",c=i;for(;c<e.length&&/\s/.test(e[c]);)c++;if(e[c]==="("){let p=X(e,c);u=e.slice(c+1,p-1),i=p}let g=ee(u,o[1]),d=g.name;if(d===void 0||!("literal"in d)||typeof d.literal!="string")throw new f(`@${o[1]} needs a literal name, as in @${o[1]}(name: "user", id: $id)`);delete g.name,(o[1]==="tag"?r:n).push({name:d.literal,vars:g});let T=a;for(;T>s&&/\s/.test(e[T-1]);)T--;t+=e.slice(s,T),s=i,a=i}return{tags:r,invalidates:n,stripped:t+e.slice(s)}}function re(e){let{tags:r,invalidates:n,stripped:t}=ne(e),a=W.exec(t.replace(/#[^\n]*/g,""));return{source:t,operation:a?.[2]??"",kind:a?.[1]??"query",tags:r,invalidates:n}}function ae(e,r){return e.map(n=>{let t={};for(let[a,s]of Object.entries(n.vars)){if("literal"in s){t[a]=s.literal;continue}let l=r[s.variable];l!=null&&(t[a]=typeof l=="object"?JSON.stringify(l):l)}return R(n.name,t)})}import{untrack as j}from"@firsthandjs/core";var w=class extends Error{constructor(n,t,a){super(`HTTP ${String(n)} for ${t}`);this.status=n;this.url=t;this.body=a;this.name="FirsthandHttpError"}status;url;body};function oe(e,r,n){let t=/^[a-z]+:\/\//i.test(r)?r:`${e.baseUrl??""}${r}`,{json:a,cacheKey:s,...l}=n,o={...e.init,...l},i=new Headers(typeof e.headers=="function"?j(e.headers):e.headers);for(let[u,c]of new Headers(n.headers??{}))i.set(u,c);return a!==void 0&&(o.body=JSON.stringify(a),i.has("content-type")||i.set("content-type","application/json")),o.headers=i,[t,o]}async function I(e,r,n,t){let s=await(e.fetch??globalThis.fetch)(r,{...n,signal:t.signal}),l=await s.text(),o=l===""?void 0:JSON.parse(l);if(!s.ok)throw new w(s.status,r,o);return o}function H(e={}){let r=e.cache===void 0||e.cache===!1?void 0:"read"in e.cache?e.cache:O(e.cache),n={cache:r,request:(t,a={})=>async s=>{let[l,o]=oe(e,t,a),i=(o.method??"GET").toUpperCase(),u=e.scope===void 0?o.headers.get("authorization")??"":j(e.scope),c=typeof a.cacheKey=="string"?a.cacheKey:i==="GET"||i==="HEAD"?`${i} ${l}`:void 0,g=c===void 0?void 0:`${u}\0${c}`;return r===void 0||a.cacheKey===!1||g===void 0?await I(e,l,o,s):await r.read(g,d=>I(e,l,o,d))(s)},get:(t,a={})=>n.request(t,a),post:(t,a={})=>n.request(t,{method:"POST",...a}),put:(t,a={})=>n.request(t,{method:"PUT",...a}),patch:(t,a={})=>n.request(t,{method:"PATCH",...a}),remove:(t,a={})=>n.request(t,{method:"DELETE",...a}),with:t=>H({...e,...t,cache:t.cache??r??!1})};return n}export{A as DataContext,f as FirsthandDirectiveError,w as FirsthandHttpError,m as anyTagMatches,O as createCacheClient,P as createData,H as createFetchClient,K as fromObservable,B as fromPromise,re as parseGraphQL,ae as resolveTags,k as stableKey,R as tag,L as tagMatches,M as useAction,y as useData,G as useInvalidate,V as useResource};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@firsthandjs/data",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
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.6.
|
|
32
|
-
"@firsthandjs/dom": "0.6.
|
|
31
|
+
"@firsthandjs/core": "0.6.2",
|
|
32
|
+
"@firsthandjs/dom": "0.6.2"
|
|
33
33
|
},
|
|
34
34
|
"engines": {
|
|
35
35
|
"node": ">=20.11.0"
|