raf-effect-atom 0.2.5 → 0.3.0

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,10 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.0](https://github.com/KATT/raf-effect-atom/compare/raf-effect-atom%400.2.6...raf-effect-atom%400.3.0) (2026-10-23)
4
+
5
+ ### ⚠ BREAKING CHANGES
6
+
7
+ * **raf-effect-atom:** `raf-effect-atom/next` is `raf-effect-atom/rsc`, without `dehydrate`; write `<HydrationBoundary registry={getRegistry()}>` from `raf-effect-atom` instead of `<HydrateAtoms state={await dehydrate()}>`, and import `RegistryProvider` from `raf-effect-atom`.
8
+
9
+ ### Features
10
+
11
+ * **raf-effect-atom:** import HydrationBoundary from raf-effect-atom in server components, and the RSC verbs from raf-effect-atom/rsc ([bf7fdbc](https://github.com/KATT/raf-effect-atom/commit/bf7fdbcde2e9f785bd276a08117aefa359051b82))
12
+
13
+ ## [0.2.6](https://github.com/KATT/raf-effect-atom/compare/raf-effect-atom%400.2.5...raf-effect-atom%400.2.6) (2026-10-23)
14
+
15
+ ### Features
16
+
17
+ - **raf-effect-atom:** start browser-only reads with prefetchInBrowser ([65f783a](https://github.com/KATT/raf-effect-atom/commit/65f783a0014e44a8aa11ce5e8fa34ab863063403))
18
+
19
+ ### Documentation
20
+
21
+ - **www:** keep a search box's text in the URL, and drop the debounce sample ([a3949bf](https://github.com/KATT/raf-effect-atom/commit/a3949bfca4b64e478132f983033b2776e50ba949))
22
+
23
+ ### Tests
24
+
25
+ - **raf-effect-atom:** start a browser-only read from a client-only TanStack route ([69a8bd8](https://github.com/KATT/raf-effect-atom/commit/69a8bd887c34a7aeef51fdd20057ac30aa9e1fbc))
26
+ - **raf-effect-atom:** type a derived atom's handle through it and its wrappers ([a16b587](https://github.com/KATT/raf-effect-atom/commit/a16b587a59dc4827dd18ecb3a3d5bb8f6fdedf0b))
27
+
3
28
  ## [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
29
 
5
30
  ### Features
6
31
 
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))
32
+ - **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
33
 
9
34
  ## [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)
10
35
 
package/README.md CHANGED
@@ -52,8 +52,8 @@ function IssueTitle(props: { readonly issue: Handle<typeof issue> }) {
52
52
  Each framework has a subpath that gives every server request its own registry and sends it to the browser:
53
53
 
54
54
  - `raf-effect-atom/tanstack-router`, for TanStack Router and TanStack Start: `setupRouterAtoms({ router, registry })` in `getRouter`, with `Loader.make(registry)` on the router context.
55
- - `raf-effect-atom/next`, for the App Router: `prefetch`, `ensure` and `fetch` in server components, which pass the handles to client components as props, and `<HydrateAtoms state={await dehydrate()}>` around them. `makeRequestAtoms({ seed })` makes the same functions over a registry seeded from the request, such as with its cookie.
56
- - `raf-effect-atom/react-router`, for Framework Mode: `atomsMiddleware(atomsContext)` as the root middleware and `clientAtomsMiddleware(atomsContext)` as its client middleware, loaders that return `atoms.dehydrate()` with their handles and double as client loaders, and `HydrationBoundary` in the route component.
55
+ - `raf-effect-atom/rsc`, for React Server Components such as the Next.js App Router: `prefetch`, `ensure` and `fetch` in server components, which pass the handles to client components as props, and `<HydrationBoundary registry={getRegistry()}>` around them. `HydrationBoundary` and `RegistryProvider` come from `raf-effect-atom` itself: server components get the build that takes the request's registry through the `react-server` export condition. `makeRequestAtoms({ seed })` makes the same functions over a registry seeded from the request, such as with its cookie.
56
+ - `raf-effect-atom/react-router`, for Framework Mode: `atomsMiddleware(atomsContext)` as the root middleware and `clientAtomsMiddleware(atomsContext)` as its client middleware, loaders that return `atoms.dehydrate()` with their handles and double as client loaders, and `<HydrationBoundary state={loaderData.atoms}>` in the route component.
57
57
 
58
58
  In development, `useAtomResult` reports a read that starts an atom nothing started, a request waterfall. `<WaterfallDetection mode="throw">` at the root makes it throw to the nearest error boundary. `useWaterfall(source, ...args)` starts an atom whose input exists only in render, marks it as a known waterfall, and returns its handle.
59
59
 
package/dist/Handle.d.mts CHANGED
@@ -67,6 +67,11 @@ export type HandleKey<S> = string & {
67
67
  */
68
68
  export interface Handle<S> {
69
69
  readonly args: ArgsOf<S>;
70
+ /**
71
+ * `true` when `Loader.prefetchInBrowser` ran on the server and left the
72
+ * atom to the browser: the browser's read starts it.
73
+ */
74
+ readonly inBrowser?: true;
70
75
  /** The atom's serialization key; `undefined` for an atom without one. */
71
76
  readonly key: HandleKey<S> | undefined;
72
77
  }
package/dist/Hooks.mjs CHANGED
@@ -3,6 +3,7 @@ import { stream } from "./internal/pending.mjs";
3
3
  import { hydrated } from "./Hydration.mjs";
4
4
  import { keyOf, release } from "./internal/leases.mjs";
5
5
  import { atomOf, sourceOf } from "./internal/handles.mjs";
6
+ import { isRequestRegistry } from "./RequestRegistry.mjs";
6
7
  import { requireRequestRegistry } from "./internal/registry.mjs";
7
8
  import { ModeContext, check } from "./internal/waterfalls.mjs";
8
9
  import * as Predicate from "effect/Predicate";
@@ -99,6 +100,7 @@ const useAtomResult = (source, handle) => {
99
100
  const registry = React.useContext(RegistryContext);
100
101
  const atom = atomOf(source, handle.args);
101
102
  requireRequestRegistry(registry, "useAtomResult");
103
+ if (handle.inBrowser === true && isRequestRegistry(registry)) throw new Error(`raf-effect-atom: ${String(atom)} starts in the browser (Loader.prefetchInBrowser), so the server renders the nearest Suspense boundary's fallback, and the browser renders the boundary.`);
102
104
  const target = sourceOf(atom);
103
105
  const started = hydrated(target);
104
106
  const read = target === atom ? hydrated(atom) : atom;
@@ -106,7 +108,7 @@ const useAtomResult = (source, handle) => {
106
108
  if (process.env.NODE_ENV !== "production" && handle.key !== void 0 && Predicate.isString(key) && handle.key !== key) throw new Error(`raf-effect-atom: useAtomResult read the handle of ${handle.key} through a source that makes ${key}. Read a handle through the source that made it.`);
107
109
  const streamed = Predicate.isString(key) ? stream(registry, key) : void 0;
108
110
  if (streamed !== void 0) React.use(streamed);
109
- if (process.env.NODE_ENV !== "production") check(registry, started, React.use(ModeContext));
111
+ if (process.env.NODE_ENV !== "production" && handle.inBrowser !== true) check(registry, started, React.use(ModeContext));
110
112
  if (read !== started) registry.get(started);
111
113
  const current = registry.get(read);
112
114
  const result = useAtomSuspense(read, {
package/dist/Loader.d.mts CHANGED
@@ -5,7 +5,7 @@ import * as Effect from "effect/Effect";
5
5
  import * as AsyncResult from "effect/reactivity/AsyncResult";
6
6
  import * as AtomRegistry from "effect/reactivity/AtomRegistry";
7
7
  declare namespace Loader_d_exports {
8
- export { LeaseTTL, Loader, MakeOptions, SettledResult, ensure, fetch, make, prefetch, result };
8
+ export { LeaseTTL, Loader, MakeOptions, SettledResult, ensure, fetch, make, prefetch, prefetchInBrowser, result };
9
9
  }
10
10
  /**
11
11
  * A settled `AsyncResult`: what `ensure` and `fetch` return.
@@ -73,6 +73,49 @@ export declare const LeaseTTL: Context.Reference<Duration.Duration>;
73
73
  * @since 0.1.0
74
74
  */
75
75
  export declare const prefetch: <S extends Source>(source: S, ...args: ArgsOf<S>) => Effect.Effect<Handle<S>, never, AtomRegistry.AtomRegistry>;
76
+ /**
77
+ * Starts a source's atom in the browser, and returns its handle: on the
78
+ * server, it starts nothing, and the browser's read starts the atom.
79
+ *
80
+ * **When to use**
81
+ *
82
+ * Use for data only the browser can load: an input the server never sees,
83
+ * such as a token in the URL's fragment or a permission, or a request the
84
+ * server can't make. The rest of the page still renders on the server.
85
+ *
86
+ * **Details**
87
+ *
88
+ * - In the browser, it's `prefetch`: a loader that runs there, such as on a
89
+ * client navigation, starts the atom and takes a lease.
90
+ * - On a request registry, it returns a handle marked `inBrowser` without
91
+ * starting the atom, so the atom needs no serialization key. On the
92
+ * server, `useAtomResult` throws for that handle, so React renders the
93
+ * nearest Suspense boundary's fallback there and renders the boundary in
94
+ * the browser. There, the read starts the atom, and the waterfall check
95
+ * skips it, since a loader asked for it.
96
+ *
97
+ * **Gotchas**
98
+ *
99
+ * - Read the handle under a Suspense boundary: on the server, a read
100
+ * outside one fails the whole render. React reports the boundary it
101
+ * renders in the browser to `onRecoverableError`.
102
+ * - On a page the server rendered, the request starts when the browser
103
+ * renders the read, after hydration's JavaScript has loaded: start the
104
+ * data the server can load with `prefetch` or `ensure`.
105
+ *
106
+ * **Example**
107
+ *
108
+ * ```ts
109
+ * import { Loader } from "raf-effect-atom"
110
+ *
111
+ * const program = Loader.prefetchInBrowser(pushSubscription)
112
+ * ```
113
+ *
114
+ * @stability unstable
115
+ * @category verbs
116
+ * @since 0.2.6
117
+ */
118
+ export declare const prefetchInBrowser: <S extends Source>(source: S, ...args: ArgsOf<S>) => Effect.Effect<Handle<S>, never, AtomRegistry.AtomRegistry>;
76
119
  /**
77
120
  * Starts a source's atom, waits only while it has no result, and returns a
78
121
  * handle whose reader renders without suspending.
@@ -179,6 +222,8 @@ export interface Loader {
179
222
  * the seed resolves.
180
223
  */
181
224
  readonly prefetch: <S extends Source>(source: S, ...args: ArgsOf<S>) => Handle<S>;
225
+ /** {@link prefetchInBrowser}, run at once. */
226
+ readonly prefetchInBrowser: <S extends Source>(source: S, ...args: ArgsOf<S>) => Handle<S>;
182
227
  /**
183
228
  * Runs an Effect that needs the registry, such as the Effect verbs in an
184
229
  * `Effect.gen`, as a Promise that rejects with its squashed cause.
@@ -213,8 +258,8 @@ export interface MakeOptions {
213
258
  *
214
259
  * Use in a framework integration, in loaders that are async functions, or
215
260
  * in an event handler that starts an atom before the state change that
216
- * renders its reader, for state that can't be in the URL, such as a
217
- * search-as-you-type query. In Effect code, use the verbs directly, and run
261
+ * renders its reader, for a choice a click or a submit makes that doesn't
262
+ * belong in the URL. In Effect code, use the verbs directly, and run
218
263
  * the Effect on the registry with `run`.
219
264
  *
220
265
  * **Details**
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, sourceOf } from "./internal/handles.mjs";
5
+ import { atomOf, inBrowser, 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";
@@ -31,6 +31,9 @@ import * as AtomRegistry from "effect/reactivity/AtomRegistry";
31
31
  * | `ensure` | if it isn't in the registry | only while it's `Initial` | returns it at once |
32
32
  * | `fetch` | if it isn't, or refreshes it | until the new result settles | refreshes, then waits |
33
33
  *
34
+ * `prefetchInBrowser` is `prefetch` in the browser. On the server, it
35
+ * starts nothing, and the browser's read starts the atom.
36
+ *
34
37
  * Typed failures don't reject: `ensure` and `fetch` settle on them, and
35
38
  * `result` returns the settled `AsyncResult`, so a loader can redirect on a
36
39
  * tag and otherwise let a boundary render the failure. Only defects and
@@ -48,6 +51,7 @@ var Loader_exports = /* @__PURE__ */ __exportAll({
48
51
  fetch: () => fetch,
49
52
  make: () => make,
50
53
  prefetch: () => prefetch,
54
+ prefetchInBrowser: () => prefetchInBrowser,
51
55
  result: () => result
52
56
  });
53
57
  /**
@@ -154,6 +158,54 @@ const prefetch = (source, ...args) => Effect.gen(function* () {
154
158
  yield* start(atom);
155
159
  return make$1(args, atom);
156
160
  });
161
+ /**
162
+ * Starts a source's atom in the browser, and returns its handle: on the
163
+ * server, it starts nothing, and the browser's read starts the atom.
164
+ *
165
+ * **When to use**
166
+ *
167
+ * Use for data only the browser can load: an input the server never sees,
168
+ * such as a token in the URL's fragment or a permission, or a request the
169
+ * server can't make. The rest of the page still renders on the server.
170
+ *
171
+ * **Details**
172
+ *
173
+ * - In the browser, it's `prefetch`: a loader that runs there, such as on a
174
+ * client navigation, starts the atom and takes a lease.
175
+ * - On a request registry, it returns a handle marked `inBrowser` without
176
+ * starting the atom, so the atom needs no serialization key. On the
177
+ * server, `useAtomResult` throws for that handle, so React renders the
178
+ * nearest Suspense boundary's fallback there and renders the boundary in
179
+ * the browser. There, the read starts the atom, and the waterfall check
180
+ * skips it, since a loader asked for it.
181
+ *
182
+ * **Gotchas**
183
+ *
184
+ * - Read the handle under a Suspense boundary: on the server, a read
185
+ * outside one fails the whole render. React reports the boundary it
186
+ * renders in the browser to `onRecoverableError`.
187
+ * - On a page the server rendered, the request starts when the browser
188
+ * renders the read, after hydration's JavaScript has loaded: start the
189
+ * data the server can load with `prefetch` or `ensure`.
190
+ *
191
+ * **Example**
192
+ *
193
+ * ```ts
194
+ * import { Loader } from "raf-effect-atom"
195
+ *
196
+ * const program = Loader.prefetchInBrowser(pushSubscription)
197
+ * ```
198
+ *
199
+ * @stability unstable
200
+ * @category verbs
201
+ * @since 0.2.6
202
+ */
203
+ const prefetchInBrowser = (source, ...args) => Effect.gen(function* () {
204
+ const atom = atomOf(source, args);
205
+ if (isRequestRegistry(yield* AtomRegistry.AtomRegistry)) return inBrowser(args, atom);
206
+ yield* start(atom);
207
+ return make$1(args, atom);
208
+ });
157
209
  const loaded = (args, atom, result) => {
158
210
  const handle = loaded$1(args, atom);
159
211
  results.set(handle, result);
@@ -277,8 +329,8 @@ const either = (first, second) => first === void 0 || second === void 0 ? first
277
329
  *
278
330
  * Use in a framework integration, in loaders that are async functions, or
279
331
  * in an event handler that starts an atom before the state change that
280
- * renders its reader, for state that can't be in the URL, such as a
281
- * search-as-you-type query. In Effect code, use the verbs directly, and run
332
+ * renders its reader, for a choice a click or a submit makes that doesn't
333
+ * belong in the URL. In Effect code, use the verbs directly, and run
282
334
  * the Effect on the registry with `run`.
283
335
  *
284
336
  * **Details**
@@ -319,7 +371,15 @@ const make = (registry, options) => {
319
371
  });
320
372
  return make$1(args, atom);
321
373
  },
374
+ prefetchInBrowser: (source, ...args) => {
375
+ const atom = atomOf(source, args);
376
+ if (isRequestRegistry(registry)) return inBrowser(args, atom);
377
+ afterSeed(registry, () => {
378
+ Effect.runSync(provide(start(atom)));
379
+ });
380
+ return make$1(args, atom);
381
+ },
322
382
  run: (effect, runOptions) => runPromise(provide(effect), either(signal, runOptions?.signal))
323
383
  };
324
384
  };
325
- export { LeaseTTL, Loader_exports, ensure, fetch, make, prefetch, result };
385
+ export { LeaseTTL, Loader_exports, ensure, fetch, make, prefetch, prefetchInBrowser, result };
@@ -1,33 +1,52 @@
1
1
  import * as Hydration from "effect/reactivity/Hydration";
2
+ import * as AtomRegistry from "effect/reactivity/AtomRegistry";
2
3
  import * as React from "react";
3
4
  /**
4
- * Props for `HydrationBoundary`.
5
+ * Props for `HydrationBoundary`: the request's `registry` in a server
6
+ * component, or the `state` it dehydrated anywhere else.
5
7
  *
6
8
  * @stability unstable
7
9
  * @category models
8
10
  * @since 0.1.0
9
11
  */
10
- export interface HydrationBoundaryProps {
12
+ export type HydrationBoundaryProps = {
11
13
  readonly children?: React.ReactNode | undefined;
14
+ /**
15
+ * The request registry of the server component render, such as
16
+ * `raf-effect-atom/rsc`'s `getRegistry()`. Server components only.
17
+ */
18
+ readonly registry: AtomRegistry.AtomRegistry;
19
+ readonly state?: never;
20
+ } | {
21
+ readonly children?: React.ReactNode | undefined;
22
+ readonly registry?: never;
12
23
  /** The state `Hydration.dehydrate` returned on the server. */
13
24
  readonly state?: Iterable<Hydration.DehydratedAtom> | undefined;
14
- }
25
+ };
15
26
  /**
16
27
  * Hydrates the state a server registry dehydrated, streamed results
17
28
  * included.
18
29
  *
19
30
  * **When to use**
20
31
  *
21
- * Use where the server's state reaches the React tree as a prop, such as
22
- * React Server Components props or a React Router loader's data. A router
23
- * that hydrates before rendering calls `Hydration.hydrate` instead.
32
+ * Use where the server's state reaches the React tree through rendering
33
+ * rather than a router: a server component's registry, or a React Router
34
+ * loader's data. A router that hydrates before rendering calls
35
+ * `Hydration.hydrate` instead.
24
36
  *
25
37
  * **Details**
26
38
  *
27
- * Settled values go to `@effect/atom-react`'s `HydrationBoundary`, which
28
- * hydrates new atoms during render and existing ones after commit. Each
29
- * streamed result is held until it arrives, so `useAtomResult` waits for it
30
- * instead of requesting it again.
39
+ * In a server component, pass the request's `registry`. Server components
40
+ * get their own build of `HydrationBoundary` through the package's
41
+ * `react-server` export condition: it waits for the registry's seed,
42
+ * dehydrates it, and sends the state with the server component's payload,
43
+ * so results still loading stream in as they settle.
44
+ *
45
+ * Anywhere else, pass the `state` a loader dehydrated. Settled values go to
46
+ * `@effect/atom-react`'s `HydrationBoundary`, which hydrates new atoms
47
+ * during render and existing ones after commit. Each streamed result is
48
+ * held until it arrives, so `useAtomResult` waits for it instead of
49
+ * requesting it again.
31
50
  *
32
51
  * During server rendering, when `state` is the array `dehydrate` returned,
33
52
  * as React Router passes a loader's data to the server render, the children
@@ -37,15 +56,29 @@ export interface HydrationBoundaryProps {
37
56
  *
38
57
  * **Gotchas**
39
58
  *
40
- * In development, it throws during server rendering when the registry isn't
41
- * a request registry, such as `@effect/atom-react`'s default one, which
42
- * every request shares: one request's state would reach the others.
59
+ * `registry` works only in server components: in a client component it
60
+ * throws, since a registry can't cross to the browser. In development, it
61
+ * throws during server rendering when the registry isn't a request
62
+ * registry, such as `@effect/atom-react`'s default one, which every request
63
+ * shares: one request's state would reach the others.
43
64
  *
44
65
  * **Example**
45
66
  *
46
67
  * ```tsx
47
68
  * import { HydrationBoundary } from "raf-effect-atom"
69
+ * import { ensure, getRegistry } from "raf-effect-atom/rsc"
70
+ *
71
+ * // A server component
72
+ * export default async function IssueRoute(props: { params: Promise<{ id: string }> }) {
73
+ * const handle = await ensure(issue, Number((await props.params).id))
74
+ * return (
75
+ * <HydrationBoundary registry={getRegistry()}>
76
+ * <IssuePage issue={handle} />
77
+ * </HydrationBoundary>
78
+ * )
79
+ * }
48
80
  *
81
+ * // A React Router route component
49
82
  * <HydrationBoundary state={loaderData.atoms}>
50
83
  * <IssuePage issue={loaderData.issue} />
51
84
  * </HydrationBoundary>
@@ -19,16 +19,24 @@ import { jsx } from "react/jsx-runtime";
19
19
  *
20
20
  * **When to use**
21
21
  *
22
- * Use where the server's state reaches the React tree as a prop, such as
23
- * React Server Components props or a React Router loader's data. A router
24
- * that hydrates before rendering calls `Hydration.hydrate` instead.
22
+ * Use where the server's state reaches the React tree through rendering
23
+ * rather than a router: a server component's registry, or a React Router
24
+ * loader's data. A router that hydrates before rendering calls
25
+ * `Hydration.hydrate` instead.
25
26
  *
26
27
  * **Details**
27
28
  *
28
- * Settled values go to `@effect/atom-react`'s `HydrationBoundary`, which
29
- * hydrates new atoms during render and existing ones after commit. Each
30
- * streamed result is held until it arrives, so `useAtomResult` waits for it
31
- * instead of requesting it again.
29
+ * In a server component, pass the request's `registry`. Server components
30
+ * get their own build of `HydrationBoundary` through the package's
31
+ * `react-server` export condition: it waits for the registry's seed,
32
+ * dehydrates it, and sends the state with the server component's payload,
33
+ * so results still loading stream in as they settle.
34
+ *
35
+ * Anywhere else, pass the `state` a loader dehydrated. Settled values go to
36
+ * `@effect/atom-react`'s `HydrationBoundary`, which hydrates new atoms
37
+ * during render and existing ones after commit. Each streamed result is
38
+ * held until it arrives, so `useAtomResult` waits for it instead of
39
+ * requesting it again.
32
40
  *
33
41
  * During server rendering, when `state` is the array `dehydrate` returned,
34
42
  * as React Router passes a loader's data to the server render, the children
@@ -38,15 +46,29 @@ import { jsx } from "react/jsx-runtime";
38
46
  *
39
47
  * **Gotchas**
40
48
  *
41
- * In development, it throws during server rendering when the registry isn't
42
- * a request registry, such as `@effect/atom-react`'s default one, which
43
- * every request shares: one request's state would reach the others.
49
+ * `registry` works only in server components: in a client component it
50
+ * throws, since a registry can't cross to the browser. In development, it
51
+ * throws during server rendering when the registry isn't a request
52
+ * registry, such as `@effect/atom-react`'s default one, which every request
53
+ * shares: one request's state would reach the others.
44
54
  *
45
55
  * **Example**
46
56
  *
47
57
  * ```tsx
48
58
  * import { HydrationBoundary } from "raf-effect-atom"
59
+ * import { ensure, getRegistry } from "raf-effect-atom/rsc"
60
+ *
61
+ * // A server component
62
+ * export default async function IssueRoute(props: { params: Promise<{ id: string }> }) {
63
+ * const handle = await ensure(issue, Number((await props.params).id))
64
+ * return (
65
+ * <HydrationBoundary registry={getRegistry()}>
66
+ * <IssuePage issue={handle} />
67
+ * </HydrationBoundary>
68
+ * )
69
+ * }
49
70
  *
71
+ * // A React Router route component
50
72
  * <HydrationBoundary state={loaderData.atoms}>
51
73
  * <IssuePage issue={loaderData.issue} />
52
74
  * </HydrationBoundary>
@@ -57,6 +79,7 @@ import { jsx } from "react/jsx-runtime";
57
79
  * @since 0.1.0
58
80
  */
59
81
  const HydrationBoundary$1 = (props) => {
82
+ if (props.registry !== void 0) throw new Error("raf-effect-atom: HydrationBoundary needs `state` here. `registry` works only in server components.");
60
83
  const context = React.useContext(RegistryContext);
61
84
  const state = props.state;
62
85
  const source = isServer() ? sourceOf(state) : void 0;
@@ -0,0 +1,25 @@
1
+ import { HydrationBoundaryProps } from "./ReactHydration.mjs";
2
+ import * as React from "react";
3
+ /**
4
+ * Sends a server component's request registry to the client components
5
+ * under it.
6
+ *
7
+ * **Details**
8
+ *
9
+ * With `registry`, it waits for the registry's seed, dehydrates the atoms
10
+ * started so far, and renders the client `HydrationBoundary` with that
11
+ * state. React serializes the state's promises with the payload, so a result
12
+ * still loading streams in when it settles. With `state`, it passes that
13
+ * state on.
14
+ *
15
+ * **Gotchas**
16
+ *
17
+ * Call the verbs before rendering it: each boundary sends what its registry
18
+ * held when it rendered. It throws for a registry that isn't a request
19
+ * registry, which every request would share.
20
+ *
21
+ * @stability unstable
22
+ * @category components
23
+ * @since 0.3.0
24
+ */
25
+ export declare const HydrationBoundary: (props: HydrationBoundaryProps) => Promise<React.ReactNode>;
@@ -0,0 +1,41 @@
1
+ import { dehydrate } from "./Hydration.mjs";
2
+ import { isRequestRegistry, settled } from "./RequestRegistry.mjs";
3
+ import { HydrationBoundary as HydrationBoundary$1 } from "./ReactHydration.mjs";
4
+ import { jsx } from "react/jsx-runtime";
5
+ // src/ReactHydration.react-server.tsx
6
+ /**
7
+ * Sends a server component's request registry to the client components
8
+ * under it.
9
+ *
10
+ * **Details**
11
+ *
12
+ * With `registry`, it waits for the registry's seed, dehydrates the atoms
13
+ * started so far, and renders the client `HydrationBoundary` with that
14
+ * state. React serializes the state's promises with the payload, so a result
15
+ * still loading streams in when it settles. With `state`, it passes that
16
+ * state on.
17
+ *
18
+ * **Gotchas**
19
+ *
20
+ * Call the verbs before rendering it: each boundary sends what its registry
21
+ * held when it rendered. It throws for a registry that isn't a request
22
+ * registry, which every request would share.
23
+ *
24
+ * @stability unstable
25
+ * @category components
26
+ * @since 0.3.0
27
+ */
28
+ const HydrationBoundary = async (props) => {
29
+ const registry = props.registry;
30
+ if (registry === void 0) return /* @__PURE__ */ jsx(HydrationBoundary$1, {
31
+ state: props.state,
32
+ children: props.children
33
+ });
34
+ if (!isRequestRegistry(registry)) throw new Error("raf-effect-atom: HydrationBoundary got a registry that isn't a request registry, which would share results between requests. Pass raf-effect-atom/rsc's getRegistry(), or one makeRequestAtoms made.");
35
+ await settled(registry);
36
+ return /* @__PURE__ */ jsx(HydrationBoundary$1, {
37
+ state: dehydrate(registry),
38
+ children: props.children
39
+ });
40
+ };
41
+ export { HydrationBoundary };
@@ -1,5 +1,4 @@
1
1
  import { Loader } from "./Loader.mjs";
2
- import { RegistryProvider } from "./RegistryProvider.mjs";
3
2
  import * as EffectHydration from "effect/reactivity/Hydration";
4
3
  import * as AtomRegistry from "effect/reactivity/AtomRegistry";
5
4
  /**
@@ -140,5 +139,4 @@ export declare const atomsMiddleware: (context: AtomsContext, options?: AtomsMid
140
139
  * @category integrations
141
140
  * @since 0.2.1
142
141
  */
143
- export declare const clientAtomsMiddleware: (context: AtomsContext) => <Result>(args: MiddlewareArgs, next: () => Promise<Result>) => Promise<Result>;
144
- export { RegistryProvider };
142
+ export declare const clientAtomsMiddleware: (context: AtomsContext) => <Result>(args: MiddlewareArgs, next: () => Promise<Result>) => Promise<Result>;
@@ -2,7 +2,6 @@ import { dehydrate } from "./Hydration.mjs";
2
2
  import { make, settled } from "./RequestRegistry.mjs";
3
3
  import { make as make$1 } from "./Loader.mjs";
4
4
  import { current } from "./internal/registry.mjs";
5
- import { RegistryProvider } from "./RegistryProvider.mjs";
6
5
  import * as Predicate from "effect/Predicate";
7
6
  // src/ReactRouter.ts
8
7
  /**
@@ -11,11 +10,12 @@ import * as Predicate from "effect/Predicate";
11
10
  * A root `middleware` gives each server request its own registry, under a
12
11
  * router context the app creates. Loaders start atoms with its verbs and
13
12
  * return its `dehydrate()` with their data; the route component hydrates it
14
- * with `HydrationBoundary`, inside the root's `RegistryProvider`. A root
15
- * `clientMiddleware` puts the browser's registry under the same context, so
16
- * one function can be both the route's `loader` and its `clientLoader`: after the first
17
- * page, loaders run in the browser, where `ensure` finds what the registry
18
- * already holds. Nothing is imported from `react-router`.
13
+ * with `HydrationBoundary`, inside the root's `RegistryProvider`, both from
14
+ * `raf-effect-atom`. A root `clientMiddleware` puts the browser's registry
15
+ * under the same context, so one function can be both the route's `loader`
16
+ * and its `clientLoader`: after the first page, loaders run in the browser,
17
+ * where `ensure` finds what the registry already holds. Nothing is imported
18
+ * from `react-router`.
19
19
  *
20
20
  * **Example**
21
21
  *
@@ -192,7 +192,7 @@ const disposeAfter = (body, registry) => {
192
192
  */
193
193
  const clientAtomsMiddleware = (context) => (args, next) => {
194
194
  const registry = current();
195
- if (registry === void 0) throw new Error("clientAtomsMiddleware found no mounted RegistryProvider: render raf-effect-atom/react-router's RegistryProvider around the app in the root layout.");
195
+ if (registry === void 0) throw new Error("clientAtomsMiddleware found no mounted RegistryProvider: render raf-effect-atom's RegistryProvider around the app in the root layout.");
196
196
  args.context.set(context, {
197
197
  ...make$1(registry, { signal: args.request.signal }),
198
198
  dehydrate: () => [],
@@ -200,4 +200,4 @@ const clientAtomsMiddleware = (context) => (args, next) => {
200
200
  });
201
201
  return next();
202
202
  };
203
- export { RegistryProvider, atomsMiddleware, clientAtomsMiddleware };
203
+ export { atomsMiddleware, clientAtomsMiddleware };
@@ -24,7 +24,7 @@ import * as React from "react";
24
24
  * **Example**
25
25
  *
26
26
  * ```tsx
27
- * import { RegistryProvider } from "raf-effect-atom/next" // or "raf-effect-atom/react-router"
27
+ * import { RegistryProvider } from "raf-effect-atom"
28
28
  *
29
29
  * export default function RootLayout(props: { children: React.ReactNode }) {
30
30
  * return (
@@ -5,7 +5,8 @@ import * as React from "react";
5
5
  import { jsx } from "react/jsx-runtime";
6
6
  // src/RegistryProvider.tsx
7
7
  /**
8
- * The registry provider shared by the Next.js and React Router integrations.
8
+ * The registry provider for apps that render the server's state through
9
+ * `HydrationBoundary`, such as React Server Components and React Router.
9
10
  *
10
11
  * @stability unstable
11
12
  * @since 0.1.0
@@ -35,7 +36,7 @@ import { jsx } from "react/jsx-runtime";
35
36
  * **Example**
36
37
  *
37
38
  * ```tsx
38
- * import { RegistryProvider } from "raf-effect-atom/next" // or "raf-effect-atom/react-router"
39
+ * import { RegistryProvider } from "raf-effect-atom"
39
40
  *
40
41
  * export default function RootLayout(props: { children: React.ReactNode }) {
41
42
  * return (
@@ -1,13 +1,10 @@
1
1
  import { ArgsOf, Handle, LoadedHandle, Source } from "./Handle.mjs";
2
2
  import { Loader } from "./Loader.mjs";
3
3
  import { Options } from "./RequestRegistry.mjs";
4
- import { HydrateAtoms } from "./NextClient.mjs";
5
- import { RegistryProvider } from "./RegistryProvider.mjs";
6
- import * as EffectHydration from "effect/reactivity/Hydration";
7
4
  import * as AtomRegistry from "effect/reactivity/AtomRegistry";
8
5
  /**
9
6
  * The loader verbs over the request registry of the server render in
10
- * progress, with its registry and `dehydrate`.
7
+ * progress, with its registry.
11
8
  *
12
9
  * @stability unstable
13
10
  * @category models
@@ -15,30 +12,28 @@ import * as AtomRegistry from "effect/reactivity/AtomRegistry";
15
12
  */
16
13
  export interface RequestAtoms extends Loader {
17
14
  /**
18
- * `Hydration.dehydrate` on the request's registry, for `HydrateAtoms`,
19
- * once its seed has settled.
15
+ * The request registry of the server render in progress, for
16
+ * `HydrationBoundary`'s `registry`.
20
17
  */
21
- readonly dehydrate: () => Promise<Array<EffectHydration.DehydratedAtom>>;
22
- /** The request registry of the server render in progress. */
23
18
  readonly getRegistry: () => AtomRegistry.AtomRegistry;
24
19
  }
25
20
  /**
26
- * Creates the verbs, `getRegistry` and `dehydrate` over a request registry
27
- * that `seed` prepares.
21
+ * Creates the verbs and `getRegistry` over a request registry that `seed`
22
+ * prepares.
28
23
  *
29
24
  * **When to use**
30
25
  *
31
26
  * Use when the request's atoms need something from the request before they
32
27
  * run, such as an RPC protocol that forwards the page request's cookie. Make
33
28
  * them once, in a server-only module, and call the verbs on them instead of
34
- * the ones `raf-effect-atom/next` exports.
29
+ * the ones `raf-effect-atom/rsc` exports.
35
30
  *
36
31
  * **Details**
37
32
  *
38
33
  * The seed runs on each request's registry when a server component first
39
34
  * uses it. It can be async, as `headers()` is: `ensure` and `fetch` wait
40
35
  * for it, `prefetch` returns its handle at once and starts the atom once the
41
- * seed resolves, and `dehydrate` waits for both.
36
+ * seed resolves, and `HydrationBoundary` waits for both.
42
37
  *
43
38
  * **Example**
44
39
  *
@@ -46,7 +41,7 @@ export interface RequestAtoms extends Loader {
46
41
  * // src/atoms.server.ts
47
42
  * import "server-only"
48
43
  * import { headers } from "next/headers"
49
- * import { makeRequestAtoms } from "raf-effect-atom/next"
44
+ * import { makeRequestAtoms } from "raf-effect-atom/rsc"
50
45
  *
51
46
  * export const atoms = makeRequestAtoms({
52
47
  * seed: async (registry) => {
@@ -90,6 +85,15 @@ export declare const getRegistry: () => AtomRegistry.AtomRegistry;
90
85
  * @since 0.1.0
91
86
  */
92
87
  export declare const prefetch: <S extends Source>(source: S, ...args: ArgsOf<S>) => Handle<S>;
88
+ /**
89
+ * `Loader.prefetchInBrowser` on the request's registry: returns a handle
90
+ * whose atom the browser's read starts, without starting it on the server.
91
+ *
92
+ * @stability unstable
93
+ * @category verbs
94
+ * @since 0.2.6
95
+ */
96
+ export declare const prefetchInBrowser: <S extends Source>(source: S, ...args: ArgsOf<S>) => Handle<S>;
93
97
  /**
94
98
  * `Loader.ensure` on the request's registry: starts a source's atom, waits
95
99
  * for its result, and returns its handle.
@@ -127,26 +131,4 @@ export declare const fetch: <S extends Source>(source: S, ...args: ArgsOf<S>) =>
127
131
  * @category verbs
128
132
  * @since 0.2.3
129
133
  */
130
- export declare const run: Loader["run"];
131
- /**
132
- * `Hydration.dehydrate` on the request's registry, for `HydrateAtoms`.
133
- *
134
- * **Gotchas**
135
- *
136
- * Includes the atoms started so far, so call it after the server component's
137
- * verbs. Each `HydrateAtoms` hydrates what its own `dehydrate()` saw.
138
- *
139
- * **Example**
140
- *
141
- * ```tsx
142
- * <HydrateAtoms state={await dehydrate()}>
143
- * <Issue issue={handle} />
144
- * </HydrateAtoms>
145
- * ```
146
- *
147
- * @stability unstable
148
- * @category dehydration
149
- * @since 0.1.0
150
- */
151
- export declare const dehydrate: () => Promise<Array<EffectHydration.DehydratedAtom>>;
152
- export { HydrateAtoms, RegistryProvider };
134
+ export declare const run: Loader["run"];
@@ -1,24 +1,22 @@
1
- import { make } from "./internal/next.mjs";
2
- import { HydrateAtoms } from "./NextClient.mjs";
3
- import { RegistryProvider } from "./RegistryProvider.mjs";
4
- // src/Next.ts
1
+ import { make } from "./internal/rsc.mjs";
2
+ // src/Rsc.ts
5
3
  /**
6
- * Creates the verbs, `getRegistry` and `dehydrate` over a request registry
7
- * that `seed` prepares.
4
+ * Creates the verbs and `getRegistry` over a request registry that `seed`
5
+ * prepares.
8
6
  *
9
7
  * **When to use**
10
8
  *
11
9
  * Use when the request's atoms need something from the request before they
12
10
  * run, such as an RPC protocol that forwards the page request's cookie. Make
13
11
  * them once, in a server-only module, and call the verbs on them instead of
14
- * the ones `raf-effect-atom/next` exports.
12
+ * the ones `raf-effect-atom/rsc` exports.
15
13
  *
16
14
  * **Details**
17
15
  *
18
16
  * The seed runs on each request's registry when a server component first
19
17
  * uses it. It can be async, as `headers()` is: `ensure` and `fetch` wait
20
18
  * for it, `prefetch` returns its handle at once and starts the atom once the
21
- * seed resolves, and `dehydrate` waits for both.
19
+ * seed resolves, and `HydrationBoundary` waits for both.
22
20
  *
23
21
  * **Example**
24
22
  *
@@ -26,7 +24,7 @@ import { RegistryProvider } from "./RegistryProvider.mjs";
26
24
  * // src/atoms.server.ts
27
25
  * import "server-only"
28
26
  * import { headers } from "next/headers"
29
- * import { makeRequestAtoms } from "raf-effect-atom/next"
27
+ * import { makeRequestAtoms } from "raf-effect-atom/rsc"
30
28
  *
31
29
  * export const atoms = makeRequestAtoms({
32
30
  * seed: async (registry) => {
@@ -72,6 +70,15 @@ const getRegistry = atoms.getRegistry;
72
70
  */
73
71
  const prefetch = atoms.prefetch;
74
72
  /**
73
+ * `Loader.prefetchInBrowser` on the request's registry: returns a handle
74
+ * whose atom the browser's read starts, without starting it on the server.
75
+ *
76
+ * @stability unstable
77
+ * @category verbs
78
+ * @since 0.2.6
79
+ */
80
+ const prefetchInBrowser = atoms.prefetchInBrowser;
81
+ /**
75
82
  * `Loader.ensure` on the request's registry: starts a source's atom, waits
76
83
  * for its result, and returns its handle.
77
84
  *
@@ -109,25 +116,4 @@ const fetch = atoms.fetch;
109
116
  * @since 0.2.3
110
117
  */
111
118
  const run = atoms.run;
112
- /**
113
- * `Hydration.dehydrate` on the request's registry, for `HydrateAtoms`.
114
- *
115
- * **Gotchas**
116
- *
117
- * Includes the atoms started so far, so call it after the server component's
118
- * verbs. Each `HydrateAtoms` hydrates what its own `dehydrate()` saw.
119
- *
120
- * **Example**
121
- *
122
- * ```tsx
123
- * <HydrateAtoms state={await dehydrate()}>
124
- * <Issue issue={handle} />
125
- * </HydrateAtoms>
126
- * ```
127
- *
128
- * @stability unstable
129
- * @category dehydration
130
- * @since 0.1.0
131
- */
132
- const dehydrate = atoms.dehydrate;
133
- export { HydrateAtoms, RegistryProvider, dehydrate, ensure, fetch, getRegistry, makeRequestAtoms, prefetch, run };
119
+ export { ensure, fetch, getRegistry, makeRequestAtoms, prefetch, prefetchInBrowser, run };
@@ -51,7 +51,7 @@ export declare const WaterfallDetection: (props: {
51
51
  }) => React.ReactNode;
52
52
  /**
53
53
  * Starts a source's atom during render and returns its handle, for an input
54
- * only a render knows, such as a debounced search query.
54
+ * only a render knows, such as an id read from another atom's result.
55
55
  *
56
56
  * **When to use**
57
57
  *
@@ -55,7 +55,7 @@ const WaterfallDetection = (props) => process.env.NODE_ENV === "production" ? pr
55
55
  });
56
56
  /**
57
57
  * Starts a source's atom during render and returns its handle, for an input
58
- * only a render knows, such as a debounced search query.
58
+ * only a render knows, such as an id read from another atom's result.
59
59
  *
60
60
  * **When to use**
61
61
  *
package/dist/index.d.mts CHANGED
@@ -1,8 +1,10 @@
1
1
  import { ArgsOf, ErrorOf, Handle, HandleKey, LoadedHandle, Source, SuccessOf } from "./Handle.mjs";
2
2
  import { Loader_d_exports } from "./Loader.mjs";
3
3
  import { RequestRegistry_d_exports } from "./RequestRegistry.mjs";
4
- import { HydrationBoundary, HydrationBoundaryProps } from "./ReactHydration.mjs";
5
4
  import { Hydration_d_exports } from "./Hydration.mjs";
6
5
  import { useAtomResult } from "./Hooks.mjs";
6
+ import { HydrationBoundary, HydrationBoundaryProps } from "./ReactHydration.mjs";
7
+ import { RegistryProvider } from "./RegistryProvider.mjs";
7
8
  import { WaterfallDetection, WaterfallMode, useWaterfall } from "./Waterfalls.mjs";
8
- export { ArgsOf, ErrorOf, Handle, HandleKey, Hydration_d_exports as Hydration, HydrationBoundary, HydrationBoundaryProps, LoadedHandle, Loader_d_exports as Loader, RequestRegistry_d_exports as RequestRegistry, Source, SuccessOf, WaterfallDetection, WaterfallMode, useAtomResult, useWaterfall };
9
+ import "./index.shared.mjs";
10
+ export { ArgsOf, ErrorOf, Handle, HandleKey, Hydration_d_exports as Hydration, HydrationBoundary, type HydrationBoundaryProps, LoadedHandle, Loader_d_exports as Loader, RegistryProvider, RequestRegistry_d_exports as RequestRegistry, Source, SuccessOf, WaterfallDetection, WaterfallMode, useAtomResult, useWaterfall };
package/dist/index.mjs CHANGED
@@ -2,6 +2,7 @@ import { Hydration_exports } from "./Hydration.mjs";
2
2
  import { RequestRegistry_exports } from "./RequestRegistry.mjs";
3
3
  import { Loader_exports } from "./Loader.mjs";
4
4
  import { useAtomResult } from "./Hooks.mjs";
5
- import { HydrationBoundary } from "./ReactHydration.mjs";
5
+ import { RegistryProvider } from "./RegistryProvider.mjs";
6
6
  import { WaterfallDetection, useWaterfall } from "./Waterfalls.mjs";
7
- export { Hydration_exports as Hydration, HydrationBoundary, Loader_exports as Loader, RequestRegistry_exports as RequestRegistry, WaterfallDetection, useAtomResult, useWaterfall };
7
+ import { HydrationBoundary } from "./ReactHydration.mjs";
8
+ export { Hydration_exports as Hydration, HydrationBoundary, Loader_exports as Loader, RegistryProvider, RequestRegistry_exports as RequestRegistry, WaterfallDetection, useAtomResult, useWaterfall };
@@ -0,0 +1,11 @@
1
+ import { ArgsOf, ErrorOf, Handle, HandleKey, LoadedHandle, Source, SuccessOf } from "./Handle.mjs";
2
+ import { Loader_d_exports } from "./Loader.mjs";
3
+ import { RequestRegistry_d_exports } from "./RequestRegistry.mjs";
4
+ import { Hydration_d_exports } from "./Hydration.mjs";
5
+ import { useAtomResult } from "./Hooks.mjs";
6
+ import { HydrationBoundaryProps } from "./ReactHydration.mjs";
7
+ import { RegistryProvider } from "./RegistryProvider.mjs";
8
+ import { WaterfallDetection, WaterfallMode, useWaterfall } from "./Waterfalls.mjs";
9
+ import "./index.shared.mjs";
10
+ import { HydrationBoundary } from "./ReactHydration.react-server.mjs";
11
+ export { ArgsOf, ErrorOf, Handle, HandleKey, Hydration_d_exports as Hydration, HydrationBoundary, type HydrationBoundaryProps, LoadedHandle, Loader_d_exports as Loader, RegistryProvider, RequestRegistry_d_exports as RequestRegistry, Source, SuccessOf, WaterfallDetection, WaterfallMode, useAtomResult, useWaterfall };
@@ -0,0 +1,8 @@
1
+ import { Hydration_exports } from "./Hydration.mjs";
2
+ import { RequestRegistry_exports } from "./RequestRegistry.mjs";
3
+ import { Loader_exports } from "./Loader.mjs";
4
+ import { useAtomResult } from "./Hooks.mjs";
5
+ import { RegistryProvider } from "./RegistryProvider.mjs";
6
+ import { WaterfallDetection, useWaterfall } from "./Waterfalls.mjs";
7
+ import { HydrationBoundary } from "./ReactHydration.react-server.mjs";
8
+ export { Hydration_exports as Hydration, HydrationBoundary, Loader_exports as Loader, RegistryProvider, RequestRegistry_exports as RequestRegistry, WaterfallDetection, useAtomResult, useWaterfall };
@@ -0,0 +1,9 @@
1
+ import { ArgsOf, ErrorOf, Handle, HandleKey, LoadedHandle, Source, SuccessOf } from "./Handle.mjs";
2
+ import { Loader_d_exports } from "./Loader.mjs";
3
+ import { RequestRegistry_d_exports } from "./RequestRegistry.mjs";
4
+ import { Hydration_d_exports } from "./Hydration.mjs";
5
+ import { useAtomResult } from "./Hooks.mjs";
6
+ import { HydrationBoundaryProps } from "./ReactHydration.mjs";
7
+ import { RegistryProvider } from "./RegistryProvider.mjs";
8
+ import { WaterfallDetection, WaterfallMode, useWaterfall } from "./Waterfalls.mjs";
9
+ export { ArgsOf, ErrorOf, Handle, HandleKey, Hydration_d_exports as Hydration, type HydrationBoundaryProps, LoadedHandle, Loader_d_exports as Loader, RegistryProvider, RequestRegistry_d_exports as RequestRegistry, Source, SuccessOf, WaterfallDetection, WaterfallMode, useAtomResult, useWaterfall };
@@ -23,9 +23,14 @@ const make = (args, atom) => ({
23
23
  args,
24
24
  key: keyOf(atom)
25
25
  });
26
+ const inBrowser = (args, atom) => ({
27
+ args,
28
+ inBrowser: true,
29
+ key: keyOf(atom)
30
+ });
26
31
  const loaded = (args, atom) => ({
27
32
  args,
28
33
  key: keyOf(atom),
29
34
  loaded: true
30
35
  });
31
- export { atomOf, loaded, make, sourceOf };
36
+ export { atomOf, inBrowser, loaded, make, sourceOf };
@@ -1,8 +1,7 @@
1
- import { dehydrate } from "../Hydration.mjs";
2
- import { make as make$1, settled } from "../RequestRegistry.mjs";
1
+ import { make as make$1 } from "../RequestRegistry.mjs";
3
2
  import { make as make$2 } from "../Loader.mjs";
4
3
  import * as React from "react";
5
- // src/internal/next.ts
4
+ // src/internal/rsc.ts
6
5
  const renderSignal = () => {
7
6
  return React.cacheSignal();
8
7
  };
@@ -14,15 +13,11 @@ const make = (options) => {
14
13
  });
15
14
  const getLoader = React.cache(() => make$2(getRegistry(), { signal: renderSignal() ?? void 0 }));
16
15
  return {
17
- dehydrate: async () => {
18
- const registry = getRegistry();
19
- await settled(registry);
20
- return dehydrate(registry);
21
- },
22
16
  ensure: (source, ...args) => getLoader().ensure(source, ...args),
23
17
  fetch: (source, ...args) => getLoader().fetch(source, ...args),
24
18
  getRegistry,
25
19
  prefetch: (source, ...args) => getLoader().prefetch(source, ...args),
20
+ prefetchInBrowser: (source, ...args) => getLoader().prefetchInBrowser(source, ...args),
26
21
  run: (effect, runOptions) => getLoader().run(effect, runOptions)
27
22
  };
28
23
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "raf-effect-atom",
3
- "version": "0.2.5",
3
+ "version": "0.3.0",
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",
@@ -33,9 +33,12 @@
33
33
  "type": "module",
34
34
  "sideEffects": false,
35
35
  "exports": {
36
- ".": "./dist/index.mjs",
37
- "./next": "./dist/Next.mjs",
36
+ ".": {
37
+ "react-server": "./dist/index.react-server.mjs",
38
+ "default": "./dist/index.mjs"
39
+ },
38
40
  "./react-router": "./dist/ReactRouter.mjs",
41
+ "./rsc": "./dist/Rsc.mjs",
39
42
  "./tanstack-router": "./dist/TanStackRouter.mjs"
40
43
  },
41
44
  "peerDependencies": {
@@ -58,7 +61,7 @@
58
61
  "@types/react-dom": "19.3.0",
59
62
  "@vitest/coverage-v8": "5.0.5",
60
63
  "effect": "4.0.6",
61
- "effect-atom-boundary": "0.1.1",
64
+ "effect-atom-boundary": "0.1.2",
62
65
  "happy-dom": "21.0.4",
63
66
  "react": "19.3.1",
64
67
  "react-dom": "19.3.1",
@@ -1,32 +0,0 @@
1
- import { HydrationBoundaryProps } from "./ReactHydration.mjs";
2
- import * as React from "react";
3
- /**
4
- * Hydrates the state a server component dehydrated with `await dehydrate()`,
5
- * streamed results included.
6
- *
7
- * **Details**
8
- *
9
- * React serializes the state's promises with the server component's
10
- * payload, so a result still loading streams in when it settles, and
11
- * `useAtomResult` waits for it instead of requesting it again.
12
- *
13
- * **Example**
14
- *
15
- * ```tsx
16
- * import { dehydrate, ensure, HydrateAtoms } from "raf-effect-atom/next"
17
- *
18
- * export default async function IssuePage(props: { params: Promise<{ id: string }> }) {
19
- * const handle = await ensure(issue, Number((await props.params).id))
20
- * return (
21
- * <HydrateAtoms state={await dehydrate()}>
22
- * <Issue issue={handle} />
23
- * </HydrateAtoms>
24
- * )
25
- * }
26
- * ```
27
- *
28
- * @stability unstable
29
- * @category components
30
- * @since 0.1.0
31
- */
32
- export declare const HydrateAtoms: (props: HydrationBoundaryProps) => React.ReactNode;
@@ -1,44 +0,0 @@
1
- "use client";
2
- import { HydrationBoundary } from "./ReactHydration.mjs";
3
- import { jsx } from "react/jsx-runtime";
4
- // src/NextClient.tsx
5
- /**
6
- * The client components of `raf-effect-atom/next`.
7
- *
8
- * @stability unstable
9
- * @since 0.1.0
10
- */
11
- /**
12
- * Hydrates the state a server component dehydrated with `await dehydrate()`,
13
- * streamed results included.
14
- *
15
- * **Details**
16
- *
17
- * React serializes the state's promises with the server component's
18
- * payload, so a result still loading streams in when it settles, and
19
- * `useAtomResult` waits for it instead of requesting it again.
20
- *
21
- * **Example**
22
- *
23
- * ```tsx
24
- * import { dehydrate, ensure, HydrateAtoms } from "raf-effect-atom/next"
25
- *
26
- * export default async function IssuePage(props: { params: Promise<{ id: string }> }) {
27
- * const handle = await ensure(issue, Number((await props.params).id))
28
- * return (
29
- * <HydrateAtoms state={await dehydrate()}>
30
- * <Issue issue={handle} />
31
- * </HydrateAtoms>
32
- * )
33
- * }
34
- * ```
35
- *
36
- * @stability unstable
37
- * @category components
38
- * @since 0.1.0
39
- */
40
- const HydrateAtoms = (props) => /* @__PURE__ */ jsx(HydrationBoundary, {
41
- state: props.state,
42
- children: props.children
43
- });
44
- export { HydrateAtoms };