doxum 0.1.21 → 0.1.23

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 (72) hide show
  1. package/README.md +35 -7
  2. package/dist/{advanced-FgdTwu5b.d.ts → advanced-CLJCWkHa.d.cts} +3 -3
  3. package/dist/{advanced-COFJtWMT.js → advanced-CRNHfJBu.js} +482 -446
  4. package/dist/advanced-CRNHfJBu.js.map +1 -0
  5. package/dist/{advanced-BRL5pN0y.cjs → advanced-Cle5g3B4.cjs} +615 -459
  6. package/dist/advanced-Cle5g3B4.cjs.map +1 -0
  7. package/dist/{advanced-DfKc7TZR.d.cts → advanced-d6J61er9.d.ts} +3 -3
  8. package/dist/advanced.cjs +1 -1
  9. package/dist/advanced.d.cts +1 -1
  10. package/dist/advanced.d.ts +1 -1
  11. package/dist/advanced.js +1 -1
  12. package/dist/context-BcLKGfq8.cjs +149 -0
  13. package/dist/context-BcLKGfq8.cjs.map +1 -0
  14. package/dist/context-DFEaDUv5.js +84 -0
  15. package/dist/context-DFEaDUv5.js.map +1 -0
  16. package/dist/{contract-CdmDHRUq.d.cts → contract-SYJFP0lM.d.cts} +62 -88
  17. package/dist/{contract-KG5XnQZt.d.ts → contract-ULPQmiEs.d.ts} +62 -88
  18. package/dist/driver-BD3DQOW6.cjs +382 -0
  19. package/dist/driver-BD3DQOW6.cjs.map +1 -0
  20. package/dist/{driver-CLYJOGDQ.js → driver-yFfvDMX5.js} +155 -26
  21. package/dist/driver-yFfvDMX5.js.map +1 -0
  22. package/dist/index.cjs +2370 -406
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.cts +6 -5
  25. package/dist/index.d.ts +6 -5
  26. package/dist/index.js +2293 -330
  27. package/dist/index.js.map +1 -1
  28. package/dist/local-sync.cjs +5 -5
  29. package/dist/local-sync.cjs.map +1 -1
  30. package/dist/local-sync.d.cts +1 -1
  31. package/dist/local-sync.d.ts +1 -1
  32. package/dist/local-sync.js +2 -2
  33. package/dist/local-sync.js.map +1 -1
  34. package/dist/react.cjs +2 -69
  35. package/dist/react.cjs.map +1 -1
  36. package/dist/react.d.cts +0 -1
  37. package/dist/react.d.ts +1 -2
  38. package/dist/react.js +3 -70
  39. package/dist/react.js.map +1 -1
  40. package/package.json +1 -6
  41. package/skills/doxum-runtime/SKILL.md +7 -1
  42. package/skills/doxum-runtime/references/guide.en.md +17 -6
  43. package/skills/doxum-runtime/references/guide.zh-CN.md +15 -5
  44. package/skills/doxum-runtime/references/invariants.en.md +5 -3
  45. package/skills/doxum-runtime/references/invariants.zh-CN.md +4 -3
  46. package/skills/doxum-runtime/references/projections.en.md +5 -0
  47. package/skills/doxum-runtime/references/projections.zh-CN.md +5 -0
  48. package/dist/access-BeSV5ve-.js +0 -816
  49. package/dist/access-BeSV5ve-.js.map +0 -1
  50. package/dist/access-Bienf28h.cjs +0 -911
  51. package/dist/access-Bienf28h.cjs.map +0 -1
  52. package/dist/advanced-BRL5pN0y.cjs.map +0 -1
  53. package/dist/advanced-COFJtWMT.js.map +0 -1
  54. package/dist/driver-CLYJOGDQ.js.map +0 -1
  55. package/dist/driver-Djr3BVvo.cjs +0 -241
  56. package/dist/driver-Djr3BVvo.cjs.map +0 -1
  57. package/dist/integration-C4Isp8lJ.js +0 -26
  58. package/dist/integration-C4Isp8lJ.js.map +0 -1
  59. package/dist/integration-Z6MsfFI7.cjs +0 -31
  60. package/dist/integration-Z6MsfFI7.cjs.map +0 -1
  61. package/dist/integration.cjs +0 -12
  62. package/dist/integration.d.cts +0 -17
  63. package/dist/integration.d.ts +0 -17
  64. package/dist/integration.js +0 -4
  65. package/dist/issue-DjK1xp5K.cjs +0 -881
  66. package/dist/issue-DjK1xp5K.cjs.map +0 -1
  67. package/dist/issue-W0hT8u8D.js +0 -642
  68. package/dist/issue-W0hT8u8D.js.map +0 -1
  69. package/dist/notification-Bkjau6B6.js +0 -232
  70. package/dist/notification-Bkjau6B6.js.map +0 -1
  71. package/dist/notification-DJfouMEb.cjs +0 -309
  72. package/dist/notification-DJfouMEb.cjs.map +0 -1
