@violetflux/kerros 0.2.4 → 0.3.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.de.md CHANGED
@@ -81,13 +81,15 @@ Ohne verstecktes Modul-Singleton kann derselbe Provider mehrfach eingebunden, ü
81
81
  ```ts
82
82
  function createStore<TStore, TProps = Record<never, never>>(
83
83
  useModel: (props: TProps) => TStore,
84
- ): readonly [StoreHook<TStore>, StoreProvider<TProps>]
84
+ ): readonly [StoreHook<TStore>, StoreProvider<TProps>, StoreGetter<TStore>]
85
85
 
86
86
  const [useStream, StreamProvider] = bindStore<Stream>('Stream')
87
87
  ```
88
88
 
89
89
  Der zurückgegebene Store Hook verwendet ohne Argument automatisches Tracking. Explizite Objekt-Selektoren sind für abgeleitete Werte und gemessene Hotspots verfügbar. Außerhalb des passenden Providers wird ein verständlicher Fehler ausgelöst.
90
90
 
91
+ Der dritte Getter liest außerhalb von React den zuletzt committeten Provider. Mit `scope?: string | number | symbol` am Provider wählt er die neueste exakt passende Instanz. Er abonniert keine Updates und wirft ohne verfügbaren Provider einen Fehler.
92
+
91
93
  React-Elemente und Portale sind automatisch atomar. `useRef()` und `createRef()` können direkt zurückgegeben werden; `ref(value)` ist nur für Proxy-inkompatible Werte oder strikte Identität gedacht.
92
94
 
93
95
  `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.
package/README.es.md CHANGED
@@ -81,13 +81,15 @@ Sin un singleton de módulo oculto, el mismo Provider puede montarse varias vece
81
81
  ```ts
82
82
  function createStore<TStore, TProps = Record<never, never>>(
83
83
  useModel: (props: TProps) => TStore,
84
- ): readonly [StoreHook<TStore>, StoreProvider<TProps>]
84
+ ): readonly [StoreHook<TStore>, StoreProvider<TProps>, StoreGetter<TStore>]
85
85
 
86
86
  const [useStream, StreamProvider] = bindStore<Stream>('Stream')
87
87
  ```
88
88
 
89
89
  Sin argumentos, el Hook devuelto usa seguimiento automático. Los selectores de objeto explícitos quedan para valores derivados y puntos críticos medidos. Usarlo fuera de su Provider produce un error claro.
90
90
 
91
+ El tercer getter lee fuera de React el último Provider confirmado. Con `scope?: string | number | symbol` en el Provider selecciona la instancia más reciente que coincida exactamente. No se suscribe y lanza un error si no hay un Provider disponible.
92
+
91
93
  Los elementos y portales de React son atómicos automáticamente. `useRef()` y `createRef()` se pueden devolver directamente; `ref(value)` se reserva para valores incompatibles con Proxy o identidad estricta.
92
94
 
93
95
  `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`.
package/README.fr.md CHANGED
@@ -81,13 +81,15 @@ Sans singleton de module caché, un même Provider peut être monté plusieurs f
81
81
  ```ts
82
82
  function createStore<TStore, TProps = Record<never, never>>(
83
83
  useModel: (props: TProps) => TStore,
84
- ): readonly [StoreHook<TStore>, StoreProvider<TProps>]
84
+ ): readonly [StoreHook<TStore>, StoreProvider<TProps>, StoreGetter<TStore>]
85
85
 
86
86
  const [useStream, StreamProvider] = bindStore<Stream>('Stream')
87
87
  ```
88
88
 
89
89
  Sans argument, le Hook retourné active le suivi automatique. Les sélecteurs d'objet explicites servent aux valeurs dérivées et aux points chauds mesurés. Son utilisation hors du Provider lève une erreur claire.
90
90
 
91
+ Le troisième getter lit hors de React le dernier Provider validé par un commit. Avec `scope?: string | number | symbol` sur le Provider, il choisit la dernière instance qui correspond exactement. Il ne s'abonne pas et lève une erreur sans Provider disponible.
92
+
91
93
  Les éléments et portails React sont automatiquement atomiques. `useRef()` et `createRef()` peuvent être retournés directement ; `ref(value)` est réservé aux valeurs incompatibles avec Proxy ou à l'identité stricte.
92
94
 
93
95
  `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`.
package/README.ja.md CHANGED
@@ -82,13 +82,15 @@ Provider は Store Hook を実行し、Context には安定した購読コンテ
82
82
  ```ts
