@violetflux/kerros 0.3.3 → 0.3.5

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
@@ -90,7 +90,7 @@ Der zurückgegebene Store Hook verwendet ohne Argument automatisches Tracking. E
90
90
 
91
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
92
 
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.
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. `null` und `undefined` werden unverändert und mit demselben Typ zurückgegeben. Optionale Objekte können daher ohne Nullprüfung direkt an `ref(value)` übergeben werden.
94
94
 
95
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.
96
96
 
package/README.es.md CHANGED
@@ -90,7 +90,7 @@ Sin argumentos, el Hook devuelto usa seguimiento automático. Los selectores de
90
90
 
91
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
92
 
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.
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. `null` y `undefined` se devuelven sin cambios y conservan su tipo, por lo que los objetos opcionales pueden pasarse directamente a `ref(value)` sin comprobar si son nulos.
94
94
 
95
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`.
96
96
 
package/README.fr.md CHANGED
@@ -90,7 +90,7 @@ Sans argument, le Hook retourné active le suivi automatique. Les sélecteurs d'
90
90
 
91
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
92
 
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.
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. `null` et `undefined` sont renvoyés tels quels, avec leur type préservé. Les objets optionnels peuvent donc être passés directement à `ref(value)` sans vérification de nullité.
94
94
 
95
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`.
96
96
 
package/README.ja.md CHANGED
@@ -91,7 +91,7 @@ const [useStream, StreamProvider] = bindStore<Stream>('Stream')
91
91
 
92
92
  3 番目の getter は React 外から最後にコミットされた Provider を読み取ります。Provider の `scope?: string | number | symbol` を渡すと完全一致する最新インスタンスを選べます。getter は購読せず、利用可能な Provider がなければエラーを投げます。
93
93
 
94
- React Element と Portal は自動的に原子的な値になります。`useRef()` と `createRef()` はそのまま返せます。`ref(value)` は Proxy 非互換の値や厳密な同一性が必要な場合だけ使います。
94
+ React Element と Portal は自動的に原子的な値になります。`useRef()` と `createRef()` はそのまま返せます。`ref(value)` は Proxy 非互換の値や厳密な同一性が必要な場合だけ使います。 `null` と `undefined` は型を保ったまま返されるため、省略可能なオブジェクトも null チェックなしで `ref(value)` に直接渡せます。
95
95
 
96
96
  高度な連携として、既存の Headless External Store にだけ `bindStore` を使います。通常の Hook 状態には `createStore` を使ってください。Context は元の Store インスタンスだけを保持し、コンシューマーは `getSnapshot` と `subscribe` を直接利用します。
97
97
 
package/README.ko.md CHANGED
@@ -91,7 +91,7 @@ const [useStream, StreamProvider] = bindStore<Stream>('Stream')
91
91
 
92
92
  세 번째 getter는 React 밖에서 마지막으로 커밋된 Provider를 읽습니다. Provider의 `scope?: string | number | symbol`을 전달하면 정확히 일치하는 최신 인스턴스를 선택합니다. getter는 구독하지 않으며 사용 가능한 Provider가 없으면 오류를 던집니다.
93
93
 
94
- React Element와 Portal은 자동으로 원자 값이 됩니다. `useRef()`와 `createRef()`는 그대로 반환할 수 있으며 `ref(value)`는 Proxy 비호환 값이나 엄격한 동일성이 필요할 때만 사용합니다.
94
+ React Element와 Portal은 자동으로 원자 값이 됩니다. `useRef()`와 `createRef()`는 그대로 반환할 수 있으며 `ref(value)`는 Proxy 비호환 값이나 엄격한 동일성이 필요할 때만 사용합니다. `null`과 `undefined`는 타입을 유지한 채 그대로 반환되므로 선택적 객체도 null 검사 없이 `ref(value)`에 직접 전달할 수 있습니다.
95
95
 
96
96
  고급 통합이 필요한 기존 Headless External Store에만 `bindStore`를 사용하세요. 일반 Hook 상태에는 `createStore`를 사용합니다. Context는 원래 Store 인스턴스만 보관하고 소비자는 `getSnapshot`과 `subscribe`를 직접 사용합니다.
97
97
 
package/README.md CHANGED
@@ -154,7 +154,7 @@ const { count, setCount } = useCounter()
154
154
 
155
155
  Primitive snapshots use `Object.is`. `Map`, `Set`, class instances, and other non-plain objects are treated as atomic references. Store and external Store snapshots must be immutable: publish a new reference for every observable change.
