doxum 0.1.23 → 0.1.26
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 +208 -102
- package/dist/advanced.cjs +237 -2
- package/dist/advanced.cjs.map +1 -0
- package/dist/advanced.d.cts +90 -2
- package/dist/advanced.d.ts +90 -2
- package/dist/advanced.js +236 -1
- package/dist/advanced.js.map +1 -0
- package/dist/{contract-ULPQmiEs.d.ts → contract-CNR6BS75.d.cts} +216 -30
- package/dist/{contract-SYJFP0lM.d.cts → contract-COgxO5sv.d.ts} +216 -30
- package/dist/definition-CDOuvb5C.d.cts +111 -0
- package/dist/definition-xgk3YNBp.d.ts +111 -0
- package/dist/{driver-yFfvDMX5.js → driver-B5vdPohL.js} +59 -3
- package/dist/driver-B5vdPohL.js.map +1 -0
- package/dist/{driver-BD3DQOW6.cjs → driver-BL91gn7_.cjs} +107 -9
- package/dist/driver-BL91gn7_.cjs.map +1 -0
- package/dist/index.cjs +1637 -703
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +65 -33
- package/dist/index.d.ts +65 -33
- package/dist/index.js +1432 -497
- package/dist/index.js.map +1 -1
- package/dist/local-sync.cjs +69 -103
- package/dist/local-sync.cjs.map +1 -1
- package/dist/local-sync.d.cts +14 -26
- package/dist/local-sync.d.ts +14 -26
- package/dist/local-sync.js +68 -96
- package/dist/local-sync.js.map +1 -1
- package/dist/react.cjs +4 -5
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +6 -8
- package/dist/react.d.ts +6 -8
- package/dist/react.js +4 -5
- package/dist/react.js.map +1 -1
- package/dist/record-B2yRhYsc.js +28 -0
- package/dist/record-B2yRhYsc.js.map +1 -0
- package/dist/record-ChYUi8PP.cjs +45 -0
- package/dist/record-ChYUi8PP.cjs.map +1 -0
- package/dist/scheduler--137L3j-.js +500 -0
- package/dist/scheduler--137L3j-.js.map +1 -0
- package/dist/scheduler-D4VthaaK.cjs +589 -0
- package/dist/scheduler-D4VthaaK.cjs.map +1 -0
- package/package.json +6 -3
- package/skills/doxum-runtime/SKILL.md +32 -7
- package/skills/doxum-runtime/agents/openai.yaml +1 -1
- package/skills/doxum-runtime/references/api.en.md +249 -0
- package/skills/doxum-runtime/references/api.zh-CN.md +242 -0
- package/skills/doxum-runtime/references/guide.en.md +18 -8
- package/skills/doxum-runtime/references/guide.zh-CN.md +14 -6
- package/skills/doxum-runtime/references/invariants.en.md +29 -5
- package/skills/doxum-runtime/references/invariants.zh-CN.md +24 -6
- package/skills/doxum-runtime/references/patterns.en.md +78 -18
- package/skills/doxum-runtime/references/patterns.zh-CN.md +76 -18
- package/skills/doxum-runtime/references/projections.en.md +209 -119
- package/skills/doxum-runtime/references/projections.zh-CN.md +200 -101
- package/dist/advanced-CLJCWkHa.d.cts +0 -176
- package/dist/advanced-CRNHfJBu.js +0 -1224
- package/dist/advanced-CRNHfJBu.js.map +0 -1
- package/dist/advanced-Cle5g3B4.cjs +0 -1517
- package/dist/advanced-Cle5g3B4.cjs.map +0 -1
- package/dist/advanced-d6J61er9.d.ts +0 -176
- package/dist/context-BcLKGfq8.cjs +0 -149
- package/dist/context-BcLKGfq8.cjs.map +0 -1
- package/dist/context-DFEaDUv5.js +0 -84
- package/dist/context-DFEaDUv5.js.map +0 -1
- package/dist/driver-BD3DQOW6.cjs.map +0 -1
- package/dist/driver-yFfvDMX5.js.map +0 -1
|
@@ -1,150 +1,240 @@
|
|
|
1
1
|
# Projection Reference
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
For exact signatures, use the [API reference](api.en.md). This document explains
|
|
4
|
+
ownership, invalidation and recovery.
|
|
5
|
+
|
|
6
|
+
## Ownership and basic declarations
|
|
7
|
+
|
|
8
|
+
Projection definitions are lazy. Root definitions are reusable across runtimes;
|
|
9
|
+
scope definitions belong to one `ProjectionScope`. A public `Projection<T>` contains
|
|
10
|
+
no materialized value, state, subscription or disposal state.
|
|
11
|
+
`KeyedProjection<K,V>` is the corresponding public keyed handle and is safe to export
|
|
12
|
+
from another package's declarations.
|
|
6
13
|
|
|
7
14
|
```ts
|
|
8
15
|
const tasks = observe(document, path => path.tasks);
|
|
9
16
|
const filter = input<'all' | 'open'>('all');
|
|
10
|
-
const visible = derive(
|
|
11
|
-
filter === 'all' ? tasks :
|
|
17
|
+
const visible = derive({ tasks, filter }, ({ tasks, filter }) =>
|
|
18
|
+
filter === 'all' ? tasks : filterOpen(tasks)
|
|
12
19
|
);
|
|
20
|
+
|
|
13
21
|
const runtime = createProjectionRuntime({ onError: report });
|
|
14
|
-
runtime.
|
|
22
|
+
runtime.read(visible);
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`ProjectionRuntime` is the single owner of materialization, retained state, source
|
|
26
|
+
attachments, settlement, publication and recovery. Its stable verbs are `read`,
|
|
27
|
+
`select`, overloaded `update`, `batch`, `scope`, and `dispose`.
|
|
28
|
+
|
|
29
|
+
`input(initial, equality?)` is scalar Runtime-local state. `input.collection(initial?,
|
|
30
|
+
equality?)` is keyed Runtime-local application/UI state. Both are outside canonical
|
|
31
|
+
document history/persistence.
|
|
32
|
+
|
|
33
|
+
## Source boundaries
|
|
34
|
+
|
|
35
|
+
`observe` is the one source declaration entry point. It adapts:
|
|
36
|
+
|
|
37
|
+
- a whole `ReadonlyDocument` or a schema-path selection;
|
|
38
|
+
- a Doxum `Readable<T>`;
|
|
39
|
+
- exported external value/collection source contracts.
|
|
40
|
+
|
|
41
|
+
Document `map`, `table`, and `list(field, { keyOf })` selections are keyed projections.
|
|
42
|
+
List identity comes from schema `keyOf`. Atomic array-valued fields remain scalar.
|
|
43
|
+
|
|
44
|
+
Trees reuse the same source model:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
const rootId = observe(document, path => path.outline.rootId);
|
|
48
|
+
const nodes = observe(document, path => path.outline.nodes);
|
|
49
|
+
const node = observe(document, path => path.outline.nodes.item(nodeId));
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`rootId` is scalar, `nodes` is keyed, and `item(id)` is one node value. A document
|
|
53
|
+
commit settles all affected source projections in one Runtime causal batch.
|
|
54
|
+
|
|
55
|
+
External collection impact is only a boundary invalidation hint. The Runtime derives
|
|
56
|
+
the exact `CollectionChange` before processors observe it.
|
|
57
|
+
|
|
58
|
+
## Pure derive and keyed projection
|
|
59
|
+
|
|
60
|
+
`derive(dependencies, compute, equality?)` always takes a named dependency object.
|
|
61
|
+
Dependencies are explicit and fixed when the definition is created.
|
|
62
|
+
|
|
63
|
+
Use `derive.keyed` when one keyed driver owns output membership and order:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
const fieldValues = derive.keyed(records, record => record.values[fieldId]);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Only affected driver entries execute the selector. Per-entry equality suppresses an
|
|
70
|
+
`updated` output transition when the selected value is unchanged. Added, removed and
|
|
71
|
+
order transitions retain their normal collection meaning.
|
|
72
|
+
|
|
73
|
+
Dynamic keyed lookups stay in the same API:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
const cardContent = derive.keyed(
|
|
77
|
+
items,
|
|
78
|
+
{
|
|
79
|
+
record: { source: records, key: item => item.recordId },
|
|
80
|
+
view: activeView,
|
|
81
|
+
fields: visibleFields,
|
|
82
|
+
},
|
|
83
|
+
(item, itemId, { record, view, fields }) => renderCard(itemId, item, record, view, fields)
|
|
84
|
+
);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
A plain Projection dependency invalidates the driver key set when it changes.
|
|
88
|
+
`{ source, key }` declares an output-key → dynamic source-key dependency. The Runtime
|
|
89
|
+
owns forward bindings and reverse invalidation. Missing selected entries remain bound,
|
|
90
|
+
so adding that source key later invalidates its dependents.
|
|
91
|
+
|
|
92
|
+
This is the keyed join protocol. Do not add a second join abstraction, wildcard
|
|
93
|
+
document-path grammar, or processor-side imperative dependency discovery.
|
|
94
|
+
|
|
95
|
+
## Runtime reads, updates and scope
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
const value = runtime.read(visible);
|
|
99
|
+
const selected = runtime.select(visible, rows => rows.get(rowId), equality);
|
|
100
|
+
const stop = selected.subscribe(listener);
|
|
101
|
+
|
|
102
|
+
runtime.update(filter, 'open');
|
|
103
|
+
runtime.update(selection, draft => {
|
|
104
|
+
draft.set(rowId, true);
|
|
105
|
+
draft.remove(previousRowId);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
runtime.batch(run, { cause });
|
|
15
109
|
```
|
|
16
110
|
|
|
17
|
-
`
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
Schema maps, tables, and `list(field, { keyOf })` nodes are all keyed collection
|
|
26
|
-
paths. A list uses its schema `keyOf` identity and preserves document order in
|
|
27
|
-
iteration; array-valued fields remain scalar value observations.
|
|
28
|
-
Tree structure reuses the same source kinds: `path.tree.rootId` is a scalar,
|
|
29
|
-
`path.tree.nodes` is a keyed collection, and `path.tree.nodes.item(id)` is a
|
|
30
|
-
single-node value. Committed tree-node groups route directly to affected keyed
|
|
31
|
-
entries, and the existing `CollectionOutput` publishes ordinary `CollectionChange`
|
|
32
|
-
entries, so unrelated node snapshots keep their identity.
|
|
33
|
-
|
|
34
|
-
External events are smaller than Runtime contexts. A value event carries the new
|
|
35
|
-
`value` and `revision`; a collection event carries a stable `previous` read,
|
|
36
|
-
`revision`, and an optional `impact` hint. The Runtime derives the exact
|
|
37
|
-
`CollectionChange`; processors never receive the impact directly.
|
|
38
|
-
|
|
39
|
-
`derive(dependencies, compute, equality?)` takes a tuple. Dependencies are
|
|
40
|
-
explicit and fixed when the definition is created. Processors must not discover
|
|
41
|
-
graph dependencies by reading other projections.
|
|
42
|
-
|
|
43
|
-
The Runtime API is intentionally small:
|
|
111
|
+
`select` returns the standard `Readable`. Keyed selectors track relevant key/structure
|
|
112
|
+
reads; equality filters the result only after a related invalidation.
|
|
113
|
+
|
|
114
|
+
`CollectionInputDraft` exposes `get`, `has`, `set`, `remove` and expires with the
|
|
115
|
+
synchronous edit callback. A throwing callback applies none of that edit. Collection
|
|
116
|
+
input equality is per entry and defaults to `Object.is`; it runs before accepted local
|
|
117
|
+
state is installed, so an equality failure also leaves the published value and next
|
|
118
|
+
draft unchanged. Equal sets do not replace stored data or publish.
|
|
44
119
|
|
|
45
120
|
```ts
|
|
46
|
-
runtime.get(projection);
|
|
47
|
-
const selected = runtime.readable(projection, value => value.get(id));
|
|
48
|
-
selected.subscribe(listener);
|
|
49
|
-
runtime.set(input, value);
|
|
50
|
-
runtime.update(collectionInput, draft => draft.set(id, value));
|
|
51
|
-
runtime.batch({ cause }, run);
|
|
52
121
|
const scope = runtime.scope();
|
|
122
|
+
const localFilter = scope.own(input<'all' | 'open'>('all'));
|
|
123
|
+
const local = scope.own(
|
|
124
|
+
derive({ tasks, filter: localFilter }, ({ tasks, filter }) =>
|
|
125
|
+
filter === 'all' ? tasks : filterOpen(tasks)
|
|
126
|
+
)
|
|
127
|
+
);
|
|
128
|
+
scope.read(local);
|
|
129
|
+
scope.update(localFilter, 'open');
|
|
53
130
|
scope.dispose();
|
|
54
|
-
runtime.dispose();
|
|
55
131
|
```
|
|
56
132
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
133
|
+
A scope shares the parent scheduler/materialization owner. `scope.own` assigns one
|
|
134
|
+
lifecycle to a projection definition or a static nested projection tree. Scoped
|
|
135
|
+
definitions may depend on root definitions; root and sibling scopes cannot depend on
|
|
136
|
+
scoped definitions, and one definition can belong to only one scope. Assign scope
|
|
137
|
+
ownership before the definition is first materialized as a root.
|
|
138
|
+
|
|
139
|
+
## One collection change protocol
|
|
140
|
+
|
|
141
|
+
All keyed projection sources and outputs use:
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
type CollectionChange<K extends string, V> =
|
|
145
|
+
| { kind: 'reset' }
|
|
146
|
+
| {
|
|
147
|
+
kind: 'incremental';
|
|
148
|
+
added: readonly { key: K; after: V }[];
|
|
149
|
+
updated: readonly { key: K; before: V; after: V }[];
|
|
150
|
+
removed: readonly { key: K; before: V }[];
|
|
151
|
+
order?: { before: readonly K[]; after: readonly K[] };
|
|
152
|
+
};
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Import `CollectionChange` from `doxum/advanced` when an advanced processor signature
|
|
156
|
+
needs it. Root projection consumers do not import this transport type.
|
|
157
|
+
|
|
158
|
+
Initial materialization and source reset report `reset` to advanced processors.
|
|
159
|
+
Incremental transitions are exact net changes across the settled Runtime batch.
|
|
65
160
|
|
|
66
|
-
|
|
67
|
-
only a `Readable`'s own publication revision is public for store integrations.
|
|
68
|
-
Runtime materialization, keyed storage, recovery and disposal are one owner.
|
|
161
|
+
## Advanced processors
|
|
69
162
|
|
|
70
|
-
|
|
163
|
+
Import `incremental` from `doxum/advanced` only when retained state, cross-key indexes,
|
|
164
|
+
or direct incremental output patching cannot be expressed cleanly with `derive` or
|
|
165
|
+
`derive.keyed`.
|
|
71
166
|
|
|
72
|
-
|
|
167
|
+
All advanced processors use named dependencies plus a required `process`. Add `state()`
|
|
168
|
+
only when the processor needs retained state:
|
|
73
169
|
|
|
74
170
|
```ts
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
})
|
|
171
|
+
const weights = incremental.collection(
|
|
172
|
+
{ tasks },
|
|
173
|
+
{
|
|
174
|
+
state: () => ({ initialized: false }),
|
|
175
|
+
process: ({ values, changes, output, state, reset }) => {
|
|
176
|
+
if (reset) {
|
|
177
|
+
for (const [id, task] of values.tasks) output.set(id, task.weight);
|
|
178
|
+
output.order([...values.tasks.keys()]);
|
|
179
|
+
state.initialized = true;
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const change = changes.tasks;
|
|
184
|
+
if (!change || change.kind === 'reset') return;
|
|
185
|
+
for (const entry of change.added) output.set(entry.key, entry.after.weight);
|
|
186
|
+
for (const entry of change.updated) output.set(entry.key, entry.after.weight);
|
|
187
|
+
for (const entry of change.removed) output.remove(entry.key);
|
|
188
|
+
if (change.order) output.order([...values.tasks.keys()]);
|
|
189
|
+
},
|
|
190
|
+
}
|
|
191
|
+
);
|
|
192
|
+
```
|
|
79
193
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
194
|
+
`values` and `changes` are keyed by dependency name. Scalar dependencies have no
|
|
195
|
+
collection change metadata. Collection processors additionally expose keyed
|
|
196
|
+
`previous`, `next`, and a borrowed `output` draft.
|
|
197
|
+
|
|
198
|
+
Multi-output processors use one closed group definition:
|
|
83
199
|
|
|
200
|
+
```ts
|
|
84
201
|
const render = incremental.group(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
shell: define.collection<
|
|
89
|
-
|
|
202
|
+
{ tasks },
|
|
203
|
+
{
|
|
204
|
+
output: define => ({
|
|
205
|
+
shell: define.collection<RowId, Shell>(),
|
|
206
|
+
count: define.value<number>(),
|
|
207
|
+
}),
|
|
208
|
+
state: () => ({ runs: 0 }),
|
|
209
|
+
process: ({ values, output, state }) => {
|
|
210
|
+
state.runs++;
|
|
211
|
+
for (const [id, task] of values.tasks) output.shell.set(id, makeShell(task));
|
|
212
|
+
output.count.set(values.tasks.size);
|
|
90
213
|
},
|
|
91
|
-
labels: define.collection<string, Label>(),
|
|
92
|
-
chrome: define.value<Chrome>(),
|
|
93
|
-
revision: define.value<number>(),
|
|
94
|
-
}),
|
|
95
|
-
({ sources, outputs }) => {
|
|
96
|
-
for (const [id, task] of sources[0]) {
|
|
97
|
-
outputs.node.shell.set(id, makeShell(task));
|
|
98
|
-
outputs.node.content.set(id, makeContent(task));
|
|
99
|
-
outputs.labels.set(id, makeLabel(task));
|
|
100
|
-
}
|
|
101
|
-
outputs.chrome.set(makeChrome(sources[0]));
|
|
102
|
-
outputs.revision.set(sources[0].size);
|
|
103
214
|
}
|
|
104
215
|
);
|
|
105
216
|
```
|
|
106
217
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
`previous`/`next` keyed reads and a callback-local `output` draft. Draft methods are
|
|
120
|
-
`set`, `remove`, and `order`; the Runtime validates and seals them after
|
|
121
|
-
the synchronous callback, computes keyed transitions and publishes one immutable
|
|
122
|
-
map-like value. Reset or fault recovery is internal.
|
|
123
|
-
`incremental.group(...)` is the composition boundary for several named value and
|
|
124
|
-
keyed collection outputs. `define.value<T>(equality?)` creates a scalar leaf and
|
|
125
|
-
`define.collection<K, V>(equality?)` creates a keyed leaf. Its nested namespace is
|
|
126
|
-
static API organization, not another producer, runtime or scheduler concept. Every
|
|
127
|
-
leaf is an ordinary `Projection` pointing to one output of the same processor
|
|
128
|
-
producer; one processor execution seals and publishes all changed leaves atomically,
|
|
129
|
-
and downstream processors depend on those leaves directly.
|
|
130
|
-
Initial builds and rebuilds must set every value leaf; ordinary incremental runs
|
|
131
|
-
may leave a value leaf untouched to preserve its current value and revision.
|
|
132
|
-
Reading any leaf materializes that producer once, while scope disposal releases the
|
|
133
|
-
producer and its retained state once.
|
|
134
|
-
|
|
135
|
-
## React selector tracking
|
|
136
|
-
|
|
137
|
-
```tsx
|
|
138
|
-
const task = useProjection(visible, tasks => tasks.get(taskId));
|
|
139
|
-
const [mode, setMode] = useInput(filter);
|
|
140
|
-
```
|
|
218
|
+
`define.collection<K,V>(equality?)` and `define.value<T>(equality?)` are available only
|
|
219
|
+
inside `output`. The returned static object tree is mirrored by ordinary Projection
|
|
220
|
+
leaves from the same producer: collections are `KeyedProjection<K,V>` and values are
|
|
221
|
+
`Projection<T>`. Value leaves must be established on initial build and
|
|
222
|
+
Runtime recovery; untouched value leaves keep their value on ordinary incremental runs.
|
|
223
|
+
|
|
224
|
+
Normal source resets preserve retained state when declared. A processor fault is
|
|
225
|
+
recovered by the Runtime: it recreates declared state and performs a reset evaluation.
|
|
226
|
+
Stateless processors follow the same recovery path without a state object. No public
|
|
227
|
+
rebuild token or manual recovery hook exists.
|
|
228
|
+
|
|
229
|
+
## React
|
|
141
230
|
|
|
142
|
-
|
|
143
|
-
`
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
231
|
+
`ProjectionProvider` supplies a Runtime or scope. `useProjection(projection, selector,
|
|
232
|
+
equality?)` uses the same Core selector semantics; `useInput` overloads scalar and
|
|
233
|
+
collection inputs. Document reads remain separate:
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
const value = useDocumentSelector(document, selector, equality);
|
|
237
|
+
```
|
|
147
238
|
|
|
148
|
-
Processors settle before listeners. Writes are forbidden
|
|
149
|
-
Listener
|
|
150
|
-
the Runtime error callback and internal recovery.
|
|
239
|
+
Processors settle before projection listeners. Writes are forbidden while processing
|
|
240
|
+
or notifying. Listener failure does not roll back an accepted document commit.
|