doxum 0.1.7 → 0.1.9

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 (70) hide show
  1. package/README.md +217 -296
  2. package/dist/contract-BNStLbSE.d.ts +441 -0
  3. package/dist/contract-CIU5FCC1.d.cts +441 -0
  4. package/dist/driver-BlR81Dqg.js +200 -0
  5. package/dist/driver-BlR81Dqg.js.map +1 -0
  6. package/dist/driver-xOIkwrB8.cjs +241 -0
  7. package/dist/driver-xOIkwrB8.cjs.map +1 -0
  8. package/dist/index.cjs +666 -2222
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +10 -20
  11. package/dist/index.d.ts +10 -18
  12. package/dist/index.js +634 -2189
  13. package/dist/index.js.map +1 -1
  14. package/dist/{integration-CZwCwFBS.cjs → integration-C87tjRop.cjs} +4 -9
  15. package/dist/integration-C87tjRop.cjs.map +1 -0
  16. package/dist/{integration-B56u1l9V.js → integration-D5XCBLJ8.js} +2 -7
  17. package/dist/integration-D5XCBLJ8.js.map +1 -0
  18. package/dist/integration.cjs +11 -11
  19. package/dist/integration.d.cts +10 -4
  20. package/dist/integration.d.ts +10 -4
  21. package/dist/integration.js +4 -3
  22. package/dist/issue-DVaGQGeP.js +576 -0
  23. package/dist/issue-DVaGQGeP.js.map +1 -0
  24. package/dist/issue-DhrNdQNg.cjs +797 -0
  25. package/dist/issue-DhrNdQNg.cjs.map +1 -0
  26. package/dist/local-sync.cjs +67 -39
  27. package/dist/local-sync.cjs.map +1 -1
  28. package/dist/local-sync.d.cts +10 -10
  29. package/dist/local-sync.d.ts +10 -10
  30. package/dist/local-sync.js +66 -38
  31. package/dist/local-sync.js.map +1 -1
  32. package/dist/react.cjs +23 -4
  33. package/dist/react.cjs.map +1 -1
  34. package/dist/react.d.cts +8 -3
  35. package/dist/react.d.ts +8 -3
  36. package/dist/react.js +21 -6
  37. package/dist/react.js.map +1 -1
  38. package/dist/store-1Uob0Ghk.cjs +2750 -0
  39. package/dist/store-1Uob0Ghk.cjs.map +1 -0
  40. package/dist/store-CD0KdGsq.d.cts +262 -0
  41. package/dist/store-D7QH6Rzw.js +2511 -0
  42. package/dist/store-D7QH6Rzw.js.map +1 -0
  43. package/dist/store-cp5CpfCy.d.ts +262 -0
  44. package/package.json +1 -1
  45. package/skills/doxum-runtime/SKILL.md +26 -44
  46. package/skills/doxum-runtime/references/guide.en.md +95 -359
  47. package/skills/doxum-runtime/references/guide.zh-CN.md +80 -285
  48. package/skills/doxum-runtime/references/invariants.en.md +42 -165
  49. package/skills/doxum-runtime/references/invariants.zh-CN.md +34 -97
  50. package/skills/doxum-runtime/references/patterns.en.md +73 -225
  51. package/skills/doxum-runtime/references/patterns.zh-CN.md +73 -179
  52. package/dist/chunk-pbuEa-1d.js +0 -13
  53. package/dist/contract-DNZ4D53r.d.ts +0 -563
  54. package/dist/contract-j3SLGAwh.d.cts +0 -563
  55. package/dist/driver-CNxqMVFH.cjs +0 -69
  56. package/dist/driver-CNxqMVFH.cjs.map +0 -1
  57. package/dist/driver-CbzfW5MR.js +0 -46
  58. package/dist/driver-CbzfW5MR.js.map +0 -1
  59. package/dist/integration-B56u1l9V.js.map +0 -1
  60. package/dist/integration-CZwCwFBS.cjs.map +0 -1
  61. package/dist/ownership-CY0nPXGF.cjs +0 -304
  62. package/dist/ownership-CY0nPXGF.cjs.map +0 -1
  63. package/dist/ownership-CduRygE7.js +0 -245
  64. package/dist/ownership-CduRygE7.js.map +0 -1
  65. package/dist/runtime-0mOFbe_H.cjs +0 -2439
  66. package/dist/runtime-0mOFbe_H.cjs.map +0 -1
  67. package/dist/runtime-B22tuj9A.d.cts +0 -150
  68. package/dist/runtime-BFmhzPpZ.d.ts +0 -150
  69. package/dist/runtime-DF1q9Gje.js +0 -2129
  70. package/dist/runtime-DF1q9Gje.js.map +0 -1
