@pumped-fn/lite-react 1.1.0 → 1.1.1

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 CHANGED
@@ -1,5 +1,12 @@
1
1
  # @pumped-fn/lite-react
2
2
 
3
+ ## 1.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 8ed17e7: - Fix watch and invalidation edge cases in `@pumped-fn/lite` by aligning `select()` with `Object.is`, snapshotting select listeners during notification, making watch option typing match the runtime contract, and surfacing invalidation-chain failures from `flush()` instead of leaking them as background rejections.
8
+ - Fix `@pumped-fn/lite-react` hook refresh behavior by keeping stale values visible during re-resolution, recomputing `useSelect` snapshots when selector or equality semantics change, tracking pending promises per controller, and suppressing non-Suspense `unhandledRejection` leaks on failed refreshes.
9
+
3
10
  ## 1.1.0
4
11
 
5
12
  ### Minor Changes
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @pumped-fn/lite-react
2
2
 
3
- React bindings for `@pumped-fn/lite` with Suspense and ErrorBoundary integration.
3
+ React bindings for `@pumped-fn/lite` with Suspense, ErrorBoundary integration, and stale-while-revalidate refreshes.
4
4
 
5
5
  **Zero dependencies** · **<2KB bundle** · **React 18+**
6
6
 
@@ -20,12 +20,14 @@ sequenceDiagram
20
20
  alt resolved
21
21
  Controller-->>useAtom: value
22
22
  useAtom->>Controller: subscribe to changes
23
- else resolving
23
+ else resolving with stale value
24
+ Controller-->>useAtom: stale value
25
+ else resolving without value
24
26
  useAtom-->>App: throw Promise (Suspense)
25
27
  else failed
26
28
  useAtom-->>App: throw Error (ErrorBoundary)
27
29
  else idle
28
- useAtom-->>App: throw Error (not resolved)
30
+ useAtom-->>App: throw Promise (Suspense)
29
31
  end
30
32
  ```
31
33
 
@@ -37,19 +39,21 @@ flowchart TD
37
39
  Hook --> State{ctrl.state?}
38
40
 
39
41
  State -->|idle| AutoResolve[Auto-resolve + Throw Promise]
40
- State -->|resolving| Promise[Throw cached Promise]
42
+ State -->|resolving + stale value| Stale[Return stale value]
43
+ State -->|resolving + no value| Promise[Throw cached Promise]
41
44
  State -->|resolved| Value[Return value]
42
45
  State -->|failed| Stored[Throw stored error]
43
46
 
44
47
  AutoResolve --> Suspense[Suspense catches]
45
48
  Promise --> Suspense
49
+ Stale --> Render[Keep current UI]
46
50
  Stored --> ErrorBoundary[ErrorBoundary catches]
47
51
  ```
48
52
 
49
53
  | State | Hook Behavior |
50
54
  |-------|---------------|
51
55
  | `idle` | Auto-resolves and suspends — Suspense shows fallback |
52
- | `resolving` | Throws cached promise Suspense shows fallback |
56
+ | `resolving` | Returns stale value if available, otherwise throws cached promise |
53
57
  | `resolved` | Returns value, subscribes to changes |
54
58
  | `failed` | Throws stored error — ErrorBoundary catches |
55
59
 
@@ -99,6 +103,8 @@ const ctrl = useController(configAtom, { resolve: true })
99
103
  ctrl.get() // safe - Suspense guarantees resolved state
100
104
  ```
101
105
 
106
+ While a controller is re-resolving, `{ resolve: true }` keeps suspending until the controller settles.
107
+
102
108
  ### useAtom
103
109
 
104
110
  Subscribe to atom value with Suspense integration.
@@ -125,6 +131,7 @@ For manual loading/error state handling without Suspense:
125
131
  function UserProfile() {
126
132
  const { data, loading, error, controller } = useAtom(userAtom, { suspense: false })
127
133
 
134
+ if (loading && data) return <div>Refreshing {data.name}...</div>
128
135
  if (loading) return <div>Loading...</div>
129
136
  if (error) return <div>Error: {error.message}</div>
130
137
  if (!data) return <div>Not loaded</div>
@@ -150,6 +157,8 @@ const { data, loading, error } = useAtom(userAtom, { suspense: false, resolve: t
150
157
  | `{ suspense: false }` | Returns state object, no auto-resolve |
151
158
  | `{ suspense: false, resolve: true }` | Returns state object, auto-resolves on mount |
152
159
 
160
+ While `loading` is `true`, `data` may still contain the last resolved value during a refresh.
161
+
153
162
  ### useSelect
154
163
 
155
164
  Fine-grained selection — only re-renders when selected value changes.
@@ -161,7 +170,7 @@ const count = useSelect(todosAtom, todos => todos.length, (a, b) => a === b)
161
170
 
162
171
  ## Invalidation
163
172
 
164
- When an atom is invalidated, hooks automatically suspend during re-resolution:
173
+ When an atom is invalidated, `useAtom` and `useSelect` keep rendering the last value while re-resolving:
165
174
 
166
175
  ```mermaid
167
176
  sequenceDiagram
@@ -174,14 +183,16 @@ sequenceDiagram
174
183
 
175
184
  Note over Controller: ctrl.invalidate()
176
185
  Controller->>Controller: state = resolving
177
- useAtom-->>Component: throw Promise
178
- Note over Component: Suspense fallback
186
+ useAtom-->>Component: stale value
187
+ Note over Component: current UI stays visible
179
188
 
180
189
  Controller->>Controller: factory runs
181
190
  Controller->>Controller: state = resolved
182
191
  useAtom->>Component: re-render (new value)
183
192
  ```
184
193
 
194
+ `useController(atom, { resolve: true })` is different: it suspends until the controller settles again.
195
+
185
196
  ## Testing
186
197
 
187
198
  Use presets for test isolation:
@@ -204,10 +215,9 @@ render(
204
215
 
205
216
  ## SSR
206
217
 
207
- SSR-compatible by design:
218
+ SSR-compatible when request-scoped atoms are resolved before rendering:
208
219
 
209
220
  - No side effects on import
210
- - Uses `useSyncExternalStore` with server snapshot
211
221
  - Scope passed as prop (no global state)
212
222
 
213
223
  ```tsx
