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.
Files changed (66) hide show
  1. package/README.md +208 -102
  2. package/dist/advanced.cjs +237 -2
  3. package/dist/advanced.cjs.map +1 -0
  4. package/dist/advanced.d.cts +90 -2
  5. package/dist/advanced.d.ts +90 -2
  6. package/dist/advanced.js +236 -1
  7. package/dist/advanced.js.map +1 -0
  8. package/dist/{contract-ULPQmiEs.d.ts → contract-CNR6BS75.d.cts} +216 -30
  9. package/dist/{contract-SYJFP0lM.d.cts → contract-COgxO5sv.d.ts} +216 -30
  10. package/dist/definition-CDOuvb5C.d.cts +111 -0
  11. package/dist/definition-xgk3YNBp.d.ts +111 -0
  12. package/dist/{driver-yFfvDMX5.js → driver-B5vdPohL.js} +59 -3
  13. package/dist/driver-B5vdPohL.js.map +1 -0
  14. package/dist/{driver-BD3DQOW6.cjs → driver-BL91gn7_.cjs} +107 -9
  15. package/dist/driver-BL91gn7_.cjs.map +1 -0
  16. package/dist/index.cjs +1637 -703
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +65 -33
  19. package/dist/index.d.ts +65 -33
  20. package/dist/index.js +1432 -497
  21. package/dist/index.js.map +1 -1
  22. package/dist/local-sync.cjs +69 -103
  23. package/dist/local-sync.cjs.map +1 -1
  24. package/dist/local-sync.d.cts +14 -26
  25. package/dist/local-sync.d.ts +14 -26
  26. package/dist/local-sync.js +68 -96
  27. package/dist/local-sync.js.map +1 -1
  28. package/dist/react.cjs +4 -5
  29. package/dist/react.cjs.map +1 -1
  30. package/dist/react.d.cts +6 -8
  31. package/dist/react.d.ts +6 -8
  32. package/dist/react.js +4 -5
  33. package/dist/react.js.map +1 -1
  34. package/dist/record-B2yRhYsc.js +28 -0
  35. package/dist/record-B2yRhYsc.js.map +1 -0
  36. package/dist/record-ChYUi8PP.cjs +45 -0
  37. package/dist/record-ChYUi8PP.cjs.map +1 -0
  38. package/dist/scheduler--137L3j-.js +500 -0
  39. package/dist/scheduler--137L3j-.js.map +1 -0
  40. package/dist/scheduler-D4VthaaK.cjs +589 -0
  41. package/dist/scheduler-D4VthaaK.cjs.map +1 -0
  42. package/package.json +6 -3
  43. package/skills/doxum-runtime/SKILL.md +32 -7
  44. package/skills/doxum-runtime/agents/openai.yaml +1 -1
  45. package/skills/doxum-runtime/references/api.en.md +249 -0
  46. package/skills/doxum-runtime/references/api.zh-CN.md +242 -0
  47. package/skills/doxum-runtime/references/guide.en.md +18 -8
  48. package/skills/doxum-runtime/references/guide.zh-CN.md +14 -6
  49. package/skills/doxum-runtime/references/invariants.en.md +29 -5
  50. package/skills/doxum-runtime/references/invariants.zh-CN.md +24 -6
  51. package/skills/doxum-runtime/references/patterns.en.md +78 -18
  52. package/skills/doxum-runtime/references/patterns.zh-CN.md +76 -18
  53. package/skills/doxum-runtime/references/projections.en.md +209 -119
  54. package/skills/doxum-runtime/references/projections.zh-CN.md +200 -101
  55. package/dist/advanced-CLJCWkHa.d.cts +0 -176
  56. package/dist/advanced-CRNHfJBu.js +0 -1224
  57. package/dist/advanced-CRNHfJBu.js.map +0 -1
  58. package/dist/advanced-Cle5g3B4.cjs +0 -1517
  59. package/dist/advanced-Cle5g3B4.cjs.map +0 -1
  60. package/dist/advanced-d6J61er9.d.ts +0 -176
  61. package/dist/context-BcLKGfq8.cjs +0 -149
  62. package/dist/context-BcLKGfq8.cjs.map +0 -1
  63. package/dist/context-DFEaDUv5.js +0 -84
  64. package/dist/context-DFEaDUv5.js.map +0 -1
  65. package/dist/driver-BD3DQOW6.cjs.map +0 -1
  66. 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.23",
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 doxum and doxum/react for schemas, scoped draft updates, ChangeSets, history, projections, subscriptions and React integration.'
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
- Use tuple `derive` for pure values. Advanced `incremental` processors declare every
42
- source, own any forward/reverse dependency indexes, derive from final batched source
43
- state, and recover from resets internally. Core projections never track reads
44
- automatically; React selectors do through `ProjectionProvider` and
45
- `useProjection(projection, selector)`.
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 derived cache.
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, modify, and review Doxum runtime code safely.'
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
- Pure synchronous functions and Standard Schema v1 validators receive original input.
74
- They must not mutate it; successful output is ignored, with no copy or deep conversion
75
- check. Transform values before entering Doxum. parse(model, unknown) copies validated
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([tasks, filter], (tasks, filter) => ...)` for pure values. Definitions are
100
- lazy and materialized by one `createProjectionRuntime({ onError })` owner. Sources
101
- remain explicit. Retained state, reverse indexes and keyed output patches belong to
102
- the isolated `doxum/advanced` incremental entry points. See the [projection
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`