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.
- package/README.md +217 -296
- 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 -359
- package/skills/doxum-runtime/references/guide.zh-CN.md +80 -285
- 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-DNZ4D53r.d.ts +0 -563
- package/dist/contract-j3SLGAwh.d.cts +0 -563
- 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-B22tuj9A.d.cts +0 -150
- package/dist/runtime-BFmhzPpZ.d.ts +0 -150
- package/dist/runtime-DF1q9Gje.js +0 -2129
- package/dist/runtime-DF1q9Gje.js.map +0 -1
|
@@ -1,367 +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.
|
|
39
|
-
|
|
40
|
-
```ts
|
|
41
|
-
import { createDocument, field, object, schema, table } from 'doxum';
|
|
42
|
-
|
|
43
|
-
const task = object({
|
|
44
|
-
title: field<string>(),
|
|
45
|
-
completed: field<boolean>(),
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
const taskSchema = schema({
|
|
49
|
-
title: field<string>(),
|
|
50
|
-
tasks: table(task),
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
const runtime = createDocument({
|
|
54
|
-
schema: taskSchema,
|
|
55
|
-
initial: {
|
|
56
|
-
title: 'Launch Doxum',
|
|
57
|
-
tasks: {
|
|
58
|
-
ids: ['write-guide'],
|
|
59
|
-
byId: {
|
|
60
|
-
'write-guide': { title: 'Write the guide', completed: false },
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
});
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
`table` preserves application-visible order through `{ ids, byId }`. `map`
|
|
68
|
-
stores id-indexed entities without order. Use `object` for one structured
|
|
69
|
-
entity, `dict` for scalar key/value data, `list` for an ordered
|
|
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
|
-
The runtime does not expose its mutable document. Read through a callback:
|
|
77
|
-
|
|
78
|
-
```ts
|
|
79
|
-
import { select } from 'doxum';
|
|
80
|
-
|
|
81
|
-
const openTitles = select(runtime, read =>
|
|
82
|
-
read.tasks.ids().flatMap(id => {
|
|
83
|
-
const task = read.tasks.get(id);
|
|
84
|
-
return task && !task.completed.get() ? [task.title.get()] : [];
|
|
85
|
-
})
|
|
86
|
-
);
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
Readers are intentionally shaped by the schema:
|
|
90
|
-
|
|
91
|
-
- A field has `get()`.
|
|
92
|
-
- A table or map has `ids()`, `has(id)`, and `get(id)`.
|
|
93
|
-
- A list has `values()`, `length()`, and `at(index)`.
|
|
94
|
-
- A tree has `rootId()`, `has(id)`, `value(id)`, `parent(id)`, and
|
|
95
|
-
`children(id)`.
|
|
96
|
-
|
|
97
|
-
Reader values for structural data are snapshots. Do not retain a transaction
|
|
98
|
-
reader after `runtime.update` returns; it is only valid during that callback.
|
|
99
|
-
|
|
100
|
-
## Mutate atomically
|
|
101
|
-
|
|
102
|
-
Use `runtime.update` for local, typed domain behavior. Its callback receives a
|
|
103
|
-
short-lived `tx.read` and `tx.write`. Writers create operations for one atomic
|
|
104
|
-
session; they never expose direct canonical mutation.
|
|
105
|
-
|
|
106
|
-
```ts
|
|
107
|
-
const result = runtime.update(tx => {
|
|
108
|
-
const task = tx.read.tasks.get('write-guide');
|
|
109
|
-
if (!task) {
|
|
110
|
-
tx.reject({
|
|
111
|
-
code: 'task-not-found',
|
|
112
|
-
message: 'The requested task no longer exists.',
|
|
113
|
-
address: ['tasks', 'write-guide'],
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
tx.write.tasks.item('write-guide').completed.set(true);
|
|
118
|
-
return task.title.get();
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
if (result.status === 'committed') {
|
|
122
|
-
console.log(result.value, result.commit.revision);
|
|
123
|
-
} else if (result.status === 'rejected') {
|
|
124
|
-
console.error(result.issues);
|
|
125
|
-
}
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
An update is synchronous and atomic:
|
|
129
|
-
|
|
130
|
-
- If a writer emits a semantically invalid operation, Doxum rolls back the
|
|
131
|
-
entire session and returns `status: 'rejected'` with `MutationIssue` values.
|
|
132
|
-
- `tx.reject(...)` rolls back and returns your application
|
|
133
|
-
`DocumentDiagnostic` values.
|
|
134
|
-
- A normal thrown error also rolls back, then is rethrown to the caller.
|
|
135
|
-
- Net-zero work returns `status: 'unchanged'` and publishes no commit.
|
|
136
|
-
|
|
137
|
-
Use `tx.report(...)` for non-blocking application diagnostics. Committed and
|
|
138
|
-
unchanged transaction results expose them as `reports`; reports and diagnostic
|
|
139
|
-
addresses are copied and frozen before they are published.
|
|
140
|
-
|
|
141
|
-
## Use writers instead of constructing local operations
|
|
142
|
-
|
|
143
|
-
For normal application behavior, writer APIs are clearer and preserve the
|
|
144
|
-
schema domain:
|
|
145
|
-
|
|
146
|
-
```ts
|
|
147
|
-
runtime.update(tx => {
|
|
148
|
-
tx.write.title.set('Ship Doxum');
|
|
149
|
-
tx.write.tasks.create(
|
|
150
|
-
{ id: 'release', value: { title: 'Publish the package', completed: false } },
|
|
151
|
-
{ after: 'write-guide' }
|
|
152
|
-
);
|
|
153
|
-
tx.write.tasks.item('release').title.set('Publish doxum');
|
|
154
|
-
tx.write.tasks.move('release', { at: 'start' });
|
|
155
|
-
tx.write.tasks.remove('write-guide');
|
|
156
|
-
});
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
For a table, `create`, `item`, `remove`, and `move` are available. A map has
|
|
160
|
-
the same API except `move`, because it is unordered. Lists offer `insert`,
|
|
161
|
-
`move`, `remove`, and `replace`; list identity comes from the `keyOf` function
|
|
162
|
-
specified in the schema. See [patterns.en.md](patterns.en.md) for complete
|
|
163
|
-
collection and tree examples.
|
|
164
|
-
|
|
165
|
-
Optional field, variant, dict, list, and tree writers expose `clear()`. Optional
|
|
166
|
-
structured leaves can also be initialized from an absent state with `replace()`.
|
|
167
|
-
Optional objects, tables, and maps are rejected by the schema constructor and
|
|
168
|
-
types. Model those containers as present and use their child or collection operations.
|
|
169
|
-
Variant readers expose get() as a discriminated union. Dictionary readers use
|
|
170
|
-
get(key), has(key), keys() and values(); lists also provide get(key)/has(key).
|
|
171
|
-
|
|
172
|
-
For one action spanning several updates, open `runtime.history.group()` and
|
|
173
|
-
call its `end()` to retain one undo entry or `cancel()` to revert the action.
|
|
174
|
-
Groups cannot nest. History implements Readable, and `localSync.state` is also
|
|
175
|
-
a Readable: both work with useReadable and projection.fromReadable.
|
|
176
|
-
|
|
177
|
-
## Replay operations at the boundary
|
|
178
|
-
|
|
179
|
-
Use `apply` for operation batches that came from persistence, a network
|
|
180
|
-
adapter, or another external boundary. Doxum decodes unknown operation payloads
|
|
181
|
-
before mutation code observes them, resolves every address against the schema,
|
|
182
|
-
and applies the batch atomically.
|
|
183
|
-
|
|
184
|
-
```ts
|
|
185
|
-
const result = runtime.apply([{ type: 'field.set', at: ['title'], value: 'Restored title' }], {
|
|
186
|
-
source: 'remote',
|
|
187
|
-
history: false,
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
if (result.status === 'rejected') {
|
|
191
|
-
// The document and revision remain unchanged.
|
|
192
|
-
console.error(result.issues);
|
|
193
|
-
}
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
Treat external operation input as untrusted, even if TypeScript types make it
|
|
197
|
-
look valid. Do not write a second path parser or validate operations by
|
|
198
|
-
partially replaying them outside Doxum. A remote commit and every `replace`
|
|
199
|
-
establish a new baseline, so they invalidate local undo/redo history.
|
|
200
|
-
|
|
201
|
-
## Interpret results and history
|
|
202
|
-
|
|
203
|
-
Every mutation entry point returns one of three states:
|
|
204
|
-
|
|
205
|
-
| Status | Meaning |
|
|
206
|
-
| ----------- | -------------------------------------------------------- |
|
|
207
|
-
| `committed` | Canonical state changed; the result contains a commit. |
|
|
208
|
-
| `unchanged` | The net state did not change; the revision is unchanged. |
|
|
209
|
-
| `rejected` | The whole batch rolled back; inspect `issues`. |
|
|
210
|
-
|
|
211
|
-
Committed operations carry forward operations, inverse operations, a revision,
|
|
212
|
-
and a `DocumentImpact`. Local history records local and system commits by
|
|
213
|
-
default. Use `runtime.history.undo()` and `runtime.history.redo()`; they replay
|
|
214
|
-
the inverse or forward operation batch through the same mutation pipeline.
|
|
215
|
-
|
|
216
|
-
`observerErrors` on a committed result are failures from processors, flushes,
|
|
217
|
-
or listeners after canonical state and history settled. They are not mutation
|
|
218
|
-
failures and must not cause the caller to repeat the write.
|
|
219
|
-
|
|
220
|
-
## Attach local browser sync
|
|
221
|
-
|
|
222
|
-
`attachLocalSync` is an optional browser attachment. Create and keep the
|
|
223
|
-
runtime yourself, then await attachment before allowing the document to be used.
|
|
224
|
-
It hydrates that runtime from an IndexedDB checkpoint and append-only command
|
|
225
|
-
tail, then uses a Web Lock to choose exactly one writable tab. The `leader`
|
|
226
|
-
uses normal synchronous runtime writes; every other attached tab is a
|
|
227
|
-
`follower` read-only mirror. A direct follower write through `update`,
|
|
228
|
-
`prepare`, `apply`, or `replace` throws `LocalSyncReadOnlyError`.
|
|
229
|
-
|
|
230
|
-
The leader's local, system, and history commits are observed after they have
|
|
231
|
-
settled and persisted to IndexedDB in order. BroadcastChannel carries only a
|
|
232
|
-
new-head hint. Followers reload the durable tail and apply it as `remote`; this
|
|
233
|
-
keeps their document ordered and invalidates their in-memory history. When the
|
|
234
|
-
leader disposes, a caught-up follower takes the lock and becomes leader.
|
|
235
|
-
|
|
236
|
-
Local-sync persists operation commands, not arbitrary new baselines. While it
|
|
237
|
-
is attached, `runtime.replace()` and an externally supplied
|
|
238
|
-
`runtime.apply(..., { source: 'remote' })` throw
|
|
239
|
-
`LocalSyncUnsupportedOperationError`; internal hydration and tail replay use a
|
|
240
|
-
trusted attachment path instead.
|
|
241
|
-
|
|
242
|
-
```ts
|
|
243
|
-
import { attachLocalSync } from 'doxum/local-sync';
|
|
244
|
-
|
|
245
|
-
const runtime = createDocument({ schema: taskSchema, initial });
|
|
246
|
-
const localSync = await attachLocalSync({
|
|
247
|
-
runtime,
|
|
248
|
-
database: 'my-app',
|
|
249
|
-
documentId: 'project-1',
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
if (localSync.state.current().status === 'leader') {
|
|
253
|
-
runtime.update(tx => tx.write.title.set('Ship Doxum'));
|
|
254
|
-
runtime.history.undo();
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
await localSync.flush(); // persist observed leader commands or catch up a follower
|
|
258
|
-
await localSync.dispose();
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
This is synchronous visibility with asynchronous persistence, not strict
|
|
262
|
-
durability: a crash, storage failure, or invalid JSON payload can leave a
|
|
263
|
-
visible leader commit unpersisted. Use `localSync.state.current()` and `onError` to show
|
|
264
|
-
that condition. `flush()` is the explicit persistence/catch-up boundary. The
|
|
265
|
-
attachment does not own or dispose the runtime and it does not expose an undo
|
|
266
|
-
API: use `runtime.history.undo()` and `runtime.history.redo()` while the tab is
|
|
267
|
-
leader. Runtime history is intentionally in-memory only; attachment hydration
|
|
268
|
-
and remote tail replay invalidate it, so it is not transferred across reopening
|
|
269
|
-
or leader handoff.
|
|
270
|
-
|
|
271
|
-
## Subscribe through schema-owned targets
|
|
272
|
-
|
|
273
|
-
Create stable selectors from the schema, then subscribe to them. This is the
|
|
274
|
-
shared address and impact model for the whole runtime.
|
|
3
|
+
## Define, Update And Read
|
|
275
4
|
|
|
276
5
|
```ts
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
const
|
|
281
|
-
|
|
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 } } },
|
|
282
14
|
});
|
|
283
|
-
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
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: [] };
|
|
289
20
|
});
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
For value selectors, use `commit.impact.affects(target)`. For table or map
|
|
296
|
-
selectors, use `commit.impact.collection(selector)`, which returns either a
|
|
297
|
-
precise incremental change or `reset` after replacement. Do not recreate path
|
|
298
|
-
comparison helpers in application modules.
|
|
299
|
-
|
|
300
|
-
## Build derived read models
|
|
301
|
-
|
|
302
|
-
```ts
|
|
303
|
-
const projection = createProjectionRuntime({ onError: error => console.error(error) });
|
|
304
|
-
const document = projection.document(runtime);
|
|
305
|
-
const notes = document.collection(path => path.notes);
|
|
306
|
-
const noteSummaries = projection.map(
|
|
307
|
-
notes,
|
|
308
|
-
(id, note) => ({ id, preview: note.body.get().slice(0, 80) }),
|
|
309
|
-
{ isEqual: (a, b) => a.id === b.id && a.preview === b.preview }
|
|
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)
|
|
310
26
|
);
|
|
311
|
-
const noteCount = projection.value({ notes }, ({ notes }) => notes.read.ids().length);
|
|
312
27
|
```
|
|
313
28
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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.
|