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 +21 -0
- package/README.md +59 -2
- package/dist/Hooks.d.mts +68 -0
- package/dist/Hooks.mjs +119 -0
- package/dist/Hydration.d.mts +93 -0
- package/dist/Hydration.mjs +134 -0
- package/dist/Loader.d.mts +157 -0
- package/dist/Loader.mjs +232 -0
- package/dist/Next.d.mts +66 -0
- package/dist/Next.mjs +63 -0
- package/dist/NextClient.d.mts +33 -0
- package/dist/NextClient.mjs +45 -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 +98 -0
- package/dist/Waterfalls.mjs +107 -0
- package/dist/_virtual/_rolldown/runtime.mjs +12 -0
- package/dist/index.d.mts +7 -0
- package/dist/index.mjs +7 -0
- package/dist/internal/development.mjs +6 -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/Loader.mjs
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { __exportAll } from "./_virtual/_rolldown/runtime.mjs";
|
|
2
|
+
import { hydrated } from "./Hydration.mjs";
|
|
3
|
+
import { isDevelopment } from "./internal/development.mjs";
|
|
4
|
+
import { keyOf, take } from "./internal/leases.mjs";
|
|
5
|
+
import { isRequestRegistry } from "./RequestRegistry.mjs";
|
|
6
|
+
import * as Atom from "effect/reactivity/Atom";
|
|
7
|
+
import * as Predicate from "effect/Predicate";
|
|
8
|
+
import * as Cause from "effect/Cause";
|
|
9
|
+
import * as Context from "effect/Context";
|
|
10
|
+
import * as Duration from "effect/Duration";
|
|
11
|
+
import * as Effect from "effect/Effect";
|
|
12
|
+
import * as Option from "effect/Option";
|
|
13
|
+
import * as AsyncResult from "effect/reactivity/AsyncResult";
|
|
14
|
+
import * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
15
|
+
// src/Loader.ts
|
|
16
|
+
/**
|
|
17
|
+
* Starts atoms from route loaders, before anything renders, so nothing
|
|
18
|
+
* waterfalls.
|
|
19
|
+
*
|
|
20
|
+
* Each verb starts an `AsyncResult` atom in the registry and takes a lease
|
|
21
|
+
* on it, so the registry's idle TTL can't evict it before the route renders
|
|
22
|
+
* and `useAtomResult` takes it over:
|
|
23
|
+
*
|
|
24
|
+
* | Verb | Starts the atom | Waits | Has a value already |
|
|
25
|
+
* | ---------- | ------------------------------- | ----------------------------- | --------------------- |
|
|
26
|
+
* | `prefetch` | if it isn't in the registry | never | leaves it alone |
|
|
27
|
+
* | `ensure` | if it isn't in the registry | only while it's `Initial` | returns it at once |
|
|
28
|
+
* | `fetch` | if it isn't, or refreshes it | until the new result settles | refreshes, then waits |
|
|
29
|
+
*
|
|
30
|
+
* Typed failures don't reject: `ensure` and `fetch` return the settled
|
|
31
|
+
* `AsyncResult`, so a loader can redirect on a tag and otherwise let a
|
|
32
|
+
* boundary render the failure. Only defects and interruptions reject.
|
|
33
|
+
*
|
|
34
|
+
* The verbs are Effects that need the `AtomRegistry`; `make` gives the same
|
|
35
|
+
* verbs as Promises over one registry, for loaders that are async functions.
|
|
36
|
+
*
|
|
37
|
+
* @stability unstable
|
|
38
|
+
* @since 0.1.0
|
|
39
|
+
*/
|
|
40
|
+
var Loader_exports = /* @__PURE__ */ __exportAll({
|
|
41
|
+
LeaseTTL: () => LeaseTTL,
|
|
42
|
+
ensure: () => ensure,
|
|
43
|
+
fetch: () => fetch,
|
|
44
|
+
make: () => make,
|
|
45
|
+
prefetch: () => prefetch
|
|
46
|
+
});
|
|
47
|
+
/**
|
|
48
|
+
* How long a loader's lease keeps an atom mounted when no component takes it
|
|
49
|
+
* over. Default 30 seconds.
|
|
50
|
+
*
|
|
51
|
+
* **Details**
|
|
52
|
+
*
|
|
53
|
+
* A lease ends when `useAtomResult` mounts the atom, or when this time has
|
|
54
|
+
* passed, after which the registry's idle TTL applies as usual. On a request
|
|
55
|
+
* registry, leases last until the registry is disposed.
|
|
56
|
+
*
|
|
57
|
+
* @stability unstable
|
|
58
|
+
* @category references
|
|
59
|
+
* @since 0.1.0
|
|
60
|
+
*/
|
|
61
|
+
const LeaseTTL = Context.Reference("raf-effect-atom/Loader/LeaseTTL", { defaultValue: () => Duration.seconds(30) });
|
|
62
|
+
const start = Effect.fnUntraced(function* (atom) {
|
|
63
|
+
const registry = yield* AtomRegistry.AtomRegistry;
|
|
64
|
+
const request = isRequestRegistry(registry);
|
|
65
|
+
if (request && !Atom.isSerializable(atom) && (yield* isDevelopment)) return yield* Effect.die(/* @__PURE__ */ new Error(`raf-effect-atom: a loader started ${String(atom)}, which isn't serializable, so the browser would request it again. Give it a serialization key, such as AtomRpc's serializationKey option.`));
|
|
66
|
+
const ttl = request ? void 0 : Duration.toMillis(yield* LeaseTTL);
|
|
67
|
+
const target = hydrated(atom);
|
|
68
|
+
const key = keyOf(target);
|
|
69
|
+
const existed = registry.getNodes().has(key) || Predicate.isString(key) && registry.preloadedSerializable.has(key);
|
|
70
|
+
take(registry, target, ttl);
|
|
71
|
+
return {
|
|
72
|
+
existed,
|
|
73
|
+
registry,
|
|
74
|
+
request,
|
|
75
|
+
target
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
const isSettled = (result) => AsyncResult.isNotInitial(result) && !result.waiting;
|
|
79
|
+
const settled = (registry, atom, isDone) => Effect.callback((resume) => {
|
|
80
|
+
const current = registry.get(atom);
|
|
81
|
+
if (isDone(current)) {
|
|
82
|
+
resume(Effect.succeed(current));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const cancel = registry.subscribe(atom, (value) => {
|
|
86
|
+
if (isDone(value)) {
|
|
87
|
+
cancel();
|
|
88
|
+
resume(Effect.succeed(value));
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
return Effect.sync(cancel);
|
|
92
|
+
}).pipe(Effect.flatMap(rejectDefects));
|
|
93
|
+
const rejectDefects = (result) => {
|
|
94
|
+
if (AsyncResult.isFailure(result) && Option.isNone(Cause.findErrorOption(result.cause))) {
|
|
95
|
+
const defects = result.cause;
|
|
96
|
+
return Effect.failCause(defects);
|
|
97
|
+
}
|
|
98
|
+
return Effect.succeed(result);
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Starts an atom without waiting for it.
|
|
102
|
+
*
|
|
103
|
+
* **When to use**
|
|
104
|
+
*
|
|
105
|
+
* Use for data the route needs but its loader doesn't: it streams in while
|
|
106
|
+
* the route renders.
|
|
107
|
+
*
|
|
108
|
+
* **Details**
|
|
109
|
+
*
|
|
110
|
+
* Starts the atom if it isn't in the registry, and leaves it alone if it is.
|
|
111
|
+
* Takes a lease either way.
|
|
112
|
+
*
|
|
113
|
+
* **Example**
|
|
114
|
+
*
|
|
115
|
+
* ```ts
|
|
116
|
+
* import { Effect } from "effect"
|
|
117
|
+
* import { Loader } from "raf-effect-atom"
|
|
118
|
+
*
|
|
119
|
+
* const program = Loader.prefetch(comments(1))
|
|
120
|
+
* ```
|
|
121
|
+
*
|
|
122
|
+
* @stability unstable
|
|
123
|
+
* @category verbs
|
|
124
|
+
* @since 0.1.0
|
|
125
|
+
*/
|
|
126
|
+
const prefetch = Effect.fnUntraced(function* (atom) {
|
|
127
|
+
yield* start(atom);
|
|
128
|
+
});
|
|
129
|
+
/**
|
|
130
|
+
* Starts an atom and returns its settled result, waiting only while it has
|
|
131
|
+
* none.
|
|
132
|
+
*
|
|
133
|
+
* **When to use**
|
|
134
|
+
*
|
|
135
|
+
* Use when the loader needs the result, such as to redirect on a tag, or
|
|
136
|
+
* when the route shouldn't render without it.
|
|
137
|
+
*
|
|
138
|
+
* **Details**
|
|
139
|
+
*
|
|
140
|
+
* An atom with a result returns it at once, without refetching. On a request
|
|
141
|
+
* registry, it also waits out a refresh in flight, so the server never
|
|
142
|
+
* renders a result that's about to change. A typed failure is returned as
|
|
143
|
+
* the result; a defect or an interruption fails the Effect.
|
|
144
|
+
*
|
|
145
|
+
* **Example**
|
|
146
|
+
*
|
|
147
|
+
* ```ts
|
|
148
|
+
* import { Effect } from "effect"
|
|
149
|
+
* import { AsyncResult } from "effect/reactivity"
|
|
150
|
+
* import { Loader } from "raf-effect-atom"
|
|
151
|
+
*
|
|
152
|
+
* const program = Effect.gen(function* () {
|
|
153
|
+
* const result = yield* Loader.ensure(issue(1))
|
|
154
|
+
* return AsyncResult.isSuccess(result) ? result.value.title : "missing"
|
|
155
|
+
* })
|
|
156
|
+
* ```
|
|
157
|
+
*
|
|
158
|
+
* @stability unstable
|
|
159
|
+
* @category verbs
|
|
160
|
+
* @since 0.1.0
|
|
161
|
+
*/
|
|
162
|
+
const ensure = Effect.fnUntraced(function* (atom) {
|
|
163
|
+
const started = yield* start(atom);
|
|
164
|
+
return yield* settled(started.registry, started.target, started.request ? isSettled : AsyncResult.isNotInitial);
|
|
165
|
+
});
|
|
166
|
+
/**
|
|
167
|
+
* Starts an atom, or refreshes it if it has a result, and returns the new
|
|
168
|
+
* settled result.
|
|
169
|
+
*
|
|
170
|
+
* **When to use**
|
|
171
|
+
*
|
|
172
|
+
* Use when the loader must not see a result from before it ran, such as
|
|
173
|
+
* after a mutation.
|
|
174
|
+
*
|
|
175
|
+
* **Details**
|
|
176
|
+
*
|
|
177
|
+
* Waits until the result settles and isn't refreshing. A typed failure is
|
|
178
|
+
* returned as the result; a defect or an interruption fails the Effect.
|
|
179
|
+
*
|
|
180
|
+
* @stability unstable
|
|
181
|
+
* @category verbs
|
|
182
|
+
* @since 0.1.0
|
|
183
|
+
*/
|
|
184
|
+
const fetch = Effect.fnUntraced(function* (atom) {
|
|
185
|
+
const started = yield* start(atom);
|
|
186
|
+
const { registry, target } = started;
|
|
187
|
+
if (started.existed && isSettled(registry.get(target))) {
|
|
188
|
+
const node = registry.getNodes().get(keyOf(target));
|
|
189
|
+
registry.refresh(node?.atom ?? target);
|
|
190
|
+
}
|
|
191
|
+
return yield* settled(registry, target, isSettled);
|
|
192
|
+
});
|
|
193
|
+
/**
|
|
194
|
+
* Creates the Promise verbs over one registry.
|
|
195
|
+
*
|
|
196
|
+
* **When to use**
|
|
197
|
+
*
|
|
198
|
+
* Use in a framework integration, in loaders that are async functions, or
|
|
199
|
+
* in an event handler that starts an atom before the state change that
|
|
200
|
+
* renders its reader, for state that can't be in the URL, such as a
|
|
201
|
+
* search-as-you-type query. In Effect code, use
|
|
202
|
+
* the verbs directly with the `AtomRegistry` provided.
|
|
203
|
+
*
|
|
204
|
+
* **Example**
|
|
205
|
+
*
|
|
206
|
+
* ```ts
|
|
207
|
+
* import { Loader, RequestRegistry } from "raf-effect-atom"
|
|
208
|
+
*
|
|
209
|
+
* const loader = Loader.make(RequestRegistry.make())
|
|
210
|
+
* loader.prefetch(comments(1))
|
|
211
|
+
* const result = await loader.ensure(issue(1))
|
|
212
|
+
* ```
|
|
213
|
+
*
|
|
214
|
+
* @stability unstable
|
|
215
|
+
* @category constructors
|
|
216
|
+
* @since 0.1.0
|
|
217
|
+
*/
|
|
218
|
+
const make = (registry, options) => {
|
|
219
|
+
const leaseTTL = options?.leaseTTL === void 0 ? void 0 : Duration.fromInputUnsafe(options.leaseTTL);
|
|
220
|
+
const provide = (effect) => {
|
|
221
|
+
const provided = Effect.provideService(effect, AtomRegistry.AtomRegistry, registry);
|
|
222
|
+
return leaseTTL === void 0 ? provided : Effect.provideService(provided, LeaseTTL, leaseTTL);
|
|
223
|
+
};
|
|
224
|
+
return {
|
|
225
|
+
ensure: (atom) => Effect.runPromise(provide(ensure(atom))),
|
|
226
|
+
fetch: (atom) => Effect.runPromise(provide(fetch(atom))),
|
|
227
|
+
prefetch: (atom) => {
|
|
228
|
+
Effect.runSync(provide(prefetch(atom)));
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
};
|
|
232
|
+
export { LeaseTTL, Loader_exports, ensure, fetch, make, prefetch };
|
package/dist/Next.d.mts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { SettledResult } from "./Loader.mjs";
|
|
2
|
+
import { HydrateAtoms } from "./NextClient.mjs";
|
|
3
|
+
import { RegistryProvider } from "./RegistryProvider.mjs";
|
|
4
|
+
import * as Atom from "effect/reactivity/Atom";
|
|
5
|
+
import * as EffectHydration from "effect/reactivity/Hydration";
|
|
6
|
+
import * as AsyncResult from "effect/reactivity/AsyncResult";
|
|
7
|
+
import * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
8
|
+
/**
|
|
9
|
+
* Returns the request registry of the server render in progress.
|
|
10
|
+
*
|
|
11
|
+
* **Details**
|
|
12
|
+
*
|
|
13
|
+
* Made once per request through React's `cache`, and disposed through
|
|
14
|
+
* `cacheSignal` when React finishes the render, after the last streamed
|
|
15
|
+
* result has been sent.
|
|
16
|
+
*
|
|
17
|
+
* **Gotchas**
|
|
18
|
+
*
|
|
19
|
+
* Only server components share it: outside a server component render,
|
|
20
|
+
* `cache` doesn't cache, so each call makes a new registry.
|
|
21
|
+
*
|
|
22
|
+
* @stability unstable
|
|
23
|
+
* @category registry
|
|
24
|
+
* @since 0.1.0
|
|
25
|
+
*/
|
|
26
|
+
export declare const getRegistry: () => AtomRegistry.AtomRegistry;
|
|
27
|
+
/**
|
|
28
|
+
* `Loader.prefetch` on the request's registry: starts an atom, to stream it.
|
|
29
|
+
*
|
|
30
|
+
* @stability unstable
|
|
31
|
+
* @category verbs
|
|
32
|
+
* @since 0.1.0
|
|
33
|
+
*/
|
|
34
|
+
export declare const prefetch: <A, E>(atom: Atom.Atom<AsyncResult.AsyncResult<A, E>>) => void;
|
|
35
|
+
/**
|
|
36
|
+
* `Loader.ensure` on the request's registry: starts an atom and returns its
|
|
37
|
+
* settled result.
|
|
38
|
+
*
|
|
39
|
+
* @stability unstable
|
|
40
|
+
* @category verbs
|
|
41
|
+
* @since 0.1.0
|
|
42
|
+
*/
|
|
43
|
+
export declare const ensure: <A, E>(atom: Atom.Atom<AsyncResult.AsyncResult<A, E>>) => Promise<SettledResult<A, E>>;
|
|
44
|
+
/**
|
|
45
|
+
* `Loader.fetch` on the request's registry: starts or refreshes an atom and
|
|
46
|
+
* returns the new settled result.
|
|
47
|
+
*
|
|
48
|
+
* @stability unstable
|
|
49
|
+
* @category verbs
|
|
50
|
+
* @since 0.1.0
|
|
51
|
+
*/
|
|
52
|
+
export declare const fetch: <A, E>(atom: Atom.Atom<AsyncResult.AsyncResult<A, E>>) => Promise<SettledResult<A, E>>;
|
|
53
|
+
/**
|
|
54
|
+
* `Hydration.dehydrate` on the request's registry, for `HydrateAtoms`.
|
|
55
|
+
*
|
|
56
|
+
* **Gotchas**
|
|
57
|
+
*
|
|
58
|
+
* Includes the atoms started so far, so call it after the server component's
|
|
59
|
+
* verbs. Each `HydrateAtoms` hydrates what its own `dehydrate()` saw.
|
|
60
|
+
*
|
|
61
|
+
* @stability unstable
|
|
62
|
+
* @category dehydration
|
|
63
|
+
* @since 0.1.0
|
|
64
|
+
*/
|
|
65
|
+
export declare const dehydrate: () => Array<EffectHydration.DehydratedAtom>;
|
|
66
|
+
export { HydrateAtoms, RegistryProvider };
|
package/dist/Next.mjs
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
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 an atom, to stream it.
|
|
26
|
+
*
|
|
27
|
+
* @stability unstable
|
|
28
|
+
* @category verbs
|
|
29
|
+
* @since 0.1.0
|
|
30
|
+
*/
|
|
31
|
+
const prefetch = prefetch$1;
|
|
32
|
+
/**
|
|
33
|
+
* `Loader.ensure` on the request's registry: starts an atom and returns its
|
|
34
|
+
* settled result.
|
|
35
|
+
*
|
|
36
|
+
* @stability unstable
|
|
37
|
+
* @category verbs
|
|
38
|
+
* @since 0.1.0
|
|
39
|
+
*/
|
|
40
|
+
const ensure = ensure$1;
|
|
41
|
+
/**
|
|
42
|
+
* `Loader.fetch` on the request's registry: starts or refreshes an atom and
|
|
43
|
+
* returns the new settled result.
|
|
44
|
+
*
|
|
45
|
+
* @stability unstable
|
|
46
|
+
* @category verbs
|
|
47
|
+
* @since 0.1.0
|
|
48
|
+
*/
|
|
49
|
+
const fetch = fetch$1;
|
|
50
|
+
/**
|
|
51
|
+
* `Hydration.dehydrate` on the request's registry, for `HydrateAtoms`.
|
|
52
|
+
*
|
|
53
|
+
* **Gotchas**
|
|
54
|
+
*
|
|
55
|
+
* Includes the atoms started so far, so call it after the server component's
|
|
56
|
+
* verbs. Each `HydrateAtoms` hydrates what its own `dehydrate()` saw.
|
|
57
|
+
*
|
|
58
|
+
* @stability unstable
|
|
59
|
+
* @category dehydration
|
|
60
|
+
* @since 0.1.0
|
|
61
|
+
*/
|
|
62
|
+
const dehydrate = dehydrate$1;
|
|
63
|
+
export { HydrateAtoms, RegistryProvider, dehydrate, ensure, fetch, getRegistry, prefetch };
|
|
@@ -0,0 +1,33 @@
|
|
|
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 id = Number((await props.params).id)
|
|
20
|
+
* await ensure(issue(id))
|
|
21
|
+
* return (
|
|
22
|
+
* <HydrateAtoms state={dehydrate()}>
|
|
23
|
+
* <Issue id={id} />
|
|
24
|
+
* </HydrateAtoms>
|
|
25
|
+
* )
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @stability unstable
|
|
30
|
+
* @category components
|
|
31
|
+
* @since 0.1.0
|
|
32
|
+
*/
|
|
33
|
+
export declare const HydrateAtoms: (props: HydrationBoundaryProps) => React.ReactNode;
|
|
@@ -0,0 +1,45 @@
|
|
|
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 id = Number((await props.params).id)
|
|
28
|
+
* await ensure(issue(id))
|
|
29
|
+
* return (
|
|
30
|
+
* <HydrateAtoms state={dehydrate()}>
|
|
31
|
+
* <Issue id={id} />
|
|
32
|
+
* </HydrateAtoms>
|
|
33
|
+
* )
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @stability unstable
|
|
38
|
+
* @category components
|
|
39
|
+
* @since 0.1.0
|
|
40
|
+
*/
|
|
41
|
+
const HydrateAtoms = (props) => /* @__PURE__ */ jsx(HydrationBoundary, {
|
|
42
|
+
state: props.state,
|
|
43
|
+
children: props.children
|
|
44
|
+
});
|
|
45
|
+
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 id={id} />
|
|
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 id={id} />
|
|
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 { Loader } from "./Loader.mjs";
|
|
2
|
+
import { RegistryProvider } from "./RegistryProvider.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 };
|