raf-effect-atom 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +111 -0
- package/LICENSE +21 -0
- package/README.md +65 -2
- package/dist/Handle.d.mts +90 -0
- package/dist/Hooks.d.mts +89 -0
- package/dist/Hooks.mjs +135 -0
- package/dist/Hydration.d.mts +93 -0
- package/dist/Hydration.mjs +134 -0
- package/dist/Loader.d.mts +192 -0
- package/dist/Loader.mjs +286 -0
- package/dist/Next.d.mts +65 -0
- package/dist/Next.mjs +64 -0
- package/dist/NextClient.d.mts +32 -0
- package/dist/NextClient.mjs +44 -0
- package/dist/ReactHydration.d.mts +52 -0
- package/dist/ReactHydration.mjs +71 -0
- package/dist/ReactRouter.d.mts +63 -0
- package/dist/ReactRouter.mjs +116 -0
- package/dist/RegistryProvider.d.mts +40 -0
- package/dist/RegistryProvider.mjs +55 -0
- package/dist/RequestRegistry.d.mts +54 -0
- package/dist/RequestRegistry.mjs +76 -0
- package/dist/TanStackRouter.d.mts +84 -0
- package/dist/TanStackRouter.mjs +134 -0
- package/dist/Waterfalls.d.mts +99 -0
- package/dist/Waterfalls.mjs +111 -0
- package/dist/_virtual/_rolldown/runtime.mjs +12 -0
- package/dist/index.d.mts +8 -0
- package/dist/index.mjs +7 -0
- package/dist/internal/development.mjs +6 -0
- package/dist/internal/handles.mjs +22 -0
- package/dist/internal/leases.mjs +39 -0
- package/dist/internal/next.mjs +16 -0
- package/dist/internal/pending.mjs +75 -0
- package/dist/internal/registry.mjs +16 -0
- package/dist/internal/sources.mjs +14 -0
- package/dist/internal/waterfalls.d.mts +5 -0
- package/dist/internal/waterfalls.mjs +83 -0
- package/package.json +80 -3
package/dist/Next.mjs
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { dehydrate as dehydrate$1, ensure as ensure$1, fetch as fetch$1, getRegistry as getRegistry$1, prefetch as prefetch$1 } from "./internal/next.mjs";
|
|
2
|
+
import { HydrateAtoms } from "./NextClient.mjs";
|
|
3
|
+
import { RegistryProvider } from "./RegistryProvider.mjs";
|
|
4
|
+
// src/Next.ts
|
|
5
|
+
/**
|
|
6
|
+
* Returns the request registry of the server render in progress.
|
|
7
|
+
*
|
|
8
|
+
* **Details**
|
|
9
|
+
*
|
|
10
|
+
* Made once per request through React's `cache`, and disposed through
|
|
11
|
+
* `cacheSignal` when React finishes the render, after the last streamed
|
|
12
|
+
* result has been sent.
|
|
13
|
+
*
|
|
14
|
+
* **Gotchas**
|
|
15
|
+
*
|
|
16
|
+
* Only server components share it: outside a server component render,
|
|
17
|
+
* `cache` doesn't cache, so each call makes a new registry.
|
|
18
|
+
*
|
|
19
|
+
* @stability unstable
|
|
20
|
+
* @category registry
|
|
21
|
+
* @since 0.1.0
|
|
22
|
+
*/
|
|
23
|
+
const getRegistry = getRegistry$1;
|
|
24
|
+
/**
|
|
25
|
+
* `Loader.prefetch` on the request's registry: starts a source's atom, to
|
|
26
|
+
* stream it, and returns its handle.
|
|
27
|
+
*
|
|
28
|
+
* @stability unstable
|
|
29
|
+
* @category verbs
|
|
30
|
+
* @since 0.1.0
|
|
31
|
+
*/
|
|
32
|
+
const prefetch = prefetch$1;
|
|
33
|
+
/**
|
|
34
|
+
* `Loader.ensure` on the request's registry: starts a source's atom, waits
|
|
35
|
+
* for its result, and returns its handle.
|
|
36
|
+
*
|
|
37
|
+
* @stability unstable
|
|
38
|
+
* @category verbs
|
|
39
|
+
* @since 0.1.0
|
|
40
|
+
*/
|
|
41
|
+
const ensure = ensure$1;
|
|
42
|
+
/**
|
|
43
|
+
* `Loader.fetch` on the request's registry: starts or refreshes a source's
|
|
44
|
+
* atom, waits for the new result, and returns its handle.
|
|
45
|
+
*
|
|
46
|
+
* @stability unstable
|
|
47
|
+
* @category verbs
|
|
48
|
+
* @since 0.1.0
|
|
49
|
+
*/
|
|
50
|
+
const fetch = fetch$1;
|
|
51
|
+
/**
|
|
52
|
+
* `Hydration.dehydrate` on the request's registry, for `HydrateAtoms`.
|
|
53
|
+
*
|
|
54
|
+
* **Gotchas**
|
|
55
|
+
*
|
|
56
|
+
* Includes the atoms started so far, so call it after the server component's
|
|
57
|
+
* verbs. Each `HydrateAtoms` hydrates what its own `dehydrate()` saw.
|
|
58
|
+
*
|
|
59
|
+
* @stability unstable
|
|
60
|
+
* @category dehydration
|
|
61
|
+
* @since 0.1.0
|
|
62
|
+
*/
|
|
63
|
+
const dehydrate = dehydrate$1;
|
|
64
|
+
export { HydrateAtoms, RegistryProvider, dehydrate, ensure, fetch, getRegistry, prefetch };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { HydrationBoundaryProps } from "./ReactHydration.mjs";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
/**
|
|
4
|
+
* Hydrates the state a server component dehydrated with `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={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;
|
|
@@ -0,0 +1,44 @@
|
|
|
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 `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={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 };
|
|
@@ -0,0 +1,52 @@
|
|
|
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
|
+
* **Example**
|
|
39
|
+
*
|
|
40
|
+
* ```tsx
|
|
41
|
+
* import { HydrationBoundary } from "raf-effect-atom"
|
|
42
|
+
*
|
|
43
|
+
* <HydrationBoundary state={loaderData.atoms}>
|
|
44
|
+
* <IssuePage issue={loaderData.issue} />
|
|
45
|
+
* </HydrationBoundary>
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @stability unstable
|
|
49
|
+
* @category components
|
|
50
|
+
* @since 0.1.0
|
|
51
|
+
*/
|
|
52
|
+
export declare const HydrationBoundary: (props: HydrationBoundaryProps) => React.ReactNode;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { split } from "./internal/pending.mjs";
|
|
3
|
+
import { sourceOf } from "./internal/sources.mjs";
|
|
4
|
+
import { isServer } from "./internal/registry.mjs";
|
|
5
|
+
import { HydrationBoundary, RegistryContext } from "@effect/atom-react";
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
import { jsx } from "react/jsx-runtime";
|
|
8
|
+
// src/ReactHydration.tsx
|
|
9
|
+
/**
|
|
10
|
+
* Applies a server registry's dehydrated state to the nearest
|
|
11
|
+
* `RegistryContext`, while rendering.
|
|
12
|
+
*
|
|
13
|
+
* @stability unstable
|
|
14
|
+
* @since 0.1.0
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Hydrates the state a server registry dehydrated, streamed results
|
|
18
|
+
* included.
|
|
19
|
+
*
|
|
20
|
+
* **When to use**
|
|
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.
|
|
25
|
+
*
|
|
26
|
+
* **Details**
|
|
27
|
+
*
|
|
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.
|
|
32
|
+
*
|
|
33
|
+
* During server rendering, when `state` is the array `dehydrate` returned,
|
|
34
|
+
* as React Router passes a loader's data to the server render, the children
|
|
35
|
+
* read the registry it came from instead of a copy. The loaders' atoms are
|
|
36
|
+
* there, so an `effect-atom-boundary` boundary sees their failures before
|
|
37
|
+
* its children read them and renders its handler on the server.
|
|
38
|
+
*
|
|
39
|
+
* **Example**
|
|
40
|
+
*
|
|
41
|
+
* ```tsx
|
|
42
|
+
* import { HydrationBoundary } from "raf-effect-atom"
|
|
43
|
+
*
|
|
44
|
+
* <HydrationBoundary state={loaderData.atoms}>
|
|
45
|
+
* <IssuePage issue={loaderData.issue} />
|
|
46
|
+
* </HydrationBoundary>
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* @stability unstable
|
|
50
|
+
* @category components
|
|
51
|
+
* @since 0.1.0
|
|
52
|
+
*/
|
|
53
|
+
const HydrationBoundary$1 = (props) => {
|
|
54
|
+
const context = React.useContext(RegistryContext);
|
|
55
|
+
const state = props.state;
|
|
56
|
+
const source = isServer() ? sourceOf(state) : void 0;
|
|
57
|
+
const registry = source ?? context;
|
|
58
|
+
const settled = React.useMemo(() => state === void 0 || source !== void 0 ? void 0 : split(registry, state), [
|
|
59
|
+
registry,
|
|
60
|
+
source,
|
|
61
|
+
state
|
|
62
|
+
]);
|
|
63
|
+
return /* @__PURE__ */ jsx(RegistryContext.Provider, {
|
|
64
|
+
value: registry,
|
|
65
|
+
children: /* @__PURE__ */ jsx(HydrationBoundary, {
|
|
66
|
+
state: settled,
|
|
67
|
+
children: props.children
|
|
68
|
+
})
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
export { HydrationBoundary$1 as HydrationBoundary };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { RegistryProvider } from "./RegistryProvider.mjs";
|
|
2
|
+
import { Loader } from "./Loader.mjs";
|
|
3
|
+
import * as EffectHydration from "effect/reactivity/Hydration";
|
|
4
|
+
import * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
5
|
+
/**
|
|
6
|
+
* One request's registry, with the loader verbs over it.
|
|
7
|
+
*
|
|
8
|
+
* @stability unstable
|
|
9
|
+
* @category models
|
|
10
|
+
* @since 0.1.0
|
|
11
|
+
*/
|
|
12
|
+
export interface RequestAtoms extends Loader {
|
|
13
|
+
/** `Hydration.dehydrate` on the request's registry, for loader data. */
|
|
14
|
+
readonly dehydrate: () => Array<EffectHydration.DehydratedAtom>;
|
|
15
|
+
readonly registry: AtomRegistry.AtomRegistry;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The router context `atomsMiddleware` sets: what `react-router`'s
|
|
19
|
+
* `createContext<RequestAtoms>()` returns.
|
|
20
|
+
*
|
|
21
|
+
* @stability unstable
|
|
22
|
+
* @category models
|
|
23
|
+
* @since 0.1.0
|
|
24
|
+
*/
|
|
25
|
+
export interface AtomsContext {
|
|
26
|
+
readonly defaultValue?: RequestAtoms | undefined;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The parts of React Router's middleware arguments `atomsMiddleware` uses.
|
|
30
|
+
*
|
|
31
|
+
* @stability unstable
|
|
32
|
+
* @category models
|
|
33
|
+
* @since 0.1.0
|
|
34
|
+
*/
|
|
35
|
+
export interface MiddlewareArgs {
|
|
36
|
+
readonly context: {
|
|
37
|
+
readonly set: (context: AtomsContext, value: RequestAtoms) => void;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Creates a root middleware that gives each server request its own
|
|
42
|
+
* registry, under the given router context.
|
|
43
|
+
*
|
|
44
|
+
* **Details**
|
|
45
|
+
*
|
|
46
|
+
* The registry is disposed once the response's body has been sent, or the
|
|
47
|
+
* client has gone: loader data streams its pending promises after the
|
|
48
|
+
* middleware returns, so the atoms behind them must keep running until
|
|
49
|
+
* then. When `next` doesn't return a `Response`, as Data Mode's
|
|
50
|
+
* `query` can, the registry is left to be collected with the request.
|
|
51
|
+
*
|
|
52
|
+
* **Gotchas**
|
|
53
|
+
*
|
|
54
|
+
* It's server middleware: export it as the root route's `middleware`, not
|
|
55
|
+
* `clientMiddleware`. In the browser, the root's `RegistryProvider` holds
|
|
56
|
+
* the registry.
|
|
57
|
+
*
|
|
58
|
+
* @stability unstable
|
|
59
|
+
* @category integrations
|
|
60
|
+
* @since 0.1.0
|
|
61
|
+
*/
|
|
62
|
+
export declare const atomsMiddleware: (context: AtomsContext) => <Result>(args: MiddlewareArgs, next: () => Promise<Result>) => Promise<Result>;
|
|
63
|
+
export { RegistryProvider };
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { dehydrate } from "./Hydration.mjs";
|
|
2
|
+
import { make } from "./RequestRegistry.mjs";
|
|
3
|
+
import { make as make$1 } from "./Loader.mjs";
|
|
4
|
+
import { RegistryProvider } from "./RegistryProvider.mjs";
|
|
5
|
+
import * as Predicate from "effect/Predicate";
|
|
6
|
+
// src/ReactRouter.ts
|
|
7
|
+
/**
|
|
8
|
+
* Connects the loader verbs to React Router's Framework Mode.
|
|
9
|
+
*
|
|
10
|
+
* A root `middleware` gives each server request its own registry, under a
|
|
11
|
+
* router context the app creates. Loaders start atoms with its verbs and
|
|
12
|
+
* return its `dehydrate()` with their data; the route component hydrates it
|
|
13
|
+
* with `HydrationBoundary`, inside the root's `RegistryProvider`. Nothing is
|
|
14
|
+
* imported from `react-router`.
|
|
15
|
+
*
|
|
16
|
+
* **Example**
|
|
17
|
+
*
|
|
18
|
+
* ```tsx
|
|
19
|
+
* // app/atoms.ts
|
|
20
|
+
* import { createContext } from "react-router"
|
|
21
|
+
* import type { RequestAtoms } from "raf-effect-atom/react-router"
|
|
22
|
+
*
|
|
23
|
+
* export const atomsContext = createContext<RequestAtoms>()
|
|
24
|
+
*
|
|
25
|
+
* // app/root.tsx
|
|
26
|
+
* import { atomsMiddleware } from "raf-effect-atom/react-router"
|
|
27
|
+
*
|
|
28
|
+
* export const middleware = [atomsMiddleware(atomsContext)]
|
|
29
|
+
*
|
|
30
|
+
* // app/routes/issue.tsx
|
|
31
|
+
* export async function loader(args: Route.LoaderArgs) {
|
|
32
|
+
* const atoms = args.context.get(atomsContext)
|
|
33
|
+
* const handle = await atoms.ensure(issue, Number(args.params.id))
|
|
34
|
+
* return { atoms: atoms.dehydrate(), issue: handle }
|
|
35
|
+
* }
|
|
36
|
+
*
|
|
37
|
+
* export default function IssueRoute(props: Route.ComponentProps) {
|
|
38
|
+
* return (
|
|
39
|
+
* <HydrationBoundary state={props.loaderData.atoms}>
|
|
40
|
+
* <Issue issue={props.loaderData.issue} />
|
|
41
|
+
* </HydrationBoundary>
|
|
42
|
+
* )
|
|
43
|
+
* }
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* @stability unstable
|
|
47
|
+
* @since 0.1.0
|
|
48
|
+
*/
|
|
49
|
+
/**
|
|
50
|
+
* Creates a root middleware that gives each server request its own
|
|
51
|
+
* registry, under the given router context.
|
|
52
|
+
*
|
|
53
|
+
* **Details**
|
|
54
|
+
*
|
|
55
|
+
* The registry is disposed once the response's body has been sent, or the
|
|
56
|
+
* client has gone: loader data streams its pending promises after the
|
|
57
|
+
* middleware returns, so the atoms behind them must keep running until
|
|
58
|
+
* then. When `next` doesn't return a `Response`, as Data Mode's
|
|
59
|
+
* `query` can, the registry is left to be collected with the request.
|
|
60
|
+
*
|
|
61
|
+
* **Gotchas**
|
|
62
|
+
*
|
|
63
|
+
* It's server middleware: export it as the root route's `middleware`, not
|
|
64
|
+
* `clientMiddleware`. In the browser, the root's `RegistryProvider` holds
|
|
65
|
+
* the registry.
|
|
66
|
+
*
|
|
67
|
+
* @stability unstable
|
|
68
|
+
* @category integrations
|
|
69
|
+
* @since 0.1.0
|
|
70
|
+
*/
|
|
71
|
+
const atomsMiddleware = (context) => async (args, next) => {
|
|
72
|
+
const registry = make();
|
|
73
|
+
const loader = make$1(registry);
|
|
74
|
+
args.context.set(context, {
|
|
75
|
+
...loader,
|
|
76
|
+
dehydrate: () => dehydrate(registry),
|
|
77
|
+
registry
|
|
78
|
+
});
|
|
79
|
+
let result;
|
|
80
|
+
try {
|
|
81
|
+
result = await next();
|
|
82
|
+
} catch (error) {
|
|
83
|
+
registry.dispose();
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
if (!isResponse(result)) return result;
|
|
87
|
+
const body = result.body;
|
|
88
|
+
if (body === null) {
|
|
89
|
+
registry.dispose();
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
return new Response(disposeAfter(body, registry), result);
|
|
93
|
+
};
|
|
94
|
+
const isResponse = (value) => Predicate.hasProperty(value, "body") && Predicate.hasProperty(value, "headers") && Predicate.hasProperty(value, "status");
|
|
95
|
+
const disposeAfter = (body, registry) => {
|
|
96
|
+
const reader = body.getReader();
|
|
97
|
+
return new ReadableStream({
|
|
98
|
+
cancel: async (reason) => {
|
|
99
|
+
registry.dispose();
|
|
100
|
+
await reader.cancel(reason);
|
|
101
|
+
},
|
|
102
|
+
pull: async (controller) => {
|
|
103
|
+
try {
|
|
104
|
+
const chunk = await reader.read();
|
|
105
|
+
if (chunk.done) {
|
|
106
|
+
registry.dispose();
|
|
107
|
+
controller.close();
|
|
108
|
+
} else controller.enqueue(chunk.value);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
registry.dispose();
|
|
111
|
+
controller.error(error);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
export { RegistryProvider, atomsMiddleware };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Provides the app's registry: a request registry while the framework
|
|
4
|
+
* renders the app on the server, and one registry for the page's lifetime
|
|
5
|
+
* in the browser.
|
|
6
|
+
*
|
|
7
|
+
* **When to use**
|
|
8
|
+
*
|
|
9
|
+
* Put it in the root layout, around `children`, in place of
|
|
10
|
+
* `@effect/atom-react`'s `RegistryProvider`, which would share one registry
|
|
11
|
+
* between requests' server renders.
|
|
12
|
+
*
|
|
13
|
+
* **Details**
|
|
14
|
+
*
|
|
15
|
+
* `defaultIdleTTL` is how many milliseconds the browser's registry keeps an
|
|
16
|
+
* atom no component reads, 400 by default, as `@effect/atom-react`'s default
|
|
17
|
+
* registry does. An atom's own `Atom.setIdleTTL` or `Atom.keepAlive` wins.
|
|
18
|
+
* It's read once, when the registry is made. The server's request registry
|
|
19
|
+
* lives for one request, so it has none.
|
|
20
|
+
*
|
|
21
|
+
* **Example**
|
|
22
|
+
*
|
|
23
|
+
* ```tsx
|
|
24
|
+
* import { RegistryProvider } from "raf-effect-atom/next" // or "raf-effect-atom/react-router"
|
|
25
|
+
*
|
|
26
|
+
* export default function RootLayout(props: { children: React.ReactNode }) {
|
|
27
|
+
* return (
|
|
28
|
+
* <RegistryProvider defaultIdleTTL={60_000}>{props.children}</RegistryProvider>
|
|
29
|
+
* )
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @stability unstable
|
|
34
|
+
* @category components
|
|
35
|
+
* @since 0.1.0
|
|
36
|
+
*/
|
|
37
|
+
export declare const RegistryProvider: (props: {
|
|
38
|
+
readonly children?: React.ReactNode | undefined;
|
|
39
|
+
readonly defaultIdleTTL?: number | undefined;
|
|
40
|
+
}) => React.ReactNode;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { make } from "./internal/registry.mjs";
|
|
3
|
+
import { RegistryContext } from "@effect/atom-react";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
// src/RegistryProvider.tsx
|
|
7
|
+
/**
|
|
8
|
+
* The registry provider shared by the Next.js and React Router integrations.
|
|
9
|
+
*
|
|
10
|
+
* @stability unstable
|
|
11
|
+
* @since 0.1.0
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Provides the app's registry: a request registry while the framework
|
|
15
|
+
* renders the app on the server, and one registry for the page's lifetime
|
|
16
|
+
* in the browser.
|
|
17
|
+
*
|
|
18
|
+
* **When to use**
|
|
19
|
+
*
|
|
20
|
+
* Put it in the root layout, around `children`, in place of
|
|
21
|
+
* `@effect/atom-react`'s `RegistryProvider`, which would share one registry
|
|
22
|
+
* between requests' server renders.
|
|
23
|
+
*
|
|
24
|
+
* **Details**
|
|
25
|
+
*
|
|
26
|
+
* `defaultIdleTTL` is how many milliseconds the browser's registry keeps an
|
|
27
|
+
* atom no component reads, 400 by default, as `@effect/atom-react`'s default
|
|
28
|
+
* registry does. An atom's own `Atom.setIdleTTL` or `Atom.keepAlive` wins.
|
|
29
|
+
* It's read once, when the registry is made. The server's request registry
|
|
30
|
+
* lives for one request, so it has none.
|
|
31
|
+
*
|
|
32
|
+
* **Example**
|
|
33
|
+
*
|
|
34
|
+
* ```tsx
|
|
35
|
+
* import { RegistryProvider } from "raf-effect-atom/next" // or "raf-effect-atom/react-router"
|
|
36
|
+
*
|
|
37
|
+
* export default function RootLayout(props: { children: React.ReactNode }) {
|
|
38
|
+
* return (
|
|
39
|
+
* <RegistryProvider defaultIdleTTL={60_000}>{props.children}</RegistryProvider>
|
|
40
|
+
* )
|
|
41
|
+
* }
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @stability unstable
|
|
45
|
+
* @category components
|
|
46
|
+
* @since 0.1.0
|
|
47
|
+
*/
|
|
48
|
+
const RegistryProvider = (props) => {
|
|
49
|
+
const [registry] = React.useState(() => make({ defaultIdleTTL: props.defaultIdleTTL }));
|
|
50
|
+
return /* @__PURE__ */ jsx(RegistryContext.Provider, {
|
|
51
|
+
value: registry,
|
|
52
|
+
children: props.children
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
export { RegistryProvider };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
2
|
+
import * as Layer from "effect/Layer";
|
|
3
|
+
declare namespace RequestRegistry_d_exports {
|
|
4
|
+
export { isRequestRegistry, layer, make };
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Creates the registry for one server request.
|
|
8
|
+
*
|
|
9
|
+
* **Details**
|
|
10
|
+
*
|
|
11
|
+
* It's an ordinary `AtomRegistry`, marked as a request registry. On a
|
|
12
|
+
* request registry, `ensure` waits like `fetch` does, nothing revalidates in
|
|
13
|
+
* the background, and a loader's lease lasts until the registry is disposed.
|
|
14
|
+
* In development, the loader verbs refuse an atom that isn't serializable,
|
|
15
|
+
* because the browser would request it again.
|
|
16
|
+
*
|
|
17
|
+
* **Example**
|
|
18
|
+
*
|
|
19
|
+
* ```ts
|
|
20
|
+
* import { RequestRegistry } from "raf-effect-atom"
|
|
21
|
+
*
|
|
22
|
+
* const registry = RequestRegistry.make()
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @stability unstable
|
|
26
|
+
* @category constructors
|
|
27
|
+
* @since 0.1.0
|
|
28
|
+
*/
|
|
29
|
+
export declare const make: () => AtomRegistry.AtomRegistry;
|
|
30
|
+
/**
|
|
31
|
+
* A layer that provides a request registry as the `AtomRegistry`, disposed
|
|
32
|
+
* when the layer's scope closes.
|
|
33
|
+
*
|
|
34
|
+
* **When to use**
|
|
35
|
+
*
|
|
36
|
+
* Use when the request is handled in Effect, around the loaders' verbs and
|
|
37
|
+
* `dehydrate`.
|
|
38
|
+
*
|
|
39
|
+
* @see {@link make} for the registry it provides
|
|
40
|
+
*
|
|
41
|
+
* @stability unstable
|
|
42
|
+
* @category layers
|
|
43
|
+
* @since 0.1.0
|
|
44
|
+
*/
|
|
45
|
+
export declare const layer: Layer.Layer<AtomRegistry.AtomRegistry>;
|
|
46
|
+
/**
|
|
47
|
+
* Returns whether a registry was made by `make`, for one server request.
|
|
48
|
+
*
|
|
49
|
+
* @stability unstable
|
|
50
|
+
* @category guards
|
|
51
|
+
* @since 0.1.0
|
|
52
|
+
*/
|
|
53
|
+
export declare const isRequestRegistry: (registry: AtomRegistry.AtomRegistry) => boolean;
|
|
54
|
+
export { RequestRegistry_d_exports };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { __exportAll } from "./_virtual/_rolldown/runtime.mjs";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
4
|
+
import * as Layer from "effect/Layer";
|
|
5
|
+
// src/RequestRegistry.ts
|
|
6
|
+
/**
|
|
7
|
+
* A registry per server request.
|
|
8
|
+
*
|
|
9
|
+
* `@effect/atom-react`'s default `RegistryContext` is one registry for the
|
|
10
|
+
* whole module, which on a server would share one user's results with every
|
|
11
|
+
* other request. Each framework integration makes a request registry with
|
|
12
|
+
* `make`, provides it through `RegistryContext`, and dehydrates it into the
|
|
13
|
+
* response.
|
|
14
|
+
*
|
|
15
|
+
* @stability unstable
|
|
16
|
+
* @since 0.1.0
|
|
17
|
+
*/
|
|
18
|
+
var RequestRegistry_exports = /* @__PURE__ */ __exportAll({
|
|
19
|
+
isRequestRegistry: () => isRequestRegistry,
|
|
20
|
+
layer: () => layer,
|
|
21
|
+
make: () => make
|
|
22
|
+
});
|
|
23
|
+
const requestRegistries = /* @__PURE__ */ new WeakSet();
|
|
24
|
+
/**
|
|
25
|
+
* Creates the registry for one server request.
|
|
26
|
+
*
|
|
27
|
+
* **Details**
|
|
28
|
+
*
|
|
29
|
+
* It's an ordinary `AtomRegistry`, marked as a request registry. On a
|
|
30
|
+
* request registry, `ensure` waits like `fetch` does, nothing revalidates in
|
|
31
|
+
* the background, and a loader's lease lasts until the registry is disposed.
|
|
32
|
+
* In development, the loader verbs refuse an atom that isn't serializable,
|
|
33
|
+
* because the browser would request it again.
|
|
34
|
+
*
|
|
35
|
+
* **Example**
|
|
36
|
+
*
|
|
37
|
+
* ```ts
|
|
38
|
+
* import { RequestRegistry } from "raf-effect-atom"
|
|
39
|
+
*
|
|
40
|
+
* const registry = RequestRegistry.make()
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* @stability unstable
|
|
44
|
+
* @category constructors
|
|
45
|
+
* @since 0.1.0
|
|
46
|
+
*/
|
|
47
|
+
const make = () => {
|
|
48
|
+
const registry = AtomRegistry.make();
|
|
49
|
+
requestRegistries.add(registry);
|
|
50
|
+
return registry;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* A layer that provides a request registry as the `AtomRegistry`, disposed
|
|
54
|
+
* when the layer's scope closes.
|
|
55
|
+
*
|
|
56
|
+
* **When to use**
|
|
57
|
+
*
|
|
58
|
+
* Use when the request is handled in Effect, around the loaders' verbs and
|
|
59
|
+
* `dehydrate`.
|
|
60
|
+
*
|
|
61
|
+
* @see {@link make} for the registry it provides
|
|
62
|
+
*
|
|
63
|
+
* @stability unstable
|
|
64
|
+
* @category layers
|
|
65
|
+
* @since 0.1.0
|
|
66
|
+
*/
|
|
67
|
+
const layer = Layer.effect(AtomRegistry.AtomRegistry, Effect.acquireRelease(Effect.sync(() => make()), (registry) => Effect.sync(() => registry.dispose())));
|
|
68
|
+
/**
|
|
69
|
+
* Returns whether a registry was made by `make`, for one server request.
|
|
70
|
+
*
|
|
71
|
+
* @stability unstable
|
|
72
|
+
* @category guards
|
|
73
|
+
* @since 0.1.0
|
|
74
|
+
*/
|
|
75
|
+
const isRequestRegistry = (registry) => requestRegistries.has(registry);
|
|
76
|
+
export { RequestRegistry_exports, isRequestRegistry, layer, make };
|