raf-effect-atom 0.2.4 → 0.2.6
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 +23 -2
- package/dist/Handle.d.mts +5 -0
- package/dist/Hooks.mjs +4 -12
- package/dist/Loader.d.mts +66 -7
- package/dist/Loader.mjs +91 -15
- package/dist/Next.d.mts +9 -0
- package/dist/Next.mjs +10 -1
- package/dist/Waterfalls.d.mts +4 -7
- package/dist/Waterfalls.mjs +6 -9
- package/dist/internal/handles.mjs +17 -3
- package/dist/internal/next.mjs +1 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,14 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [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
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
* **raf-effect-atom:** start browser-only reads with prefetchInBrowser ([65f783a](https://github.com/KATT/raf-effect-atom/commit/65f783a0014e44a8aa11ce5e8fa34ab863063403))
|
|
8
|
+
|
|
9
|
+
### Documentation
|
|
10
|
+
|
|
11
|
+
* **www:** keep a search box's text in the URL, and drop the debounce sample ([a3949bf](https://github.com/KATT/raf-effect-atom/commit/a3949bfca4b64e478132f983033b2776e50ba949))
|
|
12
|
+
|
|
13
|
+
### Tests
|
|
14
|
+
|
|
15
|
+
* **raf-effect-atom:** start a browser-only read from a client-only TanStack route ([69a8bd8](https://github.com/KATT/raf-effect-atom/commit/69a8bd887c34a7aeef51fdd20057ac30aa9e1fbc))
|
|
16
|
+
* **raf-effect-atom:** type a derived atom's handle through it and its wrappers ([a16b587](https://github.com/KATT/raf-effect-atom/commit/a16b587a59dc4827dd18ecb3a3d5bb8f6fdedf0b))
|
|
17
|
+
|
|
18
|
+
## [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
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
- **raf-effect-atom:** start a derived atom's source from the loader verbs, following initialValueTarget ([00a9760](https://github.com/KATT/raf-effect-atom/commit/00a97602068cabf085fa9cfe41e88bf853b6af32))
|
|
23
|
+
|
|
3
24
|
## [0.2.4](https://github.com/KATT/raf-effect-atom/compare/raf-effect-atom%400.2.3...raf-effect-atom%400.2.4) (2026-10-23)
|
|
4
25
|
|
|
5
26
|
### Features
|
|
6
27
|
|
|
7
|
-
|
|
28
|
+
- **raf-effect-atom:** start a request registry with initialValues, readable atoms included ([67cf767](https://github.com/KATT/raf-effect-atom/commit/67cf767e121e76b385ff0808a087efc6662aed89))
|
|
8
29
|
|
|
9
30
|
### Documentation
|
|
10
31
|
|
|
11
|
-
|
|
32
|
+
- correct the generated changelogs' cut-off breaking change and bogus issue links ([c21f64c](https://github.com/KATT/raf-effect-atom/commit/c21f64cad3b4638f2341968aa2e10af5a19904c1))
|
|
12
33
|
|
|
13
34
|
## [0.2.3](https://github.com/KATT/raf-effect-atom/compare/raf-effect-atom%400.2.2...raf-effect-atom%400.2.3) (2026-10-23)
|
|
14
35
|
|
package/dist/Handle.d.mts
CHANGED
|
@@ -67,6 +67,11 @@ export type HandleKey<S> = string & {
|
|
|
67
67
|
*/
|
|
68
68
|
export interface Handle<S> {
|
|
69
69
|
readonly args: ArgsOf<S>;
|
|
70
|
+
/**
|
|
71
|
+
* `true` when `Loader.prefetchInBrowser` ran on the server and left the
|
|
72
|
+
* atom to the browser: the browser's read starts it.
|
|
73
|
+
*/
|
|
74
|
+
readonly inBrowser?: true;
|
|
70
75
|
/** The atom's serialization key; `undefined` for an atom without one. */
|
|
71
76
|
readonly key: HandleKey<S> | undefined;
|
|
72
77
|
}
|
package/dist/Hooks.mjs
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import { stream } from "./internal/pending.mjs";
|
|
3
3
|
import { hydrated } from "./Hydration.mjs";
|
|
4
4
|
import { keyOf, release } from "./internal/leases.mjs";
|
|
5
|
-
import { atomOf } from "./internal/handles.mjs";
|
|
5
|
+
import { atomOf, sourceOf } from "./internal/handles.mjs";
|
|
6
|
+
import { isRequestRegistry } from "./RequestRegistry.mjs";
|
|
6
7
|
import { requireRequestRegistry } from "./internal/registry.mjs";
|
|
7
8
|
import { ModeContext, check } from "./internal/waterfalls.mjs";
|
|
8
|
-
import * as Atom from "effect/reactivity/Atom";
|
|
9
9
|
import * as Predicate from "effect/Predicate";
|
|
10
10
|
import * as Cause from "effect/Cause";
|
|
11
11
|
import * as Option from "effect/Option";
|
|
@@ -20,15 +20,6 @@ import * as React from "react";
|
|
|
20
20
|
* @since 0.1.0
|
|
21
21
|
*/
|
|
22
22
|
/**
|
|
23
|
-
* The atom the loader started and the server sent: the first serializable
|
|
24
|
-
* atom in `atom`'s `initialValueTarget` chain, starting with `atom`. An
|
|
25
|
-
* AtomRpc query is serializable itself, though it points at an inner atom.
|
|
26
|
-
*/
|
|
27
|
-
const sourceOf = (atom) => {
|
|
28
|
-
for (let target = atom; target !== void 0; target = target.initialValueTarget) if (Atom.isSerializable(target)) return target;
|
|
29
|
-
return atom;
|
|
30
|
-
};
|
|
31
|
-
/**
|
|
32
23
|
* Reads a handle through React Suspense, through the source that made it: a
|
|
33
24
|
* result streamed from the server, a hydrated one, or one a loader started.
|
|
34
25
|
*
|
|
@@ -109,6 +100,7 @@ const useAtomResult = (source, handle) => {
|
|
|
109
100
|
const registry = React.useContext(RegistryContext);
|
|
110
101
|
const atom = atomOf(source, handle.args);
|
|
111
102
|
requireRequestRegistry(registry, "useAtomResult");
|
|
103
|
+
if (handle.inBrowser === true && isRequestRegistry(registry)) throw new Error(`raf-effect-atom: ${String(atom)} starts in the browser (Loader.prefetchInBrowser), so the server renders the nearest Suspense boundary's fallback, and the browser renders the boundary.`);
|
|
112
104
|
const target = sourceOf(atom);
|
|
113
105
|
const started = hydrated(target);
|
|
114
106
|
const read = target === atom ? hydrated(atom) : atom;
|
|
@@ -116,7 +108,7 @@ const useAtomResult = (source, handle) => {
|
|
|
116
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.`);
|
|
117
109
|
const streamed = Predicate.isString(key) ? stream(registry, key) : void 0;
|
|
118
110
|
if (streamed !== void 0) React.use(streamed);
|
|
119
|
-
if (process.env.NODE_ENV !== "production") check(registry, started, React.use(ModeContext));
|
|
111
|
+
if (process.env.NODE_ENV !== "production" && handle.inBrowser !== true) check(registry, started, React.use(ModeContext));
|
|
120
112
|
if (read !== started) registry.get(started);
|
|
121
113
|
const current = registry.get(read);
|
|
122
114
|
const result = useAtomSuspense(read, {
|
package/dist/Loader.d.mts
CHANGED
|
@@ -5,7 +5,7 @@ import * as Effect from "effect/Effect";
|
|
|
5
5
|
import * as AsyncResult from "effect/reactivity/AsyncResult";
|
|
6
6
|
import * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
7
7
|
declare namespace Loader_d_exports {
|
|
8
|
-
export { LeaseTTL, Loader, MakeOptions, SettledResult, ensure, fetch, make, prefetch, result };
|
|
8
|
+
export { LeaseTTL, Loader, MakeOptions, SettledResult, ensure, fetch, make, prefetch, prefetchInBrowser, result };
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
11
|
* A settled `AsyncResult`: what `ensure` and `fetch` return.
|
|
@@ -40,10 +40,24 @@ export declare const LeaseTTL: Context.Reference<Duration.Duration>;
|
|
|
40
40
|
*
|
|
41
41
|
* **Details**
|
|
42
42
|
*
|
|
43
|
-
* Starts the atom if it isn't in the registry, and leaves it alone if it
|
|
44
|
-
* Takes a lease either way. The component that reads the handle
|
|
45
|
-
* until the result settles. On a request registry whose async
|
|
46
|
-
* running, it waits for the seed first.
|
|
43
|
+
* - Starts the atom if it isn't in the registry, and leaves it alone if it
|
|
44
|
+
* is. Takes a lease either way. The component that reads the handle
|
|
45
|
+
* suspends until the result settles. On a request registry whose async
|
|
46
|
+
* seed is still running, it waits for the seed first.
|
|
47
|
+
* - A derived atom that isn't serializable, such as an error boundary's
|
|
48
|
+
* `wrap(source)`, starts the first serializable atom its
|
|
49
|
+
* `initialValueTarget` chain points at, the one `useAtomResult` reads it
|
|
50
|
+
* through: the lease, the handle's key and what the server sends are
|
|
51
|
+
* that atom's. `ensure` and `fetch` wait for the derived atom's own
|
|
52
|
+
* result.
|
|
53
|
+
*
|
|
54
|
+
* **Gotchas**
|
|
55
|
+
*
|
|
56
|
+
* The server sends a result still loading as a promise only when the atom
|
|
57
|
+
* it sends holds an `AsyncResult`. A derived atom whose serializable atom
|
|
58
|
+
* holds another value, such as an `Option` snapshot, crosses with the value
|
|
59
|
+
* it has when the registry is dehydrated: point it at an `AsyncResult`
|
|
60
|
+
* atom, or `ensure` it.
|
|
47
61
|
*
|
|
48
62
|
* **Example**
|
|
49
63
|
*
|
|
@@ -59,6 +73,49 @@ export declare const LeaseTTL: Context.Reference<Duration.Duration>;
|
|
|
59
73
|
* @since 0.1.0
|
|
60
74
|
*/
|
|
61
75
|
export declare const prefetch: <S extends Source>(source: S, ...args: ArgsOf<S>) => Effect.Effect<Handle<S>, never, AtomRegistry.AtomRegistry>;
|
|
76
|
+
/**
|
|
77
|
+
* Starts a source's atom in the browser, and returns its handle: on the
|
|
78
|
+
* server, it starts nothing, and the browser's read starts the atom.
|
|
79
|
+
*
|
|
80
|
+
* **When to use**
|
|
81
|
+
*
|
|
82
|
+
* Use for data only the browser can load: an input the server never sees,
|
|
83
|
+
* such as a token in the URL's fragment or a permission, or a request the
|
|
84
|
+
* server can't make. The rest of the page still renders on the server.
|
|
85
|
+
*
|
|
86
|
+
* **Details**
|
|
87
|
+
*
|
|
88
|
+
* - In the browser, it's `prefetch`: a loader that runs there, such as on a
|
|
89
|
+
* client navigation, starts the atom and takes a lease.
|
|
90
|
+
* - On a request registry, it returns a handle marked `inBrowser` without
|
|
91
|
+
* starting the atom, so the atom needs no serialization key. On the
|
|
92
|
+
* server, `useAtomResult` throws for that handle, so React renders the
|
|
93
|
+
* nearest Suspense boundary's fallback there and renders the boundary in
|
|
94
|
+
* the browser. There, the read starts the atom, and the waterfall check
|
|
95
|
+
* skips it, since a loader asked for it.
|
|
96
|
+
*
|
|
97
|
+
* **Gotchas**
|
|
98
|
+
*
|
|
99
|
+
* - Read the handle under a Suspense boundary: on the server, a read
|
|
100
|
+
* outside one fails the whole render. React reports the boundary it
|
|
101
|
+
* renders in the browser to `onRecoverableError`.
|
|
102
|
+
* - On a page the server rendered, the request starts when the browser
|
|
103
|
+
* renders the read, after hydration's JavaScript has loaded: start the
|
|
104
|
+
* data the server can load with `prefetch` or `ensure`.
|
|
105
|
+
*
|
|
106
|
+
* **Example**
|
|
107
|
+
*
|
|
108
|
+
* ```ts
|
|
109
|
+
* import { Loader } from "raf-effect-atom"
|
|
110
|
+
*
|
|
111
|
+
* const program = Loader.prefetchInBrowser(pushSubscription)
|
|
112
|
+
* ```
|
|
113
|
+
*
|
|
114
|
+
* @stability unstable
|
|
115
|
+
* @category verbs
|
|
116
|
+
* @since 0.2.6
|
|
117
|
+
*/
|
|
118
|
+
export declare const prefetchInBrowser: <S extends Source>(source: S, ...args: ArgsOf<S>) => Effect.Effect<Handle<S>, never, AtomRegistry.AtomRegistry>;
|
|
62
119
|
/**
|
|
63
120
|
* Starts a source's atom, waits only while it has no result, and returns a
|
|
64
121
|
* handle whose reader renders without suspending.
|
|
@@ -165,6 +222,8 @@ export interface Loader {
|
|
|
165
222
|
* the seed resolves.
|
|
166
223
|
*/
|
|
167
224
|
readonly prefetch: <S extends Source>(source: S, ...args: ArgsOf<S>) => Handle<S>;
|
|
225
|
+
/** {@link prefetchInBrowser}, run at once. */
|
|
226
|
+
readonly prefetchInBrowser: <S extends Source>(source: S, ...args: ArgsOf<S>) => Handle<S>;
|
|
168
227
|
/**
|
|
169
228
|
* Runs an Effect that needs the registry, such as the Effect verbs in an
|
|
170
229
|
* `Effect.gen`, as a Promise that rejects with its squashed cause.
|
|
@@ -199,8 +258,8 @@ export interface MakeOptions {
|
|
|
199
258
|
*
|
|
200
259
|
* Use in a framework integration, in loaders that are async functions, or
|
|
201
260
|
* in an event handler that starts an atom before the state change that
|
|
202
|
-
* renders its reader, for
|
|
203
|
-
*
|
|
261
|
+
* renders its reader, for a choice a click or a submit makes that doesn't
|
|
262
|
+
* belong in the URL. In Effect code, use the verbs directly, and run
|
|
204
263
|
* the Effect on the registry with `run`.
|
|
205
264
|
*
|
|
206
265
|
* **Details**
|
package/dist/Loader.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { __exportAll } from "./_virtual/_rolldown/runtime.mjs";
|
|
|
2
2
|
import { hydrated } from "./Hydration.mjs";
|
|
3
3
|
import { isDevelopment } from "./internal/development.mjs";
|
|
4
4
|
import { keyOf, take } from "./internal/leases.mjs";
|
|
5
|
-
import { atomOf, loaded as loaded$1, make as make$1 } from "./internal/handles.mjs";
|
|
5
|
+
import { atomOf, inBrowser, loaded as loaded$1, make as make$1, sourceOf } from "./internal/handles.mjs";
|
|
6
6
|
import { afterSeed, pending } from "./internal/seeds.mjs";
|
|
7
7
|
import { isRequestRegistry } from "./RequestRegistry.mjs";
|
|
8
8
|
import * as Atom from "effect/reactivity/Atom";
|
|
@@ -31,6 +31,9 @@ import * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
|
31
31
|
* | `ensure` | if it isn't in the registry | only while it's `Initial` | returns it at once |
|
|
32
32
|
* | `fetch` | if it isn't, or refreshes it | until the new result settles | refreshes, then waits |
|
|
33
33
|
*
|
|
34
|
+
* `prefetchInBrowser` is `prefetch` in the browser. On the server, it
|
|
35
|
+
* starts nothing, and the browser's read starts the atom.
|
|
36
|
+
*
|
|
34
37
|
* Typed failures don't reject: `ensure` and `fetch` settle on them, and
|
|
35
38
|
* `result` returns the settled `AsyncResult`, so a loader can redirect on a
|
|
36
39
|
* tag and otherwise let a boundary render the failure. Only defects and
|
|
@@ -48,6 +51,7 @@ var Loader_exports = /* @__PURE__ */ __exportAll({
|
|
|
48
51
|
fetch: () => fetch,
|
|
49
52
|
make: () => make,
|
|
50
53
|
prefetch: () => prefetch,
|
|
54
|
+
prefetchInBrowser: () => prefetchInBrowser,
|
|
51
55
|
result: () => result
|
|
52
56
|
});
|
|
53
57
|
/**
|
|
@@ -70,14 +74,16 @@ const start = Effect.fnUntraced(function* (atom) {
|
|
|
70
74
|
const seed = pending(registry);
|
|
71
75
|
if (seed !== void 0) yield* Effect.promise(() => seed);
|
|
72
76
|
const request = isRequestRegistry(registry);
|
|
73
|
-
|
|
77
|
+
const source = sourceOf(atom);
|
|
78
|
+
if (request && !Atom.isSerializable(source) && (yield* isDevelopment)) return yield* Effect.die(/* @__PURE__ */ new Error(`raf-effect-atom: a loader started ${String(atom)}, which isn't serializable and doesn't point at a serializable atom with initialValueTarget, so the browser would request it again. Give it a serialization key, such as AtomRpc's serializationKey option.`));
|
|
74
79
|
const ttl = request ? void 0 : Duration.toMillis(yield* LeaseTTL);
|
|
75
|
-
const target = hydrated(
|
|
80
|
+
const target = hydrated(source);
|
|
76
81
|
const key = keyOf(target);
|
|
77
82
|
const existed = registry.getNodes().has(key) || Predicate.isString(key) && registry.preloadedSerializable.has(key);
|
|
78
83
|
take(registry, target, ttl);
|
|
79
84
|
return {
|
|
80
85
|
existed,
|
|
86
|
+
read: source === atom ? hydrated(atom) : atom,
|
|
81
87
|
registry,
|
|
82
88
|
request,
|
|
83
89
|
target
|
|
@@ -115,10 +121,24 @@ const rejectDefects = (result) => {
|
|
|
115
121
|
*
|
|
116
122
|
* **Details**
|
|
117
123
|
*
|
|
118
|
-
* Starts the atom if it isn't in the registry, and leaves it alone if it
|
|
119
|
-
* Takes a lease either way. The component that reads the handle
|
|
120
|
-
* until the result settles. On a request registry whose async
|
|
121
|
-
* running, it waits for the seed first.
|
|
124
|
+
* - Starts the atom if it isn't in the registry, and leaves it alone if it
|
|
125
|
+
* is. Takes a lease either way. The component that reads the handle
|
|
126
|
+
* suspends until the result settles. On a request registry whose async
|
|
127
|
+
* seed is still running, it waits for the seed first.
|
|
128
|
+
* - A derived atom that isn't serializable, such as an error boundary's
|
|
129
|
+
* `wrap(source)`, starts the first serializable atom its
|
|
130
|
+
* `initialValueTarget` chain points at, the one `useAtomResult` reads it
|
|
131
|
+
* through: the lease, the handle's key and what the server sends are
|
|
132
|
+
* that atom's. `ensure` and `fetch` wait for the derived atom's own
|
|
133
|
+
* result.
|
|
134
|
+
*
|
|
135
|
+
* **Gotchas**
|
|
136
|
+
*
|
|
137
|
+
* The server sends a result still loading as a promise only when the atom
|
|
138
|
+
* it sends holds an `AsyncResult`. A derived atom whose serializable atom
|
|
139
|
+
* holds another value, such as an `Option` snapshot, crosses with the value
|
|
140
|
+
* it has when the registry is dehydrated: point it at an `AsyncResult`
|
|
141
|
+
* atom, or `ensure` it.
|
|
122
142
|
*
|
|
123
143
|
* **Example**
|
|
124
144
|
*
|
|
@@ -138,6 +158,54 @@ const prefetch = (source, ...args) => Effect.gen(function* () {
|
|
|
138
158
|
yield* start(atom);
|
|
139
159
|
return make$1(args, atom);
|
|
140
160
|
});
|
|
161
|
+
/**
|
|
162
|
+
* Starts a source's atom in the browser, and returns its handle: on the
|
|
163
|
+
* server, it starts nothing, and the browser's read starts the atom.
|
|
164
|
+
*
|
|
165
|
+
* **When to use**
|
|
166
|
+
*
|
|
167
|
+
* Use for data only the browser can load: an input the server never sees,
|
|
168
|
+
* such as a token in the URL's fragment or a permission, or a request the
|
|
169
|
+
* server can't make. The rest of the page still renders on the server.
|
|
170
|
+
*
|
|
171
|
+
* **Details**
|
|
172
|
+
*
|
|
173
|
+
* - In the browser, it's `prefetch`: a loader that runs there, such as on a
|
|
174
|
+
* client navigation, starts the atom and takes a lease.
|
|
175
|
+
* - On a request registry, it returns a handle marked `inBrowser` without
|
|
176
|
+
* starting the atom, so the atom needs no serialization key. On the
|
|
177
|
+
* server, `useAtomResult` throws for that handle, so React renders the
|
|
178
|
+
* nearest Suspense boundary's fallback there and renders the boundary in
|
|
179
|
+
* the browser. There, the read starts the atom, and the waterfall check
|
|
180
|
+
* skips it, since a loader asked for it.
|
|
181
|
+
*
|
|
182
|
+
* **Gotchas**
|
|
183
|
+
*
|
|
184
|
+
* - Read the handle under a Suspense boundary: on the server, a read
|
|
185
|
+
* outside one fails the whole render. React reports the boundary it
|
|
186
|
+
* renders in the browser to `onRecoverableError`.
|
|
187
|
+
* - On a page the server rendered, the request starts when the browser
|
|
188
|
+
* renders the read, after hydration's JavaScript has loaded: start the
|
|
189
|
+
* data the server can load with `prefetch` or `ensure`.
|
|
190
|
+
*
|
|
191
|
+
* **Example**
|
|
192
|
+
*
|
|
193
|
+
* ```ts
|
|
194
|
+
* import { Loader } from "raf-effect-atom"
|
|
195
|
+
*
|
|
196
|
+
* const program = Loader.prefetchInBrowser(pushSubscription)
|
|
197
|
+
* ```
|
|
198
|
+
*
|
|
199
|
+
* @stability unstable
|
|
200
|
+
* @category verbs
|
|
201
|
+
* @since 0.2.6
|
|
202
|
+
*/
|
|
203
|
+
const prefetchInBrowser = (source, ...args) => Effect.gen(function* () {
|
|
204
|
+
const atom = atomOf(source, args);
|
|
205
|
+
if (isRequestRegistry(yield* AtomRegistry.AtomRegistry)) return inBrowser(args, atom);
|
|
206
|
+
yield* start(atom);
|
|
207
|
+
return make$1(args, atom);
|
|
208
|
+
});
|
|
141
209
|
const loaded = (args, atom, result) => {
|
|
142
210
|
const handle = loaded$1(args, atom);
|
|
143
211
|
results.set(handle, result);
|
|
@@ -181,7 +249,7 @@ const loaded = (args, atom, result) => {
|
|
|
181
249
|
const ensure = (source, ...args) => Effect.gen(function* () {
|
|
182
250
|
const atom = atomOf(source, args);
|
|
183
251
|
const started = yield* start(atom);
|
|
184
|
-
const result = yield* settled(started.registry, started.
|
|
252
|
+
const result = yield* settled(started.registry, started.read, started.request ? isSettled : AsyncResult.isNotInitial);
|
|
185
253
|
return loaded(args, atom, result);
|
|
186
254
|
});
|
|
187
255
|
/**
|
|
@@ -207,12 +275,12 @@ const ensure = (source, ...args) => Effect.gen(function* () {
|
|
|
207
275
|
const fetch = (source, ...args) => Effect.gen(function* () {
|
|
208
276
|
const atom = atomOf(source, args);
|
|
209
277
|
const started = yield* start(atom);
|
|
210
|
-
const { registry, target } = started;
|
|
211
|
-
if (started.existed && isSettled(registry.get(
|
|
278
|
+
const { read, registry, target } = started;
|
|
279
|
+
if (started.existed && isSettled(registry.get(read))) {
|
|
212
280
|
const node = registry.getNodes().get(keyOf(target));
|
|
213
|
-
registry.refresh(node?.atom ?? target);
|
|
281
|
+
registry.refresh(read === target ? node?.atom ?? target : read);
|
|
214
282
|
}
|
|
215
|
-
const result = yield* settled(registry,
|
|
283
|
+
const result = yield* settled(registry, read, isSettled);
|
|
216
284
|
return loaded(args, atom, result);
|
|
217
285
|
});
|
|
218
286
|
const results = /* @__PURE__ */ new WeakMap();
|
|
@@ -261,8 +329,8 @@ const either = (first, second) => first === void 0 || second === void 0 ? first
|
|
|
261
329
|
*
|
|
262
330
|
* Use in a framework integration, in loaders that are async functions, or
|
|
263
331
|
* in an event handler that starts an atom before the state change that
|
|
264
|
-
* renders its reader, for
|
|
265
|
-
*
|
|
332
|
+
* renders its reader, for a choice a click or a submit makes that doesn't
|
|
333
|
+
* belong in the URL. In Effect code, use the verbs directly, and run
|
|
266
334
|
* the Effect on the registry with `run`.
|
|
267
335
|
*
|
|
268
336
|
* **Details**
|
|
@@ -303,7 +371,15 @@ const make = (registry, options) => {
|
|
|
303
371
|
});
|
|
304
372
|
return make$1(args, atom);
|
|
305
373
|
},
|
|
374
|
+
prefetchInBrowser: (source, ...args) => {
|
|
375
|
+
const atom = atomOf(source, args);
|
|
376
|
+
if (isRequestRegistry(registry)) return inBrowser(args, atom);
|
|
377
|
+
afterSeed(registry, () => {
|
|
378
|
+
Effect.runSync(provide(start(atom)));
|
|
379
|
+
});
|
|
380
|
+
return make$1(args, atom);
|
|
381
|
+
},
|
|
306
382
|
run: (effect, runOptions) => runPromise(provide(effect), either(signal, runOptions?.signal))
|
|
307
383
|
};
|
|
308
384
|
};
|
|
309
|
-
export { LeaseTTL, Loader_exports, ensure, fetch, make, prefetch, result };
|
|
385
|
+
export { LeaseTTL, Loader_exports, ensure, fetch, make, prefetch, prefetchInBrowser, result };
|
package/dist/Next.d.mts
CHANGED
|
@@ -90,6 +90,15 @@ export declare const getRegistry: () => AtomRegistry.AtomRegistry;
|
|
|
90
90
|
* @since 0.1.0
|
|
91
91
|
*/
|
|
92
92
|
export declare const prefetch: <S extends Source>(source: S, ...args: ArgsOf<S>) => Handle<S>;
|
|
93
|
+
/**
|
|
94
|
+
* `Loader.prefetchInBrowser` on the request's registry: returns a handle
|
|
95
|
+
* whose atom the browser's read starts, without starting it on the server.
|
|
96
|
+
*
|
|
97
|
+
* @stability unstable
|
|
98
|
+
* @category verbs
|
|
99
|
+
* @since 0.2.6
|
|
100
|
+
*/
|
|
101
|
+
export declare const prefetchInBrowser: <S extends Source>(source: S, ...args: ArgsOf<S>) => Handle<S>;
|
|
93
102
|
/**
|
|
94
103
|
* `Loader.ensure` on the request's registry: starts a source's atom, waits
|
|
95
104
|
* for its result, and returns its handle.
|
package/dist/Next.mjs
CHANGED
|
@@ -72,6 +72,15 @@ const getRegistry = atoms.getRegistry;
|
|
|
72
72
|
*/
|
|
73
73
|
const prefetch = atoms.prefetch;
|
|
74
74
|
/**
|
|
75
|
+
* `Loader.prefetchInBrowser` on the request's registry: returns a handle
|
|
76
|
+
* whose atom the browser's read starts, without starting it on the server.
|
|
77
|
+
*
|
|
78
|
+
* @stability unstable
|
|
79
|
+
* @category verbs
|
|
80
|
+
* @since 0.2.6
|
|
81
|
+
*/
|
|
82
|
+
const prefetchInBrowser = atoms.prefetchInBrowser;
|
|
83
|
+
/**
|
|
75
84
|
* `Loader.ensure` on the request's registry: starts a source's atom, waits
|
|
76
85
|
* for its result, and returns its handle.
|
|
77
86
|
*
|
|
@@ -130,4 +139,4 @@ const run = atoms.run;
|
|
|
130
139
|
* @since 0.1.0
|
|
131
140
|
*/
|
|
132
141
|
const dehydrate = atoms.dehydrate;
|
|
133
|
-
export { HydrateAtoms, RegistryProvider, dehydrate, ensure, fetch, getRegistry, makeRequestAtoms, prefetch, run };
|
|
142
|
+
export { HydrateAtoms, RegistryProvider, dehydrate, ensure, fetch, getRegistry, makeRequestAtoms, prefetch, prefetchInBrowser, run };
|
package/dist/Waterfalls.d.mts
CHANGED
|
@@ -51,7 +51,7 @@ export declare const WaterfallDetection: (props: {
|
|
|
51
51
|
}) => React.ReactNode;
|
|
52
52
|
/**
|
|
53
53
|
* Starts a source's atom during render and returns its handle, for an input
|
|
54
|
-
* only a render knows, such as
|
|
54
|
+
* only a render knows, such as an id read from another atom's result.
|
|
55
55
|
*
|
|
56
56
|
* **When to use**
|
|
57
57
|
*
|
|
@@ -72,12 +72,9 @@ 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
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
* Pass it the source a loader would start, not an error boundary's
|
|
79
|
-
* `wrap(source)`: the wrapper's atoms aren't serializable, so the server
|
|
80
|
-
* couldn't send them to the browser. Read the handle through the wrapper.
|
|
75
|
+
* - A derived source, such as an error boundary's `wrap(source)`, starts
|
|
76
|
+
* the serializable atom its `initialValueTarget` chain points at, as
|
|
77
|
+
* `Loader.prefetch` does.
|
|
81
78
|
*
|
|
82
79
|
* **Example**
|
|
83
80
|
*
|
package/dist/Waterfalls.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { get } from "./internal/pending.mjs";
|
|
3
3
|
import { hydrated } from "./Hydration.mjs";
|
|
4
4
|
import { keyOf } from "./internal/leases.mjs";
|
|
5
|
-
import { atomOf, make } from "./internal/handles.mjs";
|
|
5
|
+
import { atomOf, make, sourceOf } from "./internal/handles.mjs";
|
|
6
6
|
import { prefetch } from "./Loader.mjs";
|
|
7
7
|
import { ModeContext, isStarted } from "./internal/waterfalls.mjs";
|
|
8
8
|
import * as Predicate from "effect/Predicate";
|
|
@@ -55,7 +55,7 @@ const WaterfallDetection = (props) => process.env.NODE_ENV === "production" ? pr
|
|
|
55
55
|
});
|
|
56
56
|
/**
|
|
57
57
|
* Starts a source's atom during render and returns its handle, for an input
|
|
58
|
-
* only a render knows, such as
|
|
58
|
+
* only a render knows, such as an id read from another atom's result.
|
|
59
59
|
*
|
|
60
60
|
* **When to use**
|
|
61
61
|
*
|
|
@@ -76,12 +76,9 @@ 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
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* Pass it the source a loader would start, not an error boundary's
|
|
83
|
-
* `wrap(source)`: the wrapper's atoms aren't serializable, so the server
|
|
84
|
-
* couldn't send them to the browser. Read the handle through the wrapper.
|
|
79
|
+
* - A derived source, such as an error boundary's `wrap(source)`, starts
|
|
80
|
+
* the serializable atom its `initialValueTarget` chain points at, as
|
|
81
|
+
* `Loader.prefetch` does.
|
|
85
82
|
*
|
|
86
83
|
* **Example**
|
|
87
84
|
*
|
|
@@ -103,7 +100,7 @@ const WaterfallDetection = (props) => process.env.NODE_ENV === "production" ? pr
|
|
|
103
100
|
const useWaterfall = (source, ...args) => {
|
|
104
101
|
const registry = React.useContext(RegistryContext);
|
|
105
102
|
const atom = atomOf(source, args);
|
|
106
|
-
const target = hydrated(atom);
|
|
103
|
+
const target = hydrated(sourceOf(atom));
|
|
107
104
|
const key = keyOf(target);
|
|
108
105
|
if (!(Predicate.isString(key) && get(registry, key) !== void 0) && !isStarted(registry, target)) return Effect.runSync(Effect.provideService(prefetch(source, ...args), AtomRegistry.AtomRegistry, registry));
|
|
109
106
|
return make(args, atom);
|
|
@@ -4,9 +4,18 @@ import * as Predicate from "effect/Predicate";
|
|
|
4
4
|
// src/internal/handles.ts
|
|
5
5
|
/** The atom a source makes for its arguments. */
|
|
6
6
|
const atomOf = (source, args) => Atom.isAtom(source) ? source : source(...args);
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* The atom the loader starts and the server sends for `atom`: the first
|
|
9
|
+
* serializable atom in its `initialValueTarget` chain, starting with `atom`.
|
|
10
|
+
* An AtomRpc query is serializable itself, though it points at an inner atom.
|
|
11
|
+
*/
|
|
12
|
+
const sourceOf = (atom) => {
|
|
13
|
+
for (let target = atom; target !== void 0; target = target.initialValueTarget) if (Atom.isSerializable(target)) return target;
|
|
14
|
+
return atom;
|
|
15
|
+
};
|
|
16
|
+
/** The registry key a handle records: its source atom's serialization key. */
|
|
8
17
|
const keyOf = (atom) => {
|
|
9
|
-
const key = keyOf$1(atom);
|
|
18
|
+
const key = keyOf$1(sourceOf(atom));
|
|
10
19
|
if (!Predicate.isString(key)) return;
|
|
11
20
|
return key;
|
|
12
21
|
};
|
|
@@ -14,9 +23,14 @@ const make = (args, atom) => ({
|
|
|
14
23
|
args,
|
|
15
24
|
key: keyOf(atom)
|
|
16
25
|
});
|
|
26
|
+
const inBrowser = (args, atom) => ({
|
|
27
|
+
args,
|
|
28
|
+
inBrowser: true,
|
|
29
|
+
key: keyOf(atom)
|
|
30
|
+
});
|
|
17
31
|
const loaded = (args, atom) => ({
|
|
18
32
|
args,
|
|
19
33
|
key: keyOf(atom),
|
|
20
34
|
loaded: true
|
|
21
35
|
});
|
|
22
|
-
export { atomOf, loaded, make };
|
|
36
|
+
export { atomOf, inBrowser, loaded, make, sourceOf };
|
package/dist/internal/next.mjs
CHANGED
|
@@ -23,6 +23,7 @@ const make = (options) => {
|
|
|
23
23
|
fetch: (source, ...args) => getLoader().fetch(source, ...args),
|
|
24
24
|
getRegistry,
|
|
25
25
|
prefetch: (source, ...args) => getLoader().prefetch(source, ...args),
|
|
26
|
+
prefetchInBrowser: (source, ...args) => getLoader().prefetchInBrowser(source, ...args),
|
|
26
27
|
run: (effect, runOptions) => getLoader().run(effect, runOptions)
|
|
27
28
|
};
|
|
28
29
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "raf-effect-atom",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
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",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"@types/react-dom": "19.3.0",
|
|
59
59
|
"@vitest/coverage-v8": "5.0.5",
|
|
60
60
|
"effect": "4.0.6",
|
|
61
|
-
"effect-atom-boundary": "0.1.
|
|
61
|
+
"effect-atom-boundary": "0.1.2",
|
|
62
62
|
"happy-dom": "21.0.4",
|
|
63
63
|
"react": "19.3.1",
|
|
64
64
|
"react-dom": "19.3.1",
|