@pumped-fn/lite-react 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +66 -0
- package/LICENSE +21 -0
- package/README.md +192 -0
- package/dist/index.cjs +173 -0
- package/dist/index.d.cts +90 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +90 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +145 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +71 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# @pumped-fn/lite-react
|
|
2
|
+
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 236aa4a: Rename packages to follow `lite-` prefix convention
|
|
8
|
+
|
|
9
|
+
**Breaking Change:** Package names have been renamed:
|
|
10
|
+
|
|
11
|
+
- `@pumped-fn/devtools` → `@pumped-fn/lite-devtools`
|
|
12
|
+
- `@pumped-fn/react-lite` → `@pumped-fn/lite-react`
|
|
13
|
+
- `@pumped-fn/vite-hmr` → `@pumped-fn/lite-hmr`
|
|
14
|
+
|
|
15
|
+
This establishes a consistent naming convention where all packages in the lite ecosystem use the `lite-` prefix.
|
|
16
|
+
|
|
17
|
+
**Migration:**
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Update your dependencies
|
|
21
|
+
pnpm remove @pumped-fn/devtools @pumped-fn/react-lite @pumped-fn/vite-hmr
|
|
22
|
+
pnpm add @pumped-fn/lite-devtools @pumped-fn/lite-react @pumped-fn/lite-hmr
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
// Update imports
|
|
27
|
+
- import { createDevtools } from '@pumped-fn/devtools'
|
|
28
|
+
+ import { createDevtools } from '@pumped-fn/lite-devtools'
|
|
29
|
+
|
|
30
|
+
- import { ScopeProvider, useAtom } from '@pumped-fn/react-lite'
|
|
31
|
+
+ import { ScopeProvider, useAtom } from '@pumped-fn/lite-react'
|
|
32
|
+
|
|
33
|
+
- import { pumpedHmr } from '@pumped-fn/vite-hmr'
|
|
34
|
+
+ import { pumpedHmr } from '@pumped-fn/lite-hmr'
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## 0.3.0
|
|
38
|
+
|
|
39
|
+
### Minor Changes
|
|
40
|
+
|
|
41
|
+
- a0362d7: ### Features
|
|
42
|
+
|
|
43
|
+
- Re-export `createScope`, `atom`, `flow`, `preset` from `@pumped-fn/lite` for convenience
|
|
44
|
+
- Update React peer dependency to support both React 18 and React 19 (`^18.0.0 || ^19.0.0`)
|
|
45
|
+
|
|
46
|
+
### Bug Fixes
|
|
47
|
+
|
|
48
|
+
- **Critical**: Fix Suspense infinite loop by caching pending promises (React expects same promise during re-renders)
|
|
49
|
+
- Auto-resolve idle atoms lazily instead of throwing error (more ergonomic)
|
|
50
|
+
- Subscribe only to `resolved` events instead of `*` to avoid unnecessary re-renders
|
|
51
|
+
|
|
52
|
+
## 0.2.0
|
|
53
|
+
|
|
54
|
+
### Minor Changes
|
|
55
|
+
|
|
56
|
+
- 1587c37: feat(lite-react): initial release of React integration for @pumped-fn/lite
|
|
57
|
+
|
|
58
|
+
Adds minimal React bindings with Suspense and ErrorBoundary integration:
|
|
59
|
+
|
|
60
|
+
- ScopeProvider and ScopeContext for scope provisioning
|
|
61
|
+
- useScope hook for accessing scope from context
|
|
62
|
+
- useController hook for obtaining memoized controllers
|
|
63
|
+
- useAtom hook with full Suspense/ErrorBoundary integration
|
|
64
|
+
- useSelect hook for fine-grained reactivity with custom equality
|
|
65
|
+
|
|
66
|
+
SSR-compatible, zero-tolerance for `any` types, comprehensive TSDoc.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License Copyright (c) 2025 Duke
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of
|
|
4
|
+
charge, to any person obtaining a copy of this software and associated
|
|
5
|
+
documentation files (the "Software"), to deal in the Software without
|
|
6
|
+
restriction, including without limitation the rights to use, copy, modify, merge,
|
|
7
|
+
publish, distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to the
|
|
9
|
+
following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice
|
|
12
|
+
(including the next paragraph) shall be included in all copies or substantial
|
|
13
|
+
portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
|
16
|
+
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
17
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
|
18
|
+
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
19
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
20
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# @pumped-fn/lite-react
|
|
2
|
+
|
|
3
|
+
React bindings for `@pumped-fn/lite` with Suspense and ErrorBoundary integration.
|
|
4
|
+
|
|
5
|
+
**Zero dependencies** · **<2KB bundle** · **React 18+**
|
|
6
|
+
|
|
7
|
+
## How It Works
|
|
8
|
+
|
|
9
|
+
```mermaid
|
|
10
|
+
sequenceDiagram
|
|
11
|
+
participant App
|
|
12
|
+
participant ScopeProvider
|
|
13
|
+
participant useAtom
|
|
14
|
+
participant Controller
|
|
15
|
+
|
|
16
|
+
App->>App: scope.resolve(atom)
|
|
17
|
+
App->>ScopeProvider: <ScopeProvider scope={scope}>
|
|
18
|
+
|
|
19
|
+
useAtom->>Controller: check ctrl.state
|
|
20
|
+
alt resolved
|
|
21
|
+
Controller-->>useAtom: value
|
|
22
|
+
useAtom->>Controller: subscribe to changes
|
|
23
|
+
else resolving
|
|
24
|
+
useAtom-->>App: throw Promise (Suspense)
|
|
25
|
+
else failed
|
|
26
|
+
useAtom-->>App: throw Error (ErrorBoundary)
|
|
27
|
+
else idle
|
|
28
|
+
useAtom-->>App: throw Error (not resolved)
|
|
29
|
+
end
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## State Handling
|
|
33
|
+
|
|
34
|
+
```mermaid
|
|
35
|
+
flowchart TD
|
|
36
|
+
Hook[useAtom/useSelect]
|
|
37
|
+
Hook --> State{ctrl.state?}
|
|
38
|
+
|
|
39
|
+
State -->|idle| AutoResolve[Auto-resolve + Throw Promise]
|
|
40
|
+
State -->|resolving| Promise[Throw cached Promise]
|
|
41
|
+
State -->|resolved| Value[Return value]
|
|
42
|
+
State -->|failed| Stored[Throw stored error]
|
|
43
|
+
|
|
44
|
+
AutoResolve --> Suspense[Suspense catches]
|
|
45
|
+
Promise --> Suspense
|
|
46
|
+
Stored --> ErrorBoundary[ErrorBoundary catches]
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
| State | Hook Behavior |
|
|
50
|
+
|-------|---------------|
|
|
51
|
+
| `idle` | Auto-resolves and suspends — Suspense shows fallback |
|
|
52
|
+
| `resolving` | Throws cached promise — Suspense shows fallback |
|
|
53
|
+
| `resolved` | Returns value, subscribes to changes |
|
|
54
|
+
| `failed` | Throws stored error — ErrorBoundary catches |
|
|
55
|
+
|
|
56
|
+
## API
|
|
57
|
+
|
|
58
|
+
### ScopeProvider
|
|
59
|
+
|
|
60
|
+
Provides scope to component tree.
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
import { createScope } from '@pumped-fn/lite'
|
|
64
|
+
import { ScopeProvider } from '@pumped-fn/lite-react'
|
|
65
|
+
|
|
66
|
+
const scope = createScope()
|
|
67
|
+
await scope.resolve(userAtom)
|
|
68
|
+
|
|
69
|
+
<ScopeProvider scope={scope}>
|
|
70
|
+
<App />
|
|
71
|
+
</ScopeProvider>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### useScope
|
|
75
|
+
|
|
76
|
+
Access scope from context.
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
const scope = useScope()
|
|
80
|
+
await scope.resolve(someAtom)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### useController
|
|
84
|
+
|
|
85
|
+
Get memoized controller for imperative operations.
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
const ctrl = useController(counterAtom)
|
|
89
|
+
ctrl.set(10)
|
|
90
|
+
ctrl.update(n => n + 1)
|
|
91
|
+
ctrl.invalidate()
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### useAtom
|
|
95
|
+
|
|
96
|
+
Subscribe to atom value with Suspense integration.
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
function UserProfile() {
|
|
100
|
+
const user = useAtom(userAtom)
|
|
101
|
+
return <div>{user.name}</div>
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Wrap with Suspense + ErrorBoundary
|
|
105
|
+
<ErrorBoundary fallback={<Error />}>
|
|
106
|
+
<Suspense fallback={<Loading />}>
|
|
107
|
+
<UserProfile />
|
|
108
|
+
</Suspense>
|
|
109
|
+
</ErrorBoundary>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### useSelect
|
|
113
|
+
|
|
114
|
+
Fine-grained selection — only re-renders when selected value changes.
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
const name = useSelect(userAtom, user => user.name)
|
|
118
|
+
const count = useSelect(todosAtom, todos => todos.length, (a, b) => a === b)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Invalidation
|
|
122
|
+
|
|
123
|
+
When an atom is invalidated, hooks automatically suspend during re-resolution:
|
|
124
|
+
|
|
125
|
+
```mermaid
|
|
126
|
+
sequenceDiagram
|
|
127
|
+
participant Component
|
|
128
|
+
participant useAtom
|
|
129
|
+
participant Controller
|
|
130
|
+
|
|
131
|
+
Note over Controller: state = resolved
|
|
132
|
+
Component->>useAtom: render (value)
|
|
133
|
+
|
|
134
|
+
Note over Controller: ctrl.invalidate()
|
|
135
|
+
Controller->>Controller: state = resolving
|
|
136
|
+
useAtom-->>Component: throw Promise
|
|
137
|
+
Note over Component: Suspense fallback
|
|
138
|
+
|
|
139
|
+
Controller->>Controller: factory runs
|
|
140
|
+
Controller->>Controller: state = resolved
|
|
141
|
+
useAtom->>Component: re-render (new value)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Testing
|
|
145
|
+
|
|
146
|
+
Use presets for test isolation:
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
import { createScope, preset } from '@pumped-fn/lite'
|
|
150
|
+
import { ScopeProvider } from '@pumped-fn/lite-react'
|
|
151
|
+
|
|
152
|
+
const scope = createScope({
|
|
153
|
+
presets: [preset(userAtom, { name: 'Test User' })]
|
|
154
|
+
})
|
|
155
|
+
await scope.resolve(userAtom)
|
|
156
|
+
|
|
157
|
+
render(
|
|
158
|
+
<ScopeProvider scope={scope}>
|
|
159
|
+
<UserProfile />
|
|
160
|
+
</ScopeProvider>
|
|
161
|
+
)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## SSR
|
|
165
|
+
|
|
166
|
+
SSR-compatible by design:
|
|
167
|
+
|
|
168
|
+
- No side effects on import
|
|
169
|
+
- Uses `useSyncExternalStore` with server snapshot
|
|
170
|
+
- Scope passed as prop (no global state)
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
// Server
|
|
174
|
+
const scope = createScope()
|
|
175
|
+
await scope.resolve(dataAtom)
|
|
176
|
+
const html = renderToString(<ScopeProvider scope={scope}><App /></ScopeProvider>)
|
|
177
|
+
|
|
178
|
+
// Client
|
|
179
|
+
const clientScope = createScope({
|
|
180
|
+
presets: [preset(dataAtom, window.__DATA__)]
|
|
181
|
+
})
|
|
182
|
+
await clientScope.resolve(dataAtom)
|
|
183
|
+
hydrateRoot(root, <ScopeProvider scope={clientScope}><App /></ScopeProvider>)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Full API
|
|
187
|
+
|
|
188
|
+
See [`dist/index.d.mts`](./dist/index.d.mts) for complete type definitions.
|
|
189
|
+
|
|
190
|
+
## License
|
|
191
|
+
|
|
192
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
let __pumped_fn_lite = require("@pumped-fn/lite");
|
|
2
|
+
let react = require("react");
|
|
3
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
4
|
+
|
|
5
|
+
//#region src/context.tsx
|
|
6
|
+
/**
|
|
7
|
+
* React context for Lite.Scope.
|
|
8
|
+
*/
|
|
9
|
+
const ScopeContext = (0, react.createContext)(null);
|
|
10
|
+
/**
|
|
11
|
+
* Provider component for Lite.Scope.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* <ScopeProvider scope={scope}>
|
|
16
|
+
* <App />
|
|
17
|
+
* </ScopeProvider>
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
function ScopeProvider({ scope, children }) {
|
|
21
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScopeContext.Provider, {
|
|
22
|
+
value: scope,
|
|
23
|
+
children
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/hooks.ts
|
|
29
|
+
const pendingPromises = /* @__PURE__ */ new WeakMap();
|
|
30
|
+
function getOrCreatePendingPromise(atom$1, ctrl) {
|
|
31
|
+
let pending = pendingPromises.get(atom$1);
|
|
32
|
+
if (!pending) {
|
|
33
|
+
pending = ctrl.resolve();
|
|
34
|
+
pendingPromises.set(atom$1, pending);
|
|
35
|
+
pending.finally(() => pendingPromises.delete(atom$1));
|
|
36
|
+
}
|
|
37
|
+
return pending;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Access the current Lite.Scope from context.
|
|
41
|
+
*
|
|
42
|
+
* @returns The current Lite.Scope instance from context
|
|
43
|
+
* @throws When called outside of a ScopeProvider
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```tsx
|
|
47
|
+
* const scope = useScope()
|
|
48
|
+
* await scope.resolve(myAtom)
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
function useScope() {
|
|
52
|
+
const scope = (0, react.useContext)(ScopeContext);
|
|
53
|
+
if (!scope) throw new Error("useScope must be used within a ScopeProvider");
|
|
54
|
+
return scope;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Get a memoized controller for an atom.
|
|
58
|
+
*
|
|
59
|
+
* @param atom - The atom to create a controller for
|
|
60
|
+
* @returns A memoized Lite.Controller instance
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```tsx
|
|
64
|
+
* const ctrl = useController(counterAtom)
|
|
65
|
+
* ctrl.set(ctrl.get() + 1)
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
function useController(atom$1) {
|
|
69
|
+
const scope = useScope();
|
|
70
|
+
return (0, react.useMemo)(() => scope.controller(atom$1), [scope, atom$1]);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Subscribe to atom value with Suspense/ErrorBoundary integration.
|
|
74
|
+
* Auto-resolves atoms lazily and throws cached Promise for Suspense.
|
|
75
|
+
*
|
|
76
|
+
* @param atom - The atom to read
|
|
77
|
+
* @returns The current value of the atom
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```tsx
|
|
81
|
+
* function UserProfile() {
|
|
82
|
+
* const user = useAtom(userAtom)
|
|
83
|
+
* return <div>{user.name}</div>
|
|
84
|
+
* }
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
function useAtom(atom$1) {
|
|
88
|
+
const ctrl = useController(atom$1);
|
|
89
|
+
const atomRef = (0, react.useRef)(atom$1);
|
|
90
|
+
atomRef.current = atom$1;
|
|
91
|
+
const getSnapshot = (0, react.useCallback)(() => {
|
|
92
|
+
const state = ctrl.state;
|
|
93
|
+
if (state === "idle" || state === "resolving") throw getOrCreatePendingPromise(atomRef.current, ctrl);
|
|
94
|
+
if (state === "failed") throw ctrl.get();
|
|
95
|
+
return ctrl.get();
|
|
96
|
+
}, [ctrl]);
|
|
97
|
+
return (0, react.useSyncExternalStore)((0, react.useCallback)((onStoreChange) => ctrl.on("resolved", onStoreChange), [ctrl]), getSnapshot, getSnapshot);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Select a derived value from an atom with fine-grained reactivity.
|
|
101
|
+
* Only re-renders when the selected value changes per equality function.
|
|
102
|
+
*
|
|
103
|
+
* @param atom - The atom to select from
|
|
104
|
+
* @param selector - Function to extract a derived value
|
|
105
|
+
* @param eq - Optional equality function
|
|
106
|
+
* @returns The selected value
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```tsx
|
|
110
|
+
* const name = useSelect(userAtom, user => user.name)
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
function useSelect(atom$1, selector, eq) {
|
|
114
|
+
const scope = useScope();
|
|
115
|
+
const ctrl = useController(atom$1);
|
|
116
|
+
const atomRef = (0, react.useRef)(atom$1);
|
|
117
|
+
atomRef.current = atom$1;
|
|
118
|
+
const selectorRef = (0, react.useRef)(selector);
|
|
119
|
+
const eqRef = (0, react.useRef)(eq);
|
|
120
|
+
selectorRef.current = selector;
|
|
121
|
+
eqRef.current = eq;
|
|
122
|
+
const handleRef = (0, react.useRef)(null);
|
|
123
|
+
const getOrCreateHandle = (0, react.useCallback)(() => {
|
|
124
|
+
if (!handleRef.current || handleRef.current.scope !== scope || handleRef.current.atom !== atom$1) handleRef.current = {
|
|
125
|
+
scope,
|
|
126
|
+
atom: atom$1,
|
|
127
|
+
handle: scope.select(atom$1, selectorRef.current, { eq: eqRef.current })
|
|
128
|
+
};
|
|
129
|
+
return handleRef.current.handle;
|
|
130
|
+
}, [scope, atom$1]);
|
|
131
|
+
const getSnapshot = (0, react.useCallback)(() => {
|
|
132
|
+
const state = ctrl.state;
|
|
133
|
+
if (state === "idle" || state === "resolving") throw getOrCreatePendingPromise(atomRef.current, ctrl);
|
|
134
|
+
if (state === "failed") throw ctrl.get();
|
|
135
|
+
return getOrCreateHandle().get();
|
|
136
|
+
}, [ctrl, getOrCreateHandle]);
|
|
137
|
+
return (0, react.useSyncExternalStore)((0, react.useCallback)((onStoreChange) => {
|
|
138
|
+
if (ctrl.state !== "resolved") return () => {};
|
|
139
|
+
return getOrCreateHandle().subscribe(onStoreChange);
|
|
140
|
+
}, [ctrl, getOrCreateHandle]), getSnapshot, getSnapshot);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
//#endregion
|
|
144
|
+
exports.ScopeContext = ScopeContext;
|
|
145
|
+
exports.ScopeProvider = ScopeProvider;
|
|
146
|
+
Object.defineProperty(exports, 'atom', {
|
|
147
|
+
enumerable: true,
|
|
148
|
+
get: function () {
|
|
149
|
+
return __pumped_fn_lite.atom;
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
Object.defineProperty(exports, 'createScope', {
|
|
153
|
+
enumerable: true,
|
|
154
|
+
get: function () {
|
|
155
|
+
return __pumped_fn_lite.createScope;
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
Object.defineProperty(exports, 'flow', {
|
|
159
|
+
enumerable: true,
|
|
160
|
+
get: function () {
|
|
161
|
+
return __pumped_fn_lite.flow;
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
Object.defineProperty(exports, 'preset', {
|
|
165
|
+
enumerable: true,
|
|
166
|
+
get: function () {
|
|
167
|
+
return __pumped_fn_lite.preset;
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
exports.useAtom = useAtom;
|
|
171
|
+
exports.useController = useController;
|
|
172
|
+
exports.useScope = useScope;
|
|
173
|
+
exports.useSelect = useSelect;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Lite, Lite as Lite$1, atom, createScope, flow, preset } from "@pumped-fn/lite";
|
|
2
|
+
import * as react0 from "react";
|
|
3
|
+
import { ReactNode } from "react";
|
|
4
|
+
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
5
|
+
|
|
6
|
+
//#region src/context.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* React context for Lite.Scope.
|
|
9
|
+
*/
|
|
10
|
+
declare const ScopeContext: react0.Context<Lite$1.Scope | null>;
|
|
11
|
+
interface ScopeProviderProps {
|
|
12
|
+
scope: Lite$1.Scope;
|
|
13
|
+
children: ReactNode;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Provider component for Lite.Scope.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* <ScopeProvider scope={scope}>
|
|
21
|
+
* <App />
|
|
22
|
+
* </ScopeProvider>
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
declare function ScopeProvider({
|
|
26
|
+
scope,
|
|
27
|
+
children
|
|
28
|
+
}: ScopeProviderProps): react_jsx_runtime0.JSX.Element;
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/hooks.d.ts
|
|
31
|
+
/**
|
|
32
|
+
* Access the current Lite.Scope from context.
|
|
33
|
+
*
|
|
34
|
+
* @returns The current Lite.Scope instance from context
|
|
35
|
+
* @throws When called outside of a ScopeProvider
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```tsx
|
|
39
|
+
* const scope = useScope()
|
|
40
|
+
* await scope.resolve(myAtom)
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
declare function useScope(): Lite$1.Scope;
|
|
44
|
+
/**
|
|
45
|
+
* Get a memoized controller for an atom.
|
|
46
|
+
*
|
|
47
|
+
* @param atom - The atom to create a controller for
|
|
48
|
+
* @returns A memoized Lite.Controller instance
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```tsx
|
|
52
|
+
* const ctrl = useController(counterAtom)
|
|
53
|
+
* ctrl.set(ctrl.get() + 1)
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
declare function useController<T>(atom: Lite$1.Atom<T>): Lite$1.Controller<T>;
|
|
57
|
+
/**
|
|
58
|
+
* Subscribe to atom value with Suspense/ErrorBoundary integration.
|
|
59
|
+
* Auto-resolves atoms lazily and throws cached Promise for Suspense.
|
|
60
|
+
*
|
|
61
|
+
* @param atom - The atom to read
|
|
62
|
+
* @returns The current value of the atom
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```tsx
|
|
66
|
+
* function UserProfile() {
|
|
67
|
+
* const user = useAtom(userAtom)
|
|
68
|
+
* return <div>{user.name}</div>
|
|
69
|
+
* }
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
declare function useAtom<T>(atom: Lite$1.Atom<T>): T;
|
|
73
|
+
/**
|
|
74
|
+
* Select a derived value from an atom with fine-grained reactivity.
|
|
75
|
+
* Only re-renders when the selected value changes per equality function.
|
|
76
|
+
*
|
|
77
|
+
* @param atom - The atom to select from
|
|
78
|
+
* @param selector - Function to extract a derived value
|
|
79
|
+
* @param eq - Optional equality function
|
|
80
|
+
* @returns The selected value
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```tsx
|
|
84
|
+
* const name = useSelect(userAtom, user => user.name)
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
declare function useSelect<T, S>(atom: Lite$1.Atom<T>, selector: (value: T) => S, eq?: (a: S, b: S) => boolean): S;
|
|
88
|
+
//#endregion
|
|
89
|
+
export { type Lite, ScopeContext, ScopeProvider, type ScopeProviderProps, atom, createScope, flow, preset, useAtom, useController, useScope, useSelect };
|
|
90
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/context.tsx","../src/hooks.ts"],"sourcesContent":[],"mappings":";;;;;;;;;cAMM,cAAY,MAAA,CAAA,QAAA,MAAA,CAAA;AALyB,UAOjC,kBAAA,CAFiD;EAEjD,KAAA,EACD,MAAA,CAAK,KADJ;EAeD,QAAA,EAbG,SAaU;;;;;;;;;ACtBqB;AA2BZ;;iBDLtB,aAAA,CCyB4B;EAAA,KAAA;EAAA;AAAA,CAAA,EDzBO,kBCyBP,CAAA,EDzByB,kBAAA,CAAA,GAAA,CAAA,OCyBzB;;;;;;;;AD/CM;AAKzB;AAIG;;;;;iBCkBZ,QAAA,CAAA,CDLqD,ECKzC,MAAA,CAAK,KDLoC;;;;ACtBnB;AA2BZ;;;;;;AAoB+B;;iBAArD,aAoBsB,CAAA,CAAA,CAAA,CAAA,IAAA,EApBC,MAAA,CAAK,IAoBN,CApBW,CAoBX,CAAA,CAAA,EApBgB,MAAA,CAAK,UAoBrB,CApBgC,CAoBhC,CAAA;;;AAAW;;;;;;;;;;;;;iBAAjC,iBAAiB,MAAA,CAAK,KAAK,KAAK;;;;;;;;;;;;;;;iBAsChC,sBACD,MAAA,CAAK,KAAK,sBACE,MAAM,YACf,MAAM,gBACd"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Lite, Lite as Lite$1, atom, createScope, flow, preset } from "@pumped-fn/lite";
|
|
2
|
+
import * as react0 from "react";
|
|
3
|
+
import { ReactNode } from "react";
|
|
4
|
+
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
5
|
+
|
|
6
|
+
//#region src/context.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* React context for Lite.Scope.
|
|
9
|
+
*/
|
|
10
|
+
declare const ScopeContext: react0.Context<Lite$1.Scope | null>;
|
|
11
|
+
interface ScopeProviderProps {
|
|
12
|
+
scope: Lite$1.Scope;
|
|
13
|
+
children: ReactNode;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Provider component for Lite.Scope.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* <ScopeProvider scope={scope}>
|
|
21
|
+
* <App />
|
|
22
|
+
* </ScopeProvider>
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
declare function ScopeProvider({
|
|
26
|
+
scope,
|
|
27
|
+
children
|
|
28
|
+
}: ScopeProviderProps): react_jsx_runtime0.JSX.Element;
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/hooks.d.ts
|
|
31
|
+
/**
|
|
32
|
+
* Access the current Lite.Scope from context.
|
|
33
|
+
*
|
|
34
|
+
* @returns The current Lite.Scope instance from context
|
|
35
|
+
* @throws When called outside of a ScopeProvider
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```tsx
|
|
39
|
+
* const scope = useScope()
|
|
40
|
+
* await scope.resolve(myAtom)
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
declare function useScope(): Lite$1.Scope;
|
|
44
|
+
/**
|
|
45
|
+
* Get a memoized controller for an atom.
|
|
46
|
+
*
|
|
47
|
+
* @param atom - The atom to create a controller for
|
|
48
|
+
* @returns A memoized Lite.Controller instance
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```tsx
|
|
52
|
+
* const ctrl = useController(counterAtom)
|
|
53
|
+
* ctrl.set(ctrl.get() + 1)
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
declare function useController<T>(atom: Lite$1.Atom<T>): Lite$1.Controller<T>;
|
|
57
|
+
/**
|
|
58
|
+
* Subscribe to atom value with Suspense/ErrorBoundary integration.
|
|
59
|
+
* Auto-resolves atoms lazily and throws cached Promise for Suspense.
|
|
60
|
+
*
|
|
61
|
+
* @param atom - The atom to read
|
|
62
|
+
* @returns The current value of the atom
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```tsx
|
|
66
|
+
* function UserProfile() {
|
|
67
|
+
* const user = useAtom(userAtom)
|
|
68
|
+
* return <div>{user.name}</div>
|
|
69
|
+
* }
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
declare function useAtom<T>(atom: Lite$1.Atom<T>): T;
|
|
73
|
+
/**
|
|
74
|
+
* Select a derived value from an atom with fine-grained reactivity.
|
|
75
|
+
* Only re-renders when the selected value changes per equality function.
|
|
76
|
+
*
|
|
77
|
+
* @param atom - The atom to select from
|
|
78
|
+
* @param selector - Function to extract a derived value
|
|
79
|
+
* @param eq - Optional equality function
|
|
80
|
+
* @returns The selected value
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```tsx
|
|
84
|
+
* const name = useSelect(userAtom, user => user.name)
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
declare function useSelect<T, S>(atom: Lite$1.Atom<T>, selector: (value: T) => S, eq?: (a: S, b: S) => boolean): S;
|
|
88
|
+
//#endregion
|
|
89
|
+
export { type Lite, ScopeContext, ScopeProvider, type ScopeProviderProps, atom, createScope, flow, preset, useAtom, useController, useScope, useSelect };
|
|
90
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/context.tsx","../src/hooks.ts"],"sourcesContent":[],"mappings":";;;;;;;;;cAMM,cAAY,MAAA,CAAA,QAAA,MAAA,CAAA;AALyB,UAOjC,kBAAA,CAFiD;EAEjD,KAAA,EACD,MAAA,CAAK,KADJ;EAeD,QAAA,EAbG,SAaU;;;;;;;;;ACtBqB;AA2BZ;;iBDLtB,aAAA,CCyB4B;EAAA,KAAA;EAAA;AAAA,CAAA,EDzBO,kBCyBP,CAAA,EDzByB,kBAAA,CAAA,GAAA,CAAA,OCyBzB;;;;;;;;AD/CM;AAKzB;AAIG;;;;;iBCkBZ,QAAA,CAAA,CDLqD,ECKzC,MAAA,CAAK,KDLoC;;;;ACtBnB;AA2BZ;;;;;;AAoB+B;;iBAArD,aAoBsB,CAAA,CAAA,CAAA,CAAA,IAAA,EApBC,MAAA,CAAK,IAoBN,CApBW,CAoBX,CAAA,CAAA,EApBgB,MAAA,CAAK,UAoBrB,CApBgC,CAoBhC,CAAA;;;AAAW;;;;;;;;;;;;;iBAAjC,iBAAiB,MAAA,CAAK,KAAK,KAAK;;;;;;;;;;;;;;;iBAsChC,sBACD,MAAA,CAAK,KAAK,sBACE,MAAM,YACf,MAAM,gBACd"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { atom, createScope, flow, preset } from "@pumped-fn/lite";
|
|
2
|
+
import { createContext, useCallback, useContext, useMemo, useRef, useSyncExternalStore } from "react";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
|
|
5
|
+
//#region src/context.tsx
|
|
6
|
+
/**
|
|
7
|
+
* React context for Lite.Scope.
|
|
8
|
+
*/
|
|
9
|
+
const ScopeContext = createContext(null);
|
|
10
|
+
/**
|
|
11
|
+
* Provider component for Lite.Scope.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* <ScopeProvider scope={scope}>
|
|
16
|
+
* <App />
|
|
17
|
+
* </ScopeProvider>
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
function ScopeProvider({ scope, children }) {
|
|
21
|
+
return /* @__PURE__ */ jsx(ScopeContext.Provider, {
|
|
22
|
+
value: scope,
|
|
23
|
+
children
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/hooks.ts
|
|
29
|
+
const pendingPromises = /* @__PURE__ */ new WeakMap();
|
|
30
|
+
function getOrCreatePendingPromise(atom$1, ctrl) {
|
|
31
|
+
let pending = pendingPromises.get(atom$1);
|
|
32
|
+
if (!pending) {
|
|
33
|
+
pending = ctrl.resolve();
|
|
34
|
+
pendingPromises.set(atom$1, pending);
|
|
35
|
+
pending.finally(() => pendingPromises.delete(atom$1));
|
|
36
|
+
}
|
|
37
|
+
return pending;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Access the current Lite.Scope from context.
|
|
41
|
+
*
|
|
42
|
+
* @returns The current Lite.Scope instance from context
|
|
43
|
+
* @throws When called outside of a ScopeProvider
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```tsx
|
|
47
|
+
* const scope = useScope()
|
|
48
|
+
* await scope.resolve(myAtom)
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
function useScope() {
|
|
52
|
+
const scope = useContext(ScopeContext);
|
|
53
|
+
if (!scope) throw new Error("useScope must be used within a ScopeProvider");
|
|
54
|
+
return scope;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Get a memoized controller for an atom.
|
|
58
|
+
*
|
|
59
|
+
* @param atom - The atom to create a controller for
|
|
60
|
+
* @returns A memoized Lite.Controller instance
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```tsx
|
|
64
|
+
* const ctrl = useController(counterAtom)
|
|
65
|
+
* ctrl.set(ctrl.get() + 1)
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
function useController(atom$1) {
|
|
69
|
+
const scope = useScope();
|
|
70
|
+
return useMemo(() => scope.controller(atom$1), [scope, atom$1]);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Subscribe to atom value with Suspense/ErrorBoundary integration.
|
|
74
|
+
* Auto-resolves atoms lazily and throws cached Promise for Suspense.
|
|
75
|
+
*
|
|
76
|
+
* @param atom - The atom to read
|
|
77
|
+
* @returns The current value of the atom
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```tsx
|
|
81
|
+
* function UserProfile() {
|
|
82
|
+
* const user = useAtom(userAtom)
|
|
83
|
+
* return <div>{user.name}</div>
|
|
84
|
+
* }
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
function useAtom(atom$1) {
|
|
88
|
+
const ctrl = useController(atom$1);
|
|
89
|
+
const atomRef = useRef(atom$1);
|
|
90
|
+
atomRef.current = atom$1;
|
|
91
|
+
const getSnapshot = useCallback(() => {
|
|
92
|
+
const state = ctrl.state;
|
|
93
|
+
if (state === "idle" || state === "resolving") throw getOrCreatePendingPromise(atomRef.current, ctrl);
|
|
94
|
+
if (state === "failed") throw ctrl.get();
|
|
95
|
+
return ctrl.get();
|
|
96
|
+
}, [ctrl]);
|
|
97
|
+
return useSyncExternalStore(useCallback((onStoreChange) => ctrl.on("resolved", onStoreChange), [ctrl]), getSnapshot, getSnapshot);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Select a derived value from an atom with fine-grained reactivity.
|
|
101
|
+
* Only re-renders when the selected value changes per equality function.
|
|
102
|
+
*
|
|
103
|
+
* @param atom - The atom to select from
|
|
104
|
+
* @param selector - Function to extract a derived value
|
|
105
|
+
* @param eq - Optional equality function
|
|
106
|
+
* @returns The selected value
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```tsx
|
|
110
|
+
* const name = useSelect(userAtom, user => user.name)
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
function useSelect(atom$1, selector, eq) {
|
|
114
|
+
const scope = useScope();
|
|
115
|
+
const ctrl = useController(atom$1);
|
|
116
|
+
const atomRef = useRef(atom$1);
|
|
117
|
+
atomRef.current = atom$1;
|
|
118
|
+
const selectorRef = useRef(selector);
|
|
119
|
+
const eqRef = useRef(eq);
|
|
120
|
+
selectorRef.current = selector;
|
|
121
|
+
eqRef.current = eq;
|
|
122
|
+
const handleRef = useRef(null);
|
|
123
|
+
const getOrCreateHandle = useCallback(() => {
|
|
124
|
+
if (!handleRef.current || handleRef.current.scope !== scope || handleRef.current.atom !== atom$1) handleRef.current = {
|
|
125
|
+
scope,
|
|
126
|
+
atom: atom$1,
|
|
127
|
+
handle: scope.select(atom$1, selectorRef.current, { eq: eqRef.current })
|
|
128
|
+
};
|
|
129
|
+
return handleRef.current.handle;
|
|
130
|
+
}, [scope, atom$1]);
|
|
131
|
+
const getSnapshot = useCallback(() => {
|
|
132
|
+
const state = ctrl.state;
|
|
133
|
+
if (state === "idle" || state === "resolving") throw getOrCreatePendingPromise(atomRef.current, ctrl);
|
|
134
|
+
if (state === "failed") throw ctrl.get();
|
|
135
|
+
return getOrCreateHandle().get();
|
|
136
|
+
}, [ctrl, getOrCreateHandle]);
|
|
137
|
+
return useSyncExternalStore(useCallback((onStoreChange) => {
|
|
138
|
+
if (ctrl.state !== "resolved") return () => {};
|
|
139
|
+
return getOrCreateHandle().subscribe(onStoreChange);
|
|
140
|
+
}, [ctrl, getOrCreateHandle]), getSnapshot, getSnapshot);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
//#endregion
|
|
144
|
+
export { ScopeContext, ScopeProvider, atom, createScope, flow, preset, useAtom, useController, useScope, useSelect };
|
|
145
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["atom"],"sources":["../src/context.tsx","../src/hooks.ts"],"sourcesContent":["import { createContext, type ReactNode } from 'react'\nimport { type Lite } from '@pumped-fn/lite'\n\n/**\n * React context for Lite.Scope.\n */\nconst ScopeContext = createContext<Lite.Scope | null>(null)\n\ninterface ScopeProviderProps {\n scope: Lite.Scope\n children: ReactNode\n}\n\n/**\n * Provider component for Lite.Scope.\n *\n * @example\n * ```tsx\n * <ScopeProvider scope={scope}>\n * <App />\n * </ScopeProvider>\n * ```\n */\nfunction ScopeProvider({ scope, children }: ScopeProviderProps) {\n return (\n <ScopeContext.Provider value={scope}>\n {children}\n </ScopeContext.Provider>\n )\n}\n\nexport { ScopeContext, ScopeProvider }\nexport type { ScopeProviderProps }\n","import { useCallback, useContext, useMemo, useRef, useSyncExternalStore } from 'react'\nimport { type Lite } from '@pumped-fn/lite'\nimport { ScopeContext } from './context'\n\nconst pendingPromises = new WeakMap<Lite.Atom<unknown>, Promise<unknown>>()\n\nfunction getOrCreatePendingPromise<T>(atom: Lite.Atom<T>, ctrl: Lite.Controller<T>): Promise<T> {\n let pending = pendingPromises.get(atom) as Promise<T> | undefined\n if (!pending) {\n pending = ctrl.resolve()\n pendingPromises.set(atom, pending)\n pending.finally(() => pendingPromises.delete(atom))\n }\n return pending\n}\n\n/**\n * Access the current Lite.Scope from context.\n *\n * @returns The current Lite.Scope instance from context\n * @throws When called outside of a ScopeProvider\n *\n * @example\n * ```tsx\n * const scope = useScope()\n * await scope.resolve(myAtom)\n * ```\n */\nfunction useScope(): Lite.Scope {\n const scope = useContext(ScopeContext)\n if (!scope) {\n throw new Error(\"useScope must be used within a ScopeProvider\")\n }\n return scope\n}\n\n/**\n * Get a memoized controller for an atom.\n *\n * @param atom - The atom to create a controller for\n * @returns A memoized Lite.Controller instance\n *\n * @example\n * ```tsx\n * const ctrl = useController(counterAtom)\n * ctrl.set(ctrl.get() + 1)\n * ```\n */\nfunction useController<T>(atom: Lite.Atom<T>): Lite.Controller<T> {\n const scope = useScope()\n return useMemo(() => scope.controller(atom), [scope, atom])\n}\n\n/**\n * Subscribe to atom value with Suspense/ErrorBoundary integration.\n * Auto-resolves atoms lazily and throws cached Promise for Suspense.\n *\n * @param atom - The atom to read\n * @returns The current value of the atom\n *\n * @example\n * ```tsx\n * function UserProfile() {\n * const user = useAtom(userAtom)\n * return <div>{user.name}</div>\n * }\n * ```\n */\nfunction useAtom<T>(atom: Lite.Atom<T>): T {\n const ctrl = useController(atom)\n const atomRef = useRef(atom)\n atomRef.current = atom\n\n const getSnapshot = useCallback((): T => {\n const state = ctrl.state\n if (state === 'idle' || state === 'resolving') {\n throw getOrCreatePendingPromise(atomRef.current, ctrl)\n }\n if (state === 'failed') {\n throw ctrl.get()\n }\n return ctrl.get()\n }, [ctrl])\n\n const subscribe = useCallback(\n (onStoreChange: () => void) => ctrl.on('resolved', onStoreChange),\n [ctrl]\n )\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n}\n\n/**\n * Select a derived value from an atom with fine-grained reactivity.\n * Only re-renders when the selected value changes per equality function.\n *\n * @param atom - The atom to select from\n * @param selector - Function to extract a derived value\n * @param eq - Optional equality function\n * @returns The selected value\n *\n * @example\n * ```tsx\n * const name = useSelect(userAtom, user => user.name)\n * ```\n */\nfunction useSelect<T, S>(\n atom: Lite.Atom<T>,\n selector: (value: T) => S,\n eq?: (a: S, b: S) => boolean\n): S {\n const scope = useScope()\n const ctrl = useController(atom)\n\n const atomRef = useRef(atom)\n atomRef.current = atom\n\n const selectorRef = useRef(selector)\n const eqRef = useRef(eq)\n selectorRef.current = selector\n eqRef.current = eq\n\n const handleRef = useRef<{\n scope: Lite.Scope\n atom: Lite.Atom<T>\n handle: Lite.SelectHandle<S>\n } | null>(null)\n\n const getOrCreateHandle = useCallback(() => {\n if (\n !handleRef.current ||\n handleRef.current.scope !== scope ||\n handleRef.current.atom !== atom\n ) {\n const handle = scope.select(atom, selectorRef.current, { eq: eqRef.current })\n handleRef.current = { scope, atom, handle }\n }\n return handleRef.current.handle\n }, [scope, atom])\n\n const getSnapshot = useCallback((): S => {\n const state = ctrl.state\n if (state === 'idle' || state === 'resolving') {\n throw getOrCreatePendingPromise(atomRef.current, ctrl)\n }\n if (state === 'failed') {\n throw ctrl.get()\n }\n return getOrCreateHandle().get()\n }, [ctrl, getOrCreateHandle])\n\n const subscribe = useCallback((onStoreChange: () => void) => {\n if (ctrl.state !== 'resolved') {\n return () => {}\n }\n return getOrCreateHandle().subscribe(onStoreChange)\n }, [ctrl, getOrCreateHandle])\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n}\n\nexport { useScope, useController, useAtom, useSelect }\n"],"mappings":";;;;;;;;AAMA,MAAM,eAAe,cAAiC,KAAK;;;;;;;;;;;AAiB3D,SAAS,cAAc,EAAE,OAAO,YAAgC;AAC9D,QACE,oBAAC,aAAa;EAAS,OAAO;EAC3B;GACqB;;;;;ACvB5B,MAAM,kCAAkB,IAAI,SAA+C;AAE3E,SAAS,0BAA6B,QAAoB,MAAsC;CAC9F,IAAI,UAAU,gBAAgB,IAAIA,OAAK;AACvC,KAAI,CAAC,SAAS;AACZ,YAAU,KAAK,SAAS;AACxB,kBAAgB,IAAIA,QAAM,QAAQ;AAClC,UAAQ,cAAc,gBAAgB,OAAOA,OAAK,CAAC;;AAErD,QAAO;;;;;;;;;;;;;;AAeT,SAAS,WAAuB;CAC9B,MAAM,QAAQ,WAAW,aAAa;AACtC,KAAI,CAAC,MACH,OAAM,IAAI,MAAM,+CAA+C;AAEjE,QAAO;;;;;;;;;;;;;;AAeT,SAAS,cAAiB,QAAwC;CAChE,MAAM,QAAQ,UAAU;AACxB,QAAO,cAAc,MAAM,WAAWA,OAAK,EAAE,CAAC,OAAOA,OAAK,CAAC;;;;;;;;;;;;;;;;;AAkB7D,SAAS,QAAW,QAAuB;CACzC,MAAM,OAAO,cAAcA,OAAK;CAChC,MAAM,UAAU,OAAOA,OAAK;AAC5B,SAAQ,UAAUA;CAElB,MAAM,cAAc,kBAAqB;EACvC,MAAM,QAAQ,KAAK;AACnB,MAAI,UAAU,UAAU,UAAU,YAChC,OAAM,0BAA0B,QAAQ,SAAS,KAAK;AAExD,MAAI,UAAU,SACZ,OAAM,KAAK,KAAK;AAElB,SAAO,KAAK,KAAK;IAChB,CAAC,KAAK,CAAC;AAOV,QAAO,qBALW,aACf,kBAA8B,KAAK,GAAG,YAAY,cAAc,EACjE,CAAC,KAAK,CACP,EAEsC,aAAa,YAAY;;;;;;;;;;;;;;;;AAiBlE,SAAS,UACP,QACA,UACA,IACG;CACH,MAAM,QAAQ,UAAU;CACxB,MAAM,OAAO,cAAcA,OAAK;CAEhC,MAAM,UAAU,OAAOA,OAAK;AAC5B,SAAQ,UAAUA;CAElB,MAAM,cAAc,OAAO,SAAS;CACpC,MAAM,QAAQ,OAAO,GAAG;AACxB,aAAY,UAAU;AACtB,OAAM,UAAU;CAEhB,MAAM,YAAY,OAIR,KAAK;CAEf,MAAM,oBAAoB,kBAAkB;AAC1C,MACE,CAAC,UAAU,WACX,UAAU,QAAQ,UAAU,SAC5B,UAAU,QAAQ,SAASA,OAG3B,WAAU,UAAU;GAAE;GAAO;GAAM,QADpB,MAAM,OAAOA,QAAM,YAAY,SAAS,EAAE,IAAI,MAAM,SAAS,CAAC;GAClC;AAE7C,SAAO,UAAU,QAAQ;IACxB,CAAC,OAAOA,OAAK,CAAC;CAEjB,MAAM,cAAc,kBAAqB;EACvC,MAAM,QAAQ,KAAK;AACnB,MAAI,UAAU,UAAU,UAAU,YAChC,OAAM,0BAA0B,QAAQ,SAAS,KAAK;AAExD,MAAI,UAAU,SACZ,OAAM,KAAK,KAAK;AAElB,SAAO,mBAAmB,CAAC,KAAK;IAC/B,CAAC,MAAM,kBAAkB,CAAC;AAS7B,QAAO,qBAPW,aAAa,kBAA8B;AAC3D,MAAI,KAAK,UAAU,WACjB,cAAa;AAEf,SAAO,mBAAmB,CAAC,UAAU,cAAc;IAClD,CAAC,MAAM,kBAAkB,CAAC,EAEU,aAAa,YAAY"}
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pumped-fn/lite-react",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "React integration for @pumped-fn/lite",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.mjs",
|
|
8
|
+
"types": "./dist/index.d.cts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.mts",
|
|
13
|
+
"default": "./dist/index.mjs"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE",
|
|
25
|
+
"CHANGELOG.md"
|
|
26
|
+
],
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"@pumped-fn/lite": ">=1.4.0",
|
|
29
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
33
|
+
"@testing-library/react": "^16.3.0",
|
|
34
|
+
"@types/react": "^18.3.18",
|
|
35
|
+
"jsdom": "^27.2.0",
|
|
36
|
+
"react": "^18.3.1",
|
|
37
|
+
"tsdown": "^0.16.5",
|
|
38
|
+
"typescript": "^5.9.3",
|
|
39
|
+
"vitest": "^4.0.5",
|
|
40
|
+
"@pumped-fn/lite": "1.6.0"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"directory": "packages/lite-react",
|
|
51
|
+
"url": "git+https://github.com/pumped-fn/pumped-fn.git"
|
|
52
|
+
},
|
|
53
|
+
"keywords": [
|
|
54
|
+
"react",
|
|
55
|
+
"hooks",
|
|
56
|
+
"dependency-injection",
|
|
57
|
+
"di",
|
|
58
|
+
"lite",
|
|
59
|
+
"lite-react",
|
|
60
|
+
"reactivity",
|
|
61
|
+
"typescript"
|
|
62
|
+
],
|
|
63
|
+
"license": "MIT",
|
|
64
|
+
"scripts": {
|
|
65
|
+
"build": "tsdown",
|
|
66
|
+
"typecheck": "tsc --noEmit",
|
|
67
|
+
"typecheck:full": "tsc --noEmit -p tsconfig.test.json",
|
|
68
|
+
"test": "vitest run",
|
|
69
|
+
"test:watch": "vitest"
|
|
70
|
+
}
|
|
71
|
+
}
|