@plasius/react-state 1.1.0 → 1.2.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/README.md +6 -0
- package/dist/index.cjs +153 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +155 -36
- package/dist/index.js.map +1 -1
- package/package.json +7 -4
package/README.md
CHANGED
|
@@ -14,6 +14,12 @@
|
|
|
14
14
|
|
|
15
15
|
`@plasius/react-state` provides a scoped state management solution for React applications. It allows developers to create isolated, testable, and composable stores without introducing heavy dependencies.
|
|
16
16
|
|
|
17
|
+
**Key traits**
|
|
18
|
+
- React 18/19 compatible; uses `useSyncExternalStore` for tearing-safe snapshots.
|
|
19
|
+
- Distinct-until-changed dispatch flow to avoid needless notifications and re-renders.
|
|
20
|
+
- Selector subscriptions accept custom equality to prevent redundant renders when slices are unchanged.
|
|
21
|
+
- Scoped Provider recreates its store when `initialState` changes so fresh trees start from fresh state.
|
|
22
|
+
|
|
17
23
|
---
|
|
18
24
|
|
|
19
25
|
## Installation
|
package/dist/index.cjs
CHANGED
|
@@ -56,6 +56,14 @@ function deepFreeze(obj, seen = /* @__PURE__ */ new WeakSet()) {
|
|
|
56
56
|
// src/store.ts
|
|
57
57
|
var import_meta = {};
|
|
58
58
|
var DEV = typeof import_meta !== "undefined" ? import_meta.env?.DEV : process.env.NODE_ENV !== "production";
|
|
59
|
+
function devTrack(name, props) {
|
|
60
|
+
if (!DEV) return;
|
|
61
|
+
try {
|
|
62
|
+
const t = globalThis?.track;
|
|
63
|
+
if (typeof t === "function") t(name, props);
|
|
64
|
+
} catch {
|
|
65
|
+
}
|
|
66
|
+
}
|
|
59
67
|
function createStore(reducer, initialState) {
|
|
60
68
|
let state = DEV ? deepFreeze(initialState) : initialState;
|
|
61
69
|
const listeners = /* @__PURE__ */ new Set();
|
|
@@ -66,49 +74,95 @@ function createStore(reducer, initialState) {
|
|
|
66
74
|
const prevState = state;
|
|
67
75
|
const nextState = reducer(state, action);
|
|
68
76
|
if (DEV) deepFreeze(nextState);
|
|
77
|
+
devTrack("store:dispatch", { type: action?.type });
|
|
69
78
|
if (Object.is(prevState, nextState)) {
|
|
70
79
|
state = nextState;
|
|
80
|
+
devTrack("store:no-op", { type: action?.type });
|
|
71
81
|
return;
|
|
72
82
|
}
|
|
73
83
|
state = nextState;
|
|
74
|
-
|
|
84
|
+
let changedKeys;
|
|
85
|
+
if (DEV) {
|
|
86
|
+
changedKeys = Object.keys(nextState).filter((k) => !Object.is(prevState[k], nextState[k]));
|
|
87
|
+
devTrack("store:state-changed", { type: action?.type, changedKeys });
|
|
88
|
+
}
|
|
89
|
+
const globalSnapshot = [...listeners];
|
|
90
|
+
devTrack("store:notify:all", { listeners: globalSnapshot.length });
|
|
91
|
+
let firstError;
|
|
92
|
+
for (const listener of globalSnapshot) {
|
|
93
|
+
try {
|
|
94
|
+
listener();
|
|
95
|
+
} catch (err) {
|
|
96
|
+
if (!firstError) firstError = err;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
75
99
|
for (const [key, set] of keyListeners.entries()) {
|
|
76
100
|
if (!Object.is(prevState[key], state[key])) {
|
|
77
|
-
|
|
101
|
+
devTrack("store:notify:key", { key: String(key), listeners: set.size });
|
|
102
|
+
for (const listener of [...set]) {
|
|
103
|
+
try {
|
|
104
|
+
listener(state[key]);
|
|
105
|
+
} catch (err) {
|
|
106
|
+
if (!firstError) firstError = err;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
78
109
|
}
|
|
79
110
|
}
|
|
80
|
-
|
|
111
|
+
let selNotifies = 0;
|
|
112
|
+
for (const entry of [...selectorListeners]) {
|
|
81
113
|
const nextValue = entry.selector(state);
|
|
82
|
-
|
|
114
|
+
const equal = entry.isEqual ?? Object.is;
|
|
115
|
+
let isSame = false;
|
|
116
|
+
try {
|
|
117
|
+
isSame = equal(entry.lastValue, nextValue);
|
|
118
|
+
} catch (err) {
|
|
119
|
+
if (!firstError) firstError = err;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (!isSame) {
|
|
83
123
|
entry.lastValue = nextValue;
|
|
84
|
-
|
|
124
|
+
try {
|
|
125
|
+
entry.listener(nextValue);
|
|
126
|
+
} catch (err) {
|
|
127
|
+
if (!firstError) firstError = err;
|
|
128
|
+
}
|
|
129
|
+
selNotifies++;
|
|
85
130
|
}
|
|
86
|
-
}
|
|
131
|
+
}
|
|
132
|
+
devTrack("store:notify:selector", { listeners: selNotifies });
|
|
133
|
+
if (firstError) throw firstError;
|
|
87
134
|
};
|
|
88
135
|
const subscribe = (listener) => {
|
|
89
136
|
listeners.add(listener);
|
|
137
|
+
devTrack("store:sub:all:add", { size: listeners.size });
|
|
90
138
|
return () => {
|
|
91
139
|
listeners.delete(listener);
|
|
140
|
+
devTrack("store:sub:all:remove", { size: listeners.size });
|
|
92
141
|
};
|
|
93
142
|
};
|
|
94
143
|
const subscribeToKey = (key, listener) => {
|
|
95
144
|
const set = keyListeners.get(key) ?? /* @__PURE__ */ new Set();
|
|
96
145
|
set.add(listener);
|
|
97
146
|
keyListeners.set(key, set);
|
|
147
|
+
devTrack("store:sub:key:add", { key: String(key), size: set.size });
|
|
98
148
|
return () => {
|
|
99
149
|
set.delete(listener);
|
|
100
150
|
if (set.size === 0) keyListeners.delete(key);
|
|
151
|
+
devTrack("store:sub:key:remove", { key: String(key), size: set.size });
|
|
101
152
|
};
|
|
102
153
|
};
|
|
103
|
-
const subscribeWithSelector = (selector, listener) => {
|
|
154
|
+
const subscribeWithSelector = (selector, listener, isEqual) => {
|
|
104
155
|
const entry = {
|
|
105
156
|
selector,
|
|
106
157
|
listener,
|
|
107
|
-
lastValue: selector(state)
|
|
158
|
+
lastValue: selector(state),
|
|
159
|
+
isEqual
|
|
108
160
|
};
|
|
109
161
|
selectorListeners.add(entry);
|
|
162
|
+
devTrack("store:sub:selector:add", { size: selectorListeners.size });
|
|
110
163
|
return () => {
|
|
111
164
|
selectorListeners.delete(entry);
|
|
165
|
+
devTrack("store:sub:selector:remove", { size: selectorListeners.size });
|
|
112
166
|
};
|
|
113
167
|
};
|
|
114
168
|
return {
|
|
@@ -122,6 +176,15 @@ function createStore(reducer, initialState) {
|
|
|
122
176
|
|
|
123
177
|
// src/create-scoped-store.tsx
|
|
124
178
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
179
|
+
var __DEV__ = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
|
|
180
|
+
function devTrack2(name, props) {
|
|
181
|
+
if (!__DEV__) return;
|
|
182
|
+
try {
|
|
183
|
+
const t = globalThis?.track;
|
|
184
|
+
if (typeof t === "function") t(name, props);
|
|
185
|
+
} catch {
|
|
186
|
+
}
|
|
187
|
+
}
|
|
125
188
|
var _queue = /* @__PURE__ */ new Set();
|
|
126
189
|
var _queued = false;
|
|
127
190
|
var _schedule = typeof queueMicrotask === "function" ? queueMicrotask : (fn) => Promise.resolve().then(fn);
|
|
@@ -134,10 +197,22 @@ function enqueue(fn) {
|
|
|
134
197
|
const fns = Array.from(_queue);
|
|
135
198
|
_queue.clear();
|
|
136
199
|
for (const f of fns) f();
|
|
200
|
+
devTrack2("scoped:batch:flush", { size: fns.length });
|
|
137
201
|
});
|
|
138
202
|
}
|
|
139
203
|
function makeBatchedSubscribe(subscribe) {
|
|
140
|
-
return (onChange) =>
|
|
204
|
+
return (onChange) => {
|
|
205
|
+
devTrack2("scoped:sub:add");
|
|
206
|
+
const wrapped = () => {
|
|
207
|
+
devTrack2("scoped:notify:enqueue");
|
|
208
|
+
enqueue(onChange);
|
|
209
|
+
};
|
|
210
|
+
const unsubscribe = subscribe(wrapped);
|
|
211
|
+
return () => {
|
|
212
|
+
devTrack2("scoped:sub:remove");
|
|
213
|
+
unsubscribe();
|
|
214
|
+
};
|
|
215
|
+
};
|
|
141
216
|
}
|
|
142
217
|
function shallowEqual(a, b) {
|
|
143
218
|
if (Object.is(a, b)) return true;
|
|
@@ -158,15 +233,21 @@ function createScopedStoreContext(reducer, initialState) {
|
|
|
158
233
|
children,
|
|
159
234
|
initialState: override
|
|
160
235
|
}) => {
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
236
|
+
const store = (0, import_react.useMemo)(() => {
|
|
237
|
+
const next = createStore(reducer, override ?? initialState);
|
|
238
|
+
devTrack2("scoped:store:create");
|
|
239
|
+
return next;
|
|
240
|
+
}, [reducer, override, initialState]);
|
|
241
|
+
(0, import_react.useEffect)(() => {
|
|
242
|
+
devTrack2("scoped:provider:mount");
|
|
243
|
+
return () => devTrack2("scoped:provider:unmount");
|
|
244
|
+
}, []);
|
|
245
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Context.Provider, { value: store, children });
|
|
166
246
|
};
|
|
167
247
|
const useStore2 = () => {
|
|
168
248
|
const ctx = (0, import_react.useContext)(Context);
|
|
169
249
|
if (!ctx) throw new Error("Store not found in context");
|
|
250
|
+
devTrack2("scoped:useStore");
|
|
170
251
|
return (0, import_react.useSyncExternalStore)(
|
|
171
252
|
makeBatchedSubscribe(ctx.subscribe),
|
|
172
253
|
ctx.getState,
|
|
@@ -181,19 +262,36 @@ function createScopedStoreContext(reducer, initialState) {
|
|
|
181
262
|
function useSelector(selector, isEqual = shallowEqual) {
|
|
182
263
|
const ctx = (0, import_react.useContext)(Context);
|
|
183
264
|
if (!ctx) throw new Error("Store not found in context");
|
|
184
|
-
const state = (0, import_react.useSyncExternalStore)(
|
|
185
|
-
makeBatchedSubscribe(ctx.subscribe),
|
|
186
|
-
ctx.getState,
|
|
187
|
-
ctx.getState
|
|
188
|
-
);
|
|
189
265
|
const lastRef = (0, import_react.useRef)(null);
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
266
|
+
const getSnapshot = () => {
|
|
267
|
+
const nextSelected = selector(ctx.getState());
|
|
268
|
+
const last = lastRef.current;
|
|
269
|
+
if (last && isEqual(last.selected, nextSelected)) {
|
|
270
|
+
devTrack2("scoped:selector:cache-hit");
|
|
271
|
+
return last.selected;
|
|
272
|
+
}
|
|
273
|
+
devTrack2("scoped:selector:cache-miss");
|
|
274
|
+
lastRef.current = { selected: nextSelected };
|
|
275
|
+
return nextSelected;
|
|
276
|
+
};
|
|
277
|
+
const subscribe = (onChange) => {
|
|
278
|
+
devTrack2("scoped:selector:sub:add");
|
|
279
|
+
const unsubscribe = ctx.subscribeWithSelector(selector, (nextSelected) => {
|
|
280
|
+
const last = lastRef.current;
|
|
281
|
+
if (last && isEqual(last.selected, nextSelected)) {
|
|
282
|
+
devTrack2("scoped:selector:skip");
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
lastRef.current = { selected: nextSelected };
|
|
286
|
+
devTrack2("scoped:selector:notify");
|
|
287
|
+
enqueue(onChange);
|
|
288
|
+
}, isEqual);
|
|
289
|
+
return () => {
|
|
290
|
+
devTrack2("scoped:selector:sub:remove");
|
|
291
|
+
unsubscribe();
|
|
292
|
+
};
|
|
293
|
+
};
|
|
294
|
+
return (0, import_react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
|
|
197
295
|
}
|
|
198
296
|
return {
|
|
199
297
|
Context,
|
|
@@ -207,10 +305,20 @@ function createScopedStoreContext(reducer, initialState) {
|
|
|
207
305
|
// src/provider.tsx
|
|
208
306
|
var import_react2 = require("react");
|
|
209
307
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
308
|
+
var __DEV__2 = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
|
|
309
|
+
function devTrack3(name, props) {
|
|
310
|
+
if (!__DEV__2) return;
|
|
311
|
+
try {
|
|
312
|
+
const t = globalThis?.track;
|
|
313
|
+
if (typeof t === "function") t(name, props);
|
|
314
|
+
} catch {
|
|
315
|
+
}
|
|
316
|
+
}
|
|
210
317
|
var StoreContext = (0, import_react2.createContext)(void 0);
|
|
211
318
|
function useStoreInstance() {
|
|
212
319
|
const store = (0, import_react2.useContext)(StoreContext);
|
|
213
320
|
if (!store) {
|
|
321
|
+
devTrack3("store:provider:missing");
|
|
214
322
|
throw new Error(
|
|
215
323
|
"StoreProvider is missing in the React tree. Wrap your app with <StoreProvider store={...}>."
|
|
216
324
|
);
|
|
@@ -221,18 +329,29 @@ function StoreProvider({
|
|
|
221
329
|
store,
|
|
222
330
|
children
|
|
223
331
|
}) {
|
|
332
|
+
(0, import_react2.useEffect)(() => {
|
|
333
|
+
devTrack3("store:provider:mount", { hasStore: !!store });
|
|
334
|
+
return () => devTrack3("store:provider:unmount");
|
|
335
|
+
}, [store]);
|
|
224
336
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StoreContext.Provider, { value: store, children });
|
|
225
337
|
}
|
|
226
338
|
function useStore() {
|
|
227
339
|
const store = useStoreInstance();
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
340
|
+
return (0, import_react2.useSyncExternalStore)(
|
|
341
|
+
(onStoreChange) => {
|
|
342
|
+
devTrack3("store:react:subscribe");
|
|
343
|
+
const unsubscribe = store.subscribe(() => {
|
|
344
|
+
devTrack3("store:react:notify");
|
|
345
|
+
onStoreChange();
|
|
346
|
+
});
|
|
347
|
+
return () => {
|
|
348
|
+
devTrack3("store:react:unsubscribe");
|
|
349
|
+
unsubscribe();
|
|
350
|
+
};
|
|
351
|
+
},
|
|
352
|
+
store.getState,
|
|
353
|
+
store.getState
|
|
354
|
+
);
|
|
236
355
|
}
|
|
237
356
|
function useDispatch() {
|
|
238
357
|
const store = useStoreInstance();
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/types.ts","../src/create-scoped-store.tsx","../src/freeze.ts","../src/store.ts","../src/provider.tsx","../src/metadata-store.ts"],"sourcesContent":["export * from \"./types.js\";\nexport * from \"./create-scoped-store.js\";\nexport * from \"./store.js\";\nexport * from \"./provider.js\";\nexport * from \"./metadata-store.js\";\n","export type Reducer<S, A> = (state: S, action: A) => S;\nexport type Listener = () => void;\nexport type Unsubscribe = () => void;\nexport const __noop = null;","import { createContext, useContext, useRef, useSyncExternalStore, useCallback } from \"react\";\nimport type { IState, IAction, Store } from \"./store.js\";\nimport { createStore } from \"./store.js\";\n\n// Local microtask-based batching (no react-dom dependency)\nconst _queue = new Set<() => void>();\nlet _queued = false;\nconst _schedule = typeof queueMicrotask === \"function\"\n ? queueMicrotask\n : (fn: () => void) => Promise.resolve().then(fn);\n\nfunction enqueue(fn: () => void) {\n _queue.add(fn);\n if (_queued) return;\n _queued = true;\n _schedule(() => {\n _queued = false;\n const fns = Array.from(_queue);\n _queue.clear();\n for (const f of fns) f();\n });\n}\n\nfunction makeBatchedSubscribe(subscribe: (l: () => void) => () => void) {\n return (onChange: () => void) => subscribe(() => enqueue(onChange));\n}\n\nfunction shallowEqual(a: any, b: any) {\n if (Object.is(a, b)) return true;\n if (\n typeof a !== \"object\" ||\n a === null ||\n typeof b !== \"object\" ||\n b === null\n )\n return false;\n const ak = Object.keys(a),\n bk = Object.keys(b);\n if (ak.length !== bk.length) return false;\n for (let i = 0; i < ak.length; i++) {\n const k = ak[i] as string;\n if (\n !Object.prototype.hasOwnProperty.call(b, k) ||\n !Object.is((a as any)[k], (b as any)[k])\n )\n return false;\n }\n return true;\n}\n\nexport function createScopedStoreContext<S extends IState, A extends IAction>(\n reducer: (state: S, action: A) => S,\n initialState: S\n) {\n const Context = createContext<Store<S, A> | null>(null);\n\n // Each Provider instance gets its own store.\n const Provider = ({\n children,\n initialState: override,\n }: {\n children: React.ReactNode;\n initialState?: S;\n }) => {\n const storeRef = useRef<Store<S, A> | null>(null);\n if (!storeRef.current) {\n storeRef.current = createStore(reducer, override ?? initialState);\n }\n return <Context.Provider value={storeRef.current}>{children}</Context.Provider>;\n };\n\n const useStore = (): S => {\n const ctx = useContext(Context);\n if (!ctx) throw new Error(\"Store not found in context\");\n return useSyncExternalStore(\n makeBatchedSubscribe(ctx.subscribe),\n ctx.getState,\n ctx.getState\n );\n };\n\n const useDispatch = (): ((action: A) => void) => {\n const ctx = useContext(Context);\n if (!ctx) throw new Error(\"Dispatch not found in context\");\n return useCallback((action: A) => ctx.dispatch(action), [ctx]);\n };\n\n function useSelector<T>(\n selector: (state: S) => T,\n isEqual: (a: T, b: T) => boolean = shallowEqual\n ): T {\n const ctx = useContext(Context);\n if (!ctx) throw new Error(\"Store not found in context\");\n\n // Subscribe to the raw state snapshot (stable reference until a dispatch)\n const state = useSyncExternalStore(\n makeBatchedSubscribe(ctx.subscribe),\n ctx.getState,\n ctx.getState\n );\n\n // Cache the selected slice per state snapshot to avoid returning fresh objects during render\n const lastRef = useRef<{ state: S; selected: T } | null>(null);\n const last = lastRef.current;\n const nextSelected = selector(state);\n\n if (last && last.state === state && isEqual(last.selected, nextSelected)) {\n return last.selected; // return cached reference to satisfy getSnapshot caching\n }\n\n lastRef.current = { state, selected: nextSelected };\n return nextSelected;\n }\n\n return {\n Context,\n Provider,\n useStore,\n useDispatch,\n useSelector,\n };\n}\n","// freeze.ts\nexport function deepFreeze<T>(obj: T, seen = new WeakSet<object>()): T {\n if (obj === null || typeof obj !== \"object\") return obj;\n const o = obj as unknown as object;\n if (seen.has(o)) return obj;\n seen.add(o);\n\n // Freeze children first\n for (const key of Object.getOwnPropertyNames(o)) {\n // @ts-expect-error index access\n const val = (o as any)[key];\n if (val && typeof val === \"object\") deepFreeze(val, seen);\n }\n // Also handle symbols (rare but safe)\n for (const sym of Object.getOwnPropertySymbols(o)) {\n // @ts-expect-error index access\n const val = (o as any)[sym];\n if (val && typeof val === \"object\") deepFreeze(val, seen);\n }\n\n return Object.freeze(obj);\n}\n","import type { Reducer, Listener } from \"./types.js\";\nimport { deepFreeze } from \"./freeze.js\";\n\nconst DEV =\n typeof import.meta !== \"undefined\"\n ? (import.meta as unknown as { env?: { DEV?: boolean } }).env?.DEV\n : process.env.NODE_ENV !== \"production\";\n\n// Allow narrower parameter types for callbacks without fighting variance\ntype BivariantListener<T> = {\n bivarianceHack(value: T): void;\n}[\"bivarianceHack\"];\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface IState {}\nexport interface IAction {\n type: string;\n}\n\nexport interface Store<S extends IState, A extends IAction> {\n getState(): S;\n dispatch(action: A): void;\n /**\n * Subscribe to all state changes.\n */\n subscribe(listener: Listener): () => void;\n /**\n * Subscribe to changes of a specific key in the state.\n */\n subscribeToKey<K extends keyof S>(\n key: K,\n listener: (value: S[K]) => void\n ): () => void;\n /**\n * Subscribe to changes in a selected value from the state.\n */\n subscribeWithSelector<T>(\n selector: (state: S) => T,\n listener: (selected: T) => void\n ): () => void;\n}\n\nexport function createStore<S extends IState, A extends IAction>(\n reducer: Reducer<S, A>,\n initialState: S\n): Store<S, A> {\n let state: S = DEV ? deepFreeze(initialState) : initialState;\n const listeners = new Set<Listener>();\n const keyListeners = new Map<keyof S, Set<BivariantListener<S[keyof S]>>>();\n\n interface SelectorEntry<T> {\n selector: (state: S) => T;\n listener: BivariantListener<T>;\n lastValue: T;\n }\n const selectorListeners = new Set<SelectorEntry<unknown>>();\n\n const getState = () => state;\n\n const dispatch = (action: A) => {\n const prevState = state;\n const nextState = reducer(state, action);\n \n if (DEV) deepFreeze(nextState);\n \n // Distinct-until-changed: if the reducer returns the same reference,\n // skip all notifications (prevents unnecessary re-renders).\n if (Object.is(prevState, nextState)) {\n state = nextState; // keep any identity guarantees from reducer\n return;\n }\n\n state = nextState;\n\n // Notify global listeners (iterate over a snapshot so unsubscribe during\n // notify does not skip the next listener)\n for (const listener of [...listeners]) listener();\n\n // Notify key listeners only when that key actually changed (Object.is)\n for (const [key, set] of keyListeners.entries()) {\n if (!Object.is(prevState[key], state[key])) {\n for (const listener of [...set]) listener(state[key]);\n }\n }\n\n // Notify selector listeners only when selected value changed (Object.is)\n selectorListeners.forEach((entry) => {\n const nextValue = (entry.selector as (s: S) => unknown)(state);\n if (!Object.is(entry.lastValue, nextValue)) {\n entry.lastValue = nextValue as unknown;\n (entry.listener as (v: unknown) => void)(nextValue);\n }\n });\n };\n\n const subscribe = (listener: Listener) => {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n };\n\n const subscribeToKey = <K extends keyof S>(\n key: K,\n listener: (value: S[K]) => void\n ) => {\n const set =\n keyListeners.get(key) ?? new Set<BivariantListener<S[keyof S]>>();\n set.add(listener as unknown as BivariantListener<S[keyof S]>);\n keyListeners.set(key, set);\n return () => {\n set.delete(listener as unknown as BivariantListener<S[keyof S]>);\n if (set.size === 0) keyListeners.delete(key);\n };\n };\n\n const subscribeWithSelector = <T>(\n selector: (state: S) => T,\n listener: (selected: T) => void\n ) => {\n const entry: SelectorEntry<T> = {\n selector,\n listener: listener as BivariantListener<T>,\n lastValue: selector(state),\n };\n selectorListeners.add(entry as unknown as SelectorEntry<unknown>);\n return () => {\n selectorListeners.delete(entry as unknown as SelectorEntry<unknown>);\n };\n };\n\n return {\n getState,\n dispatch,\n subscribe,\n subscribeToKey,\n subscribeWithSelector,\n };\n}\n","import React, { createContext, useContext, useEffect, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport type { Store, IState, IAction } from \"./store.js\";\n\nconst StoreContext = createContext<Store<IState, IAction> | undefined>(undefined);\n\nfunction useStoreInstance<S extends IState, A extends IAction>(): Store<S, A> {\n const store = useContext(StoreContext) as Store<S, A> | undefined;\n if (!store) {\n throw new Error(\n \"StoreProvider is missing in the React tree. Wrap your app with <StoreProvider store={...}>.\"\n );\n }\n return store;\n}\n\ninterface StoreProviderProps<S extends IState, A extends IAction> {\n store: Store<S, A>;\n children: ReactNode;\n}\n\nexport function StoreProvider<S extends IState, A extends IAction>({\n store,\n children,\n}: StoreProviderProps<S, A>) {\n return (\n <StoreContext.Provider value={store as unknown as Store<IState, IAction>}>\n {children}\n </StoreContext.Provider>\n );\n}\n\nexport function useStore<S extends IState>(): S {\n const store = useStoreInstance<S, IAction>();\n const [state, setState] = useState<S>(() => store.getState());\n\n useEffect(() => {\n // Subscribe to store changes and update local state.\n const unsubscribe = store.subscribe(() => {\n setState(store.getState());\n });\n return unsubscribe;\n }, [store]);\n\n return state;\n}\n\nexport function useDispatch<A extends IAction>(): Store<IState, A>[\"dispatch\"] {\n const store = useStoreInstance<IState, A>();\n // Return the store's dispatch directly; consumers can call dispatch(action).\n return store.dispatch as Store<IState, A>[\"dispatch\"];\n}\n","// metadata-store.ts\nexport class MetadataStore<T extends object, Meta extends object> {\n private readonly symbol: symbol;\n\n constructor(description: string) {\n this.symbol = Symbol(description);\n }\n\n set(target: T, meta: Meta) {\n Object.defineProperty(target, this.symbol as PropertyKey, {\n value: meta,\n writable: false,\n enumerable: false,\n });\n }\n\n get(target: T): Meta | undefined {\n return (target as Record<PropertyKey, Meta>)[this.symbol as PropertyKey];\n }\n\n has(target: T): boolean {\n return (this.symbol as PropertyKey) in target;\n }\n}\n"],"mappings":"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","names":["useStore","useDispatch","import_react","import_jsx_runtime"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/types.ts","../src/create-scoped-store.tsx","../src/freeze.ts","../src/store.ts","../src/provider.tsx","../src/metadata-store.ts"],"sourcesContent":["export * from \"./types.js\";\nexport * from \"./create-scoped-store.js\";\nexport * from \"./store.js\";\nexport * from \"./provider.js\";\nexport * from \"./metadata-store.js\";\n","export type Reducer<S, A> = (state: S, action: A) => S;\nexport type Listener = () => void;\nexport type Unsubscribe = () => void;\nexport const __noop = null;","import { createContext, useContext, useRef, useSyncExternalStore, useCallback, useEffect, useMemo } from \"react\";\nimport type { IState, IAction, Store } from \"./store.js\";\nimport { createStore } from \"./store.js\";\n\n// DEV-only tracking (no-op in production)\nconst __DEV__ = typeof process !== \"undefined\" ? process.env.NODE_ENV !== \"production\" : true;\nfunction devTrack(name: string, props?: Record<string, unknown>) {\n if (!__DEV__) return;\n try {\n const t = (globalThis as any)?.track;\n if (typeof t === \"function\") t(name, props);\n } catch {}\n}\n\n// Local microtask-based batching (no react-dom dependency)\nconst _queue = new Set<() => void>();\nlet _queued = false;\nconst _schedule = typeof queueMicrotask === \"function\"\n ? queueMicrotask\n : (fn: () => void) => Promise.resolve().then(fn);\n\nfunction enqueue(fn: () => void) {\n _queue.add(fn);\n if (_queued) return;\n _queued = true;\n _schedule(() => {\n _queued = false;\n const fns = Array.from(_queue);\n _queue.clear();\n for (const f of fns) f();\n devTrack(\"scoped:batch:flush\", { size: fns.length });\n });\n}\n\nfunction makeBatchedSubscribe(subscribe: (l: () => void) => () => void) {\n return (onChange: () => void) => {\n devTrack(\"scoped:sub:add\");\n const wrapped = () => {\n devTrack(\"scoped:notify:enqueue\");\n enqueue(onChange);\n };\n const unsubscribe = subscribe(wrapped);\n return () => {\n devTrack(\"scoped:sub:remove\");\n unsubscribe();\n };\n };\n}\n\nfunction shallowEqual(a: any, b: any) {\n if (Object.is(a, b)) return true;\n if (\n typeof a !== \"object\" ||\n a === null ||\n typeof b !== \"object\" ||\n b === null\n )\n return false;\n const ak = Object.keys(a),\n bk = Object.keys(b);\n if (ak.length !== bk.length) return false;\n for (let i = 0; i < ak.length; i++) {\n const k = ak[i] as string;\n if (\n !Object.prototype.hasOwnProperty.call(b, k) ||\n !Object.is((a as any)[k], (b as any)[k])\n )\n return false;\n }\n return true;\n}\n\nexport function createScopedStoreContext<S extends IState, A extends IAction>(\n reducer: (state: S, action: A) => S,\n initialState: S\n) {\n const Context = createContext<Store<S, A> | null>(null);\n\n // Each Provider instance gets its own store.\n const Provider = ({\n children,\n initialState: override,\n }: {\n children: React.ReactNode;\n initialState?: S;\n }) => {\n const store = useMemo(() => {\n const next = createStore(reducer, override ?? initialState);\n devTrack(\"scoped:store:create\");\n return next;\n }, [reducer, override, initialState]);\n\n useEffect(() => {\n devTrack(\"scoped:provider:mount\");\n return () => devTrack(\"scoped:provider:unmount\");\n }, []);\n\n return <Context.Provider value={store}>{children}</Context.Provider>;\n };\n\n const useStore = (): S => {\n const ctx = useContext(Context);\n if (!ctx) throw new Error(\"Store not found in context\");\n devTrack(\"scoped:useStore\");\n return useSyncExternalStore(\n makeBatchedSubscribe(ctx.subscribe),\n ctx.getState,\n ctx.getState\n );\n };\n\n const useDispatch = (): ((action: A) => void) => {\n const ctx = useContext(Context);\n if (!ctx) throw new Error(\"Dispatch not found in context\");\n return useCallback((action: A) => ctx.dispatch(action), [ctx]);\n };\n\n function useSelector<T>(\n selector: (state: S) => T,\n isEqual: (a: T, b: T) => boolean = shallowEqual\n ): T {\n const ctx = useContext(Context);\n if (!ctx) throw new Error(\"Store not found in context\");\n\n const lastRef = useRef<{ selected: T } | null>(null);\n\n const getSnapshot = () => {\n const nextSelected = selector(ctx.getState());\n const last = lastRef.current;\n if (last && isEqual(last.selected, nextSelected)) {\n devTrack(\"scoped:selector:cache-hit\");\n return last.selected;\n }\n devTrack(\"scoped:selector:cache-miss\");\n lastRef.current = { selected: nextSelected };\n return nextSelected;\n };\n\n const subscribe = (onChange: () => void) => {\n devTrack(\"scoped:selector:sub:add\");\n const unsubscribe = ctx.subscribeWithSelector(selector, (nextSelected) => {\n const last = lastRef.current;\n if (last && isEqual(last.selected, nextSelected)) {\n devTrack(\"scoped:selector:skip\");\n return;\n }\n lastRef.current = { selected: nextSelected };\n devTrack(\"scoped:selector:notify\");\n enqueue(onChange);\n }, isEqual);\n return () => {\n devTrack(\"scoped:selector:sub:remove\");\n unsubscribe();\n };\n };\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n }\n\n return {\n Context,\n Provider,\n useStore,\n useDispatch,\n useSelector,\n };\n}\n","// freeze.ts\nexport function deepFreeze<T>(obj: T, seen = new WeakSet<object>()): T {\n if (obj === null || typeof obj !== \"object\") return obj;\n const o = obj as unknown as object;\n if (seen.has(o)) return obj;\n seen.add(o);\n\n // Freeze children first\n for (const key of Object.getOwnPropertyNames(o)) {\n // @ts-expect-error index access\n const val = (o as any)[key];\n if (val && typeof val === \"object\") deepFreeze(val, seen);\n }\n // Also handle symbols (rare but safe)\n for (const sym of Object.getOwnPropertySymbols(o)) {\n // @ts-expect-error index access\n const val = (o as any)[sym];\n if (val && typeof val === \"object\") deepFreeze(val, seen);\n }\n\n return Object.freeze(obj);\n}\n","import type { Reducer, Listener } from \"./types.js\";\nimport { deepFreeze } from \"./freeze.js\";\n\nconst DEV =\n typeof import.meta !== \"undefined\"\n ? (import.meta as unknown as { env?: { DEV?: boolean } }).env?.DEV\n : process.env.NODE_ENV !== \"production\";\n\n// Lightweight DEV-only tracker\nfunction devTrack(name: string, props?: Record<string, unknown>) {\n if (!DEV) return;\n try {\n const t = (globalThis as any)?.track;\n if (typeof t === \"function\") t(name, props);\n } catch {}\n}\n\n// Allow narrower parameter types for callbacks without fighting variance\ntype BivariantListener<T> = {\n bivarianceHack(value: T): void;\n}[\"bivarianceHack\"];\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface IState {}\nexport interface IAction {\n type: string;\n}\n\nexport interface Store<S extends IState, A extends IAction> {\n getState(): S;\n dispatch(action: A): void;\n /**\n * Subscribe to all state changes.\n */\n subscribe(listener: Listener): () => void;\n /**\n * Subscribe to changes of a specific key in the state.\n */\n subscribeToKey<K extends keyof S>(\n key: K,\n listener: (value: S[K]) => void\n ): () => void;\n /**\n * Subscribe to changes in a selected value from the state.\n */\n subscribeWithSelector<T>(\n selector: (state: S) => T,\n listener: (selected: T) => void,\n isEqual?: (a: T, b: T) => boolean\n ): () => void;\n}\n\nexport function createStore<S extends IState, A extends IAction>(\n reducer: Reducer<S, A>,\n initialState: S\n): Store<S, A> {\n let state: S = DEV ? deepFreeze(initialState) : initialState;\n const listeners = new Set<Listener>();\n const keyListeners = new Map<keyof S, Set<BivariantListener<S[keyof S]>>>();\n\n interface SelectorEntry<T> {\n selector: (state: S) => T;\n listener: BivariantListener<T>;\n lastValue: T;\n isEqual?: (a: T, b: T) => boolean;\n }\n const selectorListeners = new Set<SelectorEntry<unknown>>();\n\n const getState = () => state;\n\n const dispatch = (action: A) => {\n const prevState = state;\n const nextState = reducer(state, action);\n\n if (DEV) deepFreeze(nextState);\n\n // Track the inbound action\n devTrack(\"store:dispatch\", { type: action?.type });\n\n // Distinct-until-changed: if the reducer returns the same reference,\n // skip all notifications (prevents unnecessary re-renders).\n if (Object.is(prevState, nextState)) {\n state = nextState; // keep any identity guarantees from reducer\n devTrack(\"store:no-op\", { type: action?.type });\n return;\n }\n\n state = nextState;\n\n // Compute changed keys (shallow) for diagnostics\n let changedKeys: (keyof S)[] | undefined;\n if (DEV) {\n changedKeys = Object.keys(nextState as Record<string, unknown>)\n .filter((k) => !Object.is((prevState as any)[k], (nextState as any)[k])) as (keyof S)[];\n devTrack(\"store:state-changed\", { type: action?.type, changedKeys });\n }\n\n // Notify global listeners (iterate over a snapshot so unsubscribe during\n // notify does not skip the next listener)\n const globalSnapshot = [...listeners];\n devTrack(\"store:notify:all\", { listeners: globalSnapshot.length });\n let firstError: unknown;\n for (const listener of globalSnapshot) {\n try {\n listener();\n } catch (err) {\n if (!firstError) firstError = err;\n }\n }\n\n // Notify key listeners only when that key actually changed (Object.is)\n for (const [key, set] of keyListeners.entries()) {\n if (!Object.is(prevState[key], state[key])) {\n devTrack(\"store:notify:key\", { key: String(key), listeners: set.size });\n for (const listener of [...set]) {\n try {\n listener(state[key]);\n } catch (err) {\n if (!firstError) firstError = err;\n }\n }\n }\n }\n\n // Notify selector listeners only when selected value changed (Object.is)\n let selNotifies = 0;\n for (const entry of [...selectorListeners]) {\n const nextValue = (entry.selector as (s: S) => unknown)(state);\n const equal = (entry.isEqual as ((a: unknown, b: unknown) => boolean) | undefined) ?? Object.is;\n let isSame = false;\n try {\n isSame = equal(entry.lastValue, nextValue);\n } catch (err) {\n if (!firstError) firstError = err;\n continue;\n }\n if (!isSame) {\n entry.lastValue = nextValue as unknown;\n try {\n (entry.listener as (v: unknown) => void)(nextValue);\n } catch (err) {\n if (!firstError) firstError = err;\n }\n selNotifies++;\n }\n }\n devTrack(\"store:notify:selector\", { listeners: selNotifies });\n if (firstError) throw firstError;\n };\n\n const subscribe = (listener: Listener) => {\n listeners.add(listener);\n devTrack(\"store:sub:all:add\", { size: listeners.size });\n return () => {\n listeners.delete(listener);\n devTrack(\"store:sub:all:remove\", { size: listeners.size });\n };\n };\n\n const subscribeToKey = <K extends keyof S>(\n key: K,\n listener: (value: S[K]) => void\n ) => {\n const set =\n keyListeners.get(key) ?? new Set<BivariantListener<S[keyof S]>>();\n set.add(listener as unknown as BivariantListener<S[keyof S]>);\n keyListeners.set(key, set);\n devTrack(\"store:sub:key:add\", { key: String(key), size: set.size });\n return () => {\n set.delete(listener as unknown as BivariantListener<S[keyof S]>);\n if (set.size === 0) keyListeners.delete(key);\n devTrack(\"store:sub:key:remove\", { key: String(key), size: set.size });\n };\n };\n\n const subscribeWithSelector = <T>(\n selector: (state: S) => T,\n listener: (selected: T) => void,\n isEqual?: (a: T, b: T) => boolean\n ) => {\n const entry: SelectorEntry<T> = {\n selector,\n listener: listener as BivariantListener<T>,\n lastValue: selector(state),\n isEqual,\n };\n selectorListeners.add(entry as unknown as SelectorEntry<unknown>);\n devTrack(\"store:sub:selector:add\", { size: selectorListeners.size });\n return () => {\n selectorListeners.delete(entry as unknown as SelectorEntry<unknown>);\n devTrack(\"store:sub:selector:remove\", { size: selectorListeners.size });\n };\n };\n\n return {\n getState,\n dispatch,\n subscribe,\n subscribeToKey,\n subscribeWithSelector,\n };\n}\n","import React, { createContext, useContext, useEffect, useSyncExternalStore } from \"react\";\nimport type { ReactNode } from \"react\";\nimport type { Store, IState, IAction } from \"./store.js\";\n\n// DEV-only tracking (no-op in production)\nconst __DEV__ = typeof process !== \"undefined\" ? process.env.NODE_ENV !== \"production\" : true;\nfunction devTrack(name: string, props?: Record<string, unknown>) {\n if (!__DEV__) return;\n try {\n const t = (globalThis as any)?.track;\n if (typeof t === \"function\") t(name, props);\n } catch {}\n}\n\nconst StoreContext = createContext<Store<IState, IAction> | undefined>(undefined);\n\nfunction useStoreInstance<S extends IState, A extends IAction>(): Store<S, A> {\n const store = useContext(StoreContext) as Store<S, A> | undefined;\n if (!store) {\n devTrack(\"store:provider:missing\");\n throw new Error(\n \"StoreProvider is missing in the React tree. Wrap your app with <StoreProvider store={...}>.\"\n );\n }\n return store;\n}\n\ninterface StoreProviderProps<S extends IState, A extends IAction> {\n store: Store<S, A>;\n children: ReactNode;\n}\n\nexport function StoreProvider<S extends IState, A extends IAction>({\n store,\n children,\n}: StoreProviderProps<S, A>) {\n useEffect(() => {\n devTrack(\"store:provider:mount\", { hasStore: !!store });\n return () => devTrack(\"store:provider:unmount\");\n }, [store]);\n return (\n <StoreContext.Provider value={store as unknown as Store<IState, IAction>}>\n {children}\n </StoreContext.Provider>\n );\n}\n\nexport function useStore<S extends IState>(): S {\n const store = useStoreInstance<S, IAction>();\n return useSyncExternalStore(\n (onStoreChange) => {\n devTrack(\"store:react:subscribe\");\n const unsubscribe = store.subscribe(() => {\n devTrack(\"store:react:notify\");\n onStoreChange();\n });\n return () => {\n devTrack(\"store:react:unsubscribe\");\n unsubscribe();\n };\n },\n store.getState,\n store.getState\n );\n}\n\nexport function useDispatch<A extends IAction>(): Store<IState, A>[\"dispatch\"] {\n const store = useStoreInstance<IState, A>();\n // Return the store's dispatch directly; consumers can call dispatch(action).\n return store.dispatch as Store<IState, A>[\"dispatch\"];\n}\n","// metadata-store.ts\nexport class MetadataStore<T extends object, Meta extends object> {\n private readonly symbol: symbol;\n\n constructor(description: string) {\n this.symbol = Symbol(description);\n }\n\n set(target: T, meta: Meta) {\n Object.defineProperty(target, this.symbol as PropertyKey, {\n value: meta,\n writable: false,\n enumerable: false,\n });\n }\n\n get(target: T): Meta | undefined {\n return (target as Record<PropertyKey, Meta>)[this.symbol as PropertyKey];\n }\n\n has(target: T): boolean {\n return (this.symbol as PropertyKey) in target;\n }\n}\n"],"mappings":"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","names":["devTrack","useStore","useDispatch","import_react","import_jsx_runtime","__DEV__","devTrack"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -26,7 +26,7 @@ interface Store<S extends IState, A extends IAction> {
|
|
|
26
26
|
/**
|
|
27
27
|
* Subscribe to changes in a selected value from the state.
|
|
28
28
|
*/
|
|
29
|
-
subscribeWithSelector<T>(selector: (state: S) => T, listener: (selected: T) => void): () => void;
|
|
29
|
+
subscribeWithSelector<T>(selector: (state: S) => T, listener: (selected: T) => void, isEqual?: (a: T, b: T) => boolean): () => void;
|
|
30
30
|
}
|
|
31
31
|
declare function createStore<S extends IState, A extends IAction>(reducer: Reducer<S, A>, initialState: S): Store<S, A>;
|
|
32
32
|
|
package/dist/index.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ interface Store<S extends IState, A extends IAction> {
|
|
|
26
26
|
/**
|
|
27
27
|
* Subscribe to changes in a selected value from the state.
|
|
28
28
|
*/
|
|
29
|
-
subscribeWithSelector<T>(selector: (state: S) => T, listener: (selected: T) => void): () => void;
|
|
29
|
+
subscribeWithSelector<T>(selector: (state: S) => T, listener: (selected: T) => void, isEqual?: (a: T, b: T) => boolean): () => void;
|
|
30
30
|
}
|
|
31
31
|
declare function createStore<S extends IState, A extends IAction>(reducer: Reducer<S, A>, initialState: S): Store<S, A>;
|
|
32
32
|
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
var __noop = null;
|
|
3
3
|
|
|
4
4
|
// src/create-scoped-store.tsx
|
|
5
|
-
import { createContext, useContext, useRef, useSyncExternalStore, useCallback } from "react";
|
|
5
|
+
import { createContext, useContext, useRef, useSyncExternalStore, useCallback, useEffect, useMemo } from "react";
|
|
6
6
|
|
|
7
7
|
// src/freeze.ts
|
|
8
8
|
function deepFreeze(obj, seen = /* @__PURE__ */ new WeakSet()) {
|
|
@@ -23,6 +23,14 @@ function deepFreeze(obj, seen = /* @__PURE__ */ new WeakSet()) {
|
|
|
23
23
|
|
|
24
24
|
// src/store.ts
|
|
25
25
|
var DEV = typeof import.meta !== "undefined" ? import.meta.env?.DEV : process.env.NODE_ENV !== "production";
|
|
26
|
+
function devTrack(name, props) {
|
|
27
|
+
if (!DEV) return;
|
|
28
|
+
try {
|
|
29
|
+
const t = globalThis?.track;
|
|
30
|
+
if (typeof t === "function") t(name, props);
|
|
31
|
+
} catch {
|
|
32
|
+
}
|
|
33
|
+
}
|
|
26
34
|
function createStore(reducer, initialState) {
|
|
27
35
|
let state = DEV ? deepFreeze(initialState) : initialState;
|
|
28
36
|
const listeners = /* @__PURE__ */ new Set();
|
|
@@ -33,49 +41,95 @@ function createStore(reducer, initialState) {
|
|
|
33
41
|
const prevState = state;
|
|
34
42
|
const nextState = reducer(state, action);
|
|
35
43
|
if (DEV) deepFreeze(nextState);
|
|
44
|
+
devTrack("store:dispatch", { type: action?.type });
|
|
36
45
|
if (Object.is(prevState, nextState)) {
|
|
37
46
|
state = nextState;
|
|
47
|
+
devTrack("store:no-op", { type: action?.type });
|
|
38
48
|
return;
|
|
39
49
|
}
|
|
40
50
|
state = nextState;
|
|
41
|
-
|
|
51
|
+
let changedKeys;
|
|
52
|
+
if (DEV) {
|
|
53
|
+
changedKeys = Object.keys(nextState).filter((k) => !Object.is(prevState[k], nextState[k]));
|
|
54
|
+
devTrack("store:state-changed", { type: action?.type, changedKeys });
|
|
55
|
+
}
|
|
56
|
+
const globalSnapshot = [...listeners];
|
|
57
|
+
devTrack("store:notify:all", { listeners: globalSnapshot.length });
|
|
58
|
+
let firstError;
|
|
59
|
+
for (const listener of globalSnapshot) {
|
|
60
|
+
try {
|
|
61
|
+
listener();
|
|
62
|
+
} catch (err) {
|
|
63
|
+
if (!firstError) firstError = err;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
42
66
|
for (const [key, set] of keyListeners.entries()) {
|
|
43
67
|
if (!Object.is(prevState[key], state[key])) {
|
|
44
|
-
|
|
68
|
+
devTrack("store:notify:key", { key: String(key), listeners: set.size });
|
|
69
|
+
for (const listener of [...set]) {
|
|
70
|
+
try {
|
|
71
|
+
listener(state[key]);
|
|
72
|
+
} catch (err) {
|
|
73
|
+
if (!firstError) firstError = err;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
45
76
|
}
|
|
46
77
|
}
|
|
47
|
-
|
|
78
|
+
let selNotifies = 0;
|
|
79
|
+
for (const entry of [...selectorListeners]) {
|
|
48
80
|
const nextValue = entry.selector(state);
|
|
49
|
-
|
|
81
|
+
const equal = entry.isEqual ?? Object.is;
|
|
82
|
+
let isSame = false;
|
|
83
|
+
try {
|
|
84
|
+
isSame = equal(entry.lastValue, nextValue);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
if (!firstError) firstError = err;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (!isSame) {
|
|
50
90
|
entry.lastValue = nextValue;
|
|
51
|
-
|
|
91
|
+
try {
|
|
92
|
+
entry.listener(nextValue);
|
|
93
|
+
} catch (err) {
|
|
94
|
+
if (!firstError) firstError = err;
|
|
95
|
+
}
|
|
96
|
+
selNotifies++;
|
|
52
97
|
}
|
|
53
|
-
}
|
|
98
|
+
}
|
|
99
|
+
devTrack("store:notify:selector", { listeners: selNotifies });
|
|
100
|
+
if (firstError) throw firstError;
|
|
54
101
|
};
|
|
55
102
|
const subscribe = (listener) => {
|
|
56
103
|
listeners.add(listener);
|
|
104
|
+
devTrack("store:sub:all:add", { size: listeners.size });
|
|
57
105
|
return () => {
|
|
58
106
|
listeners.delete(listener);
|
|
107
|
+
devTrack("store:sub:all:remove", { size: listeners.size });
|
|
59
108
|
};
|
|
60
109
|
};
|
|
61
110
|
const subscribeToKey = (key, listener) => {
|
|
62
111
|
const set = keyListeners.get(key) ?? /* @__PURE__ */ new Set();
|
|
63
112
|
set.add(listener);
|
|
64
113
|
keyListeners.set(key, set);
|
|
114
|
+
devTrack("store:sub:key:add", { key: String(key), size: set.size });
|
|
65
115
|
return () => {
|
|
66
116
|
set.delete(listener);
|
|
67
117
|
if (set.size === 0) keyListeners.delete(key);
|
|
118
|
+
devTrack("store:sub:key:remove", { key: String(key), size: set.size });
|
|
68
119
|
};
|
|
69
120
|
};
|
|
70
|
-
const subscribeWithSelector = (selector, listener) => {
|
|
121
|
+
const subscribeWithSelector = (selector, listener, isEqual) => {
|
|
71
122
|
const entry = {
|
|
72
123
|
selector,
|
|
73
124
|
listener,
|
|
74
|
-
lastValue: selector(state)
|
|
125
|
+
lastValue: selector(state),
|
|
126
|
+
isEqual
|
|
75
127
|
};
|
|
76
128
|
selectorListeners.add(entry);
|
|
129
|
+
devTrack("store:sub:selector:add", { size: selectorListeners.size });
|
|
77
130
|
return () => {
|
|
78
131
|
selectorListeners.delete(entry);
|
|
132
|
+
devTrack("store:sub:selector:remove", { size: selectorListeners.size });
|
|
79
133
|
};
|
|
80
134
|
};
|
|
81
135
|
return {
|
|
@@ -89,6 +143,15 @@ function createStore(reducer, initialState) {
|
|
|
89
143
|
|
|
90
144
|
// src/create-scoped-store.tsx
|
|
91
145
|
import { jsx } from "react/jsx-runtime";
|
|
146
|
+
var __DEV__ = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
|
|
147
|
+
function devTrack2(name, props) {
|
|
148
|
+
if (!__DEV__) return;
|
|
149
|
+
try {
|
|
150
|
+
const t = globalThis?.track;
|
|
151
|
+
if (typeof t === "function") t(name, props);
|
|
152
|
+
} catch {
|
|
153
|
+
}
|
|
154
|
+
}
|
|
92
155
|
var _queue = /* @__PURE__ */ new Set();
|
|
93
156
|
var _queued = false;
|
|
94
157
|
var _schedule = typeof queueMicrotask === "function" ? queueMicrotask : (fn) => Promise.resolve().then(fn);
|
|
@@ -101,10 +164,22 @@ function enqueue(fn) {
|
|
|
101
164
|
const fns = Array.from(_queue);
|
|
102
165
|
_queue.clear();
|
|
103
166
|
for (const f of fns) f();
|
|
167
|
+
devTrack2("scoped:batch:flush", { size: fns.length });
|
|
104
168
|
});
|
|
105
169
|
}
|
|
106
170
|
function makeBatchedSubscribe(subscribe) {
|
|
107
|
-
return (onChange) =>
|
|
171
|
+
return (onChange) => {
|
|
172
|
+
devTrack2("scoped:sub:add");
|
|
173
|
+
const wrapped = () => {
|
|
174
|
+
devTrack2("scoped:notify:enqueue");
|
|
175
|
+
enqueue(onChange);
|
|
176
|
+
};
|
|
177
|
+
const unsubscribe = subscribe(wrapped);
|
|
178
|
+
return () => {
|
|
179
|
+
devTrack2("scoped:sub:remove");
|
|
180
|
+
unsubscribe();
|
|
181
|
+
};
|
|
182
|
+
};
|
|
108
183
|
}
|
|
109
184
|
function shallowEqual(a, b) {
|
|
110
185
|
if (Object.is(a, b)) return true;
|
|
@@ -125,15 +200,21 @@ function createScopedStoreContext(reducer, initialState) {
|
|
|
125
200
|
children,
|
|
126
201
|
initialState: override
|
|
127
202
|
}) => {
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
203
|
+
const store = useMemo(() => {
|
|
204
|
+
const next = createStore(reducer, override ?? initialState);
|
|
205
|
+
devTrack2("scoped:store:create");
|
|
206
|
+
return next;
|
|
207
|
+
}, [reducer, override, initialState]);
|
|
208
|
+
useEffect(() => {
|
|
209
|
+
devTrack2("scoped:provider:mount");
|
|
210
|
+
return () => devTrack2("scoped:provider:unmount");
|
|
211
|
+
}, []);
|
|
212
|
+
return /* @__PURE__ */ jsx(Context.Provider, { value: store, children });
|
|
133
213
|
};
|
|
134
214
|
const useStore2 = () => {
|
|
135
215
|
const ctx = useContext(Context);
|
|
136
216
|
if (!ctx) throw new Error("Store not found in context");
|
|
217
|
+
devTrack2("scoped:useStore");
|
|
137
218
|
return useSyncExternalStore(
|
|
138
219
|
makeBatchedSubscribe(ctx.subscribe),
|
|
139
220
|
ctx.getState,
|
|
@@ -148,19 +229,36 @@ function createScopedStoreContext(reducer, initialState) {
|
|
|
148
229
|
function useSelector(selector, isEqual = shallowEqual) {
|
|
149
230
|
const ctx = useContext(Context);
|
|
150
231
|
if (!ctx) throw new Error("Store not found in context");
|
|
151
|
-
const state = useSyncExternalStore(
|
|
152
|
-
makeBatchedSubscribe(ctx.subscribe),
|
|
153
|
-
ctx.getState,
|
|
154
|
-
ctx.getState
|
|
155
|
-
);
|
|
156
232
|
const lastRef = useRef(null);
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
233
|
+
const getSnapshot = () => {
|
|
234
|
+
const nextSelected = selector(ctx.getState());
|
|
235
|
+
const last = lastRef.current;
|
|
236
|
+
if (last && isEqual(last.selected, nextSelected)) {
|
|
237
|
+
devTrack2("scoped:selector:cache-hit");
|
|
238
|
+
return last.selected;
|
|
239
|
+
}
|
|
240
|
+
devTrack2("scoped:selector:cache-miss");
|
|
241
|
+
lastRef.current = { selected: nextSelected };
|
|
242
|
+
return nextSelected;
|
|
243
|
+
};
|
|
244
|
+
const subscribe = (onChange) => {
|
|
245
|
+
devTrack2("scoped:selector:sub:add");
|
|
246
|
+
const unsubscribe = ctx.subscribeWithSelector(selector, (nextSelected) => {
|
|
247
|
+
const last = lastRef.current;
|
|
248
|
+
if (last && isEqual(last.selected, nextSelected)) {
|
|
249
|
+
devTrack2("scoped:selector:skip");
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
lastRef.current = { selected: nextSelected };
|
|
253
|
+
devTrack2("scoped:selector:notify");
|
|
254
|
+
enqueue(onChange);
|
|
255
|
+
}, isEqual);
|
|
256
|
+
return () => {
|
|
257
|
+
devTrack2("scoped:selector:sub:remove");
|
|
258
|
+
unsubscribe();
|
|
259
|
+
};
|
|
260
|
+
};
|
|
261
|
+
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
164
262
|
}
|
|
165
263
|
return {
|
|
166
264
|
Context,
|
|
@@ -172,12 +270,22 @@ function createScopedStoreContext(reducer, initialState) {
|
|
|
172
270
|
}
|
|
173
271
|
|
|
174
272
|
// src/provider.tsx
|
|
175
|
-
import { createContext as createContext2, useContext as useContext2, useEffect,
|
|
273
|
+
import { createContext as createContext2, useContext as useContext2, useEffect as useEffect2, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
176
274
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
275
|
+
var __DEV__2 = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
|
|
276
|
+
function devTrack3(name, props) {
|
|
277
|
+
if (!__DEV__2) return;
|
|
278
|
+
try {
|
|
279
|
+
const t = globalThis?.track;
|
|
280
|
+
if (typeof t === "function") t(name, props);
|
|
281
|
+
} catch {
|
|
282
|
+
}
|
|
283
|
+
}
|
|
177
284
|
var StoreContext = createContext2(void 0);
|
|
178
285
|
function useStoreInstance() {
|
|
179
286
|
const store = useContext2(StoreContext);
|
|
180
287
|
if (!store) {
|
|
288
|
+
devTrack3("store:provider:missing");
|
|
181
289
|
throw new Error(
|
|
182
290
|
"StoreProvider is missing in the React tree. Wrap your app with <StoreProvider store={...}>."
|
|
183
291
|
);
|
|
@@ -188,18 +296,29 @@ function StoreProvider({
|
|
|
188
296
|
store,
|
|
189
297
|
children
|
|
190
298
|
}) {
|
|
299
|
+
useEffect2(() => {
|
|
300
|
+
devTrack3("store:provider:mount", { hasStore: !!store });
|
|
301
|
+
return () => devTrack3("store:provider:unmount");
|
|
302
|
+
}, [store]);
|
|
191
303
|
return /* @__PURE__ */ jsx2(StoreContext.Provider, { value: store, children });
|
|
192
304
|
}
|
|
193
305
|
function useStore() {
|
|
194
306
|
const store = useStoreInstance();
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
307
|
+
return useSyncExternalStore2(
|
|
308
|
+
(onStoreChange) => {
|
|
309
|
+
devTrack3("store:react:subscribe");
|
|
310
|
+
const unsubscribe = store.subscribe(() => {
|
|
311
|
+
devTrack3("store:react:notify");
|
|
312
|
+
onStoreChange();
|
|
313
|
+
});
|
|
314
|
+
return () => {
|
|
315
|
+
devTrack3("store:react:unsubscribe");
|
|
316
|
+
unsubscribe();
|
|
317
|
+
};
|
|
318
|
+
},
|
|
319
|
+
store.getState,
|
|
320
|
+
store.getState
|
|
321
|
+
);
|
|
203
322
|
}
|
|
204
323
|
function useDispatch() {
|
|
205
324
|
const store = useStoreInstance();
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts","../src/create-scoped-store.tsx","../src/freeze.ts","../src/store.ts","../src/provider.tsx","../src/metadata-store.ts"],"sourcesContent":["export type Reducer<S, A> = (state: S, action: A) => S;\nexport type Listener = () => void;\nexport type Unsubscribe = () => void;\nexport const __noop = null;","import { createContext, useContext, useRef, useSyncExternalStore, useCallback } from \"react\";\nimport type { IState, IAction, Store } from \"./store.js\";\nimport { createStore } from \"./store.js\";\n\n// Local microtask-based batching (no react-dom dependency)\nconst _queue = new Set<() => void>();\nlet _queued = false;\nconst _schedule = typeof queueMicrotask === \"function\"\n ? queueMicrotask\n : (fn: () => void) => Promise.resolve().then(fn);\n\nfunction enqueue(fn: () => void) {\n _queue.add(fn);\n if (_queued) return;\n _queued = true;\n _schedule(() => {\n _queued = false;\n const fns = Array.from(_queue);\n _queue.clear();\n for (const f of fns) f();\n });\n}\n\nfunction makeBatchedSubscribe(subscribe: (l: () => void) => () => void) {\n return (onChange: () => void) => subscribe(() => enqueue(onChange));\n}\n\nfunction shallowEqual(a: any, b: any) {\n if (Object.is(a, b)) return true;\n if (\n typeof a !== \"object\" ||\n a === null ||\n typeof b !== \"object\" ||\n b === null\n )\n return false;\n const ak = Object.keys(a),\n bk = Object.keys(b);\n if (ak.length !== bk.length) return false;\n for (let i = 0; i < ak.length; i++) {\n const k = ak[i] as string;\n if (\n !Object.prototype.hasOwnProperty.call(b, k) ||\n !Object.is((a as any)[k], (b as any)[k])\n )\n return false;\n }\n return true;\n}\n\nexport function createScopedStoreContext<S extends IState, A extends IAction>(\n reducer: (state: S, action: A) => S,\n initialState: S\n) {\n const Context = createContext<Store<S, A> | null>(null);\n\n // Each Provider instance gets its own store.\n const Provider = ({\n children,\n initialState: override,\n }: {\n children: React.ReactNode;\n initialState?: S;\n }) => {\n const storeRef = useRef<Store<S, A> | null>(null);\n if (!storeRef.current) {\n storeRef.current = createStore(reducer, override ?? initialState);\n }\n return <Context.Provider value={storeRef.current}>{children}</Context.Provider>;\n };\n\n const useStore = (): S => {\n const ctx = useContext(Context);\n if (!ctx) throw new Error(\"Store not found in context\");\n return useSyncExternalStore(\n makeBatchedSubscribe(ctx.subscribe),\n ctx.getState,\n ctx.getState\n );\n };\n\n const useDispatch = (): ((action: A) => void) => {\n const ctx = useContext(Context);\n if (!ctx) throw new Error(\"Dispatch not found in context\");\n return useCallback((action: A) => ctx.dispatch(action), [ctx]);\n };\n\n function useSelector<T>(\n selector: (state: S) => T,\n isEqual: (a: T, b: T) => boolean = shallowEqual\n ): T {\n const ctx = useContext(Context);\n if (!ctx) throw new Error(\"Store not found in context\");\n\n // Subscribe to the raw state snapshot (stable reference until a dispatch)\n const state = useSyncExternalStore(\n makeBatchedSubscribe(ctx.subscribe),\n ctx.getState,\n ctx.getState\n );\n\n // Cache the selected slice per state snapshot to avoid returning fresh objects during render\n const lastRef = useRef<{ state: S; selected: T } | null>(null);\n const last = lastRef.current;\n const nextSelected = selector(state);\n\n if (last && last.state === state && isEqual(last.selected, nextSelected)) {\n return last.selected; // return cached reference to satisfy getSnapshot caching\n }\n\n lastRef.current = { state, selected: nextSelected };\n return nextSelected;\n }\n\n return {\n Context,\n Provider,\n useStore,\n useDispatch,\n useSelector,\n };\n}\n","// freeze.ts\nexport function deepFreeze<T>(obj: T, seen = new WeakSet<object>()): T {\n if (obj === null || typeof obj !== \"object\") return obj;\n const o = obj as unknown as object;\n if (seen.has(o)) return obj;\n seen.add(o);\n\n // Freeze children first\n for (const key of Object.getOwnPropertyNames(o)) {\n // @ts-expect-error index access\n const val = (o as any)[key];\n if (val && typeof val === \"object\") deepFreeze(val, seen);\n }\n // Also handle symbols (rare but safe)\n for (const sym of Object.getOwnPropertySymbols(o)) {\n // @ts-expect-error index access\n const val = (o as any)[sym];\n if (val && typeof val === \"object\") deepFreeze(val, seen);\n }\n\n return Object.freeze(obj);\n}\n","import type { Reducer, Listener } from \"./types.js\";\nimport { deepFreeze } from \"./freeze.js\";\n\nconst DEV =\n typeof import.meta !== \"undefined\"\n ? (import.meta as unknown as { env?: { DEV?: boolean } }).env?.DEV\n : process.env.NODE_ENV !== \"production\";\n\n// Allow narrower parameter types for callbacks without fighting variance\ntype BivariantListener<T> = {\n bivarianceHack(value: T): void;\n}[\"bivarianceHack\"];\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface IState {}\nexport interface IAction {\n type: string;\n}\n\nexport interface Store<S extends IState, A extends IAction> {\n getState(): S;\n dispatch(action: A): void;\n /**\n * Subscribe to all state changes.\n */\n subscribe(listener: Listener): () => void;\n /**\n * Subscribe to changes of a specific key in the state.\n */\n subscribeToKey<K extends keyof S>(\n key: K,\n listener: (value: S[K]) => void\n ): () => void;\n /**\n * Subscribe to changes in a selected value from the state.\n */\n subscribeWithSelector<T>(\n selector: (state: S) => T,\n listener: (selected: T) => void\n ): () => void;\n}\n\nexport function createStore<S extends IState, A extends IAction>(\n reducer: Reducer<S, A>,\n initialState: S\n): Store<S, A> {\n let state: S = DEV ? deepFreeze(initialState) : initialState;\n const listeners = new Set<Listener>();\n const keyListeners = new Map<keyof S, Set<BivariantListener<S[keyof S]>>>();\n\n interface SelectorEntry<T> {\n selector: (state: S) => T;\n listener: BivariantListener<T>;\n lastValue: T;\n }\n const selectorListeners = new Set<SelectorEntry<unknown>>();\n\n const getState = () => state;\n\n const dispatch = (action: A) => {\n const prevState = state;\n const nextState = reducer(state, action);\n \n if (DEV) deepFreeze(nextState);\n \n // Distinct-until-changed: if the reducer returns the same reference,\n // skip all notifications (prevents unnecessary re-renders).\n if (Object.is(prevState, nextState)) {\n state = nextState; // keep any identity guarantees from reducer\n return;\n }\n\n state = nextState;\n\n // Notify global listeners (iterate over a snapshot so unsubscribe during\n // notify does not skip the next listener)\n for (const listener of [...listeners]) listener();\n\n // Notify key listeners only when that key actually changed (Object.is)\n for (const [key, set] of keyListeners.entries()) {\n if (!Object.is(prevState[key], state[key])) {\n for (const listener of [...set]) listener(state[key]);\n }\n }\n\n // Notify selector listeners only when selected value changed (Object.is)\n selectorListeners.forEach((entry) => {\n const nextValue = (entry.selector as (s: S) => unknown)(state);\n if (!Object.is(entry.lastValue, nextValue)) {\n entry.lastValue = nextValue as unknown;\n (entry.listener as (v: unknown) => void)(nextValue);\n }\n });\n };\n\n const subscribe = (listener: Listener) => {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n };\n\n const subscribeToKey = <K extends keyof S>(\n key: K,\n listener: (value: S[K]) => void\n ) => {\n const set =\n keyListeners.get(key) ?? new Set<BivariantListener<S[keyof S]>>();\n set.add(listener as unknown as BivariantListener<S[keyof S]>);\n keyListeners.set(key, set);\n return () => {\n set.delete(listener as unknown as BivariantListener<S[keyof S]>);\n if (set.size === 0) keyListeners.delete(key);\n };\n };\n\n const subscribeWithSelector = <T>(\n selector: (state: S) => T,\n listener: (selected: T) => void\n ) => {\n const entry: SelectorEntry<T> = {\n selector,\n listener: listener as BivariantListener<T>,\n lastValue: selector(state),\n };\n selectorListeners.add(entry as unknown as SelectorEntry<unknown>);\n return () => {\n selectorListeners.delete(entry as unknown as SelectorEntry<unknown>);\n };\n };\n\n return {\n getState,\n dispatch,\n subscribe,\n subscribeToKey,\n subscribeWithSelector,\n };\n}\n","import React, { createContext, useContext, useEffect, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport type { Store, IState, IAction } from \"./store.js\";\n\nconst StoreContext = createContext<Store<IState, IAction> | undefined>(undefined);\n\nfunction useStoreInstance<S extends IState, A extends IAction>(): Store<S, A> {\n const store = useContext(StoreContext) as Store<S, A> | undefined;\n if (!store) {\n throw new Error(\n \"StoreProvider is missing in the React tree. Wrap your app with <StoreProvider store={...}>.\"\n );\n }\n return store;\n}\n\ninterface StoreProviderProps<S extends IState, A extends IAction> {\n store: Store<S, A>;\n children: ReactNode;\n}\n\nexport function StoreProvider<S extends IState, A extends IAction>({\n store,\n children,\n}: StoreProviderProps<S, A>) {\n return (\n <StoreContext.Provider value={store as unknown as Store<IState, IAction>}>\n {children}\n </StoreContext.Provider>\n );\n}\n\nexport function useStore<S extends IState>(): S {\n const store = useStoreInstance<S, IAction>();\n const [state, setState] = useState<S>(() => store.getState());\n\n useEffect(() => {\n // Subscribe to store changes and update local state.\n const unsubscribe = store.subscribe(() => {\n setState(store.getState());\n });\n return unsubscribe;\n }, [store]);\n\n return state;\n}\n\nexport function useDispatch<A extends IAction>(): Store<IState, A>[\"dispatch\"] {\n const store = useStoreInstance<IState, A>();\n // Return the store's dispatch directly; consumers can call dispatch(action).\n return store.dispatch as Store<IState, A>[\"dispatch\"];\n}\n","// metadata-store.ts\nexport class MetadataStore<T extends object, Meta extends object> {\n private readonly symbol: symbol;\n\n constructor(description: string) {\n this.symbol = Symbol(description);\n }\n\n set(target: T, meta: Meta) {\n Object.defineProperty(target, this.symbol as PropertyKey, {\n value: meta,\n writable: false,\n enumerable: false,\n });\n }\n\n get(target: T): Meta | undefined {\n return (target as Record<PropertyKey, Meta>)[this.symbol as PropertyKey];\n }\n\n has(target: T): boolean {\n return (this.symbol as PropertyKey) in target;\n }\n}\n"],"mappings":"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","names":["useStore","useDispatch","createContext","useContext","jsx"]}
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/create-scoped-store.tsx","../src/freeze.ts","../src/store.ts","../src/provider.tsx","../src/metadata-store.ts"],"sourcesContent":["export type Reducer<S, A> = (state: S, action: A) => S;\nexport type Listener = () => void;\nexport type Unsubscribe = () => void;\nexport const __noop = null;","import { createContext, useContext, useRef, useSyncExternalStore, useCallback, useEffect, useMemo } from \"react\";\nimport type { IState, IAction, Store } from \"./store.js\";\nimport { createStore } from \"./store.js\";\n\n// DEV-only tracking (no-op in production)\nconst __DEV__ = typeof process !== \"undefined\" ? process.env.NODE_ENV !== \"production\" : true;\nfunction devTrack(name: string, props?: Record<string, unknown>) {\n if (!__DEV__) return;\n try {\n const t = (globalThis as any)?.track;\n if (typeof t === \"function\") t(name, props);\n } catch {}\n}\n\n// Local microtask-based batching (no react-dom dependency)\nconst _queue = new Set<() => void>();\nlet _queued = false;\nconst _schedule = typeof queueMicrotask === \"function\"\n ? queueMicrotask\n : (fn: () => void) => Promise.resolve().then(fn);\n\nfunction enqueue(fn: () => void) {\n _queue.add(fn);\n if (_queued) return;\n _queued = true;\n _schedule(() => {\n _queued = false;\n const fns = Array.from(_queue);\n _queue.clear();\n for (const f of fns) f();\n devTrack(\"scoped:batch:flush\", { size: fns.length });\n });\n}\n\nfunction makeBatchedSubscribe(subscribe: (l: () => void) => () => void) {\n return (onChange: () => void) => {\n devTrack(\"scoped:sub:add\");\n const wrapped = () => {\n devTrack(\"scoped:notify:enqueue\");\n enqueue(onChange);\n };\n const unsubscribe = subscribe(wrapped);\n return () => {\n devTrack(\"scoped:sub:remove\");\n unsubscribe();\n };\n };\n}\n\nfunction shallowEqual(a: any, b: any) {\n if (Object.is(a, b)) return true;\n if (\n typeof a !== \"object\" ||\n a === null ||\n typeof b !== \"object\" ||\n b === null\n )\n return false;\n const ak = Object.keys(a),\n bk = Object.keys(b);\n if (ak.length !== bk.length) return false;\n for (let i = 0; i < ak.length; i++) {\n const k = ak[i] as string;\n if (\n !Object.prototype.hasOwnProperty.call(b, k) ||\n !Object.is((a as any)[k], (b as any)[k])\n )\n return false;\n }\n return true;\n}\n\nexport function createScopedStoreContext<S extends IState, A extends IAction>(\n reducer: (state: S, action: A) => S,\n initialState: S\n) {\n const Context = createContext<Store<S, A> | null>(null);\n\n // Each Provider instance gets its own store.\n const Provider = ({\n children,\n initialState: override,\n }: {\n children: React.ReactNode;\n initialState?: S;\n }) => {\n const store = useMemo(() => {\n const next = createStore(reducer, override ?? initialState);\n devTrack(\"scoped:store:create\");\n return next;\n }, [reducer, override, initialState]);\n\n useEffect(() => {\n devTrack(\"scoped:provider:mount\");\n return () => devTrack(\"scoped:provider:unmount\");\n }, []);\n\n return <Context.Provider value={store}>{children}</Context.Provider>;\n };\n\n const useStore = (): S => {\n const ctx = useContext(Context);\n if (!ctx) throw new Error(\"Store not found in context\");\n devTrack(\"scoped:useStore\");\n return useSyncExternalStore(\n makeBatchedSubscribe(ctx.subscribe),\n ctx.getState,\n ctx.getState\n );\n };\n\n const useDispatch = (): ((action: A) => void) => {\n const ctx = useContext(Context);\n if (!ctx) throw new Error(\"Dispatch not found in context\");\n return useCallback((action: A) => ctx.dispatch(action), [ctx]);\n };\n\n function useSelector<T>(\n selector: (state: S) => T,\n isEqual: (a: T, b: T) => boolean = shallowEqual\n ): T {\n const ctx = useContext(Context);\n if (!ctx) throw new Error(\"Store not found in context\");\n\n const lastRef = useRef<{ selected: T } | null>(null);\n\n const getSnapshot = () => {\n const nextSelected = selector(ctx.getState());\n const last = lastRef.current;\n if (last && isEqual(last.selected, nextSelected)) {\n devTrack(\"scoped:selector:cache-hit\");\n return last.selected;\n }\n devTrack(\"scoped:selector:cache-miss\");\n lastRef.current = { selected: nextSelected };\n return nextSelected;\n };\n\n const subscribe = (onChange: () => void) => {\n devTrack(\"scoped:selector:sub:add\");\n const unsubscribe = ctx.subscribeWithSelector(selector, (nextSelected) => {\n const last = lastRef.current;\n if (last && isEqual(last.selected, nextSelected)) {\n devTrack(\"scoped:selector:skip\");\n return;\n }\n lastRef.current = { selected: nextSelected };\n devTrack(\"scoped:selector:notify\");\n enqueue(onChange);\n }, isEqual);\n return () => {\n devTrack(\"scoped:selector:sub:remove\");\n unsubscribe();\n };\n };\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n }\n\n return {\n Context,\n Provider,\n useStore,\n useDispatch,\n useSelector,\n };\n}\n","// freeze.ts\nexport function deepFreeze<T>(obj: T, seen = new WeakSet<object>()): T {\n if (obj === null || typeof obj !== \"object\") return obj;\n const o = obj as unknown as object;\n if (seen.has(o)) return obj;\n seen.add(o);\n\n // Freeze children first\n for (const key of Object.getOwnPropertyNames(o)) {\n // @ts-expect-error index access\n const val = (o as any)[key];\n if (val && typeof val === \"object\") deepFreeze(val, seen);\n }\n // Also handle symbols (rare but safe)\n for (const sym of Object.getOwnPropertySymbols(o)) {\n // @ts-expect-error index access\n const val = (o as any)[sym];\n if (val && typeof val === \"object\") deepFreeze(val, seen);\n }\n\n return Object.freeze(obj);\n}\n","import type { Reducer, Listener } from \"./types.js\";\nimport { deepFreeze } from \"./freeze.js\";\n\nconst DEV =\n typeof import.meta !== \"undefined\"\n ? (import.meta as unknown as { env?: { DEV?: boolean } }).env?.DEV\n : process.env.NODE_ENV !== \"production\";\n\n// Lightweight DEV-only tracker\nfunction devTrack(name: string, props?: Record<string, unknown>) {\n if (!DEV) return;\n try {\n const t = (globalThis as any)?.track;\n if (typeof t === \"function\") t(name, props);\n } catch {}\n}\n\n// Allow narrower parameter types for callbacks without fighting variance\ntype BivariantListener<T> = {\n bivarianceHack(value: T): void;\n}[\"bivarianceHack\"];\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface IState {}\nexport interface IAction {\n type: string;\n}\n\nexport interface Store<S extends IState, A extends IAction> {\n getState(): S;\n dispatch(action: A): void;\n /**\n * Subscribe to all state changes.\n */\n subscribe(listener: Listener): () => void;\n /**\n * Subscribe to changes of a specific key in the state.\n */\n subscribeToKey<K extends keyof S>(\n key: K,\n listener: (value: S[K]) => void\n ): () => void;\n /**\n * Subscribe to changes in a selected value from the state.\n */\n subscribeWithSelector<T>(\n selector: (state: S) => T,\n listener: (selected: T) => void,\n isEqual?: (a: T, b: T) => boolean\n ): () => void;\n}\n\nexport function createStore<S extends IState, A extends IAction>(\n reducer: Reducer<S, A>,\n initialState: S\n): Store<S, A> {\n let state: S = DEV ? deepFreeze(initialState) : initialState;\n const listeners = new Set<Listener>();\n const keyListeners = new Map<keyof S, Set<BivariantListener<S[keyof S]>>>();\n\n interface SelectorEntry<T> {\n selector: (state: S) => T;\n listener: BivariantListener<T>;\n lastValue: T;\n isEqual?: (a: T, b: T) => boolean;\n }\n const selectorListeners = new Set<SelectorEntry<unknown>>();\n\n const getState = () => state;\n\n const dispatch = (action: A) => {\n const prevState = state;\n const nextState = reducer(state, action);\n\n if (DEV) deepFreeze(nextState);\n\n // Track the inbound action\n devTrack(\"store:dispatch\", { type: action?.type });\n\n // Distinct-until-changed: if the reducer returns the same reference,\n // skip all notifications (prevents unnecessary re-renders).\n if (Object.is(prevState, nextState)) {\n state = nextState; // keep any identity guarantees from reducer\n devTrack(\"store:no-op\", { type: action?.type });\n return;\n }\n\n state = nextState;\n\n // Compute changed keys (shallow) for diagnostics\n let changedKeys: (keyof S)[] | undefined;\n if (DEV) {\n changedKeys = Object.keys(nextState as Record<string, unknown>)\n .filter((k) => !Object.is((prevState as any)[k], (nextState as any)[k])) as (keyof S)[];\n devTrack(\"store:state-changed\", { type: action?.type, changedKeys });\n }\n\n // Notify global listeners (iterate over a snapshot so unsubscribe during\n // notify does not skip the next listener)\n const globalSnapshot = [...listeners];\n devTrack(\"store:notify:all\", { listeners: globalSnapshot.length });\n let firstError: unknown;\n for (const listener of globalSnapshot) {\n try {\n listener();\n } catch (err) {\n if (!firstError) firstError = err;\n }\n }\n\n // Notify key listeners only when that key actually changed (Object.is)\n for (const [key, set] of keyListeners.entries()) {\n if (!Object.is(prevState[key], state[key])) {\n devTrack(\"store:notify:key\", { key: String(key), listeners: set.size });\n for (const listener of [...set]) {\n try {\n listener(state[key]);\n } catch (err) {\n if (!firstError) firstError = err;\n }\n }\n }\n }\n\n // Notify selector listeners only when selected value changed (Object.is)\n let selNotifies = 0;\n for (const entry of [...selectorListeners]) {\n const nextValue = (entry.selector as (s: S) => unknown)(state);\n const equal = (entry.isEqual as ((a: unknown, b: unknown) => boolean) | undefined) ?? Object.is;\n let isSame = false;\n try {\n isSame = equal(entry.lastValue, nextValue);\n } catch (err) {\n if (!firstError) firstError = err;\n continue;\n }\n if (!isSame) {\n entry.lastValue = nextValue as unknown;\n try {\n (entry.listener as (v: unknown) => void)(nextValue);\n } catch (err) {\n if (!firstError) firstError = err;\n }\n selNotifies++;\n }\n }\n devTrack(\"store:notify:selector\", { listeners: selNotifies });\n if (firstError) throw firstError;\n };\n\n const subscribe = (listener: Listener) => {\n listeners.add(listener);\n devTrack(\"store:sub:all:add\", { size: listeners.size });\n return () => {\n listeners.delete(listener);\n devTrack(\"store:sub:all:remove\", { size: listeners.size });\n };\n };\n\n const subscribeToKey = <K extends keyof S>(\n key: K,\n listener: (value: S[K]) => void\n ) => {\n const set =\n keyListeners.get(key) ?? new Set<BivariantListener<S[keyof S]>>();\n set.add(listener as unknown as BivariantListener<S[keyof S]>);\n keyListeners.set(key, set);\n devTrack(\"store:sub:key:add\", { key: String(key), size: set.size });\n return () => {\n set.delete(listener as unknown as BivariantListener<S[keyof S]>);\n if (set.size === 0) keyListeners.delete(key);\n devTrack(\"store:sub:key:remove\", { key: String(key), size: set.size });\n };\n };\n\n const subscribeWithSelector = <T>(\n selector: (state: S) => T,\n listener: (selected: T) => void,\n isEqual?: (a: T, b: T) => boolean\n ) => {\n const entry: SelectorEntry<T> = {\n selector,\n listener: listener as BivariantListener<T>,\n lastValue: selector(state),\n isEqual,\n };\n selectorListeners.add(entry as unknown as SelectorEntry<unknown>);\n devTrack(\"store:sub:selector:add\", { size: selectorListeners.size });\n return () => {\n selectorListeners.delete(entry as unknown as SelectorEntry<unknown>);\n devTrack(\"store:sub:selector:remove\", { size: selectorListeners.size });\n };\n };\n\n return {\n getState,\n dispatch,\n subscribe,\n subscribeToKey,\n subscribeWithSelector,\n };\n}\n","import React, { createContext, useContext, useEffect, useSyncExternalStore } from \"react\";\nimport type { ReactNode } from \"react\";\nimport type { Store, IState, IAction } from \"./store.js\";\n\n// DEV-only tracking (no-op in production)\nconst __DEV__ = typeof process !== \"undefined\" ? process.env.NODE_ENV !== \"production\" : true;\nfunction devTrack(name: string, props?: Record<string, unknown>) {\n if (!__DEV__) return;\n try {\n const t = (globalThis as any)?.track;\n if (typeof t === \"function\") t(name, props);\n } catch {}\n}\n\nconst StoreContext = createContext<Store<IState, IAction> | undefined>(undefined);\n\nfunction useStoreInstance<S extends IState, A extends IAction>(): Store<S, A> {\n const store = useContext(StoreContext) as Store<S, A> | undefined;\n if (!store) {\n devTrack(\"store:provider:missing\");\n throw new Error(\n \"StoreProvider is missing in the React tree. Wrap your app with <StoreProvider store={...}>.\"\n );\n }\n return store;\n}\n\ninterface StoreProviderProps<S extends IState, A extends IAction> {\n store: Store<S, A>;\n children: ReactNode;\n}\n\nexport function StoreProvider<S extends IState, A extends IAction>({\n store,\n children,\n}: StoreProviderProps<S, A>) {\n useEffect(() => {\n devTrack(\"store:provider:mount\", { hasStore: !!store });\n return () => devTrack(\"store:provider:unmount\");\n }, [store]);\n return (\n <StoreContext.Provider value={store as unknown as Store<IState, IAction>}>\n {children}\n </StoreContext.Provider>\n );\n}\n\nexport function useStore<S extends IState>(): S {\n const store = useStoreInstance<S, IAction>();\n return useSyncExternalStore(\n (onStoreChange) => {\n devTrack(\"store:react:subscribe\");\n const unsubscribe = store.subscribe(() => {\n devTrack(\"store:react:notify\");\n onStoreChange();\n });\n return () => {\n devTrack(\"store:react:unsubscribe\");\n unsubscribe();\n };\n },\n store.getState,\n store.getState\n );\n}\n\nexport function useDispatch<A extends IAction>(): Store<IState, A>[\"dispatch\"] {\n const store = useStoreInstance<IState, A>();\n // Return the store's dispatch directly; consumers can call dispatch(action).\n return store.dispatch as Store<IState, A>[\"dispatch\"];\n}\n","// metadata-store.ts\nexport class MetadataStore<T extends object, Meta extends object> {\n private readonly symbol: symbol;\n\n constructor(description: string) {\n this.symbol = Symbol(description);\n }\n\n set(target: T, meta: Meta) {\n Object.defineProperty(target, this.symbol as PropertyKey, {\n value: meta,\n writable: false,\n enumerable: false,\n });\n }\n\n get(target: T): Meta | undefined {\n return (target as Record<PropertyKey, Meta>)[this.symbol as PropertyKey];\n }\n\n has(target: T): boolean {\n return (this.symbol as PropertyKey) in target;\n }\n}\n"],"mappings":"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","names":["devTrack","useStore","useDispatch","createContext","useContext","useEffect","useSyncExternalStore","jsx","__DEV__","devTrack"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plasius/react-state",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Tiny, testable, typesafe React Scoped Store helper.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -51,10 +51,10 @@
|
|
|
51
51
|
"clean": "rimraf dist"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@types/react": "^19.1.13",
|
|
55
54
|
"@testing-library/jest-dom": "^6.8.0",
|
|
56
55
|
"@testing-library/react": "^16.3.0",
|
|
57
56
|
"@types/node": "^24.5.2",
|
|
57
|
+
"@types/react": "^19.1.13",
|
|
58
58
|
"@typescript-eslint/eslint-plugin": "^8.43.0",
|
|
59
59
|
"@typescript-eslint/parser": "^8.43.0",
|
|
60
60
|
"@vitest/coverage-v8": "^3.2.4",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"access": "public"
|
|
70
70
|
},
|
|
71
71
|
"peerDependencies": {
|
|
72
|
-
"react": "^19"
|
|
72
|
+
"react": "^18.2 || ^19"
|
|
73
73
|
},
|
|
74
74
|
"funding": [
|
|
75
75
|
{
|
|
@@ -80,5 +80,8 @@
|
|
|
80
80
|
"type": "github",
|
|
81
81
|
"url": "https://github.com/sponsors/Plasius-LTD"
|
|
82
82
|
}
|
|
83
|
-
]
|
|
83
|
+
],
|
|
84
|
+
"dependencies": {
|
|
85
|
+
"@plasius/nfr": "^1.0.1"
|
|
86
|
+
}
|
|
84
87
|
}
|