doxum 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +77 -2
  2. package/dist/{contract-DwNiKioc.d.ts → contract-BsY1XLiG.d.ts} +64 -5
  3. package/dist/{contract-Otb5W6cQ.d.cts → contract-D8kjdfqT.d.cts} +64 -5
  4. package/dist/{dependency-BdEMyquf.js → dependency-C5_R1tvQ.js} +24 -263
  5. package/dist/dependency-C5_R1tvQ.js.map +1 -0
  6. package/dist/{dependency-DLcCvNKq.cjs → dependency-Dfzs3khk.cjs} +48 -341
  7. package/dist/dependency-Dfzs3khk.cjs.map +1 -0
  8. package/dist/driver-CNxqMVFH.cjs +69 -0
  9. package/dist/driver-CNxqMVFH.cjs.map +1 -0
  10. package/dist/driver-CbzfW5MR.js +46 -0
  11. package/dist/driver-CbzfW5MR.js.map +1 -0
  12. package/dist/index.cjs +428 -133
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +8 -5
  15. package/dist/index.d.ts +8 -5
  16. package/dist/index.js +352 -61
  17. package/dist/index.js.map +1 -1
  18. package/dist/integration.cjs +1 -1
  19. package/dist/integration.cjs.map +1 -1
  20. package/dist/integration.d.cts +2 -2
  21. package/dist/integration.d.ts +2 -2
  22. package/dist/integration.js +1 -1
  23. package/dist/integration.js.map +1 -1
  24. package/dist/local-sync.cjs +485 -0
  25. package/dist/local-sync.cjs.map +1 -0
  26. package/dist/local-sync.d.cts +74 -0
  27. package/dist/local-sync.d.ts +74 -0
  28. package/dist/local-sync.js +476 -0
  29. package/dist/local-sync.js.map +1 -0
  30. package/dist/ownership-B-VAPcce.js +242 -0
  31. package/dist/ownership-B-VAPcce.js.map +1 -0
  32. package/dist/ownership-CU-9DKJQ.cjs +301 -0
  33. package/dist/ownership-CU-9DKJQ.cjs.map +1 -0
  34. package/dist/react.cjs.map +1 -1
  35. package/dist/react.d.cts +2 -2
  36. package/dist/react.d.ts +2 -2
  37. package/dist/react.js.map +1 -1
  38. package/package.json +14 -8
  39. package/skills/doxum-runtime/references/guide.en.md +60 -5
  40. package/skills/doxum-runtime/references/guide.zh-CN.md +48 -2
  41. package/skills/doxum-runtime/references/invariants.en.md +23 -8
  42. package/skills/doxum-runtime/references/invariants.zh-CN.md +2 -2
  43. package/dist/dependency-BdEMyquf.js.map +0 -1
  44. package/dist/dependency-DLcCvNKq.cjs.map +0 -1
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_dependency = require("./dependency-DLcCvNKq.cjs");
2
+ const require_dependency = require("./dependency-Dfzs3khk.cjs");
3
3
  //#region core/src/integration.ts
