raf-effect-atom 0.2.4 → 0.2.5

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/CHANGELOG.md CHANGED
@@ -1,14 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.5](https://github.com/KATT/raf-effect-atom/compare/raf-effect-atom%400.2.4...raf-effect-atom%400.2.5) (2026-10-23)
4
+
5
+ ### Features
6
+
7
+ * **raf-effect-atom:** start a derived atom's source from the loader verbs, following initialValueTarget ([00a9760](https://github.com/KATT/raf-effect-atom/commit/00a97602068cabf085fa9cfe41e88bf853b6af32))
8
+
3
9
  ## [0.2.4](https://github.com/KATT/raf-effect-atom/compare/raf-effect-atom%400.2.3...raf-effect-atom%400.2.4) (2026-10-23)
4
10
 
5
11
  ### Features
6
12
 
7
- * **raf-effect-atom:** start a request registry with initialValues, readable atoms included ([67cf767](https://github.com/KATT/raf-effect-atom/commit/67cf767e121e76b385ff0808a087efc6662aed89))
13
+ - **raf-effect-atom:** start a request registry with initialValues, readable atoms included ([67cf767](https://github.com/KATT/raf-effect-atom/commit/67cf767e121e76b385ff0808a087efc6662aed89))
8
14
 
9
15
  ### Documentation
10
16
 
11
- * correct the generated changelogs' cut-off breaking change and bogus issue links ([c21f64c](https://github.com/KATT/raf-effect-atom/commit/c21f64cad3b4638f2341968aa2e10af5a19904c1))
17
+ - correct the generated changelogs' cut-off breaking change and bogus issue links ([c21f64c](https://github.com/KATT/raf-effect-atom/commit/c21f64cad3b4638f2341968aa2e10af5a19904c1))
12
18
 
13
19
  ## [0.2.3](https://github.com/KATT/raf-effect-atom/compare/raf-effect-atom%400.2.2...raf-effect-atom%400.2.3) (2026-10-23)
14
20
 
package/dist/Hooks.mjs CHANGED
@@ -2,10 +2,9 @@
2
2
  import { stream } from "./internal/pending.mjs";
3
3
  import { hydrated } from "./Hydration.mjs";
4
4
  import { keyOf, release } from "./internal/leases.mjs";
5
- import { atomOf } from "./internal/handles.mjs";
5
+ import { atomOf, sourceOf } from "./internal/handles.mjs";
6
6
  import { requireRequestRegistry } from "./internal/registry.mjs";
7
7
  import { ModeContext, check } from "./internal/waterfalls.mjs";
8
- import * as Atom from "effect/reactivity/Atom";
9
8
  import * as Predicate from "effect/Predicate";
10
9
  import * as Cause from "effect/Cause";
11
10
  import * as Option from "effect/Option";
@@ -20,15 +19,6 @@ import * as React from "react";
20
19
  * @since 0.1.0
21
20
  */
22
21
  /**
23
- * The atom the loader started and the server sent: the first serializable
24
- * atom in `atom`'s `initialValueTarget` chain, starting with `atom`. An
25
- * AtomRpc query is serializable itself, though it points at an inner atom.
26
- */
27
- const sourceOf = (atom) => {
28
- for (let target = atom; target !== void 0; target = target.initialValueTarget) if (Atom.isSerializable(target)) return target;
29
- return atom;
30
- };
31
- /**
32
22
  * Reads a handle through React Suspense, through the source that made it: a
33
23
  * result streamed from the server, a hydrated one, or one a loader started.
34
24
  *
package/dist/Loader.d.mts CHANGED
@@ -40,10 +40,24 @@ export declare const LeaseTTL: Context.Reference<Duration.Duration>;
40
40
  *
41
41
  * **Details**
42
42
  *
43
- * Starts the atom if it isn't in the registry, and leaves it alone if it is.
44
- * Takes a lease either way. The component that reads the handle suspends
45
- * until the result settles. On a request registry whose async seed is still
46
- * running, it waits for the seed first.
43
+ * - Starts the atom if it isn't in the registry, and leaves it alone if it
44
+ * is. Takes a lease either way. The component that reads the handle
45
+ * suspends until the result settles. On a request registry whose async
46
+ * seed is still running, it waits for the seed first.
47
+ * - A derived atom that isn't serializable, such as an error boundary's
48
+ * `wrap(source)`, starts the first serializable atom its
49
+ * `initialValueTarget` chain points at, the one `useAtomResult` reads it
50
+ * through: the lease, the handle's key and what the server sends are
51
+ * that atom's. `ensure` and `fetch` wait for the derived atom's own
52
+ * result.
53
+ *
54
+ * **Gotchas**
55
+ *
56
+ * The server sends a result still loading as a promise only when the atom
57
+ * it sends holds an `AsyncResult`. A derived atom whose serializable atom
58
+ * holds another value, such as an `Option` snapshot, crosses with the value
59
+ * it has when the registry is dehydrated: point it at an `AsyncResult`
60
+ * atom, or `ensure` it.
47
61
  *
48
62
  * **Example**
49
63
  *
package/dist/Loader.mjs CHANGED
@@ -2,7 +2,7 @@ import { __exportAll } from "./_virtual/_rolldown/runtime.mjs";
2
2
  import { hydrated } from "./Hydration.mjs";
3
3
  import { isDevelopment } from "./internal/development.mjs";
4
4
  import { keyOf, take } from "./internal/leases.mjs";
5
- import { atomOf, loaded as loaded$1, make as make$1 } from "./internal/handles.mjs";
5
+ import { atomOf, loaded as loaded$1, make as make$1, sourceOf } from "./internal/handles.mjs";
6
6
  import { afterSeed, pending } from "./internal/seeds.mjs";
7
7
  import { isRequestRegistry } from "./RequestRegistry.mjs";
8
8
  import * as Atom from "effect/reactivity/Atom";
@@ -70,14 +70,16 @@ const start = Effect.fnUntraced(function* (atom) {
70
70
  const seed = pending(registry);
71
71
  if (seed !== void 0) yield* Effect.promise(() => seed);
72
72
  const request = isRequestRegistry(registry);
73
- if (request && !Atom.isSerializable(atom) && (yield* isDevelopment)) return yield* Effect.die(/* @__PURE__ */ new Error(`raf-effect-atom: a loader started ${String(atom)}, which isn't serializable, so the browser would request it again. Give it a serialization key, such as AtomRpc's serializationKey option.`));
73
+ const source = sourceOf(atom);
74
+ if (request && !Atom.isSerializable(source) && (yield* isDevelopment)) return yield* Effect.die(/* @__PURE__ */ new Error(`raf-effect-atom: a loader started ${String(atom)}, which isn't serializable and doesn't point at a serializable atom with initialValueTarget, so the browser would request it again. Give it a serialization key, such as AtomRpc's serializationKey option.`));
74
75
  const ttl = request ? void 0 : Duration.toMillis(yield* LeaseTTL);
75
- const target = hydrated(atom);
76
+ const target = hydrated(source);
76
77
  const key = keyOf(target);
77
78
  const existed = registry.getNodes().has(key) || Predicate.isString(key) && registry.preloadedSerializable.has(key);
78
79
  take(registry, target, ttl);
79
80
  return {
80
81
  existed,
82
+ read: source === atom ? hydrated(atom) : atom,
81
83
  registry,
82
84
  request,
83
85
  target
@@ -115,10 +117,24 @@ const rejectDefects = (result) => {
115
117
  *
116
118
  * **Details**
117
119
  *
118
- * Starts the atom if it isn't in the registry, and leaves it alone if it is.
119
- * Takes a lease either way. The component that reads the handle suspends
120
- * until the result settles. On a request registry whose async seed is still
121
- * running, it waits for the seed first.
120
+ * - Starts the atom if it isn't in the registry, and leaves it alone if it
121
+ * is. Takes a lease either way. The component that reads the handle
122
+ * suspends until the result settles. On a request registry whose async
123
+ * seed is still running, it waits for the seed first.
124
+ * - A derived atom that isn't serializable, such as an error boundary's
125
+ * `wrap(source)`, starts the first serializable atom its
126
+ * `initialValueTarget` chain points at, the one `useAtomResult` reads it
127
+ * through: the lease, the handle's key and what the server sends are
128
+ * that atom's. `ensure` and `fetch` wait for the derived atom's own
129
+ * result.
130
+ *
131
+ * **Gotchas**
132
+ *
133
+ * The server sends a result still loading as a promise only when the atom
134
+ * it sends holds an `AsyncResult`. A derived atom whose serializable atom
135
+ * holds another value, such as an `Option` snapshot, crosses with the value
136
+ * it has when the registry is dehydrated: point it at an `AsyncResult`
137
+ * atom, or `ensure` it.
122
138
  *
123
139
  * **Example**
124
140
  *
@@ -181,7 +197,7 @@ const loaded = (args, atom, result) => {
181
197
  const ensure = (source, ...args) => Effect.gen(function* () {
182
198
  const atom = atomOf(source, args);
183
199
  const started = yield* start(atom);
184
- const result = yield* settled(started.registry, started.target, started.request ? isSettled : AsyncResult.isNotInitial);
200
+ const result = yield* settled(started.registry, started.read, started.request ? isSettled : AsyncResult.isNotInitial);
185
201
  return loaded(args, atom, result);
186
202
  });
187
203
  /**
@@ -207,12 +223,12 @@ const ensure = (source, ...args) => Effect.gen(function* () {
207
223
  const fetch = (source, ...args) => Effect.gen(function* () {
208
224
  const atom = atomOf(source, args);
209
225
  const started = yield* start(atom);
210
- const { registry, target } = started;
211
- if (started.existed && isSettled(registry.get(target))) {
226
+ const { read, registry, target } = started;
227
+ if (started.existed && isSettled(registry.get(read))) {
212
228
  const node = registry.getNodes().get(keyOf(target));
213
- registry.refresh(node?.atom ?? target);
229
+ registry.refresh(read === target ? node?.atom ?? target : read);
214
230
  }
215
- const result = yield* settled(registry, target, isSettled);
231
+ const result = yield* settled(registry, read, isSettled);
216
232
  return loaded(args, atom, result);
217
233
  });
218
234
  const results = /* @__PURE__ */ new WeakMap();
@@ -72,12 +72,9 @@ export declare const WaterfallDetection: (props: {
72
72
  * - `useAtomResult` doesn't report an atom started this way.
73
73
  * - The handle is a prefetched one: its reader suspends until the result
74
74
  * settles, as for a handle from a loader's `prefetch`.
75
- *
76
- * **Gotchas**
77
- *
78
- * Pass it the source a loader would start, not an error boundary's
79
- * `wrap(source)`: the wrapper's atoms aren't serializable, so the server
80
- * couldn't send them to the browser. Read the handle through the wrapper.
75
+ * - A derived source, such as an error boundary's `wrap(source)`, starts
76
+ * the serializable atom its `initialValueTarget` chain points at, as
77
+ * `Loader.prefetch` does.
81
78
  *
82
79
  * **Example**
83
80
  *
@@ -2,7 +2,7 @@
2
2
  import { get } from "./internal/pending.mjs";
3
3
  import { hydrated } from "./Hydration.mjs";
4
4
  import { keyOf } from "./internal/leases.mjs";
5
- import { atomOf, make } from "./internal/handles.mjs";
5
+ import { atomOf, make, sourceOf } from "./internal/handles.mjs";
6
6
  import { prefetch } from "./Loader.mjs";
7
7
  import { ModeContext, isStarted } from "./internal/waterfalls.mjs";
8
8
  import * as Predicate from "effect/Predicate";
@@ -76,12 +76,9 @@ const WaterfallDetection = (props) => process.env.NODE_ENV === "production" ? pr
76
76
  * - `useAtomResult` doesn't report an atom started this way.
77
77
  * - The handle is a prefetched one: its reader suspends until the result
78
78
  * settles, as for a handle from a loader's `prefetch`.
79
- *
80
- * **Gotchas**
81
- *
82
- * Pass it the source a loader would start, not an error boundary's
83
- * `wrap(source)`: the wrapper's atoms aren't serializable, so the server
84
- * couldn't send them to the browser. Read the handle through the wrapper.
79
+ * - A derived source, such as an error boundary's `wrap(source)`, starts
80
+ * the serializable atom its `initialValueTarget` chain points at, as
81
+ * `Loader.prefetch` does.
85
82
  *
86
83
  * **Example**
87
84
  *
@@ -103,7 +100,7 @@ const WaterfallDetection = (props) => process.env.NODE_ENV === "production" ? pr
103
100
  const useWaterfall = (source, ...args) => {
104
101
  const registry = React.useContext(RegistryContext);
105
102
  const atom = atomOf(source, args);
106
- const target = hydrated(atom);
103
+ const target = hydrated(sourceOf(atom));
107
104
  const key = keyOf(target);
108
105
  if (!(Predicate.isString(key) && get(registry, key) !== void 0) && !isStarted(registry, target)) return Effect.runSync(Effect.provideService(prefetch(source, ...args), AtomRegistry.AtomRegistry, registry));
109
106
  return make(args, atom);
@@ -4,9 +4,18 @@ import * as Predicate from "effect/Predicate";
4
4
  // src/internal/handles.ts
5
5
  /** The atom a source makes for its arguments. */
6
6
  const atomOf = (source, args) => Atom.isAtom(source) ? source : source(...args);
7
- /** The registry key a handle records: the atom's serialization key. */
7
+ /**
8
+ * The atom the loader starts and the server sends for `atom`: the first
9
+ * serializable atom in its `initialValueTarget` chain, starting with `atom`.
10
+ * An AtomRpc query is serializable itself, though it points at an inner atom.
11
+ */
12
+ const sourceOf = (atom) => {
13
+ for (let target = atom; target !== void 0; target = target.initialValueTarget) if (Atom.isSerializable(target)) return target;
14
+ return atom;
15
+ };
16
+ /** The registry key a handle records: its source atom's serialization key. */
8
17
  const keyOf = (atom) => {
9
- const key = keyOf$1(atom);
18
+ const key = keyOf$1(sourceOf(atom));
10
19
  if (!Predicate.isString(key)) return;
11
20
  return key;
12
21
  };
@@ -19,4 +28,4 @@ const loaded = (args, atom) => ({
19
28
  key: keyOf(atom),
20
29
  loaded: true
21
30
  });
22
- export { atomOf, loaded, make };
31
+ export { atomOf, loaded, make, sourceOf };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "raf-effect-atom",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "Render-as-you-fetch for Effect Atom: start atoms from route loaders and stream them into the browser, typed failures intact",
5
5
  "keywords": [
6
6
  "effect",