83
83
  function createStore<TStore, TProps = Record<never, never>>(
84
84
  useModel: (props: TProps) => TStore,
85
- ): readonly [StoreHook<TStore>, StoreProvider<TProps>]
85
+ ): readonly [StoreHook<TStore>, StoreProvider<TProps>, StoreGetter<TStore>]
86
86
 
87
87
  const [useStream, StreamProvider] = bindStore<Stream>('Stream')
88
88
  ```
89
89
 
90
90
  返される Store Hook は引数なしで自動追跡を使います。明示的なオブジェクト selector は派生値や計測済みホットスポット向けです。対応する Provider の外では明確なエラーを送出します。
91
91
 
92
+ 3 番目の getter は React 外から最後にコミットされた Provider を読み取ります。Provider の `scope?: string | number | symbol` を渡すと完全一致する最新インスタンスを選べます。getter は購読せず、利用可能な Provider がなければエラーを投げます。
93
+
92
94
  React Element と Portal は自動的に原子的な値になります。`useRef()` と `createRef()` はそのまま返せます。`ref(value)` は Proxy 非互換の値や厳密な同一性が必要な場合だけ使います。
93
95
 
94
96
  高度な連携として、既存の Headless External Store にだけ `bindStore` を使います。通常の Hook 状態には `createStore` を使ってください。Context は元の Store インスタンスだけを保持し、コンシューマーは `getSnapshot` と `subscribe` を直接利用します。
package/README.ko.md CHANGED
@@ -82,13 +82,15 @@ Provider는 Store Hook을 실행하고 Context에는 안정적인 구독 컨테
82
82
  ```ts
83
83
  function createStore<TStore, TProps = Record<never, never>>(
84
84
  useModel: (props: TProps) => TStore,
85
- ): readonly [StoreHook<TStore>, StoreProvider<TProps>]
85
+ ): readonly [StoreHook<TStore>, StoreProvider<TProps>, StoreGetter<TStore>]
86
86
 
87
87
  const [useStream, StreamProvider] = bindStore<Stream>('Stream')
88
88
  ```
89
89
 
90
90
  반환된 Store Hook은 인자 없이 자동 추적을 사용합니다. 명시적 객체 selector는 파생 값과 측정된 핫스팟을 위한 고급 경로입니다. Provider 밖에서 호출하면 명확한 오류가 발생합니다.
91
91
 
92
+ 세 번째 getter는 React 밖에서 마지막으로 커밋된 Provider를 읽습니다. Provider의 `scope?: string | number | symbol`을 전달하면 정확히 일치하는 최신 인스턴스를 선택합니다. getter는 구독하지 않으며 사용 가능한 Provider가 없으면 오류를 던집니다.
93
+
92
94
  React Element와 Portal은 자동으로 원자 값이 됩니다. `useRef()`와 `createRef()`는 그대로 반환할 수 있으며 `ref(value)`는 Proxy 비호환 값이나 엄격한 동일성이 필요할 때만 사용합니다.
93
95
 
94
96
  고급 통합이 필요한 기존 Headless External Store에만 `bindStore`를 사용하세요. 일반 Hook 상태에는 `createStore`를 사용합니다. Context는 원래 Store 인스턴스만 보관하고 소비자는 `getSnapshot`과 `subscribe`를 직접 사용합니다.
package/README.md CHANGED
@@ -62,10 +62,10 @@ function useTaskModel() {
62
62
  return { tasks, addTask, finishTask }
63
63
  }
64
64
 
65
- export const [useTask, TaskProvider] = createStore(useTaskModel)
65
+ export const [useTask, TaskProvider, getTask] = createStore(useTaskModel)
66
66
  ```
67
67
 
68
- `createStore` returns two values: the Hook used by components and its matching Provider.
68
+ `createStore` returns the component Hook, its matching Provider, and an imperative getter for committed instances outside React.
69
69
 
70
70
  The Store is still a normal React Hook. It may use `useState`, `useReducer`, Context, SDK Hooks, or your own custom Hooks.
71
71
 
