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