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.
Files changed (70) hide show
  1. package/README.md +217 -296
  2. package/dist/contract-BNStLbSE.d.ts +441 -0
  3. package/dist/contract-CIU5FCC1.d.cts +441 -0
  4. package/dist/driver-BlR81Dqg.js +200 -0
  5. package/dist/driver-BlR81Dqg.js.map +1 -0
  6. package/dist/driver-xOIkwrB8.cjs +241 -0
  7. package/dist/driver-xOIkwrB8.cjs.map +1 -0
  8. package/dist/index.cjs +666 -2222
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +10 -20
  11. package/dist/index.d.ts +10 -18
  12. package/dist/index.js +634 -2189
  13. package/dist/index.js.map +1 -1
  14. package/dist/{integration-CZwCwFBS.cjs → integration-C87tjRop.cjs} +4 -9
  15. package/dist/integration-C87tjRop.cjs.map +1 -0
  16. package/dist/{integration-B56u1l9V.js → integration-D5XCBLJ8.js} +2 -7
  17. package/dist/integration-D5XCBLJ8.js.map +1 -0
  18. package/dist/integration.cjs +11 -11
  19. package/dist/integration.d.cts +10 -4
  20. package/dist/integration.d.ts +10 -4
  21. package/dist/integration.js +4 -3
  22. package/dist/issue-DVaGQGeP.js +576 -0
  23. package/dist/issue-DVaGQGeP.js.map +1 -0
  24. package/dist/issue-DhrNdQNg.cjs +797 -0
  25. package/dist/issue-DhrNdQNg.cjs.map +1 -0
  26. package/dist/local-sync.cjs +67 -39
  27. package/dist/local-sync.cjs.map +1 -1
  28. package/dist/local-sync.d.cts +10 -10
  29. package/dist/local-sync.d.ts +10 -10
  30. package/dist/local-sync.js +66 -38
  31. package/dist/local-sync.js.map +1 -1
  32. package/dist/react.cjs +23 -4
  33. package/dist/react.cjs.map +1 -1
  34. package/dist/react.d.cts +8 -3
  35. package/dist/react.d.ts +8 -3
  36. package/dist/react.js +21 -6
  37. package/dist/react.js.map +1 -1
  38. package/dist/store-1Uob0Ghk.cjs +2750 -0
  39. package/dist/store-1Uob0Ghk.cjs.map +1 -0
  40. package/dist/store-CD0KdGsq.d.cts +262 -0
  41. package/dist/store-D7QH6Rzw.js +2511 -0
  42. package/dist/store-D7QH6Rzw.js.map +1 -0
  43. package/dist/store-cp5CpfCy.d.ts +262 -0
  44. package/package.json +1 -1
  45. package/skills/doxum-runtime/SKILL.md +26 -44
  46. package/skills/doxum-runtime/references/guide.en.md +95 -359
  47. package/skills/doxum-runtime/references/guide.zh-CN.md +80 -285
  48. package/skills/doxum-runtime/references/invariants.en.md +42 -165
  49. package/skills/doxum-runtime/references/invariants.zh-CN.md +34 -97
  50. package/skills/doxum-runtime/references/patterns.en.md +73 -225
  51. package/skills/doxum-runtime/references/patterns.zh-CN.md +73 -179
  52. package/dist/chunk-pbuEa-1d.js +0 -13
  53. package/dist/contract-DNZ4D53r.d.ts +0 -563
  54. package/dist/contract-j3SLGAwh.d.cts +0 -563
  55. package/dist/driver-CNxqMVFH.cjs +0 -69
  56. package/dist/driver-CNxqMVFH.cjs.map +0 -1
  57. package/dist/driver-CbzfW5MR.js +0 -46
  58. package/dist/driver-CbzfW5MR.js.map +0 -1
  59. package/dist/integration-B56u1l9V.js.map +0 -1
  60. package/dist/integration-CZwCwFBS.cjs.map +0 -1
  61. package/dist/ownership-CY0nPXGF.cjs +0 -304
  62. package/dist/ownership-CY0nPXGF.cjs.map +0 -1
  63. package/dist/ownership-CduRygE7.js +0 -245
  64. package/dist/ownership-CduRygE7.js.map +0 -1
  65. package/dist/runtime-0mOFbe_H.cjs +0 -2439
  66. package/dist/runtime-0mOFbe_H.cjs.map +0 -1
  67. package/dist/runtime-B22tuj9A.d.cts +0 -150
  68. package/dist/runtime-BFmhzPpZ.d.ts +0 -150
  69. package/dist/runtime-DF1q9Gje.js +0 -2129
  70. package/dist/runtime-DF1q9Gje.js.map +0 -1
@@ -1,23 +1,23 @@
1
- import { c as DocumentRuntime, v as Readable, wt as DocumentSchema } from "./contract-DNZ4D53r.js";
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 JsonCommandLimits = {
9
- readonly maxOperations?: number;
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 maxOperations: number;
15
+ readonly maxChanges: number;
16
16
  readonly maxBytes: number;
17
17
  readonly maxDepth: number;
18
18
  readonly maxStringLength: number;
19
19
  };