@@ -1,209 +1,103 @@
1
- # Doxum 模式参考
1
+ # Doxum 常见模式
2
2
 
3
- 实现 Doxum 应用代码时阅读本页。它按能保持 runtime 模型正确的决策组织,而不是机械穷举所有导出符号。
4
-
5
- ## 按修改语义建模数据
6
-
7
- | 需求 | Schema node | Canonical value | Writer 行为 |
8
- | -------------------- | -------------------------- | ------------------- | -------------------------------------------- |
9
- | 一个标量或不可变叶子 | `field<T>()` | `T` | `set`;optional field 还可 `clear` |
10
- | 命名的嵌套字段 | `object({ ... })` | object | 子 writer |
11
- | 带 tag 的结构分支 | `variant('kind', { ... })` | tagged object | `replace` 完整分支值 |
12
- | 有序实体 | `table(entity)` | `{ ids, byId }` | `create`、`item`、`remove`、`move` |
13
- | 无序实体 | `map(entity)` | id record | `create`、`item`、`remove` |
14
- | 稀疏标量字典 | `dict<Key, T>()` | partial record | `set`、`delete`、`replace` |
15
- | 有序标量/结构条目 | `list({ keyOf })` | array | `insert`、`move`、`remove`、`replace` |
16
- | 单根层级 | `tree<T>()` | `{ rootId, nodes }` | `insert`、`move`、`remove`、`set`、`replace` |
17
-
18
- 当顺序对产品可见时使用 table。不要用 map 加另一份 ids array 表示顺序:这会产生两套 mutation protocol 和两个顺序来源。list 只适合每个条目都有稳定、唯一应用 key 的情形;不能把当前 index 当作 `keyOf`。
19
-
20
- table/map 的 entry reader 与 writer 直接由 entry schema node 推导。结构化 entry 会保留
21
- 自己的 tree、list、dict、variant 等专用 access,而不会从运行时 value 反推成普通对象或
22
- 产生 value union。optional field 以及 optional 的 variant、dict、list、tree 结构叶子额外
23
- 提供 `clear()`,并可在缺失状态通过 `replace()` 初始化;object、table、map 仍使用各自的
24
- 子 writer 或集合操作。
3
+ ## 原子值与结构
25
4
 
26
5
  ```ts
27
- import { field, list, map, object, schema, table, tree } from 'doxum';
28
-
29
- type Tag = { id: string; name: string };
30
-
31
- const note = object({ body: field<string>() });
32
- const documentSchema = schema({
33
- notes: table(note),
34
- tags: list<Tag>({ keyOf: tag => tag.id }),
35
- collaborators: map(object({ name: field<string>() })),
36
- outline: tree<{ title: string }>(),
6
+ const point = object({ x: field<number>(), y: field<number>() });
7
+ const model = object({
8
+ position: point,
9
+ stroke: field<readonly { x: number; y: number }[]>(),
10
+ rows: list(field<{ id: string; label: string }>(), { keyOf: row => row.id }),
37
11
  });
38
12
  ```
39
13
 
