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
package/dist/local-sync.d.ts
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Q as ObjectNode, l as DocumentRuntime, v as Readable } from "./contract-BNStLbSE.js";
|
|
2
2
|
|
|
3
3
|
//#region core/src/local-sync/json.d.ts
|
|
4
4
|
type JsonPrimitive = null | boolean | number | string;
|
|
5
5
|
type JsonValue = JsonPrimitive | readonly JsonValue[] | {
|
|
6
6
|
readonly [key: string]: JsonValue;
|
|
7
7
|
};
|
|
8
|
-
type
|
|
9
|
-
readonly
|
|
8
|
+
type JsonChangeLimits = {
|
|
9
|
+
readonly maxChanges?: number;
|
|
10
10
|
readonly maxBytes?: number;
|
|
11
11
|
readonly maxDepth?: number;
|
|
12
12
|
readonly maxStringLength?: number;
|
|
13
13
|
};
|
|
14
14
|
type ResolvedJsonLimits = {
|
|
15
|
-
readonly
|
|
15
|
+
readonly maxChanges: number;
|
|
16
16
|
readonly maxBytes: number;
|
|
17
17
|
readonly maxDepth: number;
|
|
18
18
|
readonly maxStringLength: number;
|
|
19
19
|
};
|
|
20
|
-
declare const
|
|
20
|
+
declare const defaultJsonChangeLimits: Readonly<ResolvedJsonLimits>;
|
|
21
21
|
declare class LocalSyncDataError extends Error {
|
|
22
22
|
constructor(message: string);
|
|
23
23
|
}
|
|
@@ -58,17 +58,17 @@ type LocalSync = {
|
|
|
58
58
|
flush(): Promise<void>;
|
|
59
59
|
dispose(): Promise<void>;
|
|
60
60
|
};
|
|
61
|
-
type AttachLocalSyncOptions<TSchema extends
|
|
61
|
+
type AttachLocalSyncOptions<TSchema extends ObjectNode> = {
|
|
62
62
|
readonly runtime: DocumentRuntime<TSchema>;
|
|
63
63
|
readonly database: string;
|
|
64
64
|
readonly documentId: string;
|
|
65
|
-
readonly schemaVersion?: number;
|
|
66
|
-
readonly
|
|
65
|
+
readonly schemaVersion?: number; /** Admission limits for new local commits; durable replay does not reapply them. */
|
|
66
|
+
readonly changeLimits?: JsonChangeLimits;
|
|
67
67
|
readonly onError?: (error: unknown) => void;
|
|
68
68
|
};
|
|
69
69
|
//#endregion
|
|
70
70
|
//#region core/src/local-sync/session.d.ts
|
|
71
|
-
declare const attachLocalSync: <TSchema extends
|
|
71
|
+
declare const attachLocalSync: <TSchema extends ObjectNode>(input: AttachLocalSyncOptions<TSchema>) => Promise<LocalSync>;
|
|
72
72
|
//#endregion
|
|
73
|
-
export { type AttachLocalSyncOptions, type
|
|
73
|
+
export { type AttachLocalSyncOptions, type JsonChangeLimits, type JsonPrimitive, type JsonValue, type LocalSync, LocalSyncConsistencyError, LocalSyncDataError, LocalSyncDisposedError, LocalSyncReadOnlyError, LocalSyncSchemaError, type LocalSyncState, LocalSyncUnavailableError, LocalSyncUnsupportedOperationError, attachLocalSync, defaultJsonChangeLimits };
|
|
74
74
|
//# sourceMappingURL=local-sync.d.ts.map
|
package/dist/local-sync.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { i as installRuntimeWriteDriver } from "./driver-
|
|
1
|
+
import { T as isRecord, w as isPlainObject } from "./issue-DVaGQGeP.js";
|
|
2
|
+
import { a as changeCount, i as installRuntimeWriteDriver, o as decodeChanges } from "./driver-BlR81Dqg.js";
|
|
3
3
|
//#region core/src/local-sync/contract.ts
|
|
4
4
|
var LocalSyncUnavailableError = class extends Error {
|
|
5
5
|
constructor(capability) {
|
|
@@ -27,7 +27,7 @@ var LocalSyncReadOnlyError = class extends Error {
|
|
|
27
27
|
};
|
|
28
28
|
var LocalSyncUnsupportedOperationError = class extends Error {
|
|
29
29
|
constructor() {
|
|
30
|
-
super("Local sync
|
|
30
|
+
super("Local sync appends committed changes. runtime.replace() and externally supplied remote changes are unavailable while it is attached.");
|
|
31
31
|
this.name = "LocalSyncUnsupportedOperationError";
|
|
32
32
|
}
|
|
33
33
|
};
|
|
@@ -39,8 +39,8 @@ var LocalSyncDisposedError = class extends Error {
|
|
|
39
39
|
};
|
|
40
40
|
//#endregion
|
|
41
41
|
//#region core/src/local-sync/json.ts
|
|
42
|
-
const
|
|
43
|
-
|
|
42
|
+
const defaultJsonChangeLimits = Object.freeze({
|
|
43
|
+
maxChanges: 1e3,
|
|
44
44
|
maxBytes: 1e6,
|
|
45
45
|
maxDepth: 64,
|
|
46
46
|
maxStringLength: 256e3
|
|
@@ -58,10 +58,10 @@ const limit = (value, fallback, label) => {
|
|
|
58
58
|
throw new TypeError(`${label} must be a positive safe integer.`);
|
|
59
59
|
};
|
|
60
60
|
const resolveLimits = (input) => ({
|
|
61
|
-
|
|
62
|
-
maxBytes: limit(input?.maxBytes,
|
|
63
|
-
maxDepth: limit(input?.maxDepth,
|
|
64
|
-
maxStringLength: limit(input?.maxStringLength,
|
|
61
|
+
maxChanges: limit(input?.maxChanges, defaultJsonChangeLimits.maxChanges, "maxChanges"),
|
|
62
|
+
maxBytes: limit(input?.maxBytes, defaultJsonChangeLimits.maxBytes, "maxBytes"),
|
|
63
|
+
maxDepth: limit(input?.maxDepth, defaultJsonChangeLimits.maxDepth, "maxDepth"),
|
|
64
|
+
maxStringLength: limit(input?.maxStringLength, defaultJsonChangeLimits.maxStringLength, "maxStringLength")
|
|
65
65
|
});
|
|
66
66
|
const validate = (value, path, limits, depth, ancestors) => {
|
|
67
67
|
if (limits && depth > limits.maxDepth) throw new LocalSyncDataError(`${describePath(path)} exceeds the maximum JSON depth.`);
|
|
@@ -78,7 +78,11 @@ const validate = (value, path, limits, depth, ancestors) => {
|
|
|
78
78
|
if (ancestors.has(value)) throw new LocalSyncDataError(`${describePath(path)} contains a cycle.`);
|
|
79
79
|
ancestors.add(value);
|
|
80
80
|
try {
|
|
81
|
-
for (let index = 0; index < value.length; index += 1)
|
|
81
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
82
|
+
path.push(String(index));
|
|
83
|
+
validate(value[index], path, limits, depth + 1, ancestors);
|
|
84
|
+
path.pop();
|
|
85
|
+
}
|
|
82
86
|
} finally {
|
|
83
87
|
ancestors.delete(value);
|
|
84
88
|
}
|
|
@@ -88,7 +92,11 @@ const validate = (value, path, limits, depth, ancestors) => {
|
|
|
88
92
|
if (ancestors.has(value)) throw new LocalSyncDataError(`${describePath(path)} contains a cycle.`);
|
|
89
93
|
ancestors.add(value);
|
|
90
94
|
try {
|
|
91
|
-
for (const
|
|
95
|
+
for (const key of Object.keys(value)) {
|
|
96
|
+
path.push(key);
|
|
97
|
+
validate(value[key], path, limits, depth + 1, ancestors);
|
|
98
|
+
path.pop();
|
|
99
|
+
}
|
|
92
100
|
} finally {
|
|
93
101
|
ancestors.delete(value);
|
|
94
102
|
}
|
|
@@ -97,21 +105,28 @@ const validate = (value, path, limits, depth, ancestors) => {
|
|
|
97
105
|
throw new LocalSyncDataError(`${describePath(path)} must be JSON data.`);
|
|
98
106
|
};
|
|
99
107
|
const json = (value, label) => validate(value, [label], void 0, 0, /* @__PURE__ */ new WeakSet());
|
|
100
|
-
const
|
|
101
|
-
const limits = resolveLimits(input);
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
+
const jsonChanges = (value, label, input) => {
|
|
109
|
+
const limits = input === void 0 ? void 0 : resolveLimits(input);
|
|
110
|
+
let changes;
|
|
111
|
+
try {
|
|
112
|
+
changes = decodeChanges(value);
|
|
113
|
+
} catch {
|
|
114
|
+
throw new LocalSyncDataError(`${label} contains an invalid ChangeSet.`);
|
|
115
|
+
}
|
|
116
|
+
if (limits && changeCount(changes) > limits.maxChanges) throw new LocalSyncDataError(`${label} exceeds the maximum change count.`);
|
|
117
|
+
validate(changes.changes, [label], limits, 0, /* @__PURE__ */ new WeakSet());
|
|
118
|
+
if (limits) {
|
|
119
|
+
const serialized = JSON.stringify(changes.changes);
|
|
120
|
+
if (new TextEncoder().encode(serialized).byteLength > limits.maxBytes) throw new LocalSyncDataError(`${label} exceeds the maximum ChangeSet size.`);
|
|
121
|
+
}
|
|
122
|
+
return changes;
|
|
108
123
|
};
|
|
109
124
|
//#endregion
|
|
110
125
|
//#region core/src/local-sync/timeline.ts
|
|
111
|
-
const DATABASE_VERSION =
|
|
126
|
+
const DATABASE_VERSION = 5;
|
|
127
|
+
const FORMAT_VERSION = 3;
|
|
112
128
|
const DOCUMENTS = "documents";
|
|
113
129
|
const COMMITS = "commits";
|
|
114
|
-
const ACTORS = "actors";
|
|
115
130
|
const request = (value) => new Promise((resolve, reject) => {
|
|
116
131
|
value.onsuccess = () => resolve(value.result);
|
|
117
132
|
value.onerror = () => reject(value.error ?? /* @__PURE__ */ new Error("IndexedDB request failed."));
|
|
@@ -143,11 +158,12 @@ const positiveInteger$1 = (value, label) => {
|
|
|
143
158
|
throw new LocalSyncConsistencyError(`${label} is malformed in IndexedDB.`);
|
|
144
159
|
};
|
|
145
160
|
const documentRecord = (value) => {
|
|
146
|
-
if (!isRecord(value)) throw new LocalSyncConsistencyError("Document record is malformed in IndexedDB.");
|
|
161
|
+
if (!isRecord(value) || value.formatVersion !== FORMAT_VERSION) throw new LocalSyncConsistencyError("Document record is malformed in IndexedDB.");
|
|
147
162
|
const checkpointSeq = nonNegativeInteger(value.checkpointSeq, "document.checkpointSeq");
|
|
148
163
|
const headSeq = nonNegativeInteger(value.headSeq, "document.headSeq");
|
|
149
164
|
if (checkpointSeq > headSeq) throw new LocalSyncConsistencyError("Document checkpoint exceeds its head sequence.");
|
|
150
165
|
return Object.freeze({
|
|
166
|
+
formatVersion: FORMAT_VERSION,
|
|
151
167
|
documentId: string(value.documentId, "document.documentId"),
|
|
152
168
|
schemaVersion: positiveInteger$1(value.schemaVersion, "document.schemaVersion"),
|
|
153
169
|
checkpointSeq,
|
|
@@ -156,24 +172,30 @@ const documentRecord = (value) => {
|
|
|
156
172
|
});
|
|
157
173
|
};
|
|
158
174
|
const commitRecord = (value) => {
|
|
159
|
-
if (!isRecord(value)) throw new LocalSyncConsistencyError("Commit record is malformed in IndexedDB.");
|
|
175
|
+
if (!isRecord(value) || value.formatVersion !== FORMAT_VERSION) throw new LocalSyncConsistencyError("Commit record is malformed in IndexedDB.");
|
|
160
176
|
return Object.freeze({
|
|
177
|
+
formatVersion: FORMAT_VERSION,
|
|
161
178
|
documentId: string(value.documentId, "commit.documentId"),
|
|
162
179
|
seq: positiveInteger$1(value.seq, "commit.seq"),
|
|
163
|
-
|
|
180
|
+
changes: jsonChanges({ changes: value.changes }, "commit.changes"),
|
|
164
181
|
createdAt: nonNegativeInteger(value.createdAt, "commit.createdAt")
|
|
165
182
|
});
|
|
166
183
|
};
|
|
167
184
|
const openIndexedDbTimeline = async (databaseName) => {
|
|
168
185
|
const database = await new Promise((resolve, reject) => {
|
|
186
|
+
let incompatible = false;
|
|
169
187
|
const open = factory().open(databaseName, DATABASE_VERSION);
|
|
170
|
-
open.onerror = () => reject(open.error ?? /* @__PURE__ */ new Error("Unable to open IndexedDB."));
|
|
188
|
+
open.onerror = () => reject(incompatible ? new LocalSyncConsistencyError("Unsupported Doxum storage format. Existing data was left unchanged.") : open.error ?? /* @__PURE__ */ new Error("Unable to open IndexedDB."));
|
|
171
189
|
open.onblocked = () => reject(/* @__PURE__ */ new Error(`IndexedDB database '${databaseName}' is blocked.`));
|
|
172
|
-
open.onupgradeneeded = () => {
|
|
190
|
+
open.onupgradeneeded = (event) => {
|
|
191
|
+
if (event.oldVersion !== 0) {
|
|
192
|
+
incompatible = true;
|
|
193
|
+
open.transaction?.abort();
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
173
196
|
const db = open.result;
|
|
174
197
|
if (!db.objectStoreNames.contains(DOCUMENTS)) db.createObjectStore(DOCUMENTS, { keyPath: "documentId" });
|
|
175
198
|
if (!db.objectStoreNames.contains(COMMITS)) db.createObjectStore(COMMITS, { keyPath: ["documentId", "seq"] });
|
|
176
|
-
if (db.objectStoreNames.contains(ACTORS)) db.deleteObjectStore(ACTORS);
|
|
177
199
|
};
|
|
178
200
|
open.onsuccess = () => resolve(open.result);
|
|
179
201
|
});
|
|
@@ -194,6 +216,7 @@ const openIndexedDbTimeline = async (databaseName) => {
|
|
|
194
216
|
return record;
|
|
195
217
|
}
|
|
196
218
|
const record = {
|
|
219
|
+
formatVersion: FORMAT_VERSION,
|
|
197
220
|
documentId,
|
|
198
221
|
schemaVersion,
|
|
199
222
|
checkpointSeq: 0,
|
|
@@ -221,18 +244,22 @@ const openIndexedDbTimeline = async (databaseName) => {
|
|
|
221
244
|
const transaction = database.transaction([DOCUMENTS, COMMITS], "readwrite");
|
|
222
245
|
const documents = transaction.objectStore(DOCUMENTS);
|
|
223
246
|
const current = await readDocument(transaction, input.documentId);
|
|
224
|
-
if (current.headSeq !== input.expectedHeadSeq) throw new LocalSyncConsistencyError("Local document advanced before the leader could append its
|
|
247
|
+
if (current.headSeq !== input.expectedHeadSeq) throw new LocalSyncConsistencyError("Local document advanced before the leader could append its commit.");
|
|
225
248
|
const commit = {
|
|
249
|
+
formatVersion: FORMAT_VERSION,
|
|
226
250
|
documentId: input.documentId,
|
|
227
251
|
seq: current.headSeq + 1,
|
|
228
|
-
|
|
252
|
+
changes: input.changes,
|
|
229
253
|
createdAt: Date.now()
|
|
230
254
|
};
|
|
231
255
|
documents.put({
|
|
232
256
|
...current,
|
|
233
257
|
headSeq: commit.seq
|
|
234
258
|
});
|
|
235
|
-
transaction.objectStore(COMMITS).put(
|
|
259
|
+
transaction.objectStore(COMMITS).put({
|
|
260
|
+
...commit,
|
|
261
|
+
changes: commit.changes.changes
|
|
262
|
+
});
|
|
236
263
|
await complete(transaction);
|
|
237
264
|
return Object.freeze(commit);
|
|
238
265
|
},
|
|
@@ -339,10 +366,11 @@ const attachLocalSync = async (input) => {
|
|
|
339
366
|
const runRuntime = (run) => driver?.run(run) ?? run();
|
|
340
367
|
const applyStored = (commit) => {
|
|
341
368
|
if (commit.seq !== headSeq + 1) throw new LocalSyncConsistencyError("Local commit log contains a sequence gap.");
|
|
342
|
-
if (runRuntime(() => runtime.apply(commit.
|
|
369
|
+
if (runRuntime(() => runtime.apply(commit.changes, {
|
|
370
|
+
expectedRevision: runtime.revision(),
|
|
343
371
|
source: "remote",
|
|
344
372
|
history: false
|
|
345
|
-
})).status !== "committed") throw new LocalSyncConsistencyError("A stored local
|
|
373
|
+
})).status !== "committed") throw new LocalSyncConsistencyError("A stored local ChangeSet could not be applied.");
|
|
346
374
|
headSeq = commit.seq;
|
|
347
375
|
};
|
|
348
376
|
const restore = async (reset = false) => {
|
|
@@ -366,13 +394,13 @@ const attachLocalSync = async (input) => {
|
|
|
366
394
|
});
|
|
367
395
|
return next;
|
|
368
396
|
};
|
|
369
|
-
const persist = async (
|
|
397
|
+
const persist = async (changes) => {
|
|
370
398
|
const storedCommit = await timeline.append({
|
|
371
399
|
documentId,
|
|
372
400
|
expectedHeadSeq: headSeq,
|
|
373
|
-
|
|
401
|
+
changes
|
|
374
402
|
});
|
|
375
|
-
if (storedCommit.seq !== headSeq + 1) throw new LocalSyncConsistencyError("A local
|
|
403
|
+
if (storedCommit.seq !== headSeq + 1) throw new LocalSyncConsistencyError("A local commit was assigned an unexpected sequence.");
|
|
376
404
|
headSeq = storedCommit.seq;
|
|
377
405
|
publishState();
|
|
378
406
|
channel?.postMessage({
|
|
@@ -383,8 +411,8 @@ const attachLocalSync = async (input) => {
|
|
|
383
411
|
};
|
|
384
412
|
const record = (commit) => {
|
|
385
413
|
try {
|
|
386
|
-
const
|
|
387
|
-
enqueue(() => persist(
|
|
414
|
+
const changes = jsonChanges(commit.changes, "local commit changes", input.changeLimits ?? {});
|
|
415
|
+
enqueue(() => persist(changes)).catch(() => void 0);
|
|
388
416
|
} catch (error) {
|
|
389
417
|
fail(error);
|
|
390
418
|
}
|
|
@@ -510,6 +538,6 @@ const attachLocalSync = async (input) => {
|
|
|
510
538
|
}
|
|
511
539
|
};
|
|
512
540
|
//#endregion
|
|
513
|
-
export { LocalSyncConsistencyError, LocalSyncDataError, LocalSyncDisposedError, LocalSyncReadOnlyError, LocalSyncSchemaError, LocalSyncUnavailableError, LocalSyncUnsupportedOperationError, attachLocalSync,
|
|
541
|
+
export { LocalSyncConsistencyError, LocalSyncDataError, LocalSyncDisposedError, LocalSyncReadOnlyError, LocalSyncSchemaError, LocalSyncUnavailableError, LocalSyncUnsupportedOperationError, attachLocalSync, defaultJsonChangeLimits };
|
|
514
542
|
|
|
515
543
|
//# sourceMappingURL=local-sync.js.map
|
package/dist/local-sync.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-sync.js","names":["positiveInteger"],"sources":["../core/src/local-sync/contract.ts","../core/src/local-sync/json.ts","../core/src/local-sync/timeline.ts","../core/src/local-sync/session.ts"],"sourcesContent":["import type { DocumentSchema } from '../schema';\nimport type { DocumentRuntime } from '../runtime/contract';\nimport type { JsonCommandLimits } from './json';\nimport type { Readable } from '../projection/readable';\n\nexport class LocalSyncUnavailableError extends Error {\n constructor(capability: 'IndexedDB' | 'Web Locks' | 'BroadcastChannel') {\n super(`${capability} is required by doxum/local-sync in this environment.`);\n this.name = 'LocalSyncUnavailableError';\n }\n}\n\nexport class LocalSyncSchemaError extends Error {\n constructor(documentId: string, expected: number, actual: number) {\n super(\n `Local document '${documentId}' uses schema version ${actual}, but version ${expected} was requested.`\n );\n this.name = 'LocalSyncSchemaError';\n }\n}\n\nexport class LocalSyncConsistencyError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'LocalSyncConsistencyError';\n }\n}\n\nexport class LocalSyncReadOnlyError extends Error {\n constructor() {\n super('This tab is following the local document and cannot write until it becomes the leader.');\n this.name = 'LocalSyncReadOnlyError';\n }\n}\n\nexport class LocalSyncUnsupportedOperationError extends Error {\n constructor() {\n super(\n 'Local sync persists operation commands. runtime.replace() and externally supplied remote operations are unavailable while it is attached.'\n );\n this.name = 'LocalSyncUnsupportedOperationError';\n }\n}\n\nexport class LocalSyncDisposedError extends Error {\n constructor() {\n super('Local sync has been disposed.');\n this.name = 'LocalSyncDisposedError';\n }\n}\n\nexport type LocalSyncState =\n | {\n readonly status: 'leader' | 'follower';\n readonly headSeq: number;\n readonly checkpointSeq: number;\n }\n | {\n readonly status: 'error';\n readonly headSeq: number;\n readonly checkpointSeq: number;\n readonly error: unknown;\n }\n | { readonly status: 'disposed' };\n\nexport type LocalSync = {\n readonly state: Readable<LocalSyncState>;\n flush(): Promise<void>;\n dispose(): Promise<void>;\n};\n\nexport type AttachLocalSyncOptions<TSchema extends DocumentSchema> = {\n readonly runtime: DocumentRuntime<TSchema>;\n readonly database: string;\n readonly documentId: string;\n readonly schemaVersion?: number;\n readonly commandLimits?: JsonCommandLimits;\n readonly onError?: (error: unknown) => void;\n};\n","import { isPlainObject } from '../value/ownership';\n\nexport type JsonPrimitive = null | boolean | number | string;\nexport type JsonValue =\n JsonPrimitive | readonly JsonValue[] | { readonly [key: string]: JsonValue };\n\nexport type JsonCommandLimits = {\n readonly maxOperations?: number;\n readonly maxBytes?: number;\n readonly maxDepth?: number;\n readonly maxStringLength?: number;\n};\n\ntype ResolvedJsonLimits = {\n readonly maxOperations: number;\n readonly maxBytes: number;\n readonly maxDepth: number;\n readonly maxStringLength: number;\n};\n\nexport const defaultJsonCommandLimits: Readonly<ResolvedJsonLimits> = Object.freeze({\n maxOperations: 1_000,\n maxBytes: 1_000_000,\n maxDepth: 64,\n maxStringLength: 256_000,\n});\n\nexport class LocalSyncDataError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'LocalSyncDataError';\n }\n}\n\nconst describePath = (path: readonly string[]): string =>\n path.length === 0 ? 'value' : path.join('.');\n\nconst limit = (value: number | undefined, fallback: number, label: string): number => {\n if (value === undefined) return fallback;\n if (Number.isSafeInteger(value) && value > 0) return value;\n throw new TypeError(`${label} must be a positive safe integer.`);\n};\n\nconst resolveLimits = (input: JsonCommandLimits | undefined): ResolvedJsonLimits => ({\n maxOperations: limit(\n input?.maxOperations,\n defaultJsonCommandLimits.maxOperations,\n 'maxOperations'\n ),\n maxBytes: limit(input?.maxBytes, defaultJsonCommandLimits.maxBytes, 'maxBytes'),\n maxDepth: limit(input?.maxDepth, defaultJsonCommandLimits.maxDepth, 'maxDepth'),\n maxStringLength: limit(\n input?.maxStringLength,\n defaultJsonCommandLimits.maxStringLength,\n 'maxStringLength'\n ),\n});\n\nconst validate = (\n value: unknown,\n path: readonly string[],\n limits: ResolvedJsonLimits | undefined,\n depth: number,\n ancestors: WeakSet<object>\n): JsonValue => {\n if (limits && depth > limits.maxDepth)\n throw new LocalSyncDataError(`${describePath(path)} exceeds the maximum JSON depth.`);\n if (value === null || typeof value === 'boolean') return value;\n if (typeof value === 'string') {\n if (!limits || value.length <= limits.maxStringLength) return value;\n throw new LocalSyncDataError(`${describePath(path)} exceeds the maximum string length.`);\n }\n if (typeof value === 'number') {\n if (Number.isFinite(value)) return value;\n throw new LocalSyncDataError(`${describePath(path)} contains a non-finite number.`);\n }\n if (Array.isArray(value)) {\n if (ancestors.has(value))\n throw new LocalSyncDataError(`${describePath(path)} contains a cycle.`);\n ancestors.add(value);\n try {\n for (let index = 0; index < value.length; index += 1)\n validate(value[index], [...path, String(index)], limits, depth + 1, ancestors);\n } finally {\n ancestors.delete(value);\n }\n return value as readonly JsonValue[];\n }\n if (isPlainObject(value)) {\n if (ancestors.has(value))\n throw new LocalSyncDataError(`${describePath(path)} contains a cycle.`);\n ancestors.add(value);\n try {\n for (const [key, entry] of Object.entries(value))\n validate(entry, [...path, key], limits, depth + 1, ancestors);\n } finally {\n ancestors.delete(value);\n }\n return value as { readonly [key: string]: JsonValue };\n }\n throw new LocalSyncDataError(`${describePath(path)} must be JSON data.`);\n};\n\nexport const json = (value: unknown, label: string): JsonValue =>\n validate(value, [label], undefined, 0, new WeakSet());\n\nexport const jsonArray = (\n value: unknown,\n label: string,\n input?: JsonCommandLimits\n): readonly JsonValue[] => {\n const limits = resolveLimits(input);\n const parsed = validate(value, [label], limits, 0, new WeakSet());\n if (!Array.isArray(parsed)) throw new LocalSyncDataError(`${label} must be a JSON array.`);\n if (parsed.length > limits.maxOperations)\n throw new LocalSyncDataError(`${label} exceeds the maximum operation count.`);\n const serialized = JSON.stringify(parsed);\n if (new TextEncoder().encode(serialized).byteLength > limits.maxBytes)\n throw new LocalSyncDataError(`${label} exceeds the maximum command size.`);\n return parsed;\n};\n","import {\n LocalSyncConsistencyError,\n LocalSyncSchemaError,\n LocalSyncUnavailableError,\n} from './contract';\nimport { json, jsonArray, type JsonValue } from './json';\nimport { isRecord } from '../value/ownership';\n\nconst DATABASE_VERSION = 2;\nconst DOCUMENTS = 'documents';\nconst COMMITS = 'commits';\nconst ACTORS = 'actors';\n\nexport type StoredDocument = {\n readonly documentId: string;\n readonly schemaVersion: number;\n readonly checkpointSeq: number;\n readonly headSeq: number;\n readonly checkpoint: JsonValue;\n};\n\nexport type StoredCommit = {\n readonly documentId: string;\n readonly seq: number;\n readonly operations: readonly JsonValue[];\n readonly createdAt: number;\n};\n\nconst request = <T>(value: IDBRequest<T>): Promise<T> =>\n new Promise((resolve, reject) => {\n value.onsuccess = () => resolve(value.result);\n value.onerror = () => reject(value.error ?? new Error('IndexedDB request failed.'));\n });\n\nconst complete = (transaction: IDBTransaction): Promise<void> =>\n new Promise((resolve, reject) => {\n transaction.oncomplete = () => resolve();\n transaction.onabort = () =>\n reject(transaction.error ?? new Error('IndexedDB transaction was aborted.'));\n transaction.onerror = () =>\n reject(transaction.error ?? new Error('IndexedDB transaction failed.'));\n });\n\nconst factory = (): IDBFactory => {\n if (!globalThis.indexedDB) throw new LocalSyncUnavailableError('IndexedDB');\n return globalThis.indexedDB;\n};\n\nconst keyRange = (): typeof IDBKeyRange => {\n if (!globalThis.IDBKeyRange) throw new LocalSyncUnavailableError('IndexedDB');\n return globalThis.IDBKeyRange;\n};\n\nconst string = (value: unknown, label: string): string => {\n if (typeof value === 'string') return value;\n throw new LocalSyncConsistencyError(`${label} is malformed in IndexedDB.`);\n};\n\nconst nonNegativeInteger = (value: unknown, label: string): number => {\n if (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0) return value;\n throw new LocalSyncConsistencyError(`${label} is malformed in IndexedDB.`);\n};\n\nconst positiveInteger = (value: unknown, label: string): number => {\n const parsed = nonNegativeInteger(value, label);\n if (parsed > 0) return parsed;\n throw new LocalSyncConsistencyError(`${label} is malformed in IndexedDB.`);\n};\n\nconst documentRecord = (value: unknown): StoredDocument => {\n if (!isRecord(value))\n throw new LocalSyncConsistencyError('Document record is malformed in IndexedDB.');\n const checkpointSeq = nonNegativeInteger(value.checkpointSeq, 'document.checkpointSeq');\n const headSeq = nonNegativeInteger(value.headSeq, 'document.headSeq');\n if (checkpointSeq > headSeq)\n throw new LocalSyncConsistencyError('Document checkpoint exceeds its head sequence.');\n return Object.freeze({\n documentId: string(value.documentId, 'document.documentId'),\n schemaVersion: positiveInteger(value.schemaVersion, 'document.schemaVersion'),\n checkpointSeq,\n headSeq,\n checkpoint: json(value.checkpoint, 'document.checkpoint'),\n });\n};\n\nconst commitRecord = (value: unknown): StoredCommit => {\n if (!isRecord(value))\n throw new LocalSyncConsistencyError('Commit record is malformed in IndexedDB.');\n return Object.freeze({\n documentId: string(value.documentId, 'commit.documentId'),\n seq: positiveInteger(value.seq, 'commit.seq'),\n operations: jsonArray(value.operations, 'commit.operations'),\n createdAt: nonNegativeInteger(value.createdAt, 'commit.createdAt'),\n });\n};\n\nexport type IndexedDbTimeline = {\n readonly initialize: (\n documentId: string,\n schemaVersion: number,\n checkpoint: JsonValue\n ) => Promise<StoredDocument>;\n readonly read: (documentId: string) => Promise<StoredDocument>;\n readonly tail: (documentId: string, afterSeq: number) => Promise<readonly StoredCommit[]>;\n readonly append: (input: {\n readonly documentId: string;\n readonly expectedHeadSeq: number;\n readonly operations: readonly JsonValue[];\n }) => Promise<StoredCommit>;\n readonly close: () => void;\n};\n\nexport const openIndexedDbTimeline = async (databaseName: string): Promise<IndexedDbTimeline> => {\n const database = await new Promise<IDBDatabase>((resolve, reject) => {\n const open = factory().open(databaseName, DATABASE_VERSION);\n open.onerror = () => reject(open.error ?? new Error('Unable to open IndexedDB.'));\n open.onblocked = () => reject(new Error(`IndexedDB database '${databaseName}' is blocked.`));\n open.onupgradeneeded = () => {\n const db = open.result;\n if (!db.objectStoreNames.contains(DOCUMENTS))\n db.createObjectStore(DOCUMENTS, { keyPath: 'documentId' });\n if (!db.objectStoreNames.contains(COMMITS))\n db.createObjectStore(COMMITS, { keyPath: ['documentId', 'seq'] });\n if (db.objectStoreNames.contains(ACTORS)) db.deleteObjectStore(ACTORS);\n };\n open.onsuccess = () => resolve(open.result);\n });\n\n const readDocument = async (\n transaction: IDBTransaction,\n documentId: string\n ): Promise<StoredDocument> => {\n const value = await request(transaction.objectStore(DOCUMENTS).get(documentId));\n if (value === undefined)\n throw new LocalSyncConsistencyError(`Local document '${documentId}' does not exist.`);\n return documentRecord(value);\n };\n\n return {\n initialize: async (documentId, schemaVersion, checkpoint) => {\n const transaction = database.transaction(DOCUMENTS, 'readwrite');\n const store = transaction.objectStore(DOCUMENTS);\n const existing = await request(store.get(documentId));\n if (existing !== undefined) {\n const record = documentRecord(existing);\n if (record.schemaVersion !== schemaVersion)\n throw new LocalSyncSchemaError(documentId, schemaVersion, record.schemaVersion);\n await complete(transaction);\n return record;\n }\n const record: StoredDocument = {\n documentId,\n schemaVersion,\n checkpointSeq: 0,\n headSeq: 0,\n checkpoint,\n };\n store.put(record);\n await complete(transaction);\n return Object.freeze(record);\n },\n read: async documentId => {\n const transaction = database.transaction(DOCUMENTS, 'readonly');\n const record = await readDocument(transaction, documentId);\n await complete(transaction);\n return record;\n },\n tail: async (documentId, afterSeq) => {\n const transaction = database.transaction(COMMITS, 'readonly');\n const range = keyRange().bound(\n [documentId, afterSeq + 1],\n [documentId, Number.MAX_SAFE_INTEGER]\n );\n const values = await request(transaction.objectStore(COMMITS).getAll(range));\n await complete(transaction);\n return Object.freeze(values.map(commitRecord).sort((left, right) => left.seq - right.seq));\n },\n append: async input => {\n const transaction = database.transaction([DOCUMENTS, COMMITS], 'readwrite');\n const documents = transaction.objectStore(DOCUMENTS);\n const current = await readDocument(transaction, input.documentId);\n if (current.headSeq !== input.expectedHeadSeq)\n throw new LocalSyncConsistencyError(\n 'Local document advanced before the leader could append its command.'\n );\n const commit: StoredCommit = {\n documentId: input.documentId,\n seq: current.headSeq + 1,\n operations: input.operations,\n createdAt: Date.now(),\n };\n documents.put({ ...current, headSeq: commit.seq });\n transaction.objectStore(COMMITS).put(commit);\n await complete(transaction);\n return Object.freeze(commit);\n },\n close: () => database.close(),\n };\n};\n","import {\n installRuntimeWriteDriver,\n type RuntimeWriteDriverLease,\n type RuntimeWriteIntent,\n} from '../runtime/driver';\nimport type { DocumentCommit, DocumentRuntime, OperationResult } from '../runtime/contract';\nimport type { DocumentSchema, ReadonlyDocument } from '../schema';\nimport {\n LocalSyncConsistencyError,\n LocalSyncDisposedError,\n LocalSyncReadOnlyError,\n LocalSyncUnsupportedOperationError,\n LocalSyncUnavailableError,\n type AttachLocalSyncOptions,\n type LocalSync,\n type LocalSyncState,\n} from './contract';\nimport { json, jsonArray, type JsonValue } from './json';\nimport { openIndexedDbTimeline, type StoredCommit } from './timeline';\n\ntype LockOptions = {\n readonly mode: 'exclusive';\n readonly ifAvailable?: boolean;\n};\n\ntype LockManager = {\n readonly request: <TResult>(\n name: string,\n options: LockOptions,\n callback: (lock: unknown | null) => TResult | PromiseLike<TResult>\n ) => Promise<TResult>;\n};\n\ntype Channel = {\n onmessage: ((event: MessageEvent<unknown>) => void) | null;\n postMessage(message: unknown): void;\n close(): void;\n};\n\ntype ChannelConstructor = new (name: string) => Channel;\n\ntype CommitNotification = {\n readonly kind: 'commit';\n readonly documentId: string;\n readonly headSeq: number;\n};\n\nconst locks = (): LockManager => {\n const value: unknown = globalThis.navigator?.locks;\n if (\n typeof value !== 'object' ||\n value === null ||\n !('request' in value) ||\n typeof value.request !== 'function'\n )\n throw new LocalSyncUnavailableError('Web Locks');\n return value as LockManager;\n};\n\nconst channelConstructor = (): ChannelConstructor => {\n const value: unknown = globalThis.BroadcastChannel;\n if (typeof value !== 'function') throw new LocalSyncUnavailableError('BroadcastChannel');\n return value as ChannelConstructor;\n};\n\nconst requiredString = (value: string, label: string): string => {\n if (value.length > 0) return value;\n throw new TypeError(`${label} must not be empty.`);\n};\n\nconst positiveInteger = (value: number, label: string): number => {\n if (Number.isSafeInteger(value) && value > 0) return value;\n throw new TypeError(`${label} must be a positive safe integer.`);\n};\n\nconst notification = (value: unknown): CommitNotification | undefined => {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined;\n const record = value as Record<string, unknown>;\n if (\n record.kind !== 'commit' ||\n typeof record.documentId !== 'string' ||\n typeof record.headSeq !== 'number' ||\n !Number.isSafeInteger(record.headSeq) ||\n record.headSeq < 0\n )\n return undefined;\n return record as CommitNotification;\n};\n\nexport const attachLocalSync = async <TSchema extends DocumentSchema>(\n input: AttachLocalSyncOptions<TSchema>\n): Promise<LocalSync> => {\n const runtime = input.runtime;\n const databaseName = requiredString(input.database, 'database');\n const documentId = requiredString(input.documentId, 'documentId');\n const schemaVersion = positiveInteger(input.schemaVersion ?? 1, 'schemaVersion');\n const lockManager = locks();\n const BroadcastChannel = channelConstructor();\n const timeline = await openIndexedDbTimeline(databaseName);\n\n let channel: Channel | undefined;\n let unsubscribe: (() => void) | undefined;\n let driver: RuntimeWriteDriverLease | undefined;\n try {\n const stored = await timeline.initialize(\n documentId,\n schemaVersion,\n json(runtime.snapshot(), 'runtime snapshot')\n );\n let headSeq = stored.checkpointSeq;\n let checkpointSeq = stored.checkpointSeq;\n let role: 'leader' | 'follower' = 'follower';\n let fault: unknown;\n let closing = false;\n let disposed = false;\n let releaseLeadership: (() => void) | undefined;\n let leadershipLease: Promise<void> | undefined;\n let queued: Promise<void> = Promise.resolve();\n let snapshot: LocalSyncState = Object.freeze({ status: role, headSeq, checkpointSeq });\n let stateRevision = 0;\n const stateListeners = new Set<() => void>();\n const publishState = (): void => {\n const next: LocalSyncState = disposed\n ? { status: 'disposed' }\n : fault\n ? { status: 'error', headSeq, checkpointSeq, error: fault }\n : { status: role, headSeq, checkpointSeq };\n if (\n snapshot.status === next.status &&\n (snapshot.status === 'disposed' ||\n (next.status !== 'disposed' &&\n snapshot.headSeq === next.headSeq &&\n snapshot.checkpointSeq === next.checkpointSeq &&\n (snapshot.status !== 'error' ||\n (next.status === 'error' && snapshot.error === next.error))))\n )\n return;\n snapshot = Object.freeze(next);\n stateRevision++;\n for (const listener of [...stateListeners]) {\n if (!stateListeners.has(listener)) continue;\n try {\n listener();\n } catch (error) {\n try {\n input.onError?.(error);\n } catch {\n /* A listener cannot fail persistence. */\n }\n }\n }\n };\n\n const fail = (error: unknown): void => {\n if (fault || disposed) return;\n fault = error;\n publishState();\n try {\n input.onError?.(error);\n } catch {\n // Error reporting cannot repair or replace the original synchronization failure.\n }\n };\n\n const assertOpen = (): void => {\n if (closing || disposed) throw new LocalSyncDisposedError();\n if (fault) throw fault;\n };\n\n const assertLeader = (intent: RuntimeWriteIntent): void => {\n assertOpen();\n if (role !== 'leader') throw new LocalSyncReadOnlyError();\n if (intent.kind === 'replace' || (intent.kind === 'apply' && intent.source === 'remote'))\n throw new LocalSyncUnsupportedOperationError();\n };\n\n const runRuntime = <TResult>(run: () => TResult): TResult => driver?.run(run) ?? run();\n\n const applyStored = (commit: StoredCommit): void => {\n if (commit.seq !== headSeq + 1)\n throw new LocalSyncConsistencyError('Local commit log contains a sequence gap.');\n const result = runRuntime(() =>\n runtime.apply(commit.operations, { source: 'remote', history: false })\n );\n if (result.status !== 'committed')\n throw new LocalSyncConsistencyError('A stored local command could not be applied.');\n headSeq = commit.seq;\n };\n\n const restore = async (reset = false): Promise<void> => {\n const current = await timeline.read(documentId);\n if (current.schemaVersion !== schemaVersion)\n throw new LocalSyncConsistencyError(\n 'Local document schema changed while this attachment was open.'\n );\n if (headSeq > current.headSeq)\n throw new LocalSyncConsistencyError('The local timeline moved behind this attachment.');\n if (reset || headSeq < current.checkpointSeq) {\n const result = runRuntime(() =>\n runtime.replace(current.checkpoint as ReadonlyDocument<TSchema>, { source: 'remote' })\n );\n if (result.status === 'rejected')\n throw new LocalSyncConsistencyError('The local checkpoint could not be restored.');\n headSeq = current.checkpointSeq;\n }\n const tail = await timeline.tail(documentId, headSeq);\n for (const commit of tail) applyStored(commit);\n if (headSeq !== current.headSeq)\n throw new LocalSyncConsistencyError(\n 'Local commit log does not reach its recorded head sequence.'\n );\n checkpointSeq = current.checkpointSeq;\n publishState();\n };\n\n const enqueue = (run: () => Promise<void>): Promise<void> => {\n const next = queued.then(run, run);\n queued = next.then(\n () => undefined,\n error => {\n fail(error);\n }\n );\n return next;\n };\n\n const persist = async (operations: readonly JsonValue[]): Promise<void> => {\n const storedCommit = await timeline.append({\n documentId,\n expectedHeadSeq: headSeq,\n operations,\n });\n if (storedCommit.seq !== headSeq + 1)\n throw new LocalSyncConsistencyError('A local command was assigned an unexpected sequence.');\n headSeq = storedCommit.seq;\n publishState();\n channel?.postMessage({\n kind: 'commit',\n documentId,\n headSeq,\n } satisfies CommitNotification);\n };\n\n const record = (commit: DocumentCommit<TSchema>): void => {\n try {\n const operations = jsonArray(\n commit.operations,\n 'local command operations',\n input.commandLimits\n );\n void enqueue(() => persist(operations)).catch(() => undefined);\n } catch (error) {\n fail(error);\n }\n };\n\n const holdLeadership = (): Promise<void> =>\n new Promise(resolve => {\n releaseLeadership = resolve;\n });\n\n const lead = async (activated?: () => void): Promise<void> => {\n if (closing || disposed || fault) return;\n await restore();\n if (closing || disposed || fault) return;\n role = 'leader';\n publishState();\n activated?.();\n try {\n await holdLeadership();\n } finally {\n releaseLeadership = undefined;\n if (!closing && !disposed && !fault) {\n role = 'follower';\n publishState();\n }\n }\n };\n\n const watchLeadership = (): void => {\n leadershipLease = lockManager\n .request(`doxum:${documentId}`, { mode: 'exclusive' }, async () => {\n try {\n await lead();\n } catch (error) {\n fail(error);\n }\n })\n .catch(error => {\n if (!closing && !disposed) fail(error);\n });\n };\n\n const claimInitialLeadership = async (): Promise<void> => {\n let resolve!: (value: boolean) => void;\n const claimed = new Promise<boolean>(done => {\n resolve = done;\n });\n leadershipLease = lockManager\n .request(`doxum:${documentId}`, { mode: 'exclusive', ifAvailable: true }, async lock => {\n if (lock === null) {\n resolve(false);\n return;\n }\n try {\n await lead(() => resolve(true));\n } catch (error) {\n fail(error);\n resolve(false);\n }\n })\n .catch(error => {\n if (!closing && !disposed) fail(error);\n resolve(false);\n });\n if (!(await claimed) && !fault && !closing && !disposed) watchLeadership();\n };\n\n await restore(true);\n // The timeline, not a runtime constructed before attachment, is the baseline\n // for local undo. Even an identical restored snapshot must discard prior history.\n runtime.history.clear();\n driver = installRuntimeWriteDriver(runtime, { assertWritable: assertLeader });\n channel = new BroadcastChannel(`doxum:${databaseName}:${documentId}`);\n unsubscribe = runtime.subscribe(commit => {\n if (\n (commit.source === 'local' || commit.source === 'system' || commit.source === 'history') &&\n !closing &&\n !disposed\n )\n record(commit);\n });\n channel.onmessage = event => {\n const message = notification(event.data);\n if (\n !message ||\n message.documentId !== documentId ||\n message.headSeq <= headSeq ||\n closing ||\n disposed ||\n fault\n )\n return;\n void enqueue(restore).catch(() => undefined);\n };\n await claimInitialLeadership();\n if (fault) throw fault;\n\n const localSync: LocalSync = {\n state: Object.freeze({\n current: () => snapshot,\n revision: () => stateRevision,\n subscribe: (listener: () => void) => {\n if (disposed) throw new LocalSyncDisposedError();\n stateListeners.add(listener);\n return () => {\n stateListeners.delete(listener);\n };\n },\n }),\n flush: async (): Promise<void> => {\n assertOpen();\n try {\n await enqueue(restore);\n } catch (error) {\n fail(error);\n throw error;\n }\n assertOpen();\n },\n dispose: async (): Promise<void> => {\n if (disposed) return;\n if (closing) {\n await queued;\n return;\n }\n closing = true;\n unsubscribe?.();\n await queued;\n releaseLeadership?.();\n if (role === 'leader') await leadershipLease;\n disposed = true;\n driver?.dispose();\n channel?.close();\n timeline.close();\n publishState();\n stateListeners.clear();\n },\n };\n return Object.freeze(localSync);\n } catch (error) {\n unsubscribe?.();\n driver?.dispose();\n channel?.close();\n timeline.close();\n throw error;\n }\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"local-sync.js","names":["positiveInteger"],"sources":["../core/src/local-sync/contract.ts","../core/src/local-sync/json.ts","../core/src/local-sync/timeline.ts","../core/src/local-sync/session.ts"],"sourcesContent":["import type { ObjectNode } from '../schema';\nimport type { DocumentRuntime } from '../runtime/contract';\nimport type { JsonChangeLimits } from './json';\nimport type { Readable } from '../projection/readable';\n\nexport class LocalSyncUnavailableError extends Error {\n constructor(capability: 'IndexedDB' | 'Web Locks' | 'BroadcastChannel') {\n super(`${capability} is required by doxum/local-sync in this environment.`);\n this.name = 'LocalSyncUnavailableError';\n }\n}\n\nexport class LocalSyncSchemaError extends Error {\n constructor(documentId: string, expected: number, actual: number) {\n super(\n `Local document '${documentId}' uses schema version ${actual}, but version ${expected} was requested.`\n );\n this.name = 'LocalSyncSchemaError';\n }\n}\n\nexport class LocalSyncConsistencyError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'LocalSyncConsistencyError';\n }\n}\n\nexport class LocalSyncReadOnlyError extends Error {\n constructor() {\n super('This tab is following the local document and cannot write until it becomes the leader.');\n this.name = 'LocalSyncReadOnlyError';\n }\n}\n\nexport class LocalSyncUnsupportedOperationError extends Error {\n constructor() {\n super(\n 'Local sync appends committed changes. runtime.replace() and externally supplied remote changes are unavailable while it is attached.'\n );\n this.name = 'LocalSyncUnsupportedOperationError';\n }\n}\n\nexport class LocalSyncDisposedError extends Error {\n constructor() {\n super('Local sync has been disposed.');\n this.name = 'LocalSyncDisposedError';\n }\n}\n\nexport type LocalSyncState =\n | {\n readonly status: 'leader' | 'follower';\n readonly headSeq: number;\n readonly checkpointSeq: number;\n }\n | {\n readonly status: 'error';\n readonly headSeq: number;\n readonly checkpointSeq: number;\n readonly error: unknown;\n }\n | { readonly status: 'disposed' };\n\nexport type LocalSync = {\n readonly state: Readable<LocalSyncState>;\n flush(): Promise<void>;\n dispose(): Promise<void>;\n};\n\nexport type AttachLocalSyncOptions<TSchema extends ObjectNode> = {\n readonly runtime: DocumentRuntime<TSchema>;\n readonly database: string;\n readonly documentId: string;\n readonly schemaVersion?: number;\n /** Admission limits for new local commits; durable replay does not reapply them. */\n readonly changeLimits?: JsonChangeLimits;\n readonly onError?: (error: unknown) => void;\n};\n","import { isPlainObject } from '../value/ownership';\nimport { changeCount, decodeChanges } from '../mutation/changes';\nimport type { ChangeSet } from '../changes';\n\nexport type JsonPrimitive = null | boolean | number | string;\nexport type JsonValue =\n JsonPrimitive | readonly JsonValue[] | { readonly [key: string]: JsonValue };\n\nexport type JsonChangeLimits = {\n readonly maxChanges?: number;\n readonly maxBytes?: number;\n readonly maxDepth?: number;\n readonly maxStringLength?: number;\n};\n\ntype ResolvedJsonLimits = {\n readonly maxChanges: number;\n readonly maxBytes: number;\n readonly maxDepth: number;\n readonly maxStringLength: number;\n};\n\nexport const defaultJsonChangeLimits: Readonly<ResolvedJsonLimits> = Object.freeze({\n maxChanges: 1_000,\n maxBytes: 1_000_000,\n maxDepth: 64,\n maxStringLength: 256_000,\n});\n\nexport class LocalSyncDataError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'LocalSyncDataError';\n }\n}\n\nconst describePath = (path: readonly string[]): string =>\n path.length === 0 ? 'value' : path.join('.');\n\nconst limit = (value: number | undefined, fallback: number, label: string): number => {\n if (value === undefined) return fallback;\n if (Number.isSafeInteger(value) && value > 0) return value;\n throw new TypeError(`${label} must be a positive safe integer.`);\n};\n\nconst resolveLimits = (input: JsonChangeLimits | undefined): ResolvedJsonLimits => ({\n maxChanges: limit(input?.maxChanges, defaultJsonChangeLimits.maxChanges, 'maxChanges'),\n maxBytes: limit(input?.maxBytes, defaultJsonChangeLimits.maxBytes, 'maxBytes'),\n maxDepth: limit(input?.maxDepth, defaultJsonChangeLimits.maxDepth, 'maxDepth'),\n maxStringLength: limit(\n input?.maxStringLength,\n defaultJsonChangeLimits.maxStringLength,\n 'maxStringLength'\n ),\n});\n\nconst validate = (\n value: unknown,\n path: string[],\n limits: ResolvedJsonLimits | undefined,\n depth: number,\n ancestors: WeakSet<object>\n): JsonValue => {\n if (limits && depth > limits.maxDepth)\n throw new LocalSyncDataError(`${describePath(path)} exceeds the maximum JSON depth.`);\n if (value === null || typeof value === 'boolean') return value;\n if (typeof value === 'string') {\n if (!limits || value.length <= limits.maxStringLength) return value;\n throw new LocalSyncDataError(`${describePath(path)} exceeds the maximum string length.`);\n }\n if (typeof value === 'number') {\n if (Number.isFinite(value)) return value;\n throw new LocalSyncDataError(`${describePath(path)} contains a non-finite number.`);\n }\n if (Array.isArray(value)) {\n if (ancestors.has(value))\n throw new LocalSyncDataError(`${describePath(path)} contains a cycle.`);\n ancestors.add(value);\n try {\n for (let index = 0; index < value.length; index += 1) {\n path.push(String(index));\n validate(value[index], path, limits, depth + 1, ancestors);\n path.pop();\n }\n } finally {\n ancestors.delete(value);\n }\n return value as readonly JsonValue[];\n }\n if (isPlainObject(value)) {\n if (ancestors.has(value))\n throw new LocalSyncDataError(`${describePath(path)} contains a cycle.`);\n ancestors.add(value);\n try {\n for (const key of Object.keys(value)) {\n path.push(key);\n validate(value[key], path, limits, depth + 1, ancestors);\n path.pop();\n }\n } finally {\n ancestors.delete(value);\n }\n return value as { readonly [key: string]: JsonValue };\n }\n throw new LocalSyncDataError(`${describePath(path)} must be JSON data.`);\n};\n\nexport const json = (value: unknown, label: string): JsonValue =>\n validate(value, [label], undefined, 0, new WeakSet());\n\nexport const jsonChanges = (value: unknown, label: string, input?: JsonChangeLimits): ChangeSet => {\n const limits = input === undefined ? undefined : resolveLimits(input);\n let changes: ChangeSet;\n try {\n changes = decodeChanges(value);\n } catch {\n throw new LocalSyncDataError(`${label} contains an invalid ChangeSet.`);\n }\n if (limits && changeCount(changes) > limits.maxChanges)\n throw new LocalSyncDataError(`${label} exceeds the maximum change count.`);\n validate(changes.changes, [label], limits, 0, new WeakSet());\n if (limits) {\n const serialized = JSON.stringify(changes.changes);\n if (new TextEncoder().encode(serialized).byteLength > limits.maxBytes)\n throw new LocalSyncDataError(`${label} exceeds the maximum ChangeSet size.`);\n }\n return changes;\n};\n","import {\n LocalSyncConsistencyError,\n LocalSyncSchemaError,\n LocalSyncUnavailableError,\n} from './contract';\nimport { json, jsonChanges, type JsonValue } from './json';\nimport type { ChangeSet } from '../changes';\nimport { isRecord } from '../value/ownership';\n\nconst DATABASE_VERSION = 5;\nconst FORMAT_VERSION = 3;\nconst DOCUMENTS = 'documents';\nconst COMMITS = 'commits';\n\nexport type StoredDocument = {\n readonly formatVersion: number;\n readonly documentId: string;\n readonly schemaVersion: number;\n readonly checkpointSeq: number;\n readonly headSeq: number;\n readonly checkpoint: JsonValue;\n};\n\nexport type StoredCommit = {\n readonly formatVersion: number;\n readonly documentId: string;\n readonly seq: number;\n readonly changes: ChangeSet;\n readonly createdAt: number;\n};\n\nconst request = <T>(value: IDBRequest<T>): Promise<T> =>\n new Promise((resolve, reject) => {\n value.onsuccess = () => resolve(value.result);\n value.onerror = () => reject(value.error ?? new Error('IndexedDB request failed.'));\n });\n\nconst complete = (transaction: IDBTransaction): Promise<void> =>\n new Promise((resolve, reject) => {\n transaction.oncomplete = () => resolve();\n transaction.onabort = () =>\n reject(transaction.error ?? new Error('IndexedDB transaction was aborted.'));\n transaction.onerror = () =>\n reject(transaction.error ?? new Error('IndexedDB transaction failed.'));\n });\n\nconst factory = (): IDBFactory => {\n if (!globalThis.indexedDB) throw new LocalSyncUnavailableError('IndexedDB');\n return globalThis.indexedDB;\n};\n\nconst keyRange = (): typeof IDBKeyRange => {\n if (!globalThis.IDBKeyRange) throw new LocalSyncUnavailableError('IndexedDB');\n return globalThis.IDBKeyRange;\n};\n\nconst string = (value: unknown, label: string): string => {\n if (typeof value === 'string') return value;\n throw new LocalSyncConsistencyError(`${label} is malformed in IndexedDB.`);\n};\n\nconst nonNegativeInteger = (value: unknown, label: string): number => {\n if (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0) return value;\n throw new LocalSyncConsistencyError(`${label} is malformed in IndexedDB.`);\n};\n\nconst positiveInteger = (value: unknown, label: string): number => {\n const parsed = nonNegativeInteger(value, label);\n if (parsed > 0) return parsed;\n throw new LocalSyncConsistencyError(`${label} is malformed in IndexedDB.`);\n};\n\nconst documentRecord = (value: unknown): StoredDocument => {\n if (!isRecord(value) || value.formatVersion !== FORMAT_VERSION)\n throw new LocalSyncConsistencyError('Document record is malformed in IndexedDB.');\n const checkpointSeq = nonNegativeInteger(value.checkpointSeq, 'document.checkpointSeq');\n const headSeq = nonNegativeInteger(value.headSeq, 'document.headSeq');\n if (checkpointSeq > headSeq)\n throw new LocalSyncConsistencyError('Document checkpoint exceeds its head sequence.');\n return Object.freeze({\n formatVersion: FORMAT_VERSION,\n documentId: string(value.documentId, 'document.documentId'),\n schemaVersion: positiveInteger(value.schemaVersion, 'document.schemaVersion'),\n checkpointSeq,\n headSeq,\n checkpoint: json(value.checkpoint, 'document.checkpoint'),\n });\n};\n\nconst commitRecord = (value: unknown): StoredCommit => {\n if (!isRecord(value) || value.formatVersion !== FORMAT_VERSION)\n throw new LocalSyncConsistencyError('Commit record is malformed in IndexedDB.');\n return Object.freeze({\n formatVersion: FORMAT_VERSION,\n documentId: string(value.documentId, 'commit.documentId'),\n seq: positiveInteger(value.seq, 'commit.seq'),\n changes: jsonChanges({ changes: value.changes }, 'commit.changes'),\n createdAt: nonNegativeInteger(value.createdAt, 'commit.createdAt'),\n });\n};\n\nexport type IndexedDbTimeline = {\n readonly initialize: (\n documentId: string,\n schemaVersion: number,\n checkpoint: JsonValue\n ) => Promise<StoredDocument>;\n readonly read: (documentId: string) => Promise<StoredDocument>;\n readonly tail: (documentId: string, afterSeq: number) => Promise<readonly StoredCommit[]>;\n readonly append: (input: {\n readonly documentId: string;\n readonly expectedHeadSeq: number;\n readonly changes: ChangeSet;\n }) => Promise<StoredCommit>;\n readonly close: () => void;\n};\n\nexport const openIndexedDbTimeline = async (databaseName: string): Promise<IndexedDbTimeline> => {\n const database = await new Promise<IDBDatabase>((resolve, reject) => {\n let incompatible = false;\n const open = factory().open(databaseName, DATABASE_VERSION);\n open.onerror = () =>\n reject(\n incompatible\n ? new LocalSyncConsistencyError(\n 'Unsupported Doxum storage format. Existing data was left unchanged.'\n )\n : (open.error ?? new Error('Unable to open IndexedDB.'))\n );\n open.onblocked = () => reject(new Error(`IndexedDB database '${databaseName}' is blocked.`));\n open.onupgradeneeded = event => {\n if (event.oldVersion !== 0) {\n incompatible = true;\n open.transaction?.abort();\n return;\n }\n const db = open.result;\n if (!db.objectStoreNames.contains(DOCUMENTS))\n db.createObjectStore(DOCUMENTS, { keyPath: 'documentId' });\n if (!db.objectStoreNames.contains(COMMITS))\n db.createObjectStore(COMMITS, { keyPath: ['documentId', 'seq'] });\n };\n open.onsuccess = () => resolve(open.result);\n });\n\n const readDocument = async (\n transaction: IDBTransaction,\n documentId: string\n ): Promise<StoredDocument> => {\n const value = await request(transaction.objectStore(DOCUMENTS).get(documentId));\n if (value === undefined)\n throw new LocalSyncConsistencyError(`Local document '${documentId}' does not exist.`);\n return documentRecord(value);\n };\n\n return {\n initialize: async (documentId, schemaVersion, checkpoint) => {\n const transaction = database.transaction(DOCUMENTS, 'readwrite');\n const store = transaction.objectStore(DOCUMENTS);\n const existing = await request(store.get(documentId));\n if (existing !== undefined) {\n const record = documentRecord(existing);\n if (record.schemaVersion !== schemaVersion)\n throw new LocalSyncSchemaError(documentId, schemaVersion, record.schemaVersion);\n await complete(transaction);\n return record;\n }\n const record: StoredDocument = {\n formatVersion: FORMAT_VERSION,\n documentId,\n schemaVersion,\n checkpointSeq: 0,\n headSeq: 0,\n checkpoint,\n };\n store.put(record);\n await complete(transaction);\n return Object.freeze(record);\n },\n read: async documentId => {\n const transaction = database.transaction(DOCUMENTS, 'readonly');\n const record = await readDocument(transaction, documentId);\n await complete(transaction);\n return record;\n },\n tail: async (documentId, afterSeq) => {\n const transaction = database.transaction(COMMITS, 'readonly');\n const range = keyRange().bound(\n [documentId, afterSeq + 1],\n [documentId, Number.MAX_SAFE_INTEGER]\n );\n const values = await request(transaction.objectStore(COMMITS).getAll(range));\n await complete(transaction);\n return Object.freeze(values.map(commitRecord).sort((left, right) => left.seq - right.seq));\n },\n append: async input => {\n const transaction = database.transaction([DOCUMENTS, COMMITS], 'readwrite');\n const documents = transaction.objectStore(DOCUMENTS);\n const current = await readDocument(transaction, input.documentId);\n if (current.headSeq !== input.expectedHeadSeq)\n throw new LocalSyncConsistencyError(\n 'Local document advanced before the leader could append its commit.'\n );\n const commit: StoredCommit = {\n formatVersion: FORMAT_VERSION,\n documentId: input.documentId,\n seq: current.headSeq + 1,\n changes: input.changes,\n createdAt: Date.now(),\n };\n documents.put({ ...current, headSeq: commit.seq });\n transaction.objectStore(COMMITS).put({ ...commit, changes: commit.changes.changes });\n await complete(transaction);\n return Object.freeze(commit);\n },\n close: () => database.close(),\n };\n};\n","import {\n installRuntimeWriteDriver,\n type RuntimeWriteDriverLease,\n type RuntimeWriteIntent,\n} from '../runtime/driver';\nimport type { DocumentCommit } from '../runtime/contract';\nimport type { ObjectNode, Infer } from '../schema';\nimport {\n LocalSyncConsistencyError,\n LocalSyncDisposedError,\n LocalSyncReadOnlyError,\n LocalSyncUnsupportedOperationError,\n LocalSyncUnavailableError,\n type AttachLocalSyncOptions,\n type LocalSync,\n type LocalSyncState,\n} from './contract';\nimport { json, jsonChanges } from './json';\nimport type { ChangeSet } from '../changes';\nimport { openIndexedDbTimeline, type StoredCommit } from './timeline';\n\ntype LockOptions = {\n readonly mode: 'exclusive';\n readonly ifAvailable?: boolean;\n};\n\ntype LockManager = {\n readonly request: <TResult>(\n name: string,\n options: LockOptions,\n callback: (lock: unknown | null) => TResult | PromiseLike<TResult>\n ) => Promise<TResult>;\n};\n\ntype Channel = {\n onmessage: ((event: MessageEvent<unknown>) => void) | null;\n postMessage(message: unknown): void;\n close(): void;\n};\n\ntype ChannelConstructor = new (name: string) => Channel;\n\ntype CommitNotification = {\n readonly kind: 'commit';\n readonly documentId: string;\n readonly headSeq: number;\n};\n\nconst locks = (): LockManager => {\n const value: unknown = globalThis.navigator?.locks;\n if (\n typeof value !== 'object' ||\n value === null ||\n !('request' in value) ||\n typeof value.request !== 'function'\n )\n throw new LocalSyncUnavailableError('Web Locks');\n return value as LockManager;\n};\n\nconst channelConstructor = (): ChannelConstructor => {\n const value: unknown = globalThis.BroadcastChannel;\n if (typeof value !== 'function') throw new LocalSyncUnavailableError('BroadcastChannel');\n return value as ChannelConstructor;\n};\n\nconst requiredString = (value: string, label: string): string => {\n if (value.length > 0) return value;\n throw new TypeError(`${label} must not be empty.`);\n};\n\nconst positiveInteger = (value: number, label: string): number => {\n if (Number.isSafeInteger(value) && value > 0) return value;\n throw new TypeError(`${label} must be a positive safe integer.`);\n};\n\nconst notification = (value: unknown): CommitNotification | undefined => {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined;\n const record = value as Record<string, unknown>;\n if (\n record.kind !== 'commit' ||\n typeof record.documentId !== 'string' ||\n typeof record.headSeq !== 'number' ||\n !Number.isSafeInteger(record.headSeq) ||\n record.headSeq < 0\n )\n return undefined;\n return record as CommitNotification;\n};\n\nexport const attachLocalSync = async <TSchema extends ObjectNode>(\n input: AttachLocalSyncOptions<TSchema>\n): Promise<LocalSync> => {\n const runtime = input.runtime;\n const databaseName = requiredString(input.database, 'database');\n const documentId = requiredString(input.documentId, 'documentId');\n const schemaVersion = positiveInteger(input.schemaVersion ?? 1, 'schemaVersion');\n const lockManager = locks();\n const BroadcastChannel = channelConstructor();\n const timeline = await openIndexedDbTimeline(databaseName);\n\n let channel: Channel | undefined;\n let unsubscribe: (() => void) | undefined;\n let driver: RuntimeWriteDriverLease | undefined;\n try {\n const stored = await timeline.initialize(\n documentId,\n schemaVersion,\n json(runtime.snapshot(), 'runtime snapshot')\n );\n let headSeq = stored.checkpointSeq;\n let checkpointSeq = stored.checkpointSeq;\n let role: 'leader' | 'follower' = 'follower';\n let fault: unknown;\n let closing = false;\n let disposed = false;\n let releaseLeadership: (() => void) | undefined;\n let leadershipLease: Promise<void> | undefined;\n let queued: Promise<void> = Promise.resolve();\n let snapshot: LocalSyncState = Object.freeze({ status: role, headSeq, checkpointSeq });\n let stateRevision = 0;\n const stateListeners = new Set<() => void>();\n const publishState = (): void => {\n const next: LocalSyncState = disposed\n ? { status: 'disposed' }\n : fault\n ? { status: 'error', headSeq, checkpointSeq, error: fault }\n : { status: role, headSeq, checkpointSeq };\n if (\n snapshot.status === next.status &&\n (snapshot.status === 'disposed' ||\n (next.status !== 'disposed' &&\n snapshot.headSeq === next.headSeq &&\n snapshot.checkpointSeq === next.checkpointSeq &&\n (snapshot.status !== 'error' ||\n (next.status === 'error' && snapshot.error === next.error))))\n )\n return;\n snapshot = Object.freeze(next);\n stateRevision++;\n for (const listener of [...stateListeners]) {\n if (!stateListeners.has(listener)) continue;\n try {\n listener();\n } catch (error) {\n try {\n input.onError?.(error);\n } catch {\n /* A listener cannot fail persistence. */\n }\n }\n }\n };\n\n const fail = (error: unknown): void => {\n if (fault || disposed) return;\n fault = error;\n publishState();\n try {\n input.onError?.(error);\n } catch {\n // Error reporting cannot repair or replace the original synchronization failure.\n }\n };\n\n const assertOpen = (): void => {\n if (closing || disposed) throw new LocalSyncDisposedError();\n if (fault) throw fault;\n };\n\n const assertLeader = (intent: RuntimeWriteIntent): void => {\n assertOpen();\n if (role !== 'leader') throw new LocalSyncReadOnlyError();\n if (intent.kind === 'replace' || (intent.kind === 'apply' && intent.source === 'remote'))\n throw new LocalSyncUnsupportedOperationError();\n };\n\n const runRuntime = <TResult>(run: () => TResult): TResult => driver?.run(run) ?? run();\n\n const applyStored = (commit: StoredCommit): void => {\n if (commit.seq !== headSeq + 1)\n throw new LocalSyncConsistencyError('Local commit log contains a sequence gap.');\n const result = runRuntime(() =>\n runtime.apply(commit.changes, {\n expectedRevision: runtime.revision(),\n source: 'remote',\n history: false,\n })\n );\n if (result.status !== 'committed')\n throw new LocalSyncConsistencyError('A stored local ChangeSet could not be applied.');\n headSeq = commit.seq;\n };\n\n const restore = async (reset = false): Promise<void> => {\n const current = await timeline.read(documentId);\n if (current.schemaVersion !== schemaVersion)\n throw new LocalSyncConsistencyError(\n 'Local document schema changed while this attachment was open.'\n );\n if (headSeq > current.headSeq)\n throw new LocalSyncConsistencyError('The local timeline moved behind this attachment.');\n if (reset || headSeq < current.checkpointSeq) {\n const result = runRuntime(() =>\n runtime.replace(current.checkpoint as Infer<TSchema>, { source: 'remote' })\n );\n if (result.status === 'rejected')\n throw new LocalSyncConsistencyError('The local checkpoint could not be restored.');\n headSeq = current.checkpointSeq;\n }\n const tail = await timeline.tail(documentId, headSeq);\n for (const commit of tail) applyStored(commit);\n if (headSeq !== current.headSeq)\n throw new LocalSyncConsistencyError(\n 'Local commit log does not reach its recorded head sequence.'\n );\n checkpointSeq = current.checkpointSeq;\n publishState();\n };\n\n const enqueue = (run: () => Promise<void>): Promise<void> => {\n const next = queued.then(run, run);\n queued = next.then(\n () => undefined,\n error => {\n fail(error);\n }\n );\n return next;\n };\n\n const persist = async (changes: ChangeSet): Promise<void> => {\n const storedCommit = await timeline.append({\n documentId,\n expectedHeadSeq: headSeq,\n changes,\n });\n if (storedCommit.seq !== headSeq + 1)\n throw new LocalSyncConsistencyError('A local commit was assigned an unexpected sequence.');\n headSeq = storedCommit.seq;\n publishState();\n channel?.postMessage({\n kind: 'commit',\n documentId,\n headSeq,\n } satisfies CommitNotification);\n };\n\n const record = (commit: DocumentCommit<TSchema>): void => {\n try {\n const changes = jsonChanges(\n commit.changes,\n 'local commit changes',\n input.changeLimits ?? {}\n );\n void enqueue(() => persist(changes)).catch(() => undefined);\n } catch (error) {\n fail(error);\n }\n };\n\n const holdLeadership = (): Promise<void> =>\n new Promise(resolve => {\n releaseLeadership = resolve;\n });\n\n const lead = async (activated?: () => void): Promise<void> => {\n if (closing || disposed || fault) return;\n await restore();\n if (closing || disposed || fault) return;\n role = 'leader';\n publishState();\n activated?.();\n try {\n await holdLeadership();\n } finally {\n releaseLeadership = undefined;\n if (!closing && !disposed && !fault) {\n role = 'follower';\n publishState();\n }\n }\n };\n\n const watchLeadership = (): void => {\n leadershipLease = lockManager\n .request(`doxum:${documentId}`, { mode: 'exclusive' }, async () => {\n try {\n await lead();\n } catch (error) {\n fail(error);\n }\n })\n .catch(error => {\n if (!closing && !disposed) fail(error);\n });\n };\n\n const claimInitialLeadership = async (): Promise<void> => {\n let resolve!: (value: boolean) => void;\n const claimed = new Promise<boolean>(done => {\n resolve = done;\n });\n leadershipLease = lockManager\n .request(`doxum:${documentId}`, { mode: 'exclusive', ifAvailable: true }, async lock => {\n if (lock === null) {\n resolve(false);\n return;\n }\n try {\n await lead(() => resolve(true));\n } catch (error) {\n fail(error);\n resolve(false);\n }\n })\n .catch(error => {\n if (!closing && !disposed) fail(error);\n resolve(false);\n });\n if (!(await claimed) && !fault && !closing && !disposed) watchLeadership();\n };\n\n await restore(true);\n // The timeline, not a runtime constructed before attachment, is the baseline\n // for local undo. Even an identical restored snapshot must discard prior history.\n runtime.history.clear();\n driver = installRuntimeWriteDriver(runtime, { assertWritable: assertLeader });\n channel = new BroadcastChannel(`doxum:${databaseName}:${documentId}`);\n unsubscribe = runtime.subscribe(commit => {\n if (\n (commit.source === 'local' || commit.source === 'system' || commit.source === 'history') &&\n !closing &&\n !disposed\n )\n record(commit);\n });\n channel.onmessage = event => {\n const message = notification(event.data);\n if (\n !message ||\n message.documentId !== documentId ||\n message.headSeq <= headSeq ||\n closing ||\n disposed ||\n fault\n )\n return;\n void enqueue(restore).catch(() => undefined);\n };\n await claimInitialLeadership();\n if (fault) throw fault;\n\n const localSync: LocalSync = {\n state: Object.freeze({\n current: () => snapshot,\n revision: () => stateRevision,\n subscribe: (listener: () => void) => {\n if (disposed) throw new LocalSyncDisposedError();\n stateListeners.add(listener);\n return () => {\n stateListeners.delete(listener);\n };\n },\n }),\n flush: async (): Promise<void> => {\n assertOpen();\n try {\n await enqueue(restore);\n } catch (error) {\n fail(error);\n throw error;\n }\n assertOpen();\n },\n dispose: async (): Promise<void> => {\n if (disposed) return;\n if (closing) {\n await queued;\n return;\n }\n closing = true;\n unsubscribe?.();\n await queued;\n releaseLeadership?.();\n if (role === 'leader') await leadershipLease;\n disposed = true;\n driver?.dispose();\n channel?.close();\n timeline.close();\n publishState();\n stateListeners.clear();\n },\n };\n return Object.freeze(localSync);\n } catch (error) {\n unsubscribe?.();\n driver?.dispose();\n channel?.close();\n timeline.close();\n throw error;\n }\n};\n"],"mappings":"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"}
|
package/dist/react.cjs
CHANGED
|
@@ -1,12 +1,27 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const
|
|
3
|
-
|
|
2
|
+
const require_store = require("./store-1Uob0Ghk.cjs");
|
|
3
|
+
const require_integration = require("./integration-C87tjRop.cjs");
|
|
4
4
|
let react = require("react");
|
|
5
5
|
//#region react/src/hooks.ts
|
|
6
|
+
const ProjectionContext = (0, react.createContext)(void 0);
|
|
7
|
+
const ProjectionProvider = ProjectionContext.Provider;
|
|
8
|
+
function useProjection(projection, store) {
|
|
9
|
+
const context = (0, react.useContext)(ProjectionContext);
|
|
10
|
+
const owner = store ?? context;
|
|
11
|
+
if (!owner) throw new Error("ProjectionStore is required.");
|
|
12
|
+
const read = (0, react.useCallback)(() => owner.get(projection), [owner, projection]);
|
|
13
|
+
return (0, react.useSyncExternalStore)((0, react.useCallback)((listener) => owner.subscribe(projection, listener), [owner, projection]), read, read);
|
|
14
|
+
}
|
|
15
|
+
function useSetProjection(projection, store) {
|
|
16
|
+
const context = (0, react.useContext)(ProjectionContext);
|
|
17
|
+
const owner = store ?? context;
|
|
18
|
+
if (!owner) throw new Error("ProjectionStore is required.");
|
|
19
|
+
return (0, react.useCallback)((value) => owner.set(projection, value), [owner, projection]);
|
|
20
|
+
}
|
|
6
21
|
const objectIs = (previous, next) => Object.is(previous, next);
|
|
7
22
|
const sameTargets = (left, right) => {
|
|
8
23
|
if (left.length !== right.length) return false;
|
|
9
|
-
for (let index = 0; index < left.length; index += 1) if (!
|
|
24
|
+
for (let index = 0; index < left.length; index += 1) if (!require_store.same(left[index], right[index])) return false;
|
|
10
25
|
return true;
|
|
11
26
|
};
|
|
12
27
|
function useDocumentSelector(runtime, selector, options) {
|
|
@@ -43,7 +58,7 @@ function useDocumentSelector(runtime, selector, options) {
|
|
|
43
58
|
let unsubscribe = () => void 0;
|
|
44
59
|
const install = () => {
|
|
45
60
|
if (targets.length === 0) return () => void 0;
|
|
46
|
-
return
|
|
61
|
+
return require_store.subscribeDependencies(runtime, targets, onCommit);
|
|
47
62
|
};
|
|
48
63
|
const onCommit = () => {
|
|
49
64
|
const previous = cache.current;
|
|
@@ -100,8 +115,12 @@ function useHistory(history) {
|
|
|
100
115
|
]);
|
|
101
116
|
}
|
|
102
117
|
//#endregion
|
|
118
|
+
exports.ProjectionContext = ProjectionContext;
|
|
119
|
+
exports.ProjectionProvider = ProjectionProvider;
|
|
103
120
|
exports.useDocumentSelector = useDocumentSelector;
|
|
104
121
|
exports.useHistory = useHistory;
|
|
122
|
+
exports.useProjection = useProjection;
|
|
105
123
|
exports.useReadable = useReadable;
|
|
124
|
+
exports.useSetProjection = useSetProjection;
|
|
106
125
|
|
|
107
126
|
//# sourceMappingURL=react.cjs.map
|
package/dist/react.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.cjs","names":["
|
|
1
|
+
{"version":3,"file":"react.cjs","names":["sameTarget","track","subscribeDependencies"],"sources":["../react/src/hooks.ts"],"sourcesContent":["import type {\n DocumentReadable,\n ObjectNode,\n DocumentSelector,\n HistoryState,\n LocalHistory,\n OperationResult,\n Readable,\n ProjectionStore,\n ValueProjection,\n InputProjection,\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\nexport const ProjectionContext = createContext<ProjectionStore | undefined>(undefined);\nexport const ProjectionProvider = ProjectionContext.Provider;\n\nexport function useProjection<T>(projection: ValueProjection<T>, store?: ProjectionStore): T {\n const context = useContext(ProjectionContext);\n const owner = store ?? context;\n if (!owner) throw new Error('ProjectionStore is required.');\n const read = useCallback(() => owner.get(projection), [owner, projection]);\n const subscribe = useCallback(\n (listener: () => void) => owner.subscribe(projection, listener),\n [owner, projection]\n );\n return useSyncExternalStore(subscribe, read, read);\n}\n\nexport function useSetProjection<T>(\n projection: InputProjection<T>,\n store?: ProjectionStore\n): (value: T) => void {\n const context = useContext(ProjectionContext);\n const owner = store ?? context;\n if (!owner) throw new Error('ProjectionStore is required.');\n return useCallback((value: T) => owner.set(projection, value), [owner, projection]);\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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"}
|
package/dist/react.d.cts
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as _$react from "react";
|
|
2
|
+
import { DocumentReadable, DocumentSelector, HistoryState, InputProjection, LocalHistory, ObjectNode, OperationResult, ProjectionStore, Readable, ValueProjection } from "doxum";
|
|
2
3
|
|
|
3
4
|
//#region react/src/hooks.d.ts
|
|
5
|
+
declare const ProjectionContext: _$react.Context<ProjectionStore | undefined>;
|
|
6
|
+
declare const ProjectionProvider: _$react.Provider<ProjectionStore | undefined>;
|
|
7
|
+
declare function useProjection<T>(projection: ValueProjection<T>, store?: ProjectionStore): T;
|
|
8
|
+
declare function useSetProjection<T>(projection: InputProjection<T>, store?: ProjectionStore): (value: T) => void;
|
|
4
9
|
type DocumentSelectorOptions<TResult> = {
|
|
5
10
|
readonly isEqual?: (previous: TResult, next: TResult) => boolean;
|
|
6
11
|
readonly server?: () => TResult;
|
|
7
12
|
};
|
|
8
|
-
declare function useDocumentSelector<TSchema extends
|
|
13
|
+
declare function useDocumentSelector<TSchema extends ObjectNode, TResult>(runtime: DocumentReadable<TSchema>, selector: DocumentSelector<TSchema, TResult>, options?: DocumentSelectorOptions<TResult>): TResult;
|
|
9
14
|
declare function useReadable<T>(readable: Readable<T>): T;
|
|
10
15
|
declare function useHistory<TCommit>(history: LocalHistory<TCommit>): HistoryState & {
|
|
11
16
|
undo(): OperationResult<TCommit>;
|
|
12
17
|
redo(): OperationResult<TCommit>;
|
|
13
18
|
};
|
|
14
19
|
//#endregion
|
|
15
|
-
export { type DocumentSelectorOptions, useDocumentSelector, useHistory, useReadable };
|
|
20
|
+
export { type DocumentSelectorOptions, ProjectionContext, ProjectionProvider, useDocumentSelector, useHistory, useProjection, useReadable, useSetProjection };
|
|
16
21
|
//# sourceMappingURL=react.d.cts.map
|