20
- declare const defaultJsonCommandLimits: Readonly<ResolvedJsonLimits>;
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 DocumentSchema> = {
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 commandLimits?: JsonCommandLimits;
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 DocumentSchema>(input: AttachLocalSyncOptions<TSchema>) => Promise<LocalSync>;
71
+ declare const attachLocalSync: <TSchema extends ObjectNode>(input: AttachLocalSyncOptions<TSchema>) => Promise<LocalSync>;
72
72
  //#endregion
73
- export { type AttachLocalSyncOptions, type JsonCommandLimits, type JsonPrimitive, type JsonValue, type LocalSync, LocalSyncConsistencyError, LocalSyncDataError, LocalSyncDisposedError, LocalSyncReadOnlyError, LocalSyncSchemaError, type LocalSyncState, LocalSyncUnavailableError, LocalSyncUnsupportedOperationError, attachLocalSync, defaultJsonCommandLimits };
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
@@ -1,5 +1,5 @@
1
- import { i as isRecord, r as isPlainObject } from "./ownership-CduRygE7.js";
2
- import { i as installRuntimeWriteDriver } from "./driver-CbzfW5MR.js";
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 persists operation commands. runtime.replace() and externally supplied remote operations are unavailable while it is attached.");
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 defaultJsonCommandLimits = Object.freeze({
43
- maxOperations: 1e3,
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
- maxOperations: limit(input?.maxOperations, defaultJsonCommandLimits.maxOperations, "maxOperations"),
62
- maxBytes: limit(input?.maxBytes, defaultJsonCommandLimits.maxBytes, "maxBytes"),
63
- maxDepth: limit(input?.maxDepth, defaultJsonCommandLimits.maxDepth, "maxDepth"),
64
- maxStringLength: limit(input?.maxStringLength, defaultJsonCommandLimits.maxStringLength, "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) validate(value[index], [...path, String(index)], limits, depth + 1, ancestors);
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 [key, entry] of Object.entries(value)) validate(entry, [...path, key], limits, depth + 1, ancestors);
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 jsonArray = (value, label, input) => {
101
- const limits = resolveLimits(input);
102
- const parsed = validate(value, [label], limits, 0, /* @__PURE__ */ new WeakSet());
103
- if (!Array.isArray(parsed)) throw new LocalSyncDataError(`${label} must be a JSON array.`);
104
- if (parsed.length > limits.maxOperations) throw new LocalSyncDataError(`${label} exceeds the maximum operation count.`);
105
- const serialized = JSON.stringify(parsed);
106
- if (new TextEncoder().encode(serialized).byteLength > limits.maxBytes) throw new LocalSyncDataError(`${label} exceeds the maximum command size.`);
107
- return parsed;
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 = 2;
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
- operations: jsonArray(value.operations, "commit.operations"),
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 command.");
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
- operations: input.operations,
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(commit);
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.operations, {
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 command could not be applied.");
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 (operations) => {
397
+ const persist = async (changes) => {
370
398
  const storedCommit = await timeline.append({
371
399
  documentId,
372
400
  expectedHeadSeq: headSeq,
373
- operations
401
+ changes
374
402
  });
375
- if (storedCommit.seq !== headSeq + 1) throw new LocalSyncConsistencyError("A local command was assigned an unexpected sequence.");
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 operations = jsonArray(commit.operations, "local command operations", input.commandLimits);
387
- enqueue(() => persist(operations)).catch(() => void 0);
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, defaultJsonCommandLimits };
541
+ export { LocalSyncConsistencyError, LocalSyncDataError, LocalSyncDisposedError, LocalSyncReadOnlyError, LocalSyncSchemaError, LocalSyncUnavailableError, LocalSyncUnsupportedOperationError, attachLocalSync, defaultJsonChangeLimits };
514
542
 
515
543
  //# sourceMappingURL=local-sync.js.map
@@ -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 require_integration = require("./integration-CZwCwFBS.cjs");
3
- let doxum = require("doxum");
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 (!doxum.target.same(left[index], right[index])) return false;
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 runtime.subscribe(targets.length === 1 ? targets[0] : targets, onCommit);
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
@@ -1 +1 @@
1
- {"version":3,"file":"react.cjs","names":["target","track"],"sources":["../react/src/hooks.ts"],"sourcesContent":["import type {\n DocumentReadable,\n DocumentSchema,\n DocumentSelector,\n HistoryState,\n ImpactTarget,\n LocalHistory,\n OperationResult,\n Readable,\n} from 'doxum';\nimport { target } from 'doxum';\nimport { track } from 'doxum/integration';\nimport { useCallback, useMemo, useRef, useSyncExternalStore } from 'react';\n\nexport type DocumentSelectorOptions<TResult> = {\n readonly isEqual?: (previous: TResult, next: TResult) => boolean;\n readonly server?: () => TResult;\n};\n\ntype SelectorCache<TSchema extends DocumentSchema, 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 (!target.same(left[index], right[index])) return false;\n return true;\n};\n\nexport function useDocumentSelector<TSchema extends DocumentSchema, 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 runtime.subscribe(\n targets.length === 1\n ? targets[0]\n : (targets as [ImpactTarget<unknown>, ...ImpactTarget<unknown>[]]),\n onCommit\n );\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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"}
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 { DocumentReadable, DocumentSchema, DocumentSelector, HistoryState, LocalHistory, OperationResult, Readable } from "doxum";
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 DocumentSchema, TResult>(runtime: DocumentReadable<TSchema>, selector: DocumentSelector<TSchema, TResult>, options?: DocumentSelectorOptions<TResult>): TResult;
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