@fluixi/reactive 1.0.0-alpha.100
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/LICENSE +21 -0
- package/README.md +280 -0
- package/dist/cdn/reactive.cjs +2803 -0
- package/dist/cdn/reactive.d.ts +11 -0
- package/dist/cdn/reactive.d.ts.map +1 -0
- package/dist/cdn/reactive.global.js +2 -0
- package/dist/cdn/reactive.js +10 -0
- package/dist/cdn/reactive.mjs +2787 -0
- package/dist/cdn/signal.cjs +299 -0
- package/dist/cdn/signal.d.ts +19 -0
- package/dist/cdn/signal.d.ts.map +1 -0
- package/dist/cdn/signal.global.js +2 -0
- package/dist/cdn/signal.js +18 -0
- package/dist/cdn/signal.mjs +276 -0
- package/dist/index.cjs +2803 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/index.mjs +2787 -0
- package/dist/lib/index.cjs +2804 -0
- package/dist/lib/index.d.ts +8 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +11 -0
- package/dist/lib/index.mjs +2789 -0
- package/dist/lib/observable/index.cjs +386 -0
- package/dist/lib/observable/index.d.ts +4 -0
- package/dist/lib/observable/index.d.ts.map +1 -0
- package/dist/lib/observable/index.js +3 -0
- package/dist/lib/observable/index.mjs +364 -0
- package/dist/lib/observable/observable.cjs +384 -0
- package/dist/lib/observable/observable.d.ts +246 -0
- package/dist/lib/observable/observable.d.ts.map +1 -0
- package/dist/lib/observable/observable.js +268 -0
- package/dist/lib/observable/observable.mjs +361 -0
- package/dist/lib/observable/subscription.cjs +180 -0
- package/dist/lib/observable/subscription.d.ts +58 -0
- package/dist/lib/observable/subscription.d.ts.map +1 -0
- package/dist/lib/observable/subscription.js +177 -0
- package/dist/lib/observable/subscription.mjs +157 -0
- package/dist/lib/observable/types.cjs +18 -0
- package/dist/lib/observable/types.d.ts +44 -0
- package/dist/lib/observable/types.d.ts.map +1 -0
- package/dist/lib/observable/types.js +1 -0
- package/dist/lib/observable/types.mjs +0 -0
- package/dist/lib/predicates.cjs +44 -0
- package/dist/lib/predicates.d.ts +37 -0
- package/dist/lib/predicates.d.ts.map +1 -0
- package/dist/lib/predicates.js +62 -0
- package/dist/lib/predicates.mjs +23 -0
- package/dist/lib/signal/api.cjs +592 -0
- package/dist/lib/signal/api.d.ts +227 -0
- package/dist/lib/signal/api.d.ts.map +1 -0
- package/dist/lib/signal/api.js +228 -0
- package/dist/lib/signal/api.mjs +569 -0
- package/dist/lib/signal/cached-resource.cjs +726 -0
- package/dist/lib/signal/cached-resource.d.ts +86 -0
- package/dist/lib/signal/cached-resource.d.ts.map +1 -0
- package/dist/lib/signal/cached-resource.js +250 -0
- package/dist/lib/signal/cached-resource.mjs +704 -0
- package/dist/lib/signal/graph/children.cjs +326 -0
- package/dist/lib/signal/graph/children.d.ts +2 -0
- package/dist/lib/signal/graph/children.d.ts.map +1 -0
- package/dist/lib/signal/graph/children.js +30 -0
- package/dist/lib/signal/graph/children.mjs +303 -0
- package/dist/lib/signal/graph/compat.cjs +590 -0
- package/dist/lib/signal/graph/compat.d.ts +66 -0
- package/dist/lib/signal/graph/compat.d.ts.map +1 -0
- package/dist/lib/signal/graph/compat.js +197 -0
- package/dist/lib/signal/graph/compat.mjs +568 -0
- package/dist/lib/signal/graph/core.cjs +295 -0
- package/dist/lib/signal/graph/core.d.ts +99 -0
- package/dist/lib/signal/graph/core.d.ts.map +1 -0
- package/dist/lib/signal/graph/core.js +268 -0
- package/dist/lib/signal/graph/core.mjs +273 -0
- package/dist/lib/signal/graph/debounced.cjs +680 -0
- package/dist/lib/signal/graph/debounced.d.ts +67 -0
- package/dist/lib/signal/graph/debounced.d.ts.map +1 -0
- package/dist/lib/signal/graph/debounced.js +92 -0
- package/dist/lib/signal/graph/debounced.mjs +657 -0
- package/dist/lib/signal/graph/derived.cjs +508 -0
- package/dist/lib/signal/graph/derived.d.ts +14 -0
- package/dist/lib/signal/graph/derived.d.ts.map +1 -0
- package/dist/lib/signal/graph/derived.js +65 -0
- package/dist/lib/signal/graph/derived.mjs +485 -0
- package/dist/lib/signal/graph/detect.cjs +66 -0
- package/dist/lib/signal/graph/detect.d.ts +17 -0
- package/dist/lib/signal/graph/detect.d.ts.map +1 -0
- package/dist/lib/signal/graph/detect.js +39 -0
- package/dist/lib/signal/graph/detect.mjs +45 -0
- package/dist/lib/signal/graph/error-boundary.cjs +313 -0
- package/dist/lib/signal/graph/error-boundary.d.ts +16 -0
- package/dist/lib/signal/graph/error-boundary.d.ts.map +1 -0
- package/dist/lib/signal/graph/error-boundary.js +34 -0
- package/dist/lib/signal/graph/error-boundary.mjs +290 -0
- package/dist/lib/signal/graph/index.cjs +305 -0
- package/dist/lib/signal/graph/index.d.ts +22 -0
- package/dist/lib/signal/graph/index.d.ts.map +1 -0
- package/dist/lib/signal/graph/index.js +21 -0
- package/dist/lib/signal/graph/index.mjs +283 -0
- package/dist/lib/signal/graph/observe.cjs +198 -0
- package/dist/lib/signal/graph/observe.d.ts +185 -0
- package/dist/lib/signal/graph/observe.d.ts.map +1 -0
- package/dist/lib/signal/graph/observe.js +198 -0
- package/dist/lib/signal/graph/observe.mjs +175 -0
- package/dist/lib/signal/graph/resource.cjs +632 -0
- package/dist/lib/signal/graph/resource.d.ts +39 -0
- package/dist/lib/signal/graph/resource.d.ts.map +1 -0
- package/dist/lib/signal/graph/resource.js +165 -0
- package/dist/lib/signal/graph/resource.mjs +609 -0
- package/dist/lib/signal/graph/runtime.cjs +522 -0
- package/dist/lib/signal/graph/runtime.d.ts +80 -0
- package/dist/lib/signal/graph/runtime.d.ts.map +1 -0
- package/dist/lib/signal/graph/runtime.js +358 -0
- package/dist/lib/signal/graph/runtime.mjs +499 -0
- package/dist/lib/signal/graph/signal-next.cjs +1249 -0
- package/dist/lib/signal/graph/signal-next.d.ts +38 -0
- package/dist/lib/signal/graph/signal-next.d.ts.map +1 -0
- package/dist/lib/signal/graph/signal-next.js +44 -0
- package/dist/lib/signal/graph/signal-next.mjs +1227 -0
- package/dist/lib/signal/graph/ssr-hooks.cjs +89 -0
- package/dist/lib/signal/graph/ssr-hooks.d.ts +51 -0
- package/dist/lib/signal/graph/ssr-hooks.d.ts.map +1 -0
- package/dist/lib/signal/graph/ssr-hooks.js +80 -0
- package/dist/lib/signal/graph/ssr-hooks.mjs +68 -0
- package/dist/lib/signal/graph/state.cjs +72 -0
- package/dist/lib/signal/graph/state.d.ts +76 -0
- package/dist/lib/signal/graph/state.d.ts.map +1 -0
- package/dist/lib/signal/graph/state.js +99 -0
- package/dist/lib/signal/graph/state.mjs +49 -0
- package/dist/lib/signal/graph/suspense.cjs +313 -0
- package/dist/lib/signal/graph/suspense.d.ts +13 -0
- package/dist/lib/signal/graph/suspense.d.ts.map +1 -0
- package/dist/lib/signal/graph/suspense.js +36 -0
- package/dist/lib/signal/graph/suspense.mjs +290 -0
- package/dist/lib/signal/index-legacy.cjs +2582 -0
- package/dist/lib/signal/index-legacy.d.ts +12 -0
- package/dist/lib/signal/index-legacy.d.ts.map +1 -0
- package/dist/lib/signal/index-legacy.js +11 -0
- package/dist/lib/signal/index-legacy.mjs +2560 -0
- package/dist/lib/signal/index.cjs +1350 -0
- package/dist/lib/signal/index.d.ts +19 -0
- package/dist/lib/signal/index.d.ts.map +1 -0
- package/dist/lib/signal/index.js +19 -0
- package/dist/lib/signal/index.mjs +1328 -0
- package/dist/lib/signal/list.cjs +561 -0
- package/dist/lib/signal/list.d.ts +39 -0
- package/dist/lib/signal/list.d.ts.map +1 -0
- package/dist/lib/signal/list.js +172 -0
- package/dist/lib/signal/list.mjs +538 -0
- package/dist/lib/signal/resource-api.cjs +638 -0
- package/dist/lib/signal/resource-api.d.ts +19 -0
- package/dist/lib/signal/resource-api.d.ts.map +1 -0
- package/dist/lib/signal/resource-api.js +27 -0
- package/dist/lib/signal/resource-api.mjs +615 -0
- package/dist/lib/signal/signal.cjs +2035 -0
- package/dist/lib/signal/signal.d.ts +303 -0
- package/dist/lib/signal/signal.d.ts.map +1 -0
- package/dist/lib/signal/signal.js +1233 -0
- package/dist/lib/signal/signal.mjs +2012 -0
- package/dist/lib/signal/types.cjs +38 -0
- package/dist/lib/signal/types.d.ts +133 -0
- package/dist/lib/signal/types.d.ts.map +1 -0
- package/dist/lib/signal/types.js +38 -0
- package/dist/lib/signal/types.mjs +17 -0
- package/dist/lib/signal/utilities.cjs +1024 -0
- package/dist/lib/signal/utilities.d.ts +247 -0
- package/dist/lib/signal/utilities.d.ts.map +1 -0
- package/dist/lib/signal/utilities.js +346 -0
- package/dist/lib/signal/utilities.mjs +1002 -0
- package/dist/lib/store/api.cjs +662 -0
- package/dist/lib/store/api.d.ts +40 -0
- package/dist/lib/store/api.d.ts.map +1 -0
- package/dist/lib/store/api.js +63 -0
- package/dist/lib/store/api.mjs +646 -0
- package/dist/lib/store/index.cjs +741 -0
- package/dist/lib/store/index.d.ts +16 -0
- package/dist/lib/store/index.d.ts.map +1 -0
- package/dist/lib/store/index.js +13 -0
- package/dist/lib/store/index.mjs +726 -0
- package/dist/lib/store/setter.types.cjs +18 -0
- package/dist/lib/store/setter.types.d.ts +131 -0
- package/dist/lib/store/setter.types.d.ts.map +1 -0
- package/dist/lib/store/setter.types.js +65 -0
- package/dist/lib/store/setter.types.mjs +0 -0
- package/dist/lib/store/store.cjs +708 -0
- package/dist/lib/store/store.d.ts +142 -0
- package/dist/lib/store/store.d.ts.map +1 -0
- package/dist/lib/store/store.js +812 -0
- package/dist/lib/store/store.mjs +694 -0
- package/dist/lib/store/types.cjs +18 -0
- package/dist/lib/store/types.d.ts +57 -0
- package/dist/lib/store/types.d.ts.map +1 -0
- package/dist/lib/store/types.js +1 -0
- package/dist/lib/store/types.mjs +0 -0
- package/dist/lib/store/utils.cjs +43 -0
- package/dist/lib/store/utils.d.ts +13 -0
- package/dist/lib/store/utils.d.ts.map +1 -0
- package/dist/lib/store/utils.js +29 -0
- package/dist/lib/store/utils.mjs +22 -0
- package/dist/lib/subject.cjs +450 -0
- package/dist/lib/subject.d.ts +14 -0
- package/dist/lib/subject.d.ts.map +1 -0
- package/dist/lib/subject.js +81 -0
- package/dist/lib/subject.mjs +427 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -0
- package/dist/version.generated.cjs +27 -0
- package/dist/version.generated.d.ts +3 -0
- package/dist/version.generated.d.ts.map +1 -0
- package/dist/version.generated.js +3 -0
- package/dist/version.generated.mjs +7 -0
- package/package.json +115 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cachedResource`: a resource whose results are kept, by key, for a TTL.
|
|
3
|
+
*
|
|
4
|
+
* Fetches once and serves the cached result to later reads within the TTL; after it, the
|
|
5
|
+
* next read fetches again, showing the stale value meanwhile unless `staleWhileRevalidate`
|
|
6
|
+
* is off. Readers of the same key share one request in flight.
|
|
7
|
+
*
|
|
8
|
+
* const workspace = cachedResource(workspaceId, (id) => api.get(`/workspaces/${id}`), {
|
|
9
|
+
* name: 'workspace',
|
|
10
|
+
* ttl: 5 * 60_000,
|
|
11
|
+
* });
|
|
12
|
+
* workspace(); // the value; suspends under <Suspense> on the first load
|
|
13
|
+
* workspace.stale; // true while a background refetch runs
|
|
14
|
+
* workspace.invalidate(); // drop the entry and fetch again
|
|
15
|
+
*
|
|
16
|
+
* Where entries live is the cache's business (`ResourceCache`). By default: one cache per
|
|
17
|
+
* request on the server, so visitors never share an entry or an in-flight fetch; the page in
|
|
18
|
+
* a browser. Pass `cache` to keep them elsewhere, for instance a storage that survives a reload.
|
|
19
|
+
*
|
|
20
|
+
* A value fetched during the server render travels to the browser with the page, like a
|
|
21
|
+
* plain resource's, and seeds the first read there instead of a second fetch.
|
|
22
|
+
*
|
|
23
|
+
* It runs on the live graph. Suspense needs no bookkeeping here: the accessor throws the
|
|
24
|
+
* pending promise and the runtime finds the nearest boundary from the reading effect.
|
|
25
|
+
*/
|
|
26
|
+
import { type ResourceCache } from './graph/ssr-hooks.js';
|
|
27
|
+
import type { Setter, Resource } from './signal.js';
|
|
28
|
+
export interface CachedResourceOptions {
|
|
29
|
+
/**
|
|
30
|
+
* Cache lifetime in milliseconds.
|
|
31
|
+
* Default: `Infinity`, data lives until `invalidate()` is called.
|
|
32
|
+
*/
|
|
33
|
+
ttl?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Cache namespace. Two calls that share the same `name` share their cache
|
|
36
|
+
* entries. When omitted, each `cachedResource` call gets a
|
|
37
|
+
* private cache isolated from all others.
|
|
38
|
+
*/
|
|
39
|
+
name?: string;
|
|
40
|
+
/**
|
|
41
|
+
* When `true` (default), stale data is returned immediately while a
|
|
42
|
+
* background re-fetch runs. The resource stays in `state='ready'` and
|
|
43
|
+
* `stale=true` during revalidation, no loading skeleton.
|
|
44
|
+
*
|
|
45
|
+
* When `false`, a foreground re-fetch is triggered that sets
|
|
46
|
+
* `state='refreshing'` (data is still visible but `loading=true`).
|
|
47
|
+
*/
|
|
48
|
+
staleWhileRevalidate?: boolean;
|
|
49
|
+
/** Where entries live. Defaults to the request's cache on the server and the page's in a browser. */
|
|
50
|
+
cache?: ResourceCache;
|
|
51
|
+
/** Carry the server-fetched value to the browser with the page. Default true. */
|
|
52
|
+
transport?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Other values the data depends on: the signed-in user, the active organisation. Each
|
|
55
|
+
* combination is cached apart and a change reads (or fetches) its own entry. The fetcher
|
|
56
|
+
* still receives only the source's key.
|
|
57
|
+
*/
|
|
58
|
+
deps?: ReadonlyArray<() => unknown>;
|
|
59
|
+
}
|
|
60
|
+
export type CachedResource<T> = Resource<T> & {
|
|
61
|
+
/** `true` while serving stale data during a background re-fetch. */
|
|
62
|
+
stale: boolean;
|
|
63
|
+
};
|
|
64
|
+
export interface CachedResourceActions<T> {
|
|
65
|
+
mutate: Setter<T | undefined>;
|
|
66
|
+
/** Fetch again now, whatever the TTL. The current value stays visible. */
|
|
67
|
+
refetch: () => void;
|
|
68
|
+
/** Drop the entry for the current key and fetch again. */
|
|
69
|
+
invalidate: () => void;
|
|
70
|
+
}
|
|
71
|
+
/** One binding: the accessor, with the actions on it. */
|
|
72
|
+
export type CachedResourceHandle<T> = CachedResource<T> & CachedResourceActions<T>;
|
|
73
|
+
/** Not keyed by a reactive value. */
|
|
74
|
+
export declare function cachedResource<T>(fetcher: () => Promise<T> | T, options?: CachedResourceOptions): CachedResourceHandle<T>;
|
|
75
|
+
/**
|
|
76
|
+
* Keyed by `source`: a new key reads its own entry, or fetches. A falsy source holds the
|
|
77
|
+
* fetch, as for `resource`.
|
|
78
|
+
*/
|
|
79
|
+
export declare function cachedResource<T, S>(source: () => S | false | null | undefined, fetcher: (key: S) => Promise<T> | T, options?: CachedResourceOptions): CachedResourceHandle<T>;
|
|
80
|
+
/**
|
|
81
|
+
* Drops cached entries in the current scope: those of one `name`, or all of them. On a
|
|
82
|
+
* logout or a workspace switch, so nothing fetched for the previous one is served.
|
|
83
|
+
*/
|
|
84
|
+
export declare function clearCachedResources(name?: string, cache?: ResourceCache): void;
|
|
85
|
+
export { createResourceCache, type ResourceCache, type ResourceCacheEntry } from './graph/ssr-hooks.js';
|
|
86
|
+
//# sourceMappingURL=cached-resource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cached-resource.d.ts","sourceRoot":"","sources":["../../../src/lib/signal/cached-resource.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAUH,OAAO,EAML,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAiB,MAAM,aAAa,CAAC;AAMnE,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;;OAOG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,qGAAqG;IACrG,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,iFAAiF;IACjF,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,IAAI,CAAC,EAAE,aAAa,CAAC,MAAM,OAAO,CAAC,CAAC;CACrC;AAED,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,GAAG;IAC5C,oEAAoE;IACpE,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,MAAM,WAAW,qBAAqB,CAAC,CAAC;IACtC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IAC9B,0EAA0E;IAC1E,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,0DAA0D;IAC1D,UAAU,EAAE,MAAM,IAAI,CAAC;CACxB;AAED,yDAAyD;AACzD,MAAM,MAAM,oBAAoB,CAAC,CAAC,IAAI,cAAc,CAAC,CAAC,CAAC,GAAG,qBAAqB,CAAC,CAAC,CAAC,CAAC;AAUnF,qCAAqC;AACrC,wBAAgB,cAAc,CAAC,CAAC,EAC9B,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,EAC7B,OAAO,CAAC,EAAE,qBAAqB,GAC9B,oBAAoB,CAAC,CAAC,CAAC,CAAC;AAE3B;;;GAGG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,CAAC,EACjC,MAAM,EAAE,MAAM,CAAC,GAAG,KAAK,GAAG,IAAI,GAAG,SAAS,EAC1C,OAAO,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,EACnC,OAAO,CAAC,EAAE,qBAAqB,GAC9B,oBAAoB,CAAC,CAAC,CAAC,CAAC;AA2P3B;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,GAAE,aAAsC,GAAG,IAAI,CAIvG;AAED,OAAO,EAAE,mBAAmB,EAAE,KAAK,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cachedResource`: a resource whose results are kept, by key, for a TTL.
|
|
3
|
+
*
|
|
4
|
+
* Fetches once and serves the cached result to later reads within the TTL; after it, the
|
|
5
|
+
* next read fetches again, showing the stale value meanwhile unless `staleWhileRevalidate`
|
|
6
|
+
* is off. Readers of the same key share one request in flight.
|
|
7
|
+
*
|
|
8
|
+
* const workspace = cachedResource(workspaceId, (id) => api.get(`/workspaces/${id}`), {
|
|
9
|
+
* name: 'workspace',
|
|
10
|
+
* ttl: 5 * 60_000,
|
|
11
|
+
* });
|
|
12
|
+
* workspace(); // the value; suspends under <Suspense> on the first load
|
|
13
|
+
* workspace.stale; // true while a background refetch runs
|
|
14
|
+
* workspace.invalidate(); // drop the entry and fetch again
|
|
15
|
+
*
|
|
16
|
+
* Where entries live is the cache's business (`ResourceCache`). By default: one cache per
|
|
17
|
+
* request on the server, so visitors never share an entry or an in-flight fetch; the page in
|
|
18
|
+
* a browser. Pass `cache` to keep them elsewhere, for instance a storage that survives a reload.
|
|
19
|
+
*
|
|
20
|
+
* A value fetched during the server render travels to the browser with the page, like a
|
|
21
|
+
* plain resource's, and seeds the first read there instead of a second fetch.
|
|
22
|
+
*
|
|
23
|
+
* It runs on the live graph. Suspense needs no bookkeeping here: the accessor throws the
|
|
24
|
+
* pending promise and the runtime finds the nearest boundary from the reading effect.
|
|
25
|
+
*/
|
|
26
|
+
import { createSignal, createEffect, onCleanup, batch, untrack, } from './graph/compat.js';
|
|
27
|
+
// Graph-agnostic SSR hooks, the same ones graph/resource.ts uses.
|
|
28
|
+
import { currentResourceCache, getServerData, nextResourceId, reportResourceData, trackResourcePromise, } from './graph/ssr-hooks.js';
|
|
29
|
+
let _autoId = 0;
|
|
30
|
+
// ── Brand symbols ─────────────────────────────────────────────────────────────
|
|
31
|
+
// Registry symbols, so every copy of every graph agrees on them without importing
|
|
32
|
+
// anything. graph/resource.ts stamps a resource with the same two.
|
|
33
|
+
const $SIGNAL = Symbol.for('signal');
|
|
34
|
+
const $RESOURCE = Symbol.for('resource');
|
|
35
|
+
export function cachedResource(sourceOrFetcher, fetcherOrOptions, opts = {}) {
|
|
36
|
+
// ── Normalise overloads ──────────────────────────────────────────────────
|
|
37
|
+
let sourceFn;
|
|
38
|
+
let fetchFn;
|
|
39
|
+
let options;
|
|
40
|
+
if (typeof fetcherOrOptions === 'function') {
|
|
41
|
+
sourceFn = sourceOrFetcher;
|
|
42
|
+
fetchFn = fetcherOrOptions;
|
|
43
|
+
options = opts;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
// No source: treat the first arg as fetcher, source = () => true
|
|
47
|
+
sourceFn = () => true;
|
|
48
|
+
fetchFn = sourceOrFetcher;
|
|
49
|
+
options = fetcherOrOptions ?? opts;
|
|
50
|
+
}
|
|
51
|
+
const { ttl = Infinity, name = `_auto_${_autoId++}`, staleWhileRevalidate = true, } = options;
|
|
52
|
+
const ns = name;
|
|
53
|
+
const cache = options.cache ?? currentResourceCache();
|
|
54
|
+
const inflight = cache.inflight;
|
|
55
|
+
// The same transport as a plain resource: an id in creation order, matched on both sides.
|
|
56
|
+
const ssrId = options.transport === false ? undefined : nextResourceId();
|
|
57
|
+
const seed = ssrId !== undefined ? getServerData(ssrId) : undefined;
|
|
58
|
+
const deps = options.deps ?? [];
|
|
59
|
+
// Tracked inside the effect, untracked from invalidate().
|
|
60
|
+
const depValues = () => deps.map((dep) => dep());
|
|
61
|
+
const buildKey = (key, values = untrack(depValues)) => `${ns}:${JSON.stringify(key)}` + (deps.length ? `:${JSON.stringify(values)}` : '');
|
|
62
|
+
const isFresh = (entry) => ttl === Infinity || Date.now() - entry.timestamp <= ttl;
|
|
63
|
+
// ── Reactive state ────────────────────────────────────────────────────────
|
|
64
|
+
const [value, setValue] = createSignal(undefined, { equals: false });
|
|
65
|
+
const [resourceState, setResourceState] = createSignal('unresolved', { equals: false });
|
|
66
|
+
const [resourceError, setResourceError] = createSignal(undefined, { equals: false });
|
|
67
|
+
const [pendingP, setPendingP] = createSignal(undefined, { equals: false });
|
|
68
|
+
const [isStale, setIsStale] = createSignal(false);
|
|
69
|
+
const [tick, setTick] = createSignal(0, { equals: false });
|
|
70
|
+
const UNSET = Symbol('unset');
|
|
71
|
+
let prevKey = UNSET;
|
|
72
|
+
let flagRefetch = false;
|
|
73
|
+
let flagInvalidate = false;
|
|
74
|
+
// ── Fetch helper ──────────────────────────────────────────────────────────
|
|
75
|
+
function doFetch(key, ck, background, isCancelled) {
|
|
76
|
+
// Attach to an in-flight request for the same key (dedup).
|
|
77
|
+
const running = inflight.get(ck);
|
|
78
|
+
if (running) {
|
|
79
|
+
running.then((v) => { if (!isCancelled())
|
|
80
|
+
settle(v, background); }, (e) => { if (!isCancelled() && !background)
|
|
81
|
+
fail(e); });
|
|
82
|
+
if (!background) {
|
|
83
|
+
trackResourcePromise(running);
|
|
84
|
+
setPendingP(running);
|
|
85
|
+
}
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const promise = Promise.resolve(fetchFn(key)).then((v) => {
|
|
89
|
+
cache.set(ck, { value: v, timestamp: Date.now() });
|
|
90
|
+
inflight.delete(ck);
|
|
91
|
+
return v;
|
|
92
|
+
}, (e) => {
|
|
93
|
+
inflight.delete(ck);
|
|
94
|
+
throw e;
|
|
95
|
+
});
|
|
96
|
+
inflight.set(ck, promise);
|
|
97
|
+
if (!background) {
|
|
98
|
+
trackResourcePromise(promise); // server async SSR: collect & await
|
|
99
|
+
setPendingP(promise);
|
|
100
|
+
// No boundary bookkeeping here. The accessor throws the pending promise, and
|
|
101
|
+
// the runtime's suspend handler finds the nearest boundary from the effect that
|
|
102
|
+
// was reading. graph/resource.ts leaves it to the same path.
|
|
103
|
+
}
|
|
104
|
+
promise.then((v) => { if (!isCancelled())
|
|
105
|
+
settle(v, background); }, (e) => { if (!isCancelled() && !background)
|
|
106
|
+
fail(e); });
|
|
107
|
+
}
|
|
108
|
+
function settle(v, _background) {
|
|
109
|
+
if (ssrId !== undefined)
|
|
110
|
+
reportResourceData(ssrId, v); // serialize for hydration
|
|
111
|
+
batch(() => {
|
|
112
|
+
setValue(() => v);
|
|
113
|
+
setResourceState('ready');
|
|
114
|
+
setIsStale(false);
|
|
115
|
+
setPendingP(undefined);
|
|
116
|
+
setResourceError(undefined);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
function fail(e) {
|
|
120
|
+
batch(() => {
|
|
121
|
+
setResourceError(e);
|
|
122
|
+
setResourceState('errored');
|
|
123
|
+
setPendingP(undefined);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
// ── Main effect ───────────────────────────────────────────────────────────
|
|
127
|
+
createEffect(() => {
|
|
128
|
+
const key = sourceFn();
|
|
129
|
+
const values = depValues();
|
|
130
|
+
tick(); // track manual triggers
|
|
131
|
+
// Suspend fetch when source is falsy (mirrors createResource)
|
|
132
|
+
if (key === false || key === null || key === undefined) {
|
|
133
|
+
prevKey = UNSET;
|
|
134
|
+
batch(() => {
|
|
135
|
+
setResourceState('unresolved');
|
|
136
|
+
setPendingP(undefined);
|
|
137
|
+
setResourceError(undefined);
|
|
138
|
+
});
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const doRefetch = flagRefetch;
|
|
142
|
+
const doInvalidate = flagInvalidate;
|
|
143
|
+
flagRefetch = false;
|
|
144
|
+
flagInvalidate = false;
|
|
145
|
+
const ck = buildKey(key, values);
|
|
146
|
+
// A value the server fetched for this page: the first read takes it as fresh.
|
|
147
|
+
if (seed && prevKey === UNSET && !cache.get(ck))
|
|
148
|
+
cache.set(ck, { value: seed.value, timestamp: Date.now() });
|
|
149
|
+
// Clear cache on explicit invalidate
|
|
150
|
+
if (doInvalidate)
|
|
151
|
+
cache.delete(ck);
|
|
152
|
+
const keyChanged = prevKey === UNSET || key !== prevKey;
|
|
153
|
+
prevKey = key;
|
|
154
|
+
let cancelled = false;
|
|
155
|
+
onCleanup(() => { cancelled = true; });
|
|
156
|
+
const cached = cache.get(ck);
|
|
157
|
+
// ── Cache HIT (fresh) ─────────────────────────────────────────────────
|
|
158
|
+
if (cached && isFresh(cached) && !doRefetch && !doInvalidate) {
|
|
159
|
+
// Serve synchronously, no loading state, no fetch.
|
|
160
|
+
if (ssrId !== undefined)
|
|
161
|
+
reportResourceData(ssrId, cached.value);
|
|
162
|
+
const cur = untrack(resourceState);
|
|
163
|
+
if (cur !== 'ready' || untrack(value) !== cached.value) {
|
|
164
|
+
batch(() => {
|
|
165
|
+
setValue(() => cached.value);
|
|
166
|
+
setResourceState('ready');
|
|
167
|
+
setIsStale(false);
|
|
168
|
+
setResourceError(undefined);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
// ── Cache HIT (stale) + staleWhileRevalidate ──────────────────────────
|
|
174
|
+
if (cached && staleWhileRevalidate && !doInvalidate) {
|
|
175
|
+
// Show stale value immediately, background-fetch new data.
|
|
176
|
+
batch(() => {
|
|
177
|
+
setValue(() => cached.value);
|
|
178
|
+
setResourceState('ready');
|
|
179
|
+
setIsStale(true);
|
|
180
|
+
setResourceError(undefined);
|
|
181
|
+
setPendingP(undefined);
|
|
182
|
+
});
|
|
183
|
+
doFetch(key, ck, true /* background */, () => cancelled);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
// ── No cache / invalidated / manual-refetch / SWR=false ──────────────
|
|
187
|
+
// Show loading state (keeps current value visible during 'refreshing').
|
|
188
|
+
const cur = untrack(resourceState);
|
|
189
|
+
const isReload = cur === 'ready' || cur === 'refreshing';
|
|
190
|
+
batch(() => {
|
|
191
|
+
setResourceState(isReload ? 'refreshing' : 'pending');
|
|
192
|
+
setResourceError(undefined);
|
|
193
|
+
setIsStale(false);
|
|
194
|
+
});
|
|
195
|
+
doFetch(key, ck, false /* foreground */, () => cancelled);
|
|
196
|
+
});
|
|
197
|
+
// ── Resource accessor ─────────────────────────────────────────────────────
|
|
198
|
+
// During 'pending': throw the pending promise -> Suspense shows skeleton.
|
|
199
|
+
// During 'refreshing': return stale value (no Suspense regression).
|
|
200
|
+
// During 'ready'/'errored': return value or throw error.
|
|
201
|
+
const resource = (() => {
|
|
202
|
+
const e = resourceError();
|
|
203
|
+
if (e)
|
|
204
|
+
throw e;
|
|
205
|
+
const s = resourceState();
|
|
206
|
+
if (s === 'pending') {
|
|
207
|
+
const p = pendingP();
|
|
208
|
+
if (p)
|
|
209
|
+
throw p;
|
|
210
|
+
}
|
|
211
|
+
return value();
|
|
212
|
+
});
|
|
213
|
+
Object.defineProperty(resource, $RESOURCE, { value: true, enumerable: true });
|
|
214
|
+
Object.defineProperty(resource, $SIGNAL, { value: true, enumerable: true });
|
|
215
|
+
Object.defineProperties(resource, {
|
|
216
|
+
state: { get: resourceState },
|
|
217
|
+
loading: { get: () => { const s = resourceState(); return s === 'pending' || s === 'refreshing'; } },
|
|
218
|
+
error: { get: resourceError },
|
|
219
|
+
latest: { get: value },
|
|
220
|
+
stale: { get: isStale },
|
|
221
|
+
});
|
|
222
|
+
// ── Actions ───────────────────────────────────────────────────────────────
|
|
223
|
+
const refetch = () => {
|
|
224
|
+
flagRefetch = true;
|
|
225
|
+
setTick(v => v + 1);
|
|
226
|
+
};
|
|
227
|
+
const invalidate = () => {
|
|
228
|
+
const key = untrack(sourceFn);
|
|
229
|
+
if (key !== null && key !== undefined && key !== false) {
|
|
230
|
+
cache.delete(buildKey(key));
|
|
231
|
+
}
|
|
232
|
+
flagInvalidate = true;
|
|
233
|
+
setTick(v => v + 1);
|
|
234
|
+
};
|
|
235
|
+
return Object.assign(resource, { mutate: setValue, refetch, invalidate });
|
|
236
|
+
}
|
|
237
|
+
// ── Cache utilities ────────────────────────────────────────────────────────────
|
|
238
|
+
/**
|
|
239
|
+
* Drops cached entries in the current scope: those of one `name`, or all of them. On a
|
|
240
|
+
* logout or a workspace switch, so nothing fetched for the previous one is served.
|
|
241
|
+
*/
|
|
242
|
+
export function clearCachedResources(name, cache = currentResourceCache()) {
|
|
243
|
+
if (name === undefined)
|
|
244
|
+
return cache.clear();
|
|
245
|
+
const prefix = `${name}:`;
|
|
246
|
+
for (const key of [...cache.keys()])
|
|
247
|
+
if (key.startsWith(prefix))
|
|
248
|
+
cache.delete(key);
|
|
249
|
+
}
|
|
250
|
+
export { createResourceCache } from './graph/ssr-hooks.js';
|