@thefoxieflow/signalctx 0.0.1 → 0.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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # signal-ctx
1
+ # signalctx
2
2
 
3
3
  A tiny, signal-based state utility for React that **solves the `useContext` re-render problem** using
4
4
  **`useSyncExternalStore`**.
@@ -10,7 +10,7 @@ If you’ve ever had this issue:
10
10
  // updating count re-renders book components 😡
11
11
  ```
12
12
 
13
- `signal-ctx` is designed specifically to fix that.
13
+ `signalctx` is designed specifically to fix that.
14
14
 
15
15
  ---
16
16
 
@@ -57,7 +57,7 @@ So only the components that *actually use* the changed data re-render.
57
57
  ## 📦 Installation
58
58
 
59
59
  ```bash
60
- npm install @thefoxieflow/signal-ctx
60
+ npm install @thefoxieflow/signalctx
61
61
  ```
62
62
 
63
63
  > **Peer dependency:** React 18+
@@ -100,12 +100,12 @@ type Store<T> = {
100
100
 
101
101
  ### `createStore(()=>{})`
102
102
 
103
- ### `useStoreValue(store, selector?)`
103
+ ### `useValue(store, selector?)`
104
104
 
105
105
  Subscribe to a signal.
106
106
 
107
107
  ```tsx
108
- const count = useStoreValue(store, s => s.count)
108
+ const count = useValue(store, s => s.count)
109
109
  ```
110
110
 
111
111
  * Uses `useSyncExternalStore`
@@ -114,12 +114,12 @@ const count = useStoreValue(store, s => s.count)
114
114
 
115
115
  ---
116
116
 
117
- ### `useSetStore(store, selector?)`
117
+ ### `useSet(store, selector?)`
118
118
 
119
119
  Returns a setter function.
120
120
 
121
121
  ```ts
122
- const set = useSetStore(store)
122
+ const set = useSet(store)
123
123
 
124
124
  set(prev => ({ ...prev, count: prev.count + 1 }))
