doxum 0.1.12 → 0.1.13
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 +44 -39
- package/dist/advanced.cjs +116 -0
- package/dist/advanced.cjs.map +1 -0
- package/dist/advanced.d.cts +42 -0
- package/dist/advanced.d.ts +42 -0
- package/dist/advanced.js +115 -0
- package/dist/advanced.js.map +1 -0
- package/dist/{contract-CeAnEPBA.d.ts → contract-BT53Gg94.d.ts} +5 -4
- package/dist/{contract-DtGVSXSK.d.cts → contract-XmKnroJq.d.cts} +5 -4
- package/dist/definition-177L4Amk.cjs +898 -0
- package/dist/definition-177L4Amk.cjs.map +1 -0
- package/dist/definition-BlMeHwdz.d.ts +98 -0
- package/dist/definition-DXrKMVs7.js +785 -0
- package/dist/definition-DXrKMVs7.js.map +1 -0
- package/dist/definition-kxnWUvX9.d.cts +98 -0
- package/dist/{driver-xOIkwrB8.cjs → driver-BCyfqxsl.cjs} +1 -1
- package/dist/{driver-xOIkwrB8.cjs.map → driver-BCyfqxsl.cjs.map} +1 -1
- package/dist/{driver-BlR81Dqg.js → driver-CSnhMjbP.js} +1 -1
- package/dist/{driver-BlR81Dqg.js.map → driver-CSnhMjbP.js.map} +1 -1
- package/dist/index.cjs +31 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.js +7 -6
- package/dist/index.js.map +1 -1
- package/dist/{integration-CWjttg7H.js → integration-Bco8ZB16.js} +7 -3
- package/dist/integration-Bco8ZB16.js.map +1 -0
- package/dist/integration-CEUn96tx.cjs +52 -0
- package/dist/integration-CEUn96tx.cjs.map +1 -0
- package/dist/integration.cjs +7 -3
- package/dist/integration.d.cts +7 -3
- package/dist/integration.d.ts +7 -3
- package/dist/integration.js +3 -3
- package/dist/local-sync.cjs +1 -1
- package/dist/local-sync.d.cts +1 -1
- package/dist/local-sync.d.ts +1 -1
- package/dist/local-sync.js +1 -1
- package/dist/react.cjs +63 -36
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +8 -7
- package/dist/react.d.ts +8 -7
- package/dist/react.js +63 -35
- package/dist/react.js.map +1 -1
- package/dist/store-CDeqZEE3.d.cts +28 -0
- package/dist/{store-7XBEcR8s.js → store-CtgvCVFg.js} +585 -922
- package/dist/store-CtgvCVFg.js.map +1 -0
- package/dist/{store-DBv58dns.cjs → store-DcDpDgjk.cjs} +595 -992
- package/dist/store-DcDpDgjk.cjs.map +1 -0
- package/dist/store-P5dJ7TVe.d.ts +28 -0
- package/package.json +6 -1
- package/skills/doxum-runtime/SKILL.md +5 -7
- package/skills/doxum-runtime/references/guide.en.md +12 -16
- package/skills/doxum-runtime/references/guide.zh-CN.md +9 -14
- package/skills/doxum-runtime/references/patterns.en.md +12 -20
- package/skills/doxum-runtime/references/patterns.zh-CN.md +12 -18
- package/skills/doxum-runtime/references/projections.en.md +56 -199
- package/skills/doxum-runtime/references/projections.zh-CN.md +50 -180
- package/dist/integration-BShL68Nc.cjs +0 -30
- package/dist/integration-BShL68Nc.cjs.map +0 -1
- package/dist/integration-CWjttg7H.js.map +0 -1
- package/dist/store-2i8pYXqa.d.ts +0 -263
- package/dist/store-7XBEcR8s.js.map +0 -1
- package/dist/store-DBv58dns.cjs.map +0 -1
- package/dist/store-Ks80xWS_.d.cts +0 -263
|
@@ -84,24 +84,18 @@ document.apply(
|
|
|
84
84
|
## 投影与 React
|
|
85
85
|
|
|
86
86
|
```ts
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
(_id, task) => task.title
|
|
91
|
-
);
|
|
92
|
-
const total = project({ titles }, ({ titles }) => titles.ids().length);
|
|
87
|
+
const tasks = observe(document, path => path.tasks);
|
|
88
|
+
const titles = derive([tasks], tasks => new Map([...tasks].map(([id, task]) => [id, task.title])));
|
|
89
|
+
const total = derive([titles], titles => titles.size);
|
|
93
90
|
const zoom = input(1);
|
|
94
|
-
const scaled =
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
const title =
|
|
91
|
+
const scaled = derive([total, zoom], (total, zoom) => total * zoom);
|
|
92
|
+
const runtime = createProjectionRuntime({ onError: console.error });
|
|
93
|
+
runtime.get(scaled);
|
|
94
|
+
const title = runtime.get(tasks).get(taskId);
|
|
98
95
|
```
|
|
99
96
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
`
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
React 追踪实际读取,但 processor 依赖仍显式声明。只有 source 键影响同名输出键时
|
|
106
|
-
才使用 mapper。跨集合 join 应声明全部 source,并按
|
|
107
|
-
[Projection 参考](projections.zh-CN.md)维护领域反向依赖索引。
|
|
97
|
+
React 使用 Runtime 上下文中的 `useProjection` 读取 Projection,单键读取写成
|
|
98
|
+
`useProjection(projection, selector)`;`useInput` 返回值和 setter。高级集合 processor
|
|
99
|
+
从 `doxum/advanced` 引入,在同步 callback 中使用
|
|
100
|
+
`output.set/remove/order/replace` 与 previous/next。Processor 依赖仍显式声明,
|
|
101
|
+
React selector 追踪只属于消费端。
|
|
@@ -1,220 +1,77 @@
|
|
|
1
|
-
#
|
|
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.
|
|
1
|
+
# Projection Reference
|
|
34
2
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
Advanced processors receive events, not just current values:
|
|
3
|
+
Projection definitions are lazy and reusable. The public core consists of
|
|
4
|
+
`Projection<T>`, `ProjectionRuntime`, `input`, `observe`, and tuple `derive`.
|
|
38
5
|
|
|
39
6
|
```ts
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
};
|
|
7
|
+
const tasks = observe(document, path => path.tasks);
|
|
8
|
+
const filter = input<'all' | 'open'>('all');
|
|
9
|
+
const visible = derive([tasks, filter], (tasks, filter) =>
|
|
10
|
+
filter === 'all' ? tasks : new Map([...tasks].filter(([, task]) => !task.done))
|
|
11
|
+
);
|
|
12
|
+
const runtime = createProjectionRuntime({ onError: report });
|
|
13
|
+
runtime.get(visible);
|
|
71
14
|
```
|
|
72
15
|
|
|
73
|
-
|
|
74
|
-
`
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
16
|
+
`input(initial, equality?)` is Runtime-local and can be written only with
|
|
17
|
+
`runtime.set(input, value)`. `observe(document, selector)` compiles a document
|
|
18
|
+
boundary lazily. Collection paths publish immutable map-like snapshots; the
|
|
19
|
+
whole-document form publishes a snapshot of the root. Existing Doxum Readables
|
|
20
|
+
can also be observed at this boundary.
|
|
78
21
|
|
|
79
|
-
|
|
80
|
-
|
|
22
|
+
`derive(dependencies, compute, equality?)` takes a tuple. Dependencies are
|
|
23
|
+
explicit and fixed when the definition is created. Processors must not discover
|
|
24
|
+
graph dependencies by reading other projections.
|
|
81
25
|
|
|
82
|
-
|
|
26
|
+
The Runtime API is intentionally small:
|
|
83
27
|
|
|
84
28
|
```ts
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
update: events =>
|
|
91
|
-
needsRebuild(events)
|
|
92
|
-
? { kind: 'rebuild' }
|
|
93
|
-
: changed(events)
|
|
94
|
-
? { kind: 'changed', value: updateValue(events) }
|
|
95
|
-
: { kind: 'unchanged' },
|
|
96
|
-
}),
|
|
97
|
-
});
|
|
29
|
+
runtime.get(projection);
|
|
30
|
+
runtime.subscribe(projection, listener);
|
|
31
|
+
runtime.set(input, value);
|
|
32
|
+
runtime.batch({ cause }, run);
|
|
33
|
+
runtime.dispose();
|
|
98
34
|
```
|
|
99
35
|
|
|
100
|
-
|
|
36
|
+
There is no public revision, item handle, rebuild or release operation. Runtime
|
|
37
|
+
materialization, keyed storage, recovery and disposal are one owner.
|
|
101
38
|
|
|
102
|
-
##
|
|
39
|
+
## Incremental processors
|
|
40
|
+
|
|
41
|
+
Import retained-state processors from `doxum/advanced`:
|
|
103
42
|
|
|
104
43
|
```ts
|
|
105
|
-
const
|
|
106
|
-
|
|
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
|
-
},
|
|
44
|
+
const total = incremental([tasks], ({ sources, previous, state }) => {
|
|
45
|
+
state.calls = Number(state.calls ?? 0) + 1;
|
|
46
|
+
return sources[0].size + Number(state.calls) + (previous ?? 0);
|
|
118
47
|
});
|
|
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
48
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
|
49
|
+
const doubled = incremental.collection([tasks], ({ sources, output }) => {
|
|
50
|
+
for (const [id, task] of sources[0]) output.set(id, task.value * 2);
|
|
51
|
+
});
|
|
52
|
+
```
|
|
147
53
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
54
|
+
Value contexts expose `sources`, `previous`, `reset`, `change`, `cause` and a
|
|
55
|
+
retained `state` object. `incremental.collection(...)` uses a separate
|
|
56
|
+
collection processor protocol and additionally exposes borrowed
|
|
57
|
+
`previous`/`next` keyed reads and a callback-local `output` draft. Draft methods are
|
|
58
|
+
`set`, `remove`, `order`, and `replace`; the Runtime validates and seals them after
|
|
59
|
+
the synchronous callback, computes keyed transitions and publishes one immutable
|
|
60
|
+
map-like value. Reset or fault recovery is internal.
|
|
151
61
|
|
|
152
|
-
|
|
62
|
+
## React selector tracking
|
|
153
63
|
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
|
|
64
|
+
```tsx
|
|
65
|
+
const task = useProjection(visible, tasks => tasks.get(taskId));
|
|
66
|
+
const [mode, setMode] = useInput(filter);
|
|
157
67
|
```
|
|
158
68
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
For one materialized collection entry, `store.item(collection, key)` returns a
|
|
201
|
-
stable `Readable<V | undefined>`. It follows the key through add, update, remove,
|
|
202
|
-
and recreation; other keys and order-only changes do not notify it. React uses
|
|
203
|
-
`useProjectionItem(collection, key, store?)`. Key or store changes replace the
|
|
204
|
-
subscription. The hook internally observes membership even when both absent and
|
|
205
|
-
present values read as `undefined`. Raw document collection sources are not valid
|
|
206
|
-
inputs; use `useDocumentSelector` for canonical document items.
|
|
207
|
-
|
|
208
|
-
## Review Checklist
|
|
209
|
-
|
|
210
|
-
- All result-changing sources are declared.
|
|
211
|
-
- Candidate selection covers add, update, remove, reconnect, and missing reference.
|
|
212
|
-
- Batch logic reads final state instead of replaying imagined intermediate states.
|
|
213
|
-
- Reset and explicit rebuild reconstruct complete output, order, and indexes.
|
|
214
|
-
- Build never depends on prior output or a previous processor closure.
|
|
215
|
-
- `writer.order` is complete and valid whenever used.
|
|
216
|
-
- Equal values preserve references; unrelated changes produce no output revision.
|
|
217
|
-
- Item consumers use keyed readables/hooks instead of whole-collection value projections.
|
|
218
|
-
- A thrown update cannot silently corrupt closure indexes.
|
|
219
|
-
- Tests cover unrelated commits, dynamic dependencies, stable references, reset,
|
|
220
|
-
batch behavior, recovery, and disposal.
|
|
69
|
+
Collection `get`/`has` records one key, `keys` records key/order structure, and
|
|
70
|
+
`values`/iteration records the whole collection. Unrelated key changes do not run
|
|
71
|
+
the selector. After a related update, the selector's equality (default
|
|
72
|
+
`Object.is`) decides whether React re-renders. This tracking exists only at the
|
|
73
|
+
consumer boundary; projection dependencies remain explicit.
|
|
74
|
+
|
|
75
|
+
Processors settle before listeners. Writes are forbidden during notification.
|
|
76
|
+
Listener errors do not roll back an accepted document commit; processor errors use
|
|
77
|
+
the Runtime error callback and internal recovery.
|
|
@@ -1,203 +1,73 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Projection 参考
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Projection 定义是惰性、可复用的。默认入口只保留 `Projection<T>`、
|
|
4
|
+
`ProjectionRuntime`、`input`、`observe` 和 tuple 形式的 `derive`。
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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 可能变化
|
|
6
|
+
```ts
|
|
7
|
+
const tasks = observe(document, path => path.tasks);
|
|
8
|
+
const filter = input<'all' | 'open'>('all');
|
|
9
|
+
const visible = derive([tasks, filter], (tasks, filter) =>
|
|
10
|
+
filter === 'all' ? tasks : new Map([...tasks].filter(([, task]) => !task.done))
|
|
11
|
+
);
|
|
12
|
+
const runtime = createProjectionRuntime({ onError: report });
|
|
13
|
+
runtime.get(visible);
|
|
27
14
|
```
|
|
28
15
|
|
|
29
|
-
|
|
30
|
-
|
|
16
|
+
`input(initial, equality?)` 属于每个 Runtime,只能通过
|
|
17
|
+
`runtime.set(input, value)` 写入。`observe(document, selector)` 延迟编译文档
|
|
18
|
+
边界;集合路径发布只读 map-like 快照,省略 selector 时发布整个文档快照。
|
|
19
|
+
现有 Doxum `Readable` 也可以在这个边界接入。
|
|
31
20
|
|
|
32
|
-
|
|
21
|
+
`derive(dependencies, compute, equality?)` 使用 tuple,依赖在定义创建时固定。
|
|
22
|
+
Processor 不能通过读取另一个 Projection 隐式建立图依赖。
|
|
33
23
|
|
|
34
|
-
|
|
24
|
+
Runtime 的公开脊柱只有:
|
|
35
25
|
|
|
36
26
|
```ts
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
};
|
|
27
|
+
runtime.get(projection);
|
|
28
|
+
runtime.subscribe(projection, listener);
|
|
29
|
+
runtime.set(input, value);
|
|
30
|
+
runtime.batch({ cause }, run);
|
|
31
|
+
runtime.dispose();
|
|
68
32
|
```
|
|
69
33
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
内所有相关 commit 的并集。后续 commit 即使抵消早先变化,也不会移除候选键。
|
|
73
|
-
必须用最终 `read` 状态计算输出。需要维护关系索引时,用
|
|
74
|
-
`commit.impact.collection(pick)` 取得每次 commit 的精确变化。
|
|
34
|
+
revision、item handle、rebuild、release 都是 Runtime 内部事实,不是应用层操作。
|
|
35
|
+
Materialization、keyed storage、故障恢复和释放只有一个 owner。
|
|
75
36
|
|
|
76
|
-
|
|
77
|
-
单个净零文档事务不会产生 commit 或 candidates。
|
|
37
|
+
## 增量 Processor
|
|
78
38
|
|
|
79
|
-
|
|
39
|
+
保留状态和 keyed patch 从 `doxum/advanced` 引入:
|
|
80
40
|
|
|
81
41
|
```ts
|
|
82
|
-
const
|
|
83
|
-
|
|
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
|
-
}),
|
|
42
|
+
const total = incremental([tasks], ({ sources, previous, state }) => {
|
|
43
|
+
state.calls = Number(state.calls ?? 0) + 1;
|
|
44
|
+
return sources[0].size + Number(state.calls) + (previous ?? 0);
|
|
94
45
|
});
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
除非保留状态能避免有意义的工作,否则优先使用 `project(sources, compute)`。
|
|
98
|
-
|
|
99
|
-
## Advanced Collection Processor
|
|
100
46
|
|
|
101
|
-
|
|
102
|
-
const
|
|
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
|
-
},
|
|
47
|
+
const doubled = incremental.collection([tasks], ({ sources, output }) => {
|
|
48
|
+
for (const [id, task] of sources[0]) output.set(id, task.value * 2);
|
|
115
49
|
});
|
|
116
50
|
```
|
|
117
51
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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 顺序替换全部输出,键不能重复。
|
|
52
|
+
Value context 包含 `sources`、`previous`、`reset`、`change`、`cause` 和持久
|
|
53
|
+
`state`。`incremental.collection(...)` 使用独立的 collection processor 协议,
|
|
54
|
+
另外提供借用的 `previous`/`next` keyed read,以及
|
|
55
|
+
只在同步 callback 内有效的 `output` draft。Draft 只有 `set`、`remove`、`order`、
|
|
56
|
+
`replace`;Runtime 在 callback 返回后校验并 seal,计算 keyed transitions,发布
|
|
57
|
+
一个不可变 map-like 值。reset 或故障恢复由 Runtime 内部完成。
|
|
135
58
|
|
|
136
|
-
|
|
137
|
-
净零输出不增加 revision,也不通知 listener。
|
|
59
|
+
## React selector 追踪
|
|
138
60
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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>
|
|
61
|
+
```tsx
|
|
62
|
+
const task = useProjection(visible, tasks => tasks.get(taskId));
|
|
63
|
+
const [mode, setMode] = useInput(filter);
|
|
149
64
|
```
|
|
150
65
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
观察一个物化集合项时,`store.item(collection, key)` 返回稳定的
|
|
186
|
-
`Readable<V | undefined>`。它贯穿 add、update、remove 和 recreate;其他键和纯顺序
|
|
187
|
-
变化不会通知它。React 使用 `useProjectionItem(collection, key, store?)`,key 或 store
|
|
188
|
-
变化时替换订阅。hook 内部会观察 membership,即使 absent 与 present 值都读作
|
|
189
|
-
`undefined`。原始文档集合 source 不能作为输入;canonical 文档项使用
|
|
190
|
-
`useDocumentSelector`。
|
|
191
|
-
|
|
192
|
-
## 审查清单
|
|
193
|
-
|
|
194
|
-
- 所有能改变结果的 source 都已声明。
|
|
195
|
-
- 候选选择覆盖 add、update、remove、reconnect 和缺失引用。
|
|
196
|
-
- batch 逻辑读取最终状态,而不是模拟中间状态。
|
|
197
|
-
- reset 和显式 rebuild 能重建完整输出、顺序和索引。
|
|
198
|
-
- build 不依赖旧输出或旧 processor 闭包。
|
|
199
|
-
- 使用 `writer.order` 时提供完整且有效的顺序。
|
|
200
|
-
- 相等结果保留引用;无关变化不产生输出 revision。
|
|
201
|
-
- 单项消费者使用 keyed readable/hook,而不是整个集合的 value projection。
|
|
202
|
-
- update 抛错不会悄悄破坏闭包索引。
|
|
203
|
-
- 测试覆盖无关 commit、动态依赖、稳定引用、reset、batch、恢复和 disposal。
|
|
66
|
+
`get`/`has` 记录单 key,`keys` 记录 key/order 结构,`values` 或迭代记录整个
|
|
67
|
+
集合。无关 key 的更新不会执行 selector;相关更新后才执行 selector,并由
|
|
68
|
+
`equality`(默认 `Object.is`)决定是否重渲染。这是消费端优化,不会反向构建
|
|
69
|
+
Projection processor 依赖。
|
|
70
|
+
|
|
71
|
+
Processor 先于 listener settle。通知期间禁止写入。listener 错误不会回滚已经
|
|
72
|
+
接受的文档 commit;processor 错误交给 Runtime error callback,并由 Runtime
|
|
73
|
+
内部恢复。
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
const require_store = require("./store-DBv58dns.cjs");
|
|
2
|
-
//#region core/src/access/dependency.ts
|
|
3
|
-
const createDependencyTracker = () => {
|
|
4
|
-
const targets = [];
|
|
5
|
-
return {
|
|
6
|
-
record: (value) => {
|
|
7
|
-
if (!targets.some((entry) => require_store.same(entry, value))) targets.push(value);
|
|
8
|
-
},
|
|
9
|
-
snapshot: () => Object.freeze(targets.slice())
|
|
10
|
-
};
|
|
11
|
-
};
|
|
12
|
-
//#endregion
|
|
13
|
-
//#region core/src/integration.ts
|
|
14
|
-
const track = (runtime, selector) => {
|
|
15
|
-
const dependencies = createDependencyTracker();
|
|
16
|
-
const value = require_store.readWith(runtime, selector, dependencies);
|
|
17
|
-
return Object.freeze({
|
|
18
|
-
value,
|
|
19
|
-
targets: dependencies.snapshot()
|
|
20
|
-
});
|
|
21
|
-
};
|
|
22
|
-
//#endregion
|
|
23
|
-
Object.defineProperty(exports, "track", {
|
|
24
|
-
enumerable: true,
|
|
25
|
-
get: function() {
|
|
26
|
-
return track;
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
//# sourceMappingURL=integration-BShL68Nc.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"integration-BShL68Nc.cjs","names":["readWith"],"sources":["../core/src/access/dependency.ts","../core/src/integration.ts"],"sourcesContent":["import type { ImpactTarget } from '../schema';\nimport * as impactTarget from '../impact-target';\n\nexport type DependencyTracker = {\n readonly record: (target: ImpactTarget<unknown>) => void;\n readonly snapshot: () => readonly ImpactTarget<unknown>[];\n};\n\nexport const createDependencyTracker = (): DependencyTracker => {\n const targets: ImpactTarget<unknown>[] = [];\n return {\n record: value => {\n if (!targets.some(entry => impactTarget.same(entry, value))) targets.push(value);\n },\n snapshot: () => Object.freeze(targets.slice()),\n };\n};\n","import type { ObjectNode, ImpactTarget } from './schema';\nimport type { Read } from './access/scope';\nimport type { DocumentReadable } from './runtime/contract';\nimport { createDependencyTracker } from './access/dependency';\nimport { readWith } from './runtime/access';\nexport { projectionStoreDebug } from './projection/store';\nexport type { AddressRef } from './address';\nexport { contains, debugKey, overlaps, read as readAddress, resolveAddress } from './address';\nexport { subscribeDependencies } from './runtime/notification';\nexport { same as sameTarget } from './impact-target';\nexport type { ImpactTarget } from './schema';\n\nexport type TrackedSelection<TValue> = {\n readonly value: TValue;\n readonly targets: readonly ImpactTarget<unknown>[];\n};\n\n// Framework adapters receive one immutable result instead of coordinating a\n// mutable collector with the reader's scoped lifetime themselves.\nexport const track = <TSchema extends ObjectNode, TValue>(\n runtime: DocumentReadable<TSchema>,\n selector: (read: Read<TSchema>) => TValue\n): TrackedSelection<TValue> => {\n const dependencies = createDependencyTracker();\n const value = readWith(runtime, selector, dependencies);\n return Object.freeze({ value, targets: dependencies.snapshot() });\n};\n"],"mappings":";;AAQA,MAAa,gCAAmD;CAC9D,MAAM,UAAmC,EAAE;AAC3C,QAAO;EACL,SAAQ,UAAS;AACf,OAAI,CAAC,QAAQ,MAAK,UAAA,cAAA,KAA2B,OAAO,MAAM,CAAC,CAAE,SAAQ,KAAK,MAAM;;EAElF,gBAAgB,OAAO,OAAO,QAAQ,OAAO,CAAC;EAC/C;;;;ACIH,MAAa,SACX,SACA,aAC6B;CAC7B,MAAM,eAAe,yBAAyB;CAC9C,MAAM,QAAQA,cAAAA,SAAS,SAAS,UAAU,aAAa;AACvD,QAAO,OAAO,OAAO;EAAE;EAAO,SAAS,aAAa,UAAU;EAAE,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"integration-CWjttg7H.js","names":["impactTarget.same"],"sources":["../core/src/access/dependency.ts","../core/src/integration.ts"],"sourcesContent":["import type { ImpactTarget } from '../schema';\nimport * as impactTarget from '../impact-target';\n\nexport type DependencyTracker = {\n readonly record: (target: ImpactTarget<unknown>) => void;\n readonly snapshot: () => readonly ImpactTarget<unknown>[];\n};\n\nexport const createDependencyTracker = (): DependencyTracker => {\n const targets: ImpactTarget<unknown>[] = [];\n return {\n record: value => {\n if (!targets.some(entry => impactTarget.same(entry, value))) targets.push(value);\n },\n snapshot: () => Object.freeze(targets.slice()),\n };\n};\n","import type { ObjectNode, ImpactTarget } from './schema';\nimport type { Read } from './access/scope';\nimport type { DocumentReadable } from './runtime/contract';\nimport { createDependencyTracker } from './access/dependency';\nimport { readWith } from './runtime/access';\nexport { projectionStoreDebug } from './projection/store';\nexport type { AddressRef } from './address';\nexport { contains, debugKey, overlaps, read as readAddress, resolveAddress } from './address';\nexport { subscribeDependencies } from './runtime/notification';\nexport { same as sameTarget } from './impact-target';\nexport type { ImpactTarget } from './schema';\n\nexport type TrackedSelection<TValue> = {\n readonly value: TValue;\n readonly targets: readonly ImpactTarget<unknown>[];\n};\n\n// Framework adapters receive one immutable result instead of coordinating a\n// mutable collector with the reader's scoped lifetime themselves.\nexport const track = <TSchema extends ObjectNode, TValue>(\n runtime: DocumentReadable<TSchema>,\n selector: (read: Read<TSchema>) => TValue\n): TrackedSelection<TValue> => {\n const dependencies = createDependencyTracker();\n const value = readWith(runtime, selector, dependencies);\n return Object.freeze({ value, targets: dependencies.snapshot() });\n};\n"],"mappings":";;AAQA,MAAa,gCAAmD;CAC9D,MAAM,UAAmC,EAAE;AAC3C,QAAO;EACL,SAAQ,UAAS;AACf,OAAI,CAAC,QAAQ,MAAK,UAASA,KAAkB,OAAO,MAAM,CAAC,CAAE,SAAQ,KAAK,MAAM;;EAElF,gBAAgB,OAAO,OAAO,QAAQ,OAAO,CAAC;EAC/C;;;;ACIH,MAAa,SACX,SACA,aAC6B;CAC7B,MAAM,eAAe,yBAAyB;CAC9C,MAAM,QAAQ,SAAS,SAAS,UAAU,aAAa;AACvD,QAAO,OAAO,OAAO;EAAE;EAAO,SAAS,aAAa,UAAU;EAAE,CAAC"}
|