package/dist/react.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import * as _$react from "react";
2
1
  import { DocumentReadable, DocumentSelector, HistoryState, Input, LocalHistory, ObjectNode, OperationResult, Projection, ProjectionRuntime, ProjectionScope, Readable } from "doxum";
2
+ import * as _$react from "react";
3
3
 
4
4
  //#region react/src/hooks.d.ts
5
5
  declare const ProjectionProvider: _$react.Provider<ProjectionRuntime | ProjectionScope | undefined>;
@@ -8,7 +8,6 @@ declare function useProjection<T, R>(projection: Projection<T, unknown>, selecto
8
8
  declare function useInput<T>(input: Input<T>): readonly [T, (value: T) => void];
9
9
  type DocumentSelectorOptions<TResult> = {
10
10
  readonly isEqual?: (previous: TResult, next: TResult) => boolean;
11
- readonly server?: () => TResult;
12
11
  };
13
12
  declare function useDocumentSelector<TSchema extends ObjectNode, TResult>(runtime: DocumentReadable<TSchema>, selector: DocumentSelector<TSchema, TResult>, options?: DocumentSelectorOptions<TResult>): TResult;
14
13
  declare function useReadable<T>(readable: Readable<T>): T;
package/dist/react.js CHANGED
@@ -1,6 +1,5 @@
1
- import { c as subscribeDependencies, p as same } from "./notification-Bkjau6B6.js";
2
- import { t as track } from "./integration-C4Isp8lJ.js";
3
- import { createContext, useCallback, useContext, useMemo, useRef, useSyncExternalStore } from "react";
1
+ import { select } from "doxum";
2
+ import { createContext, useCallback, useContext, useMemo, useSyncExternalStore } from "react";
4
3
  //#region react/src/hooks.ts
5
4
  const ProjectionContext = createContext(void 0);
6
5
  const ProjectionProvider = ProjectionContext.Provider;
@@ -22,78 +21,12 @@ function useInput(input) {
22
21
  return useMemo(() => [value, set], [set, value]);
23
22
  }
24
23
  const objectIs = (previous, next) => Object.is(previous, next);
25
- const sameTargets = (left, right) => {
26
- if (left.length !== right.length) return false;
27
- for (let index = 0; index < left.length; index += 1) if (!same(left[index], right[index])) return false;
28
- return true;
29
- };
30
24
  function useDocumentSelector(runtime, selector, options) {
31
25
  const equality = options?.isEqual ?? objectIs;
32
- const cache = useRef(void 0);
33
- const serverCache = useRef(void 0);
34
- const read = useMemo(() => {
35
- let snapshot;
36
- return () => {
37
- const revision = runtime.revision();
38
- if (snapshot?.revision === revision) return snapshot.value;
39
- const selection = track(runtime, selector);
40
- const previous = cache.current;
41
- const value = previous && previous.runtime === runtime && equality(previous.value, selection.value) ? previous.value : selection.value;
42
- cache.current = {
43
- runtime,
44
- value,
45
- targets: selection.targets
46
- };
47
- snapshot = {
48
- revision,
49
- value
50
- };
51
- return value;
52
- };
53
- }, [
26
+ return useReadable(useMemo(() => select(runtime, selector, equality), [
54
27
  equality,
55
28
  runtime,
56
29
  selector
57
- ]);
58
- const subscribe = useCallback((listener) => {
59
- read();
60
- let targets = cache.current?.targets ?? [];
61
- let unsubscribe = () => void 0;
62
- const install = () => {
63
- if (targets.length === 0) return () => void 0;
64
- return subscribeDependencies(runtime, targets, onCommit);
65
- };
66
- const onCommit = () => {
67
- const previous = cache.current;
68
- const next = read();
69
- const nextTargets = cache.current?.targets ?? [];
70
- if (!sameTargets(targets, nextTargets)) {
71
- unsubscribe();
72
- targets = nextTargets;
73
- unsubscribe = install();
74
- }
75
- if (previous && !Object.is(previous.value, next)) listener();
76
- };
77
- unsubscribe = install();
78
- return () => unsubscribe();
79
- }, [read, runtime]);
80
- const server = options?.server;
81
- return useSyncExternalStore(subscribe, read, useCallback(() => {
82
- const previous = serverCache.current;
83
- if (previous && previous.source === server && previous.runtime === runtime && previous.selector === selector) return previous.value;
84
- const value = server ? server() : read();
85
- serverCache.current = {
86
- runtime,
87
- selector,
88
- source: server,
89
- value
90
- };
91
- return value;
92
- }, [
93
- read,
94
- server,
95
- runtime,
96
- selector
97
30
  ]));
98
31
  }
99
32
  function useReadable(readable) {
package/dist/react.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"react.js","names":["sameTarget"],"sources":["../react/src/hooks.ts"],"sourcesContent":["import type {\n DocumentReadable,\n ObjectNode,\n DocumentSelector,\n HistoryState,\n LocalHistory,\n OperationResult,\n Readable,\n ProjectionRuntime,\n ProjectionScope,\n Projection,\n Input,\n} from 'doxum';\nimport { track, subscribeDependencies, sameTarget, type ImpactTarget } from 'doxum/integration';\nimport {\n createContext,\n useCallback,\n useContext,\n useMemo,\n useRef,\n useSyncExternalStore,\n} from 'react';\n\nconst ProjectionContext = createContext<ProjectionRuntime | ProjectionScope | undefined>(undefined);\nexport const ProjectionProvider = ProjectionContext.Provider;\n\nexport function useProjection<T>(projection: Projection<T, unknown>): T;\nexport function useProjection<T, R>(\n projection: Projection<T, unknown>,\n selector: (value: T) => R,\n equality?: (previous: R, next: R) => boolean\n): R;\nexport function useProjection<T, R>(\n projection: Projection<T, unknown>,\n selector?: (value: T) => R,\n equality: (previous: R, next: R) => boolean = Object.is\n): T | R {\n const context = useContext(ProjectionContext);\n const owner = context;\n if (!owner) throw new Error('ProjectionRuntime is required.');\n const readable = useMemo(\n () => (selector ? owner.readable(projection, selector, equality) : owner.readable(projection)),\n [equality, owner, projection, selector]\n );\n return useReadable(readable as Readable<T | R>);\n}\n\nexport function useInput<T>(input: Input<T>): readonly [T, (value: T) => void] {\n const context = useContext(ProjectionContext);\n const owner = context;\n if (!owner) throw new Error('ProjectionRuntime is required.');\n const value = useProjection(input);\n const set = useCallback((next: T) => owner.set(input, next), [input, owner]);\n return useMemo(() => [value, set] as const, [set, value]);\n}\n\nexport type DocumentSelectorOptions<TResult> = {\n readonly isEqual?: (previous: TResult, next: TResult) => boolean;\n readonly server?: () => TResult;\n};\n\ntype SelectorCache<TSchema extends ObjectNode, TResult> = {\n readonly runtime: DocumentReadable<TSchema>;\n readonly value: TResult;\n readonly targets: readonly ImpactTarget<unknown>[];\n};\n\nconst objectIs = <T>(previous: T, next: T): boolean => Object.is(previous, next);\nconst sameTargets = (\n left: readonly ImpactTarget<unknown>[],\n right: readonly ImpactTarget<unknown>[]\n): boolean => {\n if (left.length !== right.length) return false;\n for (let index = 0; index < left.length; index += 1)\n if (!sameTarget(left[index], right[index])) return false;\n return true;\n};\n\nexport function useDocumentSelector<TSchema extends ObjectNode, TResult>(\n runtime: DocumentReadable<TSchema>,\n selector: DocumentSelector<TSchema, TResult>,\n options?: DocumentSelectorOptions<TResult>\n): TResult {\n const equality = options?.isEqual ?? objectIs;\n const cache = useRef<SelectorCache<TSchema, TResult> | undefined>(undefined);\n const serverCache = useRef<\n | {\n readonly runtime: DocumentReadable<TSchema>;\n readonly selector: DocumentSelector<TSchema, TResult>;\n readonly source: (() => TResult) | undefined;\n readonly value: TResult;\n }\n | undefined\n >(undefined);\n\n const read = useMemo(() => {\n // Each selector closure owns its revision cache, including inline selectors\n // that allocate arrays or objects. Equality only compares distinct snapshots.\n let snapshot: { readonly revision: number; readonly value: TResult } | undefined;\n return (): TResult => {\n const revision = runtime.revision();\n if (snapshot?.revision === revision) return snapshot.value;\n const selection = track(runtime, selector);\n const previous = cache.current;\n const value =\n previous && previous.runtime === runtime && equality(previous.value, selection.value)\n ? previous.value\n : selection.value;\n cache.current = { runtime, value, targets: selection.targets };\n snapshot = { revision, value };\n return value;\n };\n }, [equality, runtime, selector]);\n\n const subscribe = useCallback(\n (listener: () => void) => {\n read();\n let targets = cache.current?.targets ?? [];\n let unsubscribe: () => void = () => undefined;\n const install = () => {\n if (targets.length === 0) return () => undefined;\n return subscribeDependencies(runtime, targets, onCommit);\n };\n const onCommit = () => {\n const previous = cache.current;\n const next = read();\n const nextTargets = cache.current?.targets ?? [];\n if (!sameTargets(targets, nextTargets)) {\n unsubscribe();\n targets = nextTargets;\n unsubscribe = install();\n }\n if (previous && !Object.is(previous.value, next)) listener();\n };\n unsubscribe = install();\n return () => unsubscribe();\n },\n [read, runtime]\n );\n\n const server = options?.server;\n const readServer = useCallback((): TResult => {\n const previous = serverCache.current;\n if (\n previous &&\n previous.source === server &&\n previous.runtime === runtime &&\n previous.selector === selector\n )\n return previous.value;\n const value = server ? server() : read();\n serverCache.current = { runtime, selector, source: server, value };\n return value;\n }, [read, server, runtime, selector]);\n\n return useSyncExternalStore(subscribe, read, readServer);\n}\n\nexport function useReadable<T>(readable: Readable<T>): T {\n const subscribe = useCallback((listener: () => void) => readable.subscribe(listener), [readable]);\n const read = useCallback(() => readable.current(), [readable]);\n return useSyncExternalStore(subscribe, read, read);\n}\n\nexport function useHistory<TCommit>(history: LocalHistory<TCommit>): HistoryState & {\n undo(): OperationResult<TCommit>;\n redo(): OperationResult<TCommit>;\n} {\n const subscribe = useCallback((listener: () => void) => history.subscribe(listener), [history]);\n const read = useCallback(() => history.current(), [history]);\n const state = useSyncExternalStore(subscribe, read, read);\n const undo = useCallback(() => history.undo(), [history]);\n const redo = useCallback(() => history.redo(), [history]);\n\n return useMemo(() => ({ ...state, undo, redo }), [redo, state, undo]);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"react.js","names":[],"sources":["../react/src/hooks.ts"],"sourcesContent":["import type {\n DocumentReadable,\n ObjectNode,\n DocumentSelector,\n HistoryState,\n LocalHistory,\n OperationResult,\n Readable,\n ProjectionRuntime,\n ProjectionScope,\n Projection,\n Input,\n} from 'doxum';\nimport { select } from 'doxum';\nimport { createContext, useCallback, useContext, useMemo, useSyncExternalStore } from 'react';\n\nconst ProjectionContext = createContext<ProjectionRuntime | ProjectionScope | undefined>(undefined);\nexport const ProjectionProvider = ProjectionContext.Provider;\n\nexport function useProjection<T>(projection: Projection<T, unknown>): T;\nexport function useProjection<T, R>(\n projection: Projection<T, unknown>,\n selector: (value: T) => R,\n equality?: (previous: R, next: R) => boolean\n): R;\nexport function useProjection<T, R>(\n projection: Projection<T, unknown>,\n selector?: (value: T) => R,\n equality: (previous: R, next: R) => boolean = Object.is\n): T | R {\n const context = useContext(ProjectionContext);\n const owner = context;\n if (!owner) throw new Error('ProjectionRuntime is required.');\n const readable = useMemo(\n () => (selector ? owner.readable(projection, selector, equality) : owner.readable(projection)),\n [equality, owner, projection, selector]\n );\n return useReadable(readable as Readable<T | R>);\n}\n\nexport function useInput<T>(input: Input<T>): readonly [T, (value: T) => void] {\n const context = useContext(ProjectionContext);\n const owner = context;\n if (!owner) throw new Error('ProjectionRuntime is required.');\n const value = useProjection(input);\n const set = useCallback((next: T) => owner.set(input, next), [input, owner]);\n return useMemo(() => [value, set] as const, [set, value]);\n}\n\nexport type DocumentSelectorOptions<TResult> = {\n readonly isEqual?: (previous: TResult, next: TResult) => boolean;\n};\n\nconst objectIs = <T>(previous: T, next: T): boolean => Object.is(previous, next);\n\nexport function useDocumentSelector<TSchema extends ObjectNode, TResult>(\n runtime: DocumentReadable<TSchema>,\n selector: DocumentSelector<TSchema, TResult>,\n options?: DocumentSelectorOptions<TResult>\n): TResult {\n const equality = options?.isEqual ?? objectIs;\n const readable = useMemo(\n () => select(runtime, selector, equality),\n [equality, runtime, selector]\n );\n return useReadable(readable);\n}\n\nexport function useReadable<T>(readable: Readable<T>): T {\n const subscribe = useCallback((listener: () => void) => readable.subscribe(listener), [readable]);\n const read = useCallback(() => readable.current(), [readable]);\n return useSyncExternalStore(subscribe, read, read);\n}\n\nexport function useHistory<TCommit>(history: LocalHistory<TCommit>): HistoryState & {\n undo(): OperationResult<TCommit>;\n redo(): OperationResult<TCommit>;\n} {\n const subscribe = useCallback((listener: () => void) => history.subscribe(listener), [history]);\n const read = useCallback(() => history.current(), [history]);\n const state = useSyncExternalStore(subscribe, read, read);\n const undo = useCallback(() => history.undo(), [history]);\n const redo = useCallback(() => history.redo(), [history]);\n\n return useMemo(() => ({ ...state, undo, redo }), [redo, state, undo]);\n}\n"],"mappings":";;;AAgBA,MAAM,oBAAoB,cAA+D,KAAA,EAAU;AACnG,MAAa,qBAAqB,kBAAkB;AAQpD,SAAgB,cACd,YACA,UACA,WAA8C,OAAO,IAC9C;CAEP,MAAM,QADU,WAAW,kBACN;AACrB,KAAI,CAAC,MAAO,OAAM,IAAI,MAAM,iCAAiC;AAK7D,QAAO,YAJU,cACR,WAAW,MAAM,SAAS,YAAY,UAAU,SAAS,GAAG,MAAM,SAAS,WAAW,EAC7F;EAAC;EAAU;EAAO;EAAY;EAAS,CAEd,CAAoB;;AAGjD,SAAgB,SAAY,OAAmD;CAE7E,MAAM,QADU,WAAW,kBACN;AACrB,KAAI,CAAC,MAAO,OAAM,IAAI,MAAM,iCAAiC;CAC7D,MAAM,QAAQ,cAAc,MAAM;CAClC,MAAM,MAAM,aAAa,SAAY,MAAM,IAAI,OAAO,KAAK,EAAE,CAAC,OAAO,MAAM,CAAC;AAC5E,QAAO,cAAc,CAAC,OAAO,IAAI,EAAW,CAAC,KAAK,MAAM,CAAC;;AAO3D,MAAM,YAAe,UAAa,SAAqB,OAAO,GAAG,UAAU,KAAK;AAEhF,SAAgB,oBACd,SACA,UACA,SACS;CACT,MAAM,WAAW,SAAS,WAAW;AAKrC,QAAO,YAJU,cACT,OAAO,SAAS,UAAU,SAAS,EACzC;EAAC;EAAU;EAAS;EAAS,CAEJ,CAAC;;AAG9B,SAAgB,YAAe,UAA0B;CACvD,MAAM,YAAY,aAAa,aAAyB,SAAS,UAAU,SAAS,EAAE,CAAC,SAAS,CAAC;CACjG,MAAM,OAAO,kBAAkB,SAAS,SAAS,EAAE,CAAC,SAAS,CAAC;AAC9D,QAAO,qBAAqB,WAAW,MAAM,KAAK;;AAGpD,SAAgB,WAAoB,SAGlC;CACA,MAAM,YAAY,aAAa,aAAyB,QAAQ,UAAU,SAAS,EAAE,CAAC,QAAQ,CAAC;CAC/F,MAAM,OAAO,kBAAkB,QAAQ,SAAS,EAAE,CAAC,QAAQ,CAAC;CAC5D,MAAM,QAAQ,qBAAqB,WAAW,MAAM,KAAK;CACzD,MAAM,OAAO,kBAAkB,QAAQ,MAAM,EAAE,CAAC,QAAQ,CAAC;CACzD,MAAM,OAAO,kBAAkB,QAAQ,MAAM,EAAE,CAAC,QAAQ,CAAC;AAEzD,QAAO,eAAe;EAAE,GAAG;EAAO;EAAM;EAAM,GAAG;EAAC;EAAM;EAAO;EAAK,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doxum",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "description": "Doxum is a typed runtime for complex mutable documents.",
@@ -31,11 +31,6 @@
31
31
  "import": "./dist/index.js",
32
32
  "require": "./dist/index.cjs"
33
33
  },
34
- "./integration": {
35
- "types": "./dist/integration.d.ts",
36
- "import": "./dist/integration.js",
37
- "require": "./dist/integration.cjs"
38
- },
39
34
  "./local-sync": {
40
35
  "types": "./dist/local-sync.d.ts",
41
36
  "import": "./dist/local-sync.js",
@@ -24,6 +24,11 @@ Before runtime changes read [invariants](references/invariants.en.md) or
24
24
  object or variant members whose Draft type contains collection tools.
25
25
  - Ordered table/list Drafts use move(key | readonly key[], anchor?) for relative
26
26
  movement and reorder(keys) for an exact full-membership permutation.
27
+ - Trees may be empty. `tree(field(...))` requires every existing node to own `value`;
28
+ use `tree(optional(field(...)))` only when node payload absence is part of the model.
29
+ `optional(tree(...))` controls whole-tree presence independently.
30
+ - Tree projection paths reuse ordinary source protocols: `tree.rootId` is scalar,
31
+ `tree.nodes` is keyed, and `tree.nodes.item(id)` is a single-node value.
27
32
  - Expected business failure throws TransactionRejected. Other exceptions roll back
28
33
  and rethrow unchanged. Callback returns carry business values and notices.
29
34
  - Commits contain final reversible ChangeSets. History and impact share those facts.
@@ -39,7 +44,8 @@ Before runtime changes read [invariants](references/invariants.en.md) or
39
44
  automatically; React selectors do through `ProjectionProvider` and
40
45
  `useProjection(projection, selector)`.
41
46
  - Observer errors leave commits accepted. Do not retry as if they rolled back.
42
- - Core stays framework-neutral; adapters use integration capabilities.
47
+ - Core stays framework-neutral; adapters consume standard `Readable`,
48
+ document `select`, and projection readables without internal target/address protocols.
43
49
  - Local sync owns browser persistence/leadership. Network conflict policy and
44
50
  collaborative undo belong at a separate boundary.
45
51
 
@@ -3,7 +3,7 @@
3
3
  ## Define, Update And Read
4
4
 
5
5
  ```ts
6
- import { createDocument, field, map, object, select, snapshot, type Infer } from 'doxum';
6
+ import { createDocument, field, map, object, read, snapshot, type Infer } from 'doxum';
7
7
 
8
8
  const task = object({ title: field<string>(), done: field<boolean>() });
9
9
  const model = object({ tasks: map(task) });
@@ -18,8 +18,8 @@ document.update(draft => {
18
18
  draft.tasks.put('b', { title: 'Review', done: false });
19
19
  return { warnings: [] };
20
20
  });
21
- const done = select(document, state => state.tasks.get('a')?.done);
22
- const tasks = select(document, state => snapshot(state.tasks));
21
+ const done = read(document, state => state.tasks.get('a')?.done);
22
+ const tasks = read(document, state => snapshot(state.tasks));
23
23
  document.subscribe(
24
24
  path => path.tasks.item('a').done,
25
25
  commit => console.log(commit.changes)
@@ -27,8 +27,8 @@ document.subscribe(
27
27
  ```
28
28
 
29
29
  Root object is definition identity; runtime owns its data/revision. Updates are
30
- synchronous and atomic. Reads see preceding writes. Draft and trusted internal
31
- readWith scopes are borrowed for the synchronous callback and must not escape.
30
+ synchronous and atomic. Reads see preceding writes. Draft and the Read passed to a
31
+ `read` selector are borrowed for the synchronous callback and must not escape.
32
32
  Throw TransactionRejected for expected rejection; ordinary throws
33
33
  restore all work and rethrow unchanged. False/undefined returns are business values.
34
34
 
@@ -66,6 +66,9 @@ present undefined. Variant tags are readonly; change branch by whole replacement
66
66
  For table/list, `move(key | readonly key[], anchor?)` preserves a moved selection's
67
67
  current relative order and resolves the anchor after removal. `reorder(keys)` requires
68
68
  an exact permutation of current membership and changes only order.
69
+ Trees may be empty. `tree(field(...))` requires an own `value` on every existing
70
+ node; `tree(optional(field(...)))` explicitly permits missing node payloads, while
71
+ `optional(tree(...))` independently permits the whole tree member to be absent.
69
72
 
70
73
  Pure synchronous functions and Standard Schema v1 validators receive original input.
71
74
  They must not mutate it; successful output is ignored, with no copy or deep conversion
@@ -73,7 +76,9 @@ check. Transform values before entering Doxum. parse(model, unknown) copies vali
73
76
  structure and shares readonly payloads; strict parse requires atomic validators.
74
77
  Branded map/table keys flow through methods, symbolic paths and impact.
75
78
  Path callbacks describe locations, including absent entries, and compile at registration.
76
- React useDocumentSelector tracks actual reads and changes dependencies when branching.
79
+ Core `select(document, selector, equality?)` tracks actual reads, changes dependencies
80
+ when branching, and returns a standard `Readable`. React `useDocumentSelector`
81
+ only adapts that Core readable to React.
77
82
 
78
83
  ## Changes And Consumers
79
84
 
@@ -97,6 +102,12 @@ remain explicit. Retained state, reverse indexes and keyed output patches belong
97
102
  the isolated `doxum/advanced` incremental entry points. See the [projection
98
103
  reference](projections.en.md) for lifecycle, draft semantics, selector tracking,
99
104
  batching and recovery.
105
+ Tree structure uses the same projection source protocols: observe `path.tree.rootId`
106
+ as a scalar, `path.tree.nodes` as a keyed collection, or
107
+ `path.tree.nodes.item(id)` as a single node. Committed tree-node groups route directly
108
+ to affected keyed entries and the existing `CollectionOutput` publishes ordinary
109
+ `CollectionChange` entries; unrelated nodes keep stable snapshot references instead
110
+ of being resnapshotted.
100
111
  `input` and `observe(source)` connect external boundary values, including
101
112
  eventful value and collection sources. Dispose the
102
113
  Runtime with the owning service. `runtime.batch` defers projection settlement and
@@ -3,7 +3,7 @@
3
3
  ## 定义、修改与读取
4
4
 
5
5
  ```ts
6
- import { createDocument, field, map, object, select, snapshot, type Infer } from 'doxum';
6
+ import { createDocument, field, map, object, read, snapshot, type Infer } from 'doxum';
7
7
 
8
8
  const task = object({ title: field<string>(), done: field<boolean>() });
9
9
  const model = object({ tasks: map(task) });
@@ -18,8 +18,8 @@ document.update(draft => {
18
18
  draft.tasks.put('b', { title: 'Review', done: false });
19
19
  return { warnings: [] };
20
20
  });
21
- const done = select(document, state => state.tasks.get('a')?.done);
22
- const tasks = select(document, state => snapshot(state.tasks));
21
+ const done = read(document, state => state.tasks.get('a')?.done);
22
+ const tasks = read(document, state => snapshot(state.tasks));
23
23
  document.subscribe(
24
24
  path => path.tasks.item('a').done,
25
25
  commit => console.log(commit.changes)
@@ -27,7 +27,7 @@ document.subscribe(
27
27
  ```
28
28
 
29
29
  根 object 是定义身份,runtime 拥有状态与 revision。事务同步且原子,修改立即可读。
30
- Draft 和内部 readWith 是同步回调内的借用视图,不得逃逸。预期拒绝抛 TransactionRejected;普通异常完整恢复后
30
+ Draft `read` selector 接收的 Read 是同步回调内的借用视图,不得逃逸。预期拒绝抛 TransactionRejected;普通异常完整恢复后
31
31
  原样抛出。正常返回 false/undefined 是业务结果,不表示拒绝。
32
32
 
33
33
  object 暴露可编辑成员;field 是原子值,包括对象和数组。Infer 与作用域内原子值深只读;
@@ -58,12 +58,17 @@ table/list/tree 的 `replace(id, value)` 只替换已存在成员,保持 table
58
58
  table/list 的 `move(key | readonly key[], anchor?)` 会保持 moved selection 当前的相对顺序,
59
59
  并在移除 selection 后解析 anchor;`reorder(keys)` 要求与当前 membership 完全一致的排列,
60
60
  只改变顺序而不改变成员值。
61
+ tree 可以为空。`tree(field(...))` 要求每个已存在节点都拥有自己的 `value`;只有
62
+ `tree(optional(field(...)))` 才允许节点缺失 payload,而 `optional(tree(...))` 独立控制整棵
63
+ tree 成员是否可以缺失。
61
64
 
62
65
  校验器是纯同步函数或 Standard Schema v1,直接接收原始引用且不得修改它。
63
66
  成功返回值被忽略,不复制输入,也不深度检查转换;数据转换在进入 Doxum 前完成。
64
67
  parse(model, unknown) 复制校验后的结构并共享只读 payload;严格解析要求原子字段具备校验器。品牌键贯穿 map/table 方法、
65
68
  符号路径和 impact。路径回调描述地址,包括缺失键,订阅注册时解析。
66
- React useDocumentSelector 追踪实际读取,并在选择分支改变时更新依赖。
69
+ Core `select(document, selector, equality?)` 追踪实际读取,在选择分支改变时更新
70
+ 依赖,并返回标准 `Readable`;React 的 `useDocumentSelector` 只负责把这个 Core
71
+ readable 接到 React。
67
72
 
68
73
  ## 变化与消费者
69
74
 
@@ -83,6 +88,11 @@ observerErrors 属于已提交结果。
83
88
  `createProjectionRuntime({ onError })` owner 物化和管理。保留状态、反向索引和
84
89
  keyed patch 放在隔离的 `doxum/advanced` incremental 入口。生命周期、draft、
85
90
  selector 追踪、batch 与故障恢复见 [Projection 参考](projections.zh-CN.md)。
91
+ tree 结构复用同一套 projection source 协议:`path.tree.rootId` 是 scalar,
92
+ `path.tree.nodes` 是 keyed collection,`path.tree.nodes.item(id)` 是单节点 value。
93
+ committed tree node group 直接路由到受影响的 keyed entry,由现有
94
+ `CollectionOutput` 发布普通 `CollectionChange`;未变化节点保持稳定的 snapshot
95
+ 引用,不再因单节点变化而全部重新 snapshot。
86
96
  `input` 与 `observe(source)` 接入外部边界值,包括带事件的 value 和 collection
87
97
  source。随所属服务 dispose Runtime。
88
98
  `runtime.batch` 推迟投影结算与通知,但不推迟文档提交和文档通知;内部读取上次发布值,
@@ -24,9 +24,11 @@
24
24
  original inputs and must be pure; successful output is ignored.
25
25
  6. List identity is a stable key; replacement retains it. Anchor owns ordering.
26
26
  Tree owns reciprocal, connected, acyclic, empty-or-single-root topology.
27
- 7. ObjectNode owns schema identity; runtime owns instance identity. Shared path compiler
28
- and impact-target own addressing/identity/equality/bucketing and exact matching, including React.
29
- Notification matches grouped changes directly without building commit impact indexes.
27
+ 7. ObjectNode owns schema identity; one RuntimeContext owns instance identity for the
28
+ runtime, history and read-only aliases. The shared path compiler and Core impact target
29
+ algorithms own addressing/identity/equality/bucketing and exact matching. React sees
30
+ only Readable/select contracts. Notification matches grouped changes directly without
31
+ building commit impact indexes.
30
32
  8. Apply requires expectedRevision and captures actual local old state. Local reset is
31
33
  reversible; remote commits invalidate history. Groups travel in one session.
32
34
  9. Projections declare sources and settle before listeners. Notification failures leave
@@ -18,9 +18,10 @@
18
18
  校验器直接读取原始输入,必须纯同步,成功返回值被忽略。
19
19
  6. list 以稳定键标记身份,替换值必须保留键。anchor 拥有排序语义;
20
20
  tree 拥有双向一致、连通、无环、空树或单根的拓扑约束。
21
- 7. ObjectNode 拥有定义身份,runtime 拥有实例身份;共享路径 compiler
22
- impact-target 拥有寻址、身份、相等、分桶和精确匹配,React 也遵守此边界。
23
- 通知直接匹配分组变化,不构建 commit impact 索引。
21
+ 7. ObjectNode 拥有定义身份;runtime、history 和只读 alias 共享唯一 RuntimeContext
22
+ 实例身份。共享路径 compiler 与 Core impact target 算法拥有寻址、身份、相等、
23
+ 分桶和精确匹配;React 只消费 Readable/select 契约。通知直接匹配分组变化,
24
+ 不构建 commit impact 索引。
24
25
  8. apply 要求 expectedRevision,记录本地真实旧状态。本地 reset 可撤销,
25
26
  remote commit 使 history 失效;group 在单个 session 中旅行。
26
27
  9. projection 显式声明 source,先于监听结算。通知失败不撤销提交;
@@ -25,6 +25,11 @@ sources use `kind: 'value'` or `kind: 'collection'`; the caller still uses only
25
25
  Schema maps, tables, and `list(field, { keyOf })` nodes are all keyed collection
26
26
  paths. A list uses its schema `keyOf` identity and preserves document order in
27
27
  iteration; array-valued fields remain scalar value observations.
28
+ Tree structure reuses the same source kinds: `path.tree.rootId` is a scalar,
29
+ `path.tree.nodes` is a keyed collection, and `path.tree.nodes.item(id)` is a
30
+ single-node value. Committed tree-node groups route directly to affected keyed
31
+ entries, and the existing `CollectionOutput` publishes ordinary `CollectionChange`
32
+ entries, so unrelated node snapshots keep their identity.
28
33
 
29
34
  External events are smaller than Runtime contexts. A value event carries the new
30
35
  `value` and `revision`; a collection event carries a stable `previous` read,
@@ -23,6 +23,11 @@ source 通过 `kind: 'value'` 或 `kind: 'collection'` 区分语义,但调用
23
23
  schema map、table 和 `list(field, { keyOf })` 都是 keyed collection path。list
24
24
  直接使用 schema 的 `keyOf` 作为稳定 identity,并按文档顺序迭代;数组类型的普通
25
25
  `field(...)` 仍然是 scalar value observation。
26
+ tree 结构复用相同的 source 类型:`path.tree.rootId` 是 scalar,
27
+ `path.tree.nodes` 是 keyed collection,`path.tree.nodes.item(id)` 是单节点 value。
28
+ tree 的 committed node group 直接路由到受影响的 keyed entry,由现有
29
+ `CollectionOutput` 生成普通 `CollectionChange`;未变化节点的 snapshot identity
30
+ 保持稳定。
26
31
 
27
32
  External event 不再复用 Runtime context:value event 提供新 `value` 和
28
33
  `revision`;collection event 提供稳定的 `previous` read、`revision` 和可选的