package/dist/index.cjs CHANGED
@@ -27,12 +27,28 @@ function ScopeProvider({ scope, children }) {
27
27
  //#endregion
28
28
  //#region src/hooks.ts
29
29
  const pendingPromises = /* @__PURE__ */ new WeakMap();
30
- function getOrCreatePendingPromise(atom$1, ctrl) {
31
- let pending = pendingPromises.get(atom$1);
30
+ function getOrCreatePendingPromise(ctrl) {
31
+ let pending = pendingPromises.get(ctrl);
32
32
  if (!pending) {
33
- pending = ctrl.resolve();
34
- pendingPromises.set(atom$1, pending);
35
- pending.finally(() => pendingPromises.delete(atom$1));
33
+ if (ctrl.state === "resolving") pending = new Promise((resolve, reject) => {
34
+ const unsub = ctrl.on("*", () => {
35
+ if (ctrl.state === "resolved") {
36
+ unsub();
37
+ resolve(ctrl.get());
38
+ } else if (ctrl.state === "failed") {
39
+ unsub();
40
+ try {
41
+ ctrl.get();
42
+ } catch (e) {
43
+ reject(e);
44
+ }
45
+ }
46
+ });
47
+ });
48
+ else pending = ctrl.resolve();
49
+ pendingPromises.set(ctrl, pending);
50
+ pending.catch(() => {});
51
+ pending.then(() => pendingPromises.delete(ctrl), () => pendingPromises.delete(ctrl));
36
52
  }
37
53
  return pending;
38
54
  }
@@ -44,8 +60,10 @@ function getOrCreatePendingPromise(atom$1, ctrl) {
44
60
  *
45
61
  * @example
46
62
  * ```tsx
47
- * const scope = useScope()
48
- * await scope.resolve(myAtom)
63
+ * function MyComponent() {
64
+ * const scope = useScope()
65
+ * const handleClick = () => scope.resolve(myAtom)
66
+ * }
49
67
  * ```
50
68
  */
51
69
  function useScope() {
@@ -57,62 +75,76 @@ function useController(atom$1, options) {
57
75
  const scope = useScope();
58
76
  const ctrl = (0, react.useMemo)(() => scope.controller(atom$1), [scope, atom$1]);
59
77
  if (options?.resolve) {
60
- if (ctrl.state === "idle" || ctrl.state === "resolving") throw getOrCreatePendingPromise(atom$1, ctrl);
78
+ if (ctrl.state === "idle" || ctrl.state === "resolving") throw getOrCreatePendingPromise(ctrl);
61
79
  if (ctrl.state === "failed") throw ctrl.get();
62
80
  }
63
81
  return ctrl;
64
82
  }
65
83
  function useAtom(atom$1, options) {
66
84
  const ctrl = useController(atom$1);
67
- const atomRef = (0, react.useRef)(atom$1);
68
- atomRef.current = atom$1;
69
- if (options?.suspense === false) return useAtomState(atom$1, ctrl, options.resolve ?? false);
70
- const autoResolve = options?.resolve !== false;
71
- const getSnapshot = (0, react.useCallback)(() => {
72
- if (ctrl.state === "idle") {
73
- if (autoResolve) throw getOrCreatePendingPromise(atomRef.current, ctrl);
74
- throw new Error("Atom is not resolved. Set resolve: true or resolve the atom before rendering.");
75
- }
76
- if (ctrl.state === "resolving") throw getOrCreatePendingPromise(atomRef.current, ctrl);
77
- if (ctrl.state === "failed") throw ctrl.get();
78
- return ctrl.get();
79
- }, [ctrl, autoResolve]);
80
- return (0, react.useSyncExternalStore)((0, react.useCallback)((onStoreChange) => ctrl.on("resolved", onStoreChange), [ctrl]), getSnapshot, getSnapshot);
81
- }
82
- function useAtomState(atom$1, ctrl, autoResolve) {
85
+ const ctrlState = ctrl.state;
86
+ const isSuspense = options?.suspense !== false;
87
+ const autoResolve = isSuspense ? options?.resolve !== false : !!options?.resolve;
83
88
  const stateCache = (0, react.useRef)(null);
84
89
  (0, react.useEffect)(() => {
85
- if (autoResolve && (ctrl.state === "idle" || ctrl.state === "resolving")) getOrCreatePendingPromise(atom$1, ctrl);
90
+ if (!isSuspense && (ctrlState === "resolving" || autoResolve && ctrlState === "idle")) getOrCreatePendingPromise(ctrl).catch(() => {});
86
91
  }, [
87
- atom$1,
88
92
  ctrl,
89
- autoResolve
93
+ ctrlState,
94
+ autoResolve,
95
+ isSuspense
90
96
  ]);
91
97
  const getSnapshot = (0, react.useCallback)(() => {
98
+ if (isSuspense) {
99
+ if (ctrl.state === "idle") {
100
+ if (autoResolve) throw getOrCreatePendingPromise(ctrl);
101
+ throw new Error("Atom is not resolved. Set resolve: true or resolve the atom before rendering.");
102
+ }
103
+ if (ctrl.state === "failed") throw ctrl.get();
104
+ try {
105
+ return ctrl.get();
106
+ } catch {
107
+ throw getOrCreatePendingPromise(ctrl);
108
+ }
109
+ }
92
110
  let data;
93
111
  let error;
94
- if (ctrl.state === "resolved") data = ctrl.get();
112
+ if (ctrl.state === "resolved" || ctrl.state === "resolving") try {
113
+ data = ctrl.get();
114
+ } catch {}
95
115
  else if (ctrl.state === "failed") try {
96
116
  ctrl.get();
97
117
  } catch (e) {
98
118
  error = e instanceof Error ? e : new Error(String(e));
99
119
  }
100
- if (stateCache.current && stateCache.current.ctrlState === ctrl.state && stateCache.current.data === data && stateCache.current.error === error) return stateCache.current.result;
120
+ const loading = ctrl.state === "resolving" || autoResolve && ctrl.state === "idle";
121
+ if (stateCache.current && stateCache.current.ctrl === ctrl && stateCache.current.ctrlState === ctrl.state && stateCache.current.data === data && stateCache.current.error === error && stateCache.current.loading === loading) return stateCache.current.result;
101
122
  const result = {
102
123
  data,
103
- loading: ctrl.state === "resolving",
124
+ loading,
104
125
  error,
105
126
  controller: ctrl
106
127
  };
107
128
  stateCache.current = {
129
+ ctrl,
108
130
  ctrlState: ctrl.state,
109
131
  data,
110
132
  error,
133
+ loading,
111
134
  result
112
135
  };
113
136
  return result;
114
- }, [ctrl]);
115
- return (0, react.useSyncExternalStore)((0, react.useCallback)((onStoreChange) => ctrl.on("*", onStoreChange), [ctrl]), getSnapshot, getSnapshot);
137
+ }, [
138
+ ctrl,
139
+ autoResolve,
140
+ isSuspense
141
+ ]);
142
+ return (0, react.useSyncExternalStore)((0, react.useCallback)((onStoreChange) => {
143
+ return ctrl.on("*", () => {
144
+ if (!isSuspense && ctrl.state === "resolving") getOrCreatePendingPromise(ctrl).catch(() => {});
145
+ onStoreChange();
146
+ });
147
+ }, [ctrl, isSuspense]), getSnapshot, getSnapshot);
116
148
  }
117
149
  /**
118
150
  * Select a derived value from an atom with fine-grained reactivity.
@@ -129,33 +161,41 @@ function useAtomState(atom$1, ctrl, autoResolve) {
129
161
  * ```
130
162
  */
131
163
  function useSelect(atom$1, selector, eq) {
132
- const scope = useScope();
133
164
  const ctrl = useController(atom$1);
134
- const atomRef = (0, react.useRef)(atom$1);
135
- atomRef.current = atom$1;
136
165
  const selectorRef = (0, react.useRef)(selector);
137
166
  const eqRef = (0, react.useRef)(eq);
138
167
  selectorRef.current = selector;
139
168
  eqRef.current = eq;
140
- const handleRef = (0, react.useRef)(null);
141
- const getOrCreateHandle = (0, react.useCallback)(() => {
142
- if (!handleRef.current || handleRef.current.scope !== scope || handleRef.current.atom !== atom$1) handleRef.current = {
143
- scope,
144
- atom: atom$1,
145
- handle: scope.select(atom$1, selectorRef.current, { eq: eqRef.current })
146
- };
147
- return handleRef.current.handle;
148
- }, [scope, atom$1]);
169
+ const selectionCache = (0, react.useRef)(null);
149
170
  const getSnapshot = (0, react.useCallback)(() => {
150
171
  const state = ctrl.state;
151
- if (state === "idle" || state === "resolving") throw getOrCreatePendingPromise(atomRef.current, ctrl);
172
+ if (state === "idle") throw getOrCreatePendingPromise(ctrl);
152
173
  if (state === "failed") throw ctrl.get();
153
- return getOrCreateHandle().get();
154
- }, [ctrl, getOrCreateHandle]);
174
+ let value;
175
+ try {
176
+ value = ctrl.get();
177
+ } catch {
178
+ throw getOrCreatePendingPromise(ctrl);
179
+ }
180
+ const nextSelector = selectorRef.current;
181
+ const nextEq = eqRef.current;
182
+ const current = selectionCache.current;
183
+ if (current && current.ctrl === ctrl && current.ctrlState === state && Object.is(current.source, value) && current.selector === nextSelector && current.eq === nextEq) return current.value;
184
+ const nextValue = nextSelector(value);
185
+ const selectedValue = current && current.ctrl === ctrl && current.selector === nextSelector && (nextEq ?? Object.is)(current.value, nextValue) ? current.value : nextValue;
186
+ selectionCache.current = {
187
+ ctrl,
188
+ ctrlState: state,
189
+ source: value,
190
+ selector: nextSelector,
191
+ eq: nextEq,
192
+ value: selectedValue
193
+ };
194
+ return selectedValue;
195
+ }, [ctrl]);
155
196
  return (0, react.useSyncExternalStore)((0, react.useCallback)((onStoreChange) => {
156
- if (ctrl.state !== "resolved") return () => {};
157
- return getOrCreateHandle().subscribe(onStoreChange);
158
- }, [ctrl, getOrCreateHandle]), getSnapshot, getSnapshot);
197
+ return ctrl.on("*", onStoreChange);
198
+ }, [ctrl]), getSnapshot, getSnapshot);
159
199
  }
