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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "doxum",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.26",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Doxum is a typed runtime for complex mutable documents.",
|
|
@@ -79,11 +79,14 @@
|
|
|
79
79
|
"vitest": "^4.0.16"
|
|
80
80
|
},
|
|
81
81
|
"scripts": {
|
|
82
|
-
"build": "tsdown --config tsdown.config.ts",
|
|
82
|
+
"build": "tsdown --config tsdown.config.ts && node scripts/check-build-exports.mjs && pnpm run check:declaration-portability",
|
|
83
83
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
84
84
|
"test": "vitest run --config vitest.config.ts",
|
|
85
|
-
"check": "pnpm run format:check && pnpm run lint && pnpm run typecheck && pnpm run test && pnpm run check:projection-surface",
|
|
85
|
+
"check": "pnpm run format:check && pnpm run lint && pnpm run typecheck && pnpm run check:public-types && pnpm run test && pnpm run check:projection-surface && pnpm run check:runtime-imports",
|
|
86
86
|
"check:projection-surface": "node scripts/check-projection-surface.mjs",
|
|
87
|
+
"check:runtime-imports": "node scripts/check-runtime-imports.mjs",
|
|
88
|
+
"check:public-types": "tsc -p tsconfig.public-api.json --noEmit",
|
|
89
|
+
"check:declaration-portability": "tsc -p tsconfig.declaration-portability.json && node scripts/check-declaration-portability.mjs",
|
|
87
90
|
"bench": "vitest bench --config vitest.bench.config.ts",
|
|
88
91
|
"profile": "node --expose-gc --import tsx core/bench/profile.ts",
|
|
89
92
|
"format": "prettier --write --ignore-unknown .",
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: doxum-runtime
|
|
3
|
-
description: 'Use
|
|
3
|
+
description: 'Use Doxum core, advanced projections, React, and local-sync for schemas, document state, ChangeSets, history, projections, subscriptions, persistence, and adapters.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Doxum Runtime
|
|
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 exact public call shapes, exported package surface, callback contexts, result
|
|
11
|
+
types, and supported errors, read the [API reference](references/api.en.md) or
|
|
12
|
+
[中文 API 参考](references/api.zh-CN.md) instead of implementation source.
|
|
10
13
|
For any projection design or implementation, especially custom processors or
|
|
11
14
|
cross-collection dependencies, read the [projection reference](references/projections.en.md)
|
|
12
15
|
or [中文 Projection 参考](references/projections.zh-CN.md).
|
|
@@ -36,13 +39,34 @@ Before runtime changes read [invariants](references/invariants.en.md) or
|
|
|
36
39
|
kinds, optional before/after order in the same group, and a separate root reset.
|
|
37
40
|
Standalone order records and duplicate groups are invalid. Grouping preserves field-level impact.
|
|
38
41
|
- Paths belong in subscription, impact and collection source callbacks.
|
|
42
|
+
- `Schema` / `ObjectSchema` are portable schema handles. Export inferred schemas
|
|
43
|
+
directly; application code should not need `ReturnType<typeof object>` or internal
|
|
44
|
+
node types to make declarations portable.
|
|
39
45
|
- Projection definitions are lazy; one ProjectionRuntime owns all materialized
|
|
40
46
|
producers, outputs, scheduling and source attachments for that Runtime.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
`KeyedProjection<K,V>` is the single public keyed projection handle. Collection
|
|
48
|
+
`observe`, `derive.keyed`, `incremental.collection`, group collection leaves and
|
|
49
|
+
`CollectionInput<K,V>` all use that same keyed capability.
|
|
50
|
+
Use named-object `derive` for pure aggregate values. Use `derive.keyed` when one keyed
|
|
51
|
+
driver owns the output key domain/order: per-entry equality suppresses unchanged
|
|
52
|
+
selected values. Its dependency form uses a named object: plain Projection members
|
|
53
|
+
invalidate the driver key set, while `{ source, key }` members let the Runtime own
|
|
54
|
+
per-output-key source bindings and reverse lookup. Selectors receive
|
|
55
|
+
`(entry, key)` or `(entry, key, dependencies)`. The producer graph remains explicit and static;
|
|
56
|
+
processors never perform imperative Runtime reads to discover dependencies.
|
|
57
|
+
`input.collection` edits have a strong exception boundary: callback or per-entry
|
|
58
|
+
equality failure leaves both the published value and the next draft unchanged.
|
|
59
|
+
A `ProjectionScope` only adds lifecycle ownership through `scope.own(...)`; create
|
|
60
|
+
definitions with the root declaration APIs, then own a projection or static output tree
|
|
61
|
+
before that definition is first materialized as a root.
|
|
62
|
+
Use advanced `incremental` only for retained state or cross-key coordination that
|
|
63
|
+
cannot be expressed by `derive.keyed`. React selector tracking remains a consumer
|
|
64
|
+
concern through `ProjectionProvider` and `useProjection(projection, selector)`.
|
|
65
|
+
- Advanced incremental definitions always declare `process`; `state()` is present only
|
|
66
|
+
when retained state is actually needed. `incremental.group` declares its static
|
|
67
|
+
output tree through the synchronous `output` callback. `define.value` and
|
|
68
|
+
`define.collection` are callback methods, not separate imports; every returned leaf
|
|
69
|
+
is a `Projection<T>` or `KeyedProjection<K,V>` from the same producer.
|
|
46
70
|
- Observer errors leave commits accepted. Do not retry as if they rolled back.
|
|
47
71
|
- Core stays framework-neutral; adapters consume standard `Readable`,
|
|
48
72
|
document `select`, and projection readables without internal target/address protocols.
|
|
@@ -50,4 +74,5 @@ Before runtime changes read [invariants](references/invariants.en.md) or
|
|
|
50
74
|
collaborative undo belong at a separate boundary.
|
|
51
75
|
|
|
52
76
|
Update examples and tests when behavior changes. Never add compatibility APIs,
|
|
53
|
-
a second path grammar or another writable
|
|
77
|
+
a second path grammar, a parallel join/dependency protocol, or another writable
|
|
78
|
+
derived cache.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
interface:
|
|
2
2
|
display_name: 'Doxum Runtime'
|
|
3
|
-
short_description: 'Build
|
|
3
|
+
short_description: 'Build and review Doxum core, projections, React, and local-sync.'
|
|
4
4
|
default_prompt: 'Use $doxum-runtime to implement this Doxum feature while preserving its runtime invariants.'
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# Public API Reference
|
|
2
|
+
|
|
3
|
+
Use this as the compact public-contract lookup. For projection lifecycle and examples,
|
|
4
|
+
read [projections](projections.en.md); for modeling choices, read the [guide](guide.en.md).
|
|
5
|
+
|
|
6
|
+
## Package entry points
|
|
7
|
+
|
|
8
|
+
| Package | Purpose |
|
|
9
|
+
| ------------------ | ----------------------------------------------------------------------- |
|
|
10
|
+
| `doxum` | schema, canonical document runtime, reads, history, impact, projections |
|
|
11
|
+
| `doxum/advanced` | retained-state and multi-output projection processors |
|
|
12
|
+
| `doxum/react` | React adapters over Core capabilities |
|
|
13
|
+
| `doxum/local-sync` | IndexedDB persistence and browser leader/follower coordination |
|
|
14
|
+
|
|
15
|
+
## `doxum`
|
|
16
|
+
|
|
17
|
+
### Schema
|
|
18
|
+
|
|
19
|
+
| API | Contract |
|
|
20
|
+
| ------------------------- | ---------------------------------------------------------------------- |
|
|
21
|
+
| `field<T>(validator?)` | Atomic payload. Replace whole; payload interior is readonly. |
|
|
22
|
+
| `optional(schema)` | Optional field/variant/map/list/tree member. |
|
|
23
|
+
| `object(shape)` | Fixed declared members; returns opaque `ObjectSchema<T>`. |
|
|
24
|
+
| `variant(tag, variants)` | Tagged union of object branches. |
|
|
25
|
+
| `map(value, { key }?)` | Dynamic keyed record. |
|
|
26
|
+
| `table(entity, { key }?)` | Ordered keyed object/variant entities stored as `{ ids, byId }`. |
|
|
27
|
+
| `list(field, { keyOf })` | Ordered array with stable string identity. |
|
|
28
|
+
| `tree(field)` | Empty-or-single-root tree; optional field means optional node payload. |
|
|
29
|
+
| `parse(schema, input)` | Validate unknown input and copy schema structure. |
|
|
30
|
+
|
|
31
|
+
Public schema types are `Schema<T>`, `ObjectSchema<T>`, `Infer<S>`, `ReadonlyValue<T>`,
|
|
32
|
+
`Validator<T>`, `SchemaPath<S>`, `PathValueOf<P>`, `DocumentAnchor`,
|
|
33
|
+
`DocumentListConfig<T>`, `DocumentTreeNode<T>` and `DocumentTreeValue<T>`.
|
|
34
|
+
Concrete node representation types are internal.
|
|
35
|
+
`Schema` / `ObjectSchema` are declaration-portable handles; downstream packages can
|
|
36
|
+
export inferred schema constants directly without `ReturnType` wrappers.
|
|
37
|
+
|
|
38
|
+
`Validator<T>` is validation-only. A function validator is a predicate/assertion:
|
|
39
|
+
`true` or `undefined` succeeds, `false` rejects, and an assertion may throw.
|
|
40
|
+
A Standard Schema validator must return the original value by identity on success;
|
|
41
|
+
transformed output is rejected. Validators are synchronous and must not mutate input.
|
|
42
|
+
|
|
43
|
+
Draft collection methods:
|
|
44
|
+
|
|
45
|
+
| Container | Read | Write |
|
|
46
|
+
| --------- | -------------------------------------------- | ---------------------------------------------------------------------------- |
|
|
47
|
+
| map | `get`, `has`, `ids` | `put`, `remove`, `replace(next)` |
|
|
48
|
+
| table | `get`, `has`, `ids` | `create`, `remove`, `move`, `reorder`, `replace(id,value)`, `replace(next)` |
|
|
49
|
+
| list | `get`, `has`, `ids` | `insert`, `remove`, `move`, `reorder`, `replace(key,value)`, `replace(next)` |
|
|
50
|
+
| tree | `rootId`, `get`, `has`, `parent`, `children` | `insert`, `move`, `remove`, `replace(id,value)`, `replace(next)` |
|
|
51
|
+
|
|
52
|
+
`replace(parent, key, value)` replaces one object/variant member with plain `Infer` data.
|
|
53
|
+
`document.replace(value)` is a whole-document reset and remains a separate operation.
|
|
54
|
+
|
|
55
|
+
### Document runtime
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
const document = createDocument({ schema, initial, history: { capacity: 100 } });
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`DocumentRuntime<S>` exposes:
|
|
62
|
+
|
|
63
|
+
- `schema`, `revision()`, `snapshot()` and `readonly()`;
|
|
64
|
+
- `update(run, { source?, history? }?)` for synchronous atomic Draft work;
|
|
65
|
+
- `apply(changes, { expectedRevision, source?: 'local' | 'system', history? })`, or
|
|
66
|
+
`apply(changes, { expectedRevision, source: 'remote' })` for remote replay;
|
|
67
|
+
- `replace(value, { source? }?)` for a whole-document reset;
|
|
68
|
+
- `subscribe(listener)` and `subscribe(path | paths, listener)`;
|
|
69
|
+
- `history.undo()`, `redo()`, `clear()`, `group()`;
|
|
70
|
+
- `dispose()`.
|
|
71
|
+
|
|
72
|
+
`document.readonly()` returns `ReadonlyDocument<S>`, which has only `revision()` and
|
|
73
|
+
document commit/path subscriptions. It is the capability-stripped document boundary.
|
|
74
|
+
|
|
75
|
+
`update` returns `TransactionResult`; `apply`, whole-document `replace`, history travel
|
|
76
|
+
and group cancellation return `OperationResult`. Result status is `committed`,
|
|
77
|
+
`unchanged`, or `rejected`. Throw `TransactionRejected` for expected application
|
|
78
|
+
rejection. Ordinary thrown values roll back and rethrow unchanged.
|
|
79
|
+
|
|
80
|
+
Document errors are `TransactionRejected`, `DocumentReentrancyError` and
|
|
81
|
+
`DocumentDisposedError`. Parsing failures use `ParseError` with `ParseIssue` data.
|
|
82
|
+
|
|
83
|
+
### Reads and subscriptions
|
|
84
|
+
|
|
85
|
+
| API | Contract |
|
|
86
|
+
| --------------------------------------- | --------------------------------------------------------- |
|
|
87
|
+
| `read(document, selector)` | One synchronous borrowed `Read`. |
|
|
88
|
+
| `select(document, selector, equality?)` | Dynamic read-tracked `Readable<TResult>`. |
|
|
89
|
+
| `snapshot(value)` | Export borrowed schema structure as stable readonly data. |
|
|
90
|
+
|
|
91
|
+
`Readable<T>` has `current()`, `revision()` and `subscribe(listener)`.
|
|
92
|
+
`select` rebinds dependencies when selector control flow changes and suppresses
|
|
93
|
+
publication when `equality` reports the selected result unchanged.
|
|
94
|
+
|
|
95
|
+
A commit has `revision`, `source`, `changes`, `impact`. Public change/impact types include
|
|
96
|
+
`ChangeSet`, `Change`, `MemberChange`, `ValueTransition`, `DocumentCommit`,
|
|
97
|
+
`DocumentImpact`, `CollectionImpact`, `MutationIssue` and result/diagnostic types.
|
|
98
|
+
|
|
99
|
+
### Projections
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
const mode = input<'all' | 'open'>('all');
|
|
103
|
+
const selection = input.collection<RowId, Selection>();
|
|
104
|
+
const rows = observe(document, path => path.rows);
|
|
105
|
+
const count = derive({ rows }, ({ rows }) => rows.size);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
| API | Contract |
|
|
109
|
+
| ----------------------------------------------- | ------------------------------------------------------------------- |
|
|
110
|
+
| `input(initial, equality?)` | Runtime-local writable scalar `Input<T>`. |
|
|
111
|
+
| `input.collection<K,V>(initial?, equality?)` | Runtime-local keyed `CollectionInput<K,V>` with per-entry equality. |
|
|
112
|
+
| `observe(document)` | Whole-document Projection source. |
|
|
113
|
+
| `observe(document, path => ...)` | Schema-path scalar or keyed Projection source. |
|
|
114
|
+
| `observe(readable)` | Adapt a Doxum `Readable`. |
|
|
115
|
+
| `observe(externalSource)` | Adapt exported external value/collection source contracts. |
|
|
116
|
+
| `derive(dependencies, compute, equality?)` | Pure value Projection with named dependencies. |
|
|
117
|
+
| `derive.keyed(driver, select, equality?)` | Preserve driver keys/order and project each entry. |
|
|
118
|
+
| `derive.keyed(driver, deps, select, equality?)` | Keyed projection with named global/dynamic keyed dependencies. |
|
|
119
|
+
|
|
120
|
+
Dynamic keyed dependency syntax is
|
|
121
|
+
`{ source: keyedProjection, key: (driverValue, driverKey) => sourceKey | undefined }`.
|
|
122
|
+
The Runtime owns output-key to source-key bindings and reverse invalidation. This helper
|
|
123
|
+
shape is inferred at the call site; no dedicated public helper type is required.
|
|
124
|
+
Keyed selectors keep stable leading arguments: `(value, key)` without extra dependencies
|
|
125
|
+
and `(value, key, dependencies)` when the named dependency object is present.
|
|
126
|
+
|
|
127
|
+
`ProjectionRuntime` exposes:
|
|
128
|
+
|
|
129
|
+
- `read(projection)`;
|
|
130
|
+
- `select(projection)` and `select(projection, selector, equality?)`;
|
|
131
|
+
- `update(input, nextValue)` for scalar `Input<T>`;
|
|
132
|
+
- `update(collectionInput, draft => ...)` for `CollectionInput<K,V>`;
|
|
133
|
+
- `batch(run, { cause? }?)`;
|
|
134
|
+
- `scope()`;
|
|
135
|
+
- `dispose()`.
|
|
136
|
+
|
|
137
|
+
`input.collection` draft callbacks and per-entry equality run before accepted local
|
|
138
|
+
state is installed. If either throws, the published collection and the next draft both
|
|
139
|
+
remain at the pre-update state.
|
|
140
|
+
|
|
141
|
+
`CollectionInputDraft` exposes `get`, `has`, `set`, `remove`. A throwing edit callback
|
|
142
|
+
applies none of that edit. `ProjectionScope` mirrors `read`, `select`, `update`, `batch`,
|
|
143
|
+
`dispose` and adds `own(projectionOrTree)` for lifecycle ownership of scalar/keyed inputs,
|
|
144
|
+
derives and advanced output trees. One definition can belong to only one scope; call
|
|
145
|
+
`own` before that definition is first materialized as a root.
|
|
146
|
+
|
|
147
|
+
Public projection types are `Projection<T>`, `KeyedProjection<K,V>`, `Input<T>`,
|
|
148
|
+
`CollectionInput<K,V>`, `CollectionInputDraft<K,V>`, `ProjectionRuntime`,
|
|
149
|
+
`ProjectionScope`, plus the external source/event contracts. All keyed producers return
|
|
150
|
+
`KeyedProjection<K,V>`; `CollectionInput<K,V>` is its writable Runtime-local form.
|
|
151
|
+
`ProjectionError` exposes only `phase` and `cause`;
|
|
152
|
+
`ProjectionDisposedError` represents disposed access.
|
|
153
|
+
|
|
154
|
+
External value sources expose `kind: 'value'`, `current()`, `revision()`, `subscribe()`.
|
|
155
|
+
External collection sources expose `kind: 'collection'` plus `get/has/ids` reads and
|
|
156
|
+
optional `CollectionImpact` invalidation hints; the adapter derives exact
|
|
157
|
+
`CollectionChange` transitions.
|
|
158
|
+
|
|
159
|
+
## `doxum/advanced`
|
|
160
|
+
|
|
161
|
+
Use advanced processors only when `derive` / `derive.keyed` cannot express retained
|
|
162
|
+
state, cross-key indexes, or direct incremental output patching.
|
|
163
|
+
|
|
164
|
+
All advanced functions use named dependencies and a closed definition object.
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
const total = incremental(
|
|
168
|
+
{ rows },
|
|
169
|
+
{
|
|
170
|
+
state: () => ({ runs: 0 }),
|
|
171
|
+
process: ({ values, changes, previous, reset, cause, state }) => nextValue,
|
|
172
|
+
}
|
|
173
|
+
);
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
`incremental.collection(dependencies, { process, state? })` adds keyed `previous`,
|
|
177
|
+
`next`, and borrowed `output` with `set`, `remove`, `order`. `state()` is declared only
|
|
178
|
+
when retained state is needed; stateless process contexts do not contain `state`.
|
|
179
|
+
|
|
180
|
+
`incremental.group(dependencies, { output, process, state? })` declares several leaves:
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
const view = incremental.group(
|
|
184
|
+
{ rows },
|
|
185
|
+
{
|
|
186
|
+
output: define => ({
|
|
187
|
+
cards: define.collection<RowId, Card>(),
|
|
188
|
+
count: define.value<number>(),
|
|
189
|
+
}),
|
|
190
|
+
state: () => ({ runs: 0 }),
|
|
191
|
+
process: ({ values, changes, previous, next, output, reset, cause, state }) => {},
|
|
192
|
+
}
|
|
193
|
+
);
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
`define.collection<K,V>(equality?)` and `define.value<T>(equality?)` exist only inside
|
|
197
|
+
`output`. The returned static object tree is mirrored with `KeyedProjection<K,V>`
|
|
198
|
+
collection leaves and `Projection<T>` value leaves. Every declared descriptor must be
|
|
199
|
+
returned exactly once.
|
|
200
|
+
|
|
201
|
+
Normal source resets preserve retained state when declared. If a processor faults, the
|
|
202
|
+
Runtime owns recovery: it recreates declared state and runs a reset evaluation. Stateless
|
|
203
|
+
processors use the same recovery path without a state object. There is no public rebuild
|
|
204
|
+
token or manual recovery protocol.
|
|
205
|
+
|
|
206
|
+
Exported advanced helper types are the value/collection/group context and definition
|
|
207
|
+
types, `CollectionChange`, and the declaration-safe `IncrementalGroupOutput` /
|
|
208
|
+
`IncrementalGroupResult` type boundaries. Normal callers infer the latter two; internal
|
|
209
|
+
scheduler/output plumbing is not part of the public contract.
|
|
210
|
+
|
|
211
|
+
## `doxum/react`
|
|
212
|
+
|
|
213
|
+
| API | Contract |
|
|
214
|
+
| ---------------------------------------------------- | -------------------------------------------------------------- |
|
|
215
|
+
| `ProjectionProvider` | Provide `ProjectionRuntime` or `ProjectionScope`. |
|
|
216
|
+
| `useProjection(projection)` | Read a Projection. |
|
|
217
|
+
| `useProjection(projection, selector, equality?)` | Selected Projection read. |
|
|
218
|
+
| `useInput(input)` | Scalar `[value, setValue]` or collection `[map, updateDraft]`. |
|
|
219
|
+
| `useDocumentSelector(document, selector, equality?)` | React adapter over Core document `select`. |
|
|
220
|
+
| `useReadable(readable)` | Subscribe to any Doxum `Readable`. |
|
|
221
|
+
| `useHistory(history)` | History state plus `undo` / `redo`. |
|
|
222
|
+
|
|
223
|
+
## `doxum/local-sync`
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
const sync = await attachLocalSync({
|
|
227
|
+
runtime: document,
|
|
228
|
+
database: 'app',
|
|
229
|
+
documentId: 'doc:1',
|
|
230
|
+
schemaVersion: 1,
|
|
231
|
+
changeLimits,
|
|
232
|
+
onError,
|
|
233
|
+
});
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
`LocalSync` exposes `state: Readable<LocalSyncState>`, `flush()` and async `dispose()`.
|
|
237
|
+
Only the leader may write while attached. Whole-document replace and externally supplied
|
|
238
|
+
remote apply are unsupported while attached.
|
|
239
|
+
|
|
240
|
+
`LocalSyncError` is the single public operational error class. Its `code` is one of
|
|
241
|
+
`unavailable`, `schema-mismatch`, `consistency`, `read-only`, `unsupported-operation`,
|
|
242
|
+
`disposed`, `invalid-data`.
|
|
243
|
+
`LocalSyncState` error states carry `LocalSyncError`, and `onError` receives the same
|
|
244
|
+
operational error contract. Consumer state-listener exceptions are isolated from sync
|
|
245
|
+
fault state and `onError`.
|
|
246
|
+
|
|
247
|
+
`JsonChangeLimits` contains `maxChanges`, `maxBytes`, `maxDepth`, `maxStringLength`.
|
|
248
|
+
`defaultJsonChangeLimits` supplies defaults. Limits admit new local commits only;
|
|
249
|
+
durable replay remains readable under smaller current limits.
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# Public API 参考
|
|
2
|
+
|
|
3
|
+
这里列出稳定的公开契约。Projection 生命周期和示例见 [Projection 参考](projections.zh-CN.md),
|
|
4
|
+
建模选择见 [指南](guide.zh-CN.md)。
|
|
5
|
+
|
|
6
|
+
## Package 入口
|
|
7
|
+
|
|
8
|
+
| Package | 用途 |
|
|
9
|
+
| ------------------ | --------------------------------------------------------------------- |
|
|
10
|
+
| `doxum` | schema、canonical document runtime、读取、history、impact、projection |
|
|
11
|
+
| `doxum/advanced` | retained state 与多输出 incremental processor |
|
|
12
|
+
| `doxum/react` | Core capability 的 React adapter |
|
|
13
|
+
| `doxum/local-sync` | IndexedDB 持久化与浏览器 leader/follower 协调 |
|
|
14
|
+
|
|
15
|
+
## `doxum`
|
|
16
|
+
|
|
17
|
+
### Schema
|
|
18
|
+
|
|
19
|
+
| API | 契约 |
|
|
20
|
+
| ------------------------- | ----------------------------------------------------- |
|
|
21
|
+
| `field<T>(validator?)` | 原子 payload,整体替换;内部只读。 |
|
|
22
|
+
| `optional(schema)` | 让 field/variant/map/list/tree member 可缺省。 |
|
|
23
|
+
| `object(shape)` | 固定声明 member;返回 opaque `ObjectSchema<T>`。 |
|
|
24
|
+
| `variant(tag, variants)` | object branch 组成的 tagged union。 |
|
|
25
|
+
| `map(value, { key }?)` | 动态 keyed record。 |
|
|
26
|
+
| `table(entity, { key }?)` | 有序 object/variant entity,数据为 `{ ids, byId }`。 |
|
|
27
|
+
| `list(field, { keyOf })` | 带稳定 string identity 的有序数组。 |
|
|
28
|
+
| `tree(field)` | 空或单根树;optional field 表示 node payload 可缺省。 |
|
|
29
|
+
| `parse(schema, input)` | 校验 unknown 输入并复制 schema structure。 |
|
|
30
|
+
|
|
31
|
+
公开 schema 类型包括 `Schema<T>`、`ObjectSchema<T>`、`Infer<S>`、
|
|
32
|
+
`ReadonlyValue<T>`、`Validator<T>`、`SchemaPath<S>`、`PathValueOf<P>`、
|
|
33
|
+
`DocumentAnchor`、`DocumentListConfig<T>`、`DocumentTreeNode<T>`、
|
|
34
|
+
`DocumentTreeValue<T>`。具体 node representation 属于内部实现,不是公开契约。
|
|
35
|
+
`Schema` / `ObjectSchema` 是可跨 package 生成声明的 handle;下游 package 可以直接
|
|
36
|
+
export 推导出的 schema 常量,不需要应用层写 `ReturnType` 包装。
|
|
37
|
+
|
|
38
|
+
`Validator<T>` 只负责校验。函数 validator 是 predicate/assertion:返回 `true` 或
|
|
39
|
+
`undefined` 表示成功,返回 `false` 表示拒绝,assertion 也可以 throw。
|
|
40
|
+
Standard Schema 成功时必须按 identity 返回原输入;transform output 会被拒绝。
|
|
41
|
+
validator 必须同步且不能修改输入。
|
|
42
|
+
|
|
43
|
+
Draft collection 方法:
|
|
44
|
+
|
|
45
|
+
| Container | 读 | 写 |
|
|
46
|
+
| --------- | -------------------------------------------- | ---------------------------------------------------------------------------- |
|
|
47
|
+
| map | `get`, `has`, `ids` | `put`, `remove`, `replace(next)` |
|
|
48
|
+
| table | `get`, `has`, `ids` | `create`, `remove`, `move`, `reorder`, `replace(id,value)`, `replace(next)` |
|
|
49
|
+
| list | `get`, `has`, `ids` | `insert`, `remove`, `move`, `reorder`, `replace(key,value)`, `replace(next)` |
|
|
50
|
+
| tree | `rootId`, `get`, `has`, `parent`, `children` | `insert`, `move`, `remove`, `replace(id,value)`, `replace(next)` |
|
|
51
|
+
|
|
52
|
+
`replace(parent, key, value)` 用 plain `Infer` 数据替换一个 object/variant member。
|
|
53
|
+
`document.replace(value)` 是 whole-document reset,是独立操作。
|
|
54
|
+
|
|
55
|
+
### Document runtime
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
const document = createDocument({ schema, initial, history: { capacity: 100 } });
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`DocumentRuntime<S>` 提供:
|
|
62
|
+
|
|
63
|
+
- `schema`、`revision()`、`snapshot()`、`readonly()`;
|
|
64
|
+
- `update(run, { source?, history? }?)`:同步、原子的 Draft mutation;
|
|
65
|
+
- `apply(changes, { expectedRevision, source?: 'local' | 'system', history? })`;remote replay
|
|
66
|
+
使用 `apply(changes, { expectedRevision, source: 'remote' })`;
|
|
67
|
+
- `replace(value, { source? }?)`:whole-document reset;
|
|
68
|
+
- `subscribe(listener)`、`subscribe(path | paths, listener)`;
|
|
69
|
+
- `history.undo()`、`redo()`、`clear()`、`group()`;
|
|
70
|
+
- `dispose()`。
|
|
71
|
+
|
|
72
|
+
`document.readonly()` 返回 `ReadonlyDocument<S>`,只保留 `revision()` 和
|
|
73
|
+
document commit/path subscription,用于显式去掉写 capability 的边界。
|
|
74
|
+
|
|
75
|
+
`update` 返回 `TransactionResult`;`apply`、whole-document `replace`、history travel
|
|
76
|
+
和 group cancel 返回 `OperationResult`。状态为 `committed`、`unchanged`、`rejected`。
|
|
77
|
+
应用层预期拒绝用 `TransactionRejected`;普通 throw 会 rollback 后原样抛出。
|
|
78
|
+
|
|
79
|
+
Document 错误为 `TransactionRejected`、`DocumentReentrancyError`、
|
|
80
|
+
`DocumentDisposedError`。parse 错误为 `ParseError`,并带 `ParseIssue`。
|
|
81
|
+
|
|
82
|
+
### 读取与订阅
|
|
83
|
+
|
|
84
|
+
| API | 契约 |
|
|
85
|
+
| --------------------------------------- | ------------------------------------------------------- |
|
|
86
|
+
| `read(document, selector)` | 一次同步 borrowed `Read`。 |
|
|
87
|
+
| `select(document, selector, equality?)` | 动态 read-tracked `Readable<TResult>`。 |
|
|
88
|
+
| `snapshot(value)` | 把 borrowed schema structure 导出为稳定 readonly data。 |
|
|
89
|
+
|
|
90
|
+
`Readable<T>` 提供 `current()`、`revision()`、`subscribe(listener)`。
|
|
91
|
+
`select` 会随 selector 分支变化重绑依赖;`equality` 判定结果相等时不发布。
|
|
92
|
+
|
|
93
|
+
Commit 包含 `revision`、`source`、`changes`、`impact`。公开 change/impact 类型包括
|
|
94
|
+
`ChangeSet`、`Change`、`MemberChange`、`ValueTransition`、`DocumentCommit`、
|
|
95
|
+
`DocumentImpact`、`CollectionImpact`、`MutationIssue` 和结果/诊断类型。
|
|
96
|
+
|
|
97
|
+
### Projection
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
const mode = input<'all' | 'open'>('all');
|
|
101
|
+
const selection = input.collection<RowId, Selection>();
|
|
102
|
+
const rows = observe(document, path => path.rows);
|
|
103
|
+
const count = derive({ rows }, ({ rows }) => rows.size);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
| API | 契约 |
|
|
107
|
+
| ----------------------------------------------- | -------------------------------------------------------------------- |
|
|
108
|
+
| `input(initial, equality?)` | Runtime-local writable scalar `Input<T>`。 |
|
|
109
|
+
| `input.collection<K,V>(initial?, equality?)` | Runtime-local keyed `CollectionInput<K,V>`,equality 按 entry 比较。 |
|
|
110
|
+
| `observe(document)` | whole-document Projection source。 |
|
|
111
|
+
| `observe(document, path => ...)` | schema-path scalar 或 keyed Projection source。 |
|
|
112
|
+
| `observe(readable)` | 接入 Doxum `Readable`。 |
|
|
113
|
+
| `observe(externalSource)` | 接入公开 external value/collection source contract。 |
|
|
114
|
+
| `derive(dependencies, compute, equality?)` | named dependencies 的 pure value Projection。 |
|
|
115
|
+
| `derive.keyed(driver, select, equality?)` | 保留 driver key/order,逐 entry projection。 |
|
|
116
|
+
| `derive.keyed(driver, deps, select, equality?)` | 带 named global/dynamic keyed dependencies 的 keyed projection。 |
|
|
117
|
+
|
|
118
|
+
动态 keyed dependency 写法为
|
|
119
|
+
`{ source: keyedProjection, key: (driverValue, driverKey) => sourceKey | undefined }`。
|
|
120
|
+
Runtime 拥有 output-key → source-key binding 和 reverse invalidation。这个 helper shape
|
|
121
|
+
在调用点自动推断,不暴露独立 public helper type。
|
|
122
|
+
keyed selector 的前置参数固定:无额外 dependency 时为 `(value, key)`;存在 named
|
|
123
|
+
dependency object 时为 `(value, key, dependencies)`。
|
|
124
|
+
|
|
125
|
+
`ProjectionRuntime` 提供:
|
|
126
|
+
|
|
127
|
+
- `read(projection)`;
|
|
128
|
+
- `select(projection)`、`select(projection, selector, equality?)`;
|
|
129
|
+
- `update(input, nextValue)`:写 scalar `Input<T>`;
|
|
130
|
+
- `update(collectionInput, draft => ...)`:写 `CollectionInput<K,V>`;
|
|
131
|
+
- `batch(run, { cause? }?)`;
|
|
132
|
+
- `scope()`;
|
|
133
|
+
- `dispose()`。
|
|
134
|
+
|
|
135
|
+
`input.collection` 的 draft callback 与逐 entry equality 都在本地状态正式安装前完成;
|
|
136
|
+
任一环节抛错时,已发布 collection 与下一次 draft 都保持更新前状态。
|
|
137
|
+
|
|
138
|
+
`CollectionInputDraft` 提供 `get`、`has`、`set`、`remove`。edit callback throw 时该次 edit
|
|
139
|
+
整体不应用。`ProjectionScope` 镜像 `read`、`select`、`update`、`batch`、`dispose`,
|
|
140
|
+
并增加 `own(projectionOrTree)`,统一拥有 scalar/keyed input、derive 与 advanced output
|
|
141
|
+
tree 的 lifecycle。同一个 definition 只能属于一个 scope,并且必须在该 definition
|
|
142
|
+
首次作为 root materialize 之前调用 `own`。
|
|
143
|
+
|
|
144
|
+
公开 Projection 类型包括 `Projection<T>`、`KeyedProjection<K,V>`、`Input<T>`、
|
|
145
|
+
`CollectionInput<K,V>`、`CollectionInputDraft<K,V>`、`ProjectionRuntime`、
|
|
146
|
+
`ProjectionScope` 和 external source/event contract。所有 keyed producer 都返回
|
|
147
|
+
`KeyedProjection<K,V>`;`CollectionInput<K,V>` 是它的 Runtime-local writable 形式。
|
|
148
|
+
`ProjectionError` 只公开 `phase` 与 `cause`;
|
|
149
|
+
`ProjectionDisposedError` 表达 disposed access。
|
|
150
|
+
|
|
151
|
+
External value source 提供 `kind: 'value'`、`current()`、`revision()`、`subscribe()`。
|
|
152
|
+
External collection source 提供 `kind: 'collection'` 和 `get/has/ids` read,可给
|
|
153
|
+
`CollectionImpact` invalidation hint;adapter 会导出精确 `CollectionChange`。
|
|
154
|
+
|
|
155
|
+
## `doxum/advanced`
|
|
156
|
+
|
|
157
|
+
只有 retained state、cross-key index 或直接 incremental patch 无法用
|
|
158
|
+
`derive` / `derive.keyed` 清晰表达时才使用 advanced processor。
|
|
159
|
+
|
|
160
|
+
所有 advanced API 都使用 named dependencies + closed definition object。
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
const total = incremental(
|
|
164
|
+
{ rows },
|
|
165
|
+
{
|
|
166
|
+
state: () => ({ runs: 0 }),
|
|
167
|
+
process: ({ values, changes, previous, reset, cause, state }) => nextValue,
|
|
168
|
+
}
|
|
169
|
+
);
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
`incremental.collection(dependencies, { process, state? })` 额外提供 keyed
|
|
173
|
+
`previous`、`next` 和 borrowed `output`;output 有 `set`、`remove`、`order`。只有确实
|
|
174
|
+
需要 retained state 时才声明 `state()`;无状态 process context 不包含 `state`。
|
|
175
|
+
|
|
176
|
+
`incremental.group(dependencies, { output, process, state? })` 声明多个 leaf:
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
const view = incremental.group(
|
|
180
|
+
{ rows },
|
|
181
|
+
{
|
|
182
|
+
output: define => ({
|
|
183
|
+
cards: define.collection<RowId, Card>(),
|
|
184
|
+
count: define.value<number>(),
|
|
185
|
+
}),
|
|
186
|
+
state: () => ({ runs: 0 }),
|
|
187
|
+
process: ({ values, changes, previous, next, output, reset, cause, state }) => {},
|
|
188
|
+
}
|
|
189
|
+
);
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
`define.collection<K,V>(equality?)`、`define.value<T>(equality?)` 只存在于
|
|
193
|
+
`output` callback 内。返回值必须是非空静态 object tree,每个 descriptor 恰好返回一次;
|
|
194
|
+
最终同 shape 的 collection leaf 是 `KeyedProjection<K,V>`,value leaf 是
|
|
195
|
+
`Projection<T>`。
|
|
196
|
+
|
|
197
|
+
普通 source reset 保留已声明的 retained state。processor fault 的 recovery 由 Runtime
|
|
198
|
+
拥有:重新创建已声明的 state,再执行 reset evaluation;stateless processor 走同一恢复
|
|
199
|
+
路径但没有 state object。没有公开 rebuild token 或手工恢复协议。
|
|
200
|
+
|
|
201
|
+
advanced 导出 value/collection/group 的 context/definition 类型、`CollectionChange`,
|
|
202
|
+
以及声明可移植性所需的 `IncrementalGroupOutput` / `IncrementalGroupResult` 类型边界。
|
|
203
|
+
普通调用方通常只依赖推导;scheduler/output runtime plumbing 不属于公开 API。
|
|
204
|
+
|
|
205
|
+
## `doxum/react`
|
|
206
|
+
|
|
207
|
+
| API | 契约 |
|
|
208
|
+
| ---------------------------------------------------- | ------------------------------------------------------------- |
|
|
209
|
+
| `ProjectionProvider` | 提供 `ProjectionRuntime` 或 `ProjectionScope`。 |
|
|
210
|
+
| `useProjection(projection)` | 读取 Projection。 |
|
|
211
|
+
| `useProjection(projection, selector, equality?)` | selector 形式读取 Projection。 |
|
|
212
|
+
| `useInput(input)` | scalar `[value,setValue]` 或 collection `[map,updateDraft]`。 |
|
|
213
|
+
| `useDocumentSelector(document, selector, equality?)` | Core document `select` 的 React adapter。 |
|
|
214
|
+
| `useReadable(readable)` | 订阅任意 Doxum `Readable`。 |
|
|
215
|
+
| `useHistory(history)` | history state + `undo` / `redo`。 |
|
|
216
|
+
|
|
217
|
+
## `doxum/local-sync`
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
const sync = await attachLocalSync({
|
|
221
|
+
runtime: document,
|
|
222
|
+
database: 'app',
|
|
223
|
+
documentId: 'doc:1',
|
|
224
|
+
schemaVersion: 1,
|
|
225
|
+
changeLimits,
|
|
226
|
+
onError,
|
|
227
|
+
});
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
`LocalSync` 提供 `state: Readable<LocalSyncState>`、`flush()`、async `dispose()`。
|
|
231
|
+
attached 时只有 leader 可写;whole-document replace 和外部 remote apply 不支持。
|
|
232
|
+
|
|
233
|
+
`LocalSyncError` 是唯一公开 operational error class。`code` 取值:
|
|
234
|
+
`unavailable`、`schema-mismatch`、`consistency`、`read-only`、
|
|
235
|
+
`unsupported-operation`、`disposed`、`invalid-data`。
|
|
236
|
+
`LocalSyncState` 的 error state 持有 `LocalSyncError`,`onError` 也只接收同一
|
|
237
|
+
operational error contract;consumer state-listener exception 不进入 sync fault state
|
|
238
|
+
或 `onError`。
|
|
239
|
+
|
|
240
|
+
`JsonChangeLimits` 包含 `maxChanges`、`maxBytes`、`maxDepth`、`maxStringLength`。
|
|
241
|
+
`defaultJsonChangeLimits` 提供默认值。limits 只限制新 local commit;更小的新 limit
|
|
242
|
+
不会阻止读取已经持久化的 durable replay。
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Doxum Guide
|
|
2
2
|
|
|
3
|
+
For exact signatures across `doxum`, `doxum/advanced`, `doxum/react`, and
|
|
4
|
+
`doxum/local-sync`, use the [API reference](api.en.md). This guide focuses on choices
|
|
5
|
+
and lifecycle semantics.
|
|
6
|
+
|
|
3
7
|
## Define, Update And Read
|
|
4
8
|
|
|
5
9
|
```ts
|
|
@@ -16,7 +20,6 @@ document.update(draft => {
|
|
|
16
20
|
const task = draft.tasks.get('a');
|
|
17
21
|
if (task) task.done = !task.done;
|
|
18
22
|
draft.tasks.put('b', { title: 'Review', done: false });
|
|
19
|
-
return { warnings: [] };
|
|
20
23
|
});
|
|
21
24
|
const done = read(document, state => state.tasks.get('a')?.done);
|
|
22
25
|
const tasks = read(document, state => snapshot(state.tasks));
|
|
@@ -70,9 +73,10 @@ Trees may be empty. `tree(field(...))` requires an own `value` on every existing
|
|
|
70
73
|
node; `tree(optional(field(...)))` explicitly permits missing node payloads, while
|
|
71
74
|
`optional(tree(...))` independently permits the whole tree member to be absent.
|
|
72
75
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
+
Function validators are synchronous predicates/assertions: `true` or `undefined`
|
|
77
|
+
succeeds, `false` rejects, and assertions may throw. Standard Schema v1 validators
|
|
78
|
+
must return the original input by identity; transformed output is rejected. Validators
|
|
79
|
+
must not mutate input. Transform values before entering Doxum. parse(model, unknown) copies validated
|
|
76
80
|
structure and shares readonly payloads; strict parse requires atomic validators.
|
|
77
81
|
Branded map/table keys flow through methods, symbolic paths and impact.
|
|
78
82
|
Path callbacks describe locations, including absent entries, and compile at registration.
|
|
@@ -96,10 +100,16 @@ reversible root reset; remote commits invalidate local history. Observer errors
|
|
|
96
100
|
after acceptance.
|
|
97
101
|
|
|
98
102
|
Use `observe(document, path => path.tasks)` for a collection boundary and
|
|
99
|
-
`derive(
|
|
100
|
-
lazy and materialized by one `createProjectionRuntime({ onError })` owner.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
+
`derive({ tasks, filter }, ({ tasks, filter }) => ...)` for pure values. Definitions are
|
|
104
|
+
lazy and materialized by one `createProjectionRuntime({ onError })` owner. When a
|
|
105
|
+
keyed source determines output keys/order, map entries independently with
|
|
106
|
+
`derive.keyed(entries, entry => entry.label)`. Dynamic keyed lookup stays in that
|
|
107
|
+
same derivation through a named dependency object; plain Projection members are
|
|
108
|
+
global dependencies and `{ source, key }` members are per-output-key lookups. The
|
|
109
|
+
selector receives `(entry, key, dependencies)`, while the Runtime owns the
|
|
110
|
+
binding/reverse index and the producer DAG remains explicit. Use isolated
|
|
111
|
+
`doxum/advanced` incremental entry points only for retained state, custom cross-key
|
|
112
|
+
indexes or keyed patches that `derive.keyed` cannot express. See the [projection
|
|
103
113
|
reference](projections.en.md) for lifecycle, draft semantics, selector tracking,
|
|
104
114
|
batching and recovery.
|
|
105
115
|
Tree structure uses the same projection source protocols: observe `path.tree.rootId`
|