@violetflux/kerros 0.1.7 → 0.1.9

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.de.md CHANGED
@@ -34,14 +34,18 @@ Installiere @violetflux/kerros mit dem Paketmanager dieses Projekts und führe d
34
34
  import { createStore } from '@violetflux/kerros'
35
35
  import { useState } from 'react'
36
36
 
37
- export const [useCounter, CounterProvider] = createStore(() => {
37
+ function useCounterModel() {
38
38
  const [count, setCount] = useState(0)
39
39
  return { count, setCount }
40
- })
40
+ }
41
+
42
+ export const [useCounter, CounterProvider] = createStore(useCounterModel)
41
43
  ```
42
44
 
43
45
  Im Store Hook können weiterhin `useState`, `useReducer`, Context, SDK Hooks und eigene Hooks verwendet werden.
44
46
 
47
+ Definiere den Initializer als benannten Hook auf Modulebene, zum Beispiel `useCounterModel`. Anonyme Initializer funktionieren weiterhin zur Laufzeit, werden vom React Compiler im `infer`-Modus aber nicht automatisch als Hooks kompiliert.
48
+
45
49
  ## Provider einbinden und Werte auswählen
46
50
 
47
51
  ```tsx
@@ -79,12 +83,16 @@ Ohne verstecktes Modul-Singleton kann derselbe Provider mehrfach eingebunden, ü
79
83
 
80
84
  ```ts
81
85
  function createStore<TStore, TProps = Record<never, never>>(
82
- useStoreValue: (props: TProps) => TStore,
86
+ useModel: (props: TProps) => TStore,
83
87
  ): readonly [StoreHook<TStore>, StoreProvider<TProps>]
88
+
89
+ const [useStream, StreamProvider] = bindStore<Stream>('Stream')
84
90
  ```
85
91
 
86
92
  Der zurückgegebene Store Hook verlangt einen Selektor, der ein Objekt liefert. Außerhalb des passenden Providers wird ein verständlicher Fehler ausgelöst. Strict Mode und Server Rendering werden unterstützt.
87
93
 
94
+ `bindStore` ist eine fortgeschrittene Integration ausschließlich für einen bestehenden Headless External Store. Für normalen Hook-Zustand bleibt `createStore` die richtige Wahl. Der Context enthält nur die ursprüngliche Store-Instanz; Verbraucher verwenden `getSnapshot` und `subscribe` direkt.
95
+
88
96
  ## Dokumentation
89
97
 
