raf-effect-atom 0.0.0 → 0.2.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.
@@ -1,7 +1,7 @@
1
1
  import { HydrationBoundaryProps } from "./ReactHydration.mjs";
2
2
  import * as React from "react";
3
3
  /**
4
- * Hydrates the state a server component dehydrated with `dehydrate()`,
4
+ * Hydrates the state a server component dehydrated with `await dehydrate()`,
5
5
  * streamed results included.
6
6
  *
7
7
  * **Details**
@@ -16,11 +16,10 @@ import * as React from "react";
16
16
  * import { dehydrate, ensure, HydrateAtoms } from "raf-effect-atom/next"
17
17
  *
18
18
  * export default async function IssuePage(props: { params: Promise<{ id: string }> }) {
19
- * const id = Number((await props.params).id)
20
- * await ensure(issue(id))
19
+ * const handle = await ensure(issue, Number((await props.params).id))
21
20
  * return (
22
- * <HydrateAtoms state={dehydrate()}>
23
- * <Issue id={id} />
21
+ * <HydrateAtoms state={await dehydrate()}>
22
+ * <Issue issue={handle} />
24
23
  * </HydrateAtoms>
25
24
  * )
26
25
  * }
@@ -9,7 +9,7 @@ import { jsx } from "react/jsx-runtime";
9
9
  * @since 0.1.0
10
10
  */
11
11
  /**
12
- * Hydrates the state a server component dehydrated with `dehydrate()`,
12
+ * Hydrates the state a server component dehydrated with `await dehydrate()`,
13
13
  * streamed results included.
14
14
  *
15
15
  * **Details**
@@ -24,11 +24,10 @@ import { jsx } from "react/jsx-runtime";
24
24
  * import { dehydrate, ensure, HydrateAtoms } from "raf-effect-atom/next"
25
25
  *
26
26
  * export default async function IssuePage(props: { params: Promise<{ id: string }> }) {
27
- * const id = Number((await props.params).id)
28
- * await ensure(issue(id))
27
+ * const handle = await ensure(issue, Number((await props.params).id))
29
28
  * return (
30
- * <HydrateAtoms state={dehydrate()}>
31
- * <Issue id={id} />
29
+ * <HydrateAtoms state={await dehydrate()}>
30
+ * <Issue issue={handle} />
32
31
  * </HydrateAtoms>
33
32
  * )
34
33
  * }
@@ -41,7 +41,7 @@ export interface HydrationBoundaryProps {
41
41
  * import { HydrationBoundary } from "raf-effect-atom"
42
42
  *
43
43
  * <HydrationBoundary state={loaderData.atoms}>
44
- * <IssuePage id={id} />
44
+ * <IssuePage issue={loaderData.issue} />
45
45
  * </HydrationBoundary>
46
46
  * ```
47
47
  *
@@ -42,7 +42,7 @@ import { jsx } from "react/jsx-runtime";
42
42
  * import { HydrationBoundary } from "raf-effect-atom"
43
43
  *
44
44
  * <HydrationBoundary state={loaderData.atoms}>
45
- * <IssuePage id={id} />
45
+ * <IssuePage issue={loaderData.issue} />
46
46
  * </HydrationBoundary>
47
47
  * ```
48
48
  *
@@ -36,6 +36,22 @@ export interface MiddlewareArgs {
36
36
  readonly context: {
37
37
  readonly set: (context: AtomsContext, value: RequestAtoms) => void;
38
38
  };
39
+ readonly request: Request;
40
+ }
41
+ /**
42
+ * Options for `atomsMiddleware`.
43
+ *
44
+ * @stability unstable
45
+ * @category models
46
+ * @since 0.2.0
47
+ */
48
+ export interface AtomsMiddlewareOptions {
49
+ /**
50
+ * Runs on each request's registry before the loaders, with the request,
51
+ * to set what its atoms need, such as its cookie. The middleware waits
52
+ * for one that returns a promise.
53
+ */
54
+ readonly seed?: ((registry: AtomRegistry.AtomRegistry, request: Request) => void | Promise<void>) | undefined;
39
55
  }