156
156
 
157
- React elements and portals are atomic automatically. Standard `useRef()` and `createRef()` containers can be returned directly in React 17, 18, and 19. Use `ref(value)` only for Proxy-intolerant third-party objects or strict identity; internal mutation of an atomic value is not reactive.
157
+ React elements and portals are atomic automatically. Standard `useRef()` and `createRef()` containers can be returned directly in React 17, 18, and 19. Use `ref(value)` only for Proxy-intolerant third-party objects or strict identity; internal mutation of an atomic value is not reactive. `null` and `undefined` pass through unchanged, with their types preserved, so optional objects can be passed directly as `ref(value)` without a null check.
158
158
 
159
159
  The selector-free result is the component's read-only tracked snapshot. You may destructure it, keep it in a render-local variable, or pass it to a synchronously rendered child. Do not mutate it or retain it in state, refs, module variables, or long-lived caches as a live state object; spread, rest destructuring, enumeration, and serialization create broad subscriptions. Reactive Effects should read values during render and declare correct dependencies. Use `useInstance()` only for imperative latest-state reads that do not drive rendering, and never expose an Effect Event as a public Store action.
160
160
 
@@ -257,7 +257,7 @@ function createStore<TStore, TProps = Record<never, never>>(
257
257
  ### `ref` (identity escape hatch)
258
258
 
259
259
  ```ts
260
- function ref<T extends object>(value: T): T
260
+ function ref<T extends object | null | undefined>(value: T): T
261
261
  ```
262
262
 
263
263
  Marks an object as atomic and returns the exact same identity. Standard React refs do not need this helper.
@@ -286,7 +286,7 @@ Kerros uses the official `use-sync-external-store` shim for React 17 and prefers
286
286
 
287
287
  ## ESLint guardrails
288
288
 
289
- Install the separate type-aware plugin for the safest default usage:
289
+ Install the lightweight plugin for file-local Kerros conventions:
290
290
 
291
291
  ```sh
292
292
  npm install --save-dev @violetflux/eslint-plugin-kerros @typescript-eslint/parser
@@ -295,12 +295,12 @@ npm install --save-dev @violetflux/eslint-plugin-kerros @typescript-eslint/parse
295
295
  ```js
296
296
  import kerros from '@violetflux/eslint-plugin-kerros'
297
297
 
298
- export default [kerros.configs.recommendedTypeChecked]
298
+ export default [kerros.configs.recommended]
299
299
  ```
300
300
 
301
- `recommendedTypeChecked` enables all 16 rules as errors and uses TypeScript `projectService`. Very large repositories may use `kerros.configs.fastTypeChecked`, which keeps type-aware Store recognition but disables the most expensive whole-program and deep analyses. See the [measured ESLint benchmark](https://github.com/violetflux/kerros/blob/main/benchmarks/eslint/RESULTS.md); the fast profile is a tradeoff, not an untyped fallback. The plugin analyzes complete TS/TSX files, not incomplete Markdown snippets.
301
+ `recommended` is the plugin's only preset and does not enable TypeScript `projectService`. It recognizes direct `createStore` and `bindStore` imports, including local aliases and namespace imports, then checks the Store bindings and Hook calls created in the same file. Cross-file re-exports, wrappers, and imported Store Hooks are intentionally outside this lightweight boundary. See the [measured ESLint benchmark](https://github.com/violetflux/kerros/blob/main/benchmarks/eslint/RESULTS.md).
302
302
 
303
- `no-broad-store-access` rejects enumeration, serialization, and spreading of a complete selector-free Store snapshot. Nested object fields and intentional complete adapters inside `createStore` models are allowed by default; stricter projects can enable `includeObjectFields` and `includeStoreModels` in the rule options.
303
+ The six included rules cover factory scope, model and binding names, selector parameter names, whole-Store selectors, and broad access to file-local selector-free snapshots. The plugin favors bounded memory and predictable diagnostics over cross-file inference.
304
304
 
305
305
  For maintainers, npm Trusted Publisher entries must be configured for both `@violetflux/kerros` and `@violetflux/eslint-plugin-kerros`. That npm-side configuration is the only release step outside this repository; CI checks and publishes the runtime first, then the plugin.
306
306
 
package/README.zh-CN.md CHANGED
@@ -146,7 +146,7 @@ const { count, setCount } = useCounter()
146
146
 
147
147
  基础类型快照使用 `Object.is`。`Map`、`Set`、类实例及其他非普通对象按整体引用处理。Store 和 External Store 快照必须保持不可变:每次可观察变化都发布新引用。
148
148
 
149
- React Element 和 Portal 会自动作为原子值处理。React 17、18、19 的标准 `useRef()`、`createRef()` 容器可以直接返回。只有第三方对象不能接受 Proxy,或者必须保留严格身份时才使用 `ref(value)`;原子值的内部原地修改不是响应式更新。
149
+ React Element 和 Portal 会自动作为原子值处理。React 17、18、19 的标准 `useRef()`、`createRef()` 容器可以直接返回。只有第三方对象不能接受 Proxy,或者必须保留严格身份时才使用 `ref(value)`;原子值的内部原地修改不是响应式更新。 `null` 和 `undefined` 会原样返回并保留类型,因此可选对象可以直接传给 `ref(value)`,无需在调用处判空。
150
150
 
151
151
  无 selector 的结果是当前组件的只读追踪快照,可以直接解构、保存在渲染局部变量中,或传给同步渲染的子组件继续读取。不要修改快照,也不要把它保存到 state、ref、模块变量或长期缓存后当作实时状态源;展开、rest 解构、枚举和序列化会形成宽泛订阅。响应式 Effect 应在渲染期间读取值并声明正确依赖;只有不参与渲染、需要执行时读取最新状态的命令式逻辑才使用 `useInstance()`。也不要把 Effect Event 暴露成公共 Store action。
152
152
 
@@ -249,7 +249,7 @@ function createStore<TStore, TProps = Record<never, never>>(
249
249
  ### `ref`(身份逃生口)
250
250
 
251
251
  ```ts
252
- function ref<T extends object>(value: T): T
252
+ function ref<T extends object | null | undefined>(value: T): T
253
253
  ```
254
254
 
255
255
  把对象标记为原子值并返回完全相同的身份。标准 React ref 不需要这个辅助函数。
@@ -278,7 +278,7 @@ React 17 使用官方 `use-sync-external-store` shim;React 18 和 19 可用时
278
278
 
279
279
  ## ESLint 防护规则
280
280
 
281
- 建议安装独立的类型感知插件,并默认使用最严格配置:
281
+ 建议安装轻量插件,检查当前文件内的 Kerros 约束:
282
282
 
283
283
  ```sh
284
284
  npm install --save-dev @violetflux/eslint-plugin-kerros @typescript-eslint/parser
@@ -287,12 +287,12 @@ npm install --save-dev @violetflux/eslint-plugin-kerros @typescript-eslint/parse
287
287
  ```js
288
288
  import kerros from '@violetflux/eslint-plugin-kerros'
289
289
 
290
- export default [kerros.configs.recommendedTypeChecked]
290
+ export default [kerros.configs.recommended]
291
291
  ```
292
292
 
293
- `recommendedTypeChecked` 把全部 16 条规则设为 error,并启用 TypeScript `projectService`。超大型仓库可改用 `kerros.configs.fastTypeChecked`:它仍然通过类型识别真实 Kerros Hook,只关闭最昂贵的全程序与深层分析。请参考[真实 ESLint 压测](https://github.com/violetflux/kerros/blob/main/benchmarks/eslint/RESULTS.md);fast 是性能取舍,不是不可靠的命名降级。插件首版只分析完整 TS/TSX 文件,不分析不完整 Markdown 代码块。
293
+ `recommended` 是插件唯一的预设,不启用 TypeScript `projectService`。它识别直接导入的 `createStore`、`bindStore`、本地别名和 namespace 导入,并检查同一文件内生成的 Store 绑定与 Hook 调用。跨文件转导出、包装函数和从其他文件导入的 Store Hook 有意留在轻量边界之外。请参考[真实 ESLint 压测](https://github.com/violetflux/kerros/blob/main/benchmarks/eslint/RESULTS.md)
294
294
 
295
- `no-broad-store-access` 会禁止枚举、序列化或展开完整的无 selector Store 快照,默认允许操作嵌套对象字段,也允许在 `createStore` model 内有意代理完整 Store。需要收紧的项目可以在规则选项中启用 `includeObjectFields` 和 `includeStoreModels`。
295
+ 预设包含六条规则,覆盖工厂作用域、Model 与绑定命名、selector 参数名、返回完整 Store,以及对当前文件无 selector 快照的宽泛访问。插件优先保证内存有界和诊断稳定,不追踪跨文件类型身份。
296
296
 
297
297
  维护者还需要分别为 `@violetflux/kerros` 和 `@violetflux/eslint-plugin-kerros` 配置 npm Trusted Publisher。这是唯一的仓库外发布步骤;仓库内工作流会先检查并发布运行库,再发布插件。
298
298
 
package/dist/index.cjs CHANGED
@@ -224,9 +224,9 @@ function isShallowComparable(value) {
224
224
  }
225
225
  //#endregion
226
226
  //#region src/index.tsx
227
- /** Preserve an exact object identity and compare it as one atomic Store value */
227
+ /** 将对象标记为原子值并保留原始身份,空值直接透传 */
228
228
  function ref(value) {
229
- markToTrack(value, false);
229
+ if (value !== null && value !== void 0) markToTrack(value, false);
230
230
  return value;
231
231
  }
232
232
  const useStoreLayoutEffect = typeof window === "undefined" ? react.useEffect : react.useLayoutEffect;
package/dist/index.d.cts CHANGED
@@ -58,8 +58,8 @@ interface ExternalStore<TSnapshot> {
58
58
  type ExternalStoreSnapshot<TStore> = TStore extends ExternalStore<infer TSnapshot> ? TSnapshot : never;
59
59
  /** React bindings created for an existing external Store type */
60
60
  type StoreBinding<TStore extends ExternalStore<TSnapshot>, TSnapshot = ExternalStoreSnapshot<TStore>> = readonly [StoreHook<TSnapshot>, ExternalStoreProvider<TStore>, StoreInstanceHook<TStore>];
61
- /** Preserve an exact object identity and compare it as one atomic Store value */
62
- declare function ref<T extends object>(value: T): T;
61
+ /** 将对象标记为原子值并保留原始身份,空值直接透传 */
62
+ declare function ref<T extends object | null | undefined>(value: T): T;
63
63
  /**
64
64
  * Create a React Store with automatic tracking and explicit selector support
65
65
  */
package/dist/index.d.mts CHANGED
@@ -58,8 +58,8 @@ interface ExternalStore<TSnapshot> {
58
58
  type ExternalStoreSnapshot<TStore> = TStore extends ExternalStore<infer TSnapshot> ? TSnapshot : never;
59
59
  /** React bindings created for an existing external Store type */
60
60
  type StoreBinding<TStore extends ExternalStore<TSnapshot>, TSnapshot = ExternalStoreSnapshot<TStore>> = readonly [StoreHook<TSnapshot>, ExternalStoreProvider<TStore>, StoreInstanceHook<TStore>];
61
- /** Preserve an exact object identity and compare it as one atomic Store value */
62
- declare function ref<T extends object>(value: T): T;
61
+ /** 将对象标记为原子值并保留原始身份,空值直接透传 */
62
+ declare function ref<T extends object | null | undefined>(value: T): T;
63
63
  /**
64
64
  * Create a React Store with automatic tracking and explicit selector support
65
65
  */
package/dist/index.mjs CHANGED
@@ -223,9 +223,9 @@ function isShallowComparable(value) {
223
223
  }
224
224
  //#endregion
225
225
  //#region src/index.tsx
226
- /** Preserve an exact object identity and compare it as one atomic Store value */
226
+ /** 将对象标记为原子值并保留原始身份,空值直接透传 */
227
227
  function ref(value) {
228
- markToTrack(value, false);
228
+ if (value !== null && value !== void 0) markToTrack(value, false);
229
229
  return value;
230
230
  }
231
231
  const useStoreLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@violetflux/kerros",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "description": "Hook-native state sharing for React with automatic access tracking and focused selectors.",
5
5
  "keywords": [
6
6
  "react",
@@ -96,6 +96,7 @@ The getter does not subscribe and is unavailable before commit, after unmount, o
96
96
  - Return standard `useRef()` and `createRef()` containers directly. They work with DOM refs, `forwardRef`, and `useImperativeHandle` in React 17, 18, and 19; do not wrap them by default.
97
97
  - React elements and portals are detected lazily and returned as atomic values without a Proxy.
98
98
  - Import `ref` from `@violetflux/kerros` only for a third-party object that cannot tolerate a Proxy or when strict object identity must survive the tracked snapshot.
99
+ - `ref(value)` accepts `object | null | undefined` and preserves the exact input type. Pass optional objects directly; `null` and `undefined` are returned unchanged without marking.
99
100
  - `ref(value)`, `Map`, `Set`, and class instances are non-reactive internally. Publish a new containing-field reference for observable changes; changing a React ref's `.current` also does not rerender.
100
101
 
101
102
  ## Advanced external Store binding