@@ -243,12 +243,14 @@ const [useCounter, CounterProvider] = createStore(useCounterModel)
243
243
  function createStore<TStore, TProps = Record<never, never>>(
244
244
  useModel: (props: TProps) => TStore,
245
245
  options?: { tracking?: boolean },
246
- ): readonly [StoreHook<TStore>, StoreProvider<TProps>]
246
+ ): readonly [StoreHook<TStore>, StoreProvider<TProps>, StoreGetter<TStore>]
247
247
  ```
248
248
 
249
249
  - `useModel` follows the Rules of Hooks
250
250
  - Provider props, excluding `children`, are passed to `useModel`
251
+ - Providers accept an optional `scope?: string | number | symbol` for imperative lookup
251
252
  - the returned Store Hook accepts either no argument for automatic tracking or an object-returning selector
253
+ - the returned getter reads the latest mounted committed Provider, or the latest exact scope match; it does not subscribe
252
254
  - using the Store Hook outside its matching Provider throws a clear error
253
255
  - Provider instances work with Strict Mode and server rendering
254
256
 
package/README.zh-CN.md CHANGED
@@ -54,10 +54,10 @@ function useTaskModel() {
54
54
  return { tasks, addTask, finishTask }
55
55
  }
56
56
 
57
- export const [useTask, TaskProvider] = createStore(useTaskModel)
57
+ export const [useTask, TaskProvider, getTask] = createStore(useTaskModel)
58
58
  ```
59
59
 
60
- `createStore` 返回两个值:组件调用的 Hook 和对应的 Provider
60
+ `createStore` 返回组件 Hook、对应的 Provider,以及在 React 外命令式读取已提交实例的 getter
61
61
 
62
62
  Store 仍然是普通 React Hook,可以继续使用 `useState`、`useReducer`、Context、SDK Hook 或其他 custom Hook。
63
63
 