160
200
 
161
201
  //#endregion
package/dist/index.d.cts CHANGED
@@ -56,8 +56,10 @@ interface UseControllerOptions {
56
56
  *
57
57
  * @example
58
58
  * ```tsx
59
- * const scope = useScope()
60
- * await scope.resolve(myAtom)
59
+ * function MyComponent() {
60
+ * const scope = useScope()
61
+ * const handleClick = () => scope.resolve(myAtom)
62
+ * }
61
63
  * ```
62
64
  */
63
65
  declare function useScope(): Lite$1.Scope;
@@ -1 +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;AAGX;AAMF;AAMqC,iBDO1D,aAAA,CCLa;EAAA,KAAA;EAAA;AAAA,CAAA,EDKsB,kBCLtB,CAAA,EDKwC,kBAAA,CAAA,GAAA,CAAA,OCLxC;;;UAdZ,sBAAA;;;;;ADHiC,UCSjC,oBAAA,CDJQ;EAER,QAAA,EAAA,KAAA;EAeD;EAAgB,OAAA,CAAA,EAAA,OAAA;;KCPpB,cAAA,GAAiB,sBDOsB,GCPG,oBDOH;UCLlC,YDKoD,CAAA,CAAA,CAAA,CAAA;EAAA,IAAA,ECJtD,CDIsD,GAAA,SAAA;;SCFrD;cACK,MAAA,CAAK,WAAW;AArBa;AAGX,UAqBtB,oBAAA,CAfoB;EAMzB,OAAA,CAAA,EAAA,OAAc;AAAgD;;;;;;AAMtC;AAGC;AA4BC;;;;;iBAAtB,QAAA,CAAA,CAiBqD,EAjBzC,MAAA,CAAK,KAiBoC;AAAA;;;;;;;AAC+B;;iBADpF,aA2BsB,CAAA,CAAA,CAAA,CAAA,IAAA,EA3BC,MAAA,CAAK,IA2BN,CA3BW,CA2BX,CAAA,CAAA,EA3BgB,MAAA,CAAK,UA2BrB,CA3BgC,CA2BhC,CAAA;iBA1BtB,aA0BgC,CAAA,CAAA,CAAA,CAAA,IAAA,EA1BT,MAAA,CAAK,IA0BI,CA1BC,CA0BD,CAAA,EAAA,OAAA,EA1Bc,oBA0Bd,CAAA,EA1BqC,MAAA,CAAK,UA0B1C,CA1BqD,CA0BrD,CAAA;;AAAC;;;;;;AACiC;;iBADlE,OAEsB,CAAA,CAAA,CAAA,CAAA,IAAA,EAFL,MAAA,CAAK,IAEA,CAFK,CAEL,CAAA,CAAA,EAFU,CAEV;iBADtB,OACwC,CAAA,CAAA,CAAA,CAAA,IAAA,EADvB,MAAA,CAAK,IACkB,CADb,CACa,CAAA,EAAA,OAAA,EADA,sBACA,CAAA,EADyB,CACzB;iBAAxC,OAA4E,CAAA,CAAA,CAAA,CAAA,IAAA,EAA3D,MAAA,CAAK,IAAsD,CAAjD,CAAiD,CAAA,EAAA,OAAA,EAApC,oBAAoC,CAAA,EAAb,YAAa,CAAA,CAAA,CAAA;;;AAAD;;;;;;;;;;;;iBA8G3E,sBACD,MAAA,CAAK,KAAK,sBACE,MAAM,YACf,MAAM,gBACd"}
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;AAGX;AAMF;AAMqC,iBDO1D,aAAA,CCLa;EAAA,KAAA;EAAA;AAAA,CAAA,EDKsB,kBCLtB,CAAA,EDKwC,kBAAA,CAAA,GAAA,CAAA,OCLxC;;;UAdZ,sBAAA;;;;;ADHiC,UCSjC,oBAAA,CDJQ;EAER,QAAA,EAAA,KAAA;EAeD;EAAgB,OAAA,CAAA,EAAA,OAAA;;KCPpB,cAAA,GAAiB,sBDOsB,GCPG,oBDOH;UCLlC,YDKoD,CAAA,CAAA,CAAA,CAAA;EAAA,IAAA,ECJtD,CDIsD,GAAA,SAAA;;SCFrD;cACK,MAAA,CAAK,WAAW;AArBa;AAGX,UAqBtB,oBAAA,CAfoB;EAMzB,OAAA,CAAA,EAAA,OAAc;AAAgD;;;;;;AAMtC;AAGC;AAgDC;;;;;;AAiB+B;iBAjBrD,QAAA,CAAA,CAkBiC,EAlBrB,MAAA,CAAK,KAkBgB;;;;;;AAAmD;;;;iBADpF,aA2BiC,CAAA,CAAA,CAAA,CAAA,IAAA,EA3BV,MAAA,CAAK,IA2BK,CA3BA,CA2BA,CAAA,CAAA,EA3BK,MAAA,CAAK,UA2BV,CA3BqB,CA2BrB,CAAA;AAAA,iBA1BjC,aA2BO,CAAA,CAAA,CAAA,CAAA,IAAA,EA3BgB,MAAA,CAAK,IA2BrB,CA3B0B,CA2B1B,CAAA,EAAA,OAAA,EA3BuC,oBA2BvC,CAAA,EA3B8D,MAAA,CAAK,UA2BnE,CA3B8E,CA2B9E,CAAA;;;;;;AAA2D;;;;iBADlE,OAE4E,CAAA,CAAA,CAAA,CAAA,IAAA,EAF3D,MAAA,CAAK,IAEsD,CAFjD,CAEiD,CAAA,CAAA,EAF5C,CAE4C;iBAD5E,OAC+D,CAAA,CAAA,CAAA,CAAA,IAAA,EAD9C,MAAA,CAAK,IACyC,CADpC,CACoC,CAAA,EAAA,OAAA,EADvB,sBACuB,CAAA,EADE,CACF;iBAA/D,OAA2E,CAAA,CAAA,CAAA,CAAA,IAAA,EAA1D,MAAA,CAAK,IAAqD,CAAhD,CAAgD,CAAA,EAAA,OAAA,EAAnC,oBAAmC,CAAA,EAAZ,YAAY,CAAC,CAAD,CAAA;AAAA;;;;;;;;;;;;;;iBA0G3E,sBACD,MAAA,CAAK,KAAK,sBACE,MAAM,YACf,MAAM,gBACd"}
package/dist/index.d.mts CHANGED
@@ -56,8 +56,10 @@ interface UseControllerOptions {
56
56
  *
57
57
  * @example
58
58
  * ```tsx
59
- * const scope = useScope()
60
- * await scope.resolve(myAtom)
59
+ * function MyComponent() {
60
+ * const scope = useScope()
61
+ * const handleClick = () => scope.resolve(myAtom)
62
+ * }
61
63
  * ```
62
64
  */
63
65
  declare function useScope(): Lite$1.Scope;
@@ -1 +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;AAGX;AAMF;AAMqC,iBDO1D,aAAA,CCLa;EAAA,KAAA;EAAA;AAAA,CAAA,EDKsB,kBCLtB,CAAA,EDKwC,kBAAA,CAAA,GAAA,CAAA,OCLxC;;;UAdZ,sBAAA;;;;;ADHiC,UCSjC,oBAAA,CDJQ;EAER,QAAA,EAAA,KAAA;EAeD;EAAgB,OAAA,CAAA,EAAA,OAAA;;KCPpB,cAAA,GAAiB,sBDOsB,GCPG,oBDOH;UCLlC,YDKoD,CAAA,CAAA,CAAA,CAAA;EAAA,IAAA,ECJtD,CDIsD,GAAA,SAAA;;SCFrD;cACK,MAAA,CAAK,WAAW;AArBa;AAGX,UAqBtB,oBAAA,CAfoB;EAMzB,OAAA,CAAA,EAAA,OAAc;AAAgD;;;;;;AAMtC;AAGC;AA4BC;;;;;iBAAtB,QAAA,CAAA,CAiBqD,EAjBzC,MAAA,CAAK,KAiBoC;AAAA;;;;;;;AAC+B;;iBADpF,aA2BsB,CAAA,CAAA,CAAA,CAAA,IAAA,EA3BC,MAAA,CAAK,IA2BN,CA3BW,CA2BX,CAAA,CAAA,EA3BgB,MAAA,CAAK,UA2BrB,CA3BgC,CA2BhC,CAAA;iBA1BtB,aA0BgC,CAAA,CAAA,CAAA,CAAA,IAAA,EA1BT,MAAA,CAAK,IA0BI,CA1BC,CA0BD,CAAA,EAAA,OAAA,EA1Bc,oBA0Bd,CAAA,EA1BqC,MAAA,CAAK,UA0B1C,CA1BqD,CA0BrD,CAAA;;AAAC;;;;;;AACiC;;iBADlE,OAEsB,CAAA,CAAA,CAAA,CAAA,IAAA,EAFL,MAAA,CAAK,IAEA,CAFK,CAEL,CAAA,CAAA,EAFU,CAEV;iBADtB,OACwC,CAAA,CAAA,CAAA,CAAA,IAAA,EADvB,MAAA,CAAK,IACkB,CADb,CACa,CAAA,EAAA,OAAA,EADA,sBACA,CAAA,EADyB,CACzB;iBAAxC,OAA4E,CAAA,CAAA,CAAA,CAAA,IAAA,EAA3D,MAAA,CAAK,IAAsD,CAAjD,CAAiD,CAAA,EAAA,OAAA,EAApC,oBAAoC,CAAA,EAAb,YAAa,CAAA,CAAA,CAAA;;;AAAD;;;;;;;;;;;;iBA8G3E,sBACD,MAAA,CAAK,KAAK,sBACE,MAAM,YACf,MAAM,gBACd"}
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;AAGX;AAMF;AAMqC,iBDO1D,aAAA,CCLa;EAAA,KAAA;EAAA;AAAA,CAAA,EDKsB,kBCLtB,CAAA,EDKwC,kBAAA,CAAA,GAAA,CAAA,OCLxC;;;UAdZ,sBAAA;;;;;ADHiC,UCSjC,oBAAA,CDJQ;EAER,QAAA,EAAA,KAAA;EAeD;EAAgB,OAAA,CAAA,EAAA,OAAA;;KCPpB,cAAA,GAAiB,sBDOsB,GCPG,oBDOH;UCLlC,YDKoD,CAAA,CAAA,CAAA,CAAA;EAAA,IAAA,ECJtD,CDIsD,GAAA,SAAA;;SCFrD;cACK,MAAA,CAAK,WAAW;AArBa;AAGX,UAqBtB,oBAAA,CAfoB;EAMzB,OAAA,CAAA,EAAA,OAAc;AAAgD;;;;;;AAMtC;AAGC;AAgDC;;;;;;AAiB+B;iBAjBrD,QAAA,CAAA,CAkBiC,EAlBrB,MAAA,CAAK,KAkBgB;;;;;;AAAmD;;;;iBADpF,aA2BiC,CAAA,CAAA,CAAA,CAAA,IAAA,EA3BV,MAAA,CAAK,IA2BK,CA3BA,CA2BA,CAAA,CAAA,EA3BK,MAAA,CAAK,UA2BV,CA3BqB,CA2BrB,CAAA;AAAA,iBA1BjC,aA2BO,CAAA,CAAA,CAAA,CAAA,IAAA,EA3BgB,MAAA,CAAK,IA2BrB,CA3B0B,CA2B1B,CAAA,EAAA,OAAA,EA3BuC,oBA2BvC,CAAA,EA3B8D,MAAA,CAAK,UA2BnE,CA3B8E,CA2B9E,CAAA;;;;;;AAA2D;;;;iBADlE,OAE4E,CAAA,CAAA,CAAA,CAAA,IAAA,EAF3D,MAAA,CAAK,IAEsD,CAFjD,CAEiD,CAAA,CAAA,EAF5C,CAE4C;iBAD5E,OAC+D,CAAA,CAAA,CAAA,CAAA,IAAA,EAD9C,MAAA,CAAK,IACyC,CADpC,CACoC,CAAA,EAAA,OAAA,EADvB,sBACuB,CAAA,EADE,CACF;iBAA/D,OAA2E,CAAA,CAAA,CAAA,CAAA,IAAA,EAA1D,MAAA,CAAK,IAAqD,CAAhD,CAAgD,CAAA,EAAA,OAAA,EAAnC,oBAAmC,CAAA,EAAZ,YAAY,CAAC,CAAD,CAAA;AAAA;;;;;;;;;;;;;;iBA0G3E,sBACD,MAAA,CAAK,KAAK,sBACE,MAAM,YACf,MAAM,gBACd"}
package/dist/index.mjs CHANGED
@@ -27,12 +27,28 @@ function ScopeProvider({ scope, children }) {
27
27
  //#endregion
28
28
  //#region src/hooks.ts
29
29
  const pendingPromises = /* @__PURE__ */ new WeakMap();
30
- function getOrCreatePendingPromise(atom$1, ctrl) {
31
- let pending = pendingPromises.get(atom$1);
30
+ function getOrCreatePendingPromise(ctrl) {
31
+ let pending = pendingPromises.get(ctrl);
32
32
  if (!pending) {
33
- pending = ctrl.resolve();
34
- pendingPromises.set(atom$1, pending);
35
- pending.finally(() => pendingPromises.delete(atom$1));
33
+ if (ctrl.state === "resolving") pending = new Promise((resolve, reject) => {
34
+ const unsub = ctrl.on("*", () => {
35
+ if (ctrl.state === "resolved") {
36
+ unsub();
37
+ resolve(ctrl.get());
38
+ } else if (ctrl.state === "failed") {
39
+ unsub();
40
+ try {
41
+ ctrl.get();
42
+ } catch (e) {
43
+ reject(e);
44
+ }
45
+ }
46
+ });
47
+ });
48
+ else pending = ctrl.resolve();
49
+ pendingPromises.set(ctrl, pending);
50
+ pending.catch(() => {});
51
+ pending.then(() => pendingPromises.delete(ctrl), () => pendingPromises.delete(ctrl));
36
52
  }
37
53
  return pending;
38
54
  }
@@ -44,8 +60,10 @@ function getOrCreatePendingPromise(atom$1, ctrl) {
44
60
  *
45
61
  * @example
46
62
  * ```tsx
47
- * const scope = useScope()
48
- * await scope.resolve(myAtom)
63
+ * function MyComponent() {
64
+ * const scope = useScope()
65
+ * const handleClick = () => scope.resolve(myAtom)
66
+ * }
49
67
  * ```
50
68
  */
51
69
  function useScope() {
@@ -57,62 +75,76 @@ function useController(atom$1, options) {
57
75
  const scope = useScope();
58
76
  const ctrl = useMemo(() => scope.controller(atom$1), [scope, atom$1]);
59
77
  if (options?.resolve) {
60
- if (ctrl.state === "idle" || ctrl.state === "resolving") throw getOrCreatePendingPromise(atom$1, ctrl);
78
+ if (ctrl.state === "idle" || ctrl.state === "resolving") throw getOrCreatePendingPromise(ctrl);
61
79
  if (ctrl.state === "failed") throw ctrl.get();
62
80
  }
63
81
  return ctrl;
64
82
  }
65
83
  function useAtom(atom$1, options) {
66
84
  const ctrl = useController(atom$1);
67
- const atomRef = useRef(atom$1);
68
- atomRef.current = atom$1;
69
- if (options?.suspense === false) return useAtomState(atom$1, ctrl, options.resolve ?? false);
70
- const autoResolve = options?.resolve !== false;
71
- const getSnapshot = useCallback(() => {
72
- if (ctrl.state === "idle") {
73
- if (autoResolve) throw getOrCreatePendingPromise(atomRef.current, ctrl);
74
- throw new Error("Atom is not resolved. Set resolve: true or resolve the atom before rendering.");
75
- }
76
- if (ctrl.state === "resolving") throw getOrCreatePendingPromise(atomRef.current, ctrl);
77
- if (ctrl.state === "failed") throw ctrl.get();
78
- return ctrl.get();
79
- }, [ctrl, autoResolve]);
80
- return useSyncExternalStore(useCallback((onStoreChange) => ctrl.on("resolved", onStoreChange), [ctrl]), getSnapshot, getSnapshot);
81
- }
82
- function useAtomState(atom$1, ctrl, autoResolve) {
85
+ const ctrlState = ctrl.state;
86
+ const isSuspense = options?.suspense !== false;
87
+ const autoResolve = isSuspense ? options?.resolve !== false : !!options?.resolve;
83
88
  const stateCache = useRef(null);
84
89
  useEffect(() => {
85
- if (autoResolve && (ctrl.state === "idle" || ctrl.state === "resolving")) getOrCreatePendingPromise(atom$1, ctrl);
90
+ if (!isSuspense && (ctrlState === "resolving" || autoResolve && ctrlState === "idle")) getOrCreatePendingPromise(ctrl).catch(() => {});
86
91
  }, [
87
- atom$1,
88
92
  ctrl,
89
- autoResolve
93
+ ctrlState,
94
+ autoResolve,
95
+ isSuspense
90
96
  ]);
91
97
  const getSnapshot = useCallback(() => {
98
+ if (isSuspense) {
99
+ if (ctrl.state === "idle") {
100
+ if (autoResolve) throw getOrCreatePendingPromise(ctrl);
101
+ throw new Error("Atom is not resolved. Set resolve: true or resolve the atom before rendering.");
102
+ }
103
+ if (ctrl.state === "failed") throw ctrl.get();
104
+ try {
105
+ return ctrl.get();
106
+ } catch {
107
+ throw getOrCreatePendingPromise(ctrl);
108
+ }
109
+ }
92
110
  let data;
93
111
  let error;
94
- if (ctrl.state === "resolved") data = ctrl.get();
112
+ if (ctrl.state === "resolved" || ctrl.state === "resolving") try {
113
+ data = ctrl.get();
114
+ } catch {}
95
115
  else if (ctrl.state === "failed") try {
96
116
  ctrl.get();
97
117
  } catch (e) {
98
118
  error = e instanceof Error ? e : new Error(String(e));
99
119
  }
100
- if (stateCache.current && stateCache.current.ctrlState === ctrl.state && stateCache.current.data === data && stateCache.current.error === error) return stateCache.current.result;
120
+ const loading = ctrl.state === "resolving" || autoResolve && ctrl.state === "idle";
121
+ if (stateCache.current && stateCache.current.ctrl === ctrl && stateCache.current.ctrlState === ctrl.state && stateCache.current.data === data && stateCache.current.error === error && stateCache.current.loading === loading) return stateCache.current.result;
101
122
  const result = {
102
123
  data,
103
- loading: ctrl.state === "resolving",
124
+ loading,
104
125
  error,
105
126
  controller: ctrl
106
127
  };
107
128
  stateCache.current = {
129
+ ctrl,
108
130
  ctrlState: ctrl.state,
109
131
  data,
110
132
  error,
133
+ loading,
111
134
  result
112
135
  };
113
136
  return result;
114
- }, [ctrl]);
115
- return useSyncExternalStore(useCallback((onStoreChange) => ctrl.on("*", onStoreChange), [ctrl]), getSnapshot, getSnapshot);
137
+ }, [
138
+ ctrl,
139
+ autoResolve,
140
+ isSuspense
141
+ ]);
142
+ return useSyncExternalStore(useCallback((onStoreChange) => {
143
+ return ctrl.on("*", () => {
144
+ if (!isSuspense && ctrl.state === "resolving") getOrCreatePendingPromise(ctrl).catch(() => {});
145
+ onStoreChange();
146
+ });
147
+ }, [ctrl, isSuspense]), getSnapshot, getSnapshot);
116
148
  }
117
149
  /**
118
150
  * Select a derived value from an atom with fine-grained reactivity.
@@ -129,33 +161,41 @@ function useAtomState(atom$1, ctrl, autoResolve) {
129
161
  * ```
130
162
  */
131
163
  function useSelect(atom$1, selector, eq) {
132
- const scope = useScope();
133
164
  const ctrl = useController(atom$1);
134
- const atomRef = useRef(atom$1);
135
- atomRef.current = atom$1;
136
165
  const selectorRef = useRef(selector);
137
166
  const eqRef = useRef(eq);
138
167
  selectorRef.current = selector;
139
168
  eqRef.current = eq;
140
- const handleRef = useRef(null);
141
- const getOrCreateHandle = useCallback(() => {
142
- if (!handleRef.current || handleRef.current.scope !== scope || handleRef.current.atom !== atom$1) handleRef.current = {
143
- scope,
144
- atom: atom$1,
145
- handle: scope.select(atom$1, selectorRef.current, { eq: eqRef.current })
146
- };
147
- return handleRef.current.handle;
148
- }, [scope, atom$1]);
169
+ const selectionCache = useRef(null);
149
170
  const getSnapshot = useCallback(() => {
150
171
  const state = ctrl.state;
151
- if (state === "idle" || state === "resolving") throw getOrCreatePendingPromise(atomRef.current, ctrl);
172
+ if (state === "idle") throw getOrCreatePendingPromise(ctrl);
152
173
  if (state === "failed") throw ctrl.get();
153
- return getOrCreateHandle().get();
154
- }, [ctrl, getOrCreateHandle]);
174
+ let value;
175
+ try {
176
+ value = ctrl.get();
177
+ } catch {
178
+ throw getOrCreatePendingPromise(ctrl);
179
+ }
180
+ const nextSelector = selectorRef.current;
181
+ const nextEq = eqRef.current;
182
+ const current = selectionCache.current;
183
+ if (current && current.ctrl === ctrl && current.ctrlState === state && Object.is(current.source, value) && current.selector === nextSelector && current.eq === nextEq) return current.value;
184
+ const nextValue = nextSelector(value);
185
+ const selectedValue = current && current.ctrl === ctrl && current.selector === nextSelector && (nextEq ?? Object.is)(current.value, nextValue) ? current.value : nextValue;
186
+ selectionCache.current = {
187
+ ctrl,
188
+ ctrlState: state,
189
+ source: value,
190
+ selector: nextSelector,
191
+ eq: nextEq,
192
+ value: selectedValue
193
+ };
194
+ return selectedValue;
195
+ }, [ctrl]);
155
196
  return useSyncExternalStore(useCallback((onStoreChange) => {
156
- if (ctrl.state !== "resolved") return () => {};
157
- return getOrCreateHandle().subscribe(onStoreChange);
158
- }, [ctrl, getOrCreateHandle]), getSnapshot, getSnapshot);
197
+ return ctrl.on("*", onStoreChange);
198
+ }, [ctrl]), getSnapshot, getSnapshot);
159
199
  }