40
56
  /**
41
57
  * Creates a root middleware that gives each server request its own
@@ -49,15 +65,30 @@ export interface MiddlewareArgs {
49
65
  * then. When `next` doesn't return a `Response`, as Data Mode's
50
66
  * `query` can, the registry is left to be collected with the request.
51
67
  *
68
+ * `seed` runs on the new registry before `next`, so every loader's atoms
69
+ * start with what it sets.
70
+ *
52
71
  * **Gotchas**
53
72
  *
54
73
  * It's server middleware: export it as the root route's `middleware`, not
55
74
  * `clientMiddleware`. In the browser, the root's `RegistryProvider` holds
56
75
  * the registry.
57
76
  *
77
+ * **Example**
78
+ *
79
+ * ```ts
80
+ * export const middleware = [
81
+ * atomsMiddleware(atomsContext, {
82
+ * // In-process RPC calls send the page request's cookie.
83
+ * seed: (registry, request) =>
84
+ * registry.set(requestCookie, request.headers.get("cookie") ?? undefined)
85
+ * })
86
+ * ]
87
+ * ```
88
+ *
58
89
  * @stability unstable
59
90
  * @category integrations
60
91
  * @since 0.1.0
61
92
  */
62
- export declare const atomsMiddleware: (context: AtomsContext) => <Result>(args: MiddlewareArgs, next: () => Promise<Result>) => Promise<Result>;
93
+ export declare const atomsMiddleware: (context: AtomsContext, options?: AtomsMiddlewareOptions) => <Result>(args: MiddlewareArgs, next: () => Promise<Result>) => Promise<Result>;
63
94
  export { RegistryProvider };
@@ -1,5 +1,5 @@
1
1
  import { dehydrate } from "./Hydration.mjs";
2
- import { make } from "./RequestRegistry.mjs";
2
+ import { make, settled } from "./RequestRegistry.mjs";
3
3
  import { make as make$1 } from "./Loader.mjs";
4
4
  import { RegistryProvider } from "./RegistryProvider.mjs";
5
5
  import * as Predicate from "effect/Predicate";
@@ -30,14 +30,14 @@ import * as Predicate from "effect/Predicate";
30
30
  * // app/routes/issue.tsx
31
31
  * export async function loader(args: Route.LoaderArgs) {
32
32
  * const atoms = args.context.get(atomsContext)
33
- * await atoms.ensure(issue(Number(args.params.id)))
34
- * return { atoms: atoms.dehydrate() }
33
+ * const handle = await atoms.ensure(issue, Number(args.params.id))
34
+ * return { atoms: atoms.dehydrate(), issue: handle }
35
35
  * }
36
36
  *
37
37
  * export default function IssueRoute(props: Route.ComponentProps) {
38
38
  * return (
39
39
  * <HydrationBoundary state={props.loaderData.atoms}>
40
- * <Issue id={Number(props.params.id)} />
40
+ * <Issue issue={props.loaderData.issue} />
41
41
  * </HydrationBoundary>
42
42
  * )
43
43
  * }
@@ -58,18 +58,40 @@ import * as Predicate from "effect/Predicate";
58
58
  * then. When `next` doesn't return a `Response`, as Data Mode's
59
59
  * `query` can, the registry is left to be collected with the request.
60
60
  *
61
+ * `seed` runs on the new registry before `next`, so every loader's atoms
62
+ * start with what it sets.
63
+ *
61
64
  * **Gotchas**
62
65
  *
63
66
  * It's server middleware: export it as the root route's `middleware`, not
64
67
  * `clientMiddleware`. In the browser, the root's `RegistryProvider` holds
65
68
  * the registry.
66
69
  *
70
+ * **Example**
71
+ *
72
+ * ```ts
73
+ * export const middleware = [
74
+ * atomsMiddleware(atomsContext, {
75
+ * // In-process RPC calls send the page request's cookie.
76
+ * seed: (registry, request) =>
77
+ * registry.set(requestCookie, request.headers.get("cookie") ?? undefined)
78
+ * })
79
+ * ]
80
+ * ```
81
+ *
67
82
  * @stability unstable