@@ -235,12 +235,14 @@ const [useCounter, CounterProvider] = createStore(useCounterModel)
235
235
  function createStore<TStore, TProps = Record<never, never>>(
236
236
  useModel: (props: TProps) => TStore,
237
237
  options?: { tracking?: boolean },
238
- ): readonly [StoreHook<TStore>, StoreProvider<TProps>]
238
+ ): readonly [StoreHook<TStore>, StoreProvider<TProps>, StoreGetter<TStore>]
239
239
  ```
240
240
 
241
241
  - `useModel` 必须遵守 Hooks 规则
242
242
  - 除 `children` 外的 Provider props 会传给 `useModel`
243
+ - Provider 接受可选的 `scope?: string | number | symbol`,用于命令式查找
243
244
  - Store Hook 可不传参数使用自动追踪,也可传入返回对象的 selector
245
+ - getter 读取最后挂载且已提交的 Provider,或 scope 精确匹配的最后一个实例;它不会订阅更新
244
246
  - 在对应 Provider 外调用会抛出明确错误
245
247
  - 支持 Strict Mode、服务端渲染和 Provider 多实例
246
248
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  Kerros includes an adapted subset of `proxy-compare@3.0.1` in its access-tracking implementation.
4
4
 
5
+ The Kerros website theme adapts the BeUI Motion segmented-control pattern in `theme/components/segmented.tsx` and its class-name utility in `theme/lib/utils.ts` from https://beui.dev (https://github.com/starc007/ui-components). This code is website-only and is not distributed with the Kerros npm package.
6
+
5
7
  ## proxy-compare
6
8
 
7
9
  Copyright (c) 2020 Daishi Kato
@@ -11,3 +13,13 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
11
13
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
12
14
 
13
15
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16
+
17
+ ## BeUI Motion
18
+
19
+ Copyright (c) 2026 Saurabh Chauhan
20
+
21
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
22
+
23
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
24
+
25
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/dist/index.cjs CHANGED
@@ -237,12 +237,33 @@ function createStore(useModel, options) {
237
237
  const StoreContext = (0, react.createContext)(void 0);
238
238
  const storeName = useModel.name || "KerrosStore";
239
239
  const tracking = options?.tracking ?? true;
240
+ const registrations = [];
240
241
  /** Run the model Hook and publish its committed snapshot */
241
242
  const StoreProvider = (props) => {
243
+ const parent = (0, react.useContext)(StoreContext);
242
244
  const { children, ...storeProps } = props;
243
245
  const model = useModel(storeProps);
244
246
  const [container] = (0, react.useState)(() => createStoreContainer(model));
247
+ const scope = props.scope;
245
248
  useStoreLayoutEffect(() => container.publish(model), [container, model]);
249
+ useStoreLayoutEffect(() => {
250
+ const registration = {
251
+ container,
252
+ parent,
253
+ scope
254
+ };
255
+ const descendantIndex = registrations.findIndex((candidate) => isStoreDescendant(candidate, container, registrations));
256
+ if (descendantIndex < 0) registrations.push(registration);
257
+ else registrations.splice(descendantIndex, 0, registration);
258
+ return () => {
259
+ const index = registrations.lastIndexOf(registration);
260
+ if (index >= 0) registrations.splice(index, 1);
261
+ };
262
+ }, [
263
+ container,
264
+ parent,
265
+ scope
266
+ ]);
246
267
  return (0, react.createElement)(StoreContext.Provider, { value: container }, children);
247
268
  };
248
269
  StoreProvider.displayName = `${storeName}Provider`;
@@ -251,7 +272,28 @@ function createStore(useModel, options) {
251
272
  const useStore = ((selector) => {
252
273
  return useStoreValue(useStoreContext(StoreContext), selector, tracking);
253
274
  });
254
- return [useStore, StoreProvider];
275
+ /** Read the latest committed Store matching the optional scope */
276
+ const getStore = ((scope) => {
277
+ for (let index = registrations.length - 1; index >= 0; index--) {
278
+ const registration = registrations[index];
279
+ if (scope === void 0 || Object.is(registration.scope, scope)) return registration.container.getSnapshot();
280
+ }
281
+ throw new Error(scope === void 0 ? "Kerros store getter requires a mounted Provider" : "Kerros store getter could not find a mounted Provider for the requested scope");
282
+ });
283
+ return [
284
+ useStore,
285
+ StoreProvider,
286
+ getStore
287
+ ];
288
+ }
289
+ /** Test whether one committed Provider is nested below another container */
290
+ function isStoreDescendant(registration, ancestor, registrations) {
291
+ let parent = registration.parent;
292
+ while (parent) {
293
+ if (parent === ancestor) return true;
294
+ parent = registrations.find((candidate) => candidate.container === parent)?.parent;
295
+ }
296
+ return false;
255
297
  }
256
298
  function bindStore(nameOrOptions = "KerrosExternalStore", inputOptions) {
257
299
  const name = typeof nameOrOptions === "string" ? nameOrOptions : "KerrosExternalStore";
package/dist/index.d.cts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { FC, PropsWithChildren } from "react";
2
2
  //#region src/index.d.ts
3
3
  declare const storeHookMarker: unique symbol;
4
+ declare const storeGetterMarker: unique symbol;
4
5
  declare const storeInstanceHookMarker: unique symbol;
5
6
  declare const externalStoreProviderMarker: unique symbol;
6
7
  /** Store behavior options */
@@ -17,8 +18,22 @@ interface StoreHook<TStore> {
17
18
  (): TStore;
18
19
  <TSelection extends object>(selector: StoreSelector<TStore, TSelection>): TSelection;
19
20
  }
21
+ /** Supported identity for imperative Provider lookup */
22
+ type StoreScope = string | number | symbol;
23
+ /** Imperative reader for the latest committed Store snapshot */
24
+ interface StoreGetter<TStore> {
25
+ /** Type-only Store getter identity */
26
+ readonly [storeGetterMarker]: TStore;
27
+ /** Read the most recently mounted Store */
28
+ (): TStore;
29
+ /** Read the most recently mounted Store matching one scope */
30
+ (scope: StoreScope): TStore;
31
+ }
20
32
  /** Provider created for a Store hook */
21
- type StoreProvider<TProps> = FC<PropsWithChildren<TProps>>;
33
+ type StoreProvider<TProps> = FC<PropsWithChildren<TProps & {
34
+ /** Optional identity used by the imperative Store getter */
35
+ scope?: StoreScope;
36
+ }>>;
22
37
  /** Hook returning the exact external Store instance */
23
38
  interface StoreInstanceHook<TStore> {
24
39
  /** Type-only Store instance hook identity */
@@ -26,9 +41,9 @@ interface StoreInstanceHook<TStore> {
26
41
  (): TStore;
27
42
  }
28
43
  /** Provider carrying an existing external Store instance */
29
- type ExternalStoreProvider<TStore> = StoreProvider<{
44
+ type ExternalStoreProvider<TStore> = FC<PropsWithChildren<{
30
45
  store: TStore;
31
- }> & {
46
+ }>> & {
32
47
  /** Type-only external Store Provider identity */
33
48
  readonly [externalStoreProviderMarker]: TStore;
34
49
  };
@@ -48,11 +63,11 @@ declare function ref<T extends object>(value: T): T;
48
63
  /**
49
64
  * Create a React Store with automatic tracking and explicit selector support
50
65
  */
51
- declare function createStore<TStore, TProps = Record<never, never>>(useModel: (props: TProps) => TStore, options?: StoreOptions): readonly [StoreHook<TStore>, StoreProvider<TProps>];
66
+ declare function createStore<TStore, TProps = Record<never, never>>(useModel: (props: TProps) => TStore, options?: StoreOptions): readonly [StoreHook<TStore>, StoreProvider<TProps>, StoreGetter<TStore>];
52
67
  /**
53
68
  * Bind existing external Store instances to scoped React consumers
54
69
  */
55
70
  declare function bindStore<TStore extends ExternalStore<TSnapshot>, TSnapshot = ExternalStoreSnapshot<TStore>>(options?: StoreOptions): StoreBinding<TStore, TSnapshot>;
56
71
  declare function bindStore<TStore extends ExternalStore<TSnapshot>, TSnapshot = ExternalStoreSnapshot<TStore>>(name?: string, options?: StoreOptions): StoreBinding<TStore, TSnapshot>;
57
72
  //#endregion
58
- export { ExternalStore, ExternalStoreSnapshot, StoreBinding, StoreHook, StoreOptions, StoreProvider, StoreSelector, bindStore, createStore, ref };
73
+ export { ExternalStore, ExternalStoreSnapshot, StoreBinding, StoreGetter, StoreHook, StoreOptions, StoreProvider, StoreScope, StoreSelector, bindStore, createStore, ref };
package/dist/index.d.mts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { FC, PropsWithChildren } from "react";
2
2
  //#region src/index.d.ts
3
3
  declare const storeHookMarker: unique symbol;
4
+ declare const storeGetterMarker: unique symbol;
4
5
  declare const storeInstanceHookMarker: unique symbol;
5
6
  declare const externalStoreProviderMarker: unique symbol;
6
7
  /** Store behavior options */
@@ -17,8 +18,22 @@ interface StoreHook<TStore> {
17
18
  (): TStore;
18
19
  <TSelection extends object>(selector: StoreSelector<TStore, TSelection>): TSelection;
19
20
  }
21
+ /** Supported identity for imperative Provider lookup */
22
+ type StoreScope = string | number | symbol;
23
+ /** Imperative reader for the latest committed Store snapshot */
24
+ interface StoreGetter<TStore> {
25
+ /** Type-only Store getter identity */
26
+ readonly [storeGetterMarker]: TStore;
27
+ /** Read the most recently mounted Store */
28
+ (): TStore;
29
+ /** Read the most recently mounted Store matching one scope */
30
+ (scope: StoreScope): TStore;
31
+ }
20
32
  /** Provider created for a Store hook */
21
- type StoreProvider<TProps> = FC<PropsWithChildren<TProps>>;
33
+ type StoreProvider<TProps> = FC<PropsWithChildren<TProps & {
34
+ /** Optional identity used by the imperative Store getter */
35
+ scope?: StoreScope;
36
+ }>>;
22
37
  /** Hook returning the exact external Store instance */
23
38
  interface StoreInstanceHook<TStore> {
24
39
  /** Type-only Store instance hook identity */
@@ -26,9 +41,9 @@ interface StoreInstanceHook<TStore> {
26
41
  (): TStore;
27
42
  }
28
43
  /** Provider carrying an existing external Store instance */
29
- type ExternalStoreProvider<TStore> = StoreProvider<{
44
+ type ExternalStoreProvider<TStore> = FC<PropsWithChildren<{
30
45
  store: TStore;
31
- }> & {
46
+ }>> & {
32
47
  /** Type-only external Store Provider identity */
33
48
  readonly [externalStoreProviderMarker]: TStore;
34
49
  };
@@ -48,11 +63,11 @@ declare function ref<T extends object>(value: T): T;
48
63
  /**
49
64
  * Create a React Store with automatic tracking and explicit selector support
50
65
  */
51
- declare function createStore<TStore, TProps = Record<never, never>>(useModel: (props: TProps) => TStore, options?: StoreOptions): readonly [StoreHook<TStore>, StoreProvider<TProps>];
66
+ declare function createStore<TStore, TProps = Record<never, never>>(useModel: (props: TProps) => TStore, options?: StoreOptions): readonly [StoreHook<TStore>, StoreProvider<TProps>, StoreGetter<TStore>];
52
67
  /**
53
68
  * Bind existing external Store instances to scoped React consumers
54
69
  */
55
70
  declare function bindStore<TStore extends ExternalStore<TSnapshot>, TSnapshot = ExternalStoreSnapshot<TStore>>(options?: StoreOptions): StoreBinding<TStore, TSnapshot>;
56
71
  declare function bindStore<TStore extends ExternalStore<TSnapshot>, TSnapshot = ExternalStoreSnapshot<TStore>>(name?: string, options?: StoreOptions): StoreBinding<TStore, TSnapshot>;
57
72
  //#endregion
58
- export { ExternalStore, ExternalStoreSnapshot, StoreBinding, StoreHook, StoreOptions, StoreProvider, StoreSelector, bindStore, createStore, ref };
73
+ export { ExternalStore, ExternalStoreSnapshot, StoreBinding, StoreGetter, StoreHook, StoreOptions, StoreProvider, StoreScope, StoreSelector, bindStore, createStore, ref };
package/dist/index.mjs CHANGED
@@ -236,12 +236,33 @@ function createStore(useModel, options) {
236
236
  const StoreContext = createContext(void 0);
237
237
  const storeName = useModel.name || "KerrosStore";
238
238
  const tracking = options?.tracking ?? true;
239
+ const registrations = [];
239
240
  /** Run the model Hook and publish its committed snapshot */
240
241
  const StoreProvider = (props) => {
242
+ const parent = useContext(StoreContext);
241
243
  const { children, ...storeProps } = props;
242
244
  const model = useModel(storeProps);
243
245
  const [container] = useState(() => createStoreContainer(model));
246
+ const scope = props.scope;
244
247
  useStoreLayoutEffect(() => container.publish(model), [container, model]);
248
+ useStoreLayoutEffect(() => {
249
+ const registration = {
250
+ container,
251
+ parent,
252
+ scope
253
+ };
254
+ const descendantIndex = registrations.findIndex((candidate) => isStoreDescendant(candidate, container, registrations));
255
+ if (descendantIndex < 0) registrations.push(registration);
256
+ else registrations.splice(descendantIndex, 0, registration);
257
+ return () => {
258
+ const index = registrations.lastIndexOf(registration);
259
+ if (index >= 0) registrations.splice(index, 1);
260
+ };
261
+ }, [
262
+ container,
263
+ parent,
264
+ scope
265
+ ]);
245
266
  return createElement(StoreContext.Provider, { value: container }, children);
246
267
  };
247
268
  StoreProvider.displayName = `${storeName}Provider`;
@@ -250,7 +271,28 @@ function createStore(useModel, options) {
250
271
  const useStore = ((selector) => {
251
272
  return useStoreValue(useStoreContext(StoreContext), selector, tracking);
252
273
  });
253
- return [useStore, StoreProvider];
274
+ /** Read the latest committed Store matching the optional scope */
275
+ const getStore = ((scope) => {
276
+ for (let index = registrations.length - 1; index >= 0; index--) {
277
+ const registration = registrations[index];
278
+ if (scope === void 0 || Object.is(registration.scope, scope)) return registration.container.getSnapshot();
279
+ }
280
+ throw new Error(scope === void 0 ? "Kerros store getter requires a mounted Provider" : "Kerros store getter could not find a mounted Provider for the requested scope");
281
+ });
282
+ return [
283
+ useStore,
284
+ StoreProvider,
285
+ getStore
286
+ ];
287
+ }
288
+ /** Test whether one committed Provider is nested below another container */
289
+ function isStoreDescendant(registration, ancestor, registrations) {
290
+ let parent = registration.parent;
291
+ while (parent) {
292
+ if (parent === ancestor) return true;
293
+ parent = registrations.find((candidate) => candidate.container === parent)?.parent;
294
+ }
295
+ return false;
254
296
  }
255
297
  function bindStore(nameOrOptions = "KerrosExternalStore", inputOptions) {
256
298
  const name = typeof nameOrOptions === "string" ? nameOrOptions : "KerrosExternalStore";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@violetflux/kerros",
3
- "version": "0.2.4",
3
+ "version": "0.3.0",
4
4
  "description": "Hook-native state sharing for React with automatic access tracking and focused selectors.",
5
5
  "keywords": [
6
6
  "react",
@@ -84,18 +84,24 @@
84
84
  },
85
85
  "devDependencies": {
86
86
  "@eslint/js": "10.0.1",
87
+ "@rsbuild/plugin-tailwindcss": "^2.0.3",
87
88
  "@rspress/core": "2.0.17",
89
+ "@tabler/icons-react": "^3.46.0",
88
90
  "@types/jsdom": "28.0.3",
89
91
  "@types/node": "26.1.1",
90
92
  "@types/react": "19.2.17",
91
93
  "@types/react-dom": "19.2.3",
92
94
  "@types/use-sync-external-store": "1.5.0",
95
+ "clsx": "^2.1.1",
93
96
  "eslint": "10.7.0",
94
97
  "eslint-plugin-react-hooks": "7.1.1",
95
98
  "globals": "17.7.0",
96
99
  "jsdom": "29.1.1",
100
+ "motion": "^13.1.0",
97
101
  "react": "19.2.7",
98
102
  "react-dom": "19.2.7",
103
+ "tailwind-merge": "^3.6.0",
104
+ "tailwindcss": "^4.3.3",
99
105
  "tinybench": "6.1.2",
100
106
  "tsdown": "0.22.9",
101
107
  "typescript": "5.9.3",
@@ -45,7 +45,7 @@ function useCounterModel() {
45
45
  return { count, increment }
46
46
  }
47
47
 
48
- export const [useCounter, CounterProvider] = createStore(useCounterModel)
48
+ export const [useCounter, CounterProvider, getCounter] = createStore(useCounterModel)
49
49
  ```