40
- ## 本地领域命令:读取、校验、写入
41
-
42
- 所有修改要么全部成功、要么全部失败时,将领域命令放进同一个 transaction。业务规则依赖当前状态时,先读取再产生 operation;阻断型业务规则调用 `tx.reject`。
43
-
44
- ```ts
45
- function completeTask(id: string) {
46
- return runtime.update(tx => {
47
- const task = tx.read.tasks.get(id);
48
- if (!task) {
49
- tx.reject({
50
- code: 'task-not-found',
51
- message: `Task '${id}' does not exist.`,
52
- address: ['tasks', id],
53
- });
54
- }
55
- if (task.completed.get()) return { changed: false };
56
-
57
- tx.write.tasks.item(id).completed.set(true);
58
- tx.report({
59
- code: 'task-completed',
60
- message: 'Task marked complete.',
61
- address: ['tasks', id],
62
- });
63
- return { changed: true };
64
- });
65
- }
66
- ```
67
-
68
- `tx.report` 不会拒绝 transaction,适合 warning、审计反馈或伴随合法 commit 的应用消息。`tx.reject` 会中止 transaction,并让外层调用者得到 rejected result。两者都不能替代 malformed operation 的处理:引擎失败由 Doxum 提供的 `MutationIssue` 表示。
69
-
70
- ## 在一个边界应用外部 operation
71
-
72
- 将序列化、授权、网络排序和冲突策略集中在应用 adapter 中。adapter 判断一个 batch 可以应用后,再把完整 batch 交给 Doxum。
73
-
74
- ```ts
75
- async function receiveRemote(batch: unknown) {
76
- // 在这里执行认证、排序、去重和冲突策略。
77
- const result = runtime.apply(batch, {
78
- source: 'remote',
79
- history: false,
80
- });
81
-
82
- if (result.status === 'rejected') {
83
- logRejectedOperations(result.issues);
84
- return;
85
- }
86
- if (result.status === 'committed') reportObserverErrors(result.observerErrors);
87
- }
88
- ```
89
-
90
- 上面的断言只应存在于真正接受未知运行时输入的动态边界;不要把 operation type 放宽到整个应用。Doxum 仍会在发布 commit 前校验 malformed envelope 与语义错误。
91
-
92
- `replace` 只用于新的、可信的 canonical snapshot。它会产生 reset impact 并使 local history 失效,不是表达微小变更的便捷写法。
93
-
94
- ## 用 Anchor 放置有序条目
95
-
96
- table 与 list 共用同一套 `DocumentAnchor` 词汇:
14
+ position.x 细粒度赋值;stroke 整体替换;rows.set(key, value) 按稳定键替换项。
97
15
 
98
- ```ts
99
- {
100
- at: 'start';
101
- }
102
- {
103
- at: 'end';
104
- }
105
- {
106
- before: 'other-id';
107
- }
108
- {
109
- after: 'other-id';
110
- }
111
- ```
112
-
113
- 不要在应用代码中计算 index,而应使用 anchor。它表达领域位置,让 Doxum 校验缺失的引用,并让 table、list 与 operation replay 语义保持一致。
16
+ ## 含集合的整体替换
114
17
 
115
18
  ```ts
116
- runtime.update(tx => {
117
- tx.write.tasks.create({ id: 'review', value: newTask }, { before: 'publish' });
118
- tx.write.tags.insert({ id: 'urgent', name: 'Urgent' }, { at: 'start' });
119
- tx.write.tags.move('urgent', { after: 'planning' });
19
+ const model = object({
20
+ entries: map(object({ rows: table(object({ title: field<string>() })) })),
21
+ });
22
+ const document = createDocument({ schema: model, initial: { entries: {} } });
23
+ document.update(draft => {
24
+ assign(draft.entries, 'a', { rows: { ids: ['x'], byId: { x: { title: 'First' } } } });
25
+ draft.entries.a!.rows.get('x')!.title = 'Updated';
120
26
  });
121
27
  ```
122
28
 
123
- ## 将 tree 视为一个整体不变量
29
+ TypeScript 映射属性不能分别指定读写类型。assign 校验对应 Infer 数据,
30
+ 经同一 mutation session 修改。
124
31
 
125
- Doxum tree 要么为空,要么存在唯一且连通的 root。`nodes` 必须维护双向 parent/child 关系、无重复 child、全量可达与无环。root replace 会校验完整 snapshot;本地 writer 会以增量方式维持该不变量。
32
+ ## 领域键
126
33
 
