@fluixi/reactive 1.0.0-alpha.91 → 1.0.0-alpha.93

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 (92) hide show
  1. package/dist/cdn/reactive.cjs +105 -35
  2. package/dist/cdn/reactive.global.js +2 -2
  3. package/dist/cdn/reactive.mjs +105 -35
  4. package/dist/cdn/signal.cjs +1 -1
  5. package/dist/cdn/signal.global.js +2 -2
  6. package/dist/cdn/signal.mjs +1 -1
  7. package/dist/index.cjs +105 -35
  8. package/dist/index.mjs +105 -35
  9. package/dist/lib/index.cjs +106 -36
  10. package/dist/lib/index.mjs +106 -36
  11. package/dist/lib/observable/index.cjs +1 -1
  12. package/dist/lib/observable/index.mjs +1 -1
  13. package/dist/lib/signal/api.cjs +11 -10
  14. package/dist/lib/signal/api.d.ts +5 -1
  15. package/dist/lib/signal/api.d.ts.map +1 -1
  16. package/dist/lib/signal/api.js +4 -2
  17. package/dist/lib/signal/api.mjs +11 -10
  18. package/dist/lib/signal/cached-resource.cjs +77 -34
  19. package/dist/lib/signal/cached-resource.d.ts +48 -68
  20. package/dist/lib/signal/cached-resource.d.ts.map +1 -1
  21. package/dist/lib/signal/cached-resource.js +59 -72
  22. package/dist/lib/signal/cached-resource.mjs +77 -34
  23. package/dist/lib/signal/graph/children.cjs +3 -2
  24. package/dist/lib/signal/graph/children.mjs +3 -2
  25. package/dist/lib/signal/graph/compat.cjs +10 -9
  26. package/dist/lib/signal/graph/compat.d.ts +3 -1
  27. package/dist/lib/signal/graph/compat.d.ts.map +1 -1
  28. package/dist/lib/signal/graph/compat.js +2 -2
  29. package/dist/lib/signal/graph/compat.mjs +10 -9
  30. package/dist/lib/signal/graph/core.cjs +2 -2
  31. package/dist/lib/signal/graph/core.mjs +2 -2
  32. package/dist/lib/signal/graph/debounced.cjs +9 -8
  33. package/dist/lib/signal/graph/debounced.mjs +9 -8
  34. package/dist/lib/signal/graph/derived.cjs +9 -8
  35. package/dist/lib/signal/graph/derived.mjs +9 -8
  36. package/dist/lib/signal/graph/error-boundary.cjs +3 -2
  37. package/dist/lib/signal/graph/error-boundary.mjs +3 -2
  38. package/dist/lib/signal/graph/index.cjs +2 -2
  39. package/dist/lib/signal/graph/index.mjs +2 -2
  40. package/dist/lib/signal/graph/observe.cjs +38 -3
  41. package/dist/lib/signal/graph/observe.d.ts +44 -0
  42. package/dist/lib/signal/graph/observe.d.ts.map +1 -1
  43. package/dist/lib/signal/graph/observe.js +50 -1
  44. package/dist/lib/signal/graph/observe.mjs +38 -3
  45. package/dist/lib/signal/graph/resource.cjs +9 -8
  46. package/dist/lib/signal/graph/resource.mjs +9 -8
  47. package/dist/lib/signal/graph/runtime.cjs +7 -6
  48. package/dist/lib/signal/graph/runtime.d.ts +1 -1
  49. package/dist/lib/signal/graph/runtime.d.ts.map +1 -1
  50. package/dist/lib/signal/graph/runtime.js +4 -4
  51. package/dist/lib/signal/graph/runtime.mjs +7 -6
  52. package/dist/lib/signal/graph/signal-next.cjs +39 -9
  53. package/dist/lib/signal/graph/signal-next.d.ts +1 -1
  54. package/dist/lib/signal/graph/signal-next.d.ts.map +1 -1
  55. package/dist/lib/signal/graph/signal-next.js +1 -1
  56. package/dist/lib/signal/graph/signal-next.mjs +39 -9
  57. package/dist/lib/signal/graph/ssr-hooks.cjs +29 -0
  58. package/dist/lib/signal/graph/ssr-hooks.d.ts +24 -0
  59. package/dist/lib/signal/graph/ssr-hooks.d.ts.map +1 -1
  60. package/dist/lib/signal/graph/ssr-hooks.js +36 -0
  61. package/dist/lib/signal/graph/ssr-hooks.mjs +29 -0
  62. package/dist/lib/signal/graph/state.cjs +1 -1
  63. package/dist/lib/signal/graph/state.mjs +1 -1
  64. package/dist/lib/signal/graph/suspense.cjs +3 -2
  65. package/dist/lib/signal/graph/suspense.mjs +3 -2
  66. package/dist/lib/signal/index-legacy.cjs +65 -34
  67. package/dist/lib/signal/index-legacy.mjs +65 -34
  68. package/dist/lib/signal/index.cjs +76 -12
  69. package/dist/lib/signal/index.d.ts +2 -1
  70. package/dist/lib/signal/index.d.ts.map +1 -1
  71. package/dist/lib/signal/index.js +1 -1
  72. package/dist/lib/signal/index.mjs +76 -12
  73. package/dist/lib/signal/list.cjs +3 -2
  74. package/dist/lib/signal/list.mjs +3 -2
  75. package/dist/lib/signal/resource-api.cjs +9 -8
  76. package/dist/lib/signal/resource-api.mjs +9 -8
  77. package/dist/lib/signal/signal.cjs +9 -8
  78. package/dist/lib/signal/signal.mjs +9 -8
  79. package/dist/lib/signal/utilities.cjs +10 -9
  80. package/dist/lib/signal/utilities.mjs +10 -9
  81. package/dist/lib/store/api.cjs +3 -2
  82. package/dist/lib/store/api.mjs +3 -2
  83. package/dist/lib/store/index.cjs +4 -3
  84. package/dist/lib/store/index.mjs +4 -3
  85. package/dist/lib/store/store.cjs +3 -2
  86. package/dist/lib/store/store.mjs +3 -2
  87. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  88. package/dist/version.generated.cjs +2 -2
  89. package/dist/version.generated.d.ts +1 -1
  90. package/dist/version.generated.js +1 -1
  91. package/dist/version.generated.mjs +2 -2
  92. package/package.json +2 -2
