raf-effect-atom 0.3.1 → 0.3.2
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 +13 -7
- package/dist/Loader.d.mts +7 -4
- package/dist/Loader.mjs +7 -4
- package/dist/internal/leases.mjs +36 -15
- package/dist/use-atom-result.d.mts +5 -2
- package/dist/use-atom-result.mjs +8 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,25 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.2](https://github.com/KATT/raf-effect-atom/compare/raf-effect-atom%400.3.1...raf-effect-atom%400.3.2) (2026-10-24)
|
|
4
|
+
|
|
5
|
+
### Bug Fixes
|
|
6
|
+
|
|
7
|
+
* **raf-effect-atom:** take no lease on an atom a mounted component reads ([899d0b7](https://github.com/KATT/raf-effect-atom/commit/899d0b7c70f6836c41c4416b0c2063c1ccf3d8b2))
|
|
8
|
+
|
|
3
9
|
## [0.3.1](https://github.com/KATT/raf-effect-atom/compare/raf-effect-atom%400.3.0...raf-effect-atom%400.3.1) (2026-10-24)
|
|
4
10
|
|
|
5
11
|
### Features
|
|
6
12
|
|
|
7
|
-
|
|
13
|
+
- **raf-effect-atom:** add useHandleValue, a handle read that doesn't suspend ([4b083f7](https://github.com/KATT/raf-effect-atom/commit/4b083f7dee91834cda71691f44a1498061f3cfe1))
|
|
8
14
|
|
|
9
15
|
### Code Refactoring
|
|
10
16
|
|
|
11
|
-
|
|
17
|
+
- name files in kebab-case, PascalCase only for Effect modules ([d76b06a](https://github.com/KATT/raf-effect-atom/commit/d76b06aef3d04d6be6c0b0e086a756234ab3f253))
|
|
12
18
|
|
|
13
19
|
### Documentation
|
|
14
20
|
|
|
15
|
-
|
|
16
|
-
|
|
21
|
+
- **raf-effect-atom:** pass a read's result to a boundary's hook in the README and JSDoc ([fd82e5c](https://github.com/KATT/raf-effect-atom/commit/fd82e5c1f92b3f99dc166140383837b11b695ca7))
|
|
22
|
+
- **raf-effect-atom:** pass the atom to a boundary's hook in the README and JSDoc ([772d349](https://github.com/KATT/raf-effect-atom/commit/772d3494954f64a5c0fc92ebfd146b5f14e16e51))
|
|
17
23
|
|
|
18
24
|
### Tests
|
|
19
25
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
- **raf-effect-atom:** pass a read's result to a boundary's hook ([df11247](https://github.com/KATT/raf-effect-atom/commit/df11247edab69b060c0677059ae24a4d8835c28e))
|
|
27
|
+
- **raf-effect-atom:** read through a boundary's hook with the atom ([de19b85](https://github.com/KATT/raf-effect-atom/commit/de19b8502ef41a8efbd9b50da6e00843cb38f2fd))
|
|
28
|
+
- **raf-effect-atom:** start an Atom.swr over a keyed readable that picks a seed or a live query ([697e785](https://github.com/KATT/raf-effect-atom/commit/697e78550bff0dce02903b963677828786770338))
|
|
23
29
|
|
|
24
30
|
## [0.3.0](https://github.com/KATT/raf-effect-atom/compare/raf-effect-atom%400.2.6...raf-effect-atom%400.3.0) (2026-10-23)
|
|
25
31
|
|
package/dist/Loader.d.mts
CHANGED
|
@@ -21,9 +21,11 @@ export type SettledResult<A, E> = AsyncResult.Failure<A, E> | AsyncResult.Succes
|
|
|
21
21
|
*
|
|
22
22
|
* **Details**
|
|
23
23
|
*
|
|
24
|
-
* A lease ends when `useAtomResult` mounts the atom, or
|
|
25
|
-
* passed, after which the registry's idle TTL applies as
|
|
26
|
-
*
|
|
24
|
+
* A lease ends when `useAtomResult` or `useHandleValue` mounts the atom, or
|
|
25
|
+
* when this time has passed, after which the registry's idle TTL applies as
|
|
26
|
+
* usual. While a component reads the atom, a verb takes no lease on it, so the
|
|
27
|
+
* atom goes idle when the last reader unmounts. On a request registry, leases
|
|
28
|
+
* last until the registry is disposed.
|
|
27
29
|
*
|
|
28
30
|
* @stability unstable
|
|
29
31
|
* @category references
|
|
@@ -41,7 +43,8 @@ export declare const LeaseTTL: Context.Reference<Duration.Duration>;
|
|
|
41
43
|
* **Details**
|
|
42
44
|
*
|
|
43
45
|
* - Starts the atom if it isn't in the registry, and leaves it alone if it
|
|
44
|
-
* is. Takes a lease either way
|
|
46
|
+
* is. Takes a lease either way, unless a mounted component reads the
|
|
47
|
+
* atom. The component that reads the handle
|
|
45
48
|
* suspends until the result settles. On a request registry whose async
|
|
46
49
|
* seed is still running, it waits for the seed first.
|
|
47
50
|
* - A derived atom that isn't serializable starts the first serializable
|
package/dist/Loader.mjs
CHANGED
|
@@ -60,9 +60,11 @@ var Loader_exports = /* @__PURE__ */ __exportAll({
|
|
|
60
60
|
*
|
|
61
61
|
* **Details**
|
|
62
62
|
*
|
|
63
|
-
* A lease ends when `useAtomResult` mounts the atom, or
|
|
64
|
-
* passed, after which the registry's idle TTL applies as
|
|
65
|
-
*
|
|
63
|
+
* A lease ends when `useAtomResult` or `useHandleValue` mounts the atom, or
|
|
64
|
+
* when this time has passed, after which the registry's idle TTL applies as
|
|
65
|
+
* usual. While a component reads the atom, a verb takes no lease on it, so the
|
|
66
|
+
* atom goes idle when the last reader unmounts. On a request registry, leases
|
|
67
|
+
* last until the registry is disposed.
|
|
66
68
|
*
|
|
67
69
|
* @stability unstable
|
|
68
70
|
* @category references
|
|
@@ -122,7 +124,8 @@ const rejectDefects = (result) => {
|
|
|
122
124
|
* **Details**
|
|
123
125
|
*
|
|
124
126
|
* - Starts the atom if it isn't in the registry, and leaves it alone if it
|
|
125
|
-
* is. Takes a lease either way
|
|
127
|
+
* is. Takes a lease either way, unless a mounted component reads the
|
|
128
|
+
* atom. The component that reads the handle
|
|
126
129
|
* suspends until the result settles. On a request registry whose async
|
|
127
130
|
* seed is still running, it waits for the seed first.
|
|
128
131
|
* - A derived atom that isn't serializable starts the first serializable
|
package/dist/internal/leases.mjs
CHANGED
|
@@ -1,39 +1,60 @@
|
|
|
1
1
|
import * as Atom from "effect/reactivity/Atom";
|
|
2
2
|
// src/internal/leases.ts
|
|
3
|
-
const
|
|
3
|
+
const registries = /* @__PURE__ */ new WeakMap();
|
|
4
|
+
const leasesOf = (registry) => {
|
|
5
|
+
let state = registries.get(registry);
|
|
6
|
+
if (state === void 0) {
|
|
7
|
+
state = {
|
|
8
|
+
leases: /* @__PURE__ */ new Map(),
|
|
9
|
+
readers: /* @__PURE__ */ new Map()
|
|
10
|
+
};
|
|
11
|
+
registries.set(registry, state);
|
|
12
|
+
}
|
|
13
|
+
return state;
|
|
14
|
+
};
|
|
4
15
|
/** The registry's key for an atom: its serialization key, or the atom. */
|
|
5
16
|
const keyOf = (atom) => Atom.isSerializable(atom) ? atom[Atom.SerializableTypeId].key : atom;
|
|
6
17
|
/**
|
|
7
18
|
* Mounts `atom` until `release`, or until `ttl` milliseconds pass. Taking a
|
|
8
|
-
* lease the atom already has restarts its timer.
|
|
19
|
+
* lease the atom already has restarts its timer. While a component holds the
|
|
20
|
+
* atom (see `hold`), it takes none: the component keeps the atom mounted, and
|
|
21
|
+
* a lease would keep it mounted after the component unmounts.
|
|
9
22
|
*/
|
|
10
23
|
const take = (registry, atom, ttl) => {
|
|
11
|
-
|
|
12
|
-
if (byKey === void 0) {
|
|
13
|
-
byKey = /* @__PURE__ */ new Map();
|
|
14
|
-
leases.set(registry, byKey);
|
|
15
|
-
}
|
|
24
|
+
const state = leasesOf(registry);
|
|
16
25
|
const key = keyOf(atom);
|
|
17
|
-
|
|
26
|
+
if (state.readers.has(key)) return;
|
|
27
|
+
let lease = state.leases.get(key);
|
|
18
28
|
if (lease === void 0) {
|
|
19
29
|
const unmount = registry.mount(atom);
|
|
20
|
-
const owner = byKey;
|
|
21
30
|
const created = {
|
|
22
31
|
release: () => {
|
|
23
32
|
clearTimeout(created.timer);
|
|
24
|
-
|
|
33
|
+
state.leases.delete(key);
|
|
25
34
|
unmount();
|
|
26
35
|
},
|
|
27
36
|
timer: void 0
|
|
28
37
|
};
|
|
29
|
-
|
|
38
|
+
state.leases.set(key, created);
|
|
30
39
|
lease = created;
|
|
31
40
|
}
|
|
32
41
|
clearTimeout(lease.timer);
|
|
33
42
|
lease.timer = ttl === void 0 ? void 0 : setTimeout(lease.release, ttl);
|
|
34
43
|
};
|
|
35
|
-
/**
|
|
36
|
-
|
|
37
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Marks `atom` as held by a component subscribed to it, until the returned
|
|
46
|
+
* function runs. Ends the atom's lease, and `take` takes none while any
|
|
47
|
+
* component holds it.
|
|
48
|
+
*/
|
|
49
|
+
const hold = (registry, atom) => {
|
|
50
|
+
const state = leasesOf(registry);
|
|
51
|
+
const key = keyOf(atom);
|
|
52
|
+
state.readers.set(key, (state.readers.get(key) ?? 0) + 1);
|
|
53
|
+
state.leases.get(key)?.release();
|
|
54
|
+
return () => {
|
|
55
|
+
const count = (state.readers.get(key) ?? 1) - 1;
|
|
56
|
+
if (count === 0) state.readers.delete(key);
|
|
57
|
+
else state.readers.set(key, count);
|
|
58
|
+
};
|
|
38
59
|
};
|
|
39
|
-
export {
|
|
60
|
+
export { hold, keyOf, take };
|
|
@@ -34,7 +34,8 @@ type Readable<S> = S extends ((...args: infer Args) => Atom.Atom<AsyncResult.Asy
|
|
|
34
34
|
* instead of started, so the browser doesn't request it a second time.
|
|
35
35
|
* - The atom is read through `hydrated`, so a hydrated result is kept.
|
|
36
36
|
* - Once the component mounts, the loader's lease ends and the component
|
|
37
|
-
* keeps the atom mounted.
|
|
37
|
+
* keeps the atom mounted. A loader that starts the atom again while it's
|
|
38
|
+
* mounted takes no lease, so the atom goes idle when the component unmounts.
|
|
38
39
|
* - A derived atom is read through the first serializable atom its
|
|
39
40
|
* `initialValueTarget` chain points at: that one is awaited, hydrated and
|
|
40
41
|
* released, so the derived atom doesn't request it again.
|
|
@@ -109,7 +110,9 @@ export declare const useAtomResult: <S extends Source>(source: S, handle: NoInfe
|
|
|
109
110
|
* - A failure is returned as it is, `waiting` while the atom refreshes, and
|
|
110
111
|
* so is a defect.
|
|
111
112
|
* - Once the component has subscribed to the atom, the loader's lease ends
|
|
112
|
-
* and the component keeps the atom mounted.
|
|
113
|
+
* and the component keeps the atom mounted. A loader that starts the atom
|
|
114
|
+
* again while it's mounted takes no lease, so the atom goes idle when the
|
|
115
|
+
* component unmounts.
|
|
113
116
|
* - A handle `Loader.prefetchInBrowser` returned on the server reads as
|
|
114
117
|
* `Initial` there, without starting the atom; the browser's read starts
|
|
115
118
|
* it.
|
package/dist/use-atom-result.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { get, stream } from "./internal/pending.mjs";
|
|
3
3
|
import { hydrated } from "./Hydration.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { hold, keyOf } from "./internal/leases.mjs";
|
|
5
5
|
import { atomOf, sourceOf } from "./internal/handles.mjs";
|
|
6
6
|
import { isRequestRegistry } from "./RequestRegistry.mjs";
|
|
7
7
|
import { requireRequestRegistry } from "./internal/registry.mjs";
|
|
@@ -58,7 +58,8 @@ const targetsOf = (atom, handle, hook) => {
|
|
|
58
58
|
* instead of started, so the browser doesn't request it a second time.
|
|
59
59
|
* - The atom is read through `hydrated`, so a hydrated result is kept.
|
|
60
60
|
* - Once the component mounts, the loader's lease ends and the component
|
|
61
|
-
* keeps the atom mounted.
|
|
61
|
+
* keeps the atom mounted. A loader that starts the atom again while it's
|
|
62
|
+
* mounted takes no lease, so the atom goes idle when the component unmounts.
|
|
62
63
|
* - A derived atom is read through the first serializable atom its
|
|
63
64
|
* `initialValueTarget` chain points at: that one is awaited, hydrated and
|
|
64
65
|
* released, so the derived atom doesn't request it again.
|
|
@@ -126,9 +127,7 @@ const useAtomResult = (source, handle) => {
|
|
|
126
127
|
includeFailure: true,
|
|
127
128
|
suspendOnWaiting: AsyncResult.isFailure(current) && current.waiting
|
|
128
129
|
});
|
|
129
|
-
React.useEffect(() =>
|
|
130
|
-
release(registry, started);
|
|
131
|
-
}, [registry, started]);
|
|
130
|
+
React.useEffect(() => hold(registry, started), [registry, started]);
|
|
132
131
|
if (AsyncResult.isFailure(result) && Option.isNone(Cause.findErrorOption(result.cause))) throw Cause.squash(result.cause);
|
|
133
132
|
return result;
|
|
134
133
|
};
|
|
@@ -156,7 +155,9 @@ const loading = Atom.make(AsyncResult.initial(true));
|
|
|
156
155
|
* - A failure is returned as it is, `waiting` while the atom refreshes, and
|
|
157
156
|
* so is a defect.
|
|
158
157
|
* - Once the component has subscribed to the atom, the loader's lease ends
|
|
159
|
-
* and the component keeps the atom mounted.
|
|
158
|
+
* and the component keeps the atom mounted. A loader that starts the atom
|
|
159
|
+
* again while it's mounted takes no lease, so the atom goes idle when the
|
|
160
|
+
* component unmounts.
|
|
160
161
|
* - A handle `Loader.prefetchInBrowser` returned on the server reads as
|
|
161
162
|
* `Initial` there, without starting the atom; the browser's read starts
|
|
162
163
|
* it.
|
|
@@ -214,7 +215,7 @@ const useHandleValue = (source, handle) => {
|
|
|
214
215
|
}
|
|
215
216
|
const result = useAtomValue(waiting ? loading : targets.read);
|
|
216
217
|
React.useEffect(() => {
|
|
217
|
-
if (!waiting)
|
|
218
|
+
if (!waiting) return hold(registry, started);
|
|
218
219
|
}, [
|
|
219
220
|
registry,
|
|
220
221
|
started,
|
package/package.json
CHANGED