68
83
  * @category integrations
69
84
  * @since 0.1.0
70
85
  */
71
- const atomsMiddleware = (context) => async (args, next) => {
72
- const registry = make();
86
+ const atomsMiddleware = (context, options) => async (args, next) => {
87
+ const seed = options?.seed;
88
+ const registry = make({ seed: seed === void 0 ? void 0 : (created) => seed(created, args.request) });
89
+ try {
90
+ await settled(registry);
91
+ } catch (error) {
92
+ registry.dispose();
93
+ throw error;
94
+ }
73
95
  const loader = make$1(registry);
74
96
  args.context.set(context, {
75
97
  ...loader,
@@ -1,32 +1,88 @@
1
+ import { Seed as Seed$1 } from "./internal/seeds.mjs";
1
2
  import * as AtomRegistry from "effect/reactivity/AtomRegistry";
2
3
  import * as Layer from "effect/Layer";
3
4
  declare namespace RequestRegistry_d_exports {
4
- export { isRequestRegistry, layer, make };
5
+ export { Options, Seed, isRequestRegistry, layer, make, settled };
6
+ }
7
+ /**
8
+ * Prepares a request registry before the loader verbs start atoms in it,
9
+ * such as by setting an atom to the request's cookie.
10
+ *
11
+ * @stability unstable
12
+ * @category models
13
+ * @since 0.2.0
14
+ */
15
+ export type Seed = Seed$1;
16
+ /**
17
+ * Options for `make`.
18
+ *
19
+ * @stability unstable
20
+ * @category models
21
+ * @since 0.2.0
22
+ */
23
+ export interface Options {
24
+ /**
25
+ * Runs on the new registry before any verb starts an atom in it. When it
26
+ * returns a promise, `ensure` and `fetch` wait for it, and `prefetch`
27
+ * returns its handle at once and starts the atom once it resolves.
28
+ */
29
+ readonly seed?: Seed | undefined;
5
30
  }
6
31
  /**
7
32
  * Creates the registry for one server request.
8
33
  *
9
34
  * **Details**
10
35
  *
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.
36
+ * - It's an ordinary `AtomRegistry`, marked as a request registry. On a
37
+ * request registry, `ensure` waits like `fetch` does, nothing revalidates
38
+ * in the background, and a loader's lease lasts until the registry is
39
+ * disposed. In development, the loader verbs refuse an atom that isn't
40
+ * serializable, because the browser would request it again.
41
+ * - `seed` sets what the request's atoms need before they run, such as the
42
+ * page request's cookie for an RPC protocol that forwards it. It can be
43
+ * async, for request data only a promise gives, such as Next.js's
44
+ * `headers()`.
45
+ *
46
+ * **Gotchas**
47
+ *
48
+ * With an async seed, a `prefetch` before it settles starts its atom later,
49
+ * so `Hydration.dehydrate` would miss it: wait for `settled` first, as the
50
+ * framework integrations do.
16
51
  *
17
52
  * **Example**
18
53
  *
19
54
  * ```ts
20
55
  * import { RequestRegistry } from "raf-effect-atom"
21
56
  *
22
- * const registry = RequestRegistry.make()
57
+ * const registry = RequestRegistry.make({
58
+ * seed: (registry) => registry.set(requestCookie, request.headers.get("cookie") ?? undefined)
59
+ * })
23
60
  * ```
24
61
  *
25
62
  * @stability unstable
26
63
  * @category constructors
27
64
  * @since 0.1.0
28
65
  */
29
- export declare const make: () => AtomRegistry.AtomRegistry;
66
+ export declare const make: (options?: Options) => AtomRegistry.AtomRegistry;
67
+ /**
68
+ * Waits until a request registry's seed has settled and every atom a verb
69
+ * started while it ran has started.
70
+ *
71
+ * **When to use**
72
+ *
73
+ * Use before `Hydration.dehydrate` on a registry with an async seed, outside
74
+ * the framework integrations, which call it for you.
75
+ *
76
+ * **Details**
77
+ *
78
+ * Resolves at once for a registry without a seed, or whose seed was
79
+ * synchronous. Rejects with the seed's error.
80
+ *
81
+ * @stability unstable
82
+ * @category getters
83
+ * @since 0.2.0
84
+ */
85
+ export declare const settled: (registry: AtomRegistry.AtomRegistry) => Promise<void>;
30
86
  /**
31
87
  * A layer that provides a request registry as the `AtomRegistry`, disposed
32
88
  * when the layer's scope closes.
@@ -1,4 +1,5 @@
1
1
  import { __exportAll } from "./_virtual/_rolldown/runtime.mjs";
2
+ import { run, settled as settled$1 } from "./internal/seeds.mjs";
2
3
  import * as Effect from "effect/Effect";
3
4
  import * as AtomRegistry from "effect/reactivity/AtomRegistry";
4
5
  import * as Layer from "effect/Layer";
@@ -18,7 +19,8 @@ import * as Layer from "effect/Layer";
18
19
  var RequestRegistry_exports = /* @__PURE__ */ __exportAll({
19
20
  isRequestRegistry: () => isRequestRegistry,
20
21
  layer: () => layer,
21
- make: () => make
22
+ make: () => make,
23
+ settled: () => settled
22
24
  });
23
25
  const requestRegistries = /* @__PURE__ */ new WeakSet();
24
26
  /**
@@ -26,30 +28,62 @@ const requestRegistries = /* @__PURE__ */ new WeakSet();
26
28
  *
27
29
  * **Details**
28
30
  *
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.
31
+ * - It's an ordinary `AtomRegistry`, marked as a request registry. On a
32
+ * request registry, `ensure` waits like `fetch` does, nothing revalidates
33
+ * in the background, and a loader's lease lasts until the registry is
34
+ * disposed. In development, the loader verbs refuse an atom that isn't
35
+ * serializable, because the browser would request it again.
36
+ * - `seed` sets what the request's atoms need before they run, such as the
37
+ * page request's cookie for an RPC protocol that forwards it. It can be
38
+ * async, for request data only a promise gives, such as Next.js's
39
+ * `headers()`.
40
+ *
41
+ * **Gotchas**
42
+ *
43
+ * With an async seed, a `prefetch` before it settles starts its atom later,
44
+ * so `Hydration.dehydrate` would miss it: wait for `settled` first, as the
45
+ * framework integrations do.
34
46
  *
35
47
  * **Example**
36
48
  *
37
49
  * ```ts
38
50
  * import { RequestRegistry } from "raf-effect-atom"
39
51
  *
40
- * const registry = RequestRegistry.make()
52
+ * const registry = RequestRegistry.make({
53
+ * seed: (registry) => registry.set(requestCookie, request.headers.get("cookie") ?? undefined)
54
+ * })
41
55
  * ```
42
56
  *
43
57
  * @stability unstable
44
58
  * @category constructors
45
59
  * @since 0.1.0
46
60
  */
47
- const make = () => {
61
+ const make = (options) => {
48
62
  const registry = AtomRegistry.make();
49
63
  requestRegistries.add(registry);
64
+ if (options?.seed !== void 0) run(registry, options.seed);
50
65
  return registry;
51
66
  };
52
67
  /**
68
+ * Waits until a request registry's seed has settled and every atom a verb
69
+ * started while it ran has started.
70
+ *
71
+ * **When to use**
72
+ *
73
+ * Use before `Hydration.dehydrate` on a registry with an async seed, outside
74
+ * the framework integrations, which call it for you.
75
+ *
76
+ * **Details**
77
+ *
78
+ * Resolves at once for a registry without a seed, or whose seed was
79
+ * synchronous. Rejects with the seed's error.
80
+ *
81
+ * @stability unstable
82
+ * @category getters
83
+ * @since 0.2.0
84
+ */
85
+ const settled = (registry) => settled$1(registry);
86
+ /**
53
87
  * A layer that provides a request registry as the `AtomRegistry`, disposed
54
88
  * when the layer's scope closes.
55
89
  *
@@ -73,4 +107,4 @@ const layer = Layer.effect(AtomRegistry.AtomRegistry, Effect.acquireRelease(Effe
73
107
  * @since 0.1.0
74
108
  */
75
109
  const isRequestRegistry = (registry) => requestRegistries.has(registry);
76
- export { RequestRegistry_exports, isRequestRegistry, layer, make };
110
+ export { RequestRegistry_exports, isRequestRegistry, layer, make, settled };
@@ -1,3 +1,4 @@
1
+ import { Seed } from "./RequestRegistry.mjs";
1
2
  import * as EffectHydration from "effect/reactivity/Hydration";
2
3
  import * as AtomRegistry from "effect/reactivity/AtomRegistry";
3
4
  import { AnyRouter } from "@tanstack/react-router";
@@ -38,14 +39,23 @@ export interface RouterAtomsOptions<TRouter extends AnyRouter> {
38
39
  * default registry does. An atom's own `Atom.setIdleTTL` or
39
40
  * `Atom.keepAlive` wins. The server's request registry lives for one
40
41
  * request, so it has none.
42
+ * - `seed` runs on the server's request registry before the loaders start
43
+ * atoms in it, as `RequestRegistry.make`'s does: set what the request's
44
+ * atoms need, such as its cookie. It can be async. The browser's registry
45
+ * isn't seeded.
41
46
  *
42
47
  * **Example**
43
48
  *
44
49
  * ```ts
50
+ * import { getRequestHeader } from "@tanstack/react-start/server"
45
51
  * import { makeRegistry } from "raf-effect-atom/tanstack-router"
46
52
  *
47
- * // Keeps an issue the user navigated away from for a minute.
48
- * const registry = makeRegistry({ defaultIdleTTL: 60_000 })
53
+ * const registry = makeRegistry({
54
+ * // Keeps an issue the user navigated away from for a minute.
55
+ * defaultIdleTTL: 60_000,
56
+ * // In-process RPC calls send the page request's cookie.
57
+ * seed: (registry) => registry.set(requestCookie, getRequestHeader("cookie"))
58
+ * })
49
59
  * ```
50
60
  *
51
61
  * @stability unstable
@@ -54,6 +64,7 @@ export interface RouterAtomsOptions<TRouter extends AnyRouter> {
54
64
  */
55
65
  export declare const makeRegistry: (options?: {
56
66
  readonly defaultIdleTTL?: number | undefined;
67
+ readonly seed?: Seed | undefined;
57
68
  }) => AtomRegistry.AtomRegistry;
58
69
  /**
59
70
  * Provides a registry to a TanStack router's app and streams it from the
@@ -1,5 +1,5 @@
1
1
  import { dehydrate, hydrate } from "./Hydration.mjs";
2
- import { isRequestRegistry } from "./RequestRegistry.mjs";
2
+ import { isRequestRegistry, settled } from "./RequestRegistry.mjs";
3
3
  import { make } from "./internal/registry.mjs";
4
4
  import * as Predicate from "effect/Predicate";
5
5
  import { RegistryContext } from "@effect/atom-react";
@@ -51,14 +51,23 @@ import { jsx } from "react/jsx-runtime";
51
51
  * default registry does. An atom's own `Atom.setIdleTTL` or
52
52
  * `Atom.keepAlive` wins. The server's request registry lives for one
53
53
  * request, so it has none.
54
+ * - `seed` runs on the server's request registry before the loaders start
55
+ * atoms in it, as `RequestRegistry.make`'s does: set what the request's
56
+ * atoms need, such as its cookie. It can be async. The browser's registry
57
+ * isn't seeded.
54
58
  *
55
59
  * **Example**
56
60
  *
57
61
  * ```ts
62
+ * import { getRequestHeader } from "@tanstack/react-start/server"
58
63
  * import { makeRegistry } from "raf-effect-atom/tanstack-router"
59
64
  *
60
- * // Keeps an issue the user navigated away from for a minute.
61
- * const registry = makeRegistry({ defaultIdleTTL: 60_000 })
65
+ * const registry = makeRegistry({
66
+ * // Keeps an issue the user navigated away from for a minute.
67
+ * defaultIdleTTL: 60_000,
68
+ * // In-process RPC calls send the page request's cookie.
69
+ * seed: (registry) => registry.set(requestCookie, getRequestHeader("cookie"))
70
+ * })
62
71
  * ```
63
72
  *
64
73
  * @stability unstable
@@ -116,6 +125,7 @@ const setupRouterAtoms = (options) => {
116
125
  const dehydrate$1 = router.options.dehydrate;
117
126
  router.options.dehydrate = async () => {
118
127
  const dehydrated = await dehydrate$1?.();
128
+ await settled(registry);
119
129
  if (disposed) return dehydrated;
120
130
  const atoms = { "raf-effect-atom": dehydrate(registry) };
121
131
  return Predicate.isObject(dehydrated) ? {
@@ -1,6 +1,5 @@
1
+ import { ArgsOf, Handle, Source } from "./Handle.mjs";
1
2
  import { Mode } from "./internal/waterfalls.mjs";
2
- import * as Atom from "effect/reactivity/Atom";
3
- import * as AsyncResult from "effect/reactivity/AsyncResult";
4
3
  import * as React from "react";
5
4
  /**
6
5
  * What `useAtomResult` does in development when a read starts an atom that
@@ -51,8 +50,8 @@ export declare const WaterfallDetection: (props: {
51
50
  readonly mode: WaterfallMode;
52
51
  }) => React.ReactNode;
53
52
  /**
54
- * Starts an atom during render and returns it, for an input only a render
55
- * knows, such as a debounced search query.
53
+ * Starts a source's atom during render and returns its handle, for an input
54
+ * only a render knows, such as a debounced search query.
56
55
  *
57
56
  * **When to use**
58
57
  *
@@ -71,12 +70,14 @@ export declare const WaterfallDetection: (props: {
71
70
  * - A result the server is still streaming is awaited by `useAtomResult`,
72
71
  * not started again.
73
72
  * - `useAtomResult` doesn't report an atom started this way.
73
+ * - The handle is a prefetched one: its reader suspends until the result
74
+ * settles, as for a handle from a loader's `prefetch`.
74
75
  *
75
76
  * **Gotchas**
76
77
  *
77
- * Pass it the atom a loader would start, inside an error boundary's `wrap`:
78
- * `useAtomResult(boundary.wrap(useWaterfall(user(id))))`. The wrapper isn't
79
- * serializable, so the server couldn't send it to the browser.
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.
80
81
  *
81
82
  * **Example**
82
83
  *
@@ -84,7 +85,7 @@ export declare const WaterfallDetection: (props: {
84
85
  * import { useAtomResult, useWaterfall } from "raf-effect-atom"
85
86
  *
86
87
  * function Author(props: { readonly userId: UserId }) {
87
- * const result = useAtomResult(useWaterfall(user(props.userId)))
88
+ * const result = useAtomResult(user, useWaterfall(user, props.userId))
88
89
  * // ...
89
90
  * }
90
91
  * ```
@@ -95,4 +96,4 @@ export declare const WaterfallDetection: (props: {
95
96
  * @category hooks
96
97
  * @since 0.1.0
97
98
  */
98
- export declare const useWaterfall: <Self extends Atom.Atom<AsyncResult.AsyncResult<unknown, unknown>>>(atom: Self) => Self;
99
+ export declare const useWaterfall: <S extends Source>(source: S, ...args: ArgsOf<S>) => Handle<S>;
@@ -2,6 +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
6
  import { prefetch } from "./Loader.mjs";
6
7
  import { ModeContext, isStarted } from "./internal/waterfalls.mjs";
7
8
  import * as Predicate from "effect/Predicate";
@@ -53,8 +54,8 @@ const WaterfallDetection = (props) => process.env.NODE_ENV === "production" ? pr
53
54
  children: props.children
54
55
  });
55
56
  /**
56
- * Starts an atom during render and returns it, for an input only a render
57
- * knows, such as a debounced search query.
57
+ * Starts a source's atom during render and returns its handle, for an input
58
+ * only a render knows, such as a debounced search query.
58
59
  *
59
60
  * **When to use**
60
61
  *
@@ -73,12 +74,14 @@ const WaterfallDetection = (props) => process.env.NODE_ENV === "production" ? pr
73
74
  * - A result the server is still streaming is awaited by `useAtomResult`,
74
75
  * not started again.
75
76
  * - `useAtomResult` doesn't report an atom started this way.
77
+ * - The handle is a prefetched one: its reader suspends until the result
78
+ * settles, as for a handle from a loader's `prefetch`.
76
79
  *
77
80
  * **Gotchas**
78
81
  *
79
- * Pass it the atom a loader would start, inside an error boundary's `wrap`:
80
- * `useAtomResult(boundary.wrap(useWaterfall(user(id))))`. The wrapper isn't
81
- * serializable, so the server couldn't send it to the browser.
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.
82
85
  *
83
86
  * **Example**
84
87
  *
@@ -86,7 +89,7 @@ const WaterfallDetection = (props) => process.env.NODE_ENV === "production" ? pr
86
89
  * import { useAtomResult, useWaterfall } from "raf-effect-atom"
87
90
  *
88
91
  * function Author(props: { readonly userId: UserId }) {
89
- * const result = useAtomResult(useWaterfall(user(props.userId)))
92
+ * const result = useAtomResult(user, useWaterfall(user, props.userId))
90
93
  * // ...
91
94
  * }
92
95
  * ```
@@ -97,11 +100,12 @@ const WaterfallDetection = (props) => process.env.NODE_ENV === "production" ? pr
97
100
  * @category hooks
98
101
  * @since 0.1.0
99
102
  */
100
- const useWaterfall = (atom) => {
103
+ const useWaterfall = (source, ...args) => {
101
104
  const registry = React.useContext(RegistryContext);
105
+ const atom = atomOf(source, args);
102
106
  const target = hydrated(atom);
103
107
  const key = keyOf(target);
104
- if (!(Predicate.isString(key) && get(registry, key) !== void 0) && !isStarted(registry, target)) Effect.runSync(Effect.provideService(prefetch(atom), AtomRegistry.AtomRegistry, registry));
105
- return atom;
108
+ if (!(Predicate.isString(key) && get(registry, key) !== void 0) && !isStarted(registry, target)) return Effect.runSync(Effect.provideService(prefetch(source, ...args), AtomRegistry.AtomRegistry, registry));
109
+ return make(args, atom);
106
110
  };
107
111
  export { WaterfallDetection, useWaterfall };
package/dist/index.d.mts CHANGED
@@ -1,7 +1,8 @@
1
+ import { ArgsOf, ErrorOf, Handle, HandleKey, LoadedHandle, Source, SuccessOf } from "./Handle.mjs";
1
2
  import { Loader_d_exports } from "./Loader.mjs";
3
+ import { RequestRegistry_d_exports } from "./RequestRegistry.mjs";
2
4
  import { HydrationBoundary, HydrationBoundaryProps } from "./ReactHydration.mjs";
3
5
  import { Hydration_d_exports } from "./Hydration.mjs";
4
- import { RequestRegistry_d_exports } from "./RequestRegistry.mjs";
5
6
  import { useAtomResult } from "./Hooks.mjs";
6
7
  import { WaterfallDetection, WaterfallMode, useWaterfall } from "./Waterfalls.mjs";
7
- export { Hydration_d_exports as Hydration, HydrationBoundary, HydrationBoundaryProps, Loader_d_exports as Loader, RequestRegistry_d_exports as RequestRegistry, WaterfallDetection, WaterfallMode, useAtomResult, useWaterfall };
8
+ export { ArgsOf, ErrorOf, Handle, HandleKey, Hydration_d_exports as Hydration, HydrationBoundary, HydrationBoundaryProps, LoadedHandle, Loader_d_exports as Loader, RequestRegistry_d_exports as RequestRegistry, Source, SuccessOf, WaterfallDetection, WaterfallMode, useAtomResult, useWaterfall };
@@ -0,0 +1,22 @@
1
+ import { keyOf as keyOf$1 } from "./leases.mjs";
2
+ import * as Atom from "effect/reactivity/Atom";
3
+ import * as Predicate from "effect/Predicate";
4
+ // src/internal/handles.ts
5
+ /** The atom a source makes for its arguments. */
6
+ const atomOf = (source, args) => Atom.isAtom(source) ? source : source(...args);
7
+ /** The registry key a handle records: the atom's serialization key. */
8
+ const keyOf = (atom) => {
9
+ const key = keyOf$1(atom);
10
+ if (!Predicate.isString(key)) return;
11
+ return key;
12
+ };
13
+ const make = (args, atom) => ({
14
+ args,
15
+ key: keyOf(atom)
16
+ });
17
+ const loaded = (args, atom) => ({
18
+ args,
19
+ key: keyOf(atom),
20
+ loaded: true
21
+ });
22
+ export { atomOf, loaded, make };
@@ -1,16 +1,25 @@
1
- import { dehydrate as dehydrate$1 } from "../Hydration.mjs";
2
- import { make } from "../RequestRegistry.mjs";
3
- import { make as make$1 } from "../Loader.mjs";
1
+ import { dehydrate } from "../Hydration.mjs";
2
+ import { make as make$1, settled } from "../RequestRegistry.mjs";
3
+ import { make as make$2 } from "../Loader.mjs";
4
4
  import * as React from "react";
5
5
  // src/internal/next.ts
6
- const getRegistry = React.cache(() => {
7
- const registry = make();
8
- React.cacheSignal()?.addEventListener("abort", () => registry.dispose(), { once: true });
9
- return registry;
10
- });
11
- const getLoader = React.cache(() => make$1(getRegistry()));
12
- const prefetch = (atom) => getLoader().prefetch(atom);
13
- const ensure = (atom) => getLoader().ensure(atom);
14
- const fetch = (atom) => getLoader().fetch(atom);
15
- const dehydrate = () => dehydrate$1(getRegistry());
16
- export { dehydrate, ensure, fetch, getRegistry, prefetch };
6
+ const make = (options) => {
7
+ const getRegistry = React.cache(() => {
8
+ const registry = make$1(options);
9
+ React.cacheSignal()?.addEventListener("abort", () => registry.dispose(), { once: true });
10
+ return registry;
11
+ });
12
+ const getLoader = React.cache(() => make$2(getRegistry()));
13
+ return {
14
+ dehydrate: async () => {
15
+ const registry = getRegistry();
16
+ await settled(registry);
17
+ return dehydrate(registry);
18
+ },
19
+ ensure: (source, ...args) => getLoader().ensure(source, ...args),
20
+ fetch: (source, ...args) => getLoader().fetch(source, ...args),
21
+ getRegistry,
22
+ prefetch: (source, ...args) => getLoader().prefetch(source, ...args)
23
+ };
24
+ };
25
+ export { make };
@@ -9,7 +9,7 @@ const defaultIdleTTL = 400;
9
9
  * A request registry on the server, and in the browser a registry like
10
10
  * `@effect/atom-react`'s `RegistryProvider` makes.
11
11
  */
12
- const make = (options) => isServer() ? make$1() : AtomRegistry.make({
12
+ const make = (options) => isServer() ? make$1({ seed: options?.seed }) : AtomRegistry.make({
13
13
  defaultIdleTTL: options?.defaultIdleTTL ?? defaultIdleTTL,
14
14
  scheduleTask
15
15
  });
@@ -0,0 +1,3 @@
1
+ import * as AtomRegistry from "effect/reactivity/AtomRegistry";
2
+ /** Prepares a request registry before any loader verb starts an atom in it. */
3
+ export type Seed = (registry: AtomRegistry.AtomRegistry) => void | Promise<void>;