@@ -1,65 +1,38 @@
1
1
  /**
2
- * createCachedResource: a reactive resource with TTL-based caching.
2
+ * `cachedResource`: a resource whose results are kept, by key, for a TTL.
3
3
  *
4
- * Fetches once and returns the cached result for subsequent reads within the
5
- * TTL window. After the TTL expires the next read triggers a re-fetch.
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.
6
7
  *
7
- * It runs on the live graph, as of this change. It used to build on `./signal.js`,
8
- * which tracks its current consumer in a module-level `Listener` while everything on
9
- * the default entry tracks through `G.consumer`. The two never saw each other, so
10
- * `createEffect(() => data())` subscribed to nothing and a binding kept whatever it
11
- * first rendered, which on an initial load is the loading state. `cross-graph.spec.ts`
12
- * holds the general shape of that hazard; this file is no longer an instance of it.
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
13
15
  *
14
- * Suspense needs no bookkeeping here. The accessor throws the pending promise and the
15
- * runtime's suspend handler finds the nearest boundary from the effect that was
16
- * reading, which is what `graph/resource.ts` relies on too. The manual increment and
17
- * decrement against a lazily-resolved context went with the port.
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.
18
19
  *
19
- * Two forms (mirrors createResource):
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.
20
22
  *
21
- * // 1. no reactive source
22
- * const [data, { refetch, invalidate }] = createCachedResource(
23
- * () => apiClient.get('/data'),
24
- * { ttl: 5 * 60_000, name: 'my-data' }
25
- * );
26
- *
27
- * // 2. reactive source (re-fetches when source value changes)
28
- * const [data, { refetch, invalidate }] = createCachedResource(
29
- * () => workspaceId(),
30
- * (id) => apiClient.get(`/workspaces/${id}`),
31
- * { ttl: 5 * 60_000, name: 'workspace' }
32
- * );
33
- *
34
- * Options:
35
- * ttl: cache lifetime in ms. Default: Infinity (manual invalidate only)
36
- * name: cache namespace. If omitted, each call-site gets its own isolated cache.
37
- * staleWhileRevalidate, when true (default), show stale data while background-fetching.
38
- * When false, show a loading state during revalidation.
39
- *
40
- * Resource shape (same as createResource):
41
- * data(): current value (throws pending promise during initial load for Suspense)
42
- * data.loading: true during initial load
43
- * data.latest: value without throwing
44
- * data.error: last error
45
- * data.stale: true when showing stale data during background revalidation
46
- *
47
- * Actions:
48
- * refetch(): force re-fetch regardless of TTL, keeps showing current value while loading
49
- * invalidate(): delete cache entry and re-fetch (shows loading on next read)
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.
50
25
  */