90
98
  - [Einführung](https://violetflux.github.io/kerros/de/guide/introduction)
package/README.es.md CHANGED
@@ -34,14 +34,18 @@ Instala @violetflux/kerros con el gestor de paquetes del proyecto y después eje
34
34
  import { createStore } from '@violetflux/kerros'
35
35
  import { useState } from 'react'
36
36
 
37
- export const [useCounter, CounterProvider] = createStore(() => {
37
+ function useCounterModel() {
38
38
  const [count, setCount] = useState(0)
39
39
  return { count, setCount }
40
- })
40
+ }
41
+
42
+ export const [useCounter, CounterProvider] = createStore(useCounterModel)
41
43
  ```
42
44
 
43
45
  El Hook del Store puede seguir usando `useState`, `useReducer`, Context, Hooks de SDK y Hooks personalizados.
44
46
 
47
+ Define el initializer como un Hook con nombre en el nivel superior, por ejemplo `useCounterModel`. Los initializers anónimos siguen funcionando en runtime, pero React Compiler no los compila automáticamente como Hooks en modo `infer`.
48
+
45
49
  ## Montar el Provider y seleccionar valores
46
50
 
47
51
  ```tsx
@@ -79,12 +83,16 @@ Sin un singleton de módulo oculto, el mismo Provider puede montarse varias vece
79
83
 
80
84
  ```ts
81
85
  function createStore<TStore, TProps = Record<never, never>>(
82
- useStoreValue: (props: TProps) => TStore,
86
+ useModel: (props: TProps) => TStore,
83
87
  ): readonly [StoreHook<TStore>, StoreProvider<TProps>]
88
+
89
+ const [useStream, StreamProvider] = bindStore<Stream>('Stream')
84
90
  ```
85
91
 
86
92
  El Hook devuelto requiere un selector que retorne un objeto. Usarlo fuera de su Provider correspondiente produce un error claro. Admite Strict Mode y renderizado en servidor.
87
93
 
94
+ `bindStore` es una integración avanzada solo para un Headless External Store existente. Para el estado normal de Hooks, usa `createStore`. El Context solo contiene la instancia original; los consumidores utilizan directamente `getSnapshot` y `subscribe`.
95
+
88
96
  ## Documentación
89
97
 
90
98
  - [Introducción](https://violetflux.github.io/kerros/es/guide/introduction)
package/README.fr.md CHANGED
@@ -34,14 +34,18 @@ Installe @violetflux/kerros avec le gestionnaire de paquets du projet, puis exé
34
34
  import { createStore } from '@violetflux/kerros'
35
35
  import { useState } from 'react'
36
36
 
37
- export const [useCounter, CounterProvider] = createStore(() => {
37
+ function useCounterModel() {
38
38
  const [count, setCount] = useState(0)
39
39
  return { count, setCount }
40
- })
40
+ }
41
+
42
+ export const [useCounter, CounterProvider] = createStore(useCounterModel)
41
43
  ```
42
44
 
43
45
  Le Hook du Store peut continuer à utiliser `useState`, `useReducer`, Context, des Hooks SDK et vos Hooks personnalisés.
44
46
 
47
+ Définissez l'initializer comme un Hook nommé au niveau du module, par exemple `useCounterModel`. Les initializers anonymes fonctionnent toujours à l'exécution, mais React Compiler ne les compile pas automatiquement comme Hooks en mode `infer`.
48
+
45
49
  ## Monter le Provider et sélectionner les valeurs
46
50
 
47
51
  ```tsx
@@ -79,12 +83,16 @@ Sans singleton de module caché, un même Provider peut être monté plusieurs f
79
83
 
80
84
  ```ts
81
85
  function createStore<TStore, TProps = Record<never, never>>(
82
- useStoreValue: (props: TProps) => TStore,
86
+ useModel: (props: TProps) => TStore,
83
87
  ): readonly [StoreHook<TStore>, StoreProvider<TProps>]
88
+
89
+ const [useStream, StreamProvider] = bindStore<Stream>('Stream')
84
90
  ```
85
91
 
86
92
  Le Hook retourné exige un sélecteur qui renvoie un objet. Son utilisation hors du Provider correspondant lève une erreur claire. Strict Mode et le rendu serveur sont pris en charge.
87
93
 
94
+ `bindStore` est une intégration avancée réservée à un Headless External Store existant. Pour un état Hook ordinaire, utilisez `createStore`. Le Context ne contient que l'instance d'origine ; les consommateurs utilisent directement `getSnapshot` et `subscribe`.
95
+
88
96
  ## Documentation
89
97
 
90
98
  - [Introduction](https://violetflux.github.io/kerros/fr/guide/introduction)
package/README.ja.md CHANGED
@@ -34,14 +34,18 @@ Kerros は React の状態を Hook の中、Provider の下という自然な場
34
34
  import { createStore } from '@violetflux/kerros'
35
35
  import { useState } from 'react'
36
36
 
37
- export const [useCounter, CounterProvider] = createStore(() => {
37
+ function useCounterModel() {
38
38
  const [count, setCount] = useState(0)
39
39
  return { count, setCount }
40
- })
40
+ }
41
+
42
+ export const [useCounter, CounterProvider] = createStore(useCounterModel)
41
43
  ```
42
44
 
43
45
  Store Hook 内では `useState`、`useReducer`、Context、SDK Hook、カスタム Hook をそのまま利用できます。
44
46
 
47
+ initializer は `useCounterModel` のようなモジュール直下の名前付き Hook として定義してください。匿名 initializer も実行時には動作しますが、React Compiler の `infer` モードでは Hook として自動コンパイルされません。
48
+
45
49
  ## Provider を配置して値を選択する
46
50
 
47
51
  ```tsx
@@ -80,12 +84,16 @@ Provider は Store Hook を実行し、Context には安定した購読コンテ
80
84
 
81
85
  ```ts
82
86
  function createStore<TStore, TProps = Record<never, never>>(
83
- useStoreValue: (props: TProps) => TStore,
87
+ useModel: (props: TProps) => TStore,
84
88
  ): readonly [StoreHook<TStore>, StoreProvider<TProps>]
89
+
90
+ const [useStream, StreamProvider] = bindStore<Stream>('Stream')
85
91
  ```
86
92
 
87
93
  返される Store Hook にはオブジェクトを返す selector が必須です。対応する Provider の外で呼び出すと明確なエラーを送出します。Strict Mode とサーバーレンダリングをサポートします。
88
94
 
95
+ 高度な連携として、既存の Headless External Store にだけ `bindStore` を使います。通常の Hook 状態には `createStore` を使ってください。Context は元の Store インスタンスだけを保持し、コンシューマーは `getSnapshot` と `subscribe` を直接利用します。
96
+
89
97
  ## ドキュメント
90
98
 
91
99
  - [はじめに](https://violetflux.github.io/kerros/ja/guide/introduction)
package/README.ko.md CHANGED
@@ -34,14 +34,18 @@ Kerros는 React 상태를 Hook 안과 Provider 아래라는 자연스러운 위
34
34
  import { createStore } from '@violetflux/kerros'
35
35
  import { useState } from 'react'
36
36
 
37
- export const [useCounter, CounterProvider] = createStore(() => {
37
+ function useCounterModel() {
38
38
  const [count, setCount] = useState(0)
39
39
  return { count, setCount }
40
- })
40
+ }
41
+
42
+ export const [useCounter, CounterProvider] = createStore(useCounterModel)
41
43
  ```
42
44
 
43
45
  Store Hook 안에서 `useState`, `useReducer`, Context, SDK Hook, 사용자 Hook을 그대로 사용할 수 있습니다.
44
46
 
47
+ initializer는 `useCounterModel`처럼 모듈 최상위의 이름 있는 Hook으로 정의하세요. 익명 initializer도 런타임에서는 동작하지만 React Compiler의 `infer` 모드에서는 Hook으로 자동 컴파일되지 않습니다.
48
+
45
49
  ## Provider를 마운트하고 필요한 값 선택하기
46
50
 
47
51
  ```tsx
@@ -80,12 +84,16 @@ Provider는 Store Hook을 실행하고 Context에는 안정적인 구독 컨테
80
84
 
81
85
  ```ts
82
86
  function createStore<TStore, TProps = Record<never, never>>(
83
- useStoreValue: (props: TProps) => TStore,
87
+ useModel: (props: TProps) => TStore,
84
88
  ): readonly [StoreHook<TStore>, StoreProvider<TProps>]
89
+
90
+ const [useStream, StreamProvider] = bindStore<Stream>('Stream')
85
91
  ```
86
92
 
87
93
  반환된 Store Hook에는 객체를 반환하는 selector가 필요합니다. 대응하는 Provider 밖에서 호출하면 명확한 오류가 발생합니다. Strict Mode와 서버 렌더링을 지원합니다.
88
94
 
95
+ 고급 통합이 필요한 기존 Headless External Store에만 `bindStore`를 사용하세요. 일반 Hook 상태에는 `createStore`를 사용합니다. Context는 원래 Store 인스턴스만 보관하고 소비자는 `getSnapshot`과 `subscribe`를 직접 사용합니다.
96
+
89
97
  ## 문서
90
98
 
91
99
  - [소개](https://violetflux.github.io/kerros/ko/guide/introduction)
package/README.md CHANGED
@@ -48,7 +48,7 @@ interface Task {
48
48
  title: string
49
49
  }
50
50
 
51
- export const [useTask, TaskProvider] = createStore(() => {
51
+ function useTaskModel() {
52
52
  const [tasks, setTasks] = useState<Task[]>([])
53
53
 
54
54
  const addTask = (task: Task) => {
@@ -60,13 +60,17 @@ export const [useTask, TaskProvider] = createStore(() => {
60
60
  }
61
61
 
62
62
  return { tasks, addTask, finishTask }
63
- })
63
+ }
64
+
65
+ export const [useTask, TaskProvider] = createStore(useTaskModel)
64
66
  ```
65
67
 
66
68
  `createStore` returns two values: the Hook used by components and its matching Provider.
67
69
 
68
70
  The Store is still a normal React Hook. It may use `useState`, `useReducer`, Context, SDK Hooks, or your own custom Hooks.
69
71
 
72
+ Keep the initializer as a top-level named Hook such as `useTaskModel`. Anonymous initializers still work at runtime, but React Compiler `infer` mode does not automatically compile them as Hooks.
73
+
70
74
  ### Mount the Provider
71
75
 
72
76
  Only descendants of `TaskProvider` may use `useTask`:
@@ -162,7 +166,7 @@ Each `TaskList` automatically reads its nearest Provider.
162
166
  A Store may call another Store directly. For example, a task Store can read the current account:
163
167
 
164
168
  ```tsx
165
- export const [useTask, TaskProvider] = createStore(() => {
169
+ function useTaskModel() {
166
170
  const { user } = useAccount(s => ({ user: s.user }))
167
171
  const [tasks, setTasks] = useState<Task[]>([])
168
172
 
@@ -178,7 +182,9 @@ export const [useTask, TaskProvider] = createStore(() => {
178
182
  }
179
183
 
180
184
  return { tasks, addTask }
181
- })
185
+ }
186
+
187
+ export const [useTask, TaskProvider] = createStore(useTaskModel)
182
188
  ```
183
189
 
184
190
  Mount Providers in dependency order and keep dependencies one-way:
@@ -200,12 +206,12 @@ interface CounterProps {
200
206
  initialCount: number
201
207
  }
202
208
 
203
- const [useCounter, CounterProvider] = createStore(
204
- ({ initialCount }: CounterProps) => {
205
- const [count, setCount] = useState(initialCount)
206
- return { count, setCount }
207
- },
208
- )
209
+ function useCounterModel({ initialCount }: CounterProps) {
210
+ const [count, setCount] = useState(initialCount)
211
+ return { count, setCount }
212
+ }
213
+
214
+ const [useCounter, CounterProvider] = createStore(useCounterModel)
209
215
  ```
210
216
 
211
217
  ```tsx
@@ -216,18 +222,40 @@ const [useCounter, CounterProvider] = createStore(
216
222
 
217
223
  ## API
218
224
 
225
+ ### `createStore` (default)
226
+
219
227
  ```ts
220
228
  function createStore<TStore, TProps = Record<never, never>>(
221
- useStoreValue: (props: TProps) => TStore,
229
+ useModel: (props: TProps) => TStore,
222
230
  ): readonly [StoreHook<TStore>, StoreProvider<TProps>]
223
231
  ```
224
232
 
225
- - `useStoreValue` follows the Rules of Hooks
226
- - Provider props, excluding `children`, are passed to `useStoreValue`
233
+ - `useModel` follows the Rules of Hooks
234
+ - Provider props, excluding `children`, are passed to `useModel`
227
235
  - the returned Store Hook requires an object-returning selector
228
236
  - using the Store Hook outside its matching Provider throws a clear error
229
237
  - Provider instances work with Strict Mode and server rendering
230
238
 
239
+ ### Advanced: bind an existing external Store
240
+
241
+ Most applications only need `createStore`. Use `bindStore` when a library or SDK already owns authoritative state outside React and exposes stable `getSnapshot` and `subscribe` functions.
242
+
243
+ Without this API, an integration must either repeat the Context and selector subscription code or copy the external snapshot through a second Store. `bindStore` provides Provider scoping and selectors while keeping the original Store as the only state owner.
244
+
245
+ ```tsx
246
+ const [useStream, StreamBindingProvider] = bindStore<Stream>('Stream')
247
+
248
+ <StreamBindingProvider store={stream}>
249
+ <App />
250
+ </StreamBindingProvider>
251
+ ```
252
+
253
+ The Provider stores only the original Store instance in Context. Consumers subscribe directly, so Kerros does not copy snapshots or add another publication layer. The component that creates the Store keeps lifecycle and imperative access.
254
+
255
+ In one sentence: `bindStore` is a React adapter for an external Store, not a state synchronizer. State remains only in the original Store; React reads it with `getSnapshot` after a subscription notification.
256
+
257
+ If the state begins in `useState`, `useReducer`, an SDK Hook, or another custom Hook, keep using `createStore`.
258
+
231
259
  Kerros uses the official `use-sync-external-store` shim for React 17 and prefers React's native implementation in React 18 and 19. React Compiler is optional.
232
260
 
233
261
  ## Documentation
package/README.zh-CN.md CHANGED
@@ -40,7 +40,7 @@ interface Task {
40
40
  title: string
41
41
  }
42
42
 
43
- export const [useTask, TaskProvider] = createStore(() => {
43
+ function useTaskModel() {
44
44
  const [tasks, setTasks] = useState<Task[]>([])
45
45
 
46
46
  const addTask = (task: Task) => {
@@ -52,13 +52,17 @@ export const [useTask, TaskProvider] = createStore(() => {
52
52
  }
53
53
 
54
54
  return { tasks, addTask, finishTask }
55
- })
55
+ }
56
+
57
+ export const [useTask, TaskProvider] = createStore(useTaskModel)
56
58
  ```
57
59
 
58
60
  `createStore` 返回两个值:组件调用的 Hook 和对应的 Provider。
59
61
 
60
62
  Store 仍然是普通 React Hook,可以继续使用 `useState`、`useReducer`、Context、SDK Hook 或其他 custom Hook。
61
63
 
64
+ 请把 initializer 写成 `useTaskModel` 这类同文件顶层命名 Hook。匿名 initializer 在运行时仍然可用,但 React Compiler 的 `infer` 模式不会自动把它识别并编译为 Hook。
65
+
62
66
  ### 挂载 Provider
63
67
 
64
68
  只有 `TaskProvider` 的子节点可以调用 `useTask`:
@@ -154,7 +158,7 @@ Kerros 简单、轻量、可靠。先把状态写成普通 Hook,需要共享
154
158
  一个 Store 可以直接调用另一个 Store。例如任务 Store 读取当前账户:
155
159
 
156
160
  ```tsx
157
- export const [useTask, TaskProvider] = createStore(() => {
161
+ function useTaskModel() {
158
162
  const { user } = useAccount(s => ({ user: s.user }))
159
163
  const [tasks, setTasks] = useState<Task[]>([])
160
164
 
@@ -170,7 +174,9 @@ export const [useTask, TaskProvider] = createStore(() => {
170
174
  }
171
175
 
172
176
  return { tasks, addTask }
173
- })
177
+ }
178
+
179
+ export const [useTask, TaskProvider] = createStore(useTaskModel)
174
180
  ```
175
181
 
176
182
  按依赖顺序挂 Provider,并保持单向依赖:
@@ -192,12 +198,12 @@ interface CounterProps {
192
198
  initialCount: number
193
199
  }
194
200
 
195
- const [useCounter, CounterProvider] = createStore(
196
- ({ initialCount }: CounterProps) => {
197
- const [count, setCount] = useState(initialCount)
198
- return { count, setCount }
199
- },
200
- )
201
+ function useCounterModel({ initialCount }: CounterProps) {
202
+ const [count, setCount] = useState(initialCount)
203
+ return { count, setCount }
204
+ }
205
+
206
+ const [useCounter, CounterProvider] = createStore(useCounterModel)
201
207
  ```
202
208
 
203
209
  ```tsx
@@ -208,18 +214,40 @@ const [useCounter, CounterProvider] = createStore(
208
214
 
209
215
  ## API
210
216
 
217
+ ### `createStore`(默认用法)
218
+
211
219
  ```ts
212
220
  function createStore<TStore, TProps = Record<never, never>>(
213
- useStoreValue: (props: TProps) => TStore,
221
+ useModel: (props: TProps) => TStore,
214
222
  ): readonly [StoreHook<TStore>, StoreProvider<TProps>]
215
223
  ```
216
224
 
217
- - `useStoreValue` 必须遵守 Hooks 规则
218
- - 除 `children` 外的 Provider props 会传给 `useStoreValue`
225
+ - `useModel` 必须遵守 Hooks 规则
226
+ - 除 `children` 外的 Provider props 会传给 `useModel`
219
227
  - Store Hook 必须接收一个返回对象的 selector
220
228
  - 在对应 Provider 外调用会抛出明确错误
221
229
  - 支持 Strict Mode、服务端渲染和 Provider 多实例
222
230
 
231
+ ### 高级用法:绑定已有 External Store
232
+
233
+ 绝大多数应用只需要 `createStore`。只有当某个库或 SDK 已经在 React 外持有权威状态,并提供稳定的 `getSnapshot` 和 `subscribe` 函数时,才使用 `bindStore`。
234
+
235
+ 没有这个 API 时,集成层只能重复实现 Context 和 selector 订阅,或者把 External Store 快照复制进第二个 Store。`bindStore` 只提供 Provider 作用域和 selector,原 Store 仍是唯一状态所有者。
236
+
237
+ ```tsx
238
+ const [useStream, StreamBindingProvider] = bindStore<Stream>('Stream')
239
+
240
+ <StreamBindingProvider store={stream}>
241
+ <App />
242
+ </StreamBindingProvider>
243
+ ```
244
+
245
+ Provider 的 Context 只保存原 Store 实例,组件直接订阅它;Kerros 不复制快照,也不增加中间发布层。创建 Store 的组件继续负责生命周期和命令式访问。
246
+
247
+ 一句话理解:`bindStore` 是 External Store 的 React 适配器,不是状态同步器。状态仍只存在于原 Store 中,React 在收到订阅通知后通过 `getSnapshot` 读取它。
248
+
249
+ 如果状态来自 `useState`、`useReducer`、SDK Hook 或其他 custom Hook,继续使用 `createStore`。
250
+
223
251
  React 17 使用官方 `use-sync-external-store` shim;React 18 和 19 可用时优先使用 React 原生实现。React Compiler 不是必需项。
224
252
 
225
253
  ## 文档
package/dist/index.cjs CHANGED
@@ -6,28 +6,50 @@ const useStoreLayoutEffect = typeof window === "undefined" ? react.useEffect : r
6
6
  /**
7
7
  * Create a selector-first React Store and its matching Provider
8
8
  */
9
- function createStore(useStoreValue) {
9
+ function createStore(useModel) {
10
10
  const StoreContext = (0, react.createContext)(void 0);
11
- const storeName = useStoreValue.name || "KerrosStore";
12
- /** Run the Store hook and publish its committed snapshot */
11
+ const storeName = useModel.name || "KerrosStore";
12
+ /** Run the model Hook and publish its committed snapshot */
13
13
  const StoreProvider = (props) => {
14
14
  const { children, ...storeProps } = props;
15
- const value = useStoreValue(storeProps);
16
- const [container] = (0, react.useState)(() => createStoreContainer(value));
17
- useStoreLayoutEffect(() => container.publish(value), [container, value]);
15
+ const model = useModel(storeProps);
16
+ const [container] = (0, react.useState)(() => createStoreContainer(model));
17
+ useStoreLayoutEffect(() => container.publish(model), [container, model]);
18
18
  return (0, react.createElement)(StoreContext.Provider, { value: container }, children);
19
19
  };
20
20
  StoreProvider.displayName = `${storeName}Provider`;
21
21
  StoreContext.displayName = `${storeName}Context`;
22
22
  /** Select Store fields through the stable Provider container */
23
23
  const useStore = (selector) => {
24
- const container = (0, react.useContext)(StoreContext);
25
- if (!container) throw new Error("Kerros store hook must be used within its matching Provider");
26
- return (0, use_sync_external_store_shim_with_selector.useSyncExternalStoreWithSelector)(container.subscribe, container.getSnapshot, container.getSnapshot, selector, shallowEqual);
24
+ return useStoreSelector(useStoreContext(StoreContext), selector);
27
25
  };
28
26
  return [useStore, StoreProvider];
29
27
  }
30
28
  /**
29
+ * Bind existing external Store instances to scoped React consumers
30
+ */
31
+ function bindStore(name = "KerrosExternalStore") {
32
+ const StoreContext = (0, react.createContext)(void 0);
33
+ /** Provide one existing Store instance without copying its snapshot */
34
+ const StoreProvider = (props) => {
35
+ const { children, store } = props;
36
+ return (0, react.createElement)(StoreContext.Provider, { value: store }, children);
37
+ };
38
+ StoreProvider.displayName = `${name}Provider`;
39
+ StoreContext.displayName = `${name}Context`;
40
+ /** Select snapshot fields directly from the bound external Store */
41
+ const useStore = (selector) => {
42
+ return useStoreSelector(useStoreContext(StoreContext), selector);
43
+ };
44
+ /** Read the exact Store instance bound to the current Provider */
45
+ const useInstance = () => useStoreContext(StoreContext);
46
+ return [
47
+ useStore,
48
+ StoreProvider,
49
+ useInstance
50
+ ];
51
+ }
52
+ /**
31
53
  * Create the stable external-store container for one Provider instance
32
54
  */
33
55
  function createStoreContainer(initialSnapshot) {
@@ -47,6 +69,20 @@ function createStoreContainer(initialSnapshot) {
47
69
  };
48
70
  }
49
71
  /**
72
+ * Read the Store bound to the current Provider
73
+ */
74
+ function useStoreContext(context) {
75
+ const store = (0, react.useContext)(context);
76
+ if (!store) throw new Error("Kerros store hook must be used within its matching Provider");
77
+ return store;
78
+ }
79
+ /**
80
+ * Select fields directly from an external Store subscription
81
+ */
82
+ function useStoreSelector(store, selector) {
83
+ return (0, use_sync_external_store_shim_with_selector.useSyncExternalStoreWithSelector)(store.subscribe, store.getSnapshot, store.getSnapshot, selector, shallowEqual);
84
+ }
85
+ /**
50
86
  * Compare selector objects by their enumerable top-level fields
51
87
  */
52
88
  function shallowEqual(left, right) {
@@ -56,4 +92,5 @@ function shallowEqual(left, right) {
56
92
  return leftKeys.every((key) => Object.prototype.hasOwnProperty.call(right, key) && Object.is(left[key], right[key]));
57
93
  }
58
94
  //#endregion
95
+ exports.bindStore = bindStore;
59
96
  exports.createStore = createStore;
package/dist/index.d.cts CHANGED
@@ -8,9 +8,26 @@ interface StoreHook<TStore> {
8
8
  }
9
9
  /** Provider created for a Store hook */
10
10
  type StoreProvider<TProps> = FC<PropsWithChildren<TProps>>;
11
+ /** Existing external Store contract supported by bindStore */
12
+ interface ExternalStore<TSnapshot> {
13
+ /** Read the current immutable snapshot */
14
+ getSnapshot: () => TSnapshot;
15
+ /** Subscribe to snapshot changes */
16
+ subscribe: (listener: () => void) => () => void;
17
+ }
18
+ /** Extract the snapshot exposed by an external Store */
19
+ type ExternalStoreSnapshot<TStore> = TStore extends ExternalStore<infer TSnapshot> ? TSnapshot : never;
20
+ /** React bindings created for an existing external Store type */
21
+ type StoreBinding<TStore extends ExternalStore<TSnapshot>, TSnapshot = ExternalStoreSnapshot<TStore>> = readonly [StoreHook<TSnapshot>, StoreProvider<{
22
+ store: TStore;
23
+ }>, () => TStore];
11
24
  /**
12
25
  * Create a selector-first React Store and its matching Provider
13
26
  */
14
- declare function createStore<TStore, TProps = Record<never, never>>(useStoreValue: (props: TProps) => TStore): readonly [StoreHook<TStore>, StoreProvider<TProps>];
27
+ declare function createStore<TStore, TProps = Record<never, never>>(useModel: (props: TProps) => TStore): readonly [StoreHook<TStore>, StoreProvider<TProps>];
28
+ /**
29
+ * Bind existing external Store instances to scoped React consumers
30
+ */
31
+ declare function bindStore<TStore extends ExternalStore<TSnapshot>, TSnapshot = ExternalStoreSnapshot<TStore>>(name?: string): StoreBinding<TStore, TSnapshot>;
15
32
  //#endregion
16
- export { StoreHook, StoreProvider, StoreSelector, createStore };
33
+ export { ExternalStore, ExternalStoreSnapshot, StoreBinding, StoreHook, StoreProvider, StoreSelector, bindStore, createStore };
package/dist/index.d.mts CHANGED
@@ -8,9 +8,26 @@ interface StoreHook<TStore> {
8
8
  }
9
9
  /** Provider created for a Store hook */
10
10
  type StoreProvider<TProps> = FC<PropsWithChildren<TProps>>;
11
+ /** Existing external Store contract supported by bindStore */
12
+ interface ExternalStore<TSnapshot> {
13
+ /** Read the current immutable snapshot */
14
+ getSnapshot: () => TSnapshot;
15
+ /** Subscribe to snapshot changes */
16
+ subscribe: (listener: () => void) => () => void;
17
+ }
18
+ /** Extract the snapshot exposed by an external Store */
19
+ type ExternalStoreSnapshot<TStore> = TStore extends ExternalStore<infer TSnapshot> ? TSnapshot : never;
20
+ /** React bindings created for an existing external Store type */
21
+ type StoreBinding<TStore extends ExternalStore<TSnapshot>, TSnapshot = ExternalStoreSnapshot<TStore>> = readonly [StoreHook<TSnapshot>, StoreProvider<{
22
+ store: TStore;
23
+ }>, () => TStore];
11
24
  /**
12
25
  * Create a selector-first React Store and its matching Provider
13
26
  */
14
- declare function createStore<TStore, TProps = Record<never, never>>(useStoreValue: (props: TProps) => TStore): readonly [StoreHook<TStore>, StoreProvider<TProps>];
27
+ declare function createStore<TStore, TProps = Record<never, never>>(useModel: (props: TProps) => TStore): readonly [StoreHook<TStore>, StoreProvider<TProps>];
28
+ /**
29
+ * Bind existing external Store instances to scoped React consumers
30
+ */
31
+ declare function bindStore<TStore extends ExternalStore<TSnapshot>, TSnapshot = ExternalStoreSnapshot<TStore>>(name?: string): StoreBinding<TStore, TSnapshot>;
15
32
  //#endregion
16
- export { StoreHook, StoreProvider, StoreSelector, createStore };
33
+ export { ExternalStore, ExternalStoreSnapshot, StoreBinding, StoreHook, StoreProvider, StoreSelector, bindStore, createStore };
package/dist/index.mjs CHANGED
@@ -5,28 +5,50 @@ const useStoreLayoutEffect = typeof window === "undefined" ? useEffect : useLayo
5
5
  /**
6
6
  * Create a selector-first React Store and its matching Provider
7
7
  */
8
- function createStore(useStoreValue) {
8
+ function createStore(useModel) {
9
9
  const StoreContext = createContext(void 0);
10
- const storeName = useStoreValue.name || "KerrosStore";
11
- /** Run the Store hook and publish its committed snapshot */
10
+ const storeName = useModel.name || "KerrosStore";
11
+ /** Run the model Hook and publish its committed snapshot */
12
12
  const StoreProvider = (props) => {
13
13
  const { children, ...storeProps } = props;
14
- const value = useStoreValue(storeProps);
15
- const [container] = useState(() => createStoreContainer(value));
16
- useStoreLayoutEffect(() => container.publish(value), [container, value]);
14
+ const model = useModel(storeProps);
15
+ const [container] = useState(() => createStoreContainer(model));
16
+ useStoreLayoutEffect(() => container.publish(model), [container, model]);
17
17
  return createElement(StoreContext.Provider, { value: container }, children);
18
18
  };
19
19
  StoreProvider.displayName = `${storeName}Provider`;
20
20
  StoreContext.displayName = `${storeName}Context`;
21
21
  /** Select Store fields through the stable Provider container */
22
22
  const useStore = (selector) => {
23
- const container = useContext(StoreContext);
24
- if (!container) throw new Error("Kerros store hook must be used within its matching Provider");
25
- return useSyncExternalStoreWithSelector(container.subscribe, container.getSnapshot, container.getSnapshot, selector, shallowEqual);
23
+ return useStoreSelector(useStoreContext(StoreContext), selector);
26
24
  };
27
25
  return [useStore, StoreProvider];
28
26
  }
29
27
  /**
28
+ * Bind existing external Store instances to scoped React consumers
29
+ */
30
+ function bindStore(name = "KerrosExternalStore") {
31
+ const StoreContext = createContext(void 0);
32
+ /** Provide one existing Store instance without copying its snapshot */
33
+ const StoreProvider = (props) => {
34
+ const { children, store } = props;
35
+ return createElement(StoreContext.Provider, { value: store }, children);
36
+ };
37
+ StoreProvider.displayName = `${name}Provider`;
38
+ StoreContext.displayName = `${name}Context`;
39
+ /** Select snapshot fields directly from the bound external Store */
40
+ const useStore = (selector) => {
41
+ return useStoreSelector(useStoreContext(StoreContext), selector);
42
+ };
43
+ /** Read the exact Store instance bound to the current Provider */
44
+ const useInstance = () => useStoreContext(StoreContext);
45
+ return [
46
+ useStore,
47
+ StoreProvider,
48
+ useInstance
49
+ ];
50
+ }
51
+ /**
30
52
  * Create the stable external-store container for one Provider instance
31
53
  */
32
54
  function createStoreContainer(initialSnapshot) {
@@ -46,6 +68,20 @@ function createStoreContainer(initialSnapshot) {
46
68
  };
47
69
  }
48
70
  /**
71
+ * Read the Store bound to the current Provider
72
+ */
73
+ function useStoreContext(context) {
74
+ const store = useContext(context);
75
+ if (!store) throw new Error("Kerros store hook must be used within its matching Provider");
76
+ return store;
77
+ }
78
+ /**
79
+ * Select fields directly from an external Store subscription
80
+ */
81
+ function useStoreSelector(store, selector) {
82
+ return useSyncExternalStoreWithSelector(store.subscribe, store.getSnapshot, store.getSnapshot, selector, shallowEqual);
83
+ }
84
+ /**
49
85
  * Compare selector objects by their enumerable top-level fields
50
86
  */
51
87
  function shallowEqual(left, right) {
@@ -55,4 +91,4 @@ function shallowEqual(left, right) {
55
91
  return leftKeys.every((key) => Object.prototype.hasOwnProperty.call(right, key) && Object.is(left[key], right[key]));
56
92
  }
57
93
  //#endregion
58
- export { createStore };
94
+ export { bindStore, createStore };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@violetflux/kerros",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Hook-native state sharing for React with focused selector subscriptions.",
5
5
  "keywords": [
6
6
  "react",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: kerros
3
- description: Implement, refactor, review, or test shared React state with @violetflux/kerros. Use for createStore, Provider scoping, selector subscriptions, cross-Store composition, migrating from Hox or frequently changing React Context, preventing Context-wide rerenders, and React 17–19 compatibility.
3
+ description: Implement, refactor, review, or test shared React state with @violetflux/kerros. Use for createStore, bindStore, Provider scoping, selector subscriptions, existing headless external Stores, cross-Store composition, migrating from Hox or frequently changing React Context, preventing Context-wide rerenders, and React 17–19 compatibility.
4
4
  ---
5
5
 
6
6
  # Kerros
@@ -12,9 +12,10 @@ Build shared state from ordinary React Hooks. Keep Provider scope and multiple i
12
12
  1. Inspect the project package manager, React version, existing state owner, Provider tree, and naming conventions.
13
13
  2. Keep state local when only one component needs it. Create a Kerros Store only when several components need the same Hook state.
14
14
  3. Group state by domain and identify one authoritative owner for every mutable value.
15
- 4. Create the Store, mount its Provider at the narrowest shared ancestor, and migrate consumers to focused selectors.
16
- 5. Order composed Providers from dependency to dependent and reject circular Store dependencies.
17
- 6. Run the project's typecheck, tests, lint, and the narrowest useful render test.
15
+ 4. Use `createStore` by default. Use the advanced `bindStore` API only when an authoritative headless external Store already exists.
16
+ 5. Mount the Provider at the narrowest shared ancestor and migrate consumers to focused selectors.
17
+ 6. Order composed Providers from dependency to dependent and reject circular Store dependencies.
18
+ 7. Run the project's typecheck, tests, lint, and the narrowest useful render test.
18
19
 
19
20
  ## Install
20
21
 
@@ -31,18 +32,20 @@ Kerros supports React 17, 18, and 19. Do not change package managers or create a
31
32
 
32
33
  ## Core pattern
33
34
 
34
- Write the Store as an ordinary Hook directly inside `createStore`. Name the public pair after the domain; do not repeat `Store` unless the codebase already requires it.
35
+ Define the Store implementation as a top-level named Hook ending in `Model`, then pass that function to `createStore`. The model may return both state and actions. Name the public pair after the domain; do not repeat `Store` unless the codebase already requires it.
35
36
 
36
37
  ```tsx
37
38
  import { createStore } from '@violetflux/kerros'
38
39
  import { useState } from 'react'
39
40
 
40
- export const [useCounter, CounterProvider] = createStore(() => {
41
+ function useCounterModel() {
41
42
  const [count, setCount] = useState(0)
42
43
  const increment = () => setCount(v => v + 1)
43
44
 
44
45
  return { count, increment }
45
- })
46
+ }
47
+
48
+ export const [useCounter, CounterProvider] = createStore(useCounterModel)
46
49
  ```
47
50
 
48
51
  Mount the Provider at the narrowest ancestor shared by all consumers:
@@ -72,15 +75,53 @@ function Counter() {
72
75
 
73
76
  Kerros shallowly compares the selected object's top-level fields with `Object.is`. An update to an unselected field must not rerender this component.
74
77
 
78
+ ## Advanced external Store binding
79
+
80
+ Most applications should stop at `createStore`. Use `bindStore` only when a headless Store already owns authoritative state outside React and exposes stable `getSnapshot` and `subscribe` functions.
81
+
82
+ ```tsx
83
+ import { bindStore } from '@violetflux/kerros'
84
+
85
+ export const [
86
+ useStream,
87
+ StreamBindingProvider,
88
+ useStreamInstance,
89
+ ] = bindStore<Stream>('Stream')
90
+ ```
91
+
92
+ Mount the original instance without mirroring its snapshot:
93
+
94
+ ```tsx
95
+ <StreamBindingProvider store={stream}>
96
+ <App />
97
+ </StreamBindingProvider>
98
+ ```
99
+
100
+ Use `useStream` with focused selectors for snapshot reads. Use `useStreamInstance()` only in Provider descendants that need imperative commands or must supply the current instance to another headless service. It reads Context without subscribing to snapshots, so never use `useStreamInstance().getSnapshot()` for rendered state.
101
+
102
+ Keep creation, start, stop, and disposal in the owner that creates the instance. If that owner already has the instance, use it directly instead of calling the instance Hook.
103
+
104
+ Do not replace this with `createStore(() => useSyncExternalStore(...))`; that subscribes the Provider to the entire external snapshot and republishes it through a second container.
105
+
106
+ ## React Compiler and identity
107
+
108
+ - Generate `useXxxModel` as a top-level function by default. Anonymous initializers remain valid at runtime, but React Compiler `infer` mode does not automatically recognize and compile them as Hooks.
109
+ - Let the Store producer own action identity. React Compiler may stabilize ordinary returned actions; without Compiler support, use `useCallback` only when a consumer or effect requires a stable action reference.
110
+ - Do not claim that Kerros or `use-context-selector` can determine whether two newly allocated functions are semantically equivalent. Both can compare references, not function behavior.
111
+ - Prefer Kerros's existing object selector behavior for consumers that return fresh objects. Kerros shallowly compares the selected object's top-level fields; `use-context-selector` applies `Object.is` to the selector result, so a newly allocated object is different.
112
+ - Do not recommend migrating to `use-context-selector` merely to avoid Context-wide rerenders. Kerros already keeps a stable Context container and publishes committed snapshots through `useSyncExternalStoreWithSelector`.
113
+
75
114
  ## Provider props
76
115
 
77
116
  Accept initialization or scope-specific inputs as Store Hook props. Pass them to the generated Provider instead of reading mutable module globals.
78
117
 
79
118
  ```tsx
80
- const [useGreeting, GreetingProvider] = createStore((props: { initialName: string }) => {
119
+ function useGreetingModel(props: { initialName: string }) {
81
120
  const [name, setName] = useState(props.initialName)
82
121
  return { name, setName }
83
- })
122
+ }
123
+
124
+ const [useGreeting, GreetingProvider] = createStore(useGreetingModel)
84
125
 
85
126
  <GreetingProvider initialName="Ada">
86
127
  <Profile />
@@ -92,15 +133,19 @@ const [useGreeting, GreetingProvider] = createStore((props: { initialName: strin
92
133
  An inner Store may call an outer Store Hook. Mount the dependency first and keep the graph one-way.
93
134
 
94
135
  ```tsx
95
- const [useSession, SessionProvider] = createStore(() => {
136
+ function useSessionModel() {
96
137
  const [userId, setUserId] = useState<string>()
97
138
  return { userId, setUserId }
98
- })
139
+ }
140
+
141
+ const [useSession, SessionProvider] = createStore(useSessionModel)
99
142
 
100
- const [usePermissions, PermissionsProvider] = createStore(() => {
143
+ function usePermissionsModel() {
101
144
  const { userId } = useSession(s => ({ userId: s.userId }))
102
145
  return { canEdit: Boolean(userId) }
103
- })
146
+ }
147
+
148
+ const [usePermissions, PermissionsProvider] = createStore(usePermissionsModel)
104
149
 
105
150
  function Providers({ children }: PropsWithChildren) {
106
151
  return (
@@ -113,15 +158,16 @@ function Providers({ children }: PropsWithChildren) {
113
158
 
114
159
  ## Guardrails
115
160
 
161
+ - Use `createStore` for state owned by a React Hook. Treat `bindStore` as an advanced adapter for an already-authoritative headless Store; do not mirror that snapshot through another Hook Store.
116
162
  - Require every Store read to use an object selector. Do not use array selectors.
117
163
  - Select concrete fields and actions. Do not expose or select a changing aggregate Store snapshot.
118
164
  - Do not wrap inline selectors with `useCallback`; Kerros handles selector identity.
119
- - Keep public actions as ordinary functions. In React 19, use `useEffectEvent` only for events called from Effects, never as a public Store action.
165
+ - Keep public actions as ordinary functions unless their reference stability is an explicit producer-side requirement. In React 19, use `useEffectEvent` only for events called from Effects, never as a public Store action.
120
166
  - Do not mirror the same mutable state across Stores. Read it from its authoritative Store or move ownership.
121
167
  - Do not create circular Store dependencies. Split ownership or invert the Provider order.
122
168
  - Do not call a Store Hook outside its matching Provider; Kerros intentionally throws a clear error.
123
169
  - Do not replace scoped Providers with a hidden module singleton. Put an application-wide Provider at the root only when the state is truly application-wide.
124
- - Preserve SDK caches, subscriptions, and streams under a single owning Store when duplicating the Hook would duplicate external work.
170
+ - Preserve SDK caches, subscriptions, and streams under a single owner. Bind an existing headless Store directly; call a connection-owning SDK Hook inside one `createStore` initializer only when no external Store instance exists.
125
171
  - Respect the project's React version. Avoid React 19-only APIs when the consuming project still supports React 17 or 18.
126
172
 
127
173
  ## Migrate existing state
@@ -133,6 +179,7 @@ function Providers({ children }: PropsWithChildren) {
133
179
  ## Verify
134
180
 
135
181
  - Confirm all consumers are below the correct Provider and multiple Provider instances stay isolated.
182
+ - Search Kerros `createStore` calls and confirm every initializer references a top-level `useXxxModel` function. Search `bindStore` calls and confirm the supplied Store owns a stable immutable snapshot.
136
183
  - Test Provider props, Strict Mode, subscription cleanup, and the outside-Provider error when changing Store infrastructure.
137
184
  - Add a render-count test showing that changing an unselected field does not rerender the consumer.
138
185
  - Search for broad Store selections, array selectors, duplicate subscriptions, and dependency cycles.
@@ -1,4 +1,4 @@
1
1
  interface:
2
2
  display_name: "Kerros"
3
3
  short_description: "Build selector-based shared React state with Kerros"
4
- default_prompt: "Use $kerros to implement shared React state with createStore, Providers, and focused selectors."
4
+ default_prompt: "Use $kerros to implement shared React state with createStore or bindStore, scoped Providers, and focused selectors."