160
200
 
161
201
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["atom","data: T | undefined","error: Error | undefined","result: UseAtomState<T>"],"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, useEffect, useMemo, useRef, useSyncExternalStore } from 'react'\nimport { type Lite } from '@pumped-fn/lite'\nimport { ScopeContext } from './context'\n\ninterface UseAtomSuspenseOptions {\n suspense?: true\n /** @default true */\n resolve?: boolean\n}\n\ninterface UseAtomManualOptions {\n suspense: false\n /** @default false */\n resolve?: boolean\n}\n\ntype UseAtomOptions = UseAtomSuspenseOptions | UseAtomManualOptions\n\ninterface UseAtomState<T> {\n data: T | undefined\n loading: boolean\n error: Error | undefined\n controller: Lite.Controller<T>\n}\n\ninterface UseControllerOptions {\n resolve?: boolean\n}\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 * @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>\nfunction useController<T>(atom: Lite.Atom<T>, options: UseControllerOptions): Lite.Controller<T>\nfunction useController<T>(atom: Lite.Atom<T>, options?: UseControllerOptions): Lite.Controller<T> {\n const scope = useScope()\n const ctrl = useMemo(() => scope.controller(atom), [scope, atom])\n\n if (options?.resolve) {\n if (ctrl.state === 'idle' || ctrl.state === 'resolving') {\n throw getOrCreatePendingPromise(atom, ctrl)\n }\n if (ctrl.state === 'failed') {\n throw ctrl.get()\n }\n }\n\n return ctrl\n}\n\n/**\n * Subscribe to atom value with Suspense/ErrorBoundary integration.\n *\n * @example\n * ```tsx\n * const user = useAtom(userAtom)\n * const { data, loading, error } = useAtom(userAtom, { suspense: false })\n * ```\n */\nfunction useAtom<T>(atom: Lite.Atom<T>): T\nfunction useAtom<T>(atom: Lite.Atom<T>, options: UseAtomSuspenseOptions): T\nfunction useAtom<T>(atom: Lite.Atom<T>, options: UseAtomManualOptions): UseAtomState<T>\nfunction useAtom<T>(atom: Lite.Atom<T>, options?: UseAtomOptions): T | UseAtomState<T> {\n const ctrl = useController(atom)\n const atomRef = useRef(atom)\n atomRef.current = atom\n\n if (options?.suspense === false) {\n return useAtomState(atom, ctrl, options.resolve ?? false)\n }\n\n const autoResolve = options?.resolve !== false\n\n const getSnapshot = useCallback((): T => {\n if (ctrl.state === 'idle') {\n if (autoResolve) {\n throw getOrCreatePendingPromise(atomRef.current, ctrl)\n }\n throw new Error('Atom is not resolved. Set resolve: true or resolve the atom before rendering.')\n }\n if (ctrl.state === 'resolving') {\n throw getOrCreatePendingPromise(atomRef.current, ctrl)\n }\n if (ctrl.state === 'failed') {\n throw ctrl.get()\n }\n return ctrl.get()\n }, [ctrl, autoResolve])\n\n const subscribe = useCallback(\n (onStoreChange: () => void) => ctrl.on('resolved', onStoreChange),\n [ctrl]\n )\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n}\n\nfunction useAtomState<T>(\n atom: Lite.Atom<T>,\n ctrl: Lite.Controller<T>,\n autoResolve: boolean\n): UseAtomState<T> {\n const stateCache = useRef<{\n ctrlState: Lite.Controller<T>['state']\n data: T | undefined\n error: Error | undefined\n result: UseAtomState<T>\n } | null>(null)\n\n useEffect(() => {\n if (autoResolve && (ctrl.state === 'idle' || ctrl.state === 'resolving')) {\n getOrCreatePendingPromise(atom, ctrl)\n }\n }, [atom, ctrl, autoResolve])\n\n const getSnapshot = useCallback((): UseAtomState<T> => {\n let data: T | undefined\n let error: Error | undefined\n\n if (ctrl.state === 'resolved') {\n data = ctrl.get()\n } else if (ctrl.state === 'failed') {\n try {\n ctrl.get()\n } catch (e) {\n error = e instanceof Error ? e : new Error(String(e))\n }\n }\n\n if (\n stateCache.current &&\n stateCache.current.ctrlState === ctrl.state &&\n stateCache.current.data === data &&\n stateCache.current.error === error\n ) {\n return stateCache.current.result\n }\n\n const result: UseAtomState<T> = {\n data,\n loading: ctrl.state === 'resolving',\n error,\n controller: ctrl,\n }\n\n stateCache.current = { ctrlState: ctrl.state, data, error, result }\n return result\n }, [ctrl])\n\n const subscribe = useCallback(\n (onStoreChange: () => void) => ctrl.on('*', 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 }\nexport type { UseAtomSuspenseOptions, UseAtomManualOptions, UseAtomOptions, UseAtomState, UseControllerOptions }\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["atom","data: T | undefined","error: Error | undefined","result: UseAtomState<T>","value: T"],"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, useEffect, useMemo, useRef, useSyncExternalStore } from 'react'\nimport { type Lite } from '@pumped-fn/lite'\nimport { ScopeContext } from './context'\n\ninterface UseAtomSuspenseOptions {\n suspense?: true\n /** @default true */\n resolve?: boolean\n}\n\ninterface UseAtomManualOptions {\n suspense: false\n /** @default false */\n resolve?: boolean\n}\n\ntype UseAtomOptions = UseAtomSuspenseOptions | UseAtomManualOptions\n\ninterface UseAtomState<T> {\n data: T | undefined\n loading: boolean\n error: Error | undefined\n controller: Lite.Controller<T>\n}\n\ninterface UseControllerOptions {\n resolve?: boolean\n}\n\nconst pendingPromises = new WeakMap<Lite.Controller<unknown>, Promise<unknown>>()\n\nfunction getOrCreatePendingPromise<T>(ctrl: Lite.Controller<T>): Promise<T> {\n let pending = pendingPromises.get(ctrl) as Promise<T> | undefined\n if (!pending) {\n if (ctrl.state === 'resolving') {\n pending = new Promise<T>((resolve, reject) => {\n const unsub = ctrl.on('*', () => {\n if (ctrl.state === 'resolved') {\n unsub()\n resolve(ctrl.get())\n } else if (ctrl.state === 'failed') {\n unsub()\n try { ctrl.get() } catch (e) { reject(e) }\n }\n })\n })\n } else {\n pending = ctrl.resolve()\n }\n pendingPromises.set(ctrl, pending)\n void pending.catch(() => {})\n pending.then(\n () => pendingPromises.delete(ctrl),\n () => pendingPromises.delete(ctrl)\n )\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 * function MyComponent() {\n * const scope = useScope()\n * const handleClick = () => scope.resolve(myAtom)\n * }\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 * @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>\nfunction useController<T>(atom: Lite.Atom<T>, options: UseControllerOptions): Lite.Controller<T>\nfunction useController<T>(atom: Lite.Atom<T>, options?: UseControllerOptions): Lite.Controller<T> {\n const scope = useScope()\n const ctrl = useMemo(() => scope.controller(atom), [scope, atom])\n\n if (options?.resolve) {\n if (ctrl.state === 'idle' || ctrl.state === 'resolving') {\n throw getOrCreatePendingPromise(ctrl)\n }\n if (ctrl.state === 'failed') {\n throw ctrl.get()\n }\n }\n\n return ctrl\n}\n\n/**\n * Subscribe to atom value with Suspense/ErrorBoundary integration.\n *\n * @example\n * ```tsx\n * const user = useAtom(userAtom)\n * const { data, loading, error } = useAtom(userAtom, { suspense: false })\n * ```\n */\nfunction useAtom<T>(atom: Lite.Atom<T>): T\nfunction useAtom<T>(atom: Lite.Atom<T>, options: UseAtomSuspenseOptions): T\nfunction useAtom<T>(atom: Lite.Atom<T>, options: UseAtomManualOptions): UseAtomState<T>\nfunction useAtom<T>(atom: Lite.Atom<T>, options?: UseAtomOptions): T | UseAtomState<T> {\n const ctrl = useController(atom)\n const ctrlState = ctrl.state\n\n const isSuspense = options?.suspense !== false\n const autoResolve = isSuspense ? options?.resolve !== false : !!options?.resolve\n\n const stateCache = useRef<{\n ctrl: Lite.Controller<T>\n ctrlState: Lite.Controller<T>['state']\n data: T | undefined\n error: Error | undefined\n loading: boolean\n result: UseAtomState<T>\n } | null>(null)\n\n useEffect(() => {\n if (!isSuspense && (ctrlState === 'resolving' || (autoResolve && ctrlState === 'idle'))) {\n void getOrCreatePendingPromise(ctrl).catch(() => {})\n }\n }, [ctrl, ctrlState, autoResolve, isSuspense])\n\n const getSnapshot = useCallback((): T | UseAtomState<T> => {\n if (isSuspense) {\n if (ctrl.state === 'idle') {\n if (autoResolve) {\n throw getOrCreatePendingPromise(ctrl)\n }\n throw new Error('Atom is not resolved. Set resolve: true or resolve the atom before rendering.')\n }\n if (ctrl.state === 'failed') {\n throw ctrl.get()\n }\n try {\n return ctrl.get()\n } catch {\n throw getOrCreatePendingPromise(ctrl)\n }\n }\n\n let data: T | undefined\n let error: Error | undefined\n\n if (ctrl.state === 'resolved' || ctrl.state === 'resolving') {\n try {\n data = ctrl.get()\n } catch {}\n } else if (ctrl.state === 'failed') {\n try {\n ctrl.get()\n } catch (e) {\n error = e instanceof Error ? e : new Error(String(e))\n }\n }\n\n const loading = ctrl.state === 'resolving' || (autoResolve && ctrl.state === 'idle')\n\n if (\n stateCache.current &&\n stateCache.current.ctrl === ctrl &&\n stateCache.current.ctrlState === ctrl.state &&\n stateCache.current.data === data &&\n stateCache.current.error === error &&\n stateCache.current.loading === loading\n ) {\n return stateCache.current.result\n }\n\n const result: UseAtomState<T> = {\n data,\n loading,\n error,\n controller: ctrl,\n }\n\n stateCache.current = { ctrl, ctrlState: ctrl.state, data, error, loading, result }\n return result\n }, [ctrl, autoResolve, isSuspense])\n\n const subscribe = useCallback((onStoreChange: () => void) => {\n return ctrl.on('*', () => {\n if (!isSuspense && ctrl.state === 'resolving') {\n void getOrCreatePendingPromise(ctrl).catch(() => {})\n }\n onStoreChange()\n })\n }, [ctrl, isSuspense])\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 ctrl = useController(atom)\n\n const selectorRef = useRef(selector)\n const eqRef = useRef(eq)\n selectorRef.current = selector\n eqRef.current = eq\n\n const selectionCache = useRef<{\n ctrl: Lite.Controller<T>\n ctrlState: Lite.Controller<T>['state']\n source: T\n selector: (value: T) => S\n eq: ((a: S, b: S) => boolean) | undefined\n value: S\n } | null>(null)\n\n const getSnapshot = useCallback((): S => {\n const state = ctrl.state\n if (state === 'idle') {\n throw getOrCreatePendingPromise(ctrl)\n }\n if (state === 'failed') {\n throw ctrl.get()\n }\n let value: T\n try {\n value = ctrl.get()\n } catch {\n throw getOrCreatePendingPromise(ctrl)\n }\n\n const nextSelector = selectorRef.current\n const nextEq = eqRef.current\n const current = selectionCache.current\n if (\n current &&\n current.ctrl === ctrl &&\n current.ctrlState === state &&\n Object.is(current.source, value) &&\n current.selector === nextSelector &&\n current.eq === nextEq\n ) {\n return current.value\n }\n\n const nextValue = nextSelector(value)\n const selectedValue = current &&\n current.ctrl === ctrl &&\n current.selector === nextSelector &&\n (nextEq ?? Object.is)(current.value, nextValue)\n ? current.value\n : nextValue\n\n selectionCache.current = {\n ctrl,\n ctrlState: state,\n source: value,\n selector: nextSelector,\n eq: nextEq,\n value: selectedValue,\n }\n\n return selectedValue\n }, [ctrl])\n\n const subscribe = useCallback((onStoreChange: () => void) => {\n return ctrl.on('*', onStoreChange)\n }, [ctrl])\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n}\n\nexport { useScope, useController, useAtom, useSelect }\nexport type { UseAtomSuspenseOptions, UseAtomManualOptions, UseAtomOptions, UseAtomState, UseControllerOptions }\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pumped-fn/lite-react",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "React integration for @pumped-fn/lite",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -31,13 +31,14 @@
31
31
  "devDependencies": {
32
32
  "@testing-library/jest-dom": "^6.9.1",
33
33
  "@testing-library/react": "^16.3.0",
34
+ "@types/node": "^20.19.22",
34
35
  "@types/react": "^18.3.27",
35
36
  "jsdom": "^27.3.0",
36
37
  "react": "^18.3.1",
37
38
  "tsdown": "^0.17.2",
38
39
  "typescript": "^5.9.3",
39
- "vitest": "^4.0.15",
40
- "@pumped-fn/lite": "1.10.0"
40
+ "vitest": "^4.0.18",
41
+ "@pumped-fn/lite": "2.1.2"
41
42
  },
42
43
  "engines": {
43
44
  "node": ">=18"