51
26
  import { createSignal, createEffect, onCleanup, batch, untrack, } from './graph/compat.js';
52
27
  // Graph-agnostic SSR hooks, the same ones graph/resource.ts uses.
53
- import { trackResourcePromise } from './graph/ssr-hooks.js';
54
- const ttlCache = new Map();
55
- const inflight = new Map();
28
+ import { currentResourceCache, getServerData, nextResourceId, reportResourceData, trackResourcePromise, } from './graph/ssr-hooks.js';
56
29
  let _autoId = 0;
57
30
  // ── Brand symbols ─────────────────────────────────────────────────────────────
58
31
  // Registry symbols, so every copy of every graph agrees on them without importing
59
32
  // anything. graph/resource.ts stamps a resource with the same two.
60
33
  const $SIGNAL = Symbol.for('signal');
61
34
  const $RESOURCE = Symbol.for('resource');
62
- export function createCachedResource(sourceOrFetcher, fetcherOrOptions, opts = {}) {
35
+ export function cachedResource(sourceOrFetcher, fetcherOrOptions, opts = {}) {
63
36
  // ── Normalise overloads ──────────────────────────────────────────────────
64
37
  let sourceFn;
65
38
  let fetchFn;
@@ -77,7 +50,15 @@ export function createCachedResource(sourceOrFetcher, fetcherOrOptions, opts = {
77
50
  }
78
51
  const { ttl = Infinity, name = `_auto_${_autoId++}`, staleWhileRevalidate = true, } = options;
79
52
  const ns = name;
80
- const buildKey = (key) => `${ns}:${JSON.stringify(key)}`;
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)}` : '');
81
62
  const isFresh = (entry) => ttl === Infinity || Date.now() - entry.timestamp <= ttl;
82
63
  // ── Reactive state ────────────────────────────────────────────────────────
83
64
  const [value, setValue] = createSignal(undefined, { equals: false });
@@ -93,16 +74,19 @@ export function createCachedResource(sourceOrFetcher, fetcherOrOptions, opts = {
93
74
  // ── Fetch helper ──────────────────────────────────────────────────────────
94
75
  function doFetch(key, ck, background, isCancelled) {
95
76
  // Attach to an in-flight request for the same key (dedup).
96
- if (inflight.has(ck)) {
97
- inflight.get(ck).then((v) => { if (!isCancelled())
77
+ const running = inflight.get(ck);
78
+ if (running) {
79
+ running.then((v) => { if (!isCancelled())
98
80
  settle(v, background); }, (e) => { if (!isCancelled() && !background)
99
81
  fail(e); });
100
- if (!background)
101
- setPendingP(inflight.get(ck));
82
+ if (!background) {
83
+ trackResourcePromise(running);
84
+ setPendingP(running);
85
+ }
102
86
  return;
103
87
  }
104
88
  const promise = Promise.resolve(fetchFn(key)).then((v) => {
105
- ttlCache.set(ck, { value: v, timestamp: Date.now() });
89
+ cache.set(ck, { value: v, timestamp: Date.now() });
106
90
  inflight.delete(ck);
107
91
  return v;
108
92
  }, (e) => {
@@ -121,7 +105,9 @@ export function createCachedResource(sourceOrFetcher, fetcherOrOptions, opts = {
121
105
  settle(v, background); }, (e) => { if (!isCancelled() && !background)
122
106
  fail(e); });
123
107
  }
124
- function settle(v, background) {
108
+ function settle(v, _background) {
109
+ if (ssrId !== undefined)
110
+ reportResourceData(ssrId, v); // serialize for hydration
125
111
  batch(() => {
126
112
  setValue(() => v);
127
113
  setResourceState('ready');
@@ -140,6 +126,7 @@ export function createCachedResource(sourceOrFetcher, fetcherOrOptions, opts = {
140
126
  // ── Main effect ───────────────────────────────────────────────────────────
141
127
  createEffect(() => {
142
128
  const key = sourceFn();
129
+ const values = depValues();
143
130
  tick(); // track manual triggers
144
131
  // Suspend fetch when source is falsy (mirrors createResource)
145
132
  if (key === false || key === null || key === undefined) {
@@ -155,18 +142,23 @@ export function createCachedResource(sourceOrFetcher, fetcherOrOptions, opts = {
155
142
  const doInvalidate = flagInvalidate;
156
143
  flagRefetch = false;
157
144
  flagInvalidate = false;
158
- const ck = buildKey(key);
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() });
159
149
  // Clear cache on explicit invalidate
160
150
  if (doInvalidate)
161
- ttlCache.delete(ck);
151
+ cache.delete(ck);
162
152
  const keyChanged = prevKey === UNSET || key !== prevKey;
163
153
  prevKey = key;
164
154
  let cancelled = false;
165
155
  onCleanup(() => { cancelled = true; });
166
- const cached = ttlCache.get(ck);
156
+ const cached = cache.get(ck);
167
157
  // ── Cache HIT (fresh) ─────────────────────────────────────────────────
168
158
  if (cached && isFresh(cached) && !doRefetch && !doInvalidate) {
169
159
  // Serve synchronously, no loading state, no fetch.
160
+ if (ssrId !== undefined)
161
+ reportResourceData(ssrId, cached.value);
170
162
  const cur = untrack(resourceState);
171
163
  if (cur !== 'ready' || untrack(value) !== cached.value) {
172
164
  batch(() => {
@@ -235,29 +227,24 @@ export function createCachedResource(sourceOrFetcher, fetcherOrOptions, opts = {
235
227
  const invalidate = () => {
236
228
  const key = untrack(sourceFn);
237
229
  if (key !== null && key !== undefined && key !== false) {
238
- ttlCache.delete(buildKey(key));
230
+ cache.delete(buildKey(key));
239
231
  }
240
232
  flagInvalidate = true;
241
233
  setTick(v => v + 1);
242
234
  };
243
- return [resource, { mutate: setValue, refetch, invalidate }];
235
+ return Object.assign(resource, { mutate: setValue, refetch, invalidate });
244
236
  }
245
237
  // ── Cache utilities ────────────────────────────────────────────────────────────
246
238
  /**
247
- * Clear all TTL cache entries for a given namespace (name).
248
- * Useful to invalidate a whole category of data at once.
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.
249
241
  */
250
- export function clearCachedResourceNamespace(name) {
242
+ export function clearCachedResources(name, cache = currentResourceCache()) {
243
+ if (name === undefined)
244
+ return cache.clear();
251
245
  const prefix = `${name}:`;
252
- for (const key of ttlCache.keys()) {
246
+ for (const key of [...cache.keys()])
253
247
  if (key.startsWith(prefix))
254
- ttlCache.delete(key);
255
- }
256
- }
257
- /**
258
- * Clear the entire TTL cache. Useful on logout / workspace switch.
259
- */
260
- export function clearAllCachedResources() {
261
- ttlCache.clear();
262
- inflight.clear();
248
+ cache.delete(key);
263
249
  }
250
+ export { createResourceCache } from './graph/ssr-hooks.js';
@@ -1,7 +1,7 @@
1
- /*! @fluixi/reactive v1.0.0-alpha.91 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
1
+ /*! @fluixi/reactive v1.0.0-alpha.93 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
2
 
3
3
  // src/version.generated.ts
4
- var VERSION = "1.0.0-alpha.91";
4
+ var VERSION = "1.0.0-alpha.93";
5
5
 
6
6
  // src/lib/signal/graph/state.ts
7
7
  var MAJOR = VERSION.split(".")[0];
@@ -51,11 +51,12 @@ function observeReactiveNodes(next) {
51
51
  }
52
52
  var running = [];
53
53
  var origin;
54
+ var currentScope;
54
55
  function reportReactiveNode(report) {
55
56
  const observer = G.observer;
56
57
  if (observer) {
57
58
  observer.created(
58
- report.parents === void 0 ? { ...report, parents: running, origin: report.origin ?? origin } : report
59
+ report.parents === void 0 ? { ...report, parents: running, origin: report.origin ?? origin, scope: report.scope ?? currentScope } : { ...report, scope: report.scope ?? currentScope }
59
60
  );
60
61
  }
61
62
  }
@@ -349,7 +350,7 @@ function batch(fn) {
349
350
  }
350
351
  }
351
352
  var EffectNode = class {
352
- constructor(fn, report = false) {
353
+ constructor(fn, report = false, name) {
353
354
  this.queued = false;
354
355
  this.disposed = false;
355
356
  this.owner = makeOwner(G.owner);
@@ -361,7 +362,7 @@ var EffectNode = class {
361
362
  if (typeof r === "function") (this.owner.cleanups ??= []).push(r);
362
363
  });
363
364
  });
364
- reportReactiveNode({ kind: "effect", node: this.computed, handle: this, internal: !report });
365
+ reportReactiveNode({ kind: "effect", node: this.computed, handle: this, internal: !report, ...name ? { name } : {} });
365
366
  this.watcher = new Watcher(() => enqueue(this));
366
367
  this.run();
367
368
  this.watcher.watch(this.computed);
@@ -388,8 +389,8 @@ var EffectNode = class {
388
389
  disposeOwner(this.owner);
389
390
  }
390
391
  };
391
- function effect(fn, report = false) {
392
- const node = new EffectNode(fn, report);
392
+ function effect(fn, report = false, name) {
393
+ const node = new EffectNode(fn, report, name);
393
394
  return () => node.dispose();
394
395
  }
395
396
 
@@ -429,12 +430,12 @@ function createSignal(value, options) {
429
430
  if (isObserved()) onCleanup(() => reportReactiveDispose(state));
430
431
  return [read, write];
431
432
  }
432
- function createEffect(fn, value) {
433
+ function createEffect(fn, value, options) {
433
434
  let prev = value;
434
435
  return effect(() => {
435
436
  const r = fn(prev);
436
437
  prev = r;
437
- }, true);
438
+ }, true, options?.name);
438
439
  }
439
440
  function batch2(fn) {
440
441
  return batch(fn);
@@ -448,14 +449,47 @@ var _resourceTracker = null;
448
449
  function trackResourcePromise(p) {
449
450
  if (_resourceTracker) _resourceTracker(p);
450
451
  }
452
+ var _serverDataGetter = null;
453
+ function getServerData(id) {
454
+ return _serverDataGetter ? _serverDataGetter(id) : void 0;
455
+ }
456
+ var _resourceIdSource = null;
457
+ function nextResourceId() {
458
+ return _resourceIdSource ? _resourceIdSource() : void 0;
459
+ }
460
+ var _resourceDataSink = null;
461
+ function reportResourceData(id, value) {
462
+ if (_resourceDataSink) _resourceDataSink(id, value);
463
+ }
464
+ function createResourceCache() {
465
+ const entries = /* @__PURE__ */ new Map();
466
+ const inflight = /* @__PURE__ */ new Map();
467
+ return {
468
+ get: (key) => entries.get(key),
469
+ set: (key, entry) => void entries.set(key, entry),
470
+ delete: (key) => void entries.delete(key),
471
+ keys: () => entries.keys(),
472
+ clear: () => {
473
+ entries.clear();
474
+ inflight.clear();
475
+ },
476
+ inflight
477
+ };
478
+ }
479
+ var _cacheScope = null;
480
+ var _pageCache;
481
+ function currentResourceCache() {
482
+ const scoped = _cacheScope?.();
483
+ if (scoped) return scoped;
484
+ if (typeof window === "undefined") return createResourceCache();
485
+ return _pageCache ??= createResourceCache();
486
+ }
451
487
 
452
488
  // src/lib/signal/cached-resource.ts
453
- var ttlCache = /* @__PURE__ */ new Map();
454
- var inflight = /* @__PURE__ */ new Map();
455
489
  var _autoId = 0;
456
490
  var $SIGNAL2 = /* @__PURE__ */ Symbol.for("signal");
457
491
  var $RESOURCE = /* @__PURE__ */ Symbol.for("resource");
458
- function createCachedResource(sourceOrFetcher, fetcherOrOptions, opts = {}) {
492
+ function cachedResource(sourceOrFetcher, fetcherOrOptions, opts = {}) {
459
493
  let sourceFn;
460
494
  let fetchFn;
461
495
  let options;
@@ -474,7 +508,13 @@ function createCachedResource(sourceOrFetcher, fetcherOrOptions, opts = {}) {
474
508
  staleWhileRevalidate = true
475
509
  } = options;
476
510
  const ns = name;
477
- const buildKey = (key) => `${ns}:${JSON.stringify(key)}`;
511
+ const cache = options.cache ?? currentResourceCache();
512
+ const inflight = cache.inflight;
513
+ const ssrId = options.transport === false ? void 0 : nextResourceId();
514
+ const seed = ssrId !== void 0 ? getServerData(ssrId) : void 0;
515
+ const deps = options.deps ?? [];
516
+ const depValues = () => deps.map((dep) => dep());
517
+ const buildKey = (key, values = untrack2(depValues)) => `${ns}:${JSON.stringify(key)}` + (deps.length ? `:${JSON.stringify(values)}` : "");
478
518
  const isFresh = (entry) => ttl === Infinity || Date.now() - entry.timestamp <= ttl;
479
519
  const [value, setValue] = createSignal(void 0, { equals: false });
480
520
  const [resourceState, setResourceState] = createSignal("unresolved", { equals: false });
@@ -487,8 +527,9 @@ function createCachedResource(sourceOrFetcher, fetcherOrOptions, opts = {}) {
487
527
  let flagRefetch = false;
488
528
  let flagInvalidate = false;
489
529
  function doFetch(key, ck, background, isCancelled) {
490
- if (inflight.has(ck)) {
491
- inflight.get(ck).then(
530
+ const running2 = inflight.get(ck);
531
+ if (running2) {
532
+ running2.then(
492
533
  (v) => {
493
534
  if (!isCancelled()) settle(v, background);
494
535
  },
@@ -496,12 +537,15 @@ function createCachedResource(sourceOrFetcher, fetcherOrOptions, opts = {}) {
496
537
  if (!isCancelled() && !background) fail(e);
497
538
  }
498
539
  );
499
- if (!background) setPendingP(inflight.get(ck));
540
+ if (!background) {
541
+ trackResourcePromise(running2);
542
+ setPendingP(running2);
543
+ }
500
544
  return;
501
545
  }
502
546
  const promise = Promise.resolve(fetchFn(key)).then(
503
547
  (v) => {
504
- ttlCache.set(ck, { value: v, timestamp: Date.now() });
548
+ cache.set(ck, { value: v, timestamp: Date.now() });
505
549
  inflight.delete(ck);
506
550
  return v;
507
551
  },
@@ -524,7 +568,8 @@ function createCachedResource(sourceOrFetcher, fetcherOrOptions, opts = {}) {
524
568
  }
525
569
  );
526
570
  }
527
- function settle(v, background) {
571
+ function settle(v, _background) {
572
+ if (ssrId !== void 0) reportResourceData(ssrId, v);
528
573
  batch2(() => {
529
574
  setValue(() => v);
530
575
  setResourceState("ready");
@@ -542,6 +587,7 @@ function createCachedResource(sourceOrFetcher, fetcherOrOptions, opts = {}) {
542
587
  }
543
588
  createEffect(() => {
544
589
  const key = sourceFn();
590
+ const values = depValues();
545
591
  tick();
546
592
  if (key === false || key === null || key === void 0) {
547
593
  prevKey = UNSET;
@@ -556,16 +602,18 @@ function createCachedResource(sourceOrFetcher, fetcherOrOptions, opts = {}) {
556
602
  const doInvalidate = flagInvalidate;
557
603
  flagRefetch = false;
558
604
  flagInvalidate = false;
559
- const ck = buildKey(key);
560
- if (doInvalidate) ttlCache.delete(ck);
605
+ const ck = buildKey(key, values);
606
+ if (seed && prevKey === UNSET && !cache.get(ck)) cache.set(ck, { value: seed.value, timestamp: Date.now() });
607
+ if (doInvalidate) cache.delete(ck);
561
608
  const keyChanged = prevKey === UNSET || key !== prevKey;
562
609
  prevKey = key;
563
610
  let cancelled = false;
564
611
  onCleanup(() => {
565
612
  cancelled = true;
566
613
  });
567
- const cached = ttlCache.get(ck);
614
+ const cached = cache.get(ck);
568
615
  if (cached && isFresh(cached) && !doRefetch && !doInvalidate) {
616
+ if (ssrId !== void 0) reportResourceData(ssrId, cached.value);
569
617
  const cur2 = untrack2(resourceState);
570
618
  if (cur2 !== "ready" || untrack2(value) !== cached.value) {
571
619
  batch2(() => {
@@ -626,25 +674,20 @@ function createCachedResource(sourceOrFetcher, fetcherOrOptions, opts = {}) {
626
674
  const invalidate = () => {
627
675
  const key = untrack2(sourceFn);
628
676
  if (key !== null && key !== void 0 && key !== false) {
629
- ttlCache.delete(buildKey(key));
677
+ cache.delete(buildKey(key));
630
678
  }
631
679
  flagInvalidate = true;
632
680
  setTick((v) => v + 1);
633
681
  };
634
- return [resource, { mutate: setValue, refetch, invalidate }];
682
+ return Object.assign(resource, { mutate: setValue, refetch, invalidate });
635
683
  }
636
- function clearCachedResourceNamespace(name) {
684
+ function clearCachedResources(name, cache = currentResourceCache()) {
685
+ if (name === void 0) return cache.clear();
637
686
  const prefix = `${name}:`;
638
- for (const key of ttlCache.keys()) {
639
- if (key.startsWith(prefix)) ttlCache.delete(key);
640
- }
641
- }
642
- function clearAllCachedResources() {
643
- ttlCache.clear();
644
- inflight.clear();
687
+ for (const key of [...cache.keys()]) if (key.startsWith(prefix)) cache.delete(key);
645
688
  }
646
689
  export {
647
- clearAllCachedResources,
648
- clearCachedResourceNamespace,
649
- createCachedResource
690
+ cachedResource,
691
+ clearCachedResources,
692
+ createResourceCache
650
693
  };
@@ -25,7 +25,7 @@ __export(children_exports, {
25
25
  module.exports = __toCommonJS(children_exports);
26
26
 
27
27
  // src/version.generated.ts
28
- var VERSION = "1.0.0-alpha.91";
28
+ var VERSION = "1.0.0-alpha.93";
29
29
 
30
30
  // src/lib/signal/graph/state.ts
31
31
  var MAJOR = VERSION.split(".")[0];
@@ -75,11 +75,12 @@ function observeReactiveNodes(next) {
75
75
  }
76
76
  var running = [];
77
77
  var origin;
78
+ var currentScope;
78
79
  function reportReactiveNode(report) {
79
80
  const observer = G.observer;
80
81
  if (observer) {
81
82
  observer.created(
82
- report.parents === void 0 ? { ...report, parents: running, origin: report.origin ?? origin } : report
83
+ report.parents === void 0 ? { ...report, parents: running, origin: report.origin ?? origin, scope: report.scope ?? currentScope } : { ...report, scope: report.scope ?? currentScope }
83
84
  );
84
85
  }
85
86
  }
@@ -1,5 +1,5 @@
1
1
  // src/version.generated.ts
2
- var VERSION = "1.0.0-alpha.91";
2
+ var VERSION = "1.0.0-alpha.93";
3
3
 
4
4
  // src/lib/signal/graph/state.ts
5
5
  var MAJOR = VERSION.split(".")[0];
@@ -49,11 +49,12 @@ function observeReactiveNodes(next) {
49
49
  }
50
50
  var running = [];
51
51
  var origin;
52
+ var currentScope;
52
53
  function reportReactiveNode(report) {
53
54
  const observer = G.observer;
54
55
  if (observer) {
55
56
  observer.created(
56
- report.parents === void 0 ? { ...report, parents: running, origin: report.origin ?? origin } : report
57
+ report.parents === void 0 ? { ...report, parents: running, origin: report.origin ?? origin, scope: report.scope ?? currentScope } : { ...report, scope: report.scope ?? currentScope }
57
58
  );
58
59
  }
59
60
  }
@@ -1,4 +1,4 @@
1
- /*! @fluixi/reactive v1.0.0-alpha.91 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
1
+ /*! @fluixi/reactive v1.0.0-alpha.93 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
2
  "use strict";
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -42,7 +42,7 @@ __export(compat_exports, {
42
42
  module.exports = __toCommonJS(compat_exports);
43
43
 
44
44
  // src/version.generated.ts
45
- var VERSION = "1.0.0-alpha.91";
45
+ var VERSION = "1.0.0-alpha.93";
46
46
 
47
47
  // src/lib/signal/graph/state.ts
48
48
  var MAJOR = VERSION.split(".")[0];
@@ -92,11 +92,12 @@ function observeReactiveNodes(next) {
92
92
  }
93
93
  var running = [];
94
94
  var origin;
95
+ var currentScope;
95
96
  function reportReactiveNode(report) {
96
97
  const observer = G.observer;
97
98
  if (observer) {
98
99
  observer.created(
99
- report.parents === void 0 ? { ...report, parents: running, origin: report.origin ?? origin } : report
100
+ report.parents === void 0 ? { ...report, parents: running, origin: report.origin ?? origin, scope: report.scope ?? currentScope } : { ...report, scope: report.scope ?? currentScope }
100
101
  );
101
102
  }
102
103
  }
@@ -416,7 +417,7 @@ function batch(fn) {
416
417
  }
417
418
  }
418
419
  var EffectNode = class {
419
- constructor(fn, report = false) {
420
+ constructor(fn, report = false, name) {
420
421
  this.queued = false;
421
422
  this.disposed = false;
422
423
  this.owner = makeOwner(G.owner);
@@ -428,7 +429,7 @@ var EffectNode = class {
428
429
  if (typeof r === "function") (this.owner.cleanups ??= []).push(r);
429
430
  });
430
431
  });
431
- reportReactiveNode({ kind: "effect", node: this.computed, handle: this, internal: !report });
432
+ reportReactiveNode({ kind: "effect", node: this.computed, handle: this, internal: !report, ...name ? { name } : {} });
432
433
  this.watcher = new Watcher(() => enqueue(this));
433
434
  this.run();
434
435
  this.watcher.watch(this.computed);
@@ -455,8 +456,8 @@ var EffectNode = class {
455
456
  disposeOwner(this.owner);
456
457
  }
457
458
  };
458
- function effect(fn, report = false) {
459
- const node = new EffectNode(fn, report);
459
+ function effect(fn, report = false, name) {
460
+ const node = new EffectNode(fn, report, name);
460
461
  return () => node.dispose();
461
462
  }
462
463
 
@@ -524,12 +525,12 @@ function createMemo(fn, value, options) {
524
525
  if (isObserved()) onCleanup(() => reportReactiveDispose(c));
525
526
  return read;
526
527
  }
527
- function createEffect(fn, value) {
528
+ function createEffect(fn, value, options) {
528
529
  let prev = value;
529
530
  return effect(() => {
530
531
  const r = fn(prev);
531
532
  prev = r;
532
- }, true);
533
+ }, true, options?.name);
533
534
  }
534
535
  function createComputed(fn, value) {
535
536
  let prev = value;
@@ -20,7 +20,9 @@ export declare function createMemo<T>(fn: (prev?: T) => T, value?: T, options?:
20
20
  * createEffect, a re-running side effect. The fn receives the previous return
21
21
  * value and may return a new one (legacy signature) or a cleanup function.
22
22
  */
23
- export declare function createEffect<T>(fn: (prev?: T) => T | (() => void), value?: T): () => void;
23
+ export declare function createEffect<T>(fn: (prev?: T) => T | (() => void), value?: T, options?: {
24
+ name?: string;
25
+ }): () => void;
24
26
  /**
25
27
  * createComputed, an EAGER, owner-managed reactive computation (returns void).
26
28
  * Unlike a lazy memo it runs immediately and re-runs eagerly when a dep changes;
@@ -1 +1 @@
1
- {"version":3,"file":"compat.d.ts","sourceRoot":"","sources":["../../../../src/lib/signal/graph/compat.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"compat.d.ts","sourceRoot":"","sources":["../../../../src/lib/signal/graph/compat.ts"],"names":[],"mappings":"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"}
@@ -110,7 +110,7 @@ export function createMemo(fn, value, options) {
110
110
  * createEffect, a re-running side effect. The fn receives the previous return
111
111
  * value and may return a new one (legacy signature) or a cleanup function.
112
112
  */
113
- export function createEffect(fn, value) {
113
+ export function createEffect(fn, value, options) {
114
114
  let prev = value;
115
115
  return l1effect(() => {
116
116
  const r = fn(prev);
@@ -118,7 +118,7 @@ export function createEffect(fn, value) {
118
118
  // never returns one, in which case onCleanup is the cleanup path. We keep the
119
119
  // legacy carry-the-value behaviour; cleanups use onCleanup.
120
120
  prev = r;
121
- }, true);
121
+ }, true, options?.name);
122
122
  }
123
123
  /**
124
124
  * createComputed, an EAGER, owner-managed reactive computation (returns void).