50
50
 
51
51
  Mount the Provider at the narrowest ancestor shared by all consumers:
@@ -72,6 +72,18 @@ function Counter() {
72
72
 
73
73
  Kerros automatically tracks object, array, and nested property reads made during render. An update to an unread field must not rerender this component; Kerros does not deep-compare the complete Store.
74
74
 
75
+ Use the third return value only for imperative work outside React. `getCounter()` reads the most recently mounted committed Provider; pass a `string`, `number`, or `symbol` scope to select the latest matching Provider:
76
+
77
+ ```tsx
78
+ <CounterProvider scope="main">
79
+ <App />
80
+ </CounterProvider>
81
+
82
+ getCounter('main').increment()
83
+ ```
84
+
85
+ The getter does not subscribe and is unavailable before commit, after unmount, or during SSR. Call it at execution time; do not cache its result as a live state source. Duplicate scopes shadow and fall back in mount order. Keep using the Store Hook for rendered state.
86
+
75
87
  ## Subscription modes
76
88
 
77
89
  - `useStore()` is the default and returns a read-only tracked render snapshot. It may be destructured, kept in a render-local variable, returned from a custom Hook, or passed to a synchronously rendered child. Do not mutate it or retain it as a live state object; spread, rest destructuring, enumeration, and serialization create broad subscriptions.
@@ -177,6 +189,7 @@ function Providers({ children }: PropsWithChildren) {
177
189
  - Do not mirror the same mutable state across Stores. Read it from its authoritative Store or move ownership.
178
190
  - Do not create circular Store dependencies. Split ownership or invert the Provider order.
179
191
  - Do not call a Store Hook outside its matching Provider; Kerros intentionally throws a clear error.
192
+ - Do not use a Store getter for reactive rendering. It exposes only committed mounted instances, throws when none match, and selects by mount priority rather than Context ancestry.
180
193
  - 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.
181
194
  - 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.
182
195
  - Respect the project's React version. Avoid React 19-only APIs when the consuming project still supports React 17 or 18.