doxum 0.1.10 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -234,6 +234,9 @@ Pure computations receive current values. Advanced processors use tagged specs:
234
234
  `project({ kind: 'collection', sources, build })`.
235
235
  Document collection events provide scoped `read.get/has/ids`, final candidate
236
236
  keys, order dirtiness, commits and reset state.
237
+ Ordinary mappers intentionally model only one-source, same-key transforms.
238
+ Cross-collection relationships use an advanced collection processor with explicit,
239
+ application-owned dependency indexes; processor reads are not tracked automatically.
237
240
 
238
241
  Processors settle before external listeners. `store.batch` defers graph
239
242
  settlement and projection notifications, but document commits/listeners remain
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doxum",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "description": "Doxum is a typed runtime for complex mutable documents.",
@@ -7,6 +7,9 @@ description: 'Use doxum and doxum/react for schemas, scoped draft updates, Chang
7
7
 
8
8
  Read the [English guide](references/guide.en.md) or [中文指南](references/guide.zh-CN.md).
9
9
  For examples read [patterns](references/patterns.en.md) or [中文模式](references/patterns.zh-CN.md).
10
+ For any projection design or implementation, especially custom processors or
11
+ cross-collection dependencies, read the [projection reference](references/projections.en.md)
12
+ or [中文 Projection 参考](references/projections.zh-CN.md).
10
13
  Before runtime changes read [invariants](references/invariants.en.md) or
11
14
  [中文不变量](references/invariants.zh-CN.md).
12
15
 
@@ -26,7 +29,11 @@ Before runtime changes read [invariants](references/invariants.en.md) or
26
29
  kinds, optional before/after order in the same group, and a separate root reset.
27
30
  Standalone order records and duplicate groups are invalid. Grouping preserves field-level impact.
28
31
  - Paths belong in subscription, impact and collection source callbacks.
29
- - Projection dependencies are explicit; React selectors track actual reads.
32
+ - Projection definitions are lazy; one ProjectionStore owns each materialized graph.
33
+ Use ordinary mappers only for one-source, same-key transforms. Advanced processors
34
+ declare every source, own any forward/reverse dependency indexes, derive from final
35
+ batched source state, and rebuild on resets that invalidate those indexes. Core
36
+ projections never track reads automatically; React selectors do.
30
37
  - Observer errors leave commits accepted. Do not retry as if they rolled back.
31
38
  - Core stays framework-neutral; adapters use integration capabilities.
32
39
  - Local sync owns browser persistence/leadership. Network conflict policy and
@@ -91,6 +91,11 @@ Use `project(document, path => path.tasks, mapper)` for incremental mapping.
91
91
  Pure values use `project(sources, compute)`; stateful algorithms use tagged value
92
92
  or collection specs. Definitions are lazy and are materialized by a
93
93
  `createProjectionStore({ onError })` instance. Sources remain explicit.
94
+ Ordinary mappers only model one-source, same-key transforms. Cross-collection or
95
+ cross-key dependencies use an advanced collection processor with an application-owned
96
+ reverse dependency index; core does not track reads performed by processors. See the
97
+ [projection reference](projections.en.md) for event shapes, lifecycle, writer semantics,
98
+ join indexing, batching, and fault recovery.
94
99
  `input` and `project(readable)` connect external boundary values. Dispose the store
95
100
  with the owning service. `store.batch` defers projection settlement/listeners, not document commits/listeners. Reads
96
101
  inside a batch see the last publication; no cross-document rollback is provided.
@@ -79,6 +79,10 @@ observerErrors 属于已提交结果。
79
79
  纯派生值使用 `project(sources, compute)`;有状态算法使用带 kind 的 value
80
80
  或 collection spec。定义是惰性的,由 `createProjectionStore({ onError })`
81
81
  实例物化和管理。sources 仍然显式声明。
82
+ 普通 mapper 只表达单 source、同键转换。跨集合或跨键依赖使用 advanced collection
83
+ processor,并由应用维护反向依赖索引;core 不追踪 processor 内的读取。event 形状、
84
+ 生命周期、writer 语义、join 索引、batch 与 fault 恢复见
85
+ [Projection 参考](projections.zh-CN.md)。
82
86
  `input` 与 `project(readable)` 接入外部边界值。随所属服务 dispose store。
