muya 1.0.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/LICENSE +0 -0
- package/README.md +236 -0
- package/cjs/index.js +1 -0
- package/esm/common.js +1 -0
- package/esm/create-base-state.js +1 -0
- package/esm/create-emitter.js +1 -0
- package/esm/create-getter-state.js +1 -0
- package/esm/create.js +1 -0
- package/esm/index.js +1 -0
- package/esm/is.js +1 -0
- package/esm/merge.js +1 -0
- package/esm/select.js +1 -0
- package/esm/shallow.js +1 -0
- package/esm/types.js +1 -0
- package/esm/use-state-value.js +1 -0
- package/package.json +37 -0
- package/src/common.ts +28 -0
- package/src/create-base-state.ts +35 -0
- package/src/create-emitter.ts +24 -0
- package/src/create-getter-state.ts +19 -0
- package/src/create.ts +102 -0
- package/src/index.ts +6 -0
- package/src/is.ts +36 -0
- package/src/merge.ts +41 -0
- package/src/select.ts +33 -0
- package/src/shallow.ts +58 -0
- package/src/state.test.tsx +647 -0
- package/src/types.ts +94 -0
- package/src/use-state-value.ts +29 -0
- package/types/common.d.ts +7 -0
- package/types/create-base-state.d.ts +10 -0
- package/types/create-emitter.d.ts +7 -0
- package/types/create-getter-state.d.ts +6 -0
- package/types/create.d.ts +21 -0
- package/types/index.d.ts +6 -0
- package/types/is.d.ts +10 -0
- package/types/merge.d.ts +2 -0
- package/types/select.d.ts +2 -0
- package/types/shallow.d.ts +1 -0
- package/types/types.d.ts +68 -0
- package/types/use-state-value.d.ts +10 -0
package/LICENSE
ADDED
|
File without changes
|
package/README.md
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
|
|
2
|
+
# Muya 🌀
|
|
3
|
+
Welcome to Muya - Making state management a breeze, focused on simplicity and scalability for real-world scenarios.
|
|
4
|
+
|
|
5
|
+
[](https://github.com/samuelgja/muya/actions/workflows/build.yml)
|
|
6
|
+
[](https://github.com/samuelgja/muya/actions/workflows/code-check.yml)
|
|
7
|
+
[](https://bundlephobia.com/result?p=muya)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## 🚀 Features
|
|
12
|
+
- Easy State Creation: Kickstart your state management with simple and intuitive APIs.
|
|
13
|
+
- Selectors & Merges: Grab exactly what you need from your state and combine multiple states seamlessly.
|
|
14
|
+
- Deep Nesting Support: Handle complex state structures without breaking a sweat.
|
|
15
|
+
- Optimized Rendering: Prevent unnecessary re-renders
|
|
16
|
+
- TypeScript Ready: Fully typed for maximum developer sanity.
|
|
17
|
+
- Small Bundle Size: Lightweight and fast, no bloatware here.
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
## 📦 Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
bun add muya
|
|
24
|
+
```
|
|
25
|
+
or
|
|
26
|
+
```bash
|
|
27
|
+
yarn add muya
|
|
28
|
+
```
|
|
29
|
+
or
|
|
30
|
+
```bash
|
|
31
|
+
npm install muya
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 📝 Quick Start
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { create } from 'muya'
|
|
38
|
+
|
|
39
|
+
const useCounter = create(0)
|
|
40
|
+
|
|
41
|
+
function App() {
|
|
42
|
+
const counter = useCounter()
|
|
43
|
+
return <div onClick={() => useCounter.setState((prev) => prev + 1)}>{counter}</div>
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Update
|
|
48
|
+
Sugar syntax above the `setState` method for partially updating the state.
|
|
49
|
+
```typescript
|
|
50
|
+
import { create } from 'muya'
|
|
51
|
+
|
|
52
|
+
const useUser = create({ name: 'John', lastName: 'Doe' })
|
|
53
|
+
|
|
54
|
+
function App() {
|
|
55
|
+
const user = useUser()
|
|
56
|
+
// this will just partially update only the name field, it's sugar syntax for setState.
|
|
57
|
+
return <div onClick={() => useUser.updateState({ name: 'Nope' })}>{user.name}</div>
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
### Selecting parts of the state globally
|
|
63
|
+
```tsx
|
|
64
|
+
import { create } from 'muya'
|
|
65
|
+
|
|
66
|
+
const useUser = create({ name: 'John', age: 30 })
|
|
67
|
+
|
|
68
|
+
// Selecting only the name part of the state
|
|
69
|
+
const useName = useUser.select((user) => user.name)
|
|
70
|
+
|
|
71
|
+
function App() {
|
|
72
|
+
const name = useName()
|
|
73
|
+
return <div onClick={() => useUser.setState((prev) => ({ ...prev, name: 'Jane' }))}>{name}</div>
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Merge two states
|
|
79
|
+
```typescript
|
|
80
|
+
import { create, shallow } from 'muya'
|
|
81
|
+
|
|
82
|
+
const useName = create(() => 'John')
|
|
83
|
+
const useAge = create(() => 30)
|
|
84
|
+
|
|
85
|
+
const useFullName = useName.merge(useAge, (name, age) => ` ${name} and ${age}`, shallow)
|
|
86
|
+
|
|
87
|
+
function App() {
|
|
88
|
+
const fullName = useFullName()
|
|
89
|
+
return <div onClick={() => useName.setState((prev) => 'Jane')}>{fullName}</div>
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
### Promise based state and lifecycle management working with React Suspense
|
|
95
|
+
This methods are useful for handling async data fetching and lazy loading via React Suspense.
|
|
96
|
+
|
|
97
|
+
#### Immediate Promise resolution
|
|
98
|
+
```typescript
|
|
99
|
+
import { create } from 'muya';
|
|
100
|
+
// state will try to resolve the promise immediately, can hit the suspense boundary
|
|
101
|
+
const counterState = create(Promise.resolve(0));
|
|
102
|
+
|
|
103
|
+
function Counter() {
|
|
104
|
+
const counter = counterState();
|
|
105
|
+
return (
|
|
106
|
+
<div onClick={() => counterState.setState((prev) => prev + 1)}>
|
|
107
|
+
{counter}
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
#### Lazy Promise resolution
|
|
114
|
+
```typescript
|
|
115
|
+
import { create } from 'muya';
|
|
116
|
+
// state will lazy resolve the promise on first access, this will hit the suspense boundary if the first access is from component and via `counterState.getState()` method
|
|
117
|
+
const counterState = create(() => Promise.resolve(0));
|
|
118
|
+
|
|
119
|
+
function Counter() {
|
|
120
|
+
const counter = counterState();
|
|
121
|
+
return (
|
|
122
|
+
<div onClick={() => counterState.setState((prev) => prev + 1)}>
|
|
123
|
+
{counter}
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
## 🔍 API Reference
|
|
131
|
+
|
|
132
|
+
### `create`
|
|
133
|
+
|
|
134
|
+
Creates a basic atom state.
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
function create<T>(defaultState: T, options?: StateOptions<T>): StateSetter<T>;
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Example:**
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
const userState = create({ name: 'John', age: 30 });
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### `select`
|
|
147
|
+
|
|
148
|
+
Selects a slice of an existing state directly or via a selector function.
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
// userState is ready to use as hook, so you can name it with `use` prefix
|
|
152
|
+
const userState = create({ name: 'John', age: 30 });
|
|
153
|
+
// Direct selection outside the component, is useful for accessing the slices of the state in multiple components
|
|
154
|
+
const userAgeState = userState.select((user) => user.age);
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### `merge`
|
|
158
|
+
Merges two states into a single state.
|
|
159
|
+
```typescript
|
|
160
|
+
const useName = create(() => 'John');
|
|
161
|
+
const useAge = create(() => 30);
|
|
162
|
+
const useFullName = useName.merge(useAge, (name, age) => ` ${name} and ${age}`);
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
### `setState`
|
|
167
|
+
Sets the state to a new value or a function that returns a new value.
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
const userState = create({ name: 'John', age: 30 });
|
|
171
|
+
userState.setState({ name: 'Jane' });
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### `updateState`
|
|
175
|
+
Partially updates the state with a new value.
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
const userState = create({ name: 'John', age: 30 });
|
|
179
|
+
userState.updateState({ name: 'Jane' });
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### `getState`
|
|
183
|
+
Returns the current state value outside the component.
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
const userState = create({ name: 'John', age: 30 });
|
|
187
|
+
const user = userState.getState();
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### `use`
|
|
191
|
+
Creates a hook for the state.
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
const useCounter = create(0);
|
|
195
|
+
// use inside the component
|
|
196
|
+
const counter = useCounter();
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### `subscribe`
|
|
200
|
+
Subscribes to the state changes.
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
const userState = create({ name: 'John', age: 30 });
|
|
204
|
+
const unsubscribe = userState.subscribe((state) => console.log(state));
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
### Access from outside the component
|
|
210
|
+
:warning: Avoid using this method for state management in [React Server Components](https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md), especially in Next.js 13+. It may cause unexpected behavior or privacy concerns.
|
|
211
|
+
```typescript
|
|
212
|
+
const userState = create({ name: 'John', age: 30 });
|
|
213
|
+
const user = userState.getState();
|
|
214
|
+
```
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
### Slicing new references
|
|
219
|
+
:warning: Slicing data with new references can lead to maximum call stack exceeded error.
|
|
220
|
+
It's recommended to not use new references for the state slices, if you need so, use `shallow` or other custom equality checks.
|
|
221
|
+
```typescript
|
|
222
|
+
import { state, shallow } from 'muya';
|
|
223
|
+
const userState = create({ name: 'John', age: 30 });
|
|
224
|
+
// this slice will create new reference object on each call
|
|
225
|
+
const useName = userState.select((user) => ({newUser: user.name }), shallow);
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## 🤖 Contributing
|
|
229
|
+
Contributions are welcome! Please read the [contributing guidelines](CONTRIBUTING.md) before submitting a pull request.
|
|
230
|
+
|
|
231
|
+
## 🧪 Testing
|
|
232
|
+
Muya comes with a robust testing suite. Check out the state.test.tsx for examples on how to write your own tests.
|
|
233
|
+
|
|
234
|
+
## 📜 License
|
|
235
|
+
|
|
236
|
+
Muya is [MIT licensed](LICENSE).
|
package/cjs/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var G=Object.defineProperty;var z=Object.getOwnPropertyDescriptor;var F=Object.getOwnPropertyNames;var M=Object.prototype.hasOwnProperty;var C=(t,e)=>{for(var r in e)G(t,r,{get:e[r],enumerable:!0})},L=(t,e,r,a)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of F(e))!M.call(t,n)&&n!==r&&G(t,n,{get:()=>e[n],enumerable:!(a=z(e,n))||a.enumerable});return t};var W=t=>L(G({},"__esModule",{value:!0}),t);var H={};C(H,{StateKeys:()=>_,create:()=>D,getDefaultValue:()=>y,merge:()=>b,select:()=>g,shallow:()=>v,useStateValue:()=>x});module.exports=W(H);function p(t){return t instanceof Promise}function q(t){return typeof t=="function"}function I(t){return typeof t=="function"}function R(t){return typeof t=="object"&&t!==null}function w(t){return t instanceof Map}function k(t){return t instanceof Set}function V(t){return Array.isArray(t)}function c(t,e){return t===e?!0:!!Object.is(t,e)}var _=(r=>(r.IS_STATE="isState",r.IS_SLICE="isSlice",r))(_||{});function y(t){return p(t)?t:q(t)?t():t}function T(t){let e=new Set;return{subscribe:r=>(e.add(r),()=>{e.delete(r)}),emit:(...r)=>{for(let a of e)a(...r)},getSnapshot:t}}var P=require("use-sync-external-store/shim/with-selector"),h=require("react");function O(t){return t}function j(t,e,r){let a=(0,P.useSyncExternalStoreWithSelector)(t.subscribe,t.getSnapshot,t.getSnapshot,e?n=>e(n):O,r);return(0,h.useDebugValue)(a),a}function x(t,e=a=>a,r){let a=j(t.__internal.emitter,n=>e(n),r);if(p(a))throw a;return a}function m(t){let{baseState:e}=t,r=(a,n)=>x(r,a,n);return r.__internal=e.__internal,r.getState=e.getState,r.reset=e.reset,r.select=e.select,r.merge=e.merge,r.subscribe=e.subscribe,r}function b(t,e,r,a=c){let n,o=T(()=>{let f=r(t.getState(),e.getState());return n!==void 0&&a(n,f)?n:(n=f,f)});t.__internal.emitter.subscribe(()=>{o.emit()}),e.__internal.emitter.subscribe(()=>{o.emit()});let s=l({emitter:o,getGetterState:()=>S,getState:()=>r(t.getState(),e.getState()),reset(){t.reset(),e.reset()}}),S=m({baseState:s});return S}function g(t,e,r=c){let a,n=T(()=>{let S=e(t.getState());return a!==void 0&&r(a,S)?a:(a=S,S)});t.__internal.emitter.subscribe(()=>{n.emit()});let o=l({emitter:n,getGetterState:()=>s,getState:()=>e(t.getState()),reset:t.reset}),s=m({baseState:o});return s}function l(t){let{emitter:e,getGetterState:r,reset:a,getState:n}=t;return{getState:n,reset:a,select(o,s){let S=r();return g(S,o,s)},merge(o,s,S){let f=r();return b(f,o,s,S)},__internal:{emitter:e},subscribe(o){return o(n()),e.subscribe(()=>{o(n())})}}}function D(t,e=c){function r(i,u){return I(u)?u(i):u}let a={updateVersion:0,value:void 0};function n(){return a.value===void 0&&(a.value=y(t)),a.value}function o(){let i=n();return p(i)&&i.then(u=>{a.value=u,f.emit()}),i}function s(i){let u=n(),E=r(u,i);e?.(u,E)||E===u||(a.updateVersion++,a.value=E,f.emit())}function S(i){if(R(i))return s(u=>({...u,...i}));s(i)}let f=T(o),U=l({emitter:f,getGetterState:()=>d,getState:o,reset(){let i=y(t);if(p(i)){i.then(u=>{s(u)});return}s(i)}}),d=m({baseState:U});return d.setState=s,d.updateState=S,d}function v(t,e){if(t==e||Object.is(t,e))return!0;if(typeof t!="object"||t==null||typeof e!="object"||e==null)return!1;if(w(t)&&w(e)){if(t.size!==e.size)return!1;for(let[n,o]of t)if(!Object.is(o,e.get(n)))return!1;return!0}if(k(t)&&k(e)){if(t.size!==e.size)return!1;for(let n of t)if(!e.has(n))return!1;return!0}if(V(t)&&V(e)){if(t.length!==e.length)return!1;for(let[n,o]of t.entries())if(!Object.is(o,e[n]))return!1;return!0}let r=Object.keys(t),a=Object.keys(e);if(r.length!==a.length)return!1;for(let n of r)if(!Object.prototype.hasOwnProperty.call(e,n)||!Object.is(t[n],e[n]))return!1;return!0}
|
package/esm/common.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{useSyncExternalStoreWithSelector as u}from"use-sync-external-store/shim/with-selector";import{useDebugValue as s}from"react";function S(e){return e}function i(e,t,r){const n=u(e.subscribe,e.getSnapshot,e.getSnapshot,t?o=>t(o):S,r);return s(n),n}export{S as toType,i as useSyncExternalStore};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{merge as c}from"./merge";import{select as p}from"./select";function y(n){const{emitter:o,getGetterState:s,reset:i,getState:e}=n;return{getState:e,reset:i,select(t,r){const a=s();return p(a,t,r)},merge(t,r,a){const m=s();return c(m,t,r,a)},__internal:{emitter:o},subscribe(t){return t(e()),o.subscribe(()=>{t(e())})}}}export{y as createBaseState};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function n(r){const t=new Set;return{subscribe:e=>(t.add(e),()=>{t.delete(e)}),emit:(...e)=>{for(const i of t)i(...e)},getSnapshot:r}}export{n as createEmitter};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{useStateValue as n}from"./use-state-value";function i(r){const{baseState:t}=r,e=(a,s)=>n(e,a,s);return e.__internal=t.__internal,e.getState=t.getState,e.reset=t.reset,e.select=t.select,e.merge=t.merge,e.subscribe=t.subscribe,e}export{i as createGetterState};
|
package/esm/create.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createEmitter as d}from"./create-emitter";import{getDefaultValue as S}from"./types";import{isEqualBase as V,isObject as v,isPromise as c,isSetValueFunction as g}from"./is";import{createBaseState as D}from"./create-base-state";import{createGetterState as E}from"./create-getter-state";function y(s,f=V){function T(t,e){return g(e)?e(t):e}const a={updateVersion:0,value:void 0};function o(){return a.value===void 0&&(a.value=S(s)),a.value}function l(){const t=o();return c(t)&&t.then(e=>{a.value=e,n.emit()}),t}function r(t){const e=o(),i=T(e,t);f?.(e,i)||i===e||(a.updateVersion++,a.value=i,n.emit())}function m(t){if(v(t))return r(e=>({...e,...t}));r(t)}const n=d(l),p=D({emitter:n,getGetterState:()=>u,getState:l,reset(){const t=S(s);if(c(t)){t.then(e=>{r(e)});return}r(t)}}),u=E({baseState:p});return u.setState=r,u.updateState=m,u}export{y as create};
|
package/esm/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export*from"./types";import{create as t}from"./create";import{select as f}from"./select";import{merge as x}from"./merge";import{useStateValue as l}from"./use-state-value";import{shallow as c}from"./shallow";export{t as create,x as merge,f as select,c as shallow,l as useStateValue};
|
package/esm/is.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function o(n){return n instanceof Promise}function r(n){return typeof n=="function"}function u(n){return typeof n=="function"}function t(n){return typeof n=="object"&&n!==null}function i(n){return t(n)&&n.isRef===!0}function s(n){return n instanceof Map}function f(n){return n instanceof Set}function a(n){return Array.isArray(n)}function c(n,e){return n===e?!0:!!Object.is(n,e)}export{a as isArray,c as isEqualBase,r as isFunction,s as isMap,t as isObject,o as isPromise,i as isRef,f as isSet,u as isSetValueFunction};
|
package/esm/merge.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createBaseState as u}from"./create-base-state";import{createEmitter as s}from"./create-emitter";import{createGetterState as c}from"./create-getter-state";import{isEqualBase as f}from"./is";function T(t,e,i,n=f){let r;const a=s(()=>{const S=i(t.getState(),e.getState());return r!==void 0&&n(r,S)?r:(r=S,S)});t.__internal.emitter.subscribe(()=>{a.emit()}),e.__internal.emitter.subscribe(()=>{a.emit()});const o=u({emitter:a,getGetterState:()=>m,getState:()=>i(t.getState(),e.getState()),reset(){t.reset(),e.reset()}}),m=c({baseState:o});return m}export{T as merge};
|
package/esm/select.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createBaseState as m}from"./create-base-state";import{createEmitter as s}from"./create-emitter";import{createGetterState as u}from"./create-getter-state";import{isEqualBase as c}from"./is";function d(t,a,o=c){let e;const S=s(()=>{const r=a(t.getState());return e!==void 0&&o(e,r)?e:(e=r,r)});t.__internal.emitter.subscribe(()=>{S.emit()});const n=m({emitter:S,getGetterState:()=>i,getState:()=>a(t.getState()),reset:t.reset}),i=u({baseState:n});return i}export{d as select};
|
package/esm/shallow.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{isArray as o,isMap as f,isSet as i}from"./is";function b(t,r){if(t==r||Object.is(t,r))return!0;if(typeof t!="object"||t==null||typeof r!="object"||r==null)return!1;if(f(t)&&f(r)){if(t.size!==r.size)return!1;for(const[n,e]of t)if(!Object.is(e,r.get(n)))return!1;return!0}if(i(t)&&i(r)){if(t.size!==r.size)return!1;for(const n of t)if(!r.has(n))return!1;return!0}if(o(t)&&o(r)){if(t.length!==r.length)return!1;for(const[n,e]of t.entries())if(!Object.is(e,r[n]))return!1;return!0}const s=Object.keys(t),c=Object.keys(r);if(s.length!==c.length)return!1;for(const n of s)if(!Object.prototype.hasOwnProperty.call(r,n)||!Object.is(t[n],r[n]))return!1;return!0}export{b as shallow};
|
package/esm/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{isFunction as a,isPromise as r}from"./is";var T=(t=>(t.IS_STATE="isState",t.IS_SLICE="isSlice",t))(T||{});function o(e){return r(e)?e:a(e)?e():e}export{T as StateKeys,o as getDefaultValue};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{useSyncExternalStore as n,toType as S}from"./common";import{isPromise as i}from"./is";function m(e,r=t=>S(t),o){const t=n(e.__internal.emitter,a=>r(a),o);if(i(t))throw t;return t}export{m as useStateValue};
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "muya",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"author": "samuel.gjabel@gmail.com",
|
|
5
|
+
"description": "👀 Another React state management library",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "cjs/index.js",
|
|
8
|
+
"module": "esm/index.js",
|
|
9
|
+
"react-native": "src/index.ts",
|
|
10
|
+
"types": "types/index.d.ts",
|
|
11
|
+
"homepage": "https://github.com/samuelgjabel/muya",
|
|
12
|
+
"repository": "https://github.com/samuelgjabel/muya",
|
|
13
|
+
"sideEffects": false,
|
|
14
|
+
"keywords": [
|
|
15
|
+
"react",
|
|
16
|
+
"react-hooks",
|
|
17
|
+
"react-native",
|
|
18
|
+
"state",
|
|
19
|
+
"management",
|
|
20
|
+
"library",
|
|
21
|
+
"muya",
|
|
22
|
+
"redux",
|
|
23
|
+
"zustand"
|
|
24
|
+
],
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"use-sync-external-store": ">=1.2.0",
|
|
27
|
+
"react": ">=18.0.0"
|
|
28
|
+
},
|
|
29
|
+
"peerDependenciesMeta": {
|
|
30
|
+
"react": {
|
|
31
|
+
"optional": true
|
|
32
|
+
},
|
|
33
|
+
"use-sync-external-store": {
|
|
34
|
+
"optional": true
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/common.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useSyncExternalStoreWithSelector as useSync } from 'use-sync-external-store/shim/with-selector'
|
|
2
|
+
import type { Emitter } from './create-emitter'
|
|
3
|
+
import type { IsEqual } from './types'
|
|
4
|
+
import { useDebugValue } from 'react'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Todo need to remove this
|
|
8
|
+
*/
|
|
9
|
+
export function toType<T>(object?: unknown): T {
|
|
10
|
+
return object as T
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function useSyncExternalStore<T, S>(
|
|
14
|
+
emitter: Emitter<T>,
|
|
15
|
+
selector: (stateValue: T) => S,
|
|
16
|
+
isEqual?: IsEqual<S>,
|
|
17
|
+
): undefined extends S ? T : S {
|
|
18
|
+
const value = useSync<T, S>(
|
|
19
|
+
emitter.subscribe,
|
|
20
|
+
emitter.getSnapshot,
|
|
21
|
+
emitter.getSnapshot,
|
|
22
|
+
selector ? (stateValue) => selector(stateValue) : toType,
|
|
23
|
+
isEqual,
|
|
24
|
+
) as undefined extends S ? T : S
|
|
25
|
+
|
|
26
|
+
useDebugValue(value)
|
|
27
|
+
return value
|
|
28
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Emitter } from './create-emitter'
|
|
2
|
+
import { merge } from './merge'
|
|
3
|
+
import { select } from './select'
|
|
4
|
+
import type { BaseState, GetterState } from './types'
|
|
5
|
+
|
|
6
|
+
interface Options<T> {
|
|
7
|
+
readonly emitter: Emitter<T>
|
|
8
|
+
readonly reset: () => void
|
|
9
|
+
readonly getState: () => T
|
|
10
|
+
readonly getGetterState: () => GetterState<T>
|
|
11
|
+
}
|
|
12
|
+
export function createBaseState<T>(options: Options<T>): BaseState<T> {
|
|
13
|
+
const { emitter, getGetterState, reset, getState } = options
|
|
14
|
+
return {
|
|
15
|
+
getState,
|
|
16
|
+
reset,
|
|
17
|
+
select(selector, isSame) {
|
|
18
|
+
const state = getGetterState()
|
|
19
|
+
return select(state, selector, isSame)
|
|
20
|
+
},
|
|
21
|
+
merge(state2, selector, isEqualHook) {
|
|
22
|
+
const state = getGetterState()
|
|
23
|
+
return merge(state, state2, selector, isEqualHook)
|
|
24
|
+
},
|
|
25
|
+
__internal: {
|
|
26
|
+
emitter,
|
|
27
|
+
},
|
|
28
|
+
subscribe(listener) {
|
|
29
|
+
listener(getState())
|
|
30
|
+
return emitter.subscribe(() => {
|
|
31
|
+
listener(getState())
|
|
32
|
+
})
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type EmitterSubscribe<P = undefined> = (listener: (...params: P[]) => void) => () => void
|
|
2
|
+
export interface Emitter<T, R = T, P = undefined> {
|
|
3
|
+
subscribe: EmitterSubscribe<P>
|
|
4
|
+
getSnapshot: () => R
|
|
5
|
+
emit: (...params: P[]) => void
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function createEmitter<T, R = T, P = undefined>(getSnapshot: () => R): Emitter<T, R, P> {
|
|
9
|
+
const listeners = new Set<(...params: P[]) => void>()
|
|
10
|
+
return {
|
|
11
|
+
subscribe: (listener) => {
|
|
12
|
+
listeners.add(listener)
|
|
13
|
+
return () => {
|
|
14
|
+
listeners.delete(listener)
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
emit: (...params) => {
|
|
18
|
+
for (const listener of listeners) {
|
|
19
|
+
listener(...params)
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
getSnapshot,
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { BaseState, GetterState } from './types'
|
|
2
|
+
import { useStateValue } from './use-state-value'
|
|
3
|
+
|
|
4
|
+
interface Options<T> {
|
|
5
|
+
readonly baseState: BaseState<T>
|
|
6
|
+
}
|
|
7
|
+
export function createGetterState<T>(options: Options<T>): GetterState<T> {
|
|
8
|
+
const { baseState } = options
|
|
9
|
+
const useSliceState: GetterState<T> = (useSelector, isEqualHook) => {
|
|
10
|
+
return useStateValue(useSliceState, useSelector, isEqualHook)
|
|
11
|
+
}
|
|
12
|
+
useSliceState.__internal = baseState.__internal
|
|
13
|
+
useSliceState.getState = baseState.getState
|
|
14
|
+
useSliceState.reset = baseState.reset
|
|
15
|
+
useSliceState.select = baseState.select
|
|
16
|
+
useSliceState.merge = baseState.merge
|
|
17
|
+
useSliceState.subscribe = baseState.subscribe
|
|
18
|
+
return useSliceState
|
|
19
|
+
}
|
package/src/create.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { createEmitter } from './create-emitter'
|
|
2
|
+
import type { SetValue, SetterState, StateDataInternal, DefaultValue, GetterState, IsEqual, UpdateValue } from './types'
|
|
3
|
+
import { getDefaultValue } from './types'
|
|
4
|
+
import { isEqualBase, isObject, isPromise, isSetValueFunction } from './is'
|
|
5
|
+
import { createBaseState } from './create-base-state'
|
|
6
|
+
import { createGetterState } from './create-getter-state'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Creates a basic atom state.
|
|
10
|
+
* @param defaultValue - The initial state value.
|
|
11
|
+
* @param options - Optional settings for the state (e.g., isEqual, onSet).
|
|
12
|
+
* @returns A state object that can be used as a hook and provides state management methods.
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* // Global scope
|
|
16
|
+
* const counterState = state(0);
|
|
17
|
+
* const userState = state({ name: 'John', age: 20 });
|
|
18
|
+
*
|
|
19
|
+
* // React component
|
|
20
|
+
* const counter = counterState(); // Use as a hook
|
|
21
|
+
* const user = userState();
|
|
22
|
+
*
|
|
23
|
+
* // Access partial data from the state using slice
|
|
24
|
+
* const userAge = userState.slice((state) => state.age)();
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
export function create<T>(defaultValue: DefaultValue<T>, isEqual: IsEqual<T> = isEqualBase): SetterState<Awaited<T>> {
|
|
29
|
+
function resolveSetter(value: T, stateSetter: SetValue<T>): T {
|
|
30
|
+
if (isSetValueFunction(stateSetter)) {
|
|
31
|
+
return stateSetter(value)
|
|
32
|
+
}
|
|
33
|
+
return stateSetter
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const stateData: StateDataInternal<T> = {
|
|
37
|
+
updateVersion: 0,
|
|
38
|
+
value: undefined,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getValue(): T {
|
|
42
|
+
if (stateData.value === undefined) {
|
|
43
|
+
stateData.value = getDefaultValue(defaultValue)
|
|
44
|
+
}
|
|
45
|
+
return stateData.value
|
|
46
|
+
}
|
|
47
|
+
function get(): T {
|
|
48
|
+
const stateValue = getValue()
|
|
49
|
+
if (isPromise(stateValue)) {
|
|
50
|
+
stateValue.then((data) => {
|
|
51
|
+
stateData.value = data as Awaited<T>
|
|
52
|
+
emitter.emit()
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
return stateValue
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function set(stateValue: SetValue<T>) {
|
|
59
|
+
const stateValueData = getValue()
|
|
60
|
+
const newState = resolveSetter(stateValueData, stateValue)
|
|
61
|
+
const isEqualResult = isEqual?.(stateValueData, newState)
|
|
62
|
+
if (isEqualResult || newState === stateValueData) {
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
stateData.updateVersion++
|
|
66
|
+
stateData.value = newState
|
|
67
|
+
emitter.emit()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function update(stateValue: UpdateValue<T>) {
|
|
71
|
+
if (isObject(stateValue)) {
|
|
72
|
+
return set((previousState) => {
|
|
73
|
+
return { ...previousState, ...stateValue }
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
set(stateValue as T)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const emitter = createEmitter<T>(get)
|
|
80
|
+
|
|
81
|
+
const baseState = createBaseState({
|
|
82
|
+
emitter,
|
|
83
|
+
getGetterState: () => setterState,
|
|
84
|
+
getState: get,
|
|
85
|
+
reset() {
|
|
86
|
+
const value = getDefaultValue(defaultValue)
|
|
87
|
+
if (isPromise(value)) {
|
|
88
|
+
value.then((data) => {
|
|
89
|
+
set(data as T)
|
|
90
|
+
})
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
set(value)
|
|
94
|
+
},
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
const getterState: GetterState<T> = createGetterState<T>({ baseState })
|
|
98
|
+
const setterState: SetterState<T> = getterState as SetterState<T>
|
|
99
|
+
setterState.setState = set
|
|
100
|
+
setterState.updateState = update
|
|
101
|
+
return setterState as SetterState<Awaited<T>>
|
|
102
|
+
}
|
package/src/index.ts
ADDED
package/src/is.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Ref, Setter, SetValue } from './types'
|
|
2
|
+
|
|
3
|
+
export function isPromise(value: unknown): value is Promise<unknown> {
|
|
4
|
+
return value instanceof Promise
|
|
5
|
+
}
|
|
6
|
+
export function isFunction(value: unknown): value is (...args: unknown[]) => unknown {
|
|
7
|
+
return typeof value === 'function'
|
|
8
|
+
}
|
|
9
|
+
export function isSetValueFunction<T>(value: SetValue<T>): value is Setter<T> {
|
|
10
|
+
return typeof value === 'function'
|
|
11
|
+
}
|
|
12
|
+
export function isObject(value: unknown): value is Record<string, unknown> {
|
|
13
|
+
return typeof value === 'object' && value !== null
|
|
14
|
+
}
|
|
15
|
+
export function isRef<T>(value: unknown): value is Ref<T> {
|
|
16
|
+
return isObject(value) && value.isRef === true
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function isMap(value: unknown): value is Map<unknown, unknown> {
|
|
20
|
+
return value instanceof Map
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function isSet(value: unknown): value is Set<unknown> {
|
|
24
|
+
return value instanceof Set
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function isArray(value: unknown): value is Array<unknown> {
|
|
28
|
+
return Array.isArray(value)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function isEqualBase<T>(valueA: T, valueB: T): boolean {
|
|
32
|
+
if (valueA === valueB) {
|
|
33
|
+
return true
|
|
34
|
+
}
|
|
35
|
+
return !!Object.is(valueA, valueB)
|
|
36
|
+
}
|
package/src/merge.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createBaseState } from './create-base-state'
|
|
2
|
+
import { createEmitter } from './create-emitter'
|
|
3
|
+
import { createGetterState } from './create-getter-state'
|
|
4
|
+
import { isEqualBase } from './is'
|
|
5
|
+
import type { IsEqual, GetterState } from './types'
|
|
6
|
+
|
|
7
|
+
export function merge<T1, T2, S>(
|
|
8
|
+
state1: GetterState<T1>,
|
|
9
|
+
state2: GetterState<T2>,
|
|
10
|
+
selector: (value1: T1, value2: T2) => S,
|
|
11
|
+
isEqual: IsEqual<S> = isEqualBase,
|
|
12
|
+
): GetterState<S> {
|
|
13
|
+
let previousData: S | undefined
|
|
14
|
+
const emitter = createEmitter(() => {
|
|
15
|
+
const data = selector(state1.getState(), state2.getState())
|
|
16
|
+
if (previousData !== undefined && isEqual(previousData, data)) {
|
|
17
|
+
return previousData
|
|
18
|
+
}
|
|
19
|
+
previousData = data
|
|
20
|
+
return data
|
|
21
|
+
})
|
|
22
|
+
state1.__internal.emitter.subscribe(() => {
|
|
23
|
+
emitter.emit()
|
|
24
|
+
})
|
|
25
|
+
state2.__internal.emitter.subscribe(() => {
|
|
26
|
+
emitter.emit()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const baseState = createBaseState<S>({
|
|
30
|
+
emitter,
|
|
31
|
+
getGetterState: () => getterState,
|
|
32
|
+
getState: () => selector(state1.getState(), state2.getState()),
|
|
33
|
+
reset() {
|
|
34
|
+
state1.reset()
|
|
35
|
+
state2.reset()
|
|
36
|
+
},
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const getterState: GetterState<S> = createGetterState<S>({ baseState })
|
|
40
|
+
return getterState
|
|
41
|
+
}
|
package/src/select.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { createBaseState } from './create-base-state'
|
|
2
|
+
import { createEmitter } from './create-emitter'
|
|
3
|
+
import { createGetterState } from './create-getter-state'
|
|
4
|
+
import { isEqualBase } from './is'
|
|
5
|
+
import type { IsEqual, GetterState } from './types'
|
|
6
|
+
|
|
7
|
+
export function select<T, S>(
|
|
8
|
+
state: GetterState<T>,
|
|
9
|
+
selector: (value: T) => S,
|
|
10
|
+
isEqual: IsEqual<S> = isEqualBase,
|
|
11
|
+
): GetterState<S> {
|
|
12
|
+
let previousData: S | undefined
|
|
13
|
+
const emitter = createEmitter(() => {
|
|
14
|
+
const data = selector(state.getState())
|
|
15
|
+
if (previousData !== undefined && isEqual(previousData, data)) {
|
|
16
|
+
return previousData
|
|
17
|
+
}
|
|
18
|
+
previousData = data
|
|
19
|
+
return data
|
|
20
|
+
})
|
|
21
|
+
state.__internal.emitter.subscribe(() => {
|
|
22
|
+
emitter.emit()
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const baseState = createBaseState<S>({
|
|
26
|
+
emitter,
|
|
27
|
+
getGetterState: () => getterState,
|
|
28
|
+
getState: () => selector(state.getState()),
|
|
29
|
+
reset: state.reset,
|
|
30
|
+
})
|
|
31
|
+
const getterState: GetterState<S> = createGetterState<S>({ baseState })
|
|
32
|
+
return getterState
|
|
33
|
+
}
|