@plasius/react-state 1.0.13 → 1.1.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.cjs +34 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +35 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -122,6 +122,23 @@ function createStore(reducer, initialState) {
|
|
|
122
122
|
|
|
123
123
|
// src/create-scoped-store.tsx
|
|
124
124
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
125
|
+
var _queue = /* @__PURE__ */ new Set();
|
|
126
|
+
var _queued = false;
|
|
127
|
+
var _schedule = typeof queueMicrotask === "function" ? queueMicrotask : (fn) => Promise.resolve().then(fn);
|
|
128
|
+
function enqueue(fn) {
|
|
129
|
+
_queue.add(fn);
|
|
130
|
+
if (_queued) return;
|
|
131
|
+
_queued = true;
|
|
132
|
+
_schedule(() => {
|
|
133
|
+
_queued = false;
|
|
134
|
+
const fns = Array.from(_queue);
|
|
135
|
+
_queue.clear();
|
|
136
|
+
for (const f of fns) f();
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
function makeBatchedSubscribe(subscribe) {
|
|
140
|
+
return (onChange) => subscribe(() => enqueue(onChange));
|
|
141
|
+
}
|
|
125
142
|
function shallowEqual(a, b) {
|
|
126
143
|
if (Object.is(a, b)) return true;
|
|
127
144
|
if (typeof a !== "object" || a === null || typeof b !== "object" || b === null)
|
|
@@ -137,23 +154,35 @@ function shallowEqual(a, b) {
|
|
|
137
154
|
}
|
|
138
155
|
function createScopedStoreContext(reducer, initialState) {
|
|
139
156
|
const Context = (0, import_react.createContext)(null);
|
|
140
|
-
const
|
|
141
|
-
|
|
157
|
+
const Provider = ({
|
|
158
|
+
children,
|
|
159
|
+
initialState: override
|
|
160
|
+
}) => {
|
|
161
|
+
const storeRef = (0, import_react.useRef)(null);
|
|
162
|
+
if (!storeRef.current) {
|
|
163
|
+
storeRef.current = createStore(reducer, override ?? initialState);
|
|
164
|
+
}
|
|
165
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Context.Provider, { value: storeRef.current, children });
|
|
166
|
+
};
|
|
142
167
|
const useStore2 = () => {
|
|
143
168
|
const ctx = (0, import_react.useContext)(Context);
|
|
144
169
|
if (!ctx) throw new Error("Store not found in context");
|
|
145
|
-
return (0, import_react.useSyncExternalStore)(
|
|
170
|
+
return (0, import_react.useSyncExternalStore)(
|
|
171
|
+
makeBatchedSubscribe(ctx.subscribe),
|
|
172
|
+
ctx.getState,
|
|
173
|
+
ctx.getState
|
|
174
|
+
);
|
|
146
175
|
};
|
|
147
176
|
const useDispatch2 = () => {
|
|
148
177
|
const ctx = (0, import_react.useContext)(Context);
|
|
149
178
|
if (!ctx) throw new Error("Dispatch not found in context");
|
|
150
|
-
return (action) => ctx.dispatch(action);
|
|
179
|
+
return (0, import_react.useCallback)((action) => ctx.dispatch(action), [ctx]);
|
|
151
180
|
};
|
|
152
181
|
function useSelector(selector, isEqual = shallowEqual) {
|
|
153
182
|
const ctx = (0, import_react.useContext)(Context);
|
|
154
183
|
if (!ctx) throw new Error("Store not found in context");
|
|
155
184
|
const state = (0, import_react.useSyncExternalStore)(
|
|
156
|
-
ctx.subscribe,
|
|
185
|
+
makeBatchedSubscribe(ctx.subscribe),
|
|
157
186
|
ctx.getState,
|
|
158
187
|
ctx.getState
|
|
159
188
|
);
|
|
@@ -167,7 +196,6 @@ function createScopedStoreContext(reducer, initialState) {
|
|
|
167
196
|
return nextSelected;
|
|
168
197
|
}
|
|
169
198
|
return {
|
|
170
|
-
store,
|
|
171
199
|
Context,
|
|
172
200
|
Provider,
|
|
173
201
|
useStore: useStore2,
|
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 } from \"react\";\nimport type { IState, IAction, Store } from \"./store.js\";\nimport { createStore } from \"./store.js\";\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 const store = createStore(reducer, initialState);\n\n const Provider = ({ children }: { children: React.ReactNode }) => (\n <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 return useSyncExternalStore(ctx.subscribe, ctx.getState, ctx.getState);\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 (action: A) => ctx.dispatch(action);\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 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 store,\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 } 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"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -31,10 +31,10 @@ interface Store<S extends IState, A extends IAction> {
|
|
|
31
31
|
declare function createStore<S extends IState, A extends IAction>(reducer: Reducer<S, A>, initialState: S): Store<S, A>;
|
|
32
32
|
|
|
33
33
|
declare function createScopedStoreContext<S extends IState, A extends IAction>(reducer: (state: S, action: A) => S, initialState: S): {
|
|
34
|
-
store: Store<S, A>;
|
|
35
34
|
Context: react.Context<Store<S, A> | null>;
|
|
36
|
-
Provider: ({ children }: {
|
|
35
|
+
Provider: ({ children, initialState: override, }: {
|
|
37
36
|
children: React.ReactNode;
|
|
37
|
+
initialState?: S;
|
|
38
38
|
}) => react_jsx_runtime.JSX.Element;
|
|
39
39
|
useStore: () => S;
|
|
40
40
|
useDispatch: () => ((action: A) => void);
|
package/dist/index.d.ts
CHANGED
|
@@ -31,10 +31,10 @@ interface Store<S extends IState, A extends IAction> {
|
|
|
31
31
|
declare function createStore<S extends IState, A extends IAction>(reducer: Reducer<S, A>, initialState: S): Store<S, A>;
|
|
32
32
|
|
|
33
33
|
declare function createScopedStoreContext<S extends IState, A extends IAction>(reducer: (state: S, action: A) => S, initialState: S): {
|
|
34
|
-
store: Store<S, A>;
|
|
35
34
|
Context: react.Context<Store<S, A> | null>;
|
|
36
|
-
Provider: ({ children }: {
|
|
35
|
+
Provider: ({ children, initialState: override, }: {
|
|
37
36
|
children: React.ReactNode;
|
|
37
|
+
initialState?: S;
|
|
38
38
|
}) => react_jsx_runtime.JSX.Element;
|
|
39
39
|
useStore: () => S;
|
|
40
40
|
useDispatch: () => ((action: A) => void);
|
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 } from "react";
|
|
5
|
+
import { createContext, useContext, useRef, useSyncExternalStore, useCallback } from "react";
|
|
6
6
|
|
|
7
7
|
// src/freeze.ts
|
|
8
8
|
function deepFreeze(obj, seen = /* @__PURE__ */ new WeakSet()) {
|
|
@@ -89,6 +89,23 @@ function createStore(reducer, initialState) {
|
|
|
89
89
|
|
|
90
90
|
// src/create-scoped-store.tsx
|
|
91
91
|
import { jsx } from "react/jsx-runtime";
|
|
92
|
+
var _queue = /* @__PURE__ */ new Set();
|
|
93
|
+
var _queued = false;
|
|
94
|
+
var _schedule = typeof queueMicrotask === "function" ? queueMicrotask : (fn) => Promise.resolve().then(fn);
|
|
95
|
+
function enqueue(fn) {
|
|
96
|
+
_queue.add(fn);
|
|
97
|
+
if (_queued) return;
|
|
98
|
+
_queued = true;
|
|
99
|
+
_schedule(() => {
|
|
100
|
+
_queued = false;
|
|
101
|
+
const fns = Array.from(_queue);
|
|
102
|
+
_queue.clear();
|
|
103
|
+
for (const f of fns) f();
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
function makeBatchedSubscribe(subscribe) {
|
|
107
|
+
return (onChange) => subscribe(() => enqueue(onChange));
|
|
108
|
+
}
|
|
92
109
|
function shallowEqual(a, b) {
|
|
93
110
|
if (Object.is(a, b)) return true;
|
|
94
111
|
if (typeof a !== "object" || a === null || typeof b !== "object" || b === null)
|
|
@@ -104,23 +121,35 @@ function shallowEqual(a, b) {
|
|
|
104
121
|
}
|
|
105
122
|
function createScopedStoreContext(reducer, initialState) {
|
|
106
123
|
const Context = createContext(null);
|
|
107
|
-
const
|
|
108
|
-
|
|
124
|
+
const Provider = ({
|
|
125
|
+
children,
|
|
126
|
+
initialState: override
|
|
127
|
+
}) => {
|
|
128
|
+
const storeRef = useRef(null);
|
|
129
|
+
if (!storeRef.current) {
|
|
130
|
+
storeRef.current = createStore(reducer, override ?? initialState);
|
|
131
|
+
}
|
|
132
|
+
return /* @__PURE__ */ jsx(Context.Provider, { value: storeRef.current, children });
|
|
133
|
+
};
|
|
109
134
|
const useStore2 = () => {
|
|
110
135
|
const ctx = useContext(Context);
|
|
111
136
|
if (!ctx) throw new Error("Store not found in context");
|
|
112
|
-
return useSyncExternalStore(
|
|
137
|
+
return useSyncExternalStore(
|
|
138
|
+
makeBatchedSubscribe(ctx.subscribe),
|
|
139
|
+
ctx.getState,
|
|
140
|
+
ctx.getState
|
|
141
|
+
);
|
|
113
142
|
};
|
|
114
143
|
const useDispatch2 = () => {
|
|
115
144
|
const ctx = useContext(Context);
|
|
116
145
|
if (!ctx) throw new Error("Dispatch not found in context");
|
|
117
|
-
return (action) => ctx.dispatch(action);
|
|
146
|
+
return useCallback((action) => ctx.dispatch(action), [ctx]);
|
|
118
147
|
};
|
|
119
148
|
function useSelector(selector, isEqual = shallowEqual) {
|
|
120
149
|
const ctx = useContext(Context);
|
|
121
150
|
if (!ctx) throw new Error("Store not found in context");
|
|
122
151
|
const state = useSyncExternalStore(
|
|
123
|
-
ctx.subscribe,
|
|
152
|
+
makeBatchedSubscribe(ctx.subscribe),
|
|
124
153
|
ctx.getState,
|
|
125
154
|
ctx.getState
|
|
126
155
|
);
|
|
@@ -134,7 +163,6 @@ function createScopedStoreContext(reducer, initialState) {
|
|
|
134
163
|
return nextSelected;
|
|
135
164
|
}
|
|
136
165
|
return {
|
|
137
|
-
store,
|
|
138
166
|
Context,
|
|
139
167
|
Provider,
|
|
140
168
|
useStore: useStore2,
|
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 } from \"react\";\nimport type { IState, IAction, Store } from \"./store.js\";\nimport { createStore } from \"./store.js\";\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 const store = createStore(reducer, initialState);\n\n const Provider = ({ children }: { children: React.ReactNode }) => (\n <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 return useSyncExternalStore(ctx.subscribe, ctx.getState, ctx.getState);\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 (action: A) => ctx.dispatch(action);\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 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 store,\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 } 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"]}
|