raf-effect-atom 0.2.6 → 0.3.1

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 (31) hide show
  1. package/CHANGELOG.md +35 -4
  2. package/README.md +4 -4
  3. package/dist/Loader.d.mts +5 -6
  4. package/dist/Loader.mjs +4 -5
  5. package/dist/hydration-boundary-react-server.d.mts +25 -0
  6. package/dist/hydration-boundary-react-server.mjs +41 -0
  7. package/dist/hydration-boundary.d.mts +91 -0
  8. package/dist/{ReactHydration.mjs → hydration-boundary.mjs} +34 -11
  9. package/dist/index-react-server.d.mts +11 -0
  10. package/dist/index-react-server.mjs +8 -0
  11. package/dist/index-shared.d.mts +9 -0
  12. package/dist/index.d.mts +8 -6
  13. package/dist/index.mjs +5 -4
  14. package/dist/internal/{next.mjs → rsc.mjs} +2 -8
  15. package/dist/{ReactRouter.d.mts → react-router.d.mts} +1 -3
  16. package/dist/{ReactRouter.mjs → react-router.mjs} +9 -9
  17. package/dist/{RegistryProvider.d.mts → registry-provider.d.mts} +1 -1
  18. package/dist/{RegistryProvider.mjs → registry-provider.mjs} +4 -3
  19. package/dist/{Next.d.mts → rsc.d.mts} +10 -37
  20. package/dist/{Next.mjs → rsc.mjs} +8 -31
  21. package/dist/{TanStackRouter.mjs → tanstack-router.mjs} +1 -1
  22. package/dist/{Hooks.d.mts → use-atom-result.d.mts} +65 -9
  23. package/dist/{Hooks.mjs → use-atom-result.mjs} +118 -17
  24. package/dist/{Waterfalls.d.mts → waterfalls.d.mts} +3 -4
  25. package/dist/{Waterfalls.mjs → waterfalls.mjs} +3 -4
  26. package/package.json +9 -6
  27. package/dist/NextClient.d.mts +0 -32
  28. package/dist/NextClient.mjs +0 -44
  29. package/dist/ReactHydration.d.mts +0 -58
  30. /package/dist/{Handle.d.mts → handle.d.mts} +0 -0
  31. /package/dist/{TanStackRouter.d.mts → tanstack-router.d.mts} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,19 +1,50 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.1](https://github.com/KATT/raf-effect-atom/compare/raf-effect-atom%400.3.0...raf-effect-atom%400.3.1) (2026-10-24)
4
+
5
+ ### Features
6
+
7
+ * **raf-effect-atom:** add useHandleValue, a handle read that doesn't suspend ([4b083f7](https://github.com/KATT/raf-effect-atom/commit/4b083f7dee91834cda71691f44a1498061f3cfe1))
8
+
9
+ ### Code Refactoring
10
+
11
+ * name files in kebab-case, PascalCase only for Effect modules ([d76b06a](https://github.com/KATT/raf-effect-atom/commit/d76b06aef3d04d6be6c0b0e086a756234ab3f253))
12
+
13
+ ### Documentation
14
+
15
+ * **raf-effect-atom:** pass a read's result to a boundary's hook in the README and JSDoc ([fd82e5c](https://github.com/KATT/raf-effect-atom/commit/fd82e5c1f92b3f99dc166140383837b11b695ca7))
16
+ * **raf-effect-atom:** pass the atom to a boundary's hook in the README and JSDoc ([772d349](https://github.com/KATT/raf-effect-atom/commit/772d3494954f64a5c0fc92ebfd146b5f14e16e51))
17
+
18
+ ### Tests
19
+
20
+ * **raf-effect-atom:** pass a read's result to a boundary's hook ([df11247](https://github.com/KATT/raf-effect-atom/commit/df11247edab69b060c0677059ae24a4d8835c28e))
21
+ * **raf-effect-atom:** read through a boundary's hook with the atom ([de19b85](https://github.com/KATT/raf-effect-atom/commit/de19b8502ef41a8efbd9b50da6e00843cb38f2fd))
22
+ * **raf-effect-atom:** start an Atom.swr over a keyed readable that picks a seed or a live query ([697e785](https://github.com/KATT/raf-effect-atom/commit/697e78550bff0dce02903b963677828786770338))
23
+
24
+ ## [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)
25
+
26
+ ### ⚠ BREAKING CHANGES
27
+
28
+ - **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`.
29
+
30
+ ### Features
31
+
32
+ - **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))
33
+
3
34
  ## [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)
4
35
 
5
36
  ### Features
6
37
 
7
- * **raf-effect-atom:** start browser-only reads with prefetchInBrowser ([65f783a](https://github.com/KATT/raf-effect-atom/commit/65f783a0014e44a8aa11ce5e8fa34ab863063403))
38
+ - **raf-effect-atom:** start browser-only reads with prefetchInBrowser ([65f783a](https://github.com/KATT/raf-effect-atom/commit/65f783a0014e44a8aa11ce5e8fa34ab863063403))
8
39
 
9
40
  ### Documentation
10
41
 
11
- * **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))
42
+ - **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))
12
43
 
13
44
  ### Tests
14
45
 
15
- * **raf-effect-atom:** start a browser-only read from a client-only TanStack route ([69a8bd8](https://github.com/KATT/raf-effect-atom/commit/69a8bd887c34a7aeef51fdd20057ac30aa9e1fbc))
16
- * **raf-effect-atom:** type a derived atom's handle through it and its wrappers ([a16b587](https://github.com/KATT/raf-effect-atom/commit/a16b587a59dc4827dd18ecb3a3d5bb8f6fdedf0b))
46
+ - **raf-effect-atom:** start a browser-only read from a client-only TanStack route ([69a8bd8](https://github.com/KATT/raf-effect-atom/commit/69a8bd887c34a7aeef51fdd20057ac30aa9e1fbc))
47
+ - **raf-effect-atom:** type a derived atom's handle through it and its wrappers ([a16b587](https://github.com/KATT/raf-effect-atom/commit/a16b587a59dc4827dd18ecb3a3d5bb8f6fdedf0b))
17
48
 
18
49
  ## [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)
19
50
 
package/README.md CHANGED
@@ -37,13 +37,13 @@ export async function loader(args: LoaderArgs) {
37
37
  }
38
38
  ```
39
39
 
40
- A component takes the handle as a prop and reads it with `useAtomResult(source, handle)`, which waits for a streamed result instead of starting the atom again, and returns typed failures. Given a handle instead of an id, it can't start a request of its own. The source can be a family derived from the loader's, such as the one an [`effect-atom-boundary`](https://github.com/KATT/raf-effect-atom/tree/main/packages/effect-atom-boundary) boundary's `wrap` returns, which throws the errors that boundary claims to it:
40
+ A component takes the handle as a prop and reads it with `useAtomResult(source, handle)`, which waits for a streamed result instead of starting the atom again, and returns typed failures. Given a handle instead of an id, it can't start a request of its own. An [`effect-atom-boundary`](https://github.com/KATT/raf-effect-atom/tree/main/packages/effect-atom-boundary) boundary's hook takes the result, throws the errors that boundary claims to it, and returns the rest:
41
41
 
42
42
  ```tsx
43
43
  import { type Handle, useAtomResult } from "raf-effect-atom";
44
44
 
45
45
  function IssueTitle(props: { readonly issue: Handle<typeof issue> }) {
46
- const result = useAtomResult(useIssueBoundary().wrap(issue), props.issue);
46
+ const result = useIssueBoundary(useAtomResult(issue, props.issue));
47
47
  // Success<Issue, never>: its boundaries claim every failure
48
48
  return <h1>{result.value.title}</h1>;
49
49
  }
@@ -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/Loader.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { ArgsOf, ErrorOf, Handle, LoadedHandle, Source, SuccessOf } from "./Handle.mjs";
1
+ import { ArgsOf, ErrorOf, Handle, LoadedHandle, Source, SuccessOf } from "./handle.mjs";
2
2
  import * as Context from "effect/Context";
3
3
  import * as Duration from "effect/Duration";
4
4
  import * as Effect from "effect/Effect";
@@ -44,11 +44,10 @@ export declare const LeaseTTL: Context.Reference<Duration.Duration>;
44
44
  * is. Takes a lease either way. The component that reads the handle
45
45
  * suspends until the result settles. On a request registry whose async
46
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
47
+ * - A derived atom that isn't serializable starts the first serializable
48
+ * atom its `initialValueTarget` chain points at, the one `useAtomResult`
49
+ * reads it through: the lease, the handle's key and what the server sends
50
+ * are that atom's. `ensure` and `fetch` wait for the derived atom's own
52
51
  * result.
53
52
  *
54
53
  * **Gotchas**
package/dist/Loader.mjs CHANGED
@@ -125,11 +125,10 @@ const rejectDefects = (result) => {
125
125
  * is. Takes a lease either way. The component that reads the handle
126
126
  * suspends until the result settles. On a request registry whose async
127
127
  * seed is still running, it waits for the seed first.
128
- * - A derived atom that isn't serializable, such as an error boundary's
129
- * `wrap(source)`, starts the first serializable atom its
130
- * `initialValueTarget` chain points at, the one `useAtomResult` reads it
131
- * through: the lease, the handle's key and what the server sends are
132
- * that atom's. `ensure` and `fetch` wait for the derived atom's own
128
+ * - A derived atom that isn't serializable starts the first serializable
129
+ * atom its `initialValueTarget` chain points at, the one `useAtomResult`
130
+ * reads it through: the lease, the handle's key and what the server sends
131
+ * are that atom's. `ensure` and `fetch` wait for the derived atom's own
133
132
  * result.
134
133
  *
135
134
  * **Gotchas**
@@ -0,0 +1,25 @@
1
+ import { HydrationBoundaryProps } from "./hydration-boundary.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 "./hydration-boundary.mjs";
4
+ import { jsx } from "react/jsx-runtime";
5
+ // src/hydration-boundary-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 };
@@ -0,0 +1,91 @@
1
+ import * as Hydration from "effect/reactivity/Hydration";
2
+ import * as AtomRegistry from "effect/reactivity/AtomRegistry";
3
+ import * as React from "react";
4
+ /**
5
+ * Props for `HydrationBoundary`: the request's `registry` in a server
6
+ * component, or the `state` it dehydrated anywhere else.
7
+ *
8
+ * @stability unstable
9
+ * @category models
10
+ * @since 0.1.0
11
+ */
12
+ export type HydrationBoundaryProps = {
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;
23
+ /** The state `Hydration.dehydrate` returned on the server. */
24
+ readonly state?: Iterable<Hydration.DehydratedAtom> | undefined;
25
+ };
26
+ /**
27
+ * Hydrates the state a server registry dehydrated, streamed results
28
+ * included.
29
+ *
30
+ * **When to use**
31
+ *
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.
36
+ *
37
+ * **Details**
38
+ *
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.
50
+ *
51
+ * During server rendering, when `state` is the array `dehydrate` returned,
52
+ * as React Router passes a loader's data to the server render, the children
53
+ * read the registry it came from instead of a copy. The loaders' atoms are
54
+ * there, so an `effect-atom-boundary` boundary sees their failures before
55
+ * its children read them and renders its handler on the server.
56
+ *
57
+ * **Gotchas**
58
+ *
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.
64
+ *
65
+ * **Example**
66
+ *
67
+ * ```tsx
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
+ * }
80
+ *
81
+ * // A React Router route component
82
+ * <HydrationBoundary state={loaderData.atoms}>
83
+ * <IssuePage issue={loaderData.issue} />
84
+ * </HydrationBoundary>
85
+ * ```
86
+ *
87
+ * @stability unstable
88
+ * @category components
89
+ * @since 0.1.0
90
+ */
91
+ export declare const HydrationBoundary: (props: HydrationBoundaryProps) => React.ReactNode;
@@ -5,7 +5,7 @@ import { isServer, requireRequestRegistry } from "./internal/registry.mjs";
5
5
  import { HydrationBoundary, RegistryContext } from "@effect/atom-react";
6
6
  import * as React from "react";
7
7
  import { jsx } from "react/jsx-runtime";
8
- // src/ReactHydration.tsx
8
+ // src/hydration-boundary.tsx
9
9
  /**
10
10
  * Applies a server registry's dehydrated state to the nearest
11
11
  * `RegistryContext`, while rendering.
@@ -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,11 @@
1
+ import { ArgsOf, ErrorOf, Handle, HandleKey, LoadedHandle, Source, SuccessOf } from "./handle.mjs";
2
+ import { Hydration_d_exports } from "./Hydration.mjs";
3
+ import { Loader_d_exports } from "./Loader.mjs";
4
+ import { RequestRegistry_d_exports } from "./RequestRegistry.mjs";
5
+ import { useAtomResult, useHandleValue } from "./use-atom-result.mjs";
6
+ import { HydrationBoundaryProps } from "./hydration-boundary.mjs";
7
+ import { RegistryProvider } from "./registry-provider.mjs";
8
+ import { WaterfallDetection, WaterfallMode, useWaterfall } from "./waterfalls.mjs";
9
+ import "./index-shared.mjs";
10
+ import { HydrationBoundary } from "./hydration-boundary-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, useHandleValue, 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, useHandleValue } from "./use-atom-result.mjs";
5
+ import { RegistryProvider } from "./registry-provider.mjs";
6
+ import { WaterfallDetection, useWaterfall } from "./waterfalls.mjs";
7
+ import { HydrationBoundary } from "./hydration-boundary-react-server.mjs";
8
+ export { Hydration_exports as Hydration, HydrationBoundary, Loader_exports as Loader, RegistryProvider, RequestRegistry_exports as RequestRegistry, WaterfallDetection, useAtomResult, useHandleValue, useWaterfall };
@@ -0,0 +1,9 @@
1
+ import { ArgsOf, ErrorOf, Handle, HandleKey, LoadedHandle, Source, SuccessOf } from "./handle.mjs";
2
+ import { Hydration_d_exports } from "./Hydration.mjs";
3
+ import { Loader_d_exports } from "./Loader.mjs";
4
+ import { RequestRegistry_d_exports } from "./RequestRegistry.mjs";
5
+ import { useAtomResult, useHandleValue } from "./use-atom-result.mjs";
6
+ import { HydrationBoundaryProps } from "./hydration-boundary.mjs";
7
+ import { RegistryProvider } from "./registry-provider.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, useHandleValue, useWaterfall };
package/dist/index.d.mts CHANGED
@@ -1,8 +1,10 @@
1
- import { ArgsOf, ErrorOf, Handle, HandleKey, LoadedHandle, Source, SuccessOf } from "./Handle.mjs";
1
+ import { ArgsOf, ErrorOf, Handle, HandleKey, LoadedHandle, Source, SuccessOf } from "./handle.mjs";
2
+ import { Hydration_d_exports } from "./Hydration.mjs";
2
3
  import { Loader_d_exports } from "./Loader.mjs";
3
4
  import { RequestRegistry_d_exports } from "./RequestRegistry.mjs";
4
- import { HydrationBoundary, HydrationBoundaryProps } from "./ReactHydration.mjs";
5
- import { Hydration_d_exports } from "./Hydration.mjs";
6
- import { useAtomResult } from "./Hooks.mjs";
7
- 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 };
5
+ import { useAtomResult, useHandleValue } from "./use-atom-result.mjs";
6
+ import { HydrationBoundary, HydrationBoundaryProps } from "./hydration-boundary.mjs";
7
+ import { RegistryProvider } from "./registry-provider.mjs";
8
+ import { WaterfallDetection, WaterfallMode, useWaterfall } from "./waterfalls.mjs";
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, useHandleValue, useWaterfall };
package/dist/index.mjs CHANGED
@@ -1,7 +1,8 @@
1
1
  import { Hydration_exports } from "./Hydration.mjs";
2
2
  import { RequestRegistry_exports } from "./RequestRegistry.mjs";
3
3
  import { Loader_exports } from "./Loader.mjs";
4
- import { useAtomResult } from "./Hooks.mjs";
5
- import { HydrationBoundary } from "./ReactHydration.mjs";
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 };
4
+ import { useAtomResult, useHandleValue } from "./use-atom-result.mjs";
5
+ import { RegistryProvider } from "./registry-provider.mjs";
6
+ import { WaterfallDetection, useWaterfall } from "./waterfalls.mjs";
7
+ import { HydrationBoundary } from "./hydration-boundary.mjs";
8
+ export { Hydration_exports as Hydration, HydrationBoundary, Loader_exports as Loader, RegistryProvider, RequestRegistry_exports as RequestRegistry, WaterfallDetection, useAtomResult, useHandleValue, useWaterfall };
@@ -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,11 +13,6 @@ 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,
@@ -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,20 +2,20 @@ 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
- // src/ReactRouter.ts
6
+ // src/react-router.ts
8
7
  /**
9
8
  * Connects the loader verbs to React Router's Framework Mode.
10
9
  *
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 (
@@ -3,9 +3,10 @@ import { make, mount } from "./internal/registry.mjs";
3
3
  import { RegistryContext } from "@effect/atom-react";
4
4
  import * as React from "react";
5
5
  import { jsx } from "react/jsx-runtime";
6
- // src/RegistryProvider.tsx
6
+ // src/registry-provider.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
- import { ArgsOf, Handle, LoadedHandle, Source } from "./Handle.mjs";
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) => {
@@ -136,26 +131,4 @@ export declare const fetch: <S extends Source>(source: S, ...args: ArgsOf<S>) =>
136
131
  * @category verbs
137
132
  * @since 0.2.3
138
133
  */
139
- export declare const run: Loader["run"];
140
- /**
141
- * `Hydration.dehydrate` on the request's registry, for `HydrateAtoms`.
142
- *
143
- * **Gotchas**
144
- *
145
- * Includes the atoms started so far, so call it after the server component's
146
- * verbs. Each `HydrateAtoms` hydrates what its own `dehydrate()` saw.
147
- *
148
- * **Example**
149
- *
150
- * ```tsx
151
- * <HydrateAtoms state={await dehydrate()}>
152
- * <Issue issue={handle} />
153
- * </HydrateAtoms>
154
- * ```
155
- *
156
- * @stability unstable
157
- * @category dehydration
158
- * @since 0.1.0
159
- */
160
- export declare const dehydrate: () => Promise<Array<EffectHydration.DehydratedAtom>>;
161
- 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) => {
@@ -118,25 +116,4 @@ const fetch = atoms.fetch;
118
116
  * @since 0.2.3
119
117
  */
120
118
  const run = atoms.run;
121
- /**
122
- * `Hydration.dehydrate` on the request's registry, for `HydrateAtoms`.
123
- *
124
- * **Gotchas**
125
- *
126
- * Includes the atoms started so far, so call it after the server component's
127
- * verbs. Each `HydrateAtoms` hydrates what its own `dehydrate()` saw.
128
- *
129
- * **Example**
130
- *
131
- * ```tsx
132
- * <HydrateAtoms state={await dehydrate()}>
133
- * <Issue issue={handle} />
134
- * </HydrateAtoms>
135
- * ```
136
- *
137
- * @stability unstable
138
- * @category dehydration
139
- * @since 0.1.0
140
- */
141
- const dehydrate = atoms.dehydrate;
142
- export { HydrateAtoms, RegistryProvider, dehydrate, ensure, fetch, getRegistry, makeRequestAtoms, prefetch, prefetchInBrowser, run };
119
+ export { ensure, fetch, getRegistry, makeRequestAtoms, prefetch, prefetchInBrowser, run };
@@ -5,7 +5,7 @@ import * as Predicate from "effect/Predicate";
5
5
  import { RegistryContext } from "@effect/atom-react";
6
6
  import * as React from "react";
7
7
  import { jsx } from "react/jsx-runtime";
8
- // src/TanStackRouter.tsx
8
+ // src/tanstack-router.tsx
9
9
  /**
10
10
  * Connects the loader verbs to TanStack Router and TanStack Start.
11
11
  *
@@ -1,11 +1,11 @@
1
- import { ErrorOf, Handle, Source, SuccessOf } from "./Handle.mjs";
1
+ import { ErrorOf, Handle, Source, SuccessOf } from "./handle.mjs";
2
2
  import { SettledResult } from "./Loader.mjs";
3
3
  import * as Atom from "effect/reactivity/Atom";
4
4
  import * as AsyncResult from "effect/reactivity/AsyncResult";
5
5
  /**
6
6
  * A source with the same arguments and success type as `S`, and any error:
7
- * the sources whose handles `S` reads, such as the source an error
8
- * boundary's `wrap(S)` wraps.
7
+ * the sources whose handles `S` reads, such as the source a derived `S`
8
+ * points at.
9
9
  */
10
10
  type Readable<S> = S extends ((...args: infer Args) => Atom.Atom<AsyncResult.AsyncResult<infer A, infer _E>>) ? (...args: Args) => Atom.Atom<AsyncResult.AsyncResult<A, unknown>> : S extends Atom.Atom<AsyncResult.AsyncResult<infer A, infer _E>> ? Atom.Atom<AsyncResult.AsyncResult<A, unknown>> : never;
11
11
  /**
@@ -16,8 +16,9 @@ type Readable<S> = S extends ((...args: infer Args) => Atom.Atom<AsyncResult.Asy
16
16
  *
17
17
  * Use in the component a loader, a server component or `useWaterfall` gave
18
18
  * the handle to. The source can be the one the verb started, or one derived
19
- * from it whose atoms point at its atoms with `initialValueTarget`, such as
20
- * an error boundary's `wrap(source)`.
19
+ * from it whose atoms point at its atoms with `initialValueTarget`. To send
20
+ * some failures to an error boundary, pass the result to the boundary's hook,
21
+ * such as `useIssueBoundary(useAtomResult(issue, handle))`.
21
22
  *
22
23
  * **Details**
23
24
  *
@@ -38,8 +39,8 @@ type Readable<S> = S extends ((...args: infer Args) => Atom.Atom<AsyncResult.Asy
38
39
  * `initialValueTarget` chain points at: that one is awaited, hydrated and
39
40
  * released, so the derived atom doesn't request it again.
40
41
  * - The settled result is returned, typed failures included. A defect is
41
- * thrown to the error boundaries above. With no typed failures left, such
42
- * as an atom whose boundaries claim all of them, it's typed as a success.
42
+ * thrown to the error boundaries above. With no typed failures, it's typed
43
+ * as a success.
43
44
  * - While a failed atom refreshes, such as after a boundary's `reset`, the
44
45
  * read suspends instead of returning the old failure again.
45
46
  *
@@ -64,8 +65,8 @@ type Readable<S> = S extends ((...args: infer Args) => Atom.Atom<AsyncResult.Asy
64
65
  * `Atom.transform`'s `initialValueTarget` option points at the end of the
65
66
  * target's own chain, which for an AtomRpc query with `reactivityKeys` is an
66
67
  * inner atom with no serialization key. A derived atom read here sets
67
- * `initialValueTarget` to its source itself, as an error boundary's
68
- * wrapper does, or it requests the source again after hydration.
68
+ * `initialValueTarget` to its source itself, or it requests the source again
69
+ * after hydration.
69
70
  *
70
71
  * **Example**
71
72
  *
@@ -86,4 +87,59 @@ type Readable<S> = S extends ((...args: infer Args) => Atom.Atom<AsyncResult.Asy
86
87
  * @since 0.1.0
87
88
  */
88
89
  export declare const useAtomResult: <S extends Source>(source: S, handle: NoInfer<Handle<Readable<S>>>) => [ErrorOf<S>] extends [never] ? AsyncResult.Success<SuccessOf<S>, never> : SettledResult<SuccessOf<S>, ErrorOf<S>>;
90
+ /**
91
+ * Reads a handle without React Suspense, through the source that made it,
92
+ * and returns the atom's current result as it is.
93
+ *
94
+ * **When to use**
95
+ *
96
+ * Use where a component renders every state of the read itself: its loading
97
+ * state, a failure that stays on the page while the atom refreshes, or a
98
+ * defect. Otherwise prefer `useAtomResult`, which suspends until the result
99
+ * settles and sends defects to the error boundaries above.
100
+ *
101
+ * **Details**
102
+ *
103
+ * - It reads the atom as `useAtomResult` does: through `hydrated`, through
104
+ * the serializable atom a derived source points at, and with the same
105
+ * development checks on the handle's key and on request waterfalls.
106
+ * - It returns `Initial` while the atom loads, and while a result the server
107
+ * was still loading streams in. It doesn't read the atom until the stream
108
+ * settles, so the browser doesn't request it a second time.
109
+ * - A failure is returned as it is, `waiting` while the atom refreshes, and
110
+ * so is a defect.
111
+ * - Once the component has subscribed to the atom, the loader's lease ends
112
+ * and the component keeps the atom mounted.
113
+ * - A handle `Loader.prefetchInBrowser` returned on the server reads as
114
+ * `Initial` there, without starting the atom; the browser's read starts
115
+ * it.
116
+ *
117
+ * **Gotchas**
118
+ *
119
+ * The server renders what the read returns, `Initial` for a result still
120
+ * loading, and the browser's first render has to match it: render the same
121
+ * loading state for every `Initial`.
122
+ *
123
+ * During server rendering, the registry must be a request registry (see
124
+ * `RequestRegistry.make`): in development, the hook throws without one.
125
+ *
126
+ * **Example**
127
+ *
128
+ * ```tsx
129
+ * import { AsyncResult } from "effect/reactivity"
130
+ * import { type Handle, useHandleValue } from "raf-effect-atom"
131
+ *
132
+ * function IssueTitle(props: { readonly issue: Handle<typeof issue> }) {
133
+ * const result = useHandleValue(issue, props.issue)
134
+ * return AsyncResult.isSuccess(result) ? <h1>{result.value.title}</h1> : <p>Loading…</p>
135
+ * }
136
+ * ```
137
+ *
138
+ * @see {@link useAtomResult} for a read that suspends until the result settles
139
+ *
140
+ * @stability unstable
141
+ * @category hooks
142
+ * @since 0.3.1
143
+ */
144
+ export declare const useHandleValue: <S extends Source>(source: S, handle: NoInfer<Handle<Readable<S>>>) => AsyncResult.AsyncResult<SuccessOf<S>, ErrorOf<S>>;
89
145
  export {};
@@ -1,24 +1,37 @@
1
1
  "use client";
2
- import { stream } from "./internal/pending.mjs";
2
+ import { get, 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
6
  import { isRequestRegistry } from "./RequestRegistry.mjs";
7
7
  import { requireRequestRegistry } from "./internal/registry.mjs";
8
8
  import { ModeContext, check } from "./internal/waterfalls.mjs";
9
+ import * as Atom from "effect/reactivity/Atom";
9
10
  import * as Predicate from "effect/Predicate";
10
11
  import * as Cause from "effect/Cause";
11
12
  import * as Option from "effect/Option";
12
13
  import * as AsyncResult from "effect/reactivity/AsyncResult";
13
- import { RegistryContext, useAtomSuspense } from "@effect/atom-react";
14
+ import { RegistryContext, useAtomSuspense, useAtomValue } from "@effect/atom-react";
14
15
  import * as React from "react";
15
- // src/Hooks.ts
16
+ // src/use-atom-result.ts
16
17
  /**
17
- * The read hook for atoms started by loaders and streamed from the server.
18
+ * The read hooks for atoms started by loaders and streamed from the server.
18
19
  *
19
20
  * @stability unstable
20
21
  * @since 0.1.0
21
22
  */
23
+ const targetsOf = (atom, handle, hook) => {
24
+ const target = sourceOf(atom);
25
+ const started = hydrated(target);
26
+ const read = target === atom ? hydrated(atom) : atom;
27
+ const key = keyOf(started);
28
+ if (process.env.NODE_ENV !== "production" && handle.key !== void 0 && Predicate.isString(key) && handle.key !== key) throw new Error(`raf-effect-atom: ${hook} read the handle of ${handle.key} through a source that makes ${key}. Read a handle through the source that made it.`);
29
+ return {
30
+ key,
31
+ read,
32
+ started
33
+ };
34
+ };
22
35
  /**
23
36
  * Reads a handle through React Suspense, through the source that made it: a
24
37
  * result streamed from the server, a hydrated one, or one a loader started.
@@ -27,8 +40,9 @@ import * as React from "react";
27
40
  *
28
41
  * Use in the component a loader, a server component or `useWaterfall` gave
29
42
  * the handle to. The source can be the one the verb started, or one derived
30
- * from it whose atoms point at its atoms with `initialValueTarget`, such as
31
- * an error boundary's `wrap(source)`.
43
+ * from it whose atoms point at its atoms with `initialValueTarget`. To send
44
+ * some failures to an error boundary, pass the result to the boundary's hook,
45
+ * such as `useIssueBoundary(useAtomResult(issue, handle))`.
32
46
  *
33
47
  * **Details**
34
48
  *
@@ -49,8 +63,8 @@ import * as React from "react";
49
63
  * `initialValueTarget` chain points at: that one is awaited, hydrated and
50
64
  * released, so the derived atom doesn't request it again.
51
65
  * - The settled result is returned, typed failures included. A defect is
52
- * thrown to the error boundaries above. With no typed failures left, such
53
- * as an atom whose boundaries claim all of them, it's typed as a success.
66
+ * thrown to the error boundaries above. With no typed failures, it's typed
67
+ * as a success.
54
68
  * - While a failed atom refreshes, such as after a boundary's `reset`, the
55
69
  * read suspends instead of returning the old failure again.
56
70
  *
@@ -75,8 +89,8 @@ import * as React from "react";
75
89
  * `Atom.transform`'s `initialValueTarget` option points at the end of the
76
90
  * target's own chain, which for an AtomRpc query with `reactivityKeys` is an
77
91
  * inner atom with no serialization key. A derived atom read here sets
78
- * `initialValueTarget` to its source itself, as an error boundary's
79
- * wrapper does, or it requests the source again after hydration.
92
+ * `initialValueTarget` to its source itself, or it requests the source again
93
+ * after hydration.
80
94
  *
81
95
  * **Example**
82
96
  *
@@ -101,12 +115,9 @@ const useAtomResult = (source, handle) => {
101
115
  const atom = atomOf(source, handle.args);
102
116
  requireRequestRegistry(registry, "useAtomResult");
103
117
  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.`);
104
- const target = sourceOf(atom);
105
- const started = hydrated(target);
106
- const read = target === atom ? hydrated(atom) : atom;
107
- const key = keyOf(started);
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.`);
109
- const streamed = Predicate.isString(key) ? stream(registry, key) : void 0;
118
+ const targets = targetsOf(atom, handle, "useAtomResult");
119
+ const { read, started } = targets;
120
+ const streamed = Predicate.isString(targets.key) ? stream(registry, targets.key) : void 0;
110
121
  if (streamed !== void 0) React.use(streamed);
111
122
  if (process.env.NODE_ENV !== "production" && handle.inBrowser !== true) check(registry, started, React.use(ModeContext));
112
123
  if (read !== started) registry.get(started);
@@ -121,4 +132,94 @@ const useAtomResult = (source, handle) => {
121
132
  if (AsyncResult.isFailure(result) && Option.isNone(Cause.findErrorOption(result.cause))) throw Cause.squash(result.cause);
122
133
  return result;
123
134
  };
124
- export { useAtomResult };
135
+ /** What a read shows while it can't read the atom yet. */
136
+ const loading = Atom.make(AsyncResult.initial(true));
137
+ /**
138
+ * Reads a handle without React Suspense, through the source that made it,
139
+ * and returns the atom's current result as it is.
140
+ *
141
+ * **When to use**
142
+ *
143
+ * Use where a component renders every state of the read itself: its loading
144
+ * state, a failure that stays on the page while the atom refreshes, or a
145
+ * defect. Otherwise prefer `useAtomResult`, which suspends until the result
146
+ * settles and sends defects to the error boundaries above.
147
+ *
148
+ * **Details**
149
+ *
150
+ * - It reads the atom as `useAtomResult` does: through `hydrated`, through
151
+ * the serializable atom a derived source points at, and with the same
152
+ * development checks on the handle's key and on request waterfalls.
153
+ * - It returns `Initial` while the atom loads, and while a result the server
154
+ * was still loading streams in. It doesn't read the atom until the stream
155
+ * settles, so the browser doesn't request it a second time.
156
+ * - A failure is returned as it is, `waiting` while the atom refreshes, and
157
+ * so is a defect.
158
+ * - Once the component has subscribed to the atom, the loader's lease ends
159
+ * and the component keeps the atom mounted.
160
+ * - A handle `Loader.prefetchInBrowser` returned on the server reads as
161
+ * `Initial` there, without starting the atom; the browser's read starts
162
+ * it.
163
+ *
164
+ * **Gotchas**
165
+ *
166
+ * The server renders what the read returns, `Initial` for a result still
167
+ * loading, and the browser's first render has to match it: render the same
168
+ * loading state for every `Initial`.
169
+ *
170
+ * During server rendering, the registry must be a request registry (see
171
+ * `RequestRegistry.make`): in development, the hook throws without one.
172
+ *
173
+ * **Example**
174
+ *
175
+ * ```tsx
176
+ * import { AsyncResult } from "effect/reactivity"
177
+ * import { type Handle, useHandleValue } from "raf-effect-atom"
178
+ *
179
+ * function IssueTitle(props: { readonly issue: Handle<typeof issue> }) {
180
+ * const result = useHandleValue(issue, props.issue)
181
+ * return AsyncResult.isSuccess(result) ? <h1>{result.value.title}</h1> : <p>Loading…</p>
182
+ * }
183
+ * ```
184
+ *
185
+ * @see {@link useAtomResult} for a read that suspends until the result settles
186
+ *
187
+ * @stability unstable
188
+ * @category hooks
189
+ * @since 0.3.1
190
+ */
191
+ const useHandleValue = (source, handle) => {
192
+ const registry = React.useContext(RegistryContext);
193
+ const atom = atomOf(source, handle.args);
194
+ requireRequestRegistry(registry, "useHandleValue");
195
+ const targets = targetsOf(atom, handle, "useHandleValue");
196
+ const { started } = targets;
197
+ const streamed = Predicate.isString(targets.key) ? get(registry, targets.key) : void 0;
198
+ const [, settled] = React.useReducer((count) => count + 1, 0);
199
+ React.useEffect(() => {
200
+ if (streamed === void 0) return;
201
+ let active = true;
202
+ const rerender = () => {
203
+ if (active) settled();
204
+ };
205
+ streamed.then(rerender, rerender);
206
+ return () => {
207
+ active = false;
208
+ };
209
+ }, [streamed]);
210
+ const waiting = streamed !== void 0 || handle.inBrowser === true && isRequestRegistry(registry);
211
+ if (!waiting) {
212
+ if (process.env.NODE_ENV !== "production" && handle.inBrowser !== true) check(registry, started, React.use(ModeContext));
213
+ if (targets.read !== started) registry.get(started);
214
+ }
215
+ const result = useAtomValue(waiting ? loading : targets.read);
216
+ React.useEffect(() => {
217
+ if (!waiting) release(registry, started);
218
+ }, [
219
+ registry,
220
+ started,
221
+ waiting
222
+ ]);
223
+ return result;
224
+ };
225
+ export { useAtomResult, useHandleValue };
@@ -1,4 +1,4 @@
1
- import { ArgsOf, Handle, Source } from "./Handle.mjs";
1
+ import { ArgsOf, Handle, Source } from "./handle.mjs";
2
2
  import { Mode } from "./internal/waterfalls.mjs";
3
3
  import * as React from "react";
4
4
  /**
@@ -72,9 +72,8 @@ 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
- * - 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.
75
+ * - A derived source starts the serializable atom its `initialValueTarget`
76
+ * chain points at, as `Loader.prefetch` does.
78
77
  *
79
78
  * **Example**
80
79
  *
@@ -11,7 +11,7 @@ import * as AtomRegistry from "effect/reactivity/AtomRegistry";
11
11
  import { RegistryContext } from "@effect/atom-react";
12
12
  import * as React from "react";
13
13
  import { jsx } from "react/jsx-runtime";
14
- // src/Waterfalls.tsx
14
+ // src/waterfalls.tsx
15
15
  /**
16
16
  * Request waterfalls: the escape hatch for atoms only a render can start,
17
17
  * and the development check that finds the others.
@@ -76,9 +76,8 @@ 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
- * - 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.
79
+ * - A derived source starts the serializable atom its `initialValueTarget`
80
+ * chain points at, as `Loader.prefetch` does.
82
81
  *
83
82
  * **Example**
84
83
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "raf-effect-atom",
3
- "version": "0.2.6",
3
+ "version": "0.3.1",
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,10 +33,13 @@
33
33
  "type": "module",
34
34
  "sideEffects": false,
35
35
  "exports": {
36
- ".": "./dist/index.mjs",
37
- "./next": "./dist/Next.mjs",
38
- "./react-router": "./dist/ReactRouter.mjs",
39
- "./tanstack-router": "./dist/TanStackRouter.mjs"
36
+ ".": {
37
+ "react-server": "./dist/index-react-server.mjs",
38
+ "default": "./dist/index.mjs"
39
+ },
40
+ "./react-router": "./dist/react-router.mjs",
41
+ "./rsc": "./dist/rsc.mjs",
42
+ "./tanstack-router": "./dist/tanstack-router.mjs"
40
43
  },
41
44
  "peerDependencies": {
42
45
  "@effect/atom-react": "^4.0.6",
@@ -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.2",
64
+ "effect-atom-boundary": "0.3.0",
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 };
@@ -1,58 +0,0 @@
1
- import * as Hydration from "effect/reactivity/Hydration";
2
- import * as React from "react";
3
- /**
4
- * Props for `HydrationBoundary`.
5
- *
6
- * @stability unstable
7
- * @category models
8
- * @since 0.1.0
9
- */
10
- export interface HydrationBoundaryProps {
11
- readonly children?: React.ReactNode | undefined;
12
- /** The state `Hydration.dehydrate` returned on the server. */
13
- readonly state?: Iterable<Hydration.DehydratedAtom> | undefined;
14
- }
15
- /**
16
- * Hydrates the state a server registry dehydrated, streamed results
17
- * included.
18
- *
19
- * **When to use**
20
- *
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.
24
- *
25
- * **Details**
26
- *
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.
31
- *
32
- * During server rendering, when `state` is the array `dehydrate` returned,
33
- * as React Router passes a loader's data to the server render, the children
34
- * read the registry it came from instead of a copy. The loaders' atoms are
35
- * there, so an `effect-atom-boundary` boundary sees their failures before
36
- * its children read them and renders its handler on the server.
37
- *
38
- * **Gotchas**
39
- *
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.
43
- *
44
- * **Example**
45
- *
46
- * ```tsx
47
- * import { HydrationBoundary } from "raf-effect-atom"
48
- *
49
- * <HydrationBoundary state={loaderData.atoms}>
50
- * <IssuePage issue={loaderData.issue} />
51
- * </HydrationBoundary>
52
- * ```
53
- *
54
- * @stability unstable
55
- * @category components
56
- * @since 0.1.0
57
- */
58
- export declare const HydrationBoundary: (props: HydrationBoundaryProps) => React.ReactNode;
File without changes