@pixotope/react-context-store 0.5.0 → 0.7.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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @pixotope/react-context-store@0.5.0 build /home/circleci/project/packages/react-context-store
2
+ > @pixotope/react-context-store@0.7.0 build /home/circleci/project/packages/react-context-store
3
3
  > tsup src/index.ts --format cjs,esm --dts --sourcemap --external react
4
4
 
5
5
  CLI Building entry: src/index.ts
@@ -8,13 +8,13 @@
8
8
  CLI Target: es6
9
9
  CJS Build start
10
10
  ESM Build start
11
- CJS dist/index.cjs 8.57 KB
12
- CJS dist/index.cjs.map 16.40 KB
13
- CJS ⚡️ Build success in 70ms
14
- ESM dist/index.js 7.96 KB
15
- ESM dist/index.js.map 16.28 KB
16
- ESM ⚡️ Build success in 71ms
11
+ CJS dist/index.cjs 6.98 KB
12
+ CJS dist/index.cjs.map 21.74 KB
13
+ CJS ⚡️ Build success in 40ms
14
+ ESM dist/index.js 5.24 KB
15
+ ESM dist/index.js.map 21.67 KB
16
+ ESM ⚡️ Build success in 39ms
17
17
  DTS Build start
18
- DTS ⚡️ Build success in 3952ms
19
- DTS dist/index.d.cts 2.36 KB
20
- DTS dist/index.d.ts 2.36 KB
18
+ DTS ⚡️ Build success in 3895ms
19
+ DTS dist/index.d.cts 7.51 KB
20
+ DTS dist/index.d.ts 7.51 KB
@@ -1,4 +1,4 @@
1
1
 
2
- > @pixotope/react-context-store@0.5.0 check-types /home/circleci/project/packages/react-context-store
2
+ > @pixotope/react-context-store@0.7.0 check-types /home/circleci/project/packages/react-context-store
3
3
  > tsc --noEmit
4
4
 
@@ -1,14 +1,14 @@
1
1
 
2
- > @pixotope/react-context-store@0.5.0 test /home/circleci/project/packages/react-context-store
2
+ > @pixotope/react-context-store@0.7.0 test /home/circleci/project/packages/react-context-store
3
3
  > vitest
4
4
 
5
5
 
6
6
   RUN  v3.2.4 /home/circleci/project/packages/react-context-store
7
7
 
8
- ✓ src/store.test.tsx (14 tests) 148ms
8
+ ✓ src/store.test.tsx (14 tests) 131ms
9
9
 
10
10
   Test Files  1 passed (1)
11
11
   Tests  14 passed (14)
12
-  Start at  17:52:10
13
-  Duration  7.10s (transform 487ms, setup 443ms, collect 902ms, tests 148ms, environment 1.85s, prepare 1.20s)
12
+  Start at  13:44:03
13
+  Duration  7.50s (transform 446ms, setup 380ms, collect 927ms, tests 131ms, environment 2.09s, prepare 1.29s)
14
14
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @pixotope/react-context-store
2
2
 
3
+ ## 0.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 8a28444: Include detailed documentation for every package and setup `typedoc`
8
+
9
+ ## 0.6.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 20804c6: @pixotope/react-context-store
14
+
15
+  - remove selector hook
16
+
3
17
  ## 0.5.0
4
18
 
5
19
  ### Minor Changes
