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.
- package/CHANGELOG.md +35 -4
- package/README.md +4 -4
- package/dist/Loader.d.mts +5 -6
- package/dist/Loader.mjs +4 -5
- package/dist/hydration-boundary-react-server.d.mts +25 -0
- package/dist/hydration-boundary-react-server.mjs +41 -0
- package/dist/hydration-boundary.d.mts +91 -0
- package/dist/{ReactHydration.mjs → hydration-boundary.mjs} +34 -11
- package/dist/index-react-server.d.mts +11 -0
- package/dist/index-react-server.mjs +8 -0
- package/dist/index-shared.d.mts +9 -0
- package/dist/index.d.mts +8 -6
- package/dist/index.mjs +5 -4
- package/dist/internal/{next.mjs → rsc.mjs} +2 -8
- package/dist/{ReactRouter.d.mts → react-router.d.mts} +1 -3
- package/dist/{ReactRouter.mjs → react-router.mjs} +9 -9
- package/dist/{RegistryProvider.d.mts → registry-provider.d.mts} +1 -1
- package/dist/{RegistryProvider.mjs → registry-provider.mjs} +4 -3
- package/dist/{Next.d.mts → rsc.d.mts} +10 -37
- package/dist/{Next.mjs → rsc.mjs} +8 -31
- package/dist/{TanStackRouter.mjs → tanstack-router.mjs} +1 -1
- package/dist/{Hooks.d.mts → use-atom-result.d.mts} +65 -9
- package/dist/{Hooks.mjs → use-atom-result.mjs} +118 -17
- package/dist/{Waterfalls.d.mts → waterfalls.d.mts} +3 -4
- package/dist/{Waterfalls.mjs → waterfalls.mjs} +3 -4
- package/package.json +9 -6
- package/dist/NextClient.d.mts +0 -32
- package/dist/NextClient.mjs +0 -44
- package/dist/ReactHydration.d.mts +0 -58
- /package/dist/{Handle.d.mts → handle.d.mts} +0 -0
- /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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
16
|
-
|
|
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.
|
|
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 =
|
|
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/
|
|
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
|
|
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 "./
|
|
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
|
|
48
|
-
* `
|
|
49
|
-
*
|
|
50
|
-
*
|
|
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
|
|
129
|
-
* `
|
|
130
|
-
*
|
|
131
|
-
*
|
|
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/
|
|
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
|
|
23
|
-
*
|
|
24
|
-
* that hydrates before rendering calls
|
|
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
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
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
|
-
*
|
|
42
|
-
* a
|
|
43
|
-
*
|
|
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 "./
|
|
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 {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { WaterfallDetection, WaterfallMode, useWaterfall } from "./
|
|
8
|
-
|
|
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 "./
|
|
5
|
-
import {
|
|
6
|
-
import { WaterfallDetection, useWaterfall } from "./
|
|
7
|
-
|
|
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 {
|
|
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/
|
|
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/
|
|
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
|
|
15
|
-
* `clientMiddleware` puts the browser's registry
|
|
16
|
-
* one function can be both the route's `loader`
|
|
17
|
-
* page, loaders run in the browser,
|
|
18
|
-
* already holds. Nothing is imported
|
|
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
|
|
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 {
|
|
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
|
|
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/
|
|
6
|
+
// src/registry-provider.tsx
|
|
7
7
|
/**
|
|
8
|
-
* The registry provider
|
|
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
|
|
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 "./
|
|
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
|
|
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
|
-
*
|
|
19
|
-
*
|
|
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
|
|
27
|
-
*
|
|
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/
|
|
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 `
|
|
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/
|
|
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/
|
|
2
|
-
|
|
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
|
|
7
|
-
*
|
|
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/
|
|
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 `
|
|
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/
|
|
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/
|
|
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 "./
|
|
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
|
|
8
|
-
*
|
|
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
|
|
20
|
-
* an error boundary's
|
|
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
|
|
42
|
-
* as
|
|
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,
|
|
68
|
-
*
|
|
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/
|
|
16
|
+
// src/use-atom-result.ts
|
|
16
17
|
/**
|
|
17
|
-
* The read
|
|
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
|
|
31
|
-
* an error boundary's
|
|
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
|
|
53
|
-
* as
|
|
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,
|
|
79
|
-
*
|
|
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
|
|
105
|
-
const started =
|
|
106
|
-
const
|
|
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
|
-
|
|
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 "./
|
|
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
|
|
76
|
-
*
|
|
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/
|
|
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
|
|
80
|
-
*
|
|
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.
|
|
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
|
-
".":
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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.
|
|
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",
|
package/dist/NextClient.d.mts
DELETED
|
@@ -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;
|
package/dist/NextClient.mjs
DELETED
|
@@ -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
|
|
File without changes
|