127
34
  ```ts
128
- runtime.update(tx => {
129
- tx.write.outline.insert('root', { title: 'Project' });
130
- tx.write.outline.insert('plan', { title: 'Plan' }, { parentId: 'root' });
131
- tx.write.outline.move('plan', { parentId: 'root', index: 0 });
132
- tx.write.outline.set('plan', { title: 'Plan release' });
133
- });
35
+ type PersonId = string & { readonly __person: unique symbol };
36
+ const personId = (value: unknown): PersonId => {
37
+ if (typeof value !== 'string' || !value.startsWith('person:')) throw new Error('Person ID');
38
+ return value as PersonId;
39
+ };
40
+ const text = (value: unknown): string => {
41
+ if (typeof value !== 'string') throw new Error('String required');
42
+ return value;
43
+ };
44
+ const model = object({ people: map(object({ name: field(text) }), { key: personId }) });
45
+ const initial = parse(model, { people: { 'person:1': { name: 'Ada' } } });
134
46
  ```
135
47
 
136
- 只有在 tree 为空时,才能不传 `parentId` 创建 root。root 已存在后,每个新节点都必须有已存在的 parent。不要将非 root move 到 `undefined`,不要重新挂载 root,也不要在经过校验的 `tree.replace` 或 runtime `replace` snapshot 之外直接编辑 `{ rootId, nodes }`。
48
+ 品牌类型贯穿索引、table 方法、符号路径和 collection impact。
137
49
 
138
- ## 通过领域 target select 与 subscribe
50
+ ## 顺序、History 与重放
139
51
 
140
- 从所属 schema 创建 selector 一次。这为 subscription 与 impact 解释建立稳定的公共契约。
141
-
142
- ```ts
143
- const notes = documentSchema.collection(path => path.notes);
144
- const body = documentSchema.value(path => path.notes.item('a').body);
145
-
146
- const unsubscribe = runtime.subscribe([notes, body], commit => {
147
- if (commit.impact.affects(body)) refreshTitleUI();
148
-
149
- const change = commit.impact.collection(notes);
150
- if (change.kind === 'incremental' && change.updated.has('a')) refreshRow('a');
151
- });
152
- ```
52
+ table.create/remove/move 使用 { at: 'start' } 或 { before: id } 等 anchor。
53
+ tree.insert/move 接收 { parentId, index },index 表示移除后的最终位置,
54
+ 不能直接修改拓扑记录。
153
55
 
154
- 只有需要将两个 impact target 当作值比较时,使用 `target.same(left, right)`。应使用 `target.address`、`target.id`、`target.belongs` 与 `target.bucket`,不要在 framework adapter 或局部 helper 中复制它们的解释逻辑。
56
+ document.history.group() 开始分组,end() 分组已完成提交,cancel() 恢复起点;
57
+ undo/redo 原子重放完整 ChangeSet。外部 apply 必须提供 expectedRevision,
58
+ 适配器还须校验传输顺序。remote commit 使本地 history 失效;
59
+ 本地 revision 不是分布式时钟。
155
60
 
156
- ## 构建显式派生图
61
+ 输入的成员变化共享所属容器地址:
157
62
 
158
63
  ```ts
159
- const projection = createProjectionRuntime({ onError: error => console.error(error) });
160
- const document = projection.document(runtime);
161
- const notes = document.collection(path => path.notes);
162
- const noteSummaries = projection.map(
163
- notes,
164
- (id, note) => ({ id, preview: note.body.get().slice(0, 80) }),
165
- { isEqual: (a, b) => a.id === b.id && a.preview === b.preview }
64
+ document.apply(
65
+ {
66
+ changes: [
67
+ {
68
+ kind: 'members',
69
+ at: ['tasks', 'a'],
70
+ members: [
71
+ { key: 'complete', kind: 'updated', before: false, after: true },
72
+ { key: 'title', kind: 'updated', before: 'First', after: 'Done' },
73
+ ],
74
+ },
75
+ ],
76
+ },
77
+ { expectedRevision: document.revision() }
166
78
  );
167
- const noteCount = projection.value({ notes }, ({ notes }) => notes.read.ids().length);
168
79
  ```
169
80
 