package/README.md ADDED
@@ -0,0 +1,69 @@
1
+ # @pixotope/react-context-store
2
+
3
+ A factory for React context-based state stores, with selector hooks, typed actions, and `useSyncExternalStore` under the hood.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ pnpm add @pixotope/react-context-store
9
+ ```
10
+
11
+ This package depends on `@pixotope/utils` internally (for shallow-equality comparisons). As a workspace dependency it is resolved automatically — no extra install step is needed.
12
+
13
+ ## Quick start
14
+
15
+ ```tsx
16
+ import { createContextStore, ActionablePayload } from "@pixotope/react-context-store";
17
+
18
+ const initialState = {
19
+ count: 0,
20
+ user: { name: "John", age: 20 },
21
+ };
22
+
23
+ // Create a store with its initial state and a map of named actions.
24
+ const store = createContextStore(initialState, {
25
+ increment: ({ set, get }) => {
26
+ set({ ...get(), count: get().count + 1 });
27
+ },
28
+ incrementBy: ({ set, get }, action: ActionablePayload<number>) => {
29
+ set({ ...get(), count: get().count + action.payload });
30
+ },
31
+ });
32
+
33
+ function Counter() {
34
+ // Select only the state this component needs; it only re-renders when
35
+ // the selected value changes.
36
+ const { state: count } = store.useStore((state) => state.count);
37
+ const { increment, incrementBy } = store.useActions();
38
+
39
+ return (
40
+ <div>
41
+ <p>{count}</p>
42
+ <button onClick={() => increment()}>+1</button>
43
+ <button onClick={() => incrementBy(5)}>+5</button>
44
+ </div>
45
+ );
46
+ }
47
+
48
+ function App() {
49
+ // Wrap any component tree that uses the store's hooks in its Provider.
50
+ return (
51
+ <store.Provider>
52
+ <Counter />
53
+ </store.Provider>
54
+ );
55
+ }
56
+ ```
57
+
58
+ ## API overview
59
+
60
+ - `createContextStore(initialState, actions?, options?)` — Factory function that creates a store, returning the object described below.
61
+ - `Provider` — React component that wraps a component tree and gives it access to the store.
62
+ - `useStore(selector?, compare?)` — Hook that reads (a selection of) the store state, re-rendering the component when the selected state changes.
63
+ - `useActions()` — Hook that returns the actions passed to `createContextStore`, bound to the current store.
64
+ - `useSetStore()` — Hook that returns a function to update the store state without subscribing to updates.
65
+ - `subscribe(selector, callback, compare?)` / `unsubscribe(callback)` — Subscribe to store updates from outside the React component tree.
66
+ - `ActionablePayload<Payload>` — Type used on an action's second parameter to give it a typed payload argument (see `incrementBy` above).
67
+ - `ContextOptions` — Options passed as the third argument to `createContextStore` (`global`, `compare`).
68
+
69
+ See the generated API reference (`pnpm typedoc`) for full type signatures.
package/dist/index.cjs CHANGED
@@ -5,9 +5,6 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __commonJS = (cb, mod) => function __require() {
9
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
- };
11
8
  var __export = (target, all) => {
12
9
  for (var name in all)
13
10
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -30,71 +27,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
30
27
  ));
31
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
29
 
33
- // ../utils/dist/comparison/index.js
34
- var require_comparison = __commonJS({
35
- "../utils/dist/comparison/index.js"(exports2, module2) {
36
- "use strict";
37
- var __defProp2 = Object.defineProperty;
38
- var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
39
- var __getOwnPropNames2 = Object.getOwnPropertyNames;
40
- var __hasOwnProp2 = Object.prototype.hasOwnProperty;
41
- var __export2 = (target, all) => {
42
- for (var name in all)
43
- __defProp2(target, name, { get: all[name], enumerable: true });
44
- };
45
- var __copyProps2 = (to, from, except, desc) => {
46
- if (from && typeof from === "object" || typeof from === "function") {
47
- for (let key of __getOwnPropNames2(from))
48
- if (!__hasOwnProp2.call(to, key) && key !== except)
49
- __defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
50
- }
51
- return to;
52
- };
53
- var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
54
- var comparison_exports = {};
55
- __export2(comparison_exports, {
56
- shallowEqual: () => shallowEqual2
57
- });
58
- module2.exports = __toCommonJS2(comparison_exports);
59
- function shallowEqual2(objA, objB) {
60
- if (Object.is(objA, objB)) {
61
- return true;
62
- }
63
- if (typeof objA !== "object" || objA === null || typeof objB !== "object" || objB === null) {
64
- return false;
65
- }
66
- if (objA instanceof Map && objB instanceof Map) {
67
- if (objA.size !== objB.size) return false;
68
- for (const [key, value] of objA) {
69
- if (!Object.is(value, objB.get(key))) {
70
- return false;
71
- }
72
- }
73
- return true;
74
- }
75
- if (objA instanceof Set && objB instanceof Set) {
76
- if (objA.size !== objB.size) return false;
77
- for (const value of objA) {
78
- if (!objB.has(value)) {
79
- return false;
80
- }
81
- }
82
- return true;
83
- }
84
- const keysA = Object.keys(objA);
85
- if (keysA.length !== Object.keys(objB).length) {
86
- return false;
87
- }
88
- for (let i = 0; i < keysA.length; i++) {
89
- if (!Object.prototype.hasOwnProperty.call(objB, keysA[i]) || !Object.is(objA[keysA[i]], objB[keysA[i]])) {
90
- return false;
91
- }
92
- }
93
- return true;
94
- }
95
- }
96
- });
97
-
98
30
  // src/index.ts
99
31
  var index_exports = {};
100
32
  __export(index_exports, {
@@ -104,7 +36,46 @@ module.exports = __toCommonJS(index_exports);
104
36
 
105
37
  // src/store.tsx
106
38
  var import_react = __toESM(require("react"), 1);
107
- var import_comparison = __toESM(require_comparison(), 1);
39
+
40
+ // ../utils/dist/chunk-5QIHMQPV.js
41
+ function shallowEqual(objA, objB) {
42
+ if (Object.is(objA, objB)) {
43
+ return true;
44
+ }
45
+ if (typeof objA !== "object" || objA === null || typeof objB !== "object" || objB === null) {
46
+ return false;
47
+ }
48
+ if (objA instanceof Map && objB instanceof Map) {
49
+ if (objA.size !== objB.size) return false;
50
+ for (const [key, value] of objA) {
51
+ if (!Object.is(value, objB.get(key))) {
52
+ return false;
53
+ }
54
+ }
55
+ return true;
56
+ }
57
+ if (objA instanceof Set && objB instanceof Set) {
58
+ if (objA.size !== objB.size) return false;
59
+ for (const value of objA) {
60
+ if (!objB.has(value)) {
61
+ return false;
62
+ }
63
+ }
64
+ return true;
65
+ }
66
+ const keysA = Object.keys(objA);
67
+ if (keysA.length !== Object.keys(objB).length) {
68
+ return false;
69
+ }
70
+ for (let i = 0; i < keysA.length; i++) {
71
+ if (!Object.prototype.hasOwnProperty.call(objB, keysA[i]) || !Object.is(objA[keysA[i]], objB[keysA[i]])) {
72
+ return false;
73
+ }
74
+ }
75
+ return true;
76
+ }
77
+
78
+ // src/store.tsx
108
79
  var LIB_NAME = "@pixotope/react-context-store";
109
80
  function isFunction(value) {
110
81
  return typeof value === "function";
@@ -147,7 +118,7 @@ function createContextStore(initialState, actions = {}, options = {}) {
147
118
  );
148
119
  }
149
120
  const observers = /* @__PURE__ */ new Set();
150
- function subscribeExternal(selector = (store) => store, callback, compare = import_comparison.shallowEqual) {
121
+ function subscribeExternal(selector = (store) => store, callback, compare = shallowEqual) {
151
122
  let lastSelectedState;
152
123
  const shouldSendUpdates = (newSelectedState) => {
153
124
  if (lastSelectedState === void 0 || !compare(lastSelectedState, newSelectedState)) {
@@ -175,7 +146,7 @@ function createContextStore(initialState, actions = {}, options = {}) {
175
146
  observers.forEach((callback) => callback(state));
176
147
  }
177
148
  };
178
- function useStore(selector = (store) => store, compare = ((_a) => (_a = options.compare) != null ? _a : import_comparison.shallowEqual)()) {
149
+ function useStore(selector = (store) => store, compare = ((_a) => (_a = options.compare) != null ? _a : shallowEqual)()) {
179
150
  const store = (0, import_react.useContext)(StoreContext);
180
151
  const lastSelectedState = (0, import_react.useRef)(void 0);
181
152
  if (!store) {
@@ -235,13 +206,11 @@ function createContextStore(initialState, actions = {}, options = {}) {
235
206
  return actionProxy;
236
207
  }
237
208
  const useSetStore = () => useStore(() => false).set;
238
- const useStoreSelector = () => useStore(() => false).selector;
239
209
  return {
240
210
  Provider,
241
211
  useStore,
242
212
  useSetStore,
243
213
  useActions,
244
- useStoreSelector,
245
214
  subscribe: observable.subscribe,
246
215
  unsubscribe: observable.unsubscribe
247
216
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../utils/dist/comparison/index.js","../src/index.ts","../src/store.tsx"],"sourcesContent":["\"use strict\";\nvar __defProp = Object.defineProperty;\nvar __getOwnPropDesc = Object.getOwnPropertyDescriptor;\nvar __getOwnPropNames = Object.getOwnPropertyNames;\nvar __hasOwnProp = Object.prototype.hasOwnProperty;\nvar __export = (target, all) => {\n for (var name in all)\n __defProp(target, name, { get: all[name], enumerable: true });\n};\nvar __copyProps = (to, from, except, desc) => {\n if (from && typeof from === \"object\" || typeof from === \"function\") {\n for (let key of __getOwnPropNames(from))\n if (!__hasOwnProp.call(to, key) && key !== except)\n __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });\n }\n return to;\n};\nvar __toCommonJS = (mod) => __copyProps(__defProp({}, \"__esModule\", { value: true }), mod);\n\n// src/comparison/index.ts\nvar comparison_exports = {};\n__export(comparison_exports, {\n shallowEqual: () => shallowEqual\n});\nmodule.exports = __toCommonJS(comparison_exports);\n\n// src/comparison/shallowEqual.ts\nfunction shallowEqual(objA, objB) {\n if (Object.is(objA, objB)) {\n return true;\n }\n if (typeof objA !== \"object\" || objA === null || typeof objB !== \"object\" || objB === null) {\n return false;\n }\n if (objA instanceof Map && objB instanceof Map) {\n if (objA.size !== objB.size) return false;\n for (const [key, value] of objA) {\n if (!Object.is(value, objB.get(key))) {\n return false;\n }\n }\n return true;\n }\n if (objA instanceof Set && objB instanceof Set) {\n if (objA.size !== objB.size) return false;\n for (const value of objA) {\n if (!objB.has(value)) {\n return false;\n }\n }\n return true;\n }\n const keysA = Object.keys(objA);\n if (keysA.length !== Object.keys(objB).length) {\n return false;\n }\n for (let i = 0; i < keysA.length; i++) {\n if (!Object.prototype.hasOwnProperty.call(objB, keysA[i]) || !Object.is(objA[keysA[i]], objB[keysA[i]])) {\n return false;\n }\n }\n return true;\n}\n// Annotate the CommonJS export names for ESM import in node:\n0 && (module.exports = {\n shallowEqual\n});\n","export * from \"./store\";\n","import React, {\n createContext,\n useCallback,\n useContext,\n useMemo,\n useRef,\n useSyncExternalStore,\n} from \"react\";\nimport { shallowEqual } from \"@pixotope/utils/comparison\";\n\nconst LIB_NAME = \"@pixotope/react-context-store\";\n\nexport type SelectorOptions<Selected> = (\n first: Selected,\n second: Selected\n) => boolean;\n\nexport type ContextOptions = {\n /**\n * If true, the store state will be preserved across mounts and un-mounts\n * of the Provider.\n * @default false\n */\n global?: boolean;\n /**\n * A function that will be used to compare the selected state.\n * @default shallowEqual\n */\n compare?: SelectorOptions<unknown>;\n};\n\nexport type SetterArgs<Store> = Store | ((prev: Store) => Store);\ntype ExtractActionKeys<T> = {\n [K in keyof T]: T[K] extends (\n stateProps: never,\n action: infer A\n ) => void | Promise<void>\n ? A extends ActionablePayload<infer Payload>\n ? Payload extends undefined\n ? () => void\n : (payload: Payload) => void\n : () => void\n : never;\n};\n\ntype Prettify<T> = {\n [K in keyof T]: T[K];\n} & {};\n\nexport type ActionablePayload<Payload = any> = {\n payload: Payload;\n};\n\ntype StateActionProps<Store> = {\n set: (value: SetterArgs<Store>) => void;\n get: () => Store;\n};\n\ntype Actions<Store> = {\n [key: string]: (\n stateProps: StateActionProps<Store>,\n action: ActionablePayload\n ) => void | Promise<void>;\n};\n\ntype UseStoreReturnType<Store, SelectorOutput> = {\n state: SelectorOutput;\n get: () => SelectorOutput;\n set: (value: SetterArgs<Store>) => void;\n selector: () => Store;\n};\n\ntype ContextReturnType<Store, A extends Actions<Store>> = {\n Provider: React.FC<React.PropsWithChildren>;\n useStore: <SelectorOutput = Store>(\n selector?: (store: Store) => SelectorOutput,\n options?: SelectorOptions<SelectorOutput>\n ) => UseStoreReturnType<Store, SelectorOutput>;\n useActions: () => Prettify<ExtractActionKeys<A>>;\n useSetStore: () => (value: SetterArgs<Store>) => void;\n useStoreSelector: () => () => Store;\n subscribe: <SelectorOutput = Store>(\n selector: (store: Store) => SelectorOutput,\n callback: (state: SelectorOutput) => void,\n options?: SelectorOptions<SelectorOutput>\n ) => () => void;\n unsubscribe: (callback: () => void) => void;\n};\n\nfunction isFunction(value: any): value is (prev: any) => any {\n return typeof value === \"function\";\n}\n\nexport function createContextStore<Store, A extends Actions<Store> = Actions<Store>>(\n ...args: Extract<A, { payload: A }> extends { payload: infer Payload }\n ? [initialState: Store, actions?: A, options?: ContextOptions]\n : [initialState: Store, options?: ContextOptions]\n): ContextReturnType<Store, A>;\nexport function createContextStore<Store, A extends Actions<Store> = Actions<Store>>(\n initialState: Store,\n actions: A = {} as A,\n options: ContextOptions = {}\n): ContextReturnType<Store, A> {\n let globalStore: Store | undefined = options.global\n ? initialState\n : undefined;\n\n function useStoreData({\n defaultSubscriber = [],\n }: {\n defaultSubscriber?: ((state: Store) => void)[];\n } = {}): {\n get: () => Store;\n set: (value: SetterArgs<Store>) => void;\n subscribe: (callback: (state: Store) => void) => () => void;\n } {\n const store = useRef<Store>(globalStore ?? initialState);\n\n const get = useCallback(() => store.current, []);\n\n const subscribers = useRef(\n new Set<(state: Store) => void>(defaultSubscriber)\n );\n\n const set = useCallback((value: SetterArgs<Store>) => {\n store.current = isFunction(value) ? value(store.current) : value;\n\n if (options.global) {\n globalStore = store.current;\n }\n\n subscribers.current.forEach((callback) => callback(store.current));\n }, []);\n\n const subscribe = useCallback((callback: (state: Store) => void) => {\n subscribers.current.add(callback);\n\n return () => subscribers.current.delete(callback);\n }, []);\n\n return {\n get,\n set,\n subscribe,\n };\n }\n\n type UseStoreDataReturnType = ReturnType<typeof useStoreData>;\n\n const StoreContext = createContext<UseStoreDataReturnType | null>(null);\n\n /**\n * This is the provider that will be used to wrap the react component tree\n * to provide the store to all the components in the tree.\n */\n function Provider({ children }: React.PropsWithChildren) {\n return (\n <StoreContext.Provider\n value={useStoreData({ defaultSubscriber: [observable.broadcast] })}\n >\n {children}\n </StoreContext.Provider>\n );\n }\n\n /**\n * This holds all the observables that are subscribed to the store\n * but not being actively used by any component. Helps to broadcast\n * store updates to all the observables that are interested in reacting\n * to state outside the react component tree.\n */\n const observers = new Set<(state: Store) => void>();\n\n function subscribeExternal<SelectorOutput = Store>(\n selector: (store: Store) => SelectorOutput = (store) =>\n store as unknown as SelectorOutput,\n callback: (state: SelectorOutput) => void,\n compare: SelectorOptions<SelectorOutput> = shallowEqual\n ): () => void {\n let lastSelectedState: SelectorOutput | undefined;\n\n const shouldSendUpdates = (newSelectedState: SelectorOutput) => {\n if (\n lastSelectedState === undefined ||\n !compare(lastSelectedState, newSelectedState)\n ) {\n lastSelectedState = newSelectedState;\n\n return true;\n }\n\n return false;\n };\n\n const selectedCb = (store: Store) => {\n const selected = selector(store);\n const shouldSend = shouldSendUpdates(selected);\n\n if (shouldSend) {\n callback(selector(store));\n }\n };\n observers.add(selectedCb);\n\n return () => observers.delete(selectedCb);\n }\n\n /**\n * A simple observable that proxy store updates to all the subscribers\n * that are interested in reacting to state outside the react component tree.\n */\n const observable = {\n subscribe: subscribeExternal,\n unsubscribe: (callback: (state: Store) => void) => {\n observers.delete(callback);\n },\n broadcast: (state: Store) => {\n observers.forEach((callback) => callback(state));\n },\n } as const;\n\n /**\n * This is the hook that will be used to access the store from any component\n * in the react component tree.\n * @param selector A function that will be used to select the part of the store\n * that is needed by the component.\n * @param options Options to customize the behavior of the hook.\n * @returns An object with the selected state, a function to update the store\n * and a function to get the entire store.\n * @example\n * ```tsx\n * const { get, set } = useStore(store => store.user);\n * const { get, set } = useStore(store => store.user, { deepEqual: false });\n * const { get, set } = useStore(store => store.user, {\n * compare: (first, second) => first.id === second.id\n * });\n * ```\n */\n function useStore<SelectorOutput = Store>(\n selector: (store: Store) => SelectorOutput = (store) =>\n store as unknown as SelectorOutput,\n compare: SelectorOptions<SelectorOutput> = options.compare ?? shallowEqual\n ): UseStoreReturnType<Store, SelectorOutput> {\n const store = useContext(StoreContext);\n const lastSelectedState = useRef<SelectorOutput | undefined>(undefined);\n\n if (!store) {\n throw new Error(\n `[${LIB_NAME}] Store not found. Make sure the component is wrapped in a ${Provider} component.`\n );\n }\n\n const state = useSyncExternalStore(\n store.subscribe,\n () => {\n const selectedState = selector(store.get());\n\n if (\n lastSelectedState.current === undefined ||\n !compare(lastSelectedState.current, selectedState)\n ) {\n lastSelectedState.current = selectedState;\n }\n\n return lastSelectedState.current;\n },\n () => selector(initialState)\n );\n\n return {\n state,\n get: () => state,\n set: store.set,\n selector: () => store.get(),\n };\n }\n\n function useActions() {\n const store = useContext(StoreContext);\n\n if (!store) {\n throw new Error(\n `[${LIB_NAME}] Store not found. Make sure the component is wrapped in a ${Provider} component.`\n );\n }\n\n const actionProxy = useMemo(\n () =>\n new Proxy(actions, {\n get: (target, prop) => {\n const action = target[prop as string];\n\n if (action) {\n return (args: Parameters<typeof action>[\"1\"]) => {\n action(\n {\n set: store.set,\n get: store.get,\n },\n {\n payload: args,\n }\n );\n };\n }\n },\n set: () => {\n throw new Error(`[${LIB_NAME}] Actions cannot be updated`);\n },\n }),\n []\n );\n\n return actionProxy as unknown as ExtractActionKeys<typeof actions>;\n }\n\n const useSetStore = () => useStore(() => false).set;\n const useStoreSelector = () => useStore(() => false).selector;\n\n return {\n Provider,\n useStore,\n useSetStore,\n useActions,\n useStoreSelector,\n subscribe: observable.subscribe,\n unsubscribe: observable.unsubscribe,\n };\n}\n"],"mappings":"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","names":["exports","module","__defProp","__getOwnPropDesc","__getOwnPropNames","__hasOwnProp","__export","__copyProps","__toCommonJS","shallowEqual","React"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/store.tsx","../../utils/dist/chunk-5QIHMQPV.js"],"sourcesContent":["export * from \"./store\";\n","import React, {\n createContext,\n useCallback,\n useContext,\n useMemo,\n useRef,\n useSyncExternalStore,\n} from \"react\";\nimport { shallowEqual } from \"@pixotope/utils/comparison\";\n\nconst LIB_NAME = \"@pixotope/react-context-store\";\n\n/**\n * A function used to compare two versions of selected state, to decide\n * whether subscribers should be notified of a change.\n * @param first - The previously selected state.\n * @param second - The newly selected state.\n * @returns `true` if the two values should be treated as equal (no update triggered), `false` otherwise.\n */\nexport type SelectorOptions<Selected> = (\n first: Selected,\n second: Selected\n) => boolean;\n\n/**\n * Options that configure the behavior of a store created with {@link createContextStore}.\n */\nexport type ContextOptions = {\n /**\n * If true, the store state will be preserved across mounts and un-mounts\n * of the Provider.\n * @default false\n */\n global?: boolean;\n /**\n * A function that will be used to compare the selected state.\n * @default shallowEqual\n */\n compare?: SelectorOptions<unknown>;\n};\n\n/**\n * The value accepted by a store's `set` function: either the next state\n * directly, or an updater function that receives the previous state and\n * returns the next state.\n */\nexport type SetterArgs<Store> = Store | ((prev: Store) => Store);\n\n/** Maps an actions map (as passed to {@link createContextStore}) to the shape of the object returned by `useActions`. */\nexport type ExtractActionKeys<T> = {\n [K in keyof T]: T[K] extends (\n stateProps: never,\n action: infer A\n ) => void | Promise<void>\n ? A extends ActionablePayload<infer Payload>\n ? Payload extends undefined\n ? () => void\n : (payload: Payload) => void\n : () => void\n : never;\n};\n\n/** Flattens an intersection/mapped type into a single object type, for cleaner hover tooltips. */\nexport type Prettify<T> = {\n [K in keyof T]: T[K];\n} & {};\n\n/**\n * The action argument passed to an action handler, wrapping its payload.\n * Use this as the type of an action's second parameter to give the\n * corresponding {@link createContextStore} action a typed payload, which is\n * then reflected in the function returned by `useActions`.\n * @typeParam Payload - The type of the action's payload. Defaults to `any`.\n * @example\n * ```tsx\n * const store = createContextStore(initialState, {\n * incrementBy: ({ set, get }, action: ActionablePayload<number>) => {\n * set((prev) => ({ ...prev, count: get().count + action.payload }));\n * },\n * });\n * ```\n */\nexport type ActionablePayload<Payload = any> = {\n payload: Payload;\n};\n\n/** The state accessors passed as the first argument to every action handler. */\nexport type StateActionProps<Store> = {\n /** Updates the store state. */\n set: (value: SetterArgs<Store>) => void;\n /** Returns the current store state. */\n get: () => Store;\n};\n\n/**\n * A map of action names to action handlers, as passed to {@link createContextStore}.\n * Each handler receives {@link StateActionProps} for reading/writing the\n * store, plus an {@link ActionablePayload} carrying any payload passed by the caller.\n */\nexport type Actions<Store> = {\n [key: string]: (\n stateProps: StateActionProps<Store>,\n action: ActionablePayload\n ) => void | Promise<void>;\n};\n\n/** The object returned by a store's `useStore` hook. */\nexport type UseStoreReturnType<Store, SelectorOutput> = {\n /** The currently selected state. Reading this subscribes the component to updates. */\n state: SelectorOutput;\n /** Returns the currently selected state. Equivalent to reading `state`. */\n get: () => SelectorOutput;\n /** Updates the store state. */\n set: (value: SetterArgs<Store>) => void;\n /** Returns the entire store state, not just the selected slice. */\n selector: () => Store;\n};\n\n/**\n * The object returned by {@link createContextStore}: the `Provider`\n * component plus the hooks and functions used to read, update and\n * subscribe to the store.\n */\nexport type ContextReturnType<Store, A extends Actions<Store>> = {\n /** Wraps the part of the component tree that should have access to the store. */\n Provider: React.FC<React.PropsWithChildren>;\n /**\n * Reads (a selection of) the store state from the nearest `Provider`,\n * re-rendering the component whenever the selected state changes.\n * @param selector - Selects the part of the store the component needs. Defaults to the entire store.\n * @param options - A function used to compare the previous and next selected state. Defaults to the store's `compare` option, or shallow equality.\n * @returns The selected state, plus functions to read and update the store.\n */\n useStore: <SelectorOutput = Store>(\n selector?: (store: Store) => SelectorOutput,\n options?: SelectorOptions<SelectorOutput>\n ) => UseStoreReturnType<Store, SelectorOutput>;\n /**\n * Returns the actions passed to {@link createContextStore}, bound to the\n * current store. Actions declared with an {@link ActionablePayload}\n * parameter take a single payload argument; other actions take none.\n */\n useActions: () => Prettify<ExtractActionKeys<A>>;\n /**\n * Returns a function to update the store state from the nearest\n * `Provider`, without subscribing the component to store updates.\n */\n useSetStore: () => (value: SetterArgs<Store>) => void;\n /**\n * Subscribes to store updates from outside the React component tree.\n * @param selector - Selects the part of the store to watch.\n * @param callback - Called with the selected state whenever it changes.\n * @param options - A function used to compare the previous and next selected state. Defaults to shallow equality.\n * @returns A function to remove the subscription.\n */\n subscribe: <SelectorOutput = Store>(\n selector: (store: Store) => SelectorOutput,\n callback: (state: SelectorOutput) => void,\n options?: SelectorOptions<SelectorOutput>\n ) => () => void;\n /** Removes a subscription created with `subscribe`. */\n unsubscribe: (callback: () => void) => void;\n};\n\nfunction isFunction(value: any): value is (prev: any) => any {\n return typeof value === \"function\";\n}\n\n/**\n * Creates a React context-based store: a `Provider` component plus hooks to\n * read, update and subscribe to its state.\n *\n * Each call creates an independent store with its own React context, so the\n * `Provider` and hooks returned by one call must always be used together.\n * @typeParam Store - The shape of the store state.\n * @typeParam A - The map of named actions available to update the store.\n * @param args - Positional arguments: the `initialState` the store is initialized with, an\n * optional `actions` map of named action handlers used to update the store (see\n * {@link ActionablePayload} for actions that take a payload), and optional `options` to\n * customize the store's behavior.\n * @returns An object with a `Provider` component and hooks to read, update and subscribe to the store's state. See {@link ContextReturnType}.\n * @example\n * ```tsx\n * const store = createContextStore(\n * { count: 0 },\n * {\n * increment: ({ set, get }) => set({ ...get(), count: get().count + 1 }),\n * }\n * );\n *\n * function Counter() {\n * const { state: count } = store.useStore((state) => state.count);\n * const { increment } = store.useActions();\n *\n * return <button onClick={() => increment()}>{count}</button>;\n * }\n *\n * function App() {\n * return (\n * <store.Provider>\n * <Counter />\n * </store.Provider>\n * );\n * }\n * ```\n */\nexport function createContextStore<Store, A extends Actions<Store> = Actions<Store>>(\n ...args: Extract<A, { payload: A }> extends { payload: infer Payload }\n ? [initialState: Store, actions?: A, options?: ContextOptions]\n : [initialState: Store, options?: ContextOptions]\n): ContextReturnType<Store, A>;\nexport function createContextStore<Store, A extends Actions<Store> = Actions<Store>>(\n initialState: Store,\n actions: A = {} as A,\n options: ContextOptions = {}\n): ContextReturnType<Store, A> {\n let globalStore: Store | undefined = options.global\n ? initialState\n : undefined;\n\n function useStoreData({\n defaultSubscriber = [],\n }: {\n defaultSubscriber?: ((state: Store) => void)[];\n } = {}): {\n get: () => Store;\n set: (value: SetterArgs<Store>) => void;\n subscribe: (callback: (state: Store) => void) => () => void;\n } {\n const store = useRef<Store>(globalStore ?? initialState);\n\n const get = useCallback(() => store.current, []);\n\n const subscribers = useRef(\n new Set<(state: Store) => void>(defaultSubscriber)\n );\n\n const set = useCallback((value: SetterArgs<Store>) => {\n store.current = isFunction(value) ? value(store.current) : value;\n\n if (options.global) {\n globalStore = store.current;\n }\n\n subscribers.current.forEach((callback) => callback(store.current));\n }, []);\n\n const subscribe = useCallback((callback: (state: Store) => void) => {\n subscribers.current.add(callback);\n\n return () => subscribers.current.delete(callback);\n }, []);\n\n return {\n get,\n set,\n subscribe,\n };\n }\n\n type UseStoreDataReturnType = ReturnType<typeof useStoreData>;\n\n const StoreContext = createContext<UseStoreDataReturnType | null>(null);\n\n /**\n * Wraps the part of the component tree that should have access to the\n * store. Must wrap any component using `useStore`, `useActions` or `useSetStore`.\n * @remarks\n * If the store was created with `{ global: true }`, state changes made\n * while a `Provider` is mounted are preserved and restored the next time\n * a `Provider` for this store mounts.\n */\n function Provider({ children }: React.PropsWithChildren) {\n return (\n <StoreContext.Provider\n value={useStoreData({ defaultSubscriber: [observable.broadcast] })}\n >\n {children}\n </StoreContext.Provider>\n );\n }\n\n /**\n * This holds all the observables that are subscribed to the store\n * but not being actively used by any component. Helps to broadcast\n * store updates to all the observables that are interested in reacting\n * to state outside the react component tree.\n */\n const observers = new Set<(state: Store) => void>();\n\n /**\n * Subscribes to store updates from outside the React component tree.\n * @param selector - Selects the part of the store to watch. Defaults to the whole store.\n * @param callback - Called with the selected state whenever it changes.\n * @param compare - A function used to compare the previous and next selected state. Defaults to shallow equality.\n * @returns A function to remove the subscription.\n */\n function subscribeExternal<SelectorOutput = Store>(\n selector: (store: Store) => SelectorOutput = (store) =>\n store as unknown as SelectorOutput,\n callback: (state: SelectorOutput) => void,\n compare: SelectorOptions<SelectorOutput> = shallowEqual\n ): () => void {\n let lastSelectedState: SelectorOutput | undefined;\n\n const shouldSendUpdates = (newSelectedState: SelectorOutput) => {\n if (\n lastSelectedState === undefined ||\n !compare(lastSelectedState, newSelectedState)\n ) {\n lastSelectedState = newSelectedState;\n\n return true;\n }\n\n return false;\n };\n\n const selectedCb = (store: Store) => {\n const selected = selector(store);\n const shouldSend = shouldSendUpdates(selected);\n\n if (shouldSend) {\n callback(selector(store));\n }\n };\n observers.add(selectedCb);\n\n return () => observers.delete(selectedCb);\n }\n\n /**\n * A simple observable that proxy store updates to all the subscribers\n * that are interested in reacting to state outside the react component tree.\n */\n const observable = {\n subscribe: subscribeExternal,\n unsubscribe: (callback: (state: Store) => void) => {\n observers.delete(callback);\n },\n broadcast: (state: Store) => {\n observers.forEach((callback) => callback(state));\n },\n } as const;\n\n /**\n * Reads (a selection of) the store state from the nearest `Provider`,\n * re-rendering the component whenever the selected state changes.\n * @param selector - A function that selects the part of the store that is\n * needed by the component. Defaults to the entire store.\n * @param compare - A function used to compare the previous and next\n * selected state; the component only re-renders when it returns `false`.\n * Defaults to the store's `compare` option (see {@link ContextOptions}),\n * or `shallowEqual` from `@pixotope/utils/comparison`.\n * @returns An object containing the selected `state`, a `set` function to\n * update the store, a `get` function equivalent to reading `state`, and a\n * `selector` function to read the entire store without selecting.\n * @example\n * ```tsx\n * const { state, set } = store.useStore((store) => store.user);\n * const { state, set } = store.useStore(\n * (store) => store.user,\n * (first, second) => first.id === second.id\n * );\n * ```\n */\n function useStore<SelectorOutput = Store>(\n selector: (store: Store) => SelectorOutput = (store) =>\n store as unknown as SelectorOutput,\n compare: SelectorOptions<SelectorOutput> = options.compare ?? shallowEqual\n ): UseStoreReturnType<Store, SelectorOutput> {\n const store = useContext(StoreContext);\n const lastSelectedState = useRef<SelectorOutput | undefined>(undefined);\n\n if (!store) {\n throw new Error(\n `[${LIB_NAME}] Store not found. Make sure the component is wrapped in a ${Provider} component.`\n );\n }\n\n const state = useSyncExternalStore(\n store.subscribe,\n () => {\n const selectedState = selector(store.get());\n\n if (\n lastSelectedState.current === undefined ||\n !compare(lastSelectedState.current, selectedState)\n ) {\n lastSelectedState.current = selectedState;\n }\n\n return lastSelectedState.current;\n },\n () => selector(initialState)\n );\n\n return {\n state,\n get: () => state,\n set: store.set,\n selector: () => store.get(),\n };\n }\n\n /**\n * Returns the actions passed to {@link createContextStore}, bound to the\n * current store from the nearest `Provider`. Calling an action does not\n * itself cause a re-render; only the state changes it makes (via `set`)\n * do, and only for components subscribed to the affected state.\n * @returns An object with one bound function per action. Actions declared\n * with an {@link ActionablePayload} parameter take a single payload\n * argument; other actions take no arguments.\n * @example\n * ```tsx\n * const store = createContextStore(initialState, {\n * increment: ({ set, get }) => set({ ...get(), count: get().count + 1 }),\n * incrementBy: ({ set, get }, action: ActionablePayload<number>) =>\n * set({ ...get(), count: get().count + action.payload }),\n * });\n *\n * function Counter() {\n * const { incrementBy } = store.useActions();\n * return <button onClick={() => incrementBy(5)}>+5</button>;\n * }\n * ```\n */\n function useActions() {\n const store = useContext(StoreContext);\n\n if (!store) {\n throw new Error(\n `[${LIB_NAME}] Store not found. Make sure the component is wrapped in a ${Provider} component.`\n );\n }\n\n const actionProxy = useMemo(\n () =>\n new Proxy(actions, {\n get: (target, prop) => {\n const action = target[prop as string];\n\n if (action) {\n return (args: Parameters<typeof action>[\"1\"]) => {\n action(\n {\n set: store.set,\n get: store.get,\n },\n {\n payload: args,\n }\n );\n };\n }\n },\n set: () => {\n throw new Error(`[${LIB_NAME}] Actions cannot be updated`);\n },\n }),\n []\n );\n\n return actionProxy as unknown as ExtractActionKeys<typeof actions>;\n }\n\n /**\n * Returns a function to update the store state from the nearest\n * `Provider`, without subscribing the component to store updates.\n * @returns A function that sets the store state, accepting either the\n * next state directly or an updater function that receives the previous state.\n * @example\n * ```tsx\n * const setStore = store.useSetStore();\n * setStore((prev) => ({ ...prev, count: prev.count + 1 }));\n * ```\n */\n const useSetStore = () => useStore(() => false).set;\n\n return {\n Provider,\n useStore,\n useSetStore,\n useActions,\n subscribe: observable.subscribe,\n unsubscribe: observable.unsubscribe,\n };\n}\n","// src/comparison/shallowEqual.ts\nfunction shallowEqual(objA, objB) {\n if (Object.is(objA, objB)) {\n return true;\n }\n if (typeof objA !== \"object\" || objA === null || typeof objB !== \"object\" || objB === null) {\n return false;\n }\n if (objA instanceof Map && objB instanceof Map) {\n if (objA.size !== objB.size) return false;\n for (const [key, value] of objA) {\n if (!Object.is(value, objB.get(key))) {\n return false;\n }\n }\n return true;\n }\n if (objA instanceof Set && objB instanceof Set) {\n if (objA.size !== objB.size) return false;\n for (const value of objA) {\n if (!objB.has(value)) {\n return false;\n }\n }\n return true;\n }\n const keysA = Object.keys(objA);\n if (keysA.length !== Object.keys(objB).length) {\n return false;\n }\n for (let i = 0; i < keysA.length; i++) {\n if (!Object.prototype.hasOwnProperty.call(objB, keysA[i]) || !Object.is(objA[keysA[i]], objB[keysA[i]])) {\n return false;\n }\n }\n return true;\n}\n\nexport {\n shallowEqual\n};\n"],"mappings":"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","names":["React"]}
package/dist/index.d.cts CHANGED
@@ -1,6 +1,16 @@
1
1
  import React from 'react';
2
2
 
3
+ /**
4
+ * A function used to compare two versions of selected state, to decide
5
+ * whether subscribers should be notified of a change.
6
+ * @param first - The previously selected state.
7
+ * @param second - The newly selected state.
8
+ * @returns `true` if the two values should be treated as equal (no update triggered), `false` otherwise.
9
+ */
3
10
  type SelectorOptions<Selected> = (first: Selected, second: Selected) => boolean;
11
+ /**
12
+ * Options that configure the behavior of a store created with {@link createContextStore}.
13
+ */
4
14
  type ContextOptions = {
5
15
  /**
6
16
  * If true, the store state will be preserved across mounts and un-mounts
@@ -14,42 +24,144 @@ type ContextOptions = {
14
24
  */
15
25
  compare?: SelectorOptions<unknown>;
16
26
  };
27
+ /**
28
+ * The value accepted by a store's `set` function: either the next state
29
+ * directly, or an updater function that receives the previous state and
30
+ * returns the next state.
31
+ */
17
32
  type SetterArgs<Store> = Store | ((prev: Store) => Store);
33
+ /** Maps an actions map (as passed to {@link createContextStore}) to the shape of the object returned by `useActions`. */
18
34
  type ExtractActionKeys<T> = {
19
35
  [K in keyof T]: T[K] extends (stateProps: never, action: infer A) => void | Promise<void> ? A extends ActionablePayload<infer Payload> ? Payload extends undefined ? () => void : (payload: Payload) => void : () => void : never;
20
36
  };
37
+ /** Flattens an intersection/mapped type into a single object type, for cleaner hover tooltips. */
21
38
  type Prettify<T> = {
22
39
  [K in keyof T]: T[K];
23
40
  } & {};
41
+ /**
42
+ * The action argument passed to an action handler, wrapping its payload.
43
+ * Use this as the type of an action's second parameter to give the
44
+ * corresponding {@link createContextStore} action a typed payload, which is
45
+ * then reflected in the function returned by `useActions`.
46
+ * @typeParam Payload - The type of the action's payload. Defaults to `any`.
47
+ * @example
48
+ * ```tsx
49
+ * const store = createContextStore(initialState, {
50
+ * incrementBy: ({ set, get }, action: ActionablePayload<number>) => {
51
+ * set((prev) => ({ ...prev, count: get().count + action.payload }));
52
+ * },
53
+ * });
54
+ * ```
55
+ */
24
56
  type ActionablePayload<Payload = any> = {
25
57
  payload: Payload;
26
58
  };
59
+ /** The state accessors passed as the first argument to every action handler. */
27
60
  type StateActionProps<Store> = {
61
+ /** Updates the store state. */
28
62
  set: (value: SetterArgs<Store>) => void;
63
+ /** Returns the current store state. */
29
64
  get: () => Store;
30
65
  };
66
+ /**
67
+ * A map of action names to action handlers, as passed to {@link createContextStore}.
68
+ * Each handler receives {@link StateActionProps} for reading/writing the
69
+ * store, plus an {@link ActionablePayload} carrying any payload passed by the caller.
70
+ */
31
71
  type Actions<Store> = {
32
72
  [key: string]: (stateProps: StateActionProps<Store>, action: ActionablePayload) => void | Promise<void>;
33
73
  };
74
+ /** The object returned by a store's `useStore` hook. */
34
75
  type UseStoreReturnType<Store, SelectorOutput> = {
76
+ /** The currently selected state. Reading this subscribes the component to updates. */
35
77
  state: SelectorOutput;
78
+ /** Returns the currently selected state. Equivalent to reading `state`. */
36
79
  get: () => SelectorOutput;
80
+ /** Updates the store state. */
37
81
  set: (value: SetterArgs<Store>) => void;
82
+ /** Returns the entire store state, not just the selected slice. */
38
83
  selector: () => Store;
39
84
  };
85
+ /**
86
+ * The object returned by {@link createContextStore}: the `Provider`
87
+ * component plus the hooks and functions used to read, update and
88
+ * subscribe to the store.
89
+ */
40
90
  type ContextReturnType<Store, A extends Actions<Store>> = {
91
+ /** Wraps the part of the component tree that should have access to the store. */
41
92
  Provider: React.FC<React.PropsWithChildren>;
93
+ /**
94
+ * Reads (a selection of) the store state from the nearest `Provider`,
95
+ * re-rendering the component whenever the selected state changes.
96
+ * @param selector - Selects the part of the store the component needs. Defaults to the entire store.
97
+ * @param options - A function used to compare the previous and next selected state. Defaults to the store's `compare` option, or shallow equality.
98
+ * @returns The selected state, plus functions to read and update the store.
99
+ */
42
100
  useStore: <SelectorOutput = Store>(selector?: (store: Store) => SelectorOutput, options?: SelectorOptions<SelectorOutput>) => UseStoreReturnType<Store, SelectorOutput>;
101
+ /**
102
+ * Returns the actions passed to {@link createContextStore}, bound to the
103
+ * current store. Actions declared with an {@link ActionablePayload}
104
+ * parameter take a single payload argument; other actions take none.
105
+ */
43
106
  useActions: () => Prettify<ExtractActionKeys<A>>;
107
+ /**
108
+ * Returns a function to update the store state from the nearest
109
+ * `Provider`, without subscribing the component to store updates.
110
+ */
44
111
  useSetStore: () => (value: SetterArgs<Store>) => void;
45
- useStoreSelector: () => () => Store;
112
+ /**
113
+ * Subscribes to store updates from outside the React component tree.
114
+ * @param selector - Selects the part of the store to watch.
115
+ * @param callback - Called with the selected state whenever it changes.
116
+ * @param options - A function used to compare the previous and next selected state. Defaults to shallow equality.
117
+ * @returns A function to remove the subscription.
118
+ */
46
119
  subscribe: <SelectorOutput = Store>(selector: (store: Store) => SelectorOutput, callback: (state: SelectorOutput) => void, options?: SelectorOptions<SelectorOutput>) => () => void;
120
+ /** Removes a subscription created with `subscribe`. */
47
121
  unsubscribe: (callback: () => void) => void;
48
122
  };
123
+ /**
124
+ * Creates a React context-based store: a `Provider` component plus hooks to
125
+ * read, update and subscribe to its state.
126
+ *
127
+ * Each call creates an independent store with its own React context, so the
128
+ * `Provider` and hooks returned by one call must always be used together.
129
+ * @typeParam Store - The shape of the store state.
130
+ * @typeParam A - The map of named actions available to update the store.
131
+ * @param args - Positional arguments: the `initialState` the store is initialized with, an
132
+ * optional `actions` map of named action handlers used to update the store (see
133
+ * {@link ActionablePayload} for actions that take a payload), and optional `options` to
134
+ * customize the store's behavior.
135
+ * @returns An object with a `Provider` component and hooks to read, update and subscribe to the store's state. See {@link ContextReturnType}.
136
+ * @example
137
+ * ```tsx
138
+ * const store = createContextStore(
139
+ * { count: 0 },
140
+ * {
141
+ * increment: ({ set, get }) => set({ ...get(), count: get().count + 1 }),
142
+ * }
143
+ * );
144
+ *
145
+ * function Counter() {
146
+ * const { state: count } = store.useStore((state) => state.count);
147
+ * const { increment } = store.useActions();
148
+ *
149
+ * return <button onClick={() => increment()}>{count}</button>;
150
+ * }
151
+ *
152
+ * function App() {
153
+ * return (
154
+ * <store.Provider>
155
+ * <Counter />
156
+ * </store.Provider>
157
+ * );
158
+ * }
159
+ * ```
160
+ */
49
161
  declare function createContextStore<Store, A extends Actions<Store> = Actions<Store>>(...args: Extract<A, {
50
162
  payload: A;
51
163
  }> extends {
52
164
  payload: infer Payload;
53
165
  } ? [initialState: Store, actions?: A, options?: ContextOptions] : [initialState: Store, options?: ContextOptions]): ContextReturnType<Store, A>;
54
166
 
55
- export { type ActionablePayload, type ContextOptions, type SelectorOptions, type SetterArgs, createContextStore };
167
+ export { type ActionablePayload, type Actions, type ContextOptions, type ContextReturnType, type ExtractActionKeys, type Prettify, type SelectorOptions, type SetterArgs, type StateActionProps, type UseStoreReturnType, createContextStore };