muya 2.0.7 → 2.0.9
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/README.md +8 -0
- package/cjs/index.js +1 -1
- package/esm/create-state.js +1 -1
- package/esm/create.js +1 -1
- package/esm/select.js +1 -1
- package/esm/use-value.js +1 -1
- package/package.json +1 -1
- package/src/__tests__/compare.test.tsx +68 -0
- package/src/__tests__/select.test.tsx +30 -3
- package/src/create-state.ts +6 -5
- package/src/create.ts +1 -0
- package/src/select.ts +33 -12
- package/src/types.ts +4 -4
- package/src/use-value.ts +3 -3
- package/types/create-state.d.ts +1 -0
- package/types/select.d.ts +4 -1
- package/types/types.d.ts +4 -4
- package/types/use-value.d.ts +1 -1
package/README.md
CHANGED
|
@@ -251,6 +251,11 @@ So how `set` state will behave with async initial value?
|
|
|
251
251
|
1. Directly call `.set(2)` will be sync, and will set the value to 2 (it will cancel the initial promise)
|
|
252
252
|
2. Call `.set((prev) => prev + 1)` will wait until previous promise is resolved, so previous value in set callback is always resolved.
|
|
253
253
|
|
|
254
|
+
### Async selectors knowledge base
|
|
255
|
+
1. Values in selectors derived from another async selectors will be always sync **(not promise)**
|
|
256
|
+
2. If the selector is async (`state.select(async (s) => s + 1)`) it will automatically use suspense mode, so on each change it firstly resolved promise (hit suspense) and then update the value.
|
|
257
|
+
3. If the selector is sync, but it's derived from async selector, it will also be in suspense mode - but it depends what the parent is, if the parent is async state, it will hit the suspense only once, on initial load, but if the parent is another async selector, it will hit suspense on each change.
|
|
258
|
+
|
|
254
259
|
### Debugging
|
|
255
260
|
`Muya` in dev mode automatically connects to the `redux` devtools extension if it is installed in the browser. For now devtool api is simple - state updates.
|
|
256
261
|
|
|
@@ -258,3 +263,6 @@ So how `set` state will behave with async initial value?
|
|
|
258
263
|
|
|
259
264
|
This library is a fun, experimental project and not a replacement for robust state management tools. For more advanced use cases, consider libraries like `Zustand`, `Jotai`, or `Redux`.
|
|
260
265
|
If you enjoy `Muya`, please give it a ⭐️! :)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
|
package/cjs/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var I=Object.defineProperty;var
|
|
1
|
+
"use strict";var I=Object.defineProperty;var j=Object.getOwnPropertyDescriptor;var N=Object.getOwnPropertyNames;var _=Object.prototype.hasOwnProperty;var K=(e,t)=>{for(var r in t)I(e,r,{get:t[r],enumerable:!0})},Y=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of N(t))!_.call(e,o)&&o!==r&&I(e,o,{get:()=>t[o],enumerable:!(n=j(t,o))||n.enumerable});return e};var J=e=>Y(I({},"__esModule",{value:!0}),e);var $={};K($,{EMPTY_SELECTOR:()=>F,create:()=>L,isAbortError:()=>v,isArray:()=>k,isEqualBase:()=>S,isError:()=>D,isFunction:()=>w,isMap:()=>E,isPromise:()=>f,isSet:()=>g,isSetValueFunction:()=>G,isState:()=>W,isUndefined:()=>d,select:()=>P,shallow:()=>z,useValue:()=>V});module.exports=J($);var T=class extends Error{static Error="AbortError"};function Q(e,t){t&&t.abort();let r=new AbortController,{signal:n}=r;return{promise:new Promise((u,s)=>{n.addEventListener("abort",()=>{s(new T)}),e.then(u).catch(s)}),controller:r}}function x(e,t=S){if(!d(e.current)){if(!d(e.previous)&&t(e.current,e.previous))return!1;e.previous=e.current}return!0}function p(e,t,r){if(!f(r))return r;e.abortController&&e.abortController.abort();let{promise:n,controller:o}=Q(r,e.abortController);return e.abortController=o,n.then(u=>{e.current=u,t()}).catch(u=>{v(u)||(e.current=u,t())})}function f(e){return e instanceof Promise}function w(e){return typeof e=="function"}function E(e){return e instanceof Map}function g(e){return e instanceof Set}function k(e){return Array.isArray(e)}function S(e,t){return e===t?!0:!!Object.is(e,t)}function G(e){return typeof e=="function"}function v(e){return e instanceof T}function D(e){return e instanceof Error}function d(e){return e===void 0}function W(e){return w(e)&&"get"in e&&"set"in e&&"isSet"in e&&e.isSet===!0}var F=e=>e;function q(){let e=new Map,t=new Set,r=performance.now(),n=!1;function o(){let s=performance.now(),a=s-r,{size:i}=t;if(a<.2&&i>0&&i<10){r=s,u();return}n||(n=!0,Promise.resolve().then(()=>{n=!1,r=performance.now(),u()}))}function u(){if(t.size===0)return;let s=new Set;for(let a of t){if(e.has(a.id)){s.add(a.id);let{onResolveItem:i}=e.get(a.id);i&&i(a.value)}t.delete(a)}if(t.size>0){o();return}for(let a of s)e.get(a)?.onFinish()}return{add(s,a){return e.set(s,a),()=>{e.delete(s)}},schedule(s,a){t.add({value:a,id:s}),o()}}}function P(e,t,r){let n={};function o(){let c=!1,l=e.map(O=>{let y=O.get();return f(y)&&(c=!0),y});return c?new Promise((O,y)=>{Promise.all(l).then(U=>{if(U.some(H=>d(H)))return y(new T);let M=t(...U);O(M)})}):t(...l)}function u(){if(d(n.current)){let c=o();n.current=p(n,i.emitter.emit,c)}return n.current}function s(){if(d(n.current)){let l=o();n.current=p(n,i.emitter.emit,l)}let{current:c}=n;return f(c)?new Promise(l=>{c.then(b=>{if(d(b)){l(s());return}l(b)})}):n.current}let a=[];for(let c of e){let l=c.emitter.subscribe(()=>{h.schedule(i.id,null)});a.push(l)}let i=C({destroy(){for(let c of a)c();m(),i.emitter.clear(),n.current=void 0},get:s,getSnapshot:u}),m=h.add(i.id,{onFinish(){let c=o();n.current=p(n,i.emitter.emit,c),x(n,r)&&i.emitter.emit()}});return i}var A=require("react");function V(e,t=F){let{emitter:r}=e,n=(0,A.useSyncExternalStore)(e.emitter.subscribe,()=>t(r.getSnapshot()),()=>t(r.getInitialSnapshot?r.getInitialSnapshot():r.getSnapshot()));if((0,A.useDebugValue)(n),f(n)||D(n))throw n;return n}function R(e,t){let r=new Set,n=[];return{clear:()=>{for(let o of n)o();r.clear()},subscribe:o=>(r.add(o),()=>{r.delete(o)}),emit:(...o)=>{for(let u of r)u(...o)},contains:o=>r.has(o),getSnapshot:e,getInitialSnapshot:t,getSize:()=>r.size,subscribeToOtherEmitter(o){let u=o.subscribe(()=>{this.emit()});n.push(u)}}}var X=0;function Z(){return X++}function C(e){let{get:t,destroy:r,set:n,getSnapshot:o}=e,u=!!n,s=function(a){return V(s,a)};return s.isSet=u,s.id=Z(),s.emitter=R(o),s.destroy=r,s.listen=function(a){return this.emitter.subscribe(()=>{let i=t();f(i)||a(t())})},s.withName=function(a){return this.stateName=a,this},s.select=function(a,i=S){return P([s],a,i)},s.get=t,s.set=n,s}var h=q();function L(e,t=S){let r={};function n(){try{if(d(r.current)){let i=w(e)?e():e,m=p(r,s.emitter.emit,i);return r.current=m,r.current}return r.current}catch(i){r.current=i}return r.current}async function o(i,m){await i;let c=m(r.current),l=p(r,s.emitter.emit,c);r.current=l}function u(i){let m=n(),c=G(i);if(c&&f(m)){o(m,i);return}r.abortController&&r.abortController.abort();let l=c?i(m):i,b=p(r,s.emitter.emit,l);r.current=b}let s=C({get:n,destroy(){n(),a(),s.emitter.clear(),r.current=void 0},set(i){h.schedule(s.id,i)},getSnapshot:n}),a=h.add(s.id,{onFinish(){r.current=n(),x(r,t)&&s.emitter.emit()},onResolveItem:u});return w(e)||n(),s}function z(e,t){if(e==t)return!0;if(typeof e!="object"||e==null||typeof t!="object"||t==null)return!1;if(E(e)&&E(t)){if(e.size!==t.size)return!1;for(let[o,u]of e)if(!Object.is(u,t.get(o)))return!1;return!0}if(g(e)&&g(t)){if(e.size!==t.size)return!1;for(let o of e)if(!t.has(o))return!1;return!0}if(k(e)&&k(t)){if(e.length!==t.length)return!1;for(let[o,u]of e.entries())if(!Object.is(u,t[o]))return!1;return!0}let r=Object.keys(e),n=Object.keys(t);if(r.length!==n.length)return!1;for(let o of r)if(!Object.prototype.hasOwnProperty.call(t,o)||!Object.is(e[o],t[o]))return!1;return!0}
|
package/esm/create-state.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{select as
|
|
1
|
+
import{select as l}from"./select";import{useValue as S}from"./use-value";import{createEmitter as c}from"./utils/create-emitter";import{isEqualBase as d,isPromise as m}from"./utils/is";let T=0;function f(){return T++}function h(n){const{get:r,destroy:o,set:a,getSnapshot:i}=n,u=!!a,t=function(e){return S(t,e)};return t.isSet=u,t.id=f(),t.emitter=c(i),t.destroy=o,t.listen=function(e){return this.emitter.subscribe(()=>{const s=r();m(s)||e(r())})},t.withName=function(e){return this.stateName=e,this},t.select=function(e,s=d){return l([t],e,s)},t.get=r,t.set=a,t}export{h as createState};
|
package/esm/create.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{canUpdate as p,handleAsyncUpdate as u}from"./utils/common";import{isEqualBase as
|
|
1
|
+
import{canUpdate as p,handleAsyncUpdate as u}from"./utils/common";import{isEqualBase as h,isFunction as i,isPromise as V,isSetValueFunction as b,isUndefined as y}from"./utils/is";import{createScheduler as C}from"./scheduler";import{subscribeToDevelopmentTools as w}from"./debug/development-tools";import{createState as v}from"./create-state";const l=C();function E(c,m=h){const e={};function o(){try{if(y(e.current)){const t=i(c)?c():c,n=u(e,r.emitter.emit,t);return e.current=n,e.current}return e.current}catch(t){e.current=t}return e.current}async function d(t,n){await t;const a=n(e.current),s=u(e,r.emitter.emit,a);e.current=s}function f(t){const n=o(),a=b(t);if(a&&V(n)){d(n,t);return}e.abortController&&e.abortController.abort();const s=a?t(n):t,S=u(e,r.emitter.emit,s);e.current=S}const r=v({get:o,destroy(){o(),T(),r.emitter.clear(),e.current=void 0},set(t){l.schedule(r.id,t)},getSnapshot:o}),T=l.add(r.id,{onFinish(){e.current=o(),p(e,m)&&r.emitter.emit()},onResolveItem:f});return i(c)||o(),w(r),r}export{E as create,l as stateScheduler};
|
package/esm/select.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{stateScheduler as
|
|
1
|
+
import{stateScheduler as T}from"./create";import{createState as b}from"./create-state";import{subscribeToDevelopmentTools as P}from"./debug/development-tools";import{AbortError as V,canUpdate as g,handleAsyncUpdate as c}from"./utils/common";import{isPromise as y,isUndefined as i}from"./utils/is";function U(m,d,w){const e={};function s(){let t=!1;const r=m.map(u=>{const o=u.get();return y(o)&&(t=!0),o});return t?new Promise((u,o)=>{Promise.all(r).then(p=>{if(p.some(k=>i(k)))return o(new V);const A=d(...p);u(A)})}):d(...r)}function S(){if(i(e.current)){const t=s();e.current=c(e,n.emitter.emit,t)}return e.current}function f(){if(i(e.current)){const r=s();e.current=c(e,n.emitter.emit,r)}const{current:t}=e;return y(t)?new Promise(r=>{t.then(a=>{if(i(a)){r(f());return}r(a)})}):e.current}const l=[];for(const t of m){const r=t.emitter.subscribe(()=>{T.schedule(n.id,null)});l.push(r)}const n=b({destroy(){for(const t of l)t();h(),n.emitter.clear(),e.current=void 0},get:f,getSnapshot:S}),h=T.add(n.id,{onFinish(){const t=s();e.current=c(e,n.emitter.emit,t),g(e,w)&&n.emitter.emit()}});return P(n),n}export{U as select};
|
package/esm/use-value.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useDebugValue as
|
|
1
|
+
import{useDebugValue as n,useSyncExternalStore as r}from"react";import{EMPTY_SELECTOR as o}from"./types";import{isError as s,isPromise as S}from"./utils/is";function p(i,a=o){const{emitter:e}=i,t=r(i.emitter.subscribe,()=>a(e.getSnapshot()),()=>a(e.getInitialSnapshot?e.getInitialSnapshot():e.getSnapshot()));if(n(t),S(t)||s(t))throw t;return t}export{p as useValue};
|
package/package.json
CHANGED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { act, renderHook } from '@testing-library/react-hooks'
|
|
2
|
+
import { create } from '../create'
|
|
3
|
+
import { longPromise } from './test-utils'
|
|
4
|
+
import { waitFor } from '@testing-library/react'
|
|
5
|
+
import { atom, useAtom, useSetAtom } from 'jotai'
|
|
6
|
+
|
|
7
|
+
describe('compare', () => {
|
|
8
|
+
it('should render async value with sync selector', async () => {
|
|
9
|
+
const state = create(longPromise(100))
|
|
10
|
+
const selectedState = state.select((value) => {
|
|
11
|
+
return value + 2
|
|
12
|
+
})
|
|
13
|
+
const listen = jest.fn()
|
|
14
|
+
|
|
15
|
+
const { result } = renderHook(() => {
|
|
16
|
+
listen()
|
|
17
|
+
const value = selectedState()
|
|
18
|
+
return value
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
await waitFor(() => {
|
|
22
|
+
expect(result.current).toBe(2)
|
|
23
|
+
expect(listen).toHaveBeenCalledTimes(2)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
state.set(1)
|
|
27
|
+
|
|
28
|
+
await waitFor(() => {
|
|
29
|
+
expect(result.current).toBe(3)
|
|
30
|
+
// muya re-render only 3 times
|
|
31
|
+
expect(listen).toHaveBeenCalledTimes(3)
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
it('should render async value with sync selector with jotai', async () => {
|
|
35
|
+
const state = atom(longPromise(100))
|
|
36
|
+
|
|
37
|
+
const selectedState = atom(async (get) => {
|
|
38
|
+
const value = get(state)
|
|
39
|
+
return (await value) + 2
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
const listen = jest.fn()
|
|
43
|
+
const { result: setResult } = renderHook(() => {
|
|
44
|
+
const value = useSetAtom(state)
|
|
45
|
+
return value
|
|
46
|
+
})
|
|
47
|
+
const { result } = renderHook(() => {
|
|
48
|
+
listen()
|
|
49
|
+
const value = useAtom(selectedState)
|
|
50
|
+
return value
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
await waitFor(() => {
|
|
54
|
+
expect(result.current[0]).toBe(2)
|
|
55
|
+
expect(listen).toHaveBeenCalledTimes(3)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
act(() => {
|
|
59
|
+
setResult.current(1 as never)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
await waitFor(() => {
|
|
63
|
+
expect(result.current[0]).toBe(3)
|
|
64
|
+
// OH it render 5 times, sad.
|
|
65
|
+
expect(listen).toHaveBeenCalledTimes(5)
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
})
|
|
@@ -87,7 +87,7 @@ describe('select', () => {
|
|
|
87
87
|
const state = create(longPromise(100))
|
|
88
88
|
const selectedState = select([state], async (value) => {
|
|
89
89
|
await longPromise(100)
|
|
90
|
-
return
|
|
90
|
+
return value + 1
|
|
91
91
|
})
|
|
92
92
|
const listener = jest.fn()
|
|
93
93
|
selectedState.listen(listener)
|
|
@@ -100,7 +100,7 @@ describe('select', () => {
|
|
|
100
100
|
const state = create(longPromise(100))
|
|
101
101
|
const selectedState = select([state], async (value) => {
|
|
102
102
|
await longPromise(100)
|
|
103
|
-
return
|
|
103
|
+
return value + 1
|
|
104
104
|
})
|
|
105
105
|
const selectedState2 = selectedState.select(async (value) => value + 1)
|
|
106
106
|
const listener = jest.fn()
|
|
@@ -114,7 +114,7 @@ describe('select', () => {
|
|
|
114
114
|
const state = create(longPromise(100))
|
|
115
115
|
const selectedState = select([state], async (value) => {
|
|
116
116
|
// await longPromise(100)
|
|
117
|
-
return
|
|
117
|
+
return value + 1
|
|
118
118
|
})
|
|
119
119
|
const listener = jest.fn()
|
|
120
120
|
selectedState.listen(listener)
|
|
@@ -205,4 +205,31 @@ describe('select', () => {
|
|
|
205
205
|
expect(render).toHaveBeenCalledTimes(3)
|
|
206
206
|
})
|
|
207
207
|
})
|
|
208
|
+
|
|
209
|
+
it('should get value when initial value is promise', async () => {
|
|
210
|
+
const state = create(longPromise(100))
|
|
211
|
+
const selectedState = state.select((value) => {
|
|
212
|
+
return value + 2
|
|
213
|
+
})
|
|
214
|
+
expect(await selectedState.get()).toBe(2)
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it('should have sync get method sync', async () => {
|
|
218
|
+
const state = create(100)
|
|
219
|
+
const selectedState = state.select((value) => {
|
|
220
|
+
return value + 2
|
|
221
|
+
})
|
|
222
|
+
expect(selectedState.get()).toBe(102)
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('should have async get method sync', async () => {
|
|
226
|
+
const state = create(100)
|
|
227
|
+
const selectedState = state.select((value) => {
|
|
228
|
+
return value + 2
|
|
229
|
+
})
|
|
230
|
+
const selectedState2 = selectedState.select(async (value) => {
|
|
231
|
+
return value + 2
|
|
232
|
+
})
|
|
233
|
+
expect(await selectedState2.get()).toBe(104)
|
|
234
|
+
})
|
|
208
235
|
})
|
package/src/create-state.ts
CHANGED
|
@@ -8,6 +8,7 @@ interface GetStateOptions<T> {
|
|
|
8
8
|
readonly get: () => T
|
|
9
9
|
readonly set?: (value: SetValue<T>) => void
|
|
10
10
|
readonly destroy: () => void
|
|
11
|
+
readonly getSnapshot: () => T
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
let stateId = 0
|
|
@@ -20,7 +21,7 @@ type FullState<T> = GetStateOptions<T>['set'] extends undefined ? GetState<T> :
|
|
|
20
21
|
* This is just utility function to create state base data
|
|
21
22
|
*/
|
|
22
23
|
export function createState<T>(options: GetStateOptions<T>): FullState<T> {
|
|
23
|
-
const { get, destroy, set } = options
|
|
24
|
+
const { get, destroy, set, getSnapshot } = options
|
|
24
25
|
const isSet = !!set
|
|
25
26
|
|
|
26
27
|
const state: FullState<T> = function (selector) {
|
|
@@ -29,7 +30,7 @@ export function createState<T>(options: GetStateOptions<T>): FullState<T> {
|
|
|
29
30
|
}
|
|
30
31
|
state.isSet = isSet as true
|
|
31
32
|
state.id = getStateId()
|
|
32
|
-
state.emitter = createEmitter<T>(
|
|
33
|
+
state.emitter = createEmitter<T>(getSnapshot)
|
|
33
34
|
state.destroy = destroy
|
|
34
35
|
state.listen = function (listener) {
|
|
35
36
|
return this.emitter.subscribe(() => {
|
|
@@ -44,10 +45,10 @@ export function createState<T>(options: GetStateOptions<T>): FullState<T> {
|
|
|
44
45
|
this.stateName = name
|
|
45
46
|
return this
|
|
46
47
|
}
|
|
47
|
-
state.select = function (selector, isSelectorEqual = isEqualBase) {
|
|
48
|
+
state.select = function (selector: never, isSelectorEqual = isEqualBase) {
|
|
48
49
|
return select([state as never], selector, isSelectorEqual)
|
|
49
|
-
}
|
|
50
|
-
state.get = get
|
|
50
|
+
} as never
|
|
51
|
+
state.get = get as never
|
|
51
52
|
state.set = set as State<T>['set']
|
|
52
53
|
|
|
53
54
|
return state
|
package/src/create.ts
CHANGED
package/src/select.ts
CHANGED
|
@@ -9,29 +9,30 @@ type StateDependencies<T extends Array<unknown>> = {
|
|
|
9
9
|
[K in keyof T]: GetState<T[K]>
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
type AwaitedArray<T extends Array<unknown>> = {
|
|
13
|
+
[K in keyof T]: Awaited<T[K]>
|
|
14
|
+
}
|
|
12
15
|
/**
|
|
13
16
|
* Selecting state from multiple states.
|
|
14
17
|
* It will create new state in read-only mode (without set).
|
|
15
18
|
*/
|
|
16
19
|
export function select<T = unknown, S extends Array<unknown> = []>(
|
|
17
20
|
states: StateDependencies<S>,
|
|
18
|
-
selector: (...values: S) => T,
|
|
21
|
+
selector: (...values: AwaitedArray<S>) => T,
|
|
19
22
|
isEqual?: IsEqual<T>,
|
|
20
23
|
): GetState<T> {
|
|
21
24
|
const cache: Cache<T> = {}
|
|
22
25
|
|
|
23
26
|
function computedValue(): T {
|
|
24
|
-
// const values = states.map((state) => state.get()) as S
|
|
25
|
-
|
|
26
|
-
const values: unknown[] = []
|
|
27
27
|
let hasPromise = false
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
if (isPromise(
|
|
28
|
+
const values = states.map((state) => {
|
|
29
|
+
const stateValue = state.get()
|
|
30
|
+
if (isPromise(stateValue)) {
|
|
31
31
|
hasPromise = true
|
|
32
32
|
}
|
|
33
|
-
|
|
34
|
-
}
|
|
33
|
+
return stateValue
|
|
34
|
+
}) as S
|
|
35
|
+
|
|
35
36
|
if (hasPromise) {
|
|
36
37
|
return new Promise((resolve, reject) => {
|
|
37
38
|
Promise.all(values).then((resolvedValues) => {
|
|
@@ -40,20 +41,39 @@ export function select<T = unknown, S extends Array<unknown> = []>(
|
|
|
40
41
|
if (resolvedValues.some((element) => isUndefined(element))) {
|
|
41
42
|
return reject(new AbortError())
|
|
42
43
|
}
|
|
43
|
-
const resolved = selector(...
|
|
44
|
+
const resolved = selector(...resolvedValues)
|
|
44
45
|
resolve(resolved)
|
|
45
46
|
})
|
|
46
47
|
}) as T
|
|
47
48
|
}
|
|
48
|
-
const result = selector(...(values as S))
|
|
49
|
+
const result = selector(...(values as AwaitedArray<S>))
|
|
49
50
|
return result
|
|
50
51
|
}
|
|
51
|
-
|
|
52
|
+
function getSnapshot(): T {
|
|
53
|
+
if (isUndefined(cache.current)) {
|
|
54
|
+
const newValue = computedValue()
|
|
55
|
+
cache.current = handleAsyncUpdate(cache, state.emitter.emit, newValue)
|
|
56
|
+
}
|
|
57
|
+
return cache.current
|
|
58
|
+
}
|
|
52
59
|
function getValue(): T {
|
|
53
60
|
if (isUndefined(cache.current)) {
|
|
54
61
|
const newValue = computedValue()
|
|
55
62
|
cache.current = handleAsyncUpdate(cache, state.emitter.emit, newValue)
|
|
56
63
|
}
|
|
64
|
+
const { current } = cache
|
|
65
|
+
if (isPromise(current)) {
|
|
66
|
+
return new Promise((resolve) => {
|
|
67
|
+
current.then((value: unknown) => {
|
|
68
|
+
if (isUndefined(value)) {
|
|
69
|
+
resolve(getValue())
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
resolve(value)
|
|
74
|
+
})
|
|
75
|
+
}) as T
|
|
76
|
+
}
|
|
57
77
|
return cache.current
|
|
58
78
|
}
|
|
59
79
|
|
|
@@ -75,6 +95,7 @@ export function select<T = unknown, S extends Array<unknown> = []>(
|
|
|
75
95
|
cache.current = undefined
|
|
76
96
|
},
|
|
77
97
|
get: getValue,
|
|
98
|
+
getSnapshot,
|
|
78
99
|
})
|
|
79
100
|
|
|
80
101
|
const clearScheduler = stateScheduler.add(state.id, {
|
package/src/types.ts
CHANGED
|
@@ -13,12 +13,12 @@ export interface Cache<T> {
|
|
|
13
13
|
|
|
14
14
|
export const EMPTY_SELECTOR = <T, S>(stateValue: T) => stateValue as unknown as S
|
|
15
15
|
|
|
16
|
-
export interface GetState<T> {
|
|
17
|
-
<S>(selector?: (stateValue: T) => S): Awaited<undefined extends S ? T : S>
|
|
16
|
+
export interface GetState<T, IsFroMPromise extends boolean = false> {
|
|
17
|
+
<S>(selector?: (stateValue: Awaited<T>) => S): Awaited<undefined extends S ? T : S>
|
|
18
18
|
/**
|
|
19
19
|
* Get the cached state value.
|
|
20
20
|
*/
|
|
21
|
-
get: () => T
|
|
21
|
+
get: () => IsFroMPromise extends true ? Promise<T> : T
|
|
22
22
|
/**
|
|
23
23
|
* Get the unique id of the state.
|
|
24
24
|
*/
|
|
@@ -48,7 +48,7 @@ export interface GetState<T> {
|
|
|
48
48
|
* Select particular slice of the state.
|
|
49
49
|
* It will create "another" state in read-only mode (without set).
|
|
50
50
|
*/
|
|
51
|
-
select: <S>(selector: (state: Awaited<T>) => S, isEqual?: IsEqual<S>) => GetState<S>
|
|
51
|
+
select: <S>(selector: (state: Awaited<T>) => S, isEqual?: IsEqual<S>) => GetState<S, T extends Promise<unknown> ? true : false>
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
export interface State<T> extends GetState<T> {
|
package/src/use-value.ts
CHANGED
|
@@ -4,13 +4,13 @@ import { isError, isPromise } from './utils/is'
|
|
|
4
4
|
|
|
5
5
|
export function useValue<T, S>(
|
|
6
6
|
state: GetState<T>,
|
|
7
|
-
selector: (stateValue: T) => S = EMPTY_SELECTOR,
|
|
7
|
+
selector: (stateValue: Awaited<T>) => S = EMPTY_SELECTOR,
|
|
8
8
|
): Awaited<undefined extends S ? T : S> {
|
|
9
9
|
const { emitter } = state
|
|
10
10
|
const value = useSyncExternalStore<S>(
|
|
11
11
|
state.emitter.subscribe,
|
|
12
|
-
() => selector(emitter.getSnapshot()),
|
|
13
|
-
() => selector(emitter.getInitialSnapshot ? emitter.getInitialSnapshot() : emitter.getSnapshot()),
|
|
12
|
+
() => selector(emitter.getSnapshot() as Awaited<T>),
|
|
13
|
+
() => selector((emitter.getInitialSnapshot ? emitter.getInitialSnapshot() : emitter.getSnapshot()) as Awaited<T>),
|
|
14
14
|
)
|
|
15
15
|
useDebugValue(value)
|
|
16
16
|
if (isPromise(value)) {
|
package/types/create-state.d.ts
CHANGED
package/types/select.d.ts
CHANGED
|
@@ -2,9 +2,12 @@ import type { GetState, IsEqual } from './types';
|
|
|
2
2
|
type StateDependencies<T extends Array<unknown>> = {
|
|
3
3
|
[K in keyof T]: GetState<T[K]>;
|
|
4
4
|
};
|
|
5
|
+
type AwaitedArray<T extends Array<unknown>> = {
|
|
6
|
+
[K in keyof T]: Awaited<T[K]>;
|
|
7
|
+
};
|
|
5
8
|
/**
|
|
6
9
|
* Selecting state from multiple states.
|
|
7
10
|
* It will create new state in read-only mode (without set).
|
|
8
11
|
*/
|
|
9
|
-
export declare function select<T = unknown, S extends Array<unknown> = []>(states: StateDependencies<S>, selector: (...values: S) => T, isEqual?: IsEqual<T>): GetState<T>;
|
|
12
|
+
export declare function select<T = unknown, S extends Array<unknown> = []>(states: StateDependencies<S>, selector: (...values: AwaitedArray<S>) => T, isEqual?: IsEqual<T>): GetState<T>;
|
|
10
13
|
export {};
|
package/types/types.d.ts
CHANGED
|
@@ -10,12 +10,12 @@ export interface Cache<T> {
|
|
|
10
10
|
abortController?: AbortController;
|
|
11
11
|
}
|
|
12
12
|
export declare const EMPTY_SELECTOR: <T, S>(stateValue: T) => S;
|
|
13
|
-
export interface GetState<T> {
|
|
14
|
-
<S>(selector?: (stateValue: T) => S): Awaited<undefined extends S ? T : S>;
|
|
13
|
+
export interface GetState<T, IsFroMPromise extends boolean = false> {
|
|
14
|
+
<S>(selector?: (stateValue: Awaited<T>) => S): Awaited<undefined extends S ? T : S>;
|
|
15
15
|
/**
|
|
16
16
|
* Get the cached state value.
|
|
17
17
|
*/
|
|
18
|
-
get: () => T;
|
|
18
|
+
get: () => IsFroMPromise extends true ? Promise<T> : T;
|
|
19
19
|
/**
|
|
20
20
|
* Get the unique id of the state.
|
|
21
21
|
*/
|
|
@@ -45,7 +45,7 @@ export interface GetState<T> {
|
|
|
45
45
|
* Select particular slice of the state.
|
|
46
46
|
* It will create "another" state in read-only mode (without set).
|
|
47
47
|
*/
|
|
48
|
-
select: <S>(selector: (state: Awaited<T>) => S, isEqual?: IsEqual<S>) => GetState<S>;
|
|
48
|
+
select: <S>(selector: (state: Awaited<T>) => S, isEqual?: IsEqual<S>) => GetState<S, T extends Promise<unknown> ? true : false>;
|
|
49
49
|
}
|
|
50
50
|
export interface State<T> extends GetState<T> {
|
|
51
51
|
/**
|
package/types/use-value.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { type GetState } from './types';
|
|
2
|
-
export declare function useValue<T, S>(state: GetState<T>, selector?: (stateValue: T) => S): Awaited<undefined extends S ? T : S>;
|
|
2
|
+
export declare function useValue<T, S>(state: GetState<T>, selector?: (stateValue: Awaited<T>) => S): Awaited<undefined extends S ? T : S>;
|