@violetflux/kerros 0.2.1 → 0.2.3
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 +4 -4
- package/README.zh-CN.md +4 -4
- package/dist/index.cjs +3 -2
- package/dist/index.mjs +3 -2
- package/package.json +1 -1
- package/skills/kerros/SKILL.md +2 -2
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
Kerros is a lightweight way to share state between React components.
|
|
26
26
|
|
|
27
|
-
Write a Store the same way you write a custom Hook. When local state needs to be shared, pass it to `createStore`, mount its Provider, and let each component
|
|
27
|
+
Write a Store the same way you write a custom Hook. When local state needs to be shared, pass it to `createStore`, mount its Provider, and let automatic tracking focus each component on the properties it reads.
|
|
28
28
|
|
|
29
29
|
> [!TIP]
|
|
30
30
|
> **Install with your coding agent** — paste this sentence into your coding agent to install both the dependency and the project Skill:
|
|
@@ -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
|
-
Do not
|
|
157
|
+
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.
|
|
158
158
|
|
|
159
159
|
## Multiple instances
|
|
160
160
|
|
|
@@ -254,7 +254,7 @@ function createStore<TStore, TProps = Record<never, never>>(
|
|
|
254
254
|
|
|
255
255
|
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.
|
|
256
256
|
|
|
257
|
-
Without this API, an integration must either repeat the Context and
|
|
257
|
+
Without this API, an integration must either repeat the Context and focused-subscription code or copy the external snapshot through a second Store. `bindStore` provides Provider scoping, selector-free automatic tracking, and explicit selectors while keeping the original Store as the only state owner.
|
|
258
258
|
|
|
259
259
|
```tsx
|
|
260
260
|
const [useStream, StreamBindingProvider] = bindStore<Stream>('Stream')
|
|
@@ -286,7 +286,7 @@ import kerros from '@violetflux/eslint-plugin-kerros'
|
|
|
286
286
|
export default [kerros.configs.recommendedTypeChecked]
|
|
287
287
|
```
|
|
288
288
|
|
|
289
|
-
`recommendedTypeChecked` enables all
|
|
289
|
+
`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.
|
|
290
290
|
|
|
291
291
|
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.
|
|
292
292
|
|
package/README.zh-CN.md
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
Kerros 是一个在 React 组件间共享状态的轻量方案。
|
|
18
18
|
|
|
19
|
-
你怎么写 custom Hook,就可以怎么写 Store。只有当局部状态需要被多个组件使用时,再交给 `createStore`,用 Provider
|
|
19
|
+
你怎么写 custom Hook,就可以怎么写 Store。只有当局部状态需要被多个组件使用时,再交给 `createStore`,用 Provider 决定共享范围;组件直接解构需要的数据,Kerros 默认自动追踪实际访问的属性。
|
|
20
20
|
|
|
21
21
|
> [!TIP]
|
|
22
22
|
> **使用 Coding Agent 安装**:复制下面这句话并粘贴给你的 Coding Agent,它会同时安装依赖和当前项目的 Skill:
|
|
@@ -146,7 +146,7 @@ const { count, setCount } = useCounter()
|
|
|
146
146
|
|
|
147
147
|
基础类型快照使用 `Object.is`。`Map`、`Set`、类实例及其他非普通对象按整体引用处理。Store 和 External Store 快照必须保持不可变:每次可观察变化都发布新引用。
|
|
148
148
|
|
|
149
|
-
|
|
149
|
+
无 selector 的结果是当前组件的只读追踪快照,可以直接解构、保存在渲染局部变量中,或传给同步渲染的子组件继续读取。不要修改快照,也不要把它保存到 state、ref、模块变量或长期缓存后当作实时状态源;展开、rest 解构、枚举和序列化会形成宽泛订阅。响应式 Effect 应在渲染期间读取值并声明正确依赖;只有不参与渲染、需要执行时读取最新状态的命令式逻辑才使用 `useInstance()`。也不要把 Effect Event 暴露成公共 Store action。
|
|
150
150
|
|
|
151
151
|
## 多个实例
|
|
152
152
|
|
|
@@ -246,7 +246,7 @@ function createStore<TStore, TProps = Record<never, never>>(
|
|
|
246
246
|
|
|
247
247
|
绝大多数应用只需要 `createStore`。只有当某个库或 SDK 已经在 React 外持有权威状态,并提供稳定的 `getSnapshot` 和 `subscribe` 函数时,才使用 `bindStore`。
|
|
248
248
|
|
|
249
|
-
没有这个 API 时,集成层只能重复实现 Context
|
|
249
|
+
没有这个 API 时,集成层只能重复实现 Context 和细粒度订阅,或者把 External Store 快照复制进第二个 Store。`bindStore` 提供 Provider 作用域、无 selector 自动追踪和显式 selector,原 Store 仍是唯一状态所有者。
|
|
250
250
|
|
|
251
251
|
```tsx
|
|
252
252
|
const [useStream, StreamBindingProvider] = bindStore<Stream>('Stream')
|
|
@@ -278,7 +278,7 @@ import kerros from '@violetflux/eslint-plugin-kerros'
|
|
|
278
278
|
export default [kerros.configs.recommendedTypeChecked]
|
|
279
279
|
```
|
|
280
280
|
|
|
281
|
-
`recommendedTypeChecked` 把全部
|
|
281
|
+
`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 代码块。
|
|
282
282
|
|
|
283
283
|
维护者还需要分别为 `@violetflux/kerros` 和 `@violetflux/eslint-plugin-kerros` 配置 npm Trusted Publisher。这是唯一的仓库外发布步骤;仓库内工作流会先检查并发布运行库,再发布插件。
|
|
284
284
|
|
package/dist/index.cjs
CHANGED
|
@@ -4,6 +4,7 @@ let proxy_compare = require("proxy-compare");
|
|
|
4
4
|
let use_sync_external_store_shim_with_selector = require("use-sync-external-store/shim/with-selector");
|
|
5
5
|
//#region src/tracking.ts
|
|
6
6
|
const useStoreLayoutEffect$1 = typeof window === "undefined" ? react.useEffect : react.useLayoutEffect;
|
|
7
|
+
const proxyTargetCache = /* @__PURE__ */ new WeakMap();
|
|
7
8
|
/**
|
|
8
9
|
* Subscribe through either an explicit selector, shallow snapshots, or render access tracking
|
|
9
10
|
*/
|
|
@@ -27,7 +28,7 @@ function useStoreValue(store, selector, tracking) {
|
|
|
27
28
|
const snapshot = (0, use_sync_external_store_shim_with_selector.useSyncExternalStoreWithSelector)(store.subscribe, store.getSnapshot, store.getSnapshot, selectSnapshot, compareSelections);
|
|
28
29
|
const affected = /* @__PURE__ */ new WeakMap();
|
|
29
30
|
const shouldTrack = !selector && tracking;
|
|
30
|
-
const value = shouldTrack ? (0, proxy_compare.createProxy)(snapshot, affected, proxyCache) : snapshot;
|
|
31
|
+
const value = shouldTrack ? (0, proxy_compare.createProxy)(snapshot, affected, proxyCache, proxyTargetCache) : snapshot;
|
|
31
32
|
useStoreLayoutEffect$1(() => {
|
|
32
33
|
if (shouldTrack) {
|
|
33
34
|
const renderedSnapshot = snapshot;
|
|
@@ -36,7 +37,7 @@ function useStoreValue(store, selector, tracking) {
|
|
|
36
37
|
snapshot: renderedSnapshot
|
|
37
38
|
};
|
|
38
39
|
const currentSnapshot = store.getSnapshot();
|
|
39
|
-
if ((0, proxy_compare.isChanged)(renderedSnapshot, currentSnapshot, affected, /* @__PURE__ */ new WeakMap())) calibrate(currentSnapshot);
|
|
40
|
+
if (!Object.is(renderedSnapshot, currentSnapshot) && (0, proxy_compare.isChanged)(renderedSnapshot, currentSnapshot, affected, /* @__PURE__ */ new WeakMap())) calibrate(currentSnapshot);
|
|
40
41
|
}
|
|
41
42
|
}, [
|
|
42
43
|
affected,
|
package/dist/index.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { createProxy, isChanged } from "proxy-compare";
|
|
|
3
3
|
import { useSyncExternalStoreWithSelector } from "use-sync-external-store/shim/with-selector";
|
|
4
4
|
//#region src/tracking.ts
|
|
5
5
|
const useStoreLayoutEffect$1 = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
6
|
+
const proxyTargetCache = /* @__PURE__ */ new WeakMap();
|
|
6
7
|
/**
|
|
7
8
|
* Subscribe through either an explicit selector, shallow snapshots, or render access tracking
|
|
8
9
|
*/
|
|
@@ -26,7 +27,7 @@ function useStoreValue(store, selector, tracking) {
|
|
|
26
27
|
const snapshot = useSyncExternalStoreWithSelector(store.subscribe, store.getSnapshot, store.getSnapshot, selectSnapshot, compareSelections);
|
|
27
28
|
const affected = /* @__PURE__ */ new WeakMap();
|
|
28
29
|
const shouldTrack = !selector && tracking;
|
|
29
|
-
const value = shouldTrack ? createProxy(snapshot, affected, proxyCache) : snapshot;
|
|
30
|
+
const value = shouldTrack ? createProxy(snapshot, affected, proxyCache, proxyTargetCache) : snapshot;
|
|
30
31
|
useStoreLayoutEffect$1(() => {
|
|
31
32
|
if (shouldTrack) {
|
|
32
33
|
const renderedSnapshot = snapshot;
|
|
@@ -35,7 +36,7 @@ function useStoreValue(store, selector, tracking) {
|
|
|
35
36
|
snapshot: renderedSnapshot
|
|
36
37
|
};
|
|
37
38
|
const currentSnapshot = store.getSnapshot();
|
|
38
|
-
if (isChanged(renderedSnapshot, currentSnapshot, affected, /* @__PURE__ */ new WeakMap())) calibrate(currentSnapshot);
|
|
39
|
+
if (!Object.is(renderedSnapshot, currentSnapshot) && isChanged(renderedSnapshot, currentSnapshot, affected, /* @__PURE__ */ new WeakMap())) calibrate(currentSnapshot);
|
|
39
40
|
}
|
|
40
41
|
}, [
|
|
41
42
|
affected,
|
package/package.json
CHANGED
package/skills/kerros/SKILL.md
CHANGED
|
@@ -74,7 +74,7 @@ Kerros automatically tracks object, array, and nested property reads made during
|
|
|
74
74
|
|
|
75
75
|
## Subscription modes
|
|
76
76
|
|
|
77
|
-
- `useStore()` is the default.
|
|
77
|
+
- `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.
|
|
78
78
|
- `useStore(s => ({ ... }))` is the advanced path for derived values or measured hot spots. Keep the selector inline, name its parameter `s`, and return an object whose top-level fields are shallowly compared with `Object.is`.
|
|
79
79
|
- `createStore(model, { tracking: false })` and `bindStore({ tracking: false })` disable automatic tracking for selector-free calls and compare the complete Store at the top level instead.
|
|
80
80
|
- Primitive Store snapshots use `Object.is`. `Map`, `Set`, class instances, and other atomic objects are tracked by reference as a whole.
|
|
@@ -101,7 +101,7 @@ Mount the original instance without mirroring its snapshot:
|
|
|
101
101
|
</StreamBindingProvider>
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
Treat `useStream()` as a read-only tracked render snapshot: destructure it, keep it in a render-local variable, return it from a custom Hook, or pass it to a synchronously rendered child. Use an explicit selector only for derived values or measured hot spots. Do not mutate the snapshot or retain it as a live state object. Use `useStreamInstance()` only in Provider descendants that need imperative commands, imperative latest-state reads that do not drive rendering, 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.
|
|
105
105
|
|
|
106
106
|
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.
|
|
107
107
|
|