dev-react-microstore 1.1.3 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
- package/src/index.ts +172 -133
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var K=Object.defineProperty;var P=Object.getOwnPropertyDescriptor;var v=Object.getOwnPropertyNames;var j=Object.prototype.hasOwnProperty;var I=(o,e)=>{for(var t in e)K(o,t,{get:e[t],enumerable:!0})},R=(o,e,t,c)=>{if(e&&typeof e=="object"||typeof e=="function")for(let d of v(e))!j.call(o,d)&&d!==t&&K(o,d,{get:()=>e[d],enumerable:!(c=P(e,d))||c.enumerable});return o};var C=o=>R(K({},"__esModule",{value:!0}),o);var w={};I(w,{createStoreState:()=>F,useStoreSelector:()=>E});module.exports=C(w);var T=require("react");function V(o,e){let t;return function(...c){t!==void 0&&clearTimeout(t),t=setTimeout(()=>o.apply(this,c),e)}}function F(o){let e=o,t=new Map,c=new Map;return{get:()=>e,set:l=>{if(!l)return;let a=[];for(let n in l){let u=n,m=e[u],r=l[u];m!==r&&(Object.is(m,r)||(e[u]=r,a.push(u)))}if(a.length!==0)for(let n of a)c.has(n)||c.set(n,V(()=>{var u;(u=t.get(n))==null||u.forEach(m=>m())},5)),c.get(n)()},subscribe:(l,a)=>{for(let n of l)t.has(n)||t.set(n,new Set),t.get(n).add(a);return()=>{var n;for(let u of l)(n=t.get(u))==null||n.delete(a)}},select:l=>{let a={},n=e;for(let u of l)a[u]=n[u];return a}}}function z(o,e){return o.length===e.length&&o.every((t,c)=>t===e[c])}function E(o,e){let t=(0,T.useRef)({}),c=(0,T.useRef)(null),d=(0,T.useRef)(null),x=(0,T.useRef)(null),h=(0,T.useRef)(!0),b=(0,T.useRef)({}),l=(0,T.useRef)(null);if(!c.current||!z(c.current,e)){let r=[],p=[];for(let k of e)if(typeof k=="string"){let y=k;r.push({key:y}),p.push(y)}else{let y=k;for(let s in y){let f=y[s],i=s;r.push({key:i,compare:f}),p.push(i)}}d.current=r,x.current=p,c.current=e}let a=d.current,n=x.current,u=()=>{let r=o.get();if(h.current){h.current=!1;let s={};for(let{key:f}of a){let i=r[f];b.current[f]=i,s[f]=i}return t.current=s,s}if(!(()=>{var s;for(let{key:f,compare:i}of a){let S=b.current[f],g=r[f];if(S===void 0||((s=i==null?void 0:i(S,g))!=null?s:!Object.is(S,g)))return!0}return!1})())return t.current;let y={};for(let{key:s,compare:f}of a){let i=b.current[s],S=r[s];i===void 0||(f?!f(i,S):!Object.is(i,S))?(b.current[s]=S,y[s]=S):y[s]=i}return t.current=y,y},m=(0,T.useMemo)(()=>{let r=o.get(),p={};for(let k of n)p[k]=r[k];return p},[n]);if(!l.current||x.current!==n){let r=n;l.current=p=>o.subscribe(r,p)}return(0,T.useSyncExternalStore)(l.current,u,()=>m)}0&&(module.exports={createStoreState,useStoreSelector});
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useRef as
|
|
1
|
+
import{useMemo as K,useRef as S,useSyncExternalStore as P}from"react";function v(T,c){let t;return function(...a){t!==void 0&&clearTimeout(t),t=setTimeout(()=>T.apply(this,a),c)}}function C(T){let c=T,t=new Map,a=new Map;return{get:()=>c,set:i=>{if(!i)return;let u=[];for(let e in i){let r=e,k=c[r],n=i[r];k!==n&&(Object.is(k,n)||(c[r]=n,u.push(r)))}if(u.length!==0)for(let e of u)a.has(e)||a.set(e,v(()=>{var r;(r=t.get(e))==null||r.forEach(k=>k())},5)),a.get(e)()},subscribe:(i,u)=>{for(let e of i)t.has(e)||t.set(e,new Set),t.get(e).add(u);return()=>{var e;for(let r of i)(e=t.get(r))==null||e.delete(u)}},select:i=>{let u={},e=c;for(let r of i)u[r]=e[r];return u}}}function j(T,c){return T.length===c.length&&T.every((t,a)=>t===c[a])}function V(T,c){let t=S({}),a=S(null),x=S(null),b=S(null),h=S(!0),m=S({}),i=S(null);if(!a.current||!j(a.current,c)){let n=[],y=[];for(let p of c)if(typeof p=="string"){let f=p;n.push({key:f}),y.push(f)}else{let f=p;for(let o in f){let l=f[o],s=o;n.push({key:s,compare:l}),y.push(s)}}x.current=n,b.current=y,a.current=c}let u=x.current,e=b.current,r=()=>{let n=T.get();if(h.current){h.current=!1;let o={};for(let{key:l}of u){let s=n[l];m.current[l]=s,o[l]=s}return t.current=o,o}if(!(()=>{var o;for(let{key:l,compare:s}of u){let d=m.current[l],g=n[l];if(d===void 0||((o=s==null?void 0:s(d,g))!=null?o:!Object.is(d,g)))return!0}return!1})())return t.current;let f={};for(let{key:o,compare:l}of u){let s=m.current[o],d=n[o];s===void 0||(l?!l(s,d):!Object.is(s,d))?(m.current[o]=d,f[o]=d):f[o]=s}return t.current=f,f},k=K(()=>{let n=T.get(),y={};for(let p of e)y[p]=n[p];return y},[e]);if(!i.current||b.current!==e){let n=e;i.current=y=>T.subscribe(n,y)}return P(i.current,r,()=>k)}export{C as createStoreState,V as useStoreSelector};
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,61 +1,79 @@
|
|
|
1
|
-
import { useRef, useSyncExternalStore } from 'react'
|
|
2
|
-
type StoreListener = () => void;
|
|
1
|
+
import { useMemo, useRef, useSyncExternalStore } from 'react'
|
|
3
2
|
|
|
3
|
+
type StoreListener = () => void;
|
|
4
4
|
|
|
5
|
+
function debounce<T extends (...args: any[]) => void>(fn: T, delay: number): T {
|
|
6
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
7
|
+
return function (this: ThisParameterType<T>, ...args: Parameters<T>) {
|
|
8
|
+
if (timeoutId !== undefined) {
|
|
9
|
+
clearTimeout(timeoutId);
|
|
10
|
+
}
|
|
11
|
+
timeoutId = setTimeout(() => fn.apply(this, args), delay);
|
|
12
|
+
} as T;
|
|
13
|
+
}
|
|
5
14
|
|
|
6
15
|
export function createStoreState<T extends object>(initialState: T) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const keyListeners = new Map<keyof T, Set<StoreListener>>();
|
|
16
|
+
let state = initialState;
|
|
10
17
|
|
|
11
|
-
|
|
18
|
+
const keyListeners = new Map<keyof T, Set<StoreListener>>();
|
|
19
|
+
const debouncedNotifiers = new Map<keyof T, () => void>();
|
|
12
20
|
|
|
13
|
-
|
|
14
|
-
const updatedKeys: (keyof T)[] = [];
|
|
21
|
+
const get = () => state;
|
|
15
22
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (!Object.is(state[typedKey], next[typedKey])) {
|
|
19
|
-
updatedKeys.push(typedKey);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
23
|
+
const set = (next: Partial<T>) => {
|
|
24
|
+
if (!next) return;
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
const updatedKeys: (keyof T)[] = [];
|
|
27
|
+
for (const key in next) {
|
|
28
|
+
const typedKey = key as keyof T;
|
|
29
|
+
const currentValue = state[typedKey];
|
|
30
|
+
const nextValue = next[typedKey];
|
|
24
31
|
|
|
25
|
-
|
|
26
|
-
state[key as keyof T] = next[key as keyof T]!;
|
|
27
|
-
}
|
|
32
|
+
if (currentValue === nextValue) continue;
|
|
28
33
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const subscribe = (keys: (keyof T)[], listener: StoreListener): (() => void) => {
|
|
35
|
-
for (const key of keys) {
|
|
36
|
-
if (!keyListeners.has(key)) {
|
|
37
|
-
keyListeners.set(key, new Set());
|
|
38
|
-
}
|
|
39
|
-
keyListeners.get(key)!.add(listener);
|
|
40
|
-
}
|
|
34
|
+
if (!Object.is(currentValue, nextValue)) {
|
|
35
|
+
state[typedKey] = nextValue!;
|
|
36
|
+
updatedKeys.push(typedKey);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
41
39
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
if (updatedKeys.length === 0) return;
|
|
41
|
+
|
|
42
|
+
for (const key of updatedKeys) {
|
|
43
|
+
if (!debouncedNotifiers.has(key)) {
|
|
44
|
+
debouncedNotifiers.set(key, debounce(() => {
|
|
45
|
+
keyListeners.get(key)?.forEach(listener => listener());
|
|
46
|
+
}, 5));
|
|
47
|
+
}
|
|
48
|
+
debouncedNotifiers.get(key)!();
|
|
49
|
+
}
|
|
46
50
|
};
|
|
47
|
-
};
|
|
48
51
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
};
|
|
52
|
+
const subscribe = (keys: (keyof T)[], listener: StoreListener): (() => void) => {
|
|
53
|
+
for (const key of keys) {
|
|
54
|
+
if (!keyListeners.has(key)) {
|
|
55
|
+
keyListeners.set(key, new Set());
|
|
56
|
+
}
|
|
57
|
+
keyListeners.get(key)!.add(listener);
|
|
58
|
+
}
|
|
57
59
|
|
|
58
|
-
|
|
60
|
+
return () => {
|
|
61
|
+
for (const key of keys) {
|
|
62
|
+
keyListeners.get(key)?.delete(listener);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const select = <K extends keyof T>(keys: K[]): Pick<T, K> => {
|
|
68
|
+
const result = {} as Pick<T, K>;
|
|
69
|
+
const currentState = state;
|
|
70
|
+
for (const key of keys) {
|
|
71
|
+
result[key] = currentState[key];
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
return { get, set, subscribe, select };
|
|
59
77
|
}
|
|
60
78
|
|
|
61
79
|
type StoreType<T extends object> = ReturnType<typeof createStoreState<T>>;
|
|
@@ -67,115 +85,136 @@ type CustomSelector<T extends object> = { [K in keyof T]?: CompareFn<T[K]> };
|
|
|
67
85
|
type SelectorInput<T extends object> = ReadonlyArray<KeySelector<T> | CustomSelector<T>>;
|
|
68
86
|
|
|
69
87
|
type ExtractSelectorKeys<T extends object, S extends SelectorInput<T>> = {
|
|
70
|
-
|
|
88
|
+
[K in S[number] extends infer Item
|
|
71
89
|
? Item extends keyof T
|
|
72
|
-
|
|
73
|
-
|
|
90
|
+
? Item
|
|
91
|
+
: keyof Item
|
|
74
92
|
: never]: T[K];
|
|
75
93
|
};
|
|
76
94
|
|
|
77
95
|
type Picked<T extends object, S extends SelectorInput<T>> = ExtractSelectorKeys<T, S>;
|
|
78
96
|
|
|
79
97
|
type NormalizedSelector<T extends object> = {
|
|
80
|
-
|
|
81
|
-
|
|
98
|
+
key: keyof T;
|
|
99
|
+
compare?: CompareFn<T[keyof T]>;
|
|
82
100
|
};
|
|
83
101
|
|
|
84
102
|
function shallowEqualSelector<T extends object>(
|
|
85
|
-
|
|
86
|
-
|
|
103
|
+
a: SelectorInput<T>,
|
|
104
|
+
b: SelectorInput<T>
|
|
87
105
|
): boolean {
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function pickKeys<T extends object>(
|
|
92
|
-
base: T,
|
|
93
|
-
prev: Partial<T>,
|
|
94
|
-
selectors: NormalizedSelector<T>[]
|
|
95
|
-
): Partial<T> {
|
|
96
|
-
const out: Partial<T> = {};
|
|
97
|
-
for (const { key, compare } of selectors) {
|
|
98
|
-
const prevVal = prev[key];
|
|
99
|
-
const nextVal = base[key];
|
|
100
|
-
const changed = prevVal === undefined ? true : (compare ? compare(prevVal, nextVal) : !Object.is(prevVal, nextVal));
|
|
101
|
-
out[key] = changed ? nextVal : prevVal;
|
|
102
|
-
}
|
|
103
|
-
return out;
|
|
106
|
+
return a.length === b.length && a.every((item, i) => item === b[i]);
|
|
104
107
|
}
|
|
105
108
|
|
|
106
109
|
export function useStoreSelector<T extends object, S extends SelectorInput<T>>(
|
|
107
|
-
|
|
108
|
-
|
|
110
|
+
store: StoreType<T>,
|
|
111
|
+
selector: S
|
|
109
112
|
): Picked<T, S> {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
113
|
+
const lastSelected = useRef<Partial<T>>({});
|
|
114
|
+
const prevSelector = useRef<SelectorInput<T> | null>(null);
|
|
115
|
+
const normalizedRef = useRef<NormalizedSelector<T>[] | null>(null);
|
|
116
|
+
const keysRef = useRef<(keyof T)[] | null>(null);
|
|
117
|
+
const isFirstRunRef = useRef(true);
|
|
118
|
+
const lastValues = useRef<Partial<T>>({});
|
|
119
|
+
const subscribeRef = useRef<((onStoreChange: () => void) => () => void) | null>(null);
|
|
120
|
+
|
|
121
|
+
if (!prevSelector.current || !shallowEqualSelector(prevSelector.current, selector)) {
|
|
122
|
+
const normalized: NormalizedSelector<T>[] = [];
|
|
123
|
+
const keys: (keyof T)[] = [];
|
|
124
|
+
|
|
125
|
+
for (const item of selector) {
|
|
126
|
+
if (typeof item === 'string') {
|
|
127
|
+
const key = item as keyof T;
|
|
128
|
+
normalized.push({ key });
|
|
129
|
+
keys.push(key);
|
|
130
|
+
} else {
|
|
131
|
+
const customSelector = item as CustomSelector<T>;
|
|
132
|
+
for (const key in customSelector) {
|
|
133
|
+
const compare = customSelector[key as keyof typeof customSelector];
|
|
134
|
+
const typedKey = key as keyof T;
|
|
135
|
+
normalized.push({ key: typedKey, compare: compare as CompareFn<T[keyof T]> });
|
|
136
|
+
keys.push(typedKey);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
131
139
|
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
normalizedRef.current = normalized;
|
|
136
|
-
keysRef.current = keys;
|
|
137
|
-
prevSelector.current = selector;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const normalized = normalizedRef.current!;
|
|
141
|
-
const keys = keysRef.current!;
|
|
142
|
-
|
|
143
|
-
const getSnapshot = () => {
|
|
144
|
-
const current = store.get();
|
|
145
|
-
const isFirstRun = isFirstRunRef.current;
|
|
146
|
-
|
|
147
|
-
const changed = isFirstRun || normalized.some(({ key, compare }) => {
|
|
148
|
-
const prevVal = lastValues.current[key];
|
|
149
|
-
const nextVal = current[key];
|
|
150
|
-
return prevVal === undefined ? true : (compare?.(prevVal, nextVal) ?? !Object.is(prevVal, nextVal));
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
if (!changed) {
|
|
154
|
-
return lastSelected.current as Picked<T, S>;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (isFirstRun) {
|
|
158
|
-
isFirstRunRef.current = false;
|
|
159
|
-
}
|
|
160
140
|
|
|
161
|
-
|
|
162
|
-
|
|
141
|
+
normalizedRef.current = normalized;
|
|
142
|
+
keysRef.current = keys;
|
|
143
|
+
prevSelector.current = selector;
|
|
163
144
|
}
|
|
164
145
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
146
|
+
const normalized = normalizedRef.current!;
|
|
147
|
+
const keys = keysRef.current!;
|
|
148
|
+
|
|
149
|
+
const getSnapshot = () => {
|
|
150
|
+
const current = store.get();
|
|
151
|
+
const isFirstRun = isFirstRunRef.current;
|
|
152
|
+
|
|
153
|
+
if (isFirstRun) {
|
|
154
|
+
isFirstRunRef.current = false;
|
|
155
|
+
const result = {} as Partial<T>;
|
|
156
|
+
for (const { key } of normalized) {
|
|
157
|
+
const value = current[key];
|
|
158
|
+
lastValues.current[key] = value;
|
|
159
|
+
result[key] = value;
|
|
160
|
+
}
|
|
161
|
+
lastSelected.current = result;
|
|
162
|
+
return result as Picked<T, S>;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Check for changes with early return
|
|
166
|
+
const hasChanges = () => {
|
|
167
|
+
for (const { key, compare } of normalized) {
|
|
168
|
+
const prevVal = lastValues.current[key];
|
|
169
|
+
const nextVal = current[key];
|
|
170
|
+
if (prevVal === undefined ? true : (compare?.(prevVal, nextVal) ?? !Object.is(prevVal, nextVal))) {
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return false;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
if (!hasChanges()) {
|
|
178
|
+
return lastSelected.current as Picked<T, S>;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Build result only when changes detected
|
|
182
|
+
const result = {} as Partial<T>;
|
|
183
|
+
for (const { key, compare } of normalized) {
|
|
184
|
+
const prevVal = lastValues.current[key];
|
|
185
|
+
const nextVal = current[key];
|
|
186
|
+
|
|
187
|
+
// Hoist the comparison logic
|
|
188
|
+
const isFirstTime = prevVal === undefined;
|
|
189
|
+
const changed = isFirstTime || (compare ? !compare(prevVal, nextVal) : !Object.is(prevVal, nextVal));
|
|
190
|
+
|
|
191
|
+
if (changed) {
|
|
192
|
+
lastValues.current[key] = nextVal;
|
|
193
|
+
result[key] = nextVal;
|
|
194
|
+
} else {
|
|
195
|
+
result[key] = prevVal;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
lastSelected.current = result;
|
|
200
|
+
return result as Picked<T, S>;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const staticSnapshot = useMemo(() => {
|
|
204
|
+
const current = store.get();
|
|
205
|
+
const result = {} as Partial<T>;
|
|
206
|
+
for (const key of keys) {
|
|
207
|
+
result[key] = current[key];
|
|
208
|
+
}
|
|
209
|
+
return result as Picked<T, S>;
|
|
210
|
+
}, [keys]);
|
|
168
211
|
|
|
169
|
-
const staticSnapshot = (() => {
|
|
170
|
-
const current = store.get();
|
|
171
|
-
return keys.reduce((acc, key) => {
|
|
172
|
-
acc[key] = current[key];
|
|
173
|
-
return acc;
|
|
174
|
-
}, {} as Partial<T>) as Picked<T, S>;
|
|
175
|
-
})();
|
|
176
212
|
|
|
177
|
-
|
|
178
|
-
|
|
213
|
+
if (!subscribeRef.current || keysRef.current !== keys) {
|
|
214
|
+
const currentKeys = keys;
|
|
215
|
+
subscribeRef.current = (onStoreChange: () => void) =>
|
|
216
|
+
store.subscribe(currentKeys, onStoreChange);
|
|
217
|
+
}
|
|
179
218
|
|
|
180
|
-
|
|
219
|
+
return useSyncExternalStore(subscribeRef.current, getSnapshot, () => staticSnapshot);
|
|
181
220
|
}
|