83
87
  `store.batch` 推迟投影结算与通知,但不推迟文档提交和文档通知;内部读取上次发布值,
84
88
  不提供跨文档回滚。
@@ -101,4 +101,7 @@ Use `useProjection` with a store for projection definitions; use `useReadable`
101
101
  for history and other existing Readable values. Custom collection processors stage
102
102
  writer.set/remove/order/replace and use scoped previous/next reads. Candidates span
103
103
  the complete batch; derive output from final state. Processor dependencies remain
104
- explicit even though React selectors track actual reads.
104
+ explicit even though React selectors track actual reads. Use a mapper only when a
105
+ source key affects the same output key. For cross-collection joins, declare every
106
+ source and maintain the domain's reverse dependency index as described in the
107
+ [projection reference](projections.en.md).
@@ -100,4 +100,6 @@ store.get(scaled);
100
100
  使用 `useReadable` 或 `useHistory`。自定义集合 processor 通过
101
101
  writer.set/remove/order/replace 暂存输出,
102
102
  previous/next 读取只在作用域内有效。candidates 汇总整个 batch,以最终状态派生输出。
103
- React 追踪实际读取,但 processor 依赖仍显式声明。
103
+ React 追踪实际读取,但 processor 依赖仍显式声明。只有 source 键影响同名输出键时
104
+ 才使用 mapper。跨集合 join 应声明全部 source,并按
105
+ [Projection 参考](projections.zh-CN.md)维护领域反向依赖索引。
@@ -0,0 +1,211 @@
1
+ # Doxum Projection Reference
2
+
3
+ Use this reference when choosing, implementing, or reviewing a projection. The
4
+ [public long-form contract](../../../docs/projections.md) provides a complete join
5
+ example; source code inspection should not be necessary for normal application work.
6
+
7
+ ## Choose The Form
8
+
9
+ | Relationship | API |
10
+ | --------------------------------------------- | ------------------------------------------------- |
11
+ | Whole document source | `project(document)` |
12
+ | Target-limited document source | `project(document, [pick, ...])` |
13
+ | Document collection source | `project(document, pick)` |
14
+ | One source key to the same output key | `project(collection, mapper)` |
15
+ | Pure current values to one value | `project(sources, compute)` |
16
+ | Stateful incremental value | `project({ kind: 'value', sources, build })` |
17
+ | Multiple sources or cross-key collection work | `project({ kind: 'collection', sources, build })` |
18
+ | Application input | `input(initial)` and `store.set(input, value)` |
19
+ | Existing `Readable` | `project(readable)` |
20
+
21
+ Definitions are lazy and reusable. `createProjectionStore({ onError })` owns one
22
+ materialization graph. Every store has independent published values, revisions,
23
+ inputs, subscriptions, processor instances, and closure indexes.
24
+
25
+ An ordinary mapper is deliberately limited to:
26
+
27
+ ```text
28
+ source key K changed -> output key K may change
29
+ ```
30
+
31
+ It rebuilds on reset and follows source order, but does not track reads performed
32
+ inside the mapper. Never read another collection in a mapper and assume it becomes
33
+ a dependency. Declare all sources in an advanced processor instead.
34
+
35
+ ## Source Event Contract
36
+
37
+ Advanced processors receive events, not just current values:
38
+
39
+ ```ts
40
+ type ValueEvent<T> = {
41
+ value: T;
42
+ previous: T;
43
+ changed: boolean;
44
+ revision: number;
45
+ reset: boolean;
46
+ };
47
+
48
+ type CollectionEvent<K extends string, V> = {
49
+ get(key: K): V | undefined;
50
+ has(key: K): boolean;
51
+ ids(): readonly K[];
52
+ change: CollectionImpact<K> | undefined;
53
+ revision: number;
54
+ reset: boolean;
55
+ };
56
+
57
+ type DocumentEvent<S> = {
58
+ read: Read<S>;
59
+ revision: number;
60
+ commits: readonly DocumentCommit<S>[];
61
+ reset: boolean;
62
+ };
63
+
64
+ type DocumentCollectionEvent<S, N, K extends string> = {
65
+ read: CollectionAccess<K, N>;
66
+ revision: number;
67
+ commits: readonly DocumentCommit<S>[];
68
+ reset: boolean;
69
+ candidates: { keys: readonly K[]; orderDirty: boolean };
70
+ };
71
+ ```
72
+
73
+ Projected collection `change` is incremental (`added`, `removed`, `updated`,
74
+ `orderChanged`), reset, or undefined. Document collection candidates are the
75
+ union across all relevant commits in a store batch. A later commit may cancel an
76
+ earlier one without removing its candidate key. Always derive from final `read`
77
+ state. Use `commit.impact.collection(pick)` for per-commit relationship changes.
78
+
79
+ Across a batch, a value event's `previous` is the pre-batch value and `value` is
80
+ the final value. A net-zero document transaction produces no commit or candidates.
81
+
82
+ ## Advanced Value Processor
83
+
84
+ ```ts
85
+ const value = project({
86
+ kind: 'value',
87
+ sources,
88
+ build: events => ({
89
+ value: buildValue(events),
90
+ update: events =>
91
+ needsRebuild(events)
92
+ ? { kind: 'rebuild' }
93
+ : changed(events)
94
+ ? { kind: 'changed', value: updateValue(events) }
95
+ : { kind: 'unchanged' },
96
+ }),
97
+ });
98
+ ```
99
+
100
+ Prefer `project(sources, compute)` unless retained state prevents meaningful work.
101
+
102
+ ## Advanced Collection Processor
103
+
104
+ ```ts
105
+ const result = project({
106
+ kind: 'collection',
107
+ sources,
108
+ name: 'optional diagnostic name',
109
+ isEqual: Object.is,
110
+ build: ({ sources, previous, next, writer }) => {
111
+ // Build the complete output and any store-local indexes.
112
+ return {
113
+ update: ({ sources, previous, next, writer }) => {
114
+ // Stage only affected keys, or return { kind: 'rebuild' }.
115
+ },
116
+ };
117
+ },
118
+ });
119
+ ```
120
+
121
+ `build` runs on first materialization, source reset, explicit `store.rebuild`, an
122
+ update-requested rebuild, and fault recovery. Build starts from a cleared output
123
+ and must stage the complete result. Its closure state is local to one store and
124
+ must be reconstructible from sources.
125
+
126
+ `update` runs when a declared source participates in settlement. Returning
127
+ `{ kind: 'rebuild' }` discards staged update writes and performs a fresh build.
128
+ All callbacks are synchronous; Promises and thenables are rejected. Reads and
129
+ writes during processing or notification must not reenter a source or document.
130
+
131
+ `previous`, `next`, source readers, and `writer` are borrowed for the current
132
+ synchronous callback only:
133
+
134
+ - `previous` sees only published output.
135
+ - `next` immediately sees current staged writes.
136
+ - `writer.set(key, value)` stages presence.
137
+ - `writer.remove(key)` stages absence; a missing removal is a no-op.
138
+ - Repeated operations on one key use the last operation.
139
+ - `writer.order(ids)` supplies the entire next order, with every key exactly once.
140
+ - `writer.replace(entries)` clears and replaces all output in entry order; keys
141
+ must be unique.
142
+
143
+ Staged output publishes atomically. Equality defaults to `Object.is`; equal sets
144
+ keep the old reference. Net-zero output does not increment revision or notify.
145
+
146
+ ## Cross-Collection Join Pattern
147
+
148
+ Do not add a generic join abstraction. The domain owns missing references,
149
+ cardinality, reconnect, delete, ordering, and result-key policy. Model the join as
150
+ an advanced collection with explicit sources and indexes.
151
+
152
+ For routes derived from `edges(from, to)` and node geometry, maintain:
153
+
154
+ ```text
155
+ endpoints: edgeId -> [fromNodeId, toNodeId]
156
+ adjacency: nodeId -> Set<edgeId>
157
+ ```
158
+
159
+ On build, scan edges once, populate both indexes, and write the complete routes.
160
+ On an edge event:
161
+
162
+ 1. Read exact changed edge ids from each commit's collection impact.
163
+ 2. Detach every changed id from its old endpoints.
164
+ 3. Read final edge state; attach its final endpoints, or delete its forward entry.
165
+ 4. Add that edge id to the affected output set.
166
+
167
+ On a node collection event, map added/updated/removed node ids through `adjacency`
168
+ and add only adjacent edges to the affected set. Finally read current endpoint
169
+ geometry and set or remove each affected route. Rebuild on either source reset.
170
+
171
+ After an edge reconnects, the old node must no longer select it. Deleting an edge
172
+ must remove both forward and reverse entries. Missing endpoint behavior is an
173
+ explicit domain decision. Do not scan all edges for every node change unless the
174
+ known collection size makes that tradeoff intentional.
175
+
176
+ Closure indexes are not part of the writer's staged transaction. An uncaught
177
+ processor failure triggers fresh-build recovery, but a processor that catches a
178
+ failure and continues keeps its own mutations. Perform fallible work before
179
+ mutating live indexes, stage temporary index changes and commit them on success,
180
+ or request a complete rebuild.
181
+
182
+ ## Batching, Faults, And Lifetime
183
+
184
+ `store.batch` defers projection settlement and projection listeners only. Document
185
+ commits and document listeners remain synchronous. Reads inside the batch return
186
+ the last publication; settlement after the outer batch uses final source state.
187
+ There is no cross-document rollback.
188
+
189
+ Processor or writer validation failure publishes no partial output. The runtime
190
+ may retry with a fresh build; persistent failure faults that projection and blocks
191
+ its descendants while independent branches continue. Reads then throw
192
+ `ProjectionError`. A later source update or `store.rebuild(projection)` can recover.
193
+ `onError` also receives source, blocked, and listener failures. Listener failures
194
+ never undo published projection state or accepted document commits.
195
+
196
+ `store.release(projection)` releases an independently releasable materialization;
197
+ do not release a node still used by a materialized downstream projection.
198
+ `store.dispose()` invalidates the complete graph and releases its subscriptions.
199
+
200
+ ## Review Checklist
201
+
202
+ - All result-changing sources are declared.
203
+ - Candidate selection covers add, update, remove, reconnect, and missing reference.
204
+ - Batch logic reads final state instead of replaying imagined intermediate states.
205
+ - Reset and explicit rebuild reconstruct complete output, order, and indexes.
206
+ - Build never depends on prior output or a previous processor closure.
207
+ - `writer.order` is complete and valid whenever used.
208
+ - Equal values preserve references; unrelated changes produce no output revision.
209
+ - A thrown update cannot silently corrupt closure indexes.
210
+ - Tests cover unrelated commits, dynamic dependencies, stable references, reset,
211
+ batch behavior, recovery, and disposal.
@@ -0,0 +1,195 @@
1
+ # Doxum Projection 参考
2
+
3
+ 设计、实现或审查 projection 时使用本文。[公开完整契约](../../../docs/projections.md)
4
+ 提供完整 join 示例;正常应用开发不应再要求阅读运行时源码。
5
+
6
+ ## 选择定义形式
7
+
8
+ | 依赖关系 | API |
9
+ | ------------------------------ | ------------------------------------------------- |
10
+ | 整个文档作为 source | `project(document)` |
11
+ | 只关注指定文档路径 | `project(document, [pick, ...])` |
12
+ | 一个文档集合 source | `project(document, pick)` |
13
+ | 一个 source 键映射到同名输出键 | `project(collection, mapper)` |
14
+ | 当前 source 值纯计算出一个值 | `project(sources, compute)` |
15
+ | 有状态增量 value | `project({ kind: 'value', sources, build })` |
16
+ | 多 source 或跨键集合计算 | `project({ kind: 'collection', sources, build })` |
17
+ | 应用输入 | `input(initial)` 与 `store.set(input, value)` |
18
+ | 既有 `Readable` | `project(readable)` |
19
+
20
+ 定义是惰性、可复用的描述。`createProjectionStore({ onError })` 拥有一个物化图。
21
+ 同一定义在不同 store 内分别拥有发布值、revision、input、订阅、processor 实例和闭包索引。
22
+
23
+ 普通 mapper 的契约有意限制为:
24
+
25
+ ```text
26
+ source 键 K 变化 -> 输出键 K 可能变化
27
+ ```
28
+
29
+ 它在 reset 时重建并跟随 source 顺序,但不会追踪 mapper 内的读取。不要在 mapper
30
+ 里读取另一个集合并假定它自动成为依赖;应改用 advanced processor,并显式声明所有 source。
31
+
32
+ ## Source Event 契约
33
+
34
+ advanced processor 接收 event,而不只是当前值:
35
+
36
+ ```ts
37
+ type ValueEvent<T> = {
38
+ value: T;
39
+ previous: T;
40
+ changed: boolean;
41
+ revision: number;
42
+ reset: boolean;
43
+ };
44
+
45
+ type CollectionEvent<K extends string, V> = {
46
+ get(key: K): V | undefined;
47
+ has(key: K): boolean;
48
+ ids(): readonly K[];
49
+ change: CollectionImpact<K> | undefined;
50
+ revision: number;
51
+ reset: boolean;
52
+ };
53
+
54
+ type DocumentEvent<S> = {
55
+ read: Read<S>;
56
+ revision: number;
57
+ commits: readonly DocumentCommit<S>[];
58
+ reset: boolean;
59
+ };
60
+
61
+ type DocumentCollectionEvent<S, N, K extends string> = {
62
+ read: CollectionAccess<K, N>;
63
+ revision: number;
64
+ commits: readonly DocumentCommit<S>[];
65
+ reset: boolean;
66
+ candidates: { keys: readonly K[]; orderDirty: boolean };
67
+ };
68
+ ```
69
+
70
+ 投影集合的 `change` 可能是 incremental(`added`、`removed`、`updated`、
71
+ `orderChanged`)、reset 或 undefined。文档集合的 candidates 是一个 store batch
72
+ 内所有相关 commit 的并集。后续 commit 即使抵消早先变化,也不会移除候选键。
73
+ 必须用最终 `read` 状态计算输出。需要维护关系索引时,用
74
+ `commit.impact.collection(pick)` 取得每次 commit 的精确变化。
75
+
76
+ batch 中 value event 的 `previous` 是 batch 前的值,`value` 是最终值。
77
+ 单个净零文档事务不会产生 commit 或 candidates。
78
+
79
+ ## Advanced Value Processor
80
+
81
+ ```ts
82
+ const value = project({
83
+ kind: 'value',
84
+ sources,
85
+ build: events => ({
86
+ value: buildValue(events),
87
+ update: events =>
88
+ needsRebuild(events)
89
+ ? { kind: 'rebuild' }
90
+ : changed(events)
91
+ ? { kind: 'changed', value: updateValue(events) }
92
+ : { kind: 'unchanged' },
93
+ }),
94
+ });
95
+ ```
96
+
97
+ 除非保留状态能避免有意义的工作,否则优先使用 `project(sources, compute)`。
98
+
99
+ ## Advanced Collection Processor
100
+
101
+ ```ts
102
+ const result = project({
103
+ kind: 'collection',
104
+ sources,
105
+ name: 'optional diagnostic name',
106
+ isEqual: Object.is,
107
+ build: ({ sources, previous, next, writer }) => {
108
+ // 构建完整输出和 store 局部索引。
109
+ return {
110
+ update: ({ sources, previous, next, writer }) => {
111
+ // 只暂存受影响的键,或返回 { kind: 'rebuild' }。
112
+ },
113
+ };
114
+ },
115
+ });
116
+ ```
117
+
118
+ 首次物化、source reset、显式 `store.rebuild`、update 请求 rebuild 以及 fault
119
+ 恢复时会调用 `build`。build 从已清空的输出开始,必须写出完整结果。
120
+ 其闭包状态只属于一个 store,并且必须能完全由 sources 重建。
121
+
122
+ 任一已声明 source 参与结算时调用 `update`。返回 `{ kind: 'rebuild' }` 会丢弃
123
+ 本次 update 暂存的写入,并执行全新 build。所有回调同步执行;Promise 和 thenable
124
+ 会被拒绝。processing 或 notification 中的读取和写入不得重入 source 或 document。
125
+
126
+ `previous`、`next`、source reader 和 `writer` 都只在当前同步回调内有效:
127
+
128
+ - `previous` 只看到已发布输出。
129
+ - `next` 立即看到当前暂存的写入。
130
+ - `writer.set(key, value)` 暂存存在值。
131
+ - `writer.remove(key)` 暂存缺失;删除不存在的键是 no-op。
132
+ - 同一键多次操作以最后一次为准。
133
+ - `writer.order(ids)` 提供完整 next 顺序,每个输出键必须恰好出现一次。
134
+ - `writer.replace(entries)` 清空并按 entries 顺序替换全部输出,键不能重复。
135
+
136
+ 暂存输出原子发布。相等判断默认使用 `Object.is`,相等的 set 保留旧引用。
137
+ 净零输出不增加 revision,也不通知 listener。
138
+
139
+ ## 跨集合 Join 模式
140
+
141
+ 不要增加通用 join 抽象。缺失引用、基数、重连、删除、顺序和结果键策略都属于领域逻辑。
142
+ 应使用带显式 sources 和索引的 advanced collection 表达 join。
143
+
144
+ 例如 route 由 `edges(from, to)` 和 node 几何信息计算,维护:
145
+
146
+ ```text
147
+ endpoints: edgeId -> [fromNodeId, toNodeId]
148
+ adjacency: nodeId -> Set<edgeId>
149
+ ```
150
+
151
+ build 时扫描一次 edges,填充两个索引并写出全部 routes。edge 变化时:
152
+
153
+ 1. 从每个 commit 的 collection impact 读取精确变化的 edge id。
154
+ 2. 将每个变化 id 从旧 endpoints 中解绑。
155
+ 3. 读取最终 edge 状态;绑定其最终 endpoints,或删除其正向索引。
156
+ 4. 将该 edge id 加入受影响输出集合。
157
+
158
+ node 集合变化时,把 added/updated/removed node id 通过 `adjacency` 映射为相邻
159
+ edge,并只将这些 edge 加入受影响集合。最后读取当前 endpoint 几何信息,对每个
160
+ 受影响 route 执行 set 或 remove。任一 source reset 都应 rebuild。
161
+
162
+ edge 重连后,旧 node 不得再选中它;删除 edge 必须同时移除正向和反向索引。
163
+ endpoint 缺失时的行为由领域显式定义。除非集合规模已知且权衡明确,不要在每次
164
+ node 变化时扫描全部 edges。
165
+
166
+ 闭包索引不属于 writer 的 staged transaction。未捕获的 processor 失败会触发 fresh
167
+ build 恢复,但 processor 如果自行捕获失败后继续执行,其索引修改会保留。先完成可能
168
+ 失败的工作再修改活动索引,或在临时状态上暂存索引变化并在成功后提交,或请求完整 rebuild。
169
+
170
+ ## Batch、Fault 与生命周期
171
+
172
+ `store.batch` 只推迟 projection 结算和 projection listener;文档 commit 和文档
173
+ listener 仍同步执行。batch 内读取返回上次发布值,最外层 batch 结束后根据最终
174
+ source 状态结算。不提供跨文档回滚。
175
+
176
+ processor 或 writer 校验失败不会发布部分输出。runtime 可以用新 build 重试;
177
+ 持续失败会使该 projection fault,并阻塞下游,独立分支仍继续工作。此时读取抛
178
+ `ProjectionError`。后续 source 更新或 `store.rebuild(projection)` 可以恢复。
179
+ `onError` 还接收 source、blocked 和 listener 失败。listener 失败不会撤销已发布
180
+ projection 状态或已接受的文档 commit。
181
+
182
+ `store.release(projection)` 释放可独立释放的物化结果;仍被已物化下游使用的节点
183
+ 不能释放。`store.dispose()` 使整个图失效并解除其全部订阅。
184
+
185
+ ## 审查清单
186
+
187
+ - 所有能改变结果的 source 都已声明。
188
+ - 候选选择覆盖 add、update、remove、reconnect 和缺失引用。
189
+ - batch 逻辑读取最终状态,而不是模拟中间状态。
190
+ - reset 和显式 rebuild 能重建完整输出、顺序和索引。
191
+ - build 不依赖旧输出或旧 processor 闭包。
192
+ - 使用 `writer.order` 时提供完整且有效的顺序。
193
+ - 相等结果保留引用;无关变化不产生输出 revision。
194
+ - update 抛错不会悄悄破坏闭包索引。
195
+ - 测试覆盖无关 commit、动态依赖、稳定引用、reset、batch、恢复和 disposal。