raf-effect-atom 0.0.0-stage → 0.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alex / KATT
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,60 @@
1
- # Temporary Holding Version
1
+ # raf-effect-atom
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Render-as-you-fetch for [Effect Atom](https://github.com/Effect-TS/effect/tree/main/packages/atom/react). Route loaders start atoms before anything renders, the server streams their results into the browser, and the browser reads them without requesting them again. Typed failures survive the trip: a server-side `IssueNotFound` is an `IssueNotFound` in the browser.
4
+
5
+ **[GitHub](https://github.com/KATT/raf-effect-atom)**
6
+
7
+ > [!NOTE]
8
+ > raf-effect-atom is early. The API can still change between minor releases.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ npm install raf-effect-atom effect @effect/atom-react react
14
+ ```
15
+
16
+ `effect`, `@effect/atom-react` and `react` (19.2 or newer) are peer dependencies.
17
+
18
+ ## Usage
19
+
20
+ A loader starts atoms with a verb per call. `prefetch` starts one and streams it, `ensure` waits for its first result, and `fetch` waits for a fresh one. Typed failures come back as results, so a loader can redirect on a tag:
21
+
22
+ ```ts
23
+ // issue: (id: number) => an AtomRpc query with a serializationKey
24
+ export async function loader(args: LoaderArgs) {
25
+ const id = Number(args.params.id);
26
+ args.context.atoms.prefetch(comments(id));
27
+ const result = await args.context.atoms.ensure(issue(id));
28
+ if (AsyncResult.isFailure(result)) {
29
+ // result.cause holds IssueNotFound | Unauthorized | RpcClientError
30
+ }
31
+ }
32
+ ```
33
+
34
+ A component reads the atom with `useAtomResult`, which waits for a streamed result instead of starting the atom again, and returns typed failures. It also reads an atom derived from one, such as the atom an [`effect-atom-boundary`](https://github.com/KATT/raf-effect-atom/tree/main/packages/effect-atom-boundary) boundary's `wrap` returns, which throws the errors that boundary claims to it:
35
+
36
+ ```tsx
37
+ import { useAtomResult } from "raf-effect-atom";
38
+
39
+ function IssueTitle(props: { readonly id: number }) {
40
+ const result = useAtomResult(useIssueBoundary().wrap(issue(props.id)));
41
+ // Success<Issue, never>: its boundaries claim every failure
42
+ return <h1>{result.value.title}</h1>;
43
+ }
44
+ ```
45
+
46
+ Each framework has a subpath that gives every server request its own registry and sends it to the browser:
47
+
48
+ - `raf-effect-atom/tanstack-router`, for TanStack Router and TanStack Start: `setupRouterAtoms({ router, registry })` in `getRouter`, with `Loader.make(registry)` on the router context.
49
+ - `raf-effect-atom/next`, for the App Router: `prefetch`, `ensure` and `fetch` in server components, and `<HydrateAtoms state={dehydrate()}>` around the client components that read them.
50
+ - `raf-effect-atom/react-router`, for Framework Mode: `atomsMiddleware(atomsContext)` as the root middleware, loaders that return `atoms.dehydrate()`, and `HydrationBoundary` in the route component.
51
+
52
+ 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(atom)` starts an atom whose input exists only in render, and marks it as a known waterfall.
53
+
54
+ ## For coding agents
55
+
56
+ Start every atom in a loader or a server component, never in a component or `useEffect`, and put the state that picks data (pages, filters, open dialogs) in the URL so the loader can. Event handlers and `useWaterfall` are escape hatches for state that can't be in the URL. Read atoms with `useAtomResult`, not `useAtomValue` or `useAtomSuspense`. Keep `<WaterfallDetection mode="throw">` at the root and fix what it reports instead of turning it off. The full rules, with the reason for each: [For coding agents](https://raf-effect-atom.katt.dev/docs/coding-agents). The docs as markdown: [llms.txt](https://raf-effect-atom.katt.dev/llms.txt).
57
+
58
+ ## License
59
+
60
+ MIT
@@ -0,0 +1,68 @@
1
+ import { SettledResult } from "./Loader.mjs";
2
+ import * as Atom from "effect/reactivity/Atom";
3
+ import * as AsyncResult from "effect/reactivity/AsyncResult";
4
+ /**
5
+ * Reads an `AsyncResult` atom through React Suspense: a result streamed from
6
+ * the server, a hydrated one, or one a loader started.
7
+ *
8
+ * **When to use**
9
+ *
10
+ * Use for every atom a loader starts or the server renders, or for an atom
11
+ * derived from one with `initialValueTarget` pointing at it, such as an error
12
+ * boundary's wrapper.
13
+ *
14
+ * **Details**
15
+ *
16
+ * - A result the server was still loading when it dehydrated is awaited
17
+ * instead of started, so the browser doesn't request it a second time.
18
+ * - The atom is read through `hydrated`, so a hydrated result is kept.
19
+ * - Once the component mounts, the loader's lease ends and the component
20
+ * keeps the atom mounted.
21
+ * - A derived atom is read through the first serializable atom its
22
+ * `initialValueTarget` chain points at: that one is awaited, hydrated and
23
+ * released, so the derived atom doesn't request it again.
24
+ * - The settled result is returned, typed failures included. A defect is
25
+ * thrown to the error boundaries above. With no typed failures left, such
26
+ * as an atom whose boundaries claim all of them, it's typed as a success.
27
+ * - While a failed atom refreshes, such as after a boundary's `reset`, the
28
+ * read suspends instead of returning the old failure again.
29
+ *
30
+ * **Gotchas**
31
+ *
32
+ * A read of an atom that nothing started starts it, and its request waits
33
+ * for the render: a request waterfall. In development, the read reports it
34
+ * as `WaterfallDetection` says, by default with a warning. Start the atom in
35
+ * a loader or a server component, with the state that picks it in the URL.
36
+ * For state that can't be in the URL, start it in the event handler that
37
+ * changes the state, or read it through `useWaterfall` when only the render
38
+ * knows its input.
39
+ *
40
+ * During server rendering, the registry must be a request registry (see
41
+ * `RequestRegistry.make`), or a module-level registry would share results
42
+ * between requests: in development, the hook throws without one.
43
+ *
44
+ * `Atom.transform`'s `initialValueTarget` option points at the end of the
45
+ * target's own chain, which for an AtomRpc query with `reactivityKeys` is an
46
+ * inner atom with no serialization key. A derived atom read here sets
47
+ * `initialValueTarget` to its source itself, as an error boundary's
48
+ * wrapper does, or it requests the source again after hydration.
49
+ *
50
+ * **Example**
51
+ *
52
+ * ```tsx
53
+ * import { AsyncResult } from "effect/reactivity"
54
+ * import { useAtomResult } from "raf-effect-atom"
55
+ *
56
+ * function IssueTitle(props: { readonly id: number }) {
57
+ * const result = useAtomResult(issue(props.id))
58
+ * return AsyncResult.isSuccess(result) ? <h1>{result.value.title}</h1> : null
59
+ * }
60
+ * ```
61
+ *
62
+ * @see {@link hydrated} for reading hydrated atoms with Atom's own hooks
63
+ *
64
+ * @stability unstable
65
+ * @category hooks
66
+ * @since 0.1.0
67
+ */
68
+ export declare const useAtomResult: <A, E>(atom: Atom.Atom<AsyncResult.AsyncResult<A, E>>) => [E] extends [never] ? AsyncResult.Success<A, never> : SettledResult<A, E>;
package/dist/Hooks.mjs ADDED
@@ -0,0 +1,119 @@
1
+ "use client";
2
+ import { stream } from "./internal/pending.mjs";
3
+ import { hydrated } from "./Hydration.mjs";
4
+ import { isDevelopment } from "./internal/development.mjs";
5
+ import { keyOf, release } from "./internal/leases.mjs";
6
+ import { isRequestRegistry } from "./RequestRegistry.mjs";
7
+ import { isServer } from "./internal/registry.mjs";
8
+ import { ModeContext, check } from "./internal/waterfalls.mjs";
9
+ import * as Atom from "effect/reactivity/Atom";
10
+ import * as Predicate from "effect/Predicate";
11
+ import * as Cause from "effect/Cause";
12
+ import * as Effect from "effect/Effect";
13
+ import * as Option from "effect/Option";
14
+ import * as AsyncResult from "effect/reactivity/AsyncResult";
15
+ import { RegistryContext, useAtomSuspense } from "@effect/atom-react";
16
+ import * as React from "react";
17
+ // src/Hooks.ts
18
+ /**
19
+ * The read hook for atoms started by loaders and streamed from the server.
20
+ *
21
+ * @stability unstable
22
+ * @since 0.1.0
23
+ */
24
+ /**
25
+ * The atom the loader started and the server sent: the first serializable
26
+ * atom in `atom`'s `initialValueTarget` chain, starting with `atom`. An
27
+ * AtomRpc query is serializable itself, though it points at an inner atom.
28
+ */
29
+ const sourceOf = (atom) => {
30
+ for (let target = atom; target !== void 0; target = target.initialValueTarget) if (Atom.isSerializable(target)) return target;
31
+ return atom;
32
+ };
33
+ /**
34
+ * Reads an `AsyncResult` atom through React Suspense: a result streamed from
35
+ * the server, a hydrated one, or one a loader started.
36
+ *
37
+ * **When to use**
38
+ *
39
+ * Use for every atom a loader starts or the server renders, or for an atom
40
+ * derived from one with `initialValueTarget` pointing at it, such as an error
41
+ * boundary's wrapper.
42
+ *
43
+ * **Details**
44
+ *
45
+ * - A result the server was still loading when it dehydrated is awaited
46
+ * instead of started, so the browser doesn't request it a second time.
47
+ * - The atom is read through `hydrated`, so a hydrated result is kept.
48
+ * - Once the component mounts, the loader's lease ends and the component
49
+ * keeps the atom mounted.
50
+ * - A derived atom is read through the first serializable atom its
51
+ * `initialValueTarget` chain points at: that one is awaited, hydrated and
52
+ * released, so the derived atom doesn't request it again.
53
+ * - The settled result is returned, typed failures included. A defect is
54
+ * thrown to the error boundaries above. With no typed failures left, such
55
+ * as an atom whose boundaries claim all of them, it's typed as a success.
56
+ * - While a failed atom refreshes, such as after a boundary's `reset`, the
57
+ * read suspends instead of returning the old failure again.
58
+ *
59
+ * **Gotchas**
60
+ *
61
+ * A read of an atom that nothing started starts it, and its request waits
62
+ * for the render: a request waterfall. In development, the read reports it
63
+ * as `WaterfallDetection` says, by default with a warning. Start the atom in
64
+ * a loader or a server component, with the state that picks it in the URL.
65
+ * For state that can't be in the URL, start it in the event handler that
66
+ * changes the state, or read it through `useWaterfall` when only the render
67
+ * knows its input.
68
+ *
69
+ * During server rendering, the registry must be a request registry (see
70
+ * `RequestRegistry.make`), or a module-level registry would share results
71
+ * between requests: in development, the hook throws without one.
72
+ *
73
+ * `Atom.transform`'s `initialValueTarget` option points at the end of the
74
+ * target's own chain, which for an AtomRpc query with `reactivityKeys` is an
75
+ * inner atom with no serialization key. A derived atom read here sets
76
+ * `initialValueTarget` to its source itself, as an error boundary's
77
+ * wrapper does, or it requests the source again after hydration.
78
+ *
79
+ * **Example**
80
+ *
81
+ * ```tsx
82
+ * import { AsyncResult } from "effect/reactivity"
83
+ * import { useAtomResult } from "raf-effect-atom"
84
+ *
85
+ * function IssueTitle(props: { readonly id: number }) {
86
+ * const result = useAtomResult(issue(props.id))
87
+ * return AsyncResult.isSuccess(result) ? <h1>{result.value.title}</h1> : null
88
+ * }
89
+ * ```
90
+ *
91
+ * @see {@link hydrated} for reading hydrated atoms with Atom's own hooks
92
+ *
93
+ * @stability unstable
94
+ * @category hooks
95
+ * @since 0.1.0
96
+ */
97
+ const useAtomResult = (atom) => {
98
+ const registry = React.useContext(RegistryContext);
99
+ if (isServer() && !isRequestRegistry(registry) && Effect.runSync(isDevelopment)) throw new Error("raf-effect-atom: useAtomResult rendered on the server without a request registry, so results would be shared between requests. Provide RequestRegistry.make() through RegistryContext, as the framework integrations do.");
100
+ const target = sourceOf(atom);
101
+ const source = hydrated(target);
102
+ const read = target === atom ? hydrated(atom) : atom;
103
+ const key = keyOf(source);
104
+ const streamed = Predicate.isString(key) ? stream(registry, key) : void 0;
105
+ if (streamed !== void 0) React.use(streamed);
106
+ if (process.env.NODE_ENV !== "production") check(registry, source, React.use(ModeContext));
107
+ if (read !== source) registry.get(source);
108
+ const current = registry.get(read);
109
+ const result = useAtomSuspense(read, {
110
+ includeFailure: true,
111
+ suspendOnWaiting: AsyncResult.isFailure(current) && current.waiting
112
+ });
113
+ React.useEffect(() => {
114
+ release(registry, source);
115
+ }, [registry, source]);
116
+ if (AsyncResult.isFailure(result) && Option.isNone(Cause.findErrorOption(result.cause))) throw Cause.squash(result.cause);
117
+ return result;
118
+ };
119
+ export { useAtomResult };
@@ -0,0 +1,93 @@
1
+ import * as Atom from "effect/reactivity/Atom";
2
+ import * as EffectHydration from "effect/reactivity/Hydration";
3
+ import * as AtomRegistry from "effect/reactivity/AtomRegistry";
4
+ declare namespace Hydration_d_exports {
5
+ export { dehydrate, hydrate, hydrated };
6
+ }
7
+ /**
8
+ * Returns a serializable atom that keeps its hydrated value on its first
9
+ * read, instead of running its effect again.
10
+ *
11
+ * **When to use**
12
+ *
13
+ * The loader verbs and `useAtomResult` already read through it. Use it when
14
+ * reading a hydrated atom with `@effect/atom-react`'s own hooks.
15
+ *
16
+ * **Details**
17
+ *
18
+ * The copy shares the atom's serialization key, so both read the same
19
+ * registry node. It sets `skipInitialRun` on the atom's `Serializable`
20
+ * metadata, and is memoized per atom, so every call returns the same copy.
21
+ * An atom that isn't serializable, or already skips its initial run, is
22
+ * returned as it is.
23
+ *
24
+ * **Gotchas**
25
+ *
26
+ * The copy keeps the hydrated value only for the first read: a refresh, or a
27
+ * change to a dependency, runs the effect as usual. An `Initial` value never
28
+ * skips the effect, which is why a result the server was still loading
29
+ * crosses as a promise that `useAtomResult` waits for.
30
+ *
31
+ * **Example**
32
+ *
33
+ * ```ts
34
+ * import { useAtomValue } from "@effect/atom-react"
35
+ * import { Hydration } from "raf-effect-atom"
36
+ *
37
+ * const result = useAtomValue(Hydration.hydrated(issue(1)))
38
+ * ```
39
+ *
40
+ * @see {@link Atom.serializable} for the metadata it copies
41
+ *
42
+ * @stability unstable
43
+ * @category combinators
44
+ * @since 0.1.0
45
+ */
46
+ export declare const hydrated: <Self extends Atom.Atom<unknown>>(atom: Self) => Self;
47
+ /**
48
+ * Encodes a request registry's serializable atoms for the browser.
49
+ *
50
+ * **Details**
51
+ *
52
+ * `Hydration.dehydrate` with `encodeInitialAs: "promise"`: a settled result
53
+ * crosses as its encoded value, and a result still loading crosses as a
54
+ * promise of its encoded value, which resolves when it settles. The
55
+ * framework's serializer carries the promises (TanStack Router's dehydrated
56
+ * state, React's server component props, React Router's Single Fetch).
57
+ *
58
+ * **Gotchas**
59
+ *
60
+ * Only atoms in the registry when it's called are included, so call it once
61
+ * the loaders have started theirs.
62
+ *
63
+ * @see {@link hydrate} for the browser side
64
+ *
65
+ * @stability unstable
66
+ * @category dehydration
67
+ * @since 0.1.0
68
+ */
69
+ export declare const dehydrate: (registry: AtomRegistry.AtomRegistry) => Array<EffectHydration.DehydratedAtom>;
70
+ /**
71
+ * Applies dehydrated state to the browser's registry.
72
+ *
73
+ * **When to use**
74
+ *
75
+ * Use outside React, such as in a router's `hydrate` hook, before the app
76
+ * renders. Inside React, use `HydrationBoundary`.
77
+ *
78
+ * **Details**
79
+ *
80
+ * Settled values go through `Hydration.hydrate`. A streamed result is held
81
+ * until its promise resolves, then hydrated; until then `useAtomResult`
82
+ * suspends on it instead of starting the atom, which would request it again.
83
+ * A streamed result that can't be decoded, or whose promise rejects, is
84
+ * dropped, and the atom runs in the browser.
85
+ *
86
+ * @see {@link dehydrate} for the server side
87
+ *
88
+ * @stability unstable
89
+ * @category hydration
90
+ * @since 0.1.0
91
+ */
92
+ export declare const hydrate: (registry: AtomRegistry.AtomRegistry, state: Iterable<EffectHydration.DehydratedAtom>) => void;
93
+ export { Hydration_d_exports };
@@ -0,0 +1,134 @@
1
+ import { __exportAll } from "./_virtual/_rolldown/runtime.mjs";
2
+ import { split } from "./internal/pending.mjs";
3
+ import { remember } from "./internal/sources.mjs";
4
+ import * as Atom from "effect/reactivity/Atom";
5
+ import * as EffectHydration from "effect/reactivity/Hydration";
6
+ // src/Hydration.ts
7
+ /**
8
+ * Streams a server registry into the browser's, typed failures intact, and
9
+ * hydrates it without requesting anything again.
10
+ *
11
+ * `dehydrate` encodes the request registry's serializable atoms. Settled
12
+ * results cross as values; results still loading cross as promises that
13
+ * resolve when they settle, carried by the framework's own streaming.
14
+ * `hydrate` applies settled values at once and holds each streamed result
15
+ * until its promise resolves, so `useAtomResult` waits for it instead of
16
+ * starting the atom. `hydrated` keeps a hydrated value on the atom's first
17
+ * read instead of running its effect again.
18
+ *
19
+ * Typed failures survive because a serializable `AsyncResult` atom, such as
20
+ * an `AtomRpc` query with a `serializationKey`, encodes its `Cause` with the
21
+ * procedure's error Schemas: a server-side `IssueNotFound` decodes as an
22
+ * `IssueNotFound` in the browser.
23
+ *
24
+ * @stability unstable
25
+ * @since 0.1.0
26
+ */
27
+ var Hydration_exports = /* @__PURE__ */ __exportAll({
28
+ dehydrate: () => dehydrate,
29
+ hydrate: () => hydrate,
30
+ hydrated: () => hydrated
31
+ });
32
+ const copies = /* @__PURE__ */ new WeakMap();
33
+ /**
34
+ * Returns a serializable atom that keeps its hydrated value on its first
35
+ * read, instead of running its effect again.
36
+ *
37
+ * **When to use**
38
+ *
39
+ * The loader verbs and `useAtomResult` already read through it. Use it when
40
+ * reading a hydrated atom with `@effect/atom-react`'s own hooks.
41
+ *
42
+ * **Details**
43
+ *
44
+ * The copy shares the atom's serialization key, so both read the same
45
+ * registry node. It sets `skipInitialRun` on the atom's `Serializable`
46
+ * metadata, and is memoized per atom, so every call returns the same copy.
47
+ * An atom that isn't serializable, or already skips its initial run, is
48
+ * returned as it is.
49
+ *
50
+ * **Gotchas**
51
+ *
52
+ * The copy keeps the hydrated value only for the first read: a refresh, or a
53
+ * change to a dependency, runs the effect as usual. An `Initial` value never
54
+ * skips the effect, which is why a result the server was still loading
55
+ * crosses as a promise that `useAtomResult` waits for.
56
+ *
57
+ * **Example**
58
+ *
59
+ * ```ts
60
+ * import { useAtomValue } from "@effect/atom-react"
61
+ * import { Hydration } from "raf-effect-atom"
62
+ *
63
+ * const result = useAtomValue(Hydration.hydrated(issue(1)))
64
+ * ```
65
+ *
66
+ * @see {@link Atom.serializable} for the metadata it copies
67
+ *
68
+ * @stability unstable
69
+ * @category combinators
70
+ * @since 0.1.0
71
+ */
72
+ const hydrated = (atom) => {
73
+ if (!Atom.isSerializable(atom) || atom[Atom.SerializableTypeId].skipInitialRun === true) return atom;
74
+ const cached = copies.get(atom);
75
+ if (cached !== void 0) return cached;
76
+ const copy = Object.assign(Object.create(Object.getPrototypeOf(atom)), {
77
+ ...atom,
78
+ [Atom.SerializableTypeId]: {
79
+ ...atom[Atom.SerializableTypeId],
80
+ skipInitialRun: true
81
+ }
82
+ });
83
+ copies.set(atom, copy);
84
+ return copy;
85
+ };
86
+ /**
87
+ * Encodes a request registry's serializable atoms for the browser.
88
+ *
89
+ * **Details**
90
+ *
91
+ * `Hydration.dehydrate` with `encodeInitialAs: "promise"`: a settled result
92
+ * crosses as its encoded value, and a result still loading crosses as a
93
+ * promise of its encoded value, which resolves when it settles. The
94
+ * framework's serializer carries the promises (TanStack Router's dehydrated
95
+ * state, React's server component props, React Router's Single Fetch).
96
+ *
97
+ * **Gotchas**
98
+ *
99
+ * Only atoms in the registry when it's called are included, so call it once
100
+ * the loaders have started theirs.
101
+ *
102
+ * @see {@link hydrate} for the browser side
103
+ *
104
+ * @stability unstable
105
+ * @category dehydration
106
+ * @since 0.1.0
107
+ */
108
+ const dehydrate = (registry) => remember(EffectHydration.dehydrate(registry, { encodeInitialAs: "promise" }), registry);
109
+ /**
110
+ * Applies dehydrated state to the browser's registry.
111
+ *
112
+ * **When to use**
113
+ *
114
+ * Use outside React, such as in a router's `hydrate` hook, before the app
115
+ * renders. Inside React, use `HydrationBoundary`.
116
+ *
117
+ * **Details**
118
+ *
119
+ * Settled values go through `Hydration.hydrate`. A streamed result is held
120
+ * until its promise resolves, then hydrated; until then `useAtomResult`
121
+ * suspends on it instead of starting the atom, which would request it again.
122
+ * A streamed result that can't be decoded, or whose promise rejects, is
123
+ * dropped, and the atom runs in the browser.
124
+ *
125
+ * @see {@link dehydrate} for the server side
126
+ *
127
+ * @stability unstable
128
+ * @category hydration
129
+ * @since 0.1.0
130
+ */
131
+ const hydrate = (registry, state) => {
132
+ EffectHydration.hydrate(registry, split(registry, state));
133
+ };
134
+ export { Hydration_exports, dehydrate, hydrate, hydrated };
@@ -0,0 +1,157 @@
1
+ import * as Atom from "effect/reactivity/Atom";
2
+ import * as Context from "effect/Context";
3
+ import * as Duration from "effect/Duration";
4
+ import * as Effect from "effect/Effect";
5
+ import * as AsyncResult from "effect/reactivity/AsyncResult";
6
+ import * as AtomRegistry from "effect/reactivity/AtomRegistry";
7
+ declare namespace Loader_d_exports {
8
+ export { LeaseTTL, Loader, SettledResult, ensure, fetch, make, prefetch };
9
+ }
10
+ /**
11
+ * A settled `AsyncResult`: what `ensure` and `fetch` return.
12
+ *
13
+ * @stability unstable
14
+ * @category models
15
+ * @since 0.1.0
16
+ */
17
+ export type SettledResult<A, E> = AsyncResult.Failure<A, E> | AsyncResult.Success<A, E>;
18
+ /**
19
+ * How long a loader's lease keeps an atom mounted when no component takes it
20
+ * over. Default 30 seconds.
21
+ *
22
+ * **Details**
23
+ *
24
+ * A lease ends when `useAtomResult` mounts the atom, or when this time has
25
+ * passed, after which the registry's idle TTL applies as usual. On a request
26
+ * registry, leases last until the registry is disposed.
27
+ *
28
+ * @stability unstable
29
+ * @category references
30
+ * @since 0.1.0
31
+ */
32
+ export declare const LeaseTTL: Context.Reference<Duration.Duration>;
33
+ /**
34
+ * Starts an atom without waiting for it.
35
+ *
36
+ * **When to use**
37
+ *
38
+ * Use for data the route needs but its loader doesn't: it streams in while
39
+ * the route renders.
40
+ *
41
+ * **Details**
42
+ *
43
+ * Starts the atom if it isn't in the registry, and leaves it alone if it is.
44
+ * Takes a lease either way.
45
+ *
46
+ * **Example**
47
+ *
48
+ * ```ts
49
+ * import { Effect } from "effect"
50
+ * import { Loader } from "raf-effect-atom"
51
+ *
52
+ * const program = Loader.prefetch(comments(1))
53
+ * ```
54
+ *
55
+ * @stability unstable
56
+ * @category verbs
57
+ * @since 0.1.0
58
+ */
59
+ export declare const prefetch: <A, E>(atom: Atom.Atom<AsyncResult.AsyncResult<A, E>>) => Effect.Effect<void, never, AtomRegistry.AtomRegistry>;
60
+ /**
61
+ * Starts an atom and returns its settled result, waiting only while it has
62
+ * none.
63
+ *
64
+ * **When to use**
65
+ *
66
+ * Use when the loader needs the result, such as to redirect on a tag, or
67
+ * when the route shouldn't render without it.
68
+ *
69
+ * **Details**
70
+ *
71
+ * An atom with a result returns it at once, without refetching. On a request
72
+ * registry, it also waits out a refresh in flight, so the server never
73
+ * renders a result that's about to change. A typed failure is returned as
74
+ * the result; a defect or an interruption fails the Effect.
75
+ *
76
+ * **Example**
77
+ *
78
+ * ```ts
79
+ * import { Effect } from "effect"
80
+ * import { AsyncResult } from "effect/reactivity"
81
+ * import { Loader } from "raf-effect-atom"
82
+ *
83
+ * const program = Effect.gen(function* () {
84
+ * const result = yield* Loader.ensure(issue(1))
85
+ * return AsyncResult.isSuccess(result) ? result.value.title : "missing"
86
+ * })
87
+ * ```
88
+ *
89
+ * @stability unstable
90
+ * @category verbs
91
+ * @since 0.1.0
92
+ */
93
+ export declare const ensure: <A, E>(atom: Atom.Atom<AsyncResult.AsyncResult<A, E>>) => Effect.Effect<SettledResult<A, E>, never, AtomRegistry.AtomRegistry>;
94
+ /**
95
+ * Starts an atom, or refreshes it if it has a result, and returns the new
96
+ * settled result.
97
+ *
98
+ * **When to use**
99
+ *
100
+ * Use when the loader must not see a result from before it ran, such as
101
+ * after a mutation.
102
+ *
103
+ * **Details**
104
+ *
105
+ * Waits until the result settles and isn't refreshing. A typed failure is
106
+ * returned as the result; a defect or an interruption fails the Effect.
107
+ *
108
+ * @stability unstable
109
+ * @category verbs
110
+ * @since 0.1.0
111
+ */
112
+ export declare const fetch: <A, E>(atom: Atom.Atom<AsyncResult.AsyncResult<A, E>>) => Effect.Effect<SettledResult<A, E>, never, AtomRegistry.AtomRegistry>;
113
+ /**
114
+ * The loader verbs as Promises over one registry, as the framework
115
+ * integrations put them on the router context.
116
+ *
117
+ * @stability unstable
118
+ * @category models
119
+ * @since 0.1.0
120
+ */
121
+ export interface Loader {
122
+ /** {@link ensure}, as a Promise that rejects only on a defect. */
123
+ readonly ensure: <A, E>(atom: Atom.Atom<AsyncResult.AsyncResult<A, E>>) => Promise<SettledResult<A, E>>;
124
+ /** {@link fetch}, as a Promise that rejects only on a defect. */
125
+ readonly fetch: <A, E>(atom: Atom.Atom<AsyncResult.AsyncResult<A, E>>) => Promise<SettledResult<A, E>>;
126
+ /** {@link prefetch}, run at once. */
127
+ readonly prefetch: <A, E>(atom: Atom.Atom<AsyncResult.AsyncResult<A, E>>) => void;
128
+ }
129
+ /**
130
+ * Creates the Promise verbs over one registry.
131
+ *
132
+ * **When to use**
133
+ *
134
+ * Use in a framework integration, in loaders that are async functions, or
135
+ * in an event handler that starts an atom before the state change that
136
+ * renders its reader, for state that can't be in the URL, such as a
137
+ * search-as-you-type query. In Effect code, use
138
+ * the verbs directly with the `AtomRegistry` provided.
139
+ *
140
+ * **Example**
141
+ *
142
+ * ```ts
143
+ * import { Loader, RequestRegistry } from "raf-effect-atom"
144
+ *
145
+ * const loader = Loader.make(RequestRegistry.make())
146
+ * loader.prefetch(comments(1))
147
+ * const result = await loader.ensure(issue(1))
148
+ * ```
149
+ *
150
+ * @stability unstable
151
+ * @category constructors
152
+ * @since 0.1.0
153
+ */
154
+ export declare const make: (registry: AtomRegistry.AtomRegistry, options?: {
155
+ readonly leaseTTL?: Duration.Input | undefined;
156
+ }) => Loader;
157
+ export { Loader_d_exports };