@thefoxieflow/signalctx 0.0.1 → 0.1.1

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
  ```
@@ -128,7 +128,7 @@ Scoped update:
128
128
 
129
129
  ```ts
130
130
  // book must be object for selector
131
- const setBook = useSetSignal(store, s => s.book)
131
+ const setBook = useSet(store, s => s.book)
132
132
 
133
133
 
134
134
  // put: update an object
@@ -219,7 +219,7 @@ function Book() {
219
219
 
220
220
  ```tsx
221
221
  function Increment() {
222
- const set = useStore.useSetter()
222
+ const set = useStore.useSet()
223
223
 
224
224
  return (
225
225
  <button onClick={() =>
@@ -308,7 +308,7 @@ function AppB(){
308
308
  const storeABook = useStore(s => s.book,{ storeName :"storeA" })
309
309
 
310
310
  // same apply to setter
311
- const setBookStoreA = useStore.useSetter((s)=>s.book, {storeName : "storeA"})
311
+ const setBookStoreA = useStore.useSet((s)=>s.book, {storeName : "storeA"})
312
312
 
313
313
  const handleClick = (text)=>{
314
314
  setCountStoreA((s) => {
@@ -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
+ `Signal Ctx` 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,35 +1,36 @@
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 SetAction<T> = T | ((prev: T) => void);
5
5
  type Subscriber = () => void;
6
- /**
7
- * @description: create a signal with subscribe and update methods
8
- */
9
- declare function createStore<T = Record<string, any>>(init: () => T): {
6
+ type Signal<T> = {
10
7
  (): T;
11
- subscribe(subscriber: Subscriber): () => boolean;
12
- set(action: SetStateAction<T>): void;
8
+ on: (sub: Subscriber) => () => void;
9
+ notify: () => void;
10
+ reset: () => void;
11
+ set: (action: SetAction<T>) => void;
12
+ };
13
+ type Store<T> = {
14
+ (): T;
15
+ on: (sub: Subscriber) => () => void;
16
+ reset: () => void;
17
+ set: (action: SetAction<T>) => void;
18
+ $: Signal<T>;
13
19
  };
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;
16
20
  type StoreOption = {
17
21
  storeName?: string;
18
22
  };
23
+ declare const newStore: <T extends Record<string, any>>(init: () => T) => Store<T>;
24
+ declare const useValue: <T extends object, Dest = T>(store: Store<T>, selector?: (state: T) => Dest) => Dest;
25
+ declare const useSet: <T extends object = any, Dest extends object = T>(store: Store<T>, selector?: (state: T) => Dest) => (action: SetAction<Dest>) => void;
19
26
  declare function createCtx<T extends object>(init: () => T): {
20
27
  <Dest = any>(selector: (state: T) => Dest, opt?: StoreOption): Dest;
21
- provide: (opt?: {
28
+ Provider: ({ children, value, storeName, }: PropsWithChildren<{
22
29
  value?: T;
23
30
  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
- };
31
+ }>) => react_jsx_runtime.JSX.Element;
32
+ useSet: <Dest extends object = T>(selector?: (state: T) => Dest, opt?: StoreOption) => (action: SetAction<Dest>) => void;
33
+ useStore: (opt?: StoreOption) => Store<T>;
33
34
  };
34
35
 
35
- export { type Store, type StoreOption, type Subscriber, createCtx, useSetStore, useStoreValue };
36
+ export { type SetAction, type Signal, type Store, type Subscriber, createCtx, newStore, useSet, useValue };
package/dist/index.d.ts CHANGED
@@ -1,35 +1,36 @@
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 SetAction<T> = T | ((prev: T) => void);
5
5
  type Subscriber = () => void;
6
- /**
7
- * @description: create a signal with subscribe and update methods
8
- */
9
- declare function createStore<T = Record<string, any>>(init: () => T): {
6
+ type Signal<T> = {
10
7
  (): T;
11
- subscribe(subscriber: Subscriber): () => boolean;
12
- set(action: SetStateAction<T>): void;
8
+ on: (sub: Subscriber) => () => void;
9
+ notify: () => void;
10
+ reset: () => void;
11
+ set: (action: SetAction<T>) => void;
12
+ };
13
+ type Store<T> = {
14
+ (): T;
15
+ on: (sub: Subscriber) => () => void;
16
+ reset: () => void;
17
+ set: (action: SetAction<T>) => void;
18
+ $: Signal<T>;
13
19
  };
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;
16
20
  type StoreOption = {
17
21
  storeName?: string;
18
22
  };
23
+ declare const newStore: <T extends Record<string, any>>(init: () => T) => Store<T>;
24
+ declare const useValue: <T extends object, Dest = T>(store: Store<T>, selector?: (state: T) => Dest) => Dest;
25
+ declare const useSet: <T extends object = any, Dest extends object = T>(store: Store<T>, selector?: (state: T) => Dest) => (action: SetAction<Dest>) => void;
19
26
  declare function createCtx<T extends object>(init: () => T): {
20
27
  <Dest = any>(selector: (state: T) => Dest, opt?: StoreOption): Dest;
21
- provide: (opt?: {
28
+ Provider: ({ children, value, storeName, }: PropsWithChildren<{
22
29
  value?: T;
23
30
  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
- };
31
+ }>) => react_jsx_runtime.JSX.Element;
32
+ useSet: <Dest extends object = T>(selector?: (state: T) => Dest, opt?: StoreOption) => (action: SetAction<Dest>) => void;
33
+ useStore: (opt?: StoreOption) => Store<T>;
33
34
  };
34
35
 
35
- export { type Store, type StoreOption, type Subscriber, createCtx, useSetStore, useStoreValue };
36
+ export { type SetAction, type Signal, type Store, type Subscriber, createCtx, newStore, useSet, useValue };
package/dist/index.js CHANGED
@@ -21,99 +21,127 @@ 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
+ newStore: () => newStore,
25
+ useSet: () => useSet,
26
+ useValue: () => useValue
26
27
  });
27
28
  module.exports = __toCommonJS(index_exports);
28
29
  var import_react = require("react");
29
30
  var import_jsx_runtime = require("react/jsx-runtime");
30
- var applyAction = (target, action) => {
31
- return typeof action === "function" ? action(target) : action;
32
- };
33
- 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;
31
+ var newSignal = (init) => {
32
+ let ref;
33
+ const subs = /* @__PURE__ */ new Set();
34
+ const store = () => ref || (ref = init());
35
+ store.set = (action) => {
36
+ act(store(), action);
43
37
  };
44
- get.subscribe = (subscriber) => {
45
- subscribers.add(subscriber);
46
- const unsubscribe = () => subscribers.delete(subscriber);
47
- return unsubscribe;
38
+ store.reset = () => {
39
+ ref = init();
48
40
  };
49
- get.set = (action) => {
50
- val = applyAction(val, action);
51
- subscribers.forEach((subscriber) => subscriber());
41
+ store.notify = () => {
42
+ subs.forEach((sub) => sub());
52
43
  };
53
- return get;
54
- }
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;
44
+ store.on = (sub) => {
45
+ subs.add(sub);
46
+ return () => subs.delete(sub);
47
+ };
48
+ return store;
49
+ };
50
+ var replaceIfDiff = (target, newVal) => {
51
+ if (Object.is(target, newVal)) return;
52
+ Object.keys(target).forEach((k) => delete target[k]);
53
+ Object.keys(newVal).forEach((k) => target[k] = newVal[k]);
54
+ };
55
+ var act = (target, action) => {
56
+ if (typeof action === "function") {
57
+ action(target);
58
+ } else {
59
+ replaceIfDiff(target, action);
60
+ }
61
+ };
62
+ var newStore = (init) => {
63
+ const $ = newSignal(init);
64
+ const store = () => $();
65
+ store.$ = $;
66
+ store.on = $.on;
67
+ store.reset = () => {
68
+ $.reset();
69
+ $.notify();
70
+ };
71
+ store.set = (action) => {
72
+ $.set(action);
73
+ $.notify();
74
+ };
75
+ return store;
76
+ };
77
+ var useValue = (store, selector) => {
78
+ const get = () => selector ? selector(store()) : store();
79
+ return (0, import_react.useSyncExternalStore)(store.on, get, get);
59
80
  };
60
- var useSetStore = (store, selector) => {
81
+ var useSet = (store, selector) => {
61
82
  const setter = (action) => {
62
- const current = store();
63
- const target = selector ? selector(current) : current;
64
- applyAction(target, action);
65
- store.set(current);
83
+ const ref = store.$();
84
+ const targetRef = selector ? selector(ref) : ref;
85
+ act(targetRef, action);
86
+ store.$.notify();
66
87
  };
67
88
  return setter;
68
89
  };
69
90
  function createCtx(init) {
70
- const ctx = (0, import_react.createContext)(createStore(init));
91
+ const ctx = (0, import_react.createContext)(null);
71
92
  const stores = /* @__PURE__ */ new Map();
72
- const initStore = (name, init2) => {
93
+ const initStore = (init2, name) => {
94
+ if (!name) return newStore(init2);
73
95
  if (!stores.has(name)) {
74
- const store = createStore(init2);
75
- stores.set(name, store);
96
+ stores.set(name, newStore(init2));
76
97
  }
77
98
  return stores.get(name);
78
99
  };
79
100
  const useStore = (opt) => {
80
101
  const store = (0, import_react.useContext)(ctx);
81
- if (opt?.storeName && stores.has(opt.storeName)) {
82
- return stores.get(opt.storeName);
83
- }
102
+ if (opt?.storeName && stores.has(opt.storeName)) return stores.get(opt.storeName);
84
103
  if (!store) throw new Error("useCtx must be used within a Provider");
85
104
  return store;
86
105
  };
87
- const provide = (opt = {}) => {
88
- const value = opt.value || init();
89
- const storeName = opt.storeName || "ctx__store";
90
- const Provider = ({ children }) => {
91
- const store = initStore(storeName, () => value);
92
- (0, import_react.useEffect)(() => {
93
- return () => {
94
- stores.delete(storeName);
95
- };
96
- }, []);
97
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ctx.Provider, { value: store, children });
98
- };
99
- return Provider;
106
+ const Provider = ({
107
+ children,
108
+ value,
109
+ storeName
110
+ }) => {
111
+ const [store, name] = (0, import_react.useMemo)(() => {
112
+ const storeInit = value ? () => value : init;
113
+ const store2 = initStore(storeInit, storeName);
114
+ return [store2, storeName];
115
+ }, [value, storeName]);
116
+ (0, import_react.useEffect)(
117
+ () => () => {
118
+ if (name) stores.delete(name);
119
+ },
120
+ [store, name]
121
+ );
122
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ctx.Provider, { value: store, children: [
123
+ " ",
124
+ children,
125
+ " "
126
+ ] });
100
127
  };
101
- const use = (selector, opt) => {
128
+ const useSelect = (selector, opt) => {
102
129
  const store = useStore(opt);
103
- return useStoreValue(store, selector);
130
+ return useValue(store, selector);
104
131
  };
105
132
  const useSetter = (selector, opt) => {
106
133
  const store = useStore(opt);
107
- return useSetStore(store, selector);
134
+ return useSet(store, selector);
108
135
  };
109
- use.provide = provide;
110
- use.useSetter = useSetter;
111
- use.useStore = useStore;
112
- return use;
136
+ useSelect.Provider = Provider;
137
+ useSelect.useSet = useSetter;
138
+ useSelect.useStore = useStore;
139
+ return useSelect;
113
140
  }
114
141
  // Annotate the CommonJS export names for ESM import in node:
115
142
  0 && (module.exports = {
116
143
  createCtx,
117
- useSetStore,
118
- useStoreValue
144
+ newStore,
145
+ useSet,
146
+ useValue
119
147
  });
package/dist/index.mjs CHANGED
@@ -3,95 +3,123 @@ import {
3
3
  createContext,
4
4
  useContext,
5
5
  useEffect,
6
+ useMemo,
6
7
  useSyncExternalStore
7
8
  } from "react";
8
- import { jsx } from "react/jsx-runtime";
9
- var applyAction = (target, action) => {
10
- return typeof action === "function" ? action(target) : action;
11
- };
12
- 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;
9
+ import { jsxs } from "react/jsx-runtime";
10
+ var newSignal = (init) => {
11
+ let ref;
12
+ const subs = /* @__PURE__ */ new Set();
13
+ const store = () => ref || (ref = init());
14
+ store.set = (action) => {
15
+ act(store(), action);
22
16
  };
23
- get.subscribe = (subscriber) => {
24
- subscribers.add(subscriber);
25
- const unsubscribe = () => subscribers.delete(subscriber);
26
- return unsubscribe;
17
+ store.reset = () => {
18
+ ref = init();
27
19
  };
28
- get.set = (action) => {
29
- val = applyAction(val, action);
30
- subscribers.forEach((subscriber) => subscriber());
20
+ store.notify = () => {
21
+ subs.forEach((sub) => sub());
31
22
  };
32
- return get;
33
- }
34
- var useStoreValue = (store, selector) => {
35
- const getSnapShot = () => selector ? selector(store()) : store();
36
- const use = useSyncExternalStore(store.subscribe, getSnapShot, getSnapShot);
37
- return use;
23
+ store.on = (sub) => {
24
+ subs.add(sub);
25
+ return () => subs.delete(sub);
26
+ };
27
+ return store;
28
+ };
29
+ var replaceIfDiff = (target, newVal) => {
30
+ if (Object.is(target, newVal)) return;
31
+ Object.keys(target).forEach((k) => delete target[k]);
32
+ Object.keys(newVal).forEach((k) => target[k] = newVal[k]);
33
+ };
34
+ var act = (target, action) => {
35
+ if (typeof action === "function") {
36
+ action(target);
37
+ } else {
38
+ replaceIfDiff(target, action);
39
+ }
38
40
  };
39
- var useSetStore = (store, selector) => {
41
+ var newStore = (init) => {
42
+ const $ = newSignal(init);
43
+ const store = () => $();
44
+ store.$ = $;
45
+ store.on = $.on;
46
+ store.reset = () => {
47
+ $.reset();
48
+ $.notify();
49
+ };
50
+ store.set = (action) => {
51
+ $.set(action);
52
+ $.notify();
53
+ };
54
+ return store;
55
+ };
56
+ var useValue = (store, selector) => {
57
+ const get = () => selector ? selector(store()) : store();
58
+ return useSyncExternalStore(store.on, get, get);
59
+ };
60
+ var useSet = (store, selector) => {
40
61
  const setter = (action) => {
41
- const current = store();
42
- const target = selector ? selector(current) : current;
43
- applyAction(target, action);
44
- store.set(current);
62
+ const ref = store.$();
63
+ const targetRef = selector ? selector(ref) : ref;
64
+ act(targetRef, action);
65
+ store.$.notify();
45
66
  };
46
67
  return setter;
47
68
  };
48
69
  function createCtx(init) {
49
- const ctx = createContext(createStore(init));
70
+ const ctx = createContext(null);
50
71
  const stores = /* @__PURE__ */ new Map();
51
- const initStore = (name, init2) => {
72
+ const initStore = (init2, name) => {
73
+ if (!name) return newStore(init2);
52
74
  if (!stores.has(name)) {
53
- const store = createStore(init2);
54
- stores.set(name, store);
75
+ stores.set(name, newStore(init2));
55
76
  }
56
77
  return stores.get(name);
57
78
  };
58
79
  const useStore = (opt) => {
59
80
  const store = useContext(ctx);
60
- if (opt?.storeName && stores.has(opt.storeName)) {
61
- return stores.get(opt.storeName);
62
- }
81
+ if (opt?.storeName && stores.has(opt.storeName)) return stores.get(opt.storeName);
63
82
  if (!store) throw new Error("useCtx must be used within a Provider");
64
83
  return store;
65
84
  };
66
- const provide = (opt = {}) => {
67
- const value = opt.value || init();
68
- const storeName = opt.storeName || "ctx__store";
69
- const Provider = ({ children }) => {
70
- const store = initStore(storeName, () => value);
71
- useEffect(() => {
72
- return () => {
73
- stores.delete(storeName);
74
- };
75
- }, []);
76
- return /* @__PURE__ */ jsx(ctx.Provider, { value: store, children });
77
- };
78
- return Provider;
85
+ const Provider = ({
86
+ children,
87
+ value,
88
+ storeName
89
+ }) => {
90
+ const [store, name] = useMemo(() => {
91
+ const storeInit = value ? () => value : init;
92
+ const store2 = initStore(storeInit, storeName);
93
+ return [store2, storeName];
94
+ }, [value, storeName]);
95
+ useEffect(
96
+ () => () => {
97
+ if (name) stores.delete(name);
98
+ },
99
+ [store, name]
100
+ );
101
+ return /* @__PURE__ */ jsxs(ctx.Provider, { value: store, children: [
102
+ " ",
103
+ children,
104
+ " "
105
+ ] });
79
106
  };
80
- const use = (selector, opt) => {
107
+ const useSelect = (selector, opt) => {
81
108
  const store = useStore(opt);
82
- return useStoreValue(store, selector);
109
+ return useValue(store, selector);
83
110
  };
84
111
  const useSetter = (selector, opt) => {
85
112
  const store = useStore(opt);
86
- return useSetStore(store, selector);
113
+ return useSet(store, selector);
87
114
  };
88
- use.provide = provide;
89
- use.useSetter = useSetter;
90
- use.useStore = useStore;
91
- return use;
115
+ useSelect.Provider = Provider;
116
+ useSelect.useSet = useSetter;
117
+ useSelect.useStore = useStore;
118
+ return useSelect;
92
119
  }
93
120
  export {
94
121
  createCtx,
95
- useSetStore,
96
- useStoreValue
122
+ newStore,
123
+ useSet,
124
+ useValue
97
125
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thefoxieflow/signalctx",
3
- "version": "0.0.1",
3
+ "version": "0.1.1",
4
4
  "description": "Lightweight signal-based React context store",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",