raf-effect-atom 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +111 -0
- package/README.md +17 -11
- package/dist/Handle.d.mts +90 -0
- package/dist/Hooks.d.mts +37 -16
- package/dist/Hooks.mjs +38 -22
- package/dist/Loader.d.mts +58 -23
- package/dist/Loader.mjs +87 -33
- package/dist/Next.d.mts +10 -11
- package/dist/Next.mjs +6 -5
- package/dist/NextClient.d.mts +2 -3
- package/dist/NextClient.mjs +2 -3
- package/dist/ReactHydration.d.mts +1 -1
- package/dist/ReactHydration.mjs +1 -1
- package/dist/ReactRouter.d.mts +1 -1
- package/dist/ReactRouter.mjs +3 -3
- package/dist/Waterfalls.d.mts +10 -9
- package/dist/Waterfalls.mjs +13 -9
- package/dist/index.d.mts +3 -2
- package/dist/internal/handles.mjs +22 -0
- package/dist/internal/next.mjs +3 -3
- package/dist/internal/waterfalls.mjs +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (2026-10-23)
|
|
4
|
+
|
|
5
|
+
### ⚠ BREAKING CHANGES
|
|
6
|
+
|
|
7
|
+
* **raf-effect-atom:** `useAtomResult(atom, claims)` is gone. Read
|
|
8
|
+
`useAtomResult(boundary.wrap(atom))` instead. `effect-atom-boundary` is no
|
|
9
|
+
longer a peer dependency.
|
|
10
|
+
|
|
11
|
+
effect-atom-boundary stays a devDependency, so the hook's tests still read
|
|
12
|
+
through a real boundary's wrap (claimed tag thrown to the boundary, others
|
|
13
|
+
returned). The type test now checks that a derived atom with an
|
|
14
|
+
initialValueTarget is read with its own type.
|
|
15
|
+
|
|
16
|
+
The request waterfalls docs sample, written against the claims overload,
|
|
17
|
+
reads through `useIssueBoundary().wrap(atom)` too.
|
|
18
|
+
* **raf-effect-atom:** follow initialValueTarget in useAtomResult and throw defects
|
|
19
|
+
* **raf-effect-atom:** the loader verbs take a source and its arguments and
|
|
20
|
+
return a handle, and `useAtomResult` reads a handle through a source.
|
|
21
|
+
|
|
22
|
+
- `prefetch(issue, id)` returns a `Handle<typeof issue>`; `ensure` and
|
|
23
|
+
`fetch` return a `LoadedHandle`, which adds `loaded: true`. A source is
|
|
24
|
+
an atom family, such as an AtomRpc query, or one AsyncResult atom, whose
|
|
25
|
+
handle has no arguments. The same holds for `Loader.make(registry)`, the
|
|
26
|
+
router contexts' `atoms` and `raf-effect-atom/next`'s verbs.
|
|
27
|
+
- A handle is plain data, `{ args, key }`, so loaders return it in their
|
|
28
|
+
data and server components pass it to client components as a prop. Its
|
|
29
|
+
type names its source through a brand on the key, `HandleKey<S>`, so
|
|
30
|
+
one family's handle doesn't fit another's prop. The brand is on the key
|
|
31
|
+
because React Router's `Serialize` type drops an optional phantom field
|
|
32
|
+
from loader data and keeps branded strings; an atom source's args are a
|
|
33
|
+
mutable `[]` because `Serialize<readonly []>` is `readonly never[]`.
|
|
34
|
+
- `Loader.result(handle)` gives loader code the settled result of an
|
|
35
|
+
`ensure` or `fetch`. The result can't ride on the handle: AsyncResult and
|
|
36
|
+
Schema classes don't survive turbo-stream, seroval, JSON or server
|
|
37
|
+
component props. It's kept in a WeakMap keyed by the handle object, and
|
|
38
|
+
a copy of the handle throws.
|
|
39
|
+
- `useAtomResult(source, handle)` reads `source(...handle.args)`. Its
|
|
40
|
+
handle parameter widens the source's error to `unknown`, so a boundary's
|
|
41
|
+
`wrap(issue)` reads `issue`'s handles with the per-tag narrowing and the
|
|
42
|
+
`Success<A, never>` typing kept. In development it throws when the atom
|
|
43
|
+
it hydrates has a serialization key other than the handle's.
|
|
44
|
+
- `useWaterfall(source, ...args)` returns a handle, the one way to make a
|
|
45
|
+
handle during render.
|
|
46
|
+
|
|
47
|
+
The waterfall warning names the new call shapes.
|
|
48
|
+
|
|
49
|
+
Migrated with it, so no commit has a red tree:
|
|
50
|
+
- The three example apps: loaders return the handles of the atoms they
|
|
51
|
+
start, and pages pass them down (TanStack Start through
|
|
52
|
+
`Route.useLoaderData()`, React Router through `loaderData.handles` next
|
|
53
|
+
to the dehydrated atoms, Next.js as server component props). Components
|
|
54
|
+
take a handle instead of an id. "Load more" keeps the next page's handle
|
|
55
|
+
in state, made by `Loader.make(registry).prefetch(source, ...args)` in
|
|
56
|
+
the click handler.
|
|
57
|
+
- www: the docs route, demo embeds and the loader-verbs, waterfalls,
|
|
58
|
+
hydration, boundary-chain and every-tag-caught demos; the TanStack Start, Next.js and
|
|
59
|
+
React Router doc samples, the error-boundaries and every-tag-caught
|
|
60
|
+
samples included; the home page's steps; and the pages that
|
|
61
|
+
show the verbs and reads (loader verbs gains a Handles section and
|
|
62
|
+
`Loader.result`, reading atoms explains handles as props, request
|
|
63
|
+
waterfalls, coding agents, shared boundaries, error boundaries, loader
|
|
64
|
+
failures).
|
|
65
|
+
`www/src/use-loaded.ts` is gone: a wrapper generic over a family can't
|
|
66
|
+
satisfy `useAtomResult`'s `Readable` constraint, and
|
|
67
|
+
`useAtomResult(source, handle).value` reads as well. The "Fetches in a
|
|
68
|
+
component" mistake on the home page is now caught by the compiler.
|
|
69
|
+
- The package READMEs, the boundary's JSDoc example, and the decision in
|
|
70
|
+
`.agent-docs/decisions/atom-handles.md`.
|
|
71
|
+
|
|
72
|
+
### Features
|
|
73
|
+
|
|
74
|
+
* **raf-effect-atom:** add the next subpath ([92cacd8](https://github.com/KATT/raf-effect-atom/commit/92cacd86cb6d6e1784d9a07f71bd9fc3ec5ca0da))
|
|
75
|
+
* **raf-effect-atom:** add the react-router subpath ([09b65dc](https://github.com/KATT/raf-effect-atom/commit/09b65dcbc6cfde2ae1ba2bb89e580746e9512f8b))
|
|
76
|
+
* **raf-effect-atom:** add the tanstack-router subpath ([0189c83](https://github.com/KATT/raf-effect-atom/commit/0189c834cd46fc05ed866859e7527b3bd2ac47df))
|
|
77
|
+
* **raf-effect-atom:** drop the claims overload and the effect-atom-boundary peer ([48fe91e](https://github.com/KATT/raf-effect-atom/commit/48fe91eabf71a0eb36b49c907fabae7afa5abd90))
|
|
78
|
+
* **raf-effect-atom:** follow initialValueTarget in useAtomResult and throw defects ([7f577ea](https://github.com/KATT/raf-effect-atom/commit/7f577eabbdd567313b542db61d532bb36a2183ca))
|
|
79
|
+
* **raf-effect-atom:** loader verbs return handles that components read ([52a4323](https://github.com/KATT/raf-effect-atom/commit/52a4323bae1507e6b5f524262b7ed1609b1ed5d6))
|
|
80
|
+
* **raf-effect-atom:** loader verbs, streaming hydration and the read hook ([6870b1a](https://github.com/KATT/raf-effect-atom/commit/6870b1a9c7742d2fa15ce1391c8fdac66307d89e))
|
|
81
|
+
* **raf-effect-atom:** make the browser registry's idle TTL configurable ([1c55d0e](https://github.com/KATT/raf-effect-atom/commit/1c55d0e74f54bed7cef895434357355229cff512))
|
|
82
|
+
* **raf-effect-atom:** read the loader's registry under HydrationBoundary during server rendering ([6751792](https://github.com/KATT/raf-effect-atom/commit/675179254563b85c61f9a96e646fa9c72f47cc83))
|
|
83
|
+
* **raf-effect-atom:** report request waterfalls in development, with useWaterfall as the escape hatch ([4b6fe2a](https://github.com/KATT/raf-effect-atom/commit/4b6fe2aebb939ea0a7095a5121558aa0cbc5bac5))
|
|
84
|
+
* **raf-effect-atom:** type useAtomResult as a success when no typed failure is left ([aeb8dc6](https://github.com/KATT/raf-effect-atom/commit/aeb8dc63e0ad874a23c3a992afed540c27c9bb7d))
|
|
85
|
+
|
|
86
|
+
### Bug Fixes
|
|
87
|
+
|
|
88
|
+
* **raf-effect-atom:** call use() on a streamed result's promise on every render that reads it ([36d2aaa](https://github.com/KATT/raf-effect-atom/commit/36d2aaa8bacf18a60507e4997fd61476d7a908c0)), references [#41](https://github.com/KATT/raf-effect-atom/issues/41)
|
|
89
|
+
* **raf-effect-atom:** point waterfall reports at the URL before the escape hatches ([0d127e3](https://github.com/KATT/raf-effect-atom/commit/0d127e38a10cb3a44340c5540c75b53491faceee))
|
|
90
|
+
* **raf-effect-atom:** refresh the atom the registry runs for a key in Loader.fetch ([0b35c97](https://github.com/KATT/raf-effect-atom/commit/0b35c97b4de60de254c7fc921b89aed7be753ec8))
|
|
91
|
+
* **raf-effect-atom:** wait for results streamed in a server component payload ([00cb981](https://github.com/KATT/raf-effect-atom/commit/00cb98125aad2cda70525fbfdedcd2b5a8f7fc79))
|
|
92
|
+
|
|
93
|
+
### Code Refactoring
|
|
94
|
+
|
|
95
|
+
* **examples:** read boundary claims through wrap ([ae92f67](https://github.com/KATT/raf-effect-atom/commit/ae92f67722417542ee06a7299b327a77a0597814))
|
|
96
|
+
* **raf-effect-atom:** rename files to kebab-case ([285bf4b](https://github.com/KATT/raf-effect-atom/commit/285bf4b4e5e3184ac162129f51c04d9c96921650))
|
|
97
|
+
* **raf-effect-atom:** restore Effect's file names ([92dc1d8](https://github.com/KATT/raf-effect-atom/commit/92dc1d8e52212c28042153cbd7b1146870839831))
|
|
98
|
+
|
|
99
|
+
### Documentation
|
|
100
|
+
|
|
101
|
+
* catch RpcClientError in an app boundary, and read fully claimed atoms as successes ([fe9b485](https://github.com/KATT/raf-effect-atom/commit/fe9b485fd19643a34f1709ebb5b66c90036eea47))
|
|
102
|
+
* point coding agents at the rules from the READMEs ([e8d3047](https://github.com/KATT/raf-effect-atom/commit/e8d30477e840df83c0f47764bdf505bc61181f14))
|
|
103
|
+
* **raf-effect-atom:** add the package README ([69c589e](https://github.com/KATT/raf-effect-atom/commit/69c589e020527ca087d7a54fb99544460ebc7774))
|
|
104
|
+
|
|
105
|
+
### Tests
|
|
106
|
+
|
|
107
|
+
* keep each package's Vitest module cache to itself ([be11618](https://github.com/KATT/raf-effect-atom/commit/be11618b41f9cecaba6968a356677a7dd3bfe734))
|
|
108
|
+
|
|
109
|
+
### Continuous Integration
|
|
110
|
+
|
|
111
|
+
* release each changed package after CI passes on main, and pack-check the tarballs on every push ([8ab3b90](https://github.com/KATT/raf-effect-atom/commit/8ab3b90e4ac2e7d6c25c028aace7167279bdb2b3))
|
package/README.md
CHANGED
|
@@ -17,27 +17,33 @@ npm install raf-effect-atom effect @effect/atom-react react
|
|
|
17
17
|
|
|
18
18
|
## Usage
|
|
19
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:
|
|
20
|
+
A loader starts atoms with a verb per call, given an atom family and its arguments. `prefetch` starts one and streams it, `ensure` waits for its first result, and `fetch` waits for a fresh one. Each returns a handle, plain data naming the atom, which the loader returns for the page to pass down. Typed failures come back as results, so a loader can redirect on a tag:
|
|
21
21
|
|
|
22
22
|
```ts
|
|
23
|
+
import { Loader } from "raf-effect-atom";
|
|
24
|
+
|
|
23
25
|
// issue: (id: number) => an AtomRpc query with a serializationKey
|
|
24
26
|
export async function loader(args: LoaderArgs) {
|
|
25
27
|
const id = Number(args.params.id);
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
const handles = {
|
|
29
|
+
comments: args.context.atoms.prefetch(comments, id),
|
|
30
|
+
issue: await args.context.atoms.ensure(issue, id),
|
|
31
|
+
};
|
|
32
|
+
const result = Loader.result(handles.issue);
|
|
28
33
|
if (AsyncResult.isFailure(result)) {
|
|
29
34
|
// result.cause holds IssueNotFound | Unauthorized | RpcClientError
|
|
30
35
|
}
|
|
36
|
+
return handles;
|
|
31
37
|
}
|
|
32
38
|
```
|
|
33
39
|
|
|
34
|
-
A component
|
|
40
|
+
A component takes the handle as a prop and reads it with `useAtomResult(source, handle)`, which waits for a streamed result instead of starting the atom again, and returns typed failures. Given a handle instead of an id, it can't start a request of its own. The source can be a family derived from the loader's, such as the one 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
41
|
|
|
36
42
|
```tsx
|
|
37
|
-
import { useAtomResult } from "raf-effect-atom";
|
|
43
|
+
import { type Handle, useAtomResult } from "raf-effect-atom";
|
|
38
44
|
|
|
39
|
-
function IssueTitle(props: { readonly
|
|
40
|
-
const result = useAtomResult(useIssueBoundary().wrap(issue
|
|
45
|
+
function IssueTitle(props: { readonly issue: Handle<typeof issue> }) {
|
|
46
|
+
const result = useAtomResult(useIssueBoundary().wrap(issue), props.issue);
|
|
41
47
|
// Success<Issue, never>: its boundaries claim every failure
|
|
42
48
|
return <h1>{result.value.title}</h1>;
|
|
43
49
|
}
|
|
@@ -46,14 +52,14 @@ function IssueTitle(props: { readonly id: number }) {
|
|
|
46
52
|
Each framework has a subpath that gives every server request its own registry and sends it to the browser:
|
|
47
53
|
|
|
48
54
|
- `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
|
|
50
|
-
- `raf-effect-atom/react-router`, for Framework Mode: `atomsMiddleware(atomsContext)` as the root middleware, loaders that return `atoms.dehydrate()
|
|
55
|
+
- `raf-effect-atom/next`, for the App Router: `prefetch`, `ensure` and `fetch` in server components, which pass the handles to client components as props, and `<HydrateAtoms state={dehydrate()}>` around them.
|
|
56
|
+
- `raf-effect-atom/react-router`, for Framework Mode: `atomsMiddleware(atomsContext)` as the root middleware, loaders that return `atoms.dehydrate()` with their handles, and `HydrationBoundary` in the route component.
|
|
51
57
|
|
|
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(
|
|
58
|
+
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(source, ...args)` starts an atom whose input exists only in render, marks it as a known waterfall, and returns its handle.
|
|
53
59
|
|
|
54
60
|
## For coding agents
|
|
55
61
|
|
|
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.
|
|
62
|
+
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. Pass components the handles the loader returns, not ids, and read them 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
63
|
|
|
58
64
|
## License
|
|
59
65
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import * as Atom from "effect/reactivity/Atom";
|
|
2
|
+
import * as AsyncResult from "effect/reactivity/AsyncResult";
|
|
3
|
+
/**
|
|
4
|
+
* What the verbs start and `useAtomResult` reads: an atom family, a function
|
|
5
|
+
* from arguments to an `AsyncResult` atom such as an `AtomRpc` query, or an
|
|
6
|
+
* `AsyncResult` atom that takes no arguments.
|
|
7
|
+
*
|
|
8
|
+
* @stability unstable
|
|
9
|
+
* @category models
|
|
10
|
+
* @since 0.1.0
|
|
11
|
+
*/
|
|
12
|
+
export type Source = Atom.Atom<AsyncResult.AsyncResult<any, any>> | ((...args: any) => Atom.Atom<AsyncResult.AsyncResult<any, any>>);
|
|
13
|
+
/**
|
|
14
|
+
* The arguments a source takes: a family's parameters, or none for an atom.
|
|
15
|
+
*
|
|
16
|
+
* @stability unstable
|
|
17
|
+
* @category utility types
|
|
18
|
+
* @since 0.1.0
|
|
19
|
+
*/
|
|
20
|
+
export type ArgsOf<S> = S extends ((...args: infer Args) => unknown) ? Args : [];
|
|
21
|
+
/**
|
|
22
|
+
* The success type of a source's `AsyncResult`.
|
|
23
|
+
*
|
|
24
|
+
* @stability unstable
|
|
25
|
+
* @category utility types
|
|
26
|
+
* @since 0.1.0
|
|
27
|
+
*/
|
|
28
|
+
export type SuccessOf<S> = ResultOf<S> extends AsyncResult.AsyncResult<infer A, infer _E> ? A : never;
|
|
29
|
+
/**
|
|
30
|
+
* The error type of a source's `AsyncResult`.
|
|
31
|
+
*
|
|
32
|
+
* @stability unstable
|
|
33
|
+
* @category utility types
|
|
34
|
+
* @since 0.1.0
|
|
35
|
+
*/
|
|
36
|
+
export type ErrorOf<S> = ResultOf<S> extends AsyncResult.AsyncResult<infer _A, infer E> ? E : never;
|
|
37
|
+
type ResultOf<S> = S extends ((...args: ReadonlyArray<never>) => infer Built) ? Built extends Atom.Atom<infer Result> ? Result : never : S extends Atom.Atom<infer Result> ? Result : never;
|
|
38
|
+
declare const SourceType: unique symbol;
|
|
39
|
+
/**
|
|
40
|
+
* A handle's serialization key, branded with its source. The brand sits on
|
|
41
|
+
* a string, so types that map loader data to what survives serialization,
|
|
42
|
+
* such as React Router's, keep it as is.
|
|
43
|
+
*
|
|
44
|
+
* @stability unstable
|
|
45
|
+
* @category models
|
|
46
|
+
* @since 0.1.0
|
|
47
|
+
*/
|
|
48
|
+
export type HandleKey<S> = string & {
|
|
49
|
+
/** Type-only: ties the handle to its source. Never present at runtime. */
|
|
50
|
+
readonly [SourceType]?: S;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* An atom a loader started: its source's arguments, and the atom's
|
|
54
|
+
* serialization key. Its type names its source, so `useAtomResult` reads it
|
|
55
|
+
* only through a source with the same arguments and success type.
|
|
56
|
+
*
|
|
57
|
+
* **Details**
|
|
58
|
+
*
|
|
59
|
+
* `Loader.prefetch` and `useWaterfall` return it, without waiting: the
|
|
60
|
+
* component that reads it suspends until the result settles.
|
|
61
|
+
*
|
|
62
|
+
* @see {@link LoadedHandle} for the handle `ensure` and `fetch` return
|
|
63
|
+
*
|
|
64
|
+
* @stability unstable
|
|
65
|
+
* @category models
|
|
66
|
+
* @since 0.1.0
|
|
67
|
+
*/
|
|
68
|
+
export interface Handle<S> {
|
|
69
|
+
readonly args: ArgsOf<S>;
|
|
70
|
+
/** The atom's serialization key; `undefined` for an atom without one. */
|
|
71
|
+
readonly key: HandleKey<S> | undefined;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* A handle from `Loader.ensure` or `Loader.fetch`: they waited for the
|
|
75
|
+
* result, so the component that reads it renders without suspending.
|
|
76
|
+
*
|
|
77
|
+
* **Details**
|
|
78
|
+
*
|
|
79
|
+
* A `LoadedHandle` fits a `Handle` prop, and a `Handle` doesn't fit a
|
|
80
|
+
* `LoadedHandle` prop. In the loader that ran the verb, `Loader.result`
|
|
81
|
+
* returns the result it waited for.
|
|
82
|
+
*
|
|
83
|
+
* @stability unstable
|
|
84
|
+
* @category models
|
|
85
|
+
* @since 0.1.0
|
|
86
|
+
*/
|
|
87
|
+
export interface LoadedHandle<S> extends Handle<S> {
|
|
88
|
+
readonly loaded: true;
|
|
89
|
+
}
|
|
90
|
+
export {};
|
package/dist/Hooks.d.mts
CHANGED
|
@@ -1,18 +1,34 @@
|
|
|
1
|
+
import { ErrorOf, Handle, Source, SuccessOf } from "./Handle.mjs";
|
|
1
2
|
import { SettledResult } from "./Loader.mjs";
|
|
2
3
|
import * as Atom from "effect/reactivity/Atom";
|
|
3
4
|
import * as AsyncResult from "effect/reactivity/AsyncResult";
|
|
4
5
|
/**
|
|
5
|
-
*
|
|
6
|
-
* the
|
|
6
|
+
* A source with the same arguments and success type as `S`, and any error:
|
|
7
|
+
* the sources whose handles `S` reads, such as the source an error
|
|
8
|
+
* boundary's `wrap(S)` wraps.
|
|
9
|
+
*/
|
|
10
|
+
type Readable<S> = S extends ((...args: infer Args) => Atom.Atom<AsyncResult.AsyncResult<infer A, infer _E>>) ? (...args: Args) => Atom.Atom<AsyncResult.AsyncResult<A, unknown>> : S extends Atom.Atom<AsyncResult.AsyncResult<infer A, infer _E>> ? Atom.Atom<AsyncResult.AsyncResult<A, unknown>> : never;
|
|
11
|
+
/**
|
|
12
|
+
* Reads a handle through React Suspense, through the source that made it: a
|
|
13
|
+
* result streamed from the server, a hydrated one, or one a loader started.
|
|
7
14
|
*
|
|
8
15
|
* **When to use**
|
|
9
16
|
*
|
|
10
|
-
* Use
|
|
11
|
-
*
|
|
12
|
-
*
|
|
17
|
+
* Use in the component a loader, a server component or `useWaterfall` gave
|
|
18
|
+
* the handle to. The source can be the one the verb started, or one derived
|
|
19
|
+
* from it whose atoms point at its atoms with `initialValueTarget`, such as
|
|
20
|
+
* an error boundary's `wrap(source)`.
|
|
13
21
|
*
|
|
14
22
|
* **Details**
|
|
15
23
|
*
|
|
24
|
+
* - The atom is `source(...handle.args)`, or `source` itself for an atom.
|
|
25
|
+
* In development, the read throws when the atom it hydrates has another
|
|
26
|
+
* serialization key than the handle's: the handle came from another
|
|
27
|
+
* source.
|
|
28
|
+
* - A handle from `ensure` or `fetch` renders without suspending, unless
|
|
29
|
+
* the registry dropped the result since; one from `prefetch` or
|
|
30
|
+
* `useWaterfall` suspends until the result settles. The registry is the
|
|
31
|
+
* source of truth either way: the read follows refreshes and mutations.
|
|
16
32
|
* - A result the server was still loading when it dehydrated is awaited
|
|
17
33
|
* instead of started, so the browser doesn't request it a second time.
|
|
18
34
|
* - The atom is read through `hydrated`, so a hydrated result is kept.
|
|
@@ -29,13 +45,17 @@ import * as AsyncResult from "effect/reactivity/AsyncResult";
|
|
|
29
45
|
*
|
|
30
46
|
* **Gotchas**
|
|
31
47
|
*
|
|
32
|
-
* A read of an atom that nothing started
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
48
|
+
* A read of an atom that nothing started, such as one whose loader result the
|
|
49
|
+
* registry dropped, starts it, and its request waits for the render: a
|
|
50
|
+
* request waterfall. In development, the read reports it as
|
|
51
|
+
* `WaterfallDetection` says, by default with a warning. Start the atom in a
|
|
52
|
+
* loader or a server component, with the state that picks it in the URL,
|
|
53
|
+
* and pass its handle down. For state that can't be in the URL, start it in
|
|
54
|
+
* the event handler that changes the state, or make its handle with
|
|
55
|
+
* `useWaterfall` when only the render knows its input.
|
|
56
|
+
*
|
|
57
|
+
* Two sources with the same arguments and success type take each other's
|
|
58
|
+
* handles as far as the types go; the development check catches it.
|
|
39
59
|
*
|
|
40
60
|
* During server rendering, the registry must be a request registry (see
|
|
41
61
|
* `RequestRegistry.make`), or a module-level registry would share results
|
|
@@ -51,10 +71,10 @@ import * as AsyncResult from "effect/reactivity/AsyncResult";
|
|
|
51
71
|
*
|
|
52
72
|
* ```tsx
|
|
53
73
|
* import { AsyncResult } from "effect/reactivity"
|
|
54
|
-
* import { useAtomResult } from "raf-effect-atom"
|
|
74
|
+
* import { type Handle, useAtomResult } from "raf-effect-atom"
|
|
55
75
|
*
|
|
56
|
-
* function IssueTitle(props: { readonly
|
|
57
|
-
* const result = useAtomResult(issue
|
|
76
|
+
* function IssueTitle(props: { readonly issue: Handle<typeof issue> }) {
|
|
77
|
+
* const result = useAtomResult(issue, props.issue)
|
|
58
78
|
* return AsyncResult.isSuccess(result) ? <h1>{result.value.title}</h1> : null
|
|
59
79
|
* }
|
|
60
80
|
* ```
|
|
@@ -65,4 +85,5 @@ import * as AsyncResult from "effect/reactivity/AsyncResult";
|
|
|
65
85
|
* @category hooks
|
|
66
86
|
* @since 0.1.0
|
|
67
87
|
*/
|
|
68
|
-
export declare const useAtomResult: <
|
|
88
|
+
export declare const useAtomResult: <S extends Source>(source: S, handle: NoInfer<Handle<Readable<S>>>) => [ErrorOf<S>] extends [never] ? AsyncResult.Success<SuccessOf<S>, never> : SettledResult<SuccessOf<S>, ErrorOf<S>>;
|
|
89
|
+
export {};
|
package/dist/Hooks.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { stream } from "./internal/pending.mjs";
|
|
|
3
3
|
import { hydrated } from "./Hydration.mjs";
|
|
4
4
|
import { isDevelopment } from "./internal/development.mjs";
|
|
5
5
|
import { keyOf, release } from "./internal/leases.mjs";
|
|
6
|
+
import { atomOf } from "./internal/handles.mjs";
|
|
6
7
|
import { isRequestRegistry } from "./RequestRegistry.mjs";
|
|
7
8
|
import { isServer } from "./internal/registry.mjs";
|
|
8
9
|
import { ModeContext, check } from "./internal/waterfalls.mjs";
|
|
@@ -31,17 +32,26 @@ const sourceOf = (atom) => {
|
|
|
31
32
|
return atom;
|
|
32
33
|
};
|
|
33
34
|
/**
|
|
34
|
-
* Reads
|
|
35
|
-
* the server, a hydrated one, or one a loader started.
|
|
35
|
+
* Reads a handle through React Suspense, through the source that made it: a
|
|
36
|
+
* result streamed from the server, a hydrated one, or one a loader started.
|
|
36
37
|
*
|
|
37
38
|
* **When to use**
|
|
38
39
|
*
|
|
39
|
-
* Use
|
|
40
|
-
*
|
|
41
|
-
*
|
|
40
|
+
* Use in the component a loader, a server component or `useWaterfall` gave
|
|
41
|
+
* the handle to. The source can be the one the verb started, or one derived
|
|
42
|
+
* from it whose atoms point at its atoms with `initialValueTarget`, such as
|
|
43
|
+
* an error boundary's `wrap(source)`.
|
|
42
44
|
*
|
|
43
45
|
* **Details**
|
|
44
46
|
*
|
|
47
|
+
* - The atom is `source(...handle.args)`, or `source` itself for an atom.
|
|
48
|
+
* In development, the read throws when the atom it hydrates has another
|
|
49
|
+
* serialization key than the handle's: the handle came from another
|
|
50
|
+
* source.
|
|
51
|
+
* - A handle from `ensure` or `fetch` renders without suspending, unless
|
|
52
|
+
* the registry dropped the result since; one from `prefetch` or
|
|
53
|
+
* `useWaterfall` suspends until the result settles. The registry is the
|
|
54
|
+
* source of truth either way: the read follows refreshes and mutations.
|
|
45
55
|
* - A result the server was still loading when it dehydrated is awaited
|
|
46
56
|
* instead of started, so the browser doesn't request it a second time.
|
|
47
57
|
* - The atom is read through `hydrated`, so a hydrated result is kept.
|
|
@@ -58,13 +68,17 @@ const sourceOf = (atom) => {
|
|
|
58
68
|
*
|
|
59
69
|
* **Gotchas**
|
|
60
70
|
*
|
|
61
|
-
* A read of an atom that nothing started
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
71
|
+
* A read of an atom that nothing started, such as one whose loader result the
|
|
72
|
+
* registry dropped, starts it, and its request waits for the render: a
|
|
73
|
+
* request waterfall. In development, the read reports it as
|
|
74
|
+
* `WaterfallDetection` says, by default with a warning. Start the atom in a
|
|
75
|
+
* loader or a server component, with the state that picks it in the URL,
|
|
76
|
+
* and pass its handle down. For state that can't be in the URL, start it in
|
|
77
|
+
* the event handler that changes the state, or make its handle with
|
|
78
|
+
* `useWaterfall` when only the render knows its input.
|
|
79
|
+
*
|
|
80
|
+
* Two sources with the same arguments and success type take each other's
|
|
81
|
+
* handles as far as the types go; the development check catches it.
|
|
68
82
|
*
|
|
69
83
|
* During server rendering, the registry must be a request registry (see
|
|
70
84
|
* `RequestRegistry.make`), or a module-level registry would share results
|
|
@@ -80,10 +94,10 @@ const sourceOf = (atom) => {
|
|
|
80
94
|
*
|
|
81
95
|
* ```tsx
|
|
82
96
|
* import { AsyncResult } from "effect/reactivity"
|
|
83
|
-
* import { useAtomResult } from "raf-effect-atom"
|
|
97
|
+
* import { type Handle, useAtomResult } from "raf-effect-atom"
|
|
84
98
|
*
|
|
85
|
-
* function IssueTitle(props: { readonly
|
|
86
|
-
* const result = useAtomResult(issue
|
|
99
|
+
* function IssueTitle(props: { readonly issue: Handle<typeof issue> }) {
|
|
100
|
+
* const result = useAtomResult(issue, props.issue)
|
|
87
101
|
* return AsyncResult.isSuccess(result) ? <h1>{result.value.title}</h1> : null
|
|
88
102
|
* }
|
|
89
103
|
* ```
|
|
@@ -94,25 +108,27 @@ const sourceOf = (atom) => {
|
|
|
94
108
|
* @category hooks
|
|
95
109
|
* @since 0.1.0
|
|
96
110
|
*/
|
|
97
|
-
const useAtomResult = (
|
|
111
|
+
const useAtomResult = (source, handle) => {
|
|
98
112
|
const registry = React.useContext(RegistryContext);
|
|
113
|
+
const atom = atomOf(source, handle.args);
|
|
99
114
|
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
115
|
const target = sourceOf(atom);
|
|
101
|
-
const
|
|
116
|
+
const started = hydrated(target);
|
|
102
117
|
const read = target === atom ? hydrated(atom) : atom;
|
|
103
|
-
const key = keyOf(
|
|
118
|
+
const key = keyOf(started);
|
|
119
|
+
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.`);
|
|
104
120
|
const streamed = Predicate.isString(key) ? stream(registry, key) : void 0;
|
|
105
121
|
if (streamed !== void 0) React.use(streamed);
|
|
106
|
-
if (process.env.NODE_ENV !== "production") check(registry,
|
|
107
|
-
if (read !==
|
|
122
|
+
if (process.env.NODE_ENV !== "production") check(registry, started, React.use(ModeContext));
|
|
123
|
+
if (read !== started) registry.get(started);
|
|
108
124
|
const current = registry.get(read);
|
|
109
125
|
const result = useAtomSuspense(read, {
|
|
110
126
|
includeFailure: true,
|
|
111
127
|
suspendOnWaiting: AsyncResult.isFailure(current) && current.waiting
|
|
112
128
|
});
|
|
113
129
|
React.useEffect(() => {
|
|
114
|
-
release(registry,
|
|
115
|
-
}, [registry,
|
|
130
|
+
release(registry, started);
|
|
131
|
+
}, [registry, started]);
|
|
116
132
|
if (AsyncResult.isFailure(result) && Option.isNone(Cause.findErrorOption(result.cause))) throw Cause.squash(result.cause);
|
|
117
133
|
return result;
|
|
118
134
|
};
|
package/dist/Loader.d.mts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ArgsOf, ErrorOf, Handle, LoadedHandle, Source, SuccessOf } from "./Handle.mjs";
|
|
2
2
|
import * as Context from "effect/Context";
|
|
3
3
|
import * as Duration from "effect/Duration";
|
|
4
4
|
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, SettledResult, ensure, fetch, make, prefetch };
|
|
8
|
+
export { LeaseTTL, Loader, SettledResult, ensure, fetch, make, prefetch, result };
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
11
|
* A settled `AsyncResult`: what `ensure` and `fetch` return.
|
|
@@ -31,7 +31,7 @@ export type SettledResult<A, E> = AsyncResult.Failure<A, E> | AsyncResult.Succes
|
|
|
31
31
|
*/
|
|
32
32
|
export declare const LeaseTTL: Context.Reference<Duration.Duration>;
|
|
33
33
|
/**
|
|
34
|
-
* Starts
|
|
34
|
+
* Starts a source's atom without waiting for it, and returns its handle.
|
|
35
35
|
*
|
|
36
36
|
* **When to use**
|
|
37
37
|
*
|
|
@@ -41,7 +41,8 @@ export declare const LeaseTTL: Context.Reference<Duration.Duration>;
|
|
|
41
41
|
* **Details**
|
|
42
42
|
*
|
|
43
43
|
* Starts the atom if it isn't in the registry, and leaves it alone if it is.
|
|
44
|
-
* Takes a lease either way.
|
|
44
|
+
* Takes a lease either way. The component that reads the handle suspends
|
|
45
|
+
* until the result settles.
|
|
45
46
|
*
|
|
46
47
|
* **Example**
|
|
47
48
|
*
|
|
@@ -49,17 +50,17 @@ export declare const LeaseTTL: Context.Reference<Duration.Duration>;
|
|
|
49
50
|
* import { Effect } from "effect"
|
|
50
51
|
* import { Loader } from "raf-effect-atom"
|
|
51
52
|
*
|
|
52
|
-
* const program = Loader.prefetch(
|
|
53
|
+
* const program = Loader.prefetch(commentPage, issueId, 0)
|
|
53
54
|
* ```
|
|
54
55
|
*
|
|
55
56
|
* @stability unstable
|
|
56
57
|
* @category verbs
|
|
57
58
|
* @since 0.1.0
|
|
58
59
|
*/
|
|
59
|
-
export declare const prefetch: <
|
|
60
|
+
export declare const prefetch: <S extends Source>(source: S, ...args: ArgsOf<S>) => Effect.Effect<Handle<S>, never, AtomRegistry.AtomRegistry>;
|
|
60
61
|
/**
|
|
61
|
-
* Starts
|
|
62
|
-
*
|
|
62
|
+
* Starts a source's atom, waits only while it has no result, and returns a
|
|
63
|
+
* handle whose reader renders without suspending.
|
|
63
64
|
*
|
|
64
65
|
* **When to use**
|
|
65
66
|
*
|
|
@@ -68,10 +69,11 @@ export declare const prefetch: <A, E>(atom: Atom.Atom<AsyncResult.AsyncResult<A,
|
|
|
68
69
|
*
|
|
69
70
|
* **Details**
|
|
70
71
|
*
|
|
71
|
-
* An atom with a result returns
|
|
72
|
+
* An atom with a result returns at once, without refetching. On a request
|
|
72
73
|
* 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
|
|
74
|
-
*
|
|
74
|
+
* renders a result that's about to change. A typed failure settles it like
|
|
75
|
+
* a success: `result` returns it. A defect or an interruption fails the
|
|
76
|
+
* Effect.
|
|
75
77
|
*
|
|
76
78
|
* **Example**
|
|
77
79
|
*
|
|
@@ -81,7 +83,8 @@ export declare const prefetch: <A, E>(atom: Atom.Atom<AsyncResult.AsyncResult<A,
|
|
|
81
83
|
* import { Loader } from "raf-effect-atom"
|
|
82
84
|
*
|
|
83
85
|
* const program = Effect.gen(function* () {
|
|
84
|
-
* const
|
|
86
|
+
* const handle = yield* Loader.ensure(issue, 1)
|
|
87
|
+
* const result = Loader.result(handle)
|
|
85
88
|
* return AsyncResult.isSuccess(result) ? result.value.title : "missing"
|
|
86
89
|
* })
|
|
87
90
|
* ```
|
|
@@ -90,10 +93,11 @@ export declare const prefetch: <A, E>(atom: Atom.Atom<AsyncResult.AsyncResult<A,
|
|
|
90
93
|
* @category verbs
|
|
91
94
|
* @since 0.1.0
|
|
92
95
|
*/
|
|
93
|
-
export declare const ensure: <
|
|
96
|
+
export declare const ensure: <S extends Source>(source: S, ...args: ArgsOf<S>) => Effect.Effect<LoadedHandle<S>, never, AtomRegistry.AtomRegistry>;
|
|
94
97
|
/**
|
|
95
|
-
* Starts
|
|
96
|
-
*
|
|
98
|
+
* Starts a source's atom, or refreshes it if it has a result, waits for the
|
|
99
|
+
* new result to settle, and returns a handle whose reader renders without
|
|
100
|
+
* suspending.
|
|
97
101
|
*
|
|
98
102
|
* **When to use**
|
|
99
103
|
*
|
|
@@ -102,14 +106,45 @@ export declare const ensure: <A, E>(atom: Atom.Atom<AsyncResult.AsyncResult<A, E
|
|
|
102
106
|
*
|
|
103
107
|
* **Details**
|
|
104
108
|
*
|
|
105
|
-
* Waits until the result settles and isn't refreshing. A typed failure
|
|
106
|
-
*
|
|
109
|
+
* Waits until the result settles and isn't refreshing. A typed failure
|
|
110
|
+
* settles it like a success: `result` returns it. A defect or an
|
|
111
|
+
* interruption fails the Effect.
|
|
107
112
|
*
|
|
108
113
|
* @stability unstable
|
|
109
114
|
* @category verbs
|
|
110
115
|
* @since 0.1.0
|
|
111
116
|
*/
|
|
112
|
-
export declare const fetch: <
|
|
117
|
+
export declare const fetch: <S extends Source>(source: S, ...args: ArgsOf<S>) => Effect.Effect<LoadedHandle<S>, never, AtomRegistry.AtomRegistry>;
|
|
118
|
+
/**
|
|
119
|
+
* Returns the settled result `ensure` or `fetch` waited for.
|
|
120
|
+
*
|
|
121
|
+
* **When to use**
|
|
122
|
+
*
|
|
123
|
+
* Use in the loader that ran the verb, to branch on the result: redirect on
|
|
124
|
+
* a tag, or build another atom's arguments from it. Components read the
|
|
125
|
+
* handle with `useAtomResult`, which follows the registry.
|
|
126
|
+
*
|
|
127
|
+
* **Gotchas**
|
|
128
|
+
*
|
|
129
|
+
* Only the handle object the verb returned has it: a handle that crossed
|
|
130
|
+
* the network, as loader data or a server component's prop, is a copy, and
|
|
131
|
+
* this throws for it.
|
|
132
|
+
*
|
|
133
|
+
* **Example**
|
|
134
|
+
*
|
|
135
|
+
* ```ts
|
|
136
|
+
* import { AsyncResult } from "effect/reactivity"
|
|
137
|
+
* import { Loader } from "raf-effect-atom"
|
|
138
|
+
*
|
|
139
|
+
* const handle = await atoms.ensure(issue, id)
|
|
140
|
+
* const error = AsyncResult.error(Loader.result(handle))
|
|
141
|
+
* ```
|
|
142
|
+
*
|
|
143
|
+
* @stability unstable
|
|
144
|
+
* @category getters
|
|
145
|
+
* @since 0.1.0
|
|
146
|
+
*/
|
|
147
|
+
export declare const result: <S extends Source>(handle: LoadedHandle<S>) => SettledResult<SuccessOf<S>, ErrorOf<S>>;
|
|
113
148
|
/**
|
|
114
149
|
* The loader verbs as Promises over one registry, as the framework
|
|
115
150
|
* integrations put them on the router context.
|
|
@@ -120,11 +155,11 @@ export declare const fetch: <A, E>(atom: Atom.Atom<AsyncResult.AsyncResult<A, E>
|
|
|
120
155
|
*/
|
|
121
156
|
export interface Loader {
|
|
122
157
|
/** {@link ensure}, as a Promise that rejects only on a defect. */
|
|
123
|
-
readonly ensure: <
|
|
158
|
+
readonly ensure: <S extends Source>(source: S, ...args: ArgsOf<S>) => Promise<LoadedHandle<S>>;
|
|
124
159
|
/** {@link fetch}, as a Promise that rejects only on a defect. */
|
|
125
|
-
readonly fetch: <
|
|
160
|
+
readonly fetch: <S extends Source>(source: S, ...args: ArgsOf<S>) => Promise<LoadedHandle<S>>;
|
|
126
161
|
/** {@link prefetch}, run at once. */
|
|
127
|
-
readonly prefetch: <
|
|
162
|
+
readonly prefetch: <S extends Source>(source: S, ...args: ArgsOf<S>) => Handle<S>;
|
|
128
163
|
}
|
|
129
164
|
/**
|
|
130
165
|
* Creates the Promise verbs over one registry.
|
|
@@ -143,8 +178,8 @@ export interface Loader {
|
|
|
143
178
|
* import { Loader, RequestRegistry } from "raf-effect-atom"
|
|
144
179
|
*
|
|
145
180
|
* const loader = Loader.make(RequestRegistry.make())
|
|
146
|
-
* loader.prefetch(
|
|
147
|
-
* const
|
|
181
|
+
* const comments = loader.prefetch(commentPage, 1, 0)
|
|
182
|
+
* const issueHandle = await loader.ensure(issue, 1)
|
|
148
183
|
* ```
|
|
149
184
|
*
|
|
150
185
|
* @stability unstable
|
package/dist/Loader.mjs
CHANGED
|
@@ -2,6 +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
6
|
import { isRequestRegistry } from "./RequestRegistry.mjs";
|
|
6
7
|
import * as Atom from "effect/reactivity/Atom";
|
|
7
8
|
import * as Predicate from "effect/Predicate";
|
|
@@ -17,9 +18,11 @@ import * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
|
17
18
|
* Starts atoms from route loaders, before anything renders, so nothing
|
|
18
19
|
* waterfalls.
|
|
19
20
|
*
|
|
20
|
-
* Each verb
|
|
21
|
-
*
|
|
22
|
-
*
|
|
21
|
+
* Each verb takes a source, an atom family or an `AsyncResult` atom, with its
|
|
22
|
+
* arguments. It starts the atom in the registry, takes a lease on it, so the
|
|
23
|
+
* registry's idle TTL can't evict it before the route renders and
|
|
24
|
+
* `useAtomResult` takes it over, and returns its handle for the component
|
|
25
|
+
* that reads it:
|
|
23
26
|
*
|
|
24
27
|
* | Verb | Starts the atom | Waits | Has a value already |
|
|
25
28
|
* | ---------- | ------------------------------- | ----------------------------- | --------------------- |
|
|
@@ -27,9 +30,10 @@ import * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
|
27
30
|
* | `ensure` | if it isn't in the registry | only while it's `Initial` | returns it at once |
|
|
28
31
|
* | `fetch` | if it isn't, or refreshes it | until the new result settles | refreshes, then waits |
|
|
29
32
|
*
|
|
30
|
-
* Typed failures don't reject: `ensure` and `fetch`
|
|
31
|
-
* `AsyncResult`, so a loader can redirect on a
|
|
32
|
-
* boundary render the failure. Only defects and
|
|
33
|
+
* Typed failures don't reject: `ensure` and `fetch` settle on them, and
|
|
34
|
+
* `result` returns the settled `AsyncResult`, so a loader can redirect on a
|
|
35
|
+
* tag and otherwise let a boundary render the failure. Only defects and
|
|
36
|
+
* interruptions reject.
|
|
33
37
|
*
|
|
34
38
|
* The verbs are Effects that need the `AtomRegistry`; `make` gives the same
|
|
35
39
|
* verbs as Promises over one registry, for loaders that are async functions.
|
|
@@ -42,7 +46,8 @@ var Loader_exports = /* @__PURE__ */ __exportAll({
|
|
|
42
46
|
ensure: () => ensure,
|
|
43
47
|
fetch: () => fetch,
|
|
44
48
|
make: () => make,
|
|
45
|
-
prefetch: () => prefetch
|
|
49
|
+
prefetch: () => prefetch,
|
|
50
|
+
result: () => result
|
|
46
51
|
});
|
|
47
52
|
/**
|
|
48
53
|
* How long a loader's lease keeps an atom mounted when no component takes it
|
|
@@ -98,7 +103,7 @@ const rejectDefects = (result) => {
|
|
|
98
103
|
return Effect.succeed(result);
|
|
99
104
|
};
|
|
100
105
|
/**
|
|
101
|
-
* Starts
|
|
106
|
+
* Starts a source's atom without waiting for it, and returns its handle.
|
|
102
107
|
*
|
|
103
108
|
* **When to use**
|
|
104
109
|
*
|
|
@@ -108,7 +113,8 @@ const rejectDefects = (result) => {
|
|
|
108
113
|
* **Details**
|
|
109
114
|
*
|
|
110
115
|
* Starts the atom if it isn't in the registry, and leaves it alone if it is.
|
|
111
|
-
* Takes a lease either way.
|
|
116
|
+
* Takes a lease either way. The component that reads the handle suspends
|
|
117
|
+
* until the result settles.
|
|
112
118
|
*
|
|
113
119
|
* **Example**
|
|
114
120
|
*
|
|
@@ -116,19 +122,26 @@ const rejectDefects = (result) => {
|
|
|
116
122
|
* import { Effect } from "effect"
|
|
117
123
|
* import { Loader } from "raf-effect-atom"
|
|
118
124
|
*
|
|
119
|
-
* const program = Loader.prefetch(
|
|
125
|
+
* const program = Loader.prefetch(commentPage, issueId, 0)
|
|
120
126
|
* ```
|
|
121
127
|
*
|
|
122
128
|
* @stability unstable
|
|
123
129
|
* @category verbs
|
|
124
130
|
* @since 0.1.0
|
|
125
131
|
*/
|
|
126
|
-
const prefetch = Effect.
|
|
132
|
+
const prefetch = (source, ...args) => Effect.gen(function* () {
|
|
133
|
+
const atom = atomOf(source, args);
|
|
127
134
|
yield* start(atom);
|
|
135
|
+
return make$1(args, atom);
|
|
128
136
|
});
|
|
137
|
+
const loaded = (args, atom, result) => {
|
|
138
|
+
const handle = loaded$1(args, atom);
|
|
139
|
+
results.set(handle, result);
|
|
140
|
+
return handle;
|
|
141
|
+
};
|
|
129
142
|
/**
|
|
130
|
-
* Starts
|
|
131
|
-
*
|
|
143
|
+
* Starts a source's atom, waits only while it has no result, and returns a
|
|
144
|
+
* handle whose reader renders without suspending.
|
|
132
145
|
*
|
|
133
146
|
* **When to use**
|
|
134
147
|
*
|
|
@@ -137,10 +150,11 @@ const prefetch = Effect.fnUntraced(function* (atom) {
|
|
|
137
150
|
*
|
|
138
151
|
* **Details**
|
|
139
152
|
*
|
|
140
|
-
* An atom with a result returns
|
|
153
|
+
* An atom with a result returns at once, without refetching. On a request
|
|
141
154
|
* 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
|
|
143
|
-
*
|
|
155
|
+
* renders a result that's about to change. A typed failure settles it like
|
|
156
|
+
* a success: `result` returns it. A defect or an interruption fails the
|
|
157
|
+
* Effect.
|
|
144
158
|
*
|
|
145
159
|
* **Example**
|
|
146
160
|
*
|
|
@@ -150,7 +164,8 @@ const prefetch = Effect.fnUntraced(function* (atom) {
|
|
|
150
164
|
* import { Loader } from "raf-effect-atom"
|
|
151
165
|
*
|
|
152
166
|
* const program = Effect.gen(function* () {
|
|
153
|
-
* const
|
|
167
|
+
* const handle = yield* Loader.ensure(issue, 1)
|
|
168
|
+
* const result = Loader.result(handle)
|
|
154
169
|
* return AsyncResult.isSuccess(result) ? result.value.title : "missing"
|
|
155
170
|
* })
|
|
156
171
|
* ```
|
|
@@ -159,13 +174,16 @@ const prefetch = Effect.fnUntraced(function* (atom) {
|
|
|
159
174
|
* @category verbs
|
|
160
175
|
* @since 0.1.0
|
|
161
176
|
*/
|
|
162
|
-
const ensure = Effect.
|
|
177
|
+
const ensure = (source, ...args) => Effect.gen(function* () {
|
|
178
|
+
const atom = atomOf(source, args);
|
|
163
179
|
const started = yield* start(atom);
|
|
164
|
-
|
|
180
|
+
const result = yield* settled(started.registry, started.target, started.request ? isSettled : AsyncResult.isNotInitial);
|
|
181
|
+
return loaded(args, atom, result);
|
|
165
182
|
});
|
|
166
183
|
/**
|
|
167
|
-
* Starts
|
|
168
|
-
*
|
|
184
|
+
* Starts a source's atom, or refreshes it if it has a result, waits for the
|
|
185
|
+
* new result to settle, and returns a handle whose reader renders without
|
|
186
|
+
* suspending.
|
|
169
187
|
*
|
|
170
188
|
* **When to use**
|
|
171
189
|
*
|
|
@@ -174,22 +192,60 @@ const ensure = Effect.fnUntraced(function* (atom) {
|
|
|
174
192
|
*
|
|
175
193
|
* **Details**
|
|
176
194
|
*
|
|
177
|
-
* Waits until the result settles and isn't refreshing. A typed failure
|
|
178
|
-
*
|
|
195
|
+
* Waits until the result settles and isn't refreshing. A typed failure
|
|
196
|
+
* settles it like a success: `result` returns it. A defect or an
|
|
197
|
+
* interruption fails the Effect.
|
|
179
198
|
*
|
|
180
199
|
* @stability unstable
|
|
181
200
|
* @category verbs
|
|
182
201
|
* @since 0.1.0
|
|
183
202
|
*/
|
|
184
|
-
const fetch = Effect.
|
|
203
|
+
const fetch = (source, ...args) => Effect.gen(function* () {
|
|
204
|
+
const atom = atomOf(source, args);
|
|
185
205
|
const started = yield* start(atom);
|
|
186
206
|
const { registry, target } = started;
|
|
187
207
|
if (started.existed && isSettled(registry.get(target))) {
|
|
188
208
|
const node = registry.getNodes().get(keyOf(target));
|
|
189
209
|
registry.refresh(node?.atom ?? target);
|
|
190
210
|
}
|
|
191
|
-
|
|
211
|
+
const result = yield* settled(registry, target, isSettled);
|
|
212
|
+
return loaded(args, atom, result);
|
|
192
213
|
});
|
|
214
|
+
const results = /* @__PURE__ */ new WeakMap();
|
|
215
|
+
/**
|
|
216
|
+
* Returns the settled result `ensure` or `fetch` waited for.
|
|
217
|
+
*
|
|
218
|
+
* **When to use**
|
|
219
|
+
*
|
|
220
|
+
* Use in the loader that ran the verb, to branch on the result: redirect on
|
|
221
|
+
* a tag, or build another atom's arguments from it. Components read the
|
|
222
|
+
* handle with `useAtomResult`, which follows the registry.
|
|
223
|
+
*
|
|
224
|
+
* **Gotchas**
|
|
225
|
+
*
|
|
226
|
+
* Only the handle object the verb returned has it: a handle that crossed
|
|
227
|
+
* the network, as loader data or a server component's prop, is a copy, and
|
|
228
|
+
* this throws for it.
|
|
229
|
+
*
|
|
230
|
+
* **Example**
|
|
231
|
+
*
|
|
232
|
+
* ```ts
|
|
233
|
+
* import { AsyncResult } from "effect/reactivity"
|
|
234
|
+
* import { Loader } from "raf-effect-atom"
|
|
235
|
+
*
|
|
236
|
+
* const handle = await atoms.ensure(issue, id)
|
|
237
|
+
* const error = AsyncResult.error(Loader.result(handle))
|
|
238
|
+
* ```
|
|
239
|
+
*
|
|
240
|
+
* @stability unstable
|
|
241
|
+
* @category getters
|
|
242
|
+
* @since 0.1.0
|
|
243
|
+
*/
|
|
244
|
+
const result = (handle) => {
|
|
245
|
+
const settledResult = results.get(handle);
|
|
246
|
+
if (settledResult === void 0) throw new Error("raf-effect-atom: Loader.result got a handle that no ensure or fetch returned here. Only the code that ran the verb has its result: a handle that crossed the network is a copy. Read it with useAtomResult instead.");
|
|
247
|
+
return settledResult;
|
|
248
|
+
};
|
|
193
249
|
/**
|
|
194
250
|
* Creates the Promise verbs over one registry.
|
|
195
251
|
*
|
|
@@ -207,8 +263,8 @@ const fetch = Effect.fnUntraced(function* (atom) {
|
|
|
207
263
|
* import { Loader, RequestRegistry } from "raf-effect-atom"
|
|
208
264
|
*
|
|
209
265
|
* const loader = Loader.make(RequestRegistry.make())
|
|
210
|
-
* loader.prefetch(
|
|
211
|
-
* const
|
|
266
|
+
* const comments = loader.prefetch(commentPage, 1, 0)
|
|
267
|
+
* const issueHandle = await loader.ensure(issue, 1)
|
|
212
268
|
* ```
|
|
213
269
|
*
|
|
214
270
|
* @stability unstable
|
|
@@ -222,11 +278,9 @@ const make = (registry, options) => {
|
|
|
222
278
|
return leaseTTL === void 0 ? provided : Effect.provideService(provided, LeaseTTL, leaseTTL);
|
|
223
279
|
};
|
|
224
280
|
return {
|
|
225
|
-
ensure: (
|
|
226
|
-
fetch: (
|
|
227
|
-
prefetch: (
|
|
228
|
-
Effect.runSync(provide(prefetch(atom)));
|
|
229
|
-
}
|
|
281
|
+
ensure: (source, ...args) => Effect.runPromise(provide(ensure(source, ...args))),
|
|
282
|
+
fetch: (source, ...args) => Effect.runPromise(provide(fetch(source, ...args))),
|
|
283
|
+
prefetch: (source, ...args) => Effect.runSync(provide(prefetch(source, ...args)))
|
|
230
284
|
};
|
|
231
285
|
};
|
|
232
|
-
export { LeaseTTL, Loader_exports, ensure, fetch, make, prefetch };
|
|
286
|
+
export { LeaseTTL, Loader_exports, ensure, fetch, make, prefetch, result };
|
package/dist/Next.d.mts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ArgsOf, Handle, LoadedHandle, Source } from "./Handle.mjs";
|
|
2
2
|
import { HydrateAtoms } from "./NextClient.mjs";
|
|
3
3
|
import { RegistryProvider } from "./RegistryProvider.mjs";
|
|
4
|
-
import * as Atom from "effect/reactivity/Atom";
|
|
5
4
|
import * as EffectHydration from "effect/reactivity/Hydration";
|
|
6
|
-
import * as AsyncResult from "effect/reactivity/AsyncResult";
|
|
7
5
|
import * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
8
6
|
/**
|
|
9
7
|
* Returns the request registry of the server render in progress.
|
|
@@ -25,31 +23,32 @@ import * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
|
25
23
|
*/
|
|
26
24
|
export declare const getRegistry: () => AtomRegistry.AtomRegistry;
|
|
27
25
|
/**
|
|
28
|
-
* `Loader.prefetch` on the request's registry: starts
|
|
26
|
+
* `Loader.prefetch` on the request's registry: starts a source's atom, to
|
|
27
|
+
* stream it, and returns its handle.
|
|
29
28
|
*
|
|
30
29
|
* @stability unstable
|
|
31
30
|
* @category verbs
|
|
32
31
|
* @since 0.1.0
|
|
33
32
|
*/
|
|
34
|
-
export declare const prefetch: <
|
|
33
|
+
export declare const prefetch: <S extends Source>(source: S, ...args: ArgsOf<S>) => Handle<S>;
|
|
35
34
|
/**
|
|
36
|
-
* `Loader.ensure` on the request's registry: starts
|
|
37
|
-
*
|
|
35
|
+
* `Loader.ensure` on the request's registry: starts a source's atom, waits
|
|
36
|
+
* for its result, and returns its handle.
|
|
38
37
|
*
|
|
39
38
|
* @stability unstable
|
|
40
39
|
* @category verbs
|
|
41
40
|
* @since 0.1.0
|
|
42
41
|
*/
|
|
43
|
-
export declare const ensure: <
|
|
42
|
+
export declare const ensure: <S extends Source>(source: S, ...args: ArgsOf<S>) => Promise<LoadedHandle<S>>;
|
|
44
43
|
/**
|
|
45
|
-
* `Loader.fetch` on the request's registry: starts or refreshes
|
|
46
|
-
*
|
|
44
|
+
* `Loader.fetch` on the request's registry: starts or refreshes a source's
|
|
45
|
+
* atom, waits for the new result, and returns its handle.
|
|
47
46
|
*
|
|
48
47
|
* @stability unstable
|
|
49
48
|
* @category verbs
|
|
50
49
|
* @since 0.1.0
|
|
51
50
|
*/
|
|
52
|
-
export declare const fetch: <
|
|
51
|
+
export declare const fetch: <S extends Source>(source: S, ...args: ArgsOf<S>) => Promise<LoadedHandle<S>>;
|
|
53
52
|
/**
|
|
54
53
|
* `Hydration.dehydrate` on the request's registry, for `HydrateAtoms`.
|
|
55
54
|
*
|
package/dist/Next.mjs
CHANGED
|
@@ -22,7 +22,8 @@ import { RegistryProvider } from "./RegistryProvider.mjs";
|
|
|
22
22
|
*/
|
|
23
23
|
const getRegistry = getRegistry$1;
|
|
24
24
|
/**
|
|
25
|
-
* `Loader.prefetch` on the request's registry: starts
|
|
25
|
+
* `Loader.prefetch` on the request's registry: starts a source's atom, to
|
|
26
|
+
* stream it, and returns its handle.
|
|
26
27
|
*
|
|
27
28
|
* @stability unstable
|
|
28
29
|
* @category verbs
|
|
@@ -30,8 +31,8 @@ const getRegistry = getRegistry$1;
|
|
|
30
31
|
*/
|
|
31
32
|
const prefetch = prefetch$1;
|
|
32
33
|
/**
|
|
33
|
-
* `Loader.ensure` on the request's registry: starts
|
|
34
|
-
*
|
|
34
|
+
* `Loader.ensure` on the request's registry: starts a source's atom, waits
|
|
35
|
+
* for its result, and returns its handle.
|
|
35
36
|
*
|
|
36
37
|
* @stability unstable
|
|
37
38
|
* @category verbs
|
|
@@ -39,8 +40,8 @@ const prefetch = prefetch$1;
|
|
|
39
40
|
*/
|
|
40
41
|
const ensure = ensure$1;
|
|
41
42
|
/**
|
|
42
|
-
* `Loader.fetch` on the request's registry: starts or refreshes
|
|
43
|
-
*
|
|
43
|
+
* `Loader.fetch` on the request's registry: starts or refreshes a source's
|
|
44
|
+
* atom, waits for the new result, and returns its handle.
|
|
44
45
|
*
|
|
45
46
|
* @stability unstable
|
|
46
47
|
* @category verbs
|
package/dist/NextClient.d.mts
CHANGED
|
@@ -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
|
|
20
|
-
* await ensure(issue(id))
|
|
19
|
+
* const handle = await ensure(issue, Number((await props.params).id))
|
|
21
20
|
* return (
|
|
22
21
|
* <HydrateAtoms state={dehydrate()}>
|
|
23
|
-
* <Issue
|
|
22
|
+
* <Issue issue={handle} />
|
|
24
23
|
* </HydrateAtoms>
|
|
25
24
|
* )
|
|
26
25
|
* }
|
package/dist/NextClient.mjs
CHANGED
|
@@ -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
|
|
28
|
-
* await ensure(issue(id))
|
|
27
|
+
* const handle = await ensure(issue, Number((await props.params).id))
|
|
29
28
|
* return (
|
|
30
29
|
* <HydrateAtoms state={dehydrate()}>
|
|
31
|
-
* <Issue
|
|
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
|
|
44
|
+
* <IssuePage issue={loaderData.issue} />
|
|
45
45
|
* </HydrationBoundary>
|
|
46
46
|
* ```
|
|
47
47
|
*
|
package/dist/ReactHydration.mjs
CHANGED
|
@@ -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
|
|
45
|
+
* <IssuePage issue={loaderData.issue} />
|
|
46
46
|
* </HydrationBoundary>
|
|
47
47
|
* ```
|
|
48
48
|
*
|
package/dist/ReactRouter.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Loader } from "./Loader.mjs";
|
|
2
1
|
import { RegistryProvider } from "./RegistryProvider.mjs";
|
|
2
|
+
import { Loader } from "./Loader.mjs";
|
|
3
3
|
import * as EffectHydration from "effect/reactivity/Hydration";
|
|
4
4
|
import * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
5
5
|
/**
|
package/dist/ReactRouter.mjs
CHANGED
|
@@ -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
|
|
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
|
|
40
|
+
* <Issue issue={props.loaderData.issue} />
|
|
41
41
|
* </HydrationBoundary>
|
|
42
42
|
* )
|
|
43
43
|
* }
|
package/dist/Waterfalls.d.mts
CHANGED
|
@@ -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
|
|
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
|
|
78
|
-
* `
|
|
79
|
-
*
|
|
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
|
|
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: <
|
|
99
|
+
export declare const useWaterfall: <S extends Source>(source: S, ...args: ArgsOf<S>) => Handle<S>;
|
package/dist/Waterfalls.mjs
CHANGED
|
@@ -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
|
|
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
|
|
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.
|
|
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
|
|
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 = (
|
|
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(
|
|
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 {
|
|
1
|
+
import { ArgsOf, ErrorOf, Handle, HandleKey, LoadedHandle, Source, SuccessOf } from "./Handle.mjs";
|
|
2
2
|
import { HydrationBoundary, HydrationBoundaryProps } from "./ReactHydration.mjs";
|
|
3
|
+
import { Loader_d_exports } from "./Loader.mjs";
|
|
3
4
|
import { Hydration_d_exports } from "./Hydration.mjs";
|
|
4
5
|
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 };
|
package/dist/internal/next.mjs
CHANGED
|
@@ -9,8 +9,8 @@ const getRegistry = React.cache(() => {
|
|
|
9
9
|
return registry;
|
|
10
10
|
});
|
|
11
11
|
const getLoader = React.cache(() => make$1(getRegistry()));
|
|
12
|
-
const prefetch = (
|
|
13
|
-
const ensure = (
|
|
14
|
-
const fetch = (
|
|
12
|
+
const prefetch = (source, ...args) => getLoader().prefetch(source, ...args);
|
|
13
|
+
const ensure = (source, ...args) => getLoader().ensure(source, ...args);
|
|
14
|
+
const fetch = (source, ...args) => getLoader().fetch(source, ...args);
|
|
15
15
|
const dehydrate = () => dehydrate$1(getRegistry());
|
|
16
16
|
export { dehydrate, ensure, fetch, getRegistry, prefetch };
|
|
@@ -38,7 +38,7 @@ const nameOf = (atom) => {
|
|
|
38
38
|
const key = keyOf(atom);
|
|
39
39
|
return atom.label?.[0] ?? (Predicate.isString(key) ? key : "an atom");
|
|
40
40
|
};
|
|
41
|
-
const message = (read, started) => `raf-effect-atom: rendering ${read === started ? `read ${read}` : `read ${read}, which started ${started}`} before anything else had started it, so its request waits for this render: a request waterfall. Start it before render,
|
|
41
|
+
const message = (read, started) => `raf-effect-atom: rendering ${read === started ? `read ${read}` : `read ${read}, which started ${started}`} before anything else had started it, so its request waits for this render: a request waterfall. Start it before render and pass its handle down, from the route's loader or the server component (atoms.prefetch(source, ...args) or atoms.ensure). If the state that picks it, such as a page, a filter or an open dialog, isn't in the URL, put it there so the loader can. As escape hatches for state that can't be in the URL: start it in the event handler that changes the state (Loader.make(registry).prefetch), or, if its input only exists in render, read it with useAtomResult(source, useWaterfall(source, ...args)). ${docs}`;
|
|
42
42
|
const warned = /* @__PURE__ */ new WeakMap();
|
|
43
43
|
/**
|
|
44
44
|
* The nodes a read started in throw mode, with the error. React renders a
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "raf-effect-atom",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.0",
|
|
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.
|
|
61
|
+
"effect-atom-boundary": "0.1.0",
|
|
62
62
|
"happy-dom": "21.0.4",
|
|
63
63
|
"react": "19.3.1",
|
|
64
64
|
"react-dom": "19.3.1",
|