@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.
Files changed (211) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +280 -0
  3. package/dist/cdn/reactive.cjs +2803 -0
  4. package/dist/cdn/reactive.d.ts +11 -0
  5. package/dist/cdn/reactive.d.ts.map +1 -0
  6. package/dist/cdn/reactive.global.js +2 -0
  7. package/dist/cdn/reactive.js +10 -0
  8. package/dist/cdn/reactive.mjs +2787 -0
  9. package/dist/cdn/signal.cjs +299 -0
  10. package/dist/cdn/signal.d.ts +19 -0
  11. package/dist/cdn/signal.d.ts.map +1 -0
  12. package/dist/cdn/signal.global.js +2 -0
  13. package/dist/cdn/signal.js +18 -0
  14. package/dist/cdn/signal.mjs +276 -0
  15. package/dist/index.cjs +2803 -0
  16. package/dist/index.d.ts +2 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +1 -0
  19. package/dist/index.mjs +2787 -0
  20. package/dist/lib/index.cjs +2804 -0
  21. package/dist/lib/index.d.ts +8 -0
  22. package/dist/lib/index.d.ts.map +1 -0
  23. package/dist/lib/index.js +11 -0
  24. package/dist/lib/index.mjs +2789 -0
  25. package/dist/lib/observable/index.cjs +386 -0
  26. package/dist/lib/observable/index.d.ts +4 -0
  27. package/dist/lib/observable/index.d.ts.map +1 -0
  28. package/dist/lib/observable/index.js +3 -0
  29. package/dist/lib/observable/index.mjs +364 -0
  30. package/dist/lib/observable/observable.cjs +384 -0
  31. package/dist/lib/observable/observable.d.ts +246 -0
  32. package/dist/lib/observable/observable.d.ts.map +1 -0
  33. package/dist/lib/observable/observable.js +268 -0
  34. package/dist/lib/observable/observable.mjs +361 -0
  35. package/dist/lib/observable/subscription.cjs +180 -0
  36. package/dist/lib/observable/subscription.d.ts +58 -0
  37. package/dist/lib/observable/subscription.d.ts.map +1 -0
  38. package/dist/lib/observable/subscription.js +177 -0
  39. package/dist/lib/observable/subscription.mjs +157 -0
  40. package/dist/lib/observable/types.cjs +18 -0
  41. package/dist/lib/observable/types.d.ts +44 -0
  42. package/dist/lib/observable/types.d.ts.map +1 -0
  43. package/dist/lib/observable/types.js +1 -0
  44. package/dist/lib/observable/types.mjs +0 -0
  45. package/dist/lib/predicates.cjs +44 -0
  46. package/dist/lib/predicates.d.ts +37 -0
  47. package/dist/lib/predicates.d.ts.map +1 -0
  48. package/dist/lib/predicates.js +62 -0
  49. package/dist/lib/predicates.mjs +23 -0
  50. package/dist/lib/signal/api.cjs +592 -0
  51. package/dist/lib/signal/api.d.ts +227 -0
  52. package/dist/lib/signal/api.d.ts.map +1 -0
  53. package/dist/lib/signal/api.js +228 -0
  54. package/dist/lib/signal/api.mjs +569 -0
  55. package/dist/lib/signal/cached-resource.cjs +726 -0
  56. package/dist/lib/signal/cached-resource.d.ts +86 -0
  57. package/dist/lib/signal/cached-resource.d.ts.map +1 -0
  58. package/dist/lib/signal/cached-resource.js +250 -0
  59. package/dist/lib/signal/cached-resource.mjs +704 -0
  60. package/dist/lib/signal/graph/children.cjs +326 -0
  61. package/dist/lib/signal/graph/children.d.ts +2 -0
  62. package/dist/lib/signal/graph/children.d.ts.map +1 -0
  63. package/dist/lib/signal/graph/children.js +30 -0
  64. package/dist/lib/signal/graph/children.mjs +303 -0
  65. package/dist/lib/signal/graph/compat.cjs +590 -0
  66. package/dist/lib/signal/graph/compat.d.ts +66 -0
  67. package/dist/lib/signal/graph/compat.d.ts.map +1 -0
  68. package/dist/lib/signal/graph/compat.js +197 -0
  69. package/dist/lib/signal/graph/compat.mjs +568 -0
  70. package/dist/lib/signal/graph/core.cjs +295 -0
  71. package/dist/lib/signal/graph/core.d.ts +99 -0
  72. package/dist/lib/signal/graph/core.d.ts.map +1 -0
  73. package/dist/lib/signal/graph/core.js +268 -0
  74. package/dist/lib/signal/graph/core.mjs +273 -0
  75. package/dist/lib/signal/graph/debounced.cjs +680 -0
  76. package/dist/lib/signal/graph/debounced.d.ts +67 -0
  77. package/dist/lib/signal/graph/debounced.d.ts.map +1 -0
  78. package/dist/lib/signal/graph/debounced.js +92 -0
  79. package/dist/lib/signal/graph/debounced.mjs +657 -0
  80. package/dist/lib/signal/graph/derived.cjs +508 -0
  81. package/dist/lib/signal/graph/derived.d.ts +14 -0
  82. package/dist/lib/signal/graph/derived.d.ts.map +1 -0
  83. package/dist/lib/signal/graph/derived.js +65 -0
  84. package/dist/lib/signal/graph/derived.mjs +485 -0
  85. package/dist/lib/signal/graph/detect.cjs +66 -0
  86. package/dist/lib/signal/graph/detect.d.ts +17 -0
  87. package/dist/lib/signal/graph/detect.d.ts.map +1 -0
  88. package/dist/lib/signal/graph/detect.js +39 -0
  89. package/dist/lib/signal/graph/detect.mjs +45 -0
  90. package/dist/lib/signal/graph/error-boundary.cjs +313 -0
  91. package/dist/lib/signal/graph/error-boundary.d.ts +16 -0
  92. package/dist/lib/signal/graph/error-boundary.d.ts.map +1 -0
  93. package/dist/lib/signal/graph/error-boundary.js +34 -0
  94. package/dist/lib/signal/graph/error-boundary.mjs +290 -0
  95. package/dist/lib/signal/graph/index.cjs +305 -0
  96. package/dist/lib/signal/graph/index.d.ts +22 -0
  97. package/dist/lib/signal/graph/index.d.ts.map +1 -0
  98. package/dist/lib/signal/graph/index.js +21 -0
  99. package/dist/lib/signal/graph/index.mjs +283 -0
  100. package/dist/lib/signal/graph/observe.cjs +198 -0
  101. package/dist/lib/signal/graph/observe.d.ts +185 -0
  102. package/dist/lib/signal/graph/observe.d.ts.map +1 -0
  103. package/dist/lib/signal/graph/observe.js +198 -0
  104. package/dist/lib/signal/graph/observe.mjs +175 -0
  105. package/dist/lib/signal/graph/resource.cjs +632 -0
  106. package/dist/lib/signal/graph/resource.d.ts +39 -0
  107. package/dist/lib/signal/graph/resource.d.ts.map +1 -0
  108. package/dist/lib/signal/graph/resource.js +165 -0
  109. package/dist/lib/signal/graph/resource.mjs +609 -0
  110. package/dist/lib/signal/graph/runtime.cjs +522 -0
  111. package/dist/lib/signal/graph/runtime.d.ts +80 -0
  112. package/dist/lib/signal/graph/runtime.d.ts.map +1 -0
  113. package/dist/lib/signal/graph/runtime.js +358 -0
  114. package/dist/lib/signal/graph/runtime.mjs +499 -0
  115. package/dist/lib/signal/graph/signal-next.cjs +1249 -0
  116. package/dist/lib/signal/graph/signal-next.d.ts +38 -0
  117. package/dist/lib/signal/graph/signal-next.d.ts.map +1 -0
  118. package/dist/lib/signal/graph/signal-next.js +44 -0
  119. package/dist/lib/signal/graph/signal-next.mjs +1227 -0
  120. package/dist/lib/signal/graph/ssr-hooks.cjs +89 -0
  121. package/dist/lib/signal/graph/ssr-hooks.d.ts +51 -0
  122. package/dist/lib/signal/graph/ssr-hooks.d.ts.map +1 -0
  123. package/dist/lib/signal/graph/ssr-hooks.js +80 -0
  124. package/dist/lib/signal/graph/ssr-hooks.mjs +68 -0
  125. package/dist/lib/signal/graph/state.cjs +72 -0
  126. package/dist/lib/signal/graph/state.d.ts +76 -0
  127. package/dist/lib/signal/graph/state.d.ts.map +1 -0
  128. package/dist/lib/signal/graph/state.js +99 -0
  129. package/dist/lib/signal/graph/state.mjs +49 -0
  130. package/dist/lib/signal/graph/suspense.cjs +313 -0
  131. package/dist/lib/signal/graph/suspense.d.ts +13 -0
  132. package/dist/lib/signal/graph/suspense.d.ts.map +1 -0
  133. package/dist/lib/signal/graph/suspense.js +36 -0
  134. package/dist/lib/signal/graph/suspense.mjs +290 -0
  135. package/dist/lib/signal/index-legacy.cjs +2582 -0
  136. package/dist/lib/signal/index-legacy.d.ts +12 -0
  137. package/dist/lib/signal/index-legacy.d.ts.map +1 -0
  138. package/dist/lib/signal/index-legacy.js +11 -0
  139. package/dist/lib/signal/index-legacy.mjs +2560 -0
  140. package/dist/lib/signal/index.cjs +1350 -0
  141. package/dist/lib/signal/index.d.ts +19 -0
  142. package/dist/lib/signal/index.d.ts.map +1 -0
  143. package/dist/lib/signal/index.js +19 -0
  144. package/dist/lib/signal/index.mjs +1328 -0
  145. package/dist/lib/signal/list.cjs +561 -0
  146. package/dist/lib/signal/list.d.ts +39 -0
  147. package/dist/lib/signal/list.d.ts.map +1 -0
  148. package/dist/lib/signal/list.js +172 -0
  149. package/dist/lib/signal/list.mjs +538 -0
  150. package/dist/lib/signal/resource-api.cjs +638 -0
  151. package/dist/lib/signal/resource-api.d.ts +19 -0
  152. package/dist/lib/signal/resource-api.d.ts.map +1 -0
  153. package/dist/lib/signal/resource-api.js +27 -0
  154. package/dist/lib/signal/resource-api.mjs +615 -0
  155. package/dist/lib/signal/signal.cjs +2035 -0
  156. package/dist/lib/signal/signal.d.ts +303 -0
  157. package/dist/lib/signal/signal.d.ts.map +1 -0
  158. package/dist/lib/signal/signal.js +1233 -0
  159. package/dist/lib/signal/signal.mjs +2012 -0
  160. package/dist/lib/signal/types.cjs +38 -0
  161. package/dist/lib/signal/types.d.ts +133 -0
  162. package/dist/lib/signal/types.d.ts.map +1 -0
  163. package/dist/lib/signal/types.js +38 -0
  164. package/dist/lib/signal/types.mjs +17 -0
  165. package/dist/lib/signal/utilities.cjs +1024 -0
  166. package/dist/lib/signal/utilities.d.ts +247 -0
  167. package/dist/lib/signal/utilities.d.ts.map +1 -0
  168. package/dist/lib/signal/utilities.js +346 -0
  169. package/dist/lib/signal/utilities.mjs +1002 -0
  170. package/dist/lib/store/api.cjs +662 -0
  171. package/dist/lib/store/api.d.ts +40 -0
  172. package/dist/lib/store/api.d.ts.map +1 -0
  173. package/dist/lib/store/api.js +63 -0
  174. package/dist/lib/store/api.mjs +646 -0
  175. package/dist/lib/store/index.cjs +741 -0
  176. package/dist/lib/store/index.d.ts +16 -0
  177. package/dist/lib/store/index.d.ts.map +1 -0
  178. package/dist/lib/store/index.js +13 -0
  179. package/dist/lib/store/index.mjs +726 -0
  180. package/dist/lib/store/setter.types.cjs +18 -0
  181. package/dist/lib/store/setter.types.d.ts +131 -0
  182. package/dist/lib/store/setter.types.d.ts.map +1 -0
  183. package/dist/lib/store/setter.types.js +65 -0
  184. package/dist/lib/store/setter.types.mjs +0 -0
  185. package/dist/lib/store/store.cjs +708 -0
  186. package/dist/lib/store/store.d.ts +142 -0
  187. package/dist/lib/store/store.d.ts.map +1 -0
  188. package/dist/lib/store/store.js +812 -0
  189. package/dist/lib/store/store.mjs +694 -0
  190. package/dist/lib/store/types.cjs +18 -0
  191. package/dist/lib/store/types.d.ts +57 -0
  192. package/dist/lib/store/types.d.ts.map +1 -0
  193. package/dist/lib/store/types.js +1 -0
  194. package/dist/lib/store/types.mjs +0 -0
  195. package/dist/lib/store/utils.cjs +43 -0
  196. package/dist/lib/store/utils.d.ts +13 -0
  197. package/dist/lib/store/utils.d.ts.map +1 -0
  198. package/dist/lib/store/utils.js +29 -0
  199. package/dist/lib/store/utils.mjs +22 -0
  200. package/dist/lib/subject.cjs +450 -0
  201. package/dist/lib/subject.d.ts +14 -0
  202. package/dist/lib/subject.d.ts.map +1 -0
  203. package/dist/lib/subject.js +81 -0
  204. package/dist/lib/subject.mjs +427 -0
  205. package/dist/tsconfig.lib.tsbuildinfo +1 -0
  206. package/dist/version.generated.cjs +27 -0
  207. package/dist/version.generated.d.ts +3 -0
  208. package/dist/version.generated.d.ts.map +1 -0
  209. package/dist/version.generated.js +3 -0
  210. package/dist/version.generated.mjs +7 -0
  211. 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';