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.
- package/README.md +35 -7
- package/dist/{advanced-FgdTwu5b.d.ts → advanced-CLJCWkHa.d.cts} +3 -3
- package/dist/{advanced-COFJtWMT.js → advanced-CRNHfJBu.js} +482 -446
- package/dist/advanced-CRNHfJBu.js.map +1 -0
- package/dist/{advanced-BRL5pN0y.cjs → advanced-Cle5g3B4.cjs} +615 -459
- package/dist/advanced-Cle5g3B4.cjs.map +1 -0
- package/dist/{advanced-DfKc7TZR.d.cts → advanced-d6J61er9.d.ts} +3 -3
- package/dist/advanced.cjs +1 -1
- package/dist/advanced.d.cts +1 -1
- package/dist/advanced.d.ts +1 -1
- package/dist/advanced.js +1 -1
- package/dist/context-BcLKGfq8.cjs +149 -0
- package/dist/context-BcLKGfq8.cjs.map +1 -0
- package/dist/context-DFEaDUv5.js +84 -0
- package/dist/context-DFEaDUv5.js.map +1 -0
- package/dist/{contract-CdmDHRUq.d.cts → contract-SYJFP0lM.d.cts} +62 -88
- package/dist/{contract-KG5XnQZt.d.ts → contract-ULPQmiEs.d.ts} +62 -88
- package/dist/driver-BD3DQOW6.cjs +382 -0
- package/dist/driver-BD3DQOW6.cjs.map +1 -0
- package/dist/{driver-CLYJOGDQ.js → driver-yFfvDMX5.js} +155 -26
- package/dist/driver-yFfvDMX5.js.map +1 -0
- package/dist/index.cjs +2370 -406
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -5
- package/dist/index.d.ts +6 -5
- package/dist/index.js +2293 -330
- package/dist/index.js.map +1 -1
- package/dist/local-sync.cjs +5 -5
- package/dist/local-sync.cjs.map +1 -1
- package/dist/local-sync.d.cts +1 -1
- package/dist/local-sync.d.ts +1 -1
- package/dist/local-sync.js +2 -2
- package/dist/local-sync.js.map +1 -1
- package/dist/react.cjs +2 -69
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +0 -1
- package/dist/react.d.ts +1 -2
- package/dist/react.js +3 -70
- package/dist/react.js.map +1 -1
- package/package.json +1 -6
- package/skills/doxum-runtime/SKILL.md +7 -1
- package/skills/doxum-runtime/references/guide.en.md +17 -6
- package/skills/doxum-runtime/references/guide.zh-CN.md +15 -5
- package/skills/doxum-runtime/references/invariants.en.md +5 -3
- package/skills/doxum-runtime/references/invariants.zh-CN.md +4 -3
- package/skills/doxum-runtime/references/projections.en.md +5 -0
- package/skills/doxum-runtime/references/projections.zh-CN.md +5 -0
- package/dist/access-BeSV5ve-.js +0 -816
- package/dist/access-BeSV5ve-.js.map +0 -1
- package/dist/access-Bienf28h.cjs +0 -911
- package/dist/access-Bienf28h.cjs.map +0 -1
- package/dist/advanced-BRL5pN0y.cjs.map +0 -1
- package/dist/advanced-COFJtWMT.js.map +0 -1
- package/dist/driver-CLYJOGDQ.js.map +0 -1
- package/dist/driver-Djr3BVvo.cjs +0 -241
- package/dist/driver-Djr3BVvo.cjs.map +0 -1
- package/dist/integration-C4Isp8lJ.js +0 -26
- package/dist/integration-C4Isp8lJ.js.map +0 -1
- package/dist/integration-Z6MsfFI7.cjs +0 -31
- package/dist/integration-Z6MsfFI7.cjs.map +0 -1
- package/dist/integration.cjs +0 -12
- package/dist/integration.d.cts +0 -17
- package/dist/integration.d.ts +0 -17
- package/dist/integration.js +0 -4
- package/dist/issue-DjK1xp5K.cjs +0 -881
- package/dist/issue-DjK1xp5K.cjs.map +0 -1
- package/dist/issue-W0hT8u8D.js +0 -642
- package/dist/issue-W0hT8u8D.js.map +0 -1
- package/dist/notification-Bkjau6B6.js +0 -232
- package/dist/notification-Bkjau6B6.js.map +0 -1
- package/dist/notification-DJfouMEb.cjs +0 -309
- 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 {
|
|
2
|
-
import {
|
|
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
|
-
|
|
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":[
|
|
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.
|
|
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
|
|
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,
|
|
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 =
|
|
22
|
-
const 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
|
|
31
|
-
|
|
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
|
-
|
|
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,
|
|
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 =
|
|
22
|
-
const 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
|
|
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
|
-
|
|
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;
|
|
28
|
-
and
|
|
29
|
-
|
|
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
|
|
22
|
-
impact
|
|
23
|
-
|
|
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` 和可选的
|