125
125
  ```
@@ -327,7 +327,7 @@ Each store is independent.
327
327
 
328
328
  ## 🌐 Server-Side Rendering (SSR)
329
329
 
330
- `signal-ctx` is SSR-safe.
330
+ `signalctx` is SSR-safe.
331
331
 
332
332
  * Uses `useSyncExternalStore`
333
333
  * Identical snapshot logic on server & client
@@ -368,7 +368,7 @@ MIT
368
368
 
369
369
  ## ⭐ Philosophy
370
370
 
371
- `signal-ctx` is intentionally small.
371
+ `signalctx` is intentionally small.
372
372
 
373
373
  It favors:
374
374
 
@@ -376,4 +376,4 @@ It favors:
376
376
  * Predictable updates
377
377
  * Minimal abstraction
378
378
 
379
- If you understand React, you understand `signal-ctx`.
379
+ If you understand React, you understand `signalctx`.
package/dist/index.d.mts CHANGED
@@ -1,18 +1,38 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { SetStateAction, ReactNode } from 'react';
2
+ import { PropsWithChildren } from 'react';
3
3
 
4
- type Store<T = any> = ReturnType<typeof createStore<T>>;
4
+ type SetStateAction<T> = T | ((prev: T) => T);
5
5
  type Subscriber = () => void;
6
+ /** for controlling store internal state without notifying subscribers
7
+ * @function calling the store internal state reference
8
+ * @method on subscribe to state changes
9
+ * @method set update the state without notifying subscribers
10
+ * @method reset reset the state to initial value without notifying subscribers
11
+ * @method notify manually notify all subscribers
12
+ */
13
+ type InnerStore<T> = {
14
+ (): T;
15
+ on: (sub: Subscriber) => () => void;
16
+ notify: () => void;
17
+ reset: () => void;
18
+ set: (action: SetStateAction<T>) => void;
19
+ };
6
20
  /**
7
- * @description: create a signal with subscribe and update methods
21
+ * @function calling the store returns a copy of the internal state
22
+ * @method on subscribe to state changes
23
+ * @method set update the state with notifying subscribers
24
+ * @method reset reset the state to initial value with notifying subscribers
25
+ * @property $ access to non-notifying methods
8
26
  */
9
- declare function createStore<T = Record<string, any>>(init: () => T): {
27
+ type Store<T> = {
10
28
  (): T;
11
- subscribe(subscriber: Subscriber): () => boolean;
12
- set(action: SetStateAction<T>): void;
29
+ on: (sub: Subscriber) => () => void;
30
+ reset: () => void;
31
+ set: (action: SetStateAction<T>) => void;
32
+ $: InnerStore<T>;
13
33
  };
14
- declare const useStoreValue: <T extends object, Dest = T>(store: Store<T>, selector?: (state: T) => Dest) => Dest;
15
- declare const useSetStore: <T extends object = any, Dest extends object = T>(store: Store<T>, selector?: (state: T) => Dest) => (action: SetStateAction<Dest>) => void;
34
+ declare const useValue: <T extends object, Dest = T>(store: Store<T>, selector?: (state: T) => Dest) => Dest;
35
+ declare const useSet: <T extends object = any, Dest extends object = T>(store: Store<T>, selector?: (state: T) => Dest) => (action: SetStateAction<Dest>) => Dest;
16
36
  type StoreOption = {
17
37
  storeName?: string;
18
38
  };
@@ -21,15 +41,9 @@ declare function createCtx<T extends object>(init: () => T): {
21
41
  provide: (opt?: {
22
42
  value?: T;
23
43
  storeName?: string;
24
- }) => ({ children }: {
25
- children: ReactNode;
26
- }) => react_jsx_runtime.JSX.Element;
27
- useSetter: <Dest extends object = T>(selector?: (state: T) => Dest, opt?: StoreOption) => (action: SetStateAction<Dest>) => void;
28
- useStore: (opt?: StoreOption) => {
29
- (): T;
30
- subscribe(subscriber: Subscriber): () => boolean;
31
- set(action: SetStateAction<T>): void;
32
- };
44
+ }) => ({ children }: PropsWithChildren) => react_jsx_runtime.JSX.Element;
45
+ useSet: <Dest extends object = T>(selector?: (state: T) => Dest, opt?: StoreOption) => (action: SetStateAction<Dest>) => Dest;
46
+ useStore: (opt?: StoreOption) => Store<T>;
33
47
  };
34
48
 
35
- export { type Store, type StoreOption, type Subscriber, createCtx, useSetStore, useStoreValue };
49
+ export { type InnerStore, type SetStateAction, type Store, type Subscriber, createCtx, useSet, useValue };
package/dist/index.d.ts CHANGED
@@ -1,18 +1,38 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { SetStateAction, ReactNode } from 'react';
2
+ import { PropsWithChildren } from 'react';
3
3
 
4
- type Store<T = any> = ReturnType<typeof createStore<T>>;
4
+ type SetStateAction<T> = T | ((prev: T) => T);
5
5
  type Subscriber = () => void;
6
+ /** for controlling store internal state without notifying subscribers
7
+ * @function calling the store internal state reference
8
+ * @method on subscribe to state changes
9
+ * @method set update the state without notifying subscribers
10
+ * @method reset reset the state to initial value without notifying subscribers
11
+ * @method notify manually notify all subscribers
12
+ */
13
+ type InnerStore<T> = {
14
+ (): T;
15
+ on: (sub: Subscriber) => () => void;
16
+ notify: () => void;
17
+ reset: () => void;
18
+ set: (action: SetStateAction<T>) => void;
19
+ };
6
20
  /**
7
- * @description: create a signal with subscribe and update methods
21
+ * @function calling the store returns a copy of the internal state
22
+ * @method on subscribe to state changes
23
+ * @method set update the state with notifying subscribers
24
+ * @method reset reset the state to initial value with notifying subscribers
25
+ * @property $ access to non-notifying methods
8
26
  */
9
- declare function createStore<T = Record<string, any>>(init: () => T): {
27
+ type Store<T> = {
10
28
  (): T;
11
- subscribe(subscriber: Subscriber): () => boolean;
12
- set(action: SetStateAction<T>): void;
29
+ on: (sub: Subscriber) => () => void;
30
+ reset: () => void;
31
+ set: (action: SetStateAction<T>) => void;
32
+ $: InnerStore<T>;
13
33
  };
14
- declare const useStoreValue: <T extends object, Dest = T>(store: Store<T>, selector?: (state: T) => Dest) => Dest;
15
- declare const useSetStore: <T extends object = any, Dest extends object = T>(store: Store<T>, selector?: (state: T) => Dest) => (action: SetStateAction<Dest>) => void;
34
+ declare const useValue: <T extends object, Dest = T>(store: Store<T>, selector?: (state: T) => Dest) => Dest;
35
+ declare const useSet: <T extends object = any, Dest extends object = T>(store: Store<T>, selector?: (state: T) => Dest) => (action: SetStateAction<Dest>) => Dest;
16
36
  type StoreOption = {
17
37
  storeName?: string;
18
38
  };
@@ -21,15 +41,9 @@ declare function createCtx<T extends object>(init: () => T): {
21
41
  provide: (opt?: {
22
42
  value?: T;
23
43
  storeName?: string;
24
- }) => ({ children }: {
25
- children: ReactNode;
26
- }) => react_jsx_runtime.JSX.Element;
27
- useSetter: <Dest extends object = T>(selector?: (state: T) => Dest, opt?: StoreOption) => (action: SetStateAction<Dest>) => void;
28
- useStore: (opt?: StoreOption) => {
29
- (): T;
30
- subscribe(subscriber: Subscriber): () => boolean;
31
- set(action: SetStateAction<T>): void;
32
- };
44
+ }) => ({ children }: PropsWithChildren) => react_jsx_runtime.JSX.Element;
45
+ useSet: <Dest extends object = T>(selector?: (state: T) => Dest, opt?: StoreOption) => (action: SetStateAction<Dest>) => Dest;
46
+ useStore: (opt?: StoreOption) => Store<T>;
33
47
  };
34
48
 
35
- export { type Store, type StoreOption, type Subscriber, createCtx, useSetStore, useStoreValue };
49
+ export { type InnerStore, type SetStateAction, type Store, type Subscriber, createCtx, useSet, useValue };
package/dist/index.js CHANGED
@@ -21,48 +21,71 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  createCtx: () => createCtx,
24
- useSetStore: () => useSetStore,
25
- useStoreValue: () => useStoreValue
24
+ useSet: () => useSet,
25
+ useValue: () => useValue
26
26
  });
27
27
  module.exports = __toCommonJS(index_exports);
28
28
  var import_react = require("react");
29
29
  var import_jsx_runtime = require("react/jsx-runtime");
30
- var applyAction = (target, action) => {
31
- return typeof action === "function" ? action(target) : action;
30
+ var createInnerStore = (init) => {
31
+ let ref;
32
+ const subs = /* @__PURE__ */ new Set();
33
+ const store = () => ref || (ref = init());
34
+ store.set = (action) => {
35
+ apply(store(), action);
36
+ };
37
+ store.reset = () => {
38
+ ref = init();
39
+ };
40
+ store.notify = () => {
41
+ subs.forEach((sub) => sub());
42
+ };
43
+ store.on = (sub) => {
44
+ subs.add(sub);
45
+ return () => subs.delete(sub);
46
+ };
47
+ return store;
48
+ };
49
+ var isFunction = (val) => typeof val === "function";
50
+ var setTargetValue = (target, newVal) => {
51
+ if (Object.is(target, newVal)) return target;
52
+ Object.keys(target).forEach((k) => delete target[k]);
53
+ Object.keys(newVal).forEach((k) => target[k] = newVal[k]);
54
+ return target;
55
+ };
56
+ var apply = (target, action) => {
57
+ const val = isFunction(action) ? action(target) : action;
58
+ return setTargetValue(target, val);
32
59
  };
33
60
  function createStore(init) {
34
- let val;
35
- let isInit = false;
36
- const subscribers = /* @__PURE__ */ new Set();
37
- const get = () => {
38
- if (!isInit) {
39
- val = init();
40
- isInit = true;
41
- }
42
- return val;
61
+ const $ = createInnerStore(init);
62
+ const store = () => {
63
+ const ref = $();
64
+ return { ...ref };
43
65
  };
44
- get.subscribe = (subscriber) => {
45
- subscribers.add(subscriber);
46
- const unsubscribe = () => subscribers.delete(subscriber);
47
- return unsubscribe;
66
+ store.reset = () => {
67
+ $.reset();
68
+ $.notify();
48
69
  };
49
- get.set = (action) => {
50
- val = applyAction(val, action);
51
- subscribers.forEach((subscriber) => subscriber());
70
+ store.set = (action) => {
71
+ $.set(action);
72
+ $.notify();
52
73
  };
53
- return get;
74
+ store.on = $.on;
75
+ store.$ = $;
76
+ return store;
54
77
  }
55
- var useStoreValue = (store, selector) => {
56
- const getSnapShot = () => selector ? selector(store()) : store();
57
- const use = (0, import_react.useSyncExternalStore)(store.subscribe, getSnapShot, getSnapShot);
58
- return use;
78
+ var useValue = (store, selector) => {
79
+ const get = () => selector ? selector(store()) : store();
80
+ return (0, import_react.useSyncExternalStore)(store.on, get, get);
59
81
  };
60
- var useSetStore = (store, selector) => {
82
+ var useSet = (store, selector) => {
61
83
  const setter = (action) => {
62
84
  const current = store();
63
85
  const target = selector ? selector(current) : current;
64
- applyAction(target, action);
86
+ apply(target, action);
65
87
  store.set(current);
88
+ return target;
66
89
  };
67
90
  return setter;
68
91
  };
@@ -70,29 +93,22 @@ function createCtx(init) {
70
93
  const ctx = (0, import_react.createContext)(createStore(init));
71
94
  const stores = /* @__PURE__ */ new Map();
72
95
  const initStore = (name, init2) => {
73
- if (!stores.has(name)) {
74
- const store = createStore(init2);
75
- stores.set(name, store);
76
- }
96
+ if (!stores.has(name)) stores.set(name, createStore(init2));
77
97
  return stores.get(name);
78
98
  };
79
99
  const useStore = (opt) => {
80
100
  const store = (0, import_react.useContext)(ctx);
81
- if (opt?.storeName && stores.has(opt.storeName)) {
82
- return stores.get(opt.storeName);
83
- }
101
+ if (opt?.storeName && stores.has(opt.storeName)) return stores.get(opt.storeName);
84
102
  if (!store) throw new Error("useCtx must be used within a Provider");
85
103
  return store;
86
104
  };
87
105
  const provide = (opt = {}) => {
88
- const value = opt.value || init();
106
+ const storeInit = opt.value ? () => opt.value : init;
89
107
  const storeName = opt.storeName || "ctx__store";
90
108
  const Provider = ({ children }) => {
91
- const store = initStore(storeName, () => value);
92
- (0, import_react.useEffect)(() => {
93
- return () => {
94
- stores.delete(storeName);
95
- };
109
+ const store = initStore(storeName, storeInit);
110
+ (0, import_react.useEffect)(() => () => {
111
+ stores.delete(storeName);
96
112
  }, []);
97
113
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ctx.Provider, { value: store, children });
98
114
  };
@@ -100,20 +116,20 @@ function createCtx(init) {
100
116
  };
101
117
  const use = (selector, opt) => {
102
118
  const store = useStore(opt);
103
- return useStoreValue(store, selector);
119
+ return useValue(store, selector);
104
120
  };
105
121
  const useSetter = (selector, opt) => {
106
122
  const store = useStore(opt);
107
- return useSetStore(store, selector);
123
+ return useSet(store, selector);
108
124
  };
109
125
  use.provide = provide;
110
- use.useSetter = useSetter;
126
+ use.useSet = useSetter;
111
127
  use.useStore = useStore;
112
128
  return use;
113
129
  }
114
130
  // Annotate the CommonJS export names for ESM import in node:
115
131
  0 && (module.exports = {
116
132
  createCtx,
117
- useSetStore,
118
- useStoreValue
133
+ useSet,
134
+ useValue
119
135
  });
package/dist/index.mjs CHANGED
@@ -6,42 +6,65 @@ import {
6
6
  useSyncExternalStore
7
7
  } from "react";
8
8
  import { jsx } from "react/jsx-runtime";
9
- var applyAction = (target, action) => {
10
- return typeof action === "function" ? action(target) : action;
9
+ var createInnerStore = (init) => {
10
+ let ref;
11
+ const subs = /* @__PURE__ */ new Set();
12
+ const store = () => ref || (ref = init());
13
+ store.set = (action) => {
14
+ apply(store(), action);
15
+ };
16
+ store.reset = () => {
17
+ ref = init();
18
+ };
19
+ store.notify = () => {
20
+ subs.forEach((sub) => sub());
21
+ };
22
+ store.on = (sub) => {
23
+ subs.add(sub);
24
+ return () => subs.delete(sub);
25
+ };
26
+ return store;
27
+ };
28
+ var isFunction = (val) => typeof val === "function";
29
+ var setTargetValue = (target, newVal) => {
30
+ if (Object.is(target, newVal)) return target;
31
+ Object.keys(target).forEach((k) => delete target[k]);
32
+ Object.keys(newVal).forEach((k) => target[k] = newVal[k]);
33
+ return target;
34
+ };
35
+ var apply = (target, action) => {
36
+ const val = isFunction(action) ? action(target) : action;
37
+ return setTargetValue(target, val);
11
38
  };
12
39
  function createStore(init) {
13
- let val;
14
- let isInit = false;
15
- const subscribers = /* @__PURE__ */ new Set();
16
- const get = () => {
17
- if (!isInit) {
18
- val = init();
19
- isInit = true;
20
- }
21
- return val;
40
+ const $ = createInnerStore(init);
41
+ const store = () => {
42
+ const ref = $();
43
+ return { ...ref };
22
44
  };
23
- get.subscribe = (subscriber) => {
24
- subscribers.add(subscriber);
25
- const unsubscribe = () => subscribers.delete(subscriber);
26
- return unsubscribe;
45
+ store.reset = () => {
46
+ $.reset();
47
+ $.notify();
27
48
  };
28
- get.set = (action) => {
29
- val = applyAction(val, action);
30
- subscribers.forEach((subscriber) => subscriber());
49
+ store.set = (action) => {
50
+ $.set(action);
51
+ $.notify();
31
52
  };
32
- return get;
53
+ store.on = $.on;
54
+ store.$ = $;
55
+ return store;
33
56
  }
34
- var useStoreValue = (store, selector) => {
35
- const getSnapShot = () => selector ? selector(store()) : store();
36
- const use = useSyncExternalStore(store.subscribe, getSnapShot, getSnapShot);
37
- return use;
57
+ var useValue = (store, selector) => {
58
+ const get = () => selector ? selector(store()) : store();
59
+ return useSyncExternalStore(store.on, get, get);
38
60
  };
39
- var useSetStore = (store, selector) => {
61
+ var useSet = (store, selector) => {
40
62
  const setter = (action) => {
41
63
  const current = store();
42
64
  const target = selector ? selector(current) : current;
43
- applyAction(target, action);
65
+ apply(target, action);
44
66
  store.set(current);
67
+ return target;
45
68
  };
46
69
  return setter;
47
70
  };
@@ -49,29 +72,22 @@ function createCtx(init) {
49
72
  const ctx = createContext(createStore(init));
50
73
  const stores = /* @__PURE__ */ new Map();
51
74
  const initStore = (name, init2) => {
52
- if (!stores.has(name)) {
53
- const store = createStore(init2);
54
- stores.set(name, store);
55
- }
75
+ if (!stores.has(name)) stores.set(name, createStore(init2));
56
76
  return stores.get(name);
57
77
  };
58
78
  const useStore = (opt) => {
59
79
  const store = useContext(ctx);
60
- if (opt?.storeName && stores.has(opt.storeName)) {
61
- return stores.get(opt.storeName);
62
- }
80
+ if (opt?.storeName && stores.has(opt.storeName)) return stores.get(opt.storeName);
63
81
  if (!store) throw new Error("useCtx must be used within a Provider");
64
82
  return store;
65
83
  };
66
84
  const provide = (opt = {}) => {
67
- const value = opt.value || init();
85
+ const storeInit = opt.value ? () => opt.value : init;
68
86
  const storeName = opt.storeName || "ctx__store";
69
87
  const Provider = ({ children }) => {
70
- const store = initStore(storeName, () => value);
71
- useEffect(() => {
72
- return () => {
73
- stores.delete(storeName);
74
- };
88
+ const store = initStore(storeName, storeInit);
89
+ useEffect(() => () => {
90
+ stores.delete(storeName);
75
91
  }, []);
76
92
  return /* @__PURE__ */ jsx(ctx.Provider, { value: store, children });
77
93
  };
@@ -79,19 +95,19 @@ function createCtx(init) {
79
95
  };
80
96
  const use = (selector, opt) => {
81
97
  const store = useStore(opt);
82
- return useStoreValue(store, selector);
98
+ return useValue(store, selector);
83
99
  };
84
100
  const useSetter = (selector, opt) => {
85
101
  const store = useStore(opt);
86
- return useSetStore(store, selector);
102
+ return useSet(store, selector);
87
103
  };
88
104
  use.provide = provide;
89
- use.useSetter = useSetter;
105
+ use.useSet = useSetter;
90
106
  use.useStore = useStore;
91
107
  return use;
92
108
  }
93
109
  export {
94
110
  createCtx,
95
- useSetStore,
96
- useStoreValue
111
+ useSet,
112
+ useValue
97
113
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thefoxieflow/signalctx",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "description": "Lightweight signal-based React context store",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",