170
- map 用于 document 或投影集合中保留 key 和顺序的一对一映射。自定义索引使用 `projection.collection<Item>()(spec)`,声明 sources,并通过 scoped writer.set/remove/order/replace 更新。previous 与 next 分别读取上次输出和本轮暂存结果。
171
-
172
- document collection 提供整个批次的 reset、candidates.keys 和 candidates.orderDirty;需要更多细节时仍可读取原生 commits/impact 或上游 collection change。候选 key 包括净零变化,必须读取最终状态决定输出。依赖显式固定,不通过 reader 自动学习。Doxum 根据 equality 决定最终 change。
173
-
174
- update 失败后丢弃实例并限一次全新 build 恢复;持续失败阻断下游,独立分支继续。rebuild 经过同一调度图,不手动 emit。projection 随 owner dispose。使用 projection.batch 包住首次 document commit 之前到 editor cleanup 结束的完整同步动作;batch 内派生读取保持上次发布状态。
175
-
176
- ## 在 React 中绑定读模型
177
-
178
- 直接读取 document 时使用 `useDocumentSelector`。它会跟踪每次 selector 执行中读取的 path 和 collection entry,包括动态依赖。
81
+ 每个容器只能有一组。added 仅携带 after,removed 仅携带 before;存在的 undefined
82
+ 仍是一个值。不要展开成旧 value envelope,也不要将分组地址当作整个容器失效。
179
83
 
180
- ```tsx
181
- function Note({ id }: { id: string }) {
182
- const body = useDocumentSelector(runtime, read => read.notes.get(id)?.body.get());
183
- return <p>{body ?? 'Missing note'}</p>;
184
- }
185
- ```
84
+ ## 投影与 React
186
85
 
187
- 已有 read model 则使用更窄的 hook:
188
-
189
- ```tsx
190
- function NoteRow({ id }: { id: string }) {
191
- const summary = useReadable(noteSummaries.item(id));
192
- return <p>{summary?.preview}</p>;
193
- }
194
-
195
- function UndoButton() {
196
- const history = useHistory(runtime.history);
197
- return (
198
- <button disabled={history.undoDepth === 0} onClick={() => history.undo()}>
199
- Undo
200
- </button>
201
- );
202
- }
86
+ ```ts
87
+ const titles = project(
88
+ document,
89
+ path => path.tasks,
90
+ (_id, task) => task.title
91
+ );
92
+ const total = project({ titles }, ({ titles }) => titles.ids().length);
93
+ const zoom = input(1);
94
+ const scaled = project({ total, zoom }, ({ total, zoom }) => total * zoom);
95
+ const store = createProjectionStore({ onError: console.error });
96
+ store.get(scaled);
203
97
  ```
204
98
 
205
- selector 应保持纯粹:读取 Doxum state 并计算结果即可;不要在 selector 执行中写入、手动订阅或发起 I/O。
206
-
207
- ## 测试真正发生变化的行为
208
-
209
- mutation 改动应覆盖成功 commit、部分 batch 的 rejected rollback、inverse/history 结果,以及相关 impact 或 subscription 行为。projection 改动应覆盖无关 commit、动态依赖、应当稳定的引用和 dispose。大型 table、list 与 tree 应增加回归测试,证明无关数据没有被复制或遍历。
99
+ 投影定义使用 `useProjection` 和一个 store,document.history 等既有 Readable
100
+ 使用 `useReadable` 或 `useHistory`。自定义集合 processor 通过
101
+ writer.set/remove/order/replace 暂存输出,
102
+ previous/next 读取只在作用域内有效。candidates 汇总整个 batch,以最终状态派生输出。
103
+ React 追踪实际读取,但 processor 依赖仍显式声明。
@@ -1,13 +0,0 @@
1
- //#region \0rolldown/runtime.js
2
- var __defProp = Object.defineProperty;
3
- var __exportAll = (all, no_symbols) => {
4
- let target = {};
5
- for (var name in all) __defProp(target, name, {
6
- get: all[name],
7
- enumerable: true
8
- });
9
- if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
10
- return target;
11
- };
12
- //#endregion
13
- export { __exportAll as t };