4
4
  const track = (runtime, selector) => {
5
5
  const dependencies = require_dependency.createDependencyTracker();
@@ -1 +1 @@
1
- {"version":3,"file":"integration.cjs","names":["createDependencyTracker","readWith"],"sources":["../core/src/integration.ts"],"sourcesContent":["import type { DocumentSchema, ImpactTarget } from './schema';\nimport type { DocumentReader } from './access/reader';\nimport type { DocumentRuntime } from './runtime/contract';\nimport { createDependencyTracker } from './access/dependency';\nimport { readWith } from './runtime/access';\n\nexport type TrackedSelection<TValue> = {\n readonly value: TValue;\n readonly targets: readonly ImpactTarget<unknown>[];\n};\n\n// Framework adapters receive one immutable result instead of coordinating a\n// mutable collector with the reader's scoped lifetime themselves.\nexport const track = <TSchema extends DocumentSchema, TValue>(\n runtime: DocumentRuntime<TSchema>,\n selector: (read: DocumentReader<TSchema>) => TValue\n): TrackedSelection<TValue> => {\n const dependencies = createDependencyTracker();\n const value = readWith(runtime, selector, dependencies);\n return Object.freeze({ value, targets: dependencies.snapshot() });\n};\n"],"mappings":";;;AAaA,MAAa,SACX,SACA,aAC6B;CAC7B,MAAM,eAAeA,mBAAAA,yBAAyB;CAC9C,MAAM,QAAQC,mBAAAA,SAAS,SAAS,UAAU,aAAa;AACvD,QAAO,OAAO,OAAO;EAAE;EAAO,SAAS,aAAa,UAAU;EAAE,CAAC"}
1
+ {"version":3,"file":"integration.cjs","names":["createDependencyTracker","readWith"],"sources":["../core/src/integration.ts"],"sourcesContent":["import type { DocumentSchema, ImpactTarget } from './schema';\nimport type { DocumentReader } from './access/reader';\nimport type { DocumentReadable } from './runtime/contract';\nimport { createDependencyTracker } from './access/dependency';\nimport { readWith } from './runtime/access';\n\nexport type TrackedSelection<TValue> = {\n readonly value: TValue;\n readonly targets: readonly ImpactTarget<unknown>[];\n};\n\n// Framework adapters receive one immutable result instead of coordinating a\n// mutable collector with the reader's scoped lifetime themselves.\nexport const track = <TSchema extends DocumentSchema, TValue>(\n runtime: DocumentReadable<TSchema>,\n selector: (read: DocumentReader<TSchema>) => TValue\n): TrackedSelection<TValue> => {\n const dependencies = createDependencyTracker();\n const value = readWith(runtime, selector, dependencies);\n return Object.freeze({ value, targets: dependencies.snapshot() });\n};\n"],"mappings":";;;AAaA,MAAa,SACX,SACA,aAC6B;CAC7B,MAAM,eAAeA,mBAAAA,yBAAyB;CAC9C,MAAM,QAAQC,mBAAAA,SAAS,SAAS,UAAU,aAAa;AACvD,QAAO,OAAO,OAAO;EAAE;EAAO,SAAS,aAAa,UAAU;EAAE,CAAC"}
@@ -1,11 +1,11 @@
1
- import { F as DocumentReader, bt as ImpactTarget, mt as DocumentSchema, s as DocumentRuntime } from "./contract-Otb5W6cQ.cjs";
1
+ import { Dt as ImpactTarget, H as DocumentReader, o as DocumentReadable, xt as DocumentSchema } from "./contract-D8kjdfqT.cjs";
2
2
 
3
3
  //#region core/src/integration.d.ts
4
4
  type TrackedSelection<TValue> = {
5
5
  readonly value: TValue;
6
6
  readonly targets: readonly ImpactTarget<unknown>[];
7
7
  };
8
- declare const track: <TSchema extends DocumentSchema, TValue>(runtime: DocumentRuntime<TSchema>, selector: (read: DocumentReader<TSchema>) => TValue) => TrackedSelection<TValue>;
8
+ declare const track: <TSchema extends DocumentSchema, TValue>(runtime: DocumentReadable<TSchema>, selector: (read: DocumentReader<TSchema>) => TValue) => TrackedSelection<TValue>;
9
9
  //#endregion
10
10
  export { TrackedSelection, track };
11
11
  //# sourceMappingURL=integration.d.cts.map
@@ -1,11 +1,11 @@
1
- import { F as DocumentReader, bt as ImpactTarget, mt as DocumentSchema, s as DocumentRuntime } from "./contract-DwNiKioc.js";
1
+ import { Dt as ImpactTarget, H as DocumentReader, o as DocumentReadable, xt as DocumentSchema } from "./contract-BsY1XLiG.js";
2
2
 
3
3
  //#region core/src/integration.d.ts
4
4
  type TrackedSelection<TValue> = {
5
5
  readonly value: TValue;
6
6
  readonly targets: readonly ImpactTarget<unknown>[];
7
7
  };
8
- declare const track: <TSchema extends DocumentSchema, TValue>(runtime: DocumentRuntime<TSchema>, selector: (read: DocumentReader<TSchema>) => TValue) => TrackedSelection<TValue>;
8
+ declare const track: <TSchema extends DocumentSchema, TValue>(runtime: DocumentReadable<TSchema>, selector: (read: DocumentReader<TSchema>) => TValue) => TrackedSelection<TValue>;
9
9
  //#endregion
10
10
  export { TrackedSelection, track };
11
11
  //# sourceMappingURL=integration.d.ts.map
@@ -1,4 +1,4 @@
1
- import { i as readWith, t as createDependencyTracker } from "./dependency-BdEMyquf.js";
1
+ import { a as readWith, t as createDependencyTracker } from "./dependency-C5_R1tvQ.js";
2
2
  //#region core/src/integration.ts
3
3
  const track = (runtime, selector) => {
4
4
  const dependencies = createDependencyTracker();
@@ -1 +1 @@
1
- {"version":3,"file":"integration.js","names":[],"sources":["../core/src/integration.ts"],"sourcesContent":["import type { DocumentSchema, ImpactTarget } from './schema';\nimport type { DocumentReader } from './access/reader';\nimport type { DocumentRuntime } from './runtime/contract';\nimport { createDependencyTracker } from './access/dependency';\nimport { readWith } from './runtime/access';\n\nexport type TrackedSelection<TValue> = {\n readonly value: TValue;\n readonly targets: readonly ImpactTarget<unknown>[];\n};\n\n// Framework adapters receive one immutable result instead of coordinating a\n// mutable collector with the reader's scoped lifetime themselves.\nexport const track = <TSchema extends DocumentSchema, TValue>(\n runtime: DocumentRuntime<TSchema>,\n selector: (read: DocumentReader<TSchema>) => TValue\n): TrackedSelection<TValue> => {\n const dependencies = createDependencyTracker();\n const value = readWith(runtime, selector, dependencies);\n return Object.freeze({ value, targets: dependencies.snapshot() });\n};\n"],"mappings":";;AAaA,MAAa,SACX,SACA,aAC6B;CAC7B,MAAM,eAAe,yBAAyB;CAC9C,MAAM,QAAQ,SAAS,SAAS,UAAU,aAAa;AACvD,QAAO,OAAO,OAAO;EAAE;EAAO,SAAS,aAAa,UAAU;EAAE,CAAC"}
1
+ {"version":3,"file":"integration.js","names":[],"sources":["../core/src/integration.ts"],"sourcesContent":["import type { DocumentSchema, ImpactTarget } from './schema';\nimport type { DocumentReader } from './access/reader';\nimport type { DocumentReadable } from './runtime/contract';\nimport { createDependencyTracker } from './access/dependency';\nimport { readWith } from './runtime/access';\n\nexport type TrackedSelection<TValue> = {\n readonly value: TValue;\n readonly targets: readonly ImpactTarget<unknown>[];\n};\n\n// Framework adapters receive one immutable result instead of coordinating a\n// mutable collector with the reader's scoped lifetime themselves.\nexport const track = <TSchema extends DocumentSchema, TValue>(\n runtime: DocumentReadable<TSchema>,\n selector: (read: DocumentReader<TSchema>) => TValue\n): TrackedSelection<TValue> => {\n const dependencies = createDependencyTracker();\n const value = readWith(runtime, selector, dependencies);\n return Object.freeze({ value, targets: dependencies.snapshot() });\n};\n"],"mappings":";;AAaA,MAAa,SACX,SACA,aAC6B;CAC7B,MAAM,eAAe,yBAAyB;CAC9C,MAAM,QAAQ,SAAS,SAAS,UAAU,aAAa;AACvD,QAAO,OAAO,OAAO;EAAE;EAAO,SAAS,aAAa,UAAU;EAAE,CAAC"}
@@ -0,0 +1,485 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_ownership = require("./ownership-CU-9DKJQ.cjs");
3
+ const require_driver = require("./driver-CNxqMVFH.cjs");
4
+ //#region core/src/local-sync/contract.ts
5
+ var LocalSyncUnavailableError = class extends Error {
6
+ constructor(capability) {
7
+ super(`${capability} is required by doxum/local-sync in this environment.`);
8
+ this.name = "LocalSyncUnavailableError";
9
+ }
10
+ };
11
+ var LocalSyncSchemaError = class extends Error {
12
+ constructor(documentId, expected, actual) {
13
+ super(`Local document '${documentId}' uses schema version ${actual}, but version ${expected} was requested.`);
14
+ this.name = "LocalSyncSchemaError";
15
+ }
16
+ };
17
+ var LocalSyncConsistencyError = class extends Error {
18
+ constructor(message) {
19
+ super(message);
20
+ this.name = "LocalSyncConsistencyError";
21
+ }
22
+ };
23
+ var LocalSyncReadOnlyError = class extends Error {
24
+ constructor() {
25
+ super("This tab is following the local document and cannot write until it becomes the leader.");
26
+ this.name = "LocalSyncReadOnlyError";
27
+ }
28
+ };
29
+ var LocalSyncUnsupportedOperationError = class extends Error {
30
+ constructor() {
31
+ super("Local sync persists operation commands. runtime.replace() and externally supplied remote operations are unavailable while it is attached.");
32
+ this.name = "LocalSyncUnsupportedOperationError";
33
+ }
34
+ };
35
+ var LocalSyncDisposedError = class extends Error {
36
+ constructor() {
37
+ super("Local sync has been disposed.");
38
+ this.name = "LocalSyncDisposedError";
39
+ }
40
+ };
41
+ //#endregion
42
+ //#region core/src/local-sync/json.ts
43
+ const defaultJsonCommandLimits = Object.freeze({
44
+ maxOperations: 1e3,
45
+ maxBytes: 1e6,
46
+ maxDepth: 64,
47
+ maxStringLength: 256e3
48
+ });
49
+ var LocalSyncDataError = class extends Error {
50
+ constructor(message) {
51
+ super(message);
52
+ this.name = "LocalSyncDataError";
53
+ }
54
+ };
55
+ const describePath = (path) => path.length === 0 ? "value" : path.join(".");
56
+ const limit = (value, fallback, label) => {
57
+ if (value === void 0) return fallback;
58
+ if (Number.isSafeInteger(value) && value > 0) return value;
59
+ throw new TypeError(`${label} must be a positive safe integer.`);
60
+ };
61
+ const resolveLimits = (input) => ({
62
+ maxOperations: limit(input?.maxOperations, defaultJsonCommandLimits.maxOperations, "maxOperations"),
63
+ maxBytes: limit(input?.maxBytes, defaultJsonCommandLimits.maxBytes, "maxBytes"),
64
+ maxDepth: limit(input?.maxDepth, defaultJsonCommandLimits.maxDepth, "maxDepth"),
65
+ maxStringLength: limit(input?.maxStringLength, defaultJsonCommandLimits.maxStringLength, "maxStringLength")
66
+ });
67
+ const validate = (value, path, limits, depth, ancestors) => {
68
+ if (limits && depth > limits.maxDepth) throw new LocalSyncDataError(`${describePath(path)} exceeds the maximum JSON depth.`);
69
+ if (value === null || typeof value === "boolean") return value;
70
+ if (typeof value === "string") {
71
+ if (!limits || value.length <= limits.maxStringLength) return value;
72
+ throw new LocalSyncDataError(`${describePath(path)} exceeds the maximum string length.`);
73
+ }
74
+ if (typeof value === "number") {
75
+ if (Number.isFinite(value)) return value;
76
+ throw new LocalSyncDataError(`${describePath(path)} contains a non-finite number.`);
77
+ }
78
+ if (Array.isArray(value)) {
79
+ if (ancestors.has(value)) throw new LocalSyncDataError(`${describePath(path)} contains a cycle.`);
80
+ ancestors.add(value);
81
+ try {
82
+ for (let index = 0; index < value.length; index += 1) validate(value[index], [...path, String(index)], limits, depth + 1, ancestors);
83
+ } finally {
84
+ ancestors.delete(value);
85
+ }
86
+ return value;
87
+ }
88
+ if (require_ownership.isPlainObject(value)) {
89
+ if (ancestors.has(value)) throw new LocalSyncDataError(`${describePath(path)} contains a cycle.`);
90
+ ancestors.add(value);
91
+ try {
92
+ for (const [key, entry] of Object.entries(value)) validate(entry, [...path, key], limits, depth + 1, ancestors);
93
+ } finally {
94
+ ancestors.delete(value);
95
+ }
96
+ return value;
97
+ }
98
+ throw new LocalSyncDataError(`${describePath(path)} must be JSON data.`);
99
+ };
100
+ const json = (value, label) => validate(value, [label], void 0, 0, /* @__PURE__ */ new WeakSet());
101
+ const jsonArray = (value, label, input) => {
102
+ const limits = resolveLimits(input);
103
+ const parsed = validate(value, [label], limits, 0, /* @__PURE__ */ new WeakSet());
104
+ if (!Array.isArray(parsed)) throw new LocalSyncDataError(`${label} must be a JSON array.`);
105
+ if (parsed.length > limits.maxOperations) throw new LocalSyncDataError(`${label} exceeds the maximum operation count.`);
106
+ const serialized = JSON.stringify(parsed);
107
+ if (new TextEncoder().encode(serialized).byteLength > limits.maxBytes) throw new LocalSyncDataError(`${label} exceeds the maximum command size.`);
108
+ return parsed;
109
+ };
110
+ //#endregion
111
+ //#region core/src/local-sync/timeline.ts
112
+ const DATABASE_VERSION = 2;
113
+ const DOCUMENTS = "documents";
114
+ const COMMITS = "commits";
115
+ const ACTORS = "actors";
116
+ const request = (value) => new Promise((resolve, reject) => {
117
+ value.onsuccess = () => resolve(value.result);
118
+ value.onerror = () => reject(value.error ?? /* @__PURE__ */ new Error("IndexedDB request failed."));
119
+ });
120
+ const complete = (transaction) => new Promise((resolve, reject) => {
121
+ transaction.oncomplete = () => resolve();
122
+ transaction.onabort = () => reject(transaction.error ?? /* @__PURE__ */ new Error("IndexedDB transaction was aborted."));
123
+ transaction.onerror = () => reject(transaction.error ?? /* @__PURE__ */ new Error("IndexedDB transaction failed."));
124
+ });
125
+ const factory = () => {
126
+ if (!globalThis.indexedDB) throw new LocalSyncUnavailableError("IndexedDB");
127
+ return globalThis.indexedDB;
128
+ };
129
+ const keyRange = () => {
130
+ if (!globalThis.IDBKeyRange) throw new LocalSyncUnavailableError("IndexedDB");
131
+ return globalThis.IDBKeyRange;
132
+ };
133
+ const string = (value, label) => {
134
+ if (typeof value === "string") return value;
135
+ throw new LocalSyncConsistencyError(`${label} is malformed in IndexedDB.`);
136
+ };
137
+ const nonNegativeInteger = (value, label) => {
138
+ if (typeof value === "number" && Number.isSafeInteger(value) && value >= 0) return value;
139
+ throw new LocalSyncConsistencyError(`${label} is malformed in IndexedDB.`);
140
+ };
141
+ const positiveInteger$1 = (value, label) => {
142
+ const parsed = nonNegativeInteger(value, label);
143
+ if (parsed > 0) return parsed;
144
+ throw new LocalSyncConsistencyError(`${label} is malformed in IndexedDB.`);
145
+ };
146
+ const documentRecord = (value) => {
147
+ if (!require_ownership.isRecord(value)) throw new LocalSyncConsistencyError("Document record is malformed in IndexedDB.");
148
+ const checkpointSeq = nonNegativeInteger(value.checkpointSeq, "document.checkpointSeq");
149
+ const headSeq = nonNegativeInteger(value.headSeq, "document.headSeq");
150
+ if (checkpointSeq > headSeq) throw new LocalSyncConsistencyError("Document checkpoint exceeds its head sequence.");
151
+ return Object.freeze({
152
+ documentId: string(value.documentId, "document.documentId"),
153
+ schemaVersion: positiveInteger$1(value.schemaVersion, "document.schemaVersion"),
154
+ checkpointSeq,
155
+ headSeq,
156
+ checkpoint: json(value.checkpoint, "document.checkpoint")
157
+ });
158
+ };
159
+ const commitRecord = (value) => {
160
+ if (!require_ownership.isRecord(value)) throw new LocalSyncConsistencyError("Commit record is malformed in IndexedDB.");
161
+ return Object.freeze({
162
+ documentId: string(value.documentId, "commit.documentId"),
163
+ seq: positiveInteger$1(value.seq, "commit.seq"),
164
+ operations: jsonArray(value.operations, "commit.operations"),
165
+ createdAt: nonNegativeInteger(value.createdAt, "commit.createdAt")
166
+ });
167
+ };
168
+ const openIndexedDbTimeline = async (databaseName) => {
169
+ const database = await new Promise((resolve, reject) => {
170
+ const open = factory().open(databaseName, DATABASE_VERSION);
171
+ open.onerror = () => reject(open.error ?? /* @__PURE__ */ new Error("Unable to open IndexedDB."));
172
+ open.onblocked = () => reject(/* @__PURE__ */ new Error(`IndexedDB database '${databaseName}' is blocked.`));
173
+ open.onupgradeneeded = () => {
174
+ const db = open.result;
175
+ if (!db.objectStoreNames.contains(DOCUMENTS)) db.createObjectStore(DOCUMENTS, { keyPath: "documentId" });
176
+ if (!db.objectStoreNames.contains(COMMITS)) db.createObjectStore(COMMITS, { keyPath: ["documentId", "seq"] });
177
+ if (db.objectStoreNames.contains(ACTORS)) db.deleteObjectStore(ACTORS);
178
+ };
179
+ open.onsuccess = () => resolve(open.result);
180
+ });
181
+ const readDocument = async (transaction, documentId) => {
182
+ const value = await request(transaction.objectStore(DOCUMENTS).get(documentId));
183
+ if (value === void 0) throw new LocalSyncConsistencyError(`Local document '${documentId}' does not exist.`);
184
+ return documentRecord(value);
185
+ };
186
+ return {
187
+ initialize: async (documentId, schemaVersion, checkpoint) => {
188
+ const transaction = database.transaction(DOCUMENTS, "readwrite");
189
+ const store = transaction.objectStore(DOCUMENTS);
190
+ const existing = await request(store.get(documentId));
191
+ if (existing !== void 0) {
192
+ const record = documentRecord(existing);
193
+ if (record.schemaVersion !== schemaVersion) throw new LocalSyncSchemaError(documentId, schemaVersion, record.schemaVersion);
194
+ await complete(transaction);
195
+ return record;
196
+ }
197
+ const record = {
198
+ documentId,
199
+ schemaVersion,
200
+ checkpointSeq: 0,
201
+ headSeq: 0,
202
+ checkpoint
203
+ };
204
+ store.put(record);
205
+ await complete(transaction);
206
+ return Object.freeze(record);
207
+ },
208
+ read: async (documentId) => {
209
+ const transaction = database.transaction(DOCUMENTS, "readonly");
210
+ const record = await readDocument(transaction, documentId);
211
+ await complete(transaction);
212
+ return record;
213
+ },
214
+ tail: async (documentId, afterSeq) => {
215
+ const transaction = database.transaction(COMMITS, "readonly");
216
+ const range = keyRange().bound([documentId, afterSeq + 1], [documentId, Number.MAX_SAFE_INTEGER]);
217
+ const values = await request(transaction.objectStore(COMMITS).getAll(range));
218
+ await complete(transaction);
219
+ return Object.freeze(values.map(commitRecord).sort((left, right) => left.seq - right.seq));
220
+ },
221
+ append: async (input) => {
222
+ const transaction = database.transaction([DOCUMENTS, COMMITS], "readwrite");
223
+ const documents = transaction.objectStore(DOCUMENTS);
224
+ const current = await readDocument(transaction, input.documentId);
225
+ if (current.headSeq !== input.expectedHeadSeq) throw new LocalSyncConsistencyError("Local document advanced before the leader could append its command.");
226
+ const commit = {
227
+ documentId: input.documentId,
228
+ seq: current.headSeq + 1,
229
+ operations: input.operations,
230
+ createdAt: Date.now()
231
+ };
232
+ documents.put({
233
+ ...current,
234
+ headSeq: commit.seq
235
+ });
236
+ transaction.objectStore(COMMITS).put(commit);
237
+ await complete(transaction);
238
+ return Object.freeze(commit);
239
+ },
240
+ close: () => database.close()
241
+ };
242
+ };
243
+ //#endregion
244
+ //#region core/src/local-sync/session.ts
245
+ const locks = () => {
246
+ const value = globalThis.navigator?.locks;
247
+ if (typeof value !== "object" || value === null || !("request" in value) || typeof value.request !== "function") throw new LocalSyncUnavailableError("Web Locks");
248
+ return value;
249
+ };
250
+ const channelConstructor = () => {
251
+ const value = globalThis.BroadcastChannel;
252
+ if (typeof value !== "function") throw new LocalSyncUnavailableError("BroadcastChannel");
253
+ return value;
254
+ };
255
+ const requiredString = (value, label) => {
256
+ if (value.length > 0) return value;
257
+ throw new TypeError(`${label} must not be empty.`);
258
+ };
259
+ const positiveInteger = (value, label) => {
260
+ if (Number.isSafeInteger(value) && value > 0) return value;
261
+ throw new TypeError(`${label} must be a positive safe integer.`);
262
+ };
263
+ const notification = (value) => {
264
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
265
+ const record = value;
266
+ if (record.kind !== "commit" || typeof record.documentId !== "string" || typeof record.headSeq !== "number" || !Number.isSafeInteger(record.headSeq) || record.headSeq < 0) return void 0;
267
+ return record;
268
+ };
269
+ const attachLocalSync = async (input) => {
270
+ const runtime = input.runtime;
271
+ const databaseName = requiredString(input.database, "database");
272
+ const documentId = requiredString(input.documentId, "documentId");
273
+ const schemaVersion = positiveInteger(input.schemaVersion ?? 1, "schemaVersion");
274
+ const lockManager = locks();
275
+ const BroadcastChannel = channelConstructor();
276
+ const timeline = await openIndexedDbTimeline(databaseName);
277
+ let channel;
278
+ let unsubscribe;
279
+ let driver;
280
+ try {
281
+ const stored = await timeline.initialize(documentId, schemaVersion, json(runtime.snapshot(), "runtime snapshot"));
282
+ let headSeq = stored.checkpointSeq;
283
+ let checkpointSeq = stored.checkpointSeq;
284
+ let role = "follower";
285
+ let fault;
286
+ let closing = false;
287
+ let disposed = false;
288
+ let releaseLeadership;
289
+ let leadershipLease;
290
+ let queued = Promise.resolve();
291
+ const fail = (error) => {
292
+ if (fault || disposed) return;
293
+ fault = error;
294
+ try {
295
+ input.onError?.(error);
296
+ } catch {}
297
+ };
298
+ const assertOpen = () => {
299
+ if (closing || disposed) throw new LocalSyncDisposedError();
300
+ if (fault) throw fault;
301
+ };
302
+ const assertLeader = (intent) => {
303
+ assertOpen();
304
+ if (role !== "leader") throw new LocalSyncReadOnlyError();
305
+ if (intent.kind === "replace" || intent.kind === "apply" && intent.source === "remote") throw new LocalSyncUnsupportedOperationError();
306
+ };
307
+ const runRuntime = (run) => driver?.run(run) ?? run();
308
+ const applyStored = (commit) => {
309
+ if (commit.seq !== headSeq + 1) throw new LocalSyncConsistencyError("Local commit log contains a sequence gap.");
310
+ if (runRuntime(() => runtime.apply(commit.operations, {
311
+ source: "remote",
312
+ history: false
313
+ })).status !== "committed") throw new LocalSyncConsistencyError("A stored local command could not be applied.");
314
+ headSeq = commit.seq;
315
+ };
316
+ const restore = async (reset = false) => {
317
+ const current = await timeline.read(documentId);
318
+ if (current.schemaVersion !== schemaVersion) throw new LocalSyncConsistencyError("Local document schema changed while this attachment was open.");
319
+ if (headSeq > current.headSeq) throw new LocalSyncConsistencyError("The local timeline moved behind this attachment.");
320
+ if (reset || headSeq < current.checkpointSeq) {
321
+ if (runRuntime(() => runtime.replace(current.checkpoint, { source: "remote" })).status === "rejected") throw new LocalSyncConsistencyError("The local checkpoint could not be restored.");
322
+ headSeq = current.checkpointSeq;
323
+ }
324
+ const tail = await timeline.tail(documentId, headSeq);
325
+ for (const commit of tail) applyStored(commit);
326
+ if (headSeq !== current.headSeq) throw new LocalSyncConsistencyError("Local commit log does not reach its recorded head sequence.");
327
+ checkpointSeq = current.checkpointSeq;
328
+ };
329
+ const enqueue = (run) => {
330
+ const next = queued.then(run, run);
331
+ queued = next.then(() => void 0, (error) => {
332
+ fail(error);
333
+ });
334
+ return next;
335
+ };
336
+ const persist = async (operations) => {
337
+ const storedCommit = await timeline.append({
338
+ documentId,
339
+ expectedHeadSeq: headSeq,
340
+ operations
341
+ });
342
+ if (storedCommit.seq !== headSeq + 1) throw new LocalSyncConsistencyError("A local command was assigned an unexpected sequence.");
343
+ headSeq = storedCommit.seq;
344
+ channel?.postMessage({
345
+ kind: "commit",
346
+ documentId,
347
+ headSeq
348
+ });
349
+ };
350
+ const record = (commit) => {
351
+ try {
352
+ const operations = jsonArray(commit.operations, "local command operations", input.commandLimits);
353
+ enqueue(() => persist(operations)).catch(() => void 0);
354
+ } catch (error) {
355
+ fail(error);
356
+ }
357
+ };
358
+ const holdLeadership = () => new Promise((resolve) => {
359
+ releaseLeadership = resolve;
360
+ });
361
+ const lead = async (activated) => {
362
+ if (closing || disposed || fault) return;
363
+ await restore();
364
+ if (closing || disposed || fault) return;
365
+ role = "leader";
366
+ activated?.();
367
+ try {
368
+ await holdLeadership();
369
+ } finally {
370
+ releaseLeadership = void 0;
371
+ if (!closing && !disposed && !fault) role = "follower";
372
+ }
373
+ };
374
+ const watchLeadership = () => {
375
+ leadershipLease = lockManager.request(`doxum:${documentId}`, { mode: "exclusive" }, async () => {
376
+ try {
377
+ await lead();
378
+ } catch (error) {
379
+ fail(error);
380
+ }
381
+ }).catch((error) => {
382
+ if (!closing && !disposed) fail(error);
383
+ });
384
+ };
385
+ const claimInitialLeadership = async () => {
386
+ let resolve;
387
+ const claimed = new Promise((done) => {
388
+ resolve = done;
389
+ });
390
+ leadershipLease = lockManager.request(`doxum:${documentId}`, {
391
+ mode: "exclusive",
392
+ ifAvailable: true
393
+ }, async (lock) => {
394
+ if (lock === null) {
395
+ resolve(false);
396
+ return;
397
+ }
398
+ try {
399
+ await lead(() => resolve(true));
400
+ } catch (error) {
401
+ fail(error);
402
+ resolve(false);
403
+ }
404
+ }).catch((error) => {
405
+ if (!closing && !disposed) fail(error);
406
+ resolve(false);
407
+ });
408
+ if (!await claimed && !fault && !closing && !disposed) watchLeadership();
409
+ };
410
+ await restore(true);
411
+ runtime.history.clear();
412
+ driver = require_driver.installRuntimeWriteDriver(runtime, { assertWritable: assertLeader });
413
+ channel = new BroadcastChannel(`doxum:${databaseName}:${documentId}`);
414
+ unsubscribe = runtime.subscribe((commit) => {
415
+ if ((commit.source === "local" || commit.source === "system" || commit.source === "history") && !closing && !disposed) record(commit);
416
+ });
417
+ channel.onmessage = (event) => {
418
+ const message = notification(event.data);
419
+ if (!message || message.documentId !== documentId || message.headSeq <= headSeq || closing || disposed || fault) return;
420
+ enqueue(restore).catch(() => void 0);
421
+ };
422
+ await claimInitialLeadership();
423
+ if (fault) throw fault;
424
+ return Object.freeze({
425
+ state: () => {
426
+ if (disposed) return { status: "disposed" };
427
+ if (fault) return {
428
+ status: "error",
429
+ headSeq,
430
+ checkpointSeq,
431
+ error: fault
432
+ };
433
+ return {
434
+ status: role,
435
+ headSeq,
436
+ checkpointSeq
437
+ };
438
+ },
439
+ flush: async () => {
440
+ assertOpen();
441
+ try {
442
+ await enqueue(restore);
443
+ } catch (error) {
444
+ fail(error);
445
+ throw error;
446
+ }
447
+ assertOpen();
448
+ },
449
+ dispose: async () => {
450
+ if (disposed) return;
451
+ if (closing) {
452
+ await queued;
453
+ return;
454
+ }
455
+ closing = true;
456
+ unsubscribe?.();
457
+ await queued;
458
+ releaseLeadership?.();
459
+ if (role === "leader") await leadershipLease;
460
+ disposed = true;
461
+ driver?.dispose();
462
+ channel?.close();
463
+ timeline.close();
464
+ }
465
+ });
466
+ } catch (error) {
467
+ unsubscribe?.();
468
+ driver?.dispose();
469
+ channel?.close();
470
+ timeline.close();
471
+ throw error;
472
+ }
473
+ };
474
+ //#endregion
475
+ exports.LocalSyncConsistencyError = LocalSyncConsistencyError;
476
+ exports.LocalSyncDataError = LocalSyncDataError;
477
+ exports.LocalSyncDisposedError = LocalSyncDisposedError;
478
+ exports.LocalSyncReadOnlyError = LocalSyncReadOnlyError;
479
+ exports.LocalSyncSchemaError = LocalSyncSchemaError;
480
+ exports.LocalSyncUnavailableError = LocalSyncUnavailableError;
481
+ exports.LocalSyncUnsupportedOperationError = LocalSyncUnsupportedOperationError;
482
+ exports.attachLocalSync = attachLocalSync;
483
+ exports.defaultJsonCommandLimits = defaultJsonCommandLimits;
484
+
485
+ //# sourceMappingURL=local-sync.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-sync.cjs","names":["isPlainObject","positiveInteger","isRecord","installRuntimeWriteDriver"],"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';\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 state(): 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\n const fail = (error: unknown): void => {\n if (fault || disposed) return;\n fault = error;\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 };\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 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 activated?.();\n try {\n await holdLeadership();\n } finally {\n releaseLeadership = undefined;\n if (!closing && !disposed && !fault) role = 'follower';\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: (): LocalSyncState => {\n if (disposed) return { status: 'disposed' };\n if (fault) return { status: 'error', headSeq, checkpointSeq, error: fault };\n return { status: role, headSeq, checkpointSeq };\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 },\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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"}