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