doxum 0.1.8 → 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.
- package/README.md +215 -322
- package/dist/contract-BNStLbSE.d.ts +441 -0
- package/dist/contract-CIU5FCC1.d.cts +441 -0
- package/dist/driver-BlR81Dqg.js +200 -0
- package/dist/driver-BlR81Dqg.js.map +1 -0
- package/dist/driver-xOIkwrB8.cjs +241 -0
- package/dist/driver-xOIkwrB8.cjs.map +1 -0
- package/dist/index.cjs +666 -2222
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -20
- package/dist/index.d.ts +10 -18
- package/dist/index.js +634 -2189
- package/dist/index.js.map +1 -1
- package/dist/{integration-CZwCwFBS.cjs → integration-C87tjRop.cjs} +4 -9
- package/dist/integration-C87tjRop.cjs.map +1 -0
- package/dist/{integration-B56u1l9V.js → integration-D5XCBLJ8.js} +2 -7
- package/dist/integration-D5XCBLJ8.js.map +1 -0
- package/dist/integration.cjs +11 -11
- package/dist/integration.d.cts +10 -4
- package/dist/integration.d.ts +10 -4
- package/dist/integration.js +4 -3
- package/dist/issue-DVaGQGeP.js +576 -0
- package/dist/issue-DVaGQGeP.js.map +1 -0
- package/dist/issue-DhrNdQNg.cjs +797 -0
- package/dist/issue-DhrNdQNg.cjs.map +1 -0
- package/dist/local-sync.cjs +67 -39
- package/dist/local-sync.cjs.map +1 -1
- package/dist/local-sync.d.cts +10 -10
- package/dist/local-sync.d.ts +10 -10
- package/dist/local-sync.js +66 -38
- package/dist/local-sync.js.map +1 -1
- package/dist/react.cjs +23 -4
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +8 -3
- package/dist/react.d.ts +8 -3
- package/dist/react.js +21 -6
- package/dist/react.js.map +1 -1
- package/dist/store-1Uob0Ghk.cjs +2750 -0
- package/dist/store-1Uob0Ghk.cjs.map +1 -0
- package/dist/store-CD0KdGsq.d.cts +262 -0
- package/dist/store-D7QH6Rzw.js +2511 -0
- package/dist/store-D7QH6Rzw.js.map +1 -0
- package/dist/store-cp5CpfCy.d.ts +262 -0
- package/package.json +1 -1
- package/skills/doxum-runtime/SKILL.md +26 -44
- package/skills/doxum-runtime/references/guide.en.md +95 -366
- package/skills/doxum-runtime/references/guide.zh-CN.md +80 -290
- package/skills/doxum-runtime/references/invariants.en.md +42 -165
- package/skills/doxum-runtime/references/invariants.zh-CN.md +34 -97
- package/skills/doxum-runtime/references/patterns.en.md +73 -225
- package/skills/doxum-runtime/references/patterns.zh-CN.md +73 -179
- package/dist/chunk-pbuEa-1d.js +0 -13
- package/dist/contract-CIcftgR3.d.ts +0 -561
- package/dist/contract-b87yPRDp.d.cts +0 -561
- package/dist/driver-CNxqMVFH.cjs +0 -69
- package/dist/driver-CNxqMVFH.cjs.map +0 -1
- package/dist/driver-CbzfW5MR.js +0 -46
- package/dist/driver-CbzfW5MR.js.map +0 -1
- package/dist/integration-B56u1l9V.js.map +0 -1
- package/dist/integration-CZwCwFBS.cjs.map +0 -1
- package/dist/ownership-CY0nPXGF.cjs +0 -304
- package/dist/ownership-CY0nPXGF.cjs.map +0 -1
- package/dist/ownership-CduRygE7.js +0 -245
- package/dist/ownership-CduRygE7.js.map +0 -1
- package/dist/runtime-0mOFbe_H.cjs +0 -2439
- package/dist/runtime-0mOFbe_H.cjs.map +0 -1
- package/dist/runtime-BzxODO9Q.d.ts +0 -150
- package/dist/runtime-CRVMB9n1.d.cts +0 -150
- package/dist/runtime-DF1q9Gje.js +0 -2129
- package/dist/runtime-DF1q9Gje.js.map +0 -1
|
@@ -1,374 +1,103 @@
|
|
|
1
|
-
# Doxum
|
|
1
|
+
# Doxum Guide
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
read, mutate, observe, and project one in-memory document with `doxum`, persist
|
|
5
|
-
one browser-origin document with `doxum/local-sync`, and bind read models with
|
|
6
|
-
`doxum/react`.
|
|
7
|
-
|
|
8
|
-
The `doxum` core entry owns typed document state, atomic mutation, history,
|
|
9
|
-
impact, subscriptions, and derived views. `doxum/local-sync` is the optional
|
|
10
|
-
browser adapter for IndexedDB-backed offline editing and same-origin cross-tab
|
|
11
|
-
sync. Your application still owns network ordering, authorization, and conflict
|
|
12
|
-
resolution.
|
|
13
|
-
|
|
14
|
-
## Choose the right entry point
|
|
15
|
-
|
|
16
|
-
| Goal | Use |
|
|
17
|
-
| ------------------------------------- | -------------------------------------------------------- |
|
|
18
|
-
| Define document shape | `schema`, `field`, `object`, and collection constructors |
|
|
19
|
-
| Create the canonical runtime | `createDocument` |
|
|
20
|
-
| Persist and sync one browser document | `attachLocalSync` from `doxum/local-sync` |
|
|
21
|
-
| Read once | `select(runtime, read => ...)` |
|
|
22
|
-
| Make local business changes | `runtime.update(tx => ...)` |
|
|
23
|
-
| Replay persisted or remote operations | `runtime.apply(operations, options)` |
|
|
24
|
-
| Replace an entire trusted snapshot | `runtime.replace(document, options)` |
|
|
25
|
-
| Observe one schema location | `schema.value` plus `runtime.subscribe` |
|
|
26
|
-
| Observe a table or map | `schema.collection` plus `runtime.subscribe` |
|
|
27
|
-
| Maintain mapped collection data | `projection.map` |
|
|
28
|
-
| Maintain an aggregate or index | `projection.value / projection.collection` |
|
|
29
|
-
| Read in React | `useDocumentSelector`, `useReadable`, or `useReadable` |
|
|
30
|
-
|
|
31
|
-
Do not write a second mutable copy of the document. `createDocument` is the
|
|
32
|
-
only owner of canonical state.
|
|
33
|
-
|
|
34
|
-
## Start with a schema
|
|
35
|
-
|
|
36
|
-
A schema is both the TypeScript shape of the document and the authoritative
|
|
37
|
-
address model for writers, operations, selectors, and subscriptions. Define it
|
|
38
|
-
once and keep it close to the domain it describes.
|
|
3
|
+
## Define, Update And Read
|
|
39
4
|
|
|
40
5
|
```ts
|
|
41
|
-
import { createDocument, field, object,
|
|
42
|
-
|
|
43
|
-
const task = object({
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
title: field<string>(),
|
|
50
|
-
tasks: table(task),
|
|
6
|
+
import { createDocument, field, map, object, select, snapshot, type Infer } from 'doxum';
|
|
7
|
+
|
|
8
|
+
const task = object({ title: field<string>(), done: field<boolean>() });
|
|
9
|
+
const model = object({ tasks: map(task) });
|
|
10
|
+
type DocumentValue = Infer<typeof model>;
|
|
11
|
+
const document = createDocument({
|
|
12
|
+
schema: model,
|
|
13
|
+
initial: { tasks: { a: { title: 'Write', done: false } } },
|
|
51
14
|
});
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
tasks: {
|
|
58
|
-
ids: ['write-guide'],
|
|
59
|
-
byId: {
|
|
60
|
-
'write-guide': { title: 'Write the guide', completed: false },
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
},
|
|
15
|
+
document.update(draft => {
|
|
16
|
+
const task = draft.tasks.a;
|
|
17
|
+
if (task) task.done = !task.done;
|
|
18
|
+
draft.tasks.b = { title: 'Review', done: false };
|
|
19
|
+
return { warnings: [] };
|
|
64
20
|
});
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
sequence with an application-supplied stable key, and `tree` for a validated
|
|
71
|
-
single-root hierarchy. See [patterns.en.md](patterns.en.md) for modelling
|
|
72
|
-
guidance.
|
|
73
|
-
|
|
74
|
-
## Read through readers
|
|
75
|
-
|
|
76
|
-
Use `import type { Infer } from 'doxum'` to name schema-derived values:
|
|
77
|
-
`Infer<typeof task>` for a node and `Infer<typeof taskSchema>` for a document.
|
|
78
|
-
Generated object types and variant branches are flat, with readonly fields
|
|
79
|
-
and discriminants. Optional nodes include undefined; optional object members
|
|
80
|
-
can be omitted. User-provided scalar payload types retain their own structure
|
|
81
|
-
and mutability. Infer accepts nodes and schemas, not a raw shape object.
|
|
82
|
-
|
|
83
|
-
The runtime does not expose its mutable document. Read through a callback:
|
|
84
|
-
|
|
85
|
-
```ts
|
|
86
|
-
import { select } from 'doxum';
|
|
87
|
-
|
|
88
|
-
const openTitles = select(runtime, read =>
|
|
89
|
-
read.tasks.ids().flatMap(id => {
|
|
90
|
-
const task = read.tasks.get(id);
|
|
91
|
-
return task && !task.completed.get() ? [task.title.get()] : [];
|
|
92
|
-
})
|
|
21
|
+
const done = select(document, state => state.tasks.a?.done);
|
|
22
|
+
const tasks = select(document, state => snapshot(state.tasks));
|
|
23
|
+
document.subscribe(
|
|
24
|
+
path => path.tasks.item('a').done,
|
|
25
|
+
commit => console.log(commit.changes)
|
|
93
26
|
);
|
|
94
27
|
```
|
|
95
28
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
Optional field, variant, dict, list, and tree writers expose `clear()`. Optional
|
|
173
|
-
structured leaves can also be initialized from an absent state with `replace()`.
|
|
174
|
-
Optional objects, tables, and maps are rejected by the schema constructor and
|
|
175
|
-
types. Model those containers as present and use their child or collection operations.
|
|
176
|
-
Variant readers expose get() as a discriminated union. Dictionary readers use
|
|
177
|
-
get(key), has(key), keys() and values(); lists also provide get(key)/has(key).
|
|
178
|
-
|
|
179
|
-
For one action spanning several updates, open `runtime.history.group()` and
|
|
180
|
-
call its `end()` to retain one undo entry or `cancel()` to revert the action.
|
|
181
|
-
Groups cannot nest. History implements Readable, and `localSync.state` is also
|
|
182
|
-
a Readable: both work with useReadable and projection.fromReadable.
|
|
183
|
-
|
|
184
|
-
## Replay operations at the boundary
|
|
185
|
-
|
|
186
|
-
Use `apply` for operation batches that came from persistence, a network
|
|
187
|
-
adapter, or another external boundary. Doxum decodes unknown operation payloads
|
|
188
|
-
before mutation code observes them, resolves every address against the schema,
|
|
189
|
-
and applies the batch atomically.
|
|
190
|
-
|
|
191
|
-
```ts
|
|
192
|
-
const result = runtime.apply([{ type: 'field.set', at: ['title'], value: 'Restored title' }], {
|
|
193
|
-
source: 'remote',
|
|
194
|
-
history: false,
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
if (result.status === 'rejected') {
|
|
198
|
-
// The document and revision remain unchanged.
|
|
199
|
-
console.error(result.issues);
|
|
200
|
-
}
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
Treat external operation input as untrusted, even if TypeScript types make it
|
|
204
|
-
look valid. Do not write a second path parser or validate operations by
|
|
205
|
-
partially replaying them outside Doxum. A remote commit and every `replace`
|
|
206
|
-
establish a new baseline, so they invalidate local undo/redo history.
|
|
207
|
-
|
|
208
|
-
## Interpret results and history
|
|
209
|
-
|
|
210
|
-
Every mutation entry point returns one of three states:
|
|
211
|
-
|
|
212
|
-
| Status | Meaning |
|
|
213
|
-
| ----------- | -------------------------------------------------------- |
|
|
214
|
-
| `committed` | Canonical state changed; the result contains a commit. |
|
|
215
|
-
| `unchanged` | The net state did not change; the revision is unchanged. |
|
|
216
|
-
| `rejected` | The whole batch rolled back; inspect `issues`. |
|
|
217
|
-
|
|
218
|
-
Committed operations carry forward operations, inverse operations, a revision,
|
|
219
|
-
and a `DocumentImpact`. Local history records local and system commits by
|
|
220
|
-
default. Use `runtime.history.undo()` and `runtime.history.redo()`; they replay
|
|
221
|
-
the inverse or forward operation batch through the same mutation pipeline.
|
|
222
|
-
|
|
223
|
-
`observerErrors` on a committed result are failures from processors, flushes,
|
|
224
|
-
or listeners after canonical state and history settled. They are not mutation
|
|
225
|
-
failures and must not cause the caller to repeat the write.
|
|
226
|
-
|
|
227
|
-
## Attach local browser sync
|
|
228
|
-
|
|
229
|
-
`attachLocalSync` is an optional browser attachment. Create and keep the
|
|
230
|
-
runtime yourself, then await attachment before allowing the document to be used.
|
|
231
|
-
It hydrates that runtime from an IndexedDB checkpoint and append-only command
|
|
232
|
-
tail, then uses a Web Lock to choose exactly one writable tab. The `leader`
|
|
233
|
-
uses normal synchronous runtime writes; every other attached tab is a
|
|
234
|
-
`follower` read-only mirror. A direct follower write through `update`,
|
|
235
|
-
`prepare`, `apply`, or `replace` throws `LocalSyncReadOnlyError`.
|
|
236
|
-
|
|
237
|
-
The leader's local, system, and history commits are observed after they have
|
|
238
|
-
settled and persisted to IndexedDB in order. BroadcastChannel carries only a
|
|
239
|
-
new-head hint. Followers reload the durable tail and apply it as `remote`; this
|
|
240
|
-
keeps their document ordered and invalidates their in-memory history. When the
|
|
241
|
-
leader disposes, a caught-up follower takes the lock and becomes leader.
|
|
242
|
-
|
|
243
|
-
Local-sync persists operation commands, not arbitrary new baselines. While it
|
|
244
|
-
is attached, `runtime.replace()` and an externally supplied
|
|
245
|
-
`runtime.apply(..., { source: 'remote' })` throw
|
|
246
|
-
`LocalSyncUnsupportedOperationError`; internal hydration and tail replay use a
|
|
247
|
-
trusted attachment path instead.
|
|
248
|
-
|
|
249
|
-
```ts
|
|
250
|
-
import { attachLocalSync } from 'doxum/local-sync';
|
|
251
|
-
|
|
252
|
-
const runtime = createDocument({ schema: taskSchema, initial });
|
|
253
|
-
const localSync = await attachLocalSync({
|
|
254
|
-
runtime,
|
|
255
|
-
database: 'my-app',
|
|
256
|
-
documentId: 'project-1',
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
if (localSync.state.current().status === 'leader') {
|
|
260
|
-
runtime.update(tx => tx.write.title.set('Ship Doxum'));
|
|
261
|
-
runtime.history.undo();
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
await localSync.flush(); // persist observed leader commands or catch up a follower
|
|
265
|
-
await localSync.dispose();
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
This is synchronous visibility with asynchronous persistence, not strict
|
|
269
|
-
durability: a crash, storage failure, or invalid JSON payload can leave a
|
|
270
|
-
visible leader commit unpersisted. Use `localSync.state.current()` and `onError` to show
|
|
271
|
-
that condition. `flush()` is the explicit persistence/catch-up boundary. The
|
|
272
|
-
attachment does not own or dispose the runtime and it does not expose an undo
|
|
273
|
-
API: use `runtime.history.undo()` and `runtime.history.redo()` while the tab is
|
|
274
|
-
leader. Runtime history is intentionally in-memory only; attachment hydration
|
|
275
|
-
and remote tail replay invalidate it, so it is not transferred across reopening
|
|
276
|
-
or leader handoff.
|
|
277
|
-
|
|
278
|
-
## Subscribe through schema-owned targets
|
|
279
|
-
|
|
280
|
-
Create stable selectors from the schema, then subscribe to them. This is the
|
|
281
|
-
shared address and impact model for the whole runtime.
|
|
282
|
-
|
|
283
|
-
```ts
|
|
284
|
-
const title = taskSchema.value(path => path.title);
|
|
285
|
-
const tasks = taskSchema.collection(path => path.tasks);
|
|
286
|
-
|
|
287
|
-
const stopTitle = runtime.subscribe(title, commit => {
|
|
288
|
-
console.log('title changed at revision', commit.revision);
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
const stopTasks = runtime.subscribe(tasks, commit => {
|
|
292
|
-
const change = commit.impact.collection(tasks);
|
|
293
|
-
if (change.kind === 'incremental') {
|
|
294
|
-
console.log(change.added, change.removed, change.updated, change.orderChanged);
|
|
295
|
-
}
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
stopTitle();
|
|
299
|
-
stopTasks();
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
For value selectors, use `commit.impact.affects(target)`. For table or map
|
|
303
|
-
selectors, use `commit.impact.collection(selector)`, which returns either a
|
|
304
|
-
precise incremental change or `reset` after replacement. Do not recreate path
|
|
305
|
-
comparison helpers in application modules.
|
|
306
|
-
|
|
307
|
-
## Build derived read models
|
|
308
|
-
|
|
309
|
-
```ts
|
|
310
|
-
const projection = createProjectionRuntime({ onError: error => console.error(error) });
|
|
311
|
-
const document = projection.document(runtime);
|
|
312
|
-
const notes = document.collection(path => path.notes);
|
|
313
|
-
const noteSummaries = projection.map(
|
|
314
|
-
notes,
|
|
315
|
-
(id, note) => ({ id, preview: note.body.get().slice(0, 80) }),
|
|
316
|
-
{ isEqual: (a, b) => a.id === b.id && a.preview === b.preview }
|
|
317
|
-
);
|
|
318
|
-
const noteCount = projection.value({ notes }, ({ notes }) => notes.read.ids().length);
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
projection.map produces stable ids/item readables and lazy all. Declare a
|
|
322
|
-
DocumentCollectionSource directly in any processor's sources, or use
|
|
323
|
-
`projection.value({ sources, build }, { isEqual }?)` and
|
|
324
|
-
`projection.collection<Item>()(spec)` for explicit incremental logic.
|
|
325
|
-
Map also accepts upstream projection collections. Document collection contexts
|
|
326
|
-
provide batch-wide `candidates.keys`, `candidates.orderDirty` and `reset`.
|
|
327
|
-
There is no automatic keyed dependency tracking. All sources belong to the
|
|
328
|
-
same projection owner, but may refer to different document runtimes.
|
|
329
|
-
|
|
330
|
-
Use projection.input(initial, { isEqual }) for boundary values; give processors
|
|
331
|
-
its source and keep set at the application boundary. fromReadable attaches an
|
|
332
|
-
existing external source without taking ownership of it. Batch synchronous
|
|
333
|
-
multi-source changes before the first commit. Dispose the projection at service
|
|
334
|
-
shutdown; React unmount only unsubscribes. Disposed handles throw.
|
|
335
|
-
|
|
336
|
-
## React integration
|
|
337
|
-
|
|
338
|
-
`doxum/react` uses `useSyncExternalStore` and tracked Doxum dependencies. A
|
|
339
|
-
component re-renders only for commits that can affect the selector it read.
|
|
340
|
-
|
|
341
|
-
```tsx
|
|
342
|
-
import { useDocumentSelector } from 'doxum/react';
|
|
343
|
-
|
|
344
|
-
function OpenTaskCount() {
|
|
345
|
-
const count = useDocumentSelector(
|
|
346
|
-
runtime,
|
|
347
|
-
read => read.tasks.ids().filter(id => !read.tasks.get(id)?.completed.get()).length
|
|
348
|
-
);
|
|
349
|
-
|
|
350
|
-
return <output>{count}</output>;
|
|
351
|
-
}
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
Use `useReadable(view.all)` for a `Readable`, `useReadable(view.item(id))`
|
|
355
|
-
for one keyed value, and `useHistory(runtime.history)` for undo/redo state and
|
|
356
|
-
actions. Keep `core` free of React imports; React-specific code belongs in the
|
|
357
|
-
adapter or application layer.
|
|
358
|
-
|
|
359
|
-
## Lifecycle and ownership
|
|
360
|
-
|
|
361
|
-
- The initial document is cloned when `createDocument` starts.
|
|
362
|
-
- Structural payloads passed through operations are transferred into canonical
|
|
363
|
-
state. Do not mutate them afterwards unless you intentionally want to mutate
|
|
364
|
-
the canonical document.
|
|
365
|
-
- Published commits, history payloads, diagnostics, and selector addresses are
|
|
366
|
-
immutable snapshots.
|
|
367
|
-
- Tree replacement snapshots are validated and cloned to preserve structural
|
|
368
|
-
integrity.
|
|
369
|
-
- Call `runtime.dispose()` when the runtime is no longer usable. Existing
|
|
370
|
-
subscriptions, history state, and views should be disposed with their owners.
|
|
371
|
-
|
|
372
|
-
For decision rules and anti-patterns, read
|
|
373
|
-
[invariants.en.md](invariants.en.md). For copyable implementation patterns,
|
|
374
|
-
read [patterns.en.md](patterns.en.md).
|
|
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.
|
|
32
|
+
Throw TransactionRejected for expected rejection; ordinary throws
|
|
33
|
+
restore all work and rethrow unchanged. False/undefined returns are business values.
|
|
34
|
+
|
|
35
|
+
Object exposes editable members; field is atomic, including arrays and objects.
|
|
36
|
+
Atomic values are deeply readonly in Infer and scoped access. Inputs, snapshots,
|
|
37
|
+
commits and history share payload references. Object/variant structure accepts only
|
|
38
|
+
declared own properties (plus the variant discriminant); extra string, symbol and
|
|
39
|
+
non-enumerable properties are rejected. Use maps for dynamic keys and fields for
|
|
40
|
+
arbitrary payload objects. Never mutate payloads through any alias,
|
|
41
|
+
even after removal. Snapshot copies schema structure only; snapshot(rawPayload)
|
|
42
|
+
returns its original readonly reference. Published data is not frozen. Field copiers
|
|
43
|
+
are not supported; mutable exports and serialization belong to application boundaries.
|
|
44
|
+
|
|
45
|
+
Infer preserves optional properties and flat variant unions. Read/Draft contain
|
|
46
|
+
collection tools; assign(scope, key, inferValue) handles plain replacements containing
|
|
47
|
+
nested table/list/tree data.
|
|
48
|
+
|
|
49
|
+
## Containers And Validation
|
|
50
|
+
|
|
51
|
+
| Definition | Data | Draft methods |
|
|
52
|
+
| -------------------------------- | ------------- | ------------------------------------------------------------- |
|
|
53
|
+
| map(valueSchema, { key }?) | record | indexing, assignment, delete |
|
|
54
|
+
| table(objectOrVariant, { key }?) | ids/byId | get/has/ids/create/remove/move |
|
|
55
|
+
| list(field, { keyOf }) | array | get/has/ids/insert/set/remove/move/replace |
|
|
56
|
+
| tree(field) | rootId?/nodes | get/has/rootId/parent/children/insert/set/remove/move/replace |
|
|
57
|
+
|
|
58
|
+
Read scopes expose only read methods. Map supports field/object/variant values.
|
|
59
|
+
List replacement retains the addressed key. Simple arrays and strokes can be one
|
|
60
|
+
atomic field. Optional supports field/variant/map/list/tree. Absent differs from
|
|
61
|
+
present undefined. Variant tags are readonly; change branch by whole replacement.
|
|
62
|
+
|
|
63
|
+
Pure synchronous functions and Standard Schema v1 validators receive original input.
|
|
64
|
+
They must not mutate it; successful output is ignored, with no copy or deep conversion
|
|
65
|
+
check. Transform values before entering Doxum. parse(model, unknown) copies validated
|
|
66
|
+
structure and shares readonly payloads; strict parse requires atomic validators.
|
|
67
|
+
Branded map/table keys flow through access, symbolic paths and impact.
|
|
68
|
+
Path callbacks describe locations, including absent entries, and compile at registration.
|
|
69
|
+
React useDocumentSelector tracks actual reads and changes dependencies when branching.
|
|
70
|
+
|
|
71
|
+
## Changes And Consumers
|
|
72
|
+
|
|
73
|
+
Commits contain revision/source/changes/impact. ChangeSet groups members by owning
|
|
74
|
+
container, with direct added/removed/updated transitions and optional before/after
|
|
75
|
+
order in the same group. Order-only groups have empty members; standalone order
|
|
76
|
+
changes and duplicate groups are invalid. Each group is applied completely before
|
|
77
|
+
the next. Touched tree
|
|
78
|
+
nodes retain structural semantics; reset is an explicit whole-document transition.
|
|
79
|
+
Root member groups remain incremental. Net-zero changes do not publish.
|
|
80
|
+
apply(changes, { expectedRevision }) rejects a missing or mismatched local baseline.
|
|
81
|
+
Received before values are untrusted; local undo records actual old state.
|
|
82
|
+
History travels complete ChangeSets; grouped travel is atomic. Local replace is a
|
|
83
|
+
reversible root reset; remote commits invalidate local history. Observer errors occur
|
|
84
|
+
after acceptance.
|
|
85
|
+
|
|
86
|
+
Use `project(document, path => path.tasks, mapper)` for incremental mapping.
|
|
87
|
+
Pure values use `project(sources, compute)`; stateful algorithms use tagged value
|
|
88
|
+
or collection specs. Definitions are lazy and are materialized by a
|
|
89
|
+
`createProjectionStore({ onError })` instance. Sources remain explicit.
|
|
90
|
+
`input` and `project(readable)` connect external boundary values. Dispose the store
|
|
91
|
+
with the owning service. `store.batch` defers projection settlement/listeners, not document commits/listeners. Reads
|
|
92
|
+
inside a batch see the last publication; no cross-document rollback is provided.
|
|
93
|
+
|
|
94
|
+
doxum/local-sync attaches IndexedDB and Web Lock leadership. Only the leader writes;
|
|
95
|
+
followers replay contiguous durable sequence. Writes become visible before async
|
|
96
|
+
persistence; flush waits for durability. External replace and external remote-marked
|
|
97
|
+
apply are forbidden while attached. Version 5 / format 3 rejects old storage without
|
|
98
|
+
deleting or migrating it. The adapter accepts JSON values only. Change limits count
|
|
99
|
+
logical members and tree nodes, not just outer groups.
|
|
100
|
+
Limits govern new local commits only; existing durable commits remain readable
|
|
101
|
+
under smaller current limits. Treat the whole published ChangeSet as readonly:
|
|
102
|
+
its identity carries reusable structural validation, not authority to skip local
|
|
103
|
+
revision, schema or actual-before checks.
|