doxum 0.1.23 → 0.1.26

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 (66) hide show
  1. package/README.md +208 -102
  2. package/dist/advanced.cjs +237 -2
  3. package/dist/advanced.cjs.map +1 -0
  4. package/dist/advanced.d.cts +90 -2
  5. package/dist/advanced.d.ts +90 -2
  6. package/dist/advanced.js +236 -1
  7. package/dist/advanced.js.map +1 -0
  8. package/dist/{contract-ULPQmiEs.d.ts → contract-CNR6BS75.d.cts} +216 -30
  9. package/dist/{contract-SYJFP0lM.d.cts → contract-COgxO5sv.d.ts} +216 -30
  10. package/dist/definition-CDOuvb5C.d.cts +111 -0
  11. package/dist/definition-xgk3YNBp.d.ts +111 -0
  12. package/dist/{driver-yFfvDMX5.js → driver-B5vdPohL.js} +59 -3
  13. package/dist/driver-B5vdPohL.js.map +1 -0
  14. package/dist/{driver-BD3DQOW6.cjs → driver-BL91gn7_.cjs} +107 -9
  15. package/dist/driver-BL91gn7_.cjs.map +1 -0
  16. package/dist/index.cjs +1637 -703
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +65 -33
  19. package/dist/index.d.ts +65 -33
  20. package/dist/index.js +1432 -497
  21. package/dist/index.js.map +1 -1
  22. package/dist/local-sync.cjs +69 -103
  23. package/dist/local-sync.cjs.map +1 -1
  24. package/dist/local-sync.d.cts +14 -26
  25. package/dist/local-sync.d.ts +14 -26
  26. package/dist/local-sync.js +68 -96
  27. package/dist/local-sync.js.map +1 -1
  28. package/dist/react.cjs +4 -5
  29. package/dist/react.cjs.map +1 -1
  30. package/dist/react.d.cts +6 -8
  31. package/dist/react.d.ts +6 -8
  32. package/dist/react.js +4 -5
  33. package/dist/react.js.map +1 -1
  34. package/dist/record-B2yRhYsc.js +28 -0
  35. package/dist/record-B2yRhYsc.js.map +1 -0
  36. package/dist/record-ChYUi8PP.cjs +45 -0
  37. package/dist/record-ChYUi8PP.cjs.map +1 -0
  38. package/dist/scheduler--137L3j-.js +500 -0
  39. package/dist/scheduler--137L3j-.js.map +1 -0
  40. package/dist/scheduler-D4VthaaK.cjs +589 -0
  41. package/dist/scheduler-D4VthaaK.cjs.map +1 -0
  42. package/package.json +6 -3
  43. package/skills/doxum-runtime/SKILL.md +32 -7
  44. package/skills/doxum-runtime/agents/openai.yaml +1 -1
  45. package/skills/doxum-runtime/references/api.en.md +249 -0
  46. package/skills/doxum-runtime/references/api.zh-CN.md +242 -0
  47. package/skills/doxum-runtime/references/guide.en.md +18 -8
  48. package/skills/doxum-runtime/references/guide.zh-CN.md +14 -6
  49. package/skills/doxum-runtime/references/invariants.en.md +29 -5
  50. package/skills/doxum-runtime/references/invariants.zh-CN.md +24 -6
  51. package/skills/doxum-runtime/references/patterns.en.md +78 -18
  52. package/skills/doxum-runtime/references/patterns.zh-CN.md +76 -18
  53. package/skills/doxum-runtime/references/projections.en.md +209 -119
  54. package/skills/doxum-runtime/references/projections.zh-CN.md +200 -101
  55. package/dist/advanced-CLJCWkHa.d.cts +0 -176
  56. package/dist/advanced-CRNHfJBu.js +0 -1224
  57. package/dist/advanced-CRNHfJBu.js.map +0 -1
  58. package/dist/advanced-Cle5g3B4.cjs +0 -1517
  59. package/dist/advanced-Cle5g3B4.cjs.map +0 -1
  60. package/dist/advanced-d6J61er9.d.ts +0 -176
  61. package/dist/context-BcLKGfq8.cjs +0 -149
  62. package/dist/context-BcLKGfq8.cjs.map +0 -1
  63. package/dist/context-DFEaDUv5.js +0 -84
  64. package/dist/context-DFEaDUv5.js.map +0 -1
  65. package/dist/driver-BD3DQOW6.cjs.map +0 -1
  66. package/dist/driver-yFfvDMX5.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { K as ObjectNode, c as DocumentRuntime, g as Readable } from "./contract-SYJFP0lM.cjs";
1
+ import { W as ObjectSchema, g as Readable, s as DocumentRuntime } from "./contract-CNR6BS75.cjs";
2
2
 
3
3
  //#region core/src/local-sync/json.d.ts
4
4
  type JsonPrimitive = null | boolean | number | string;
@@ -18,29 +18,17 @@ type ResolvedJsonLimits = {
18
18
  readonly maxStringLength: number;
19
19
  };
20
20
  declare const defaultJsonChangeLimits: Readonly<ResolvedJsonLimits>;
21
- declare class LocalSyncDataError extends Error {
22
- constructor(message: string);
21
+ //#endregion
22
+ //#region core/src/local-sync/error.d.ts
23
+ type LocalSyncErrorCode = 'unavailable' | 'schema-mismatch' | 'consistency' | 'read-only' | 'unsupported-operation' | 'disposed' | 'invalid-data';
24
+ declare class LocalSyncError extends Error {
25
+ readonly code: LocalSyncErrorCode;
26
+ constructor(code: LocalSyncErrorCode, message: string, options?: {
27
+ readonly cause?: unknown;
28
+ });
23
29
  }
24
30
  //#endregion
25
31
  //#region core/src/local-sync/contract.d.ts
26
- declare class LocalSyncUnavailableError extends Error {
27
- constructor(capability: 'IndexedDB' | 'Web Locks' | 'BroadcastChannel');
28
- }
29
- declare class LocalSyncSchemaError extends Error {
30
- constructor(documentId: string, expected: number, actual: number);
31
- }
32
- declare class LocalSyncConsistencyError extends Error {
33
- constructor(message: string);
34
- }
35
- declare class LocalSyncReadOnlyError extends Error {
36
- constructor();
37
- }
38
- declare class LocalSyncUnsupportedOperationError extends Error {
39
- constructor();
40
- }
41
- declare class LocalSyncDisposedError extends Error {
42
- constructor();
43
- }
44
32
  type LocalSyncState = {
45
33
  readonly status: 'leader' | 'follower';
46
34
  readonly headSeq: number;
@@ -49,7 +37,7 @@ type LocalSyncState = {
49
37
  readonly status: 'error';
50
38
  readonly headSeq: number;
51
39
  readonly checkpointSeq: number;
52
- readonly error: unknown;
40
+ readonly error: LocalSyncError;
53
41
  } | {
54
42
  readonly status: 'disposed';
55
43
  };
@@ -58,17 +46,17 @@ type LocalSync = {
58
46
  flush(): Promise<void>;
59
47
  dispose(): Promise<void>;
60
48
  };
61
- type AttachLocalSyncOptions<TSchema extends ObjectNode> = {
49
+ type AttachLocalSyncOptions<TSchema extends ObjectSchema<object>> = {
62
50
  readonly runtime: DocumentRuntime<TSchema>;
63
51
  readonly database: string;
64
52
  readonly documentId: string;
65
53
  readonly schemaVersion?: number; /** Admission limits for new local commits; durable replay does not reapply them. */
66
54
  readonly changeLimits?: JsonChangeLimits;
67
- readonly onError?: (error: unknown) => void;
55
+ readonly onError?: (error: LocalSyncError) => void;
68
56
  };
69
57
  //#endregion
70
58
  //#region core/src/local-sync/session.d.ts
71
- declare const attachLocalSync: <TSchema extends ObjectNode>(input: AttachLocalSyncOptions<TSchema>) => Promise<LocalSync>;
59
+ declare const attachLocalSync: <TSchema extends ObjectSchema<object>>(input: AttachLocalSyncOptions<TSchema>) => Promise<LocalSync>;
72
60
  //#endregion
73
- export { type AttachLocalSyncOptions, type JsonChangeLimits, type JsonPrimitive, type JsonValue, type LocalSync, LocalSyncConsistencyError, LocalSyncDataError, LocalSyncDisposedError, LocalSyncReadOnlyError, LocalSyncSchemaError, type LocalSyncState, LocalSyncUnavailableError, LocalSyncUnsupportedOperationError, attachLocalSync, defaultJsonChangeLimits };
61
+ export { type AttachLocalSyncOptions, type JsonChangeLimits, type JsonPrimitive, type JsonValue, type LocalSync, LocalSyncError, type LocalSyncErrorCode, type LocalSyncState, attachLocalSync, defaultJsonChangeLimits };
74
62
  //# sourceMappingURL=local-sync.d.cts.map
@@ -1,4 +1,4 @@
1
- import { K as ObjectNode, c as DocumentRuntime, g as Readable } from "./contract-ULPQmiEs.js";
1
+ import { W as ObjectSchema, g as Readable, s as DocumentRuntime } from "./contract-COgxO5sv.js";
2
2
 
3
3
  //#region core/src/local-sync/json.d.ts
4
4
  type JsonPrimitive = null | boolean | number | string;
@@ -18,29 +18,17 @@ type ResolvedJsonLimits = {
18
18
  readonly maxStringLength: number;
19
19
  };
20
20
  declare const defaultJsonChangeLimits: Readonly<ResolvedJsonLimits>;
21
- declare class LocalSyncDataError extends Error {
22
- constructor(message: string);
21
+ //#endregion
22
+ //#region core/src/local-sync/error.d.ts
23
+ type LocalSyncErrorCode = 'unavailable' | 'schema-mismatch' | 'consistency' | 'read-only' | 'unsupported-operation' | 'disposed' | 'invalid-data';
24
+ declare class LocalSyncError extends Error {
25
+ readonly code: LocalSyncErrorCode;
26
+ constructor(code: LocalSyncErrorCode, message: string, options?: {
27
+ readonly cause?: unknown;
28
+ });
23
29
  }
24
30
  //#endregion
25
31
  //#region core/src/local-sync/contract.d.ts
26
- declare class LocalSyncUnavailableError extends Error {
27
- constructor(capability: 'IndexedDB' | 'Web Locks' | 'BroadcastChannel');
28
- }
29
- declare class LocalSyncSchemaError extends Error {
30
- constructor(documentId: string, expected: number, actual: number);
31
- }
32
- declare class LocalSyncConsistencyError extends Error {
33
- constructor(message: string);
34
- }
35
- declare class LocalSyncReadOnlyError extends Error {
36
- constructor();
37
- }
38
- declare class LocalSyncUnsupportedOperationError extends Error {
39
- constructor();
40
- }
41
- declare class LocalSyncDisposedError extends Error {
42
- constructor();
43
- }
44
32
  type LocalSyncState = {
45
33
  readonly status: 'leader' | 'follower';
46
34
  readonly headSeq: number;
@@ -49,7 +37,7 @@ type LocalSyncState = {
49
37
  readonly status: 'error';
50
38
  readonly headSeq: number;
51
39
  readonly checkpointSeq: number;
52
- readonly error: unknown;
40
+ readonly error: LocalSyncError;
53
41
  } | {
54
42
  readonly status: 'disposed';
55
43
  };
@@ -58,17 +46,17 @@ type LocalSync = {
58
46
  flush(): Promise<void>;
59
47
  dispose(): Promise<void>;
60
48
  };
61
- type AttachLocalSyncOptions<TSchema extends ObjectNode> = {
49
+ type AttachLocalSyncOptions<TSchema extends ObjectSchema<object>> = {
62
50
  readonly runtime: DocumentRuntime<TSchema>;
63
51
  readonly database: string;
64
52
  readonly documentId: string;
65
53
  readonly schemaVersion?: number; /** Admission limits for new local commits; durable replay does not reapply them. */
66
54
  readonly changeLimits?: JsonChangeLimits;
67
- readonly onError?: (error: unknown) => void;
55
+ readonly onError?: (error: LocalSyncError) => void;
68
56
  };
69
57
  //#endregion
70
58
  //#region core/src/local-sync/session.d.ts
71
- declare const attachLocalSync: <TSchema extends ObjectNode>(input: AttachLocalSyncOptions<TSchema>) => Promise<LocalSync>;
59
+ declare const attachLocalSync: <TSchema extends ObjectSchema<object>>(input: AttachLocalSyncOptions<TSchema>) => Promise<LocalSync>;
72
60
  //#endregion
73
- export { type AttachLocalSyncOptions, type JsonChangeLimits, type JsonPrimitive, type JsonValue, type LocalSync, LocalSyncConsistencyError, LocalSyncDataError, LocalSyncDisposedError, LocalSyncReadOnlyError, LocalSyncSchemaError, type LocalSyncState, LocalSyncUnavailableError, LocalSyncUnsupportedOperationError, attachLocalSync, defaultJsonChangeLimits };
61
+ export { type AttachLocalSyncOptions, type JsonChangeLimits, type JsonPrimitive, type JsonValue, type LocalSync, LocalSyncError, type LocalSyncErrorCode, type LocalSyncState, attachLocalSync, defaultJsonChangeLimits };
74
62
  //# sourceMappingURL=local-sync.d.ts.map
@@ -1,42 +1,15 @@
1
- import { d as isRecord, u as isPlainObject } from "./context-DFEaDUv5.js";
2
- import { i as decodeChanges, n as installRuntimeWriteDriver, r as changeCount } from "./driver-yFfvDMX5.js";
3
- //#region core/src/local-sync/contract.ts
4
- var LocalSyncUnavailableError = class extends Error {
5
- constructor(capability) {
6
- super(`${capability} is required by doxum/local-sync in this environment.`);
7
- this.name = "LocalSyncUnavailableError";
8
- }
9
- };
10
- var LocalSyncSchemaError = class extends Error {
11
- constructor(documentId, expected, actual) {
12
- super(`Local document '${documentId}' uses schema version ${actual}, but version ${expected} was requested.`);
13
- this.name = "LocalSyncSchemaError";
14
- }
15
- };
16
- var LocalSyncConsistencyError = class extends Error {
17
- constructor(message) {
18
- super(message);
19
- this.name = "LocalSyncConsistencyError";
20
- }
21
- };
22
- var LocalSyncReadOnlyError = class extends Error {
23
- constructor() {
24
- super("This tab is following the local document and cannot write until it becomes the leader.");
25
- this.name = "LocalSyncReadOnlyError";
26
- }
27
- };
28
- var LocalSyncUnsupportedOperationError = class extends Error {
29
- constructor() {
30
- super("Local sync appends committed changes. runtime.replace() and externally supplied remote changes are unavailable while it is attached.");
31
- this.name = "LocalSyncUnsupportedOperationError";
32
- }
33
- };
34
- var LocalSyncDisposedError = class extends Error {
35
- constructor() {
36
- super("Local sync has been disposed.");
37
- this.name = "LocalSyncDisposedError";
1
+ import { n as isPlainObject, r as isRecord } from "./record-B2yRhYsc.js";
2
+ import { a as changeCount, n as installRuntimeWriteDriver, o as decodeChanges } from "./driver-B5vdPohL.js";
3
+ //#region core/src/local-sync/error.ts
4
+ var LocalSyncError = class extends Error {
5
+ code;
6
+ constructor(code, message, options) {
7
+ super(message, options);
8
+ this.name = "LocalSyncError";
9
+ this.code = code;
38
10
  }
39
11
  };
12
+ const normalizeLocalSyncError = (error, code, message) => error instanceof LocalSyncError ? error : new LocalSyncError(code, message, { cause: error });
40
13
  //#endregion
41
14
  //#region core/src/local-sync/json.ts
42
15
  const defaultJsonChangeLimits = Object.freeze({
@@ -45,12 +18,6 @@ const defaultJsonChangeLimits = Object.freeze({
45
18
  maxDepth: 64,
46
19
  maxStringLength: 256e3
47
20
  });
48
- var LocalSyncDataError = class extends Error {
49
- constructor(message) {
50
- super(message);
51
- this.name = "LocalSyncDataError";
52
- }
53
- };
54
21
  const describePath = (path) => path.length === 0 ? "value" : path.join(".");
55
22
  const limit = (value, fallback, label) => {
56
23
  if (value === void 0) return fallback;
@@ -64,18 +31,18 @@ const resolveLimits = (input) => ({
64
31
  maxStringLength: limit(input?.maxStringLength, defaultJsonChangeLimits.maxStringLength, "maxStringLength")
65
32
  });
66
33
  const validate = (value, path, limits, depth, ancestors) => {
67
- if (limits && depth > limits.maxDepth) throw new LocalSyncDataError(`${describePath(path)} exceeds the maximum JSON depth.`);
34
+ if (limits && depth > limits.maxDepth) throw new LocalSyncError("invalid-data", `${describePath(path)} exceeds the maximum JSON depth.`);
68
35
  if (value === null || typeof value === "boolean") return value;
69
36
  if (typeof value === "string") {
70
37
  if (!limits || value.length <= limits.maxStringLength) return value;
71
- throw new LocalSyncDataError(`${describePath(path)} exceeds the maximum string length.`);
38
+ throw new LocalSyncError("invalid-data", `${describePath(path)} exceeds the maximum string length.`);
72
39
  }
73
40
  if (typeof value === "number") {
74
41
  if (Number.isFinite(value)) return value;
75
- throw new LocalSyncDataError(`${describePath(path)} contains a non-finite number.`);
42
+ throw new LocalSyncError("invalid-data", `${describePath(path)} contains a non-finite number.`);
76
43
  }
77
44
  if (Array.isArray(value)) {
78
- if (ancestors.has(value)) throw new LocalSyncDataError(`${describePath(path)} contains a cycle.`);
45
+ if (ancestors.has(value)) throw new LocalSyncError("invalid-data", `${describePath(path)} contains a cycle.`);
79
46
  ancestors.add(value);
80
47
  try {
81
48
  for (let index = 0; index < value.length; index += 1) {
@@ -89,7 +56,7 @@ const validate = (value, path, limits, depth, ancestors) => {
89
56
  return value;
90
57
  }
91
58
  if (isPlainObject(value)) {
92
- if (ancestors.has(value)) throw new LocalSyncDataError(`${describePath(path)} contains a cycle.`);
59
+ if (ancestors.has(value)) throw new LocalSyncError("invalid-data", `${describePath(path)} contains a cycle.`);
93
60
  ancestors.add(value);
94
61
  try {
95
62
  for (const key of Object.keys(value)) {
@@ -102,7 +69,7 @@ const validate = (value, path, limits, depth, ancestors) => {
102
69
  }
103
70
  return value;
104
71
  }
105
- throw new LocalSyncDataError(`${describePath(path)} must be JSON data.`);
72
+ throw new LocalSyncError("invalid-data", `${describePath(path)} must be JSON data.`);
106
73
  };
107
74
  const json = (value, label) => validate(value, [label], void 0, 0, /* @__PURE__ */ new WeakSet());
108
75
  const jsonChanges = (value, label, input) => {
@@ -111,13 +78,13 @@ const jsonChanges = (value, label, input) => {
111
78
  try {
112
79
  changes = decodeChanges(value);
113
80
  } catch {
114
- throw new LocalSyncDataError(`${label} contains an invalid ChangeSet.`);
81
+ throw new LocalSyncError("invalid-data", `${label} contains an invalid ChangeSet.`);
115
82
  }
116
- if (limits && changeCount(changes) > limits.maxChanges) throw new LocalSyncDataError(`${label} exceeds the maximum change count.`);
83
+ if (limits && changeCount(changes) > limits.maxChanges) throw new LocalSyncError("invalid-data", `${label} exceeds the maximum change count.`);
117
84
  validate(changes.changes, [label], limits, 0, /* @__PURE__ */ new WeakSet());
118
85
  if (limits) {
119
86
  const serialized = JSON.stringify(changes.changes);
120
- if (new TextEncoder().encode(serialized).byteLength > limits.maxBytes) throw new LocalSyncDataError(`${label} exceeds the maximum ChangeSet size.`);
87
+ if (new TextEncoder().encode(serialized).byteLength > limits.maxBytes) throw new LocalSyncError("invalid-data", `${label} exceeds the maximum ChangeSet size.`);
121
88
  }
122
89
  return changes;
123
90
  };
@@ -129,39 +96,39 @@ const DOCUMENTS = "documents";
129
96
  const COMMITS = "commits";
130
97
  const request = (value) => new Promise((resolve, reject) => {
131
98
  value.onsuccess = () => resolve(value.result);
132
- value.onerror = () => reject(value.error ?? /* @__PURE__ */ new Error("IndexedDB request failed."));
99
+ value.onerror = () => reject(normalizeLocalSyncError(value.error, "unavailable", "IndexedDB request failed."));
133
100
  });
134
101
  const complete = (transaction) => new Promise((resolve, reject) => {
135
102
  transaction.oncomplete = () => resolve();
136
- transaction.onabort = () => reject(transaction.error ?? /* @__PURE__ */ new Error("IndexedDB transaction was aborted."));
137
- transaction.onerror = () => reject(transaction.error ?? /* @__PURE__ */ new Error("IndexedDB transaction failed."));
103
+ transaction.onabort = () => reject(normalizeLocalSyncError(transaction.error, "unavailable", "IndexedDB transaction was aborted."));
104
+ transaction.onerror = () => reject(normalizeLocalSyncError(transaction.error, "unavailable", "IndexedDB transaction failed."));
138
105
  });
139
106
  const factory = () => {
140
- if (!globalThis.indexedDB) throw new LocalSyncUnavailableError("IndexedDB");
107
+ if (!globalThis.indexedDB) throw new LocalSyncError("unavailable", "IndexedDB is required by doxum/local-sync in this environment.");
141
108
  return globalThis.indexedDB;
142
109
  };
143
110
  const keyRange = () => {
144
- if (!globalThis.IDBKeyRange) throw new LocalSyncUnavailableError("IndexedDB");
111
+ if (!globalThis.IDBKeyRange) throw new LocalSyncError("unavailable", "IndexedDB is required by doxum/local-sync in this environment.");
145
112
  return globalThis.IDBKeyRange;
146
113
  };
147
114
  const string = (value, label) => {
148
115
  if (typeof value === "string") return value;
149
- throw new LocalSyncConsistencyError(`${label} is malformed in IndexedDB.`);
116
+ throw new LocalSyncError("consistency", `${label} is malformed in IndexedDB.`);
150
117
  };
151
118
  const nonNegativeInteger = (value, label) => {
152
119
  if (typeof value === "number" && Number.isSafeInteger(value) && value >= 0) return value;
153
- throw new LocalSyncConsistencyError(`${label} is malformed in IndexedDB.`);
120
+ throw new LocalSyncError("consistency", `${label} is malformed in IndexedDB.`);
154
121
  };
155
122
  const positiveInteger$1 = (value, label) => {
156
123
  const parsed = nonNegativeInteger(value, label);
157
124
  if (parsed > 0) return parsed;
158
- throw new LocalSyncConsistencyError(`${label} is malformed in IndexedDB.`);
125
+ throw new LocalSyncError("consistency", `${label} is malformed in IndexedDB.`);
159
126
  };
160
127
  const documentRecord = (value) => {
161
- if (!isRecord(value) || value.formatVersion !== FORMAT_VERSION) throw new LocalSyncConsistencyError("Document record is malformed in IndexedDB.");
128
+ if (!isRecord(value) || value.formatVersion !== FORMAT_VERSION) throw new LocalSyncError("consistency", "Document record is malformed in IndexedDB.");
162
129
  const checkpointSeq = nonNegativeInteger(value.checkpointSeq, "document.checkpointSeq");
163
130
  const headSeq = nonNegativeInteger(value.headSeq, "document.headSeq");
164
- if (checkpointSeq > headSeq) throw new LocalSyncConsistencyError("Document checkpoint exceeds its head sequence.");
131
+ if (checkpointSeq > headSeq) throw new LocalSyncError("consistency", "Document checkpoint exceeds its head sequence.");
165
132
  return Object.freeze({
166
133
  formatVersion: FORMAT_VERSION,
167
134
  documentId: string(value.documentId, "document.documentId"),
@@ -172,7 +139,7 @@ const documentRecord = (value) => {
172
139
  });
173
140
  };
174
141
  const commitRecord = (value) => {
175
- if (!isRecord(value) || value.formatVersion !== FORMAT_VERSION) throw new LocalSyncConsistencyError("Commit record is malformed in IndexedDB.");
142
+ if (!isRecord(value) || value.formatVersion !== FORMAT_VERSION) throw new LocalSyncError("consistency", "Commit record is malformed in IndexedDB.");
176
143
  return Object.freeze({
177
144
  formatVersion: FORMAT_VERSION,
178
145
  documentId: string(value.documentId, "commit.documentId"),
@@ -185,7 +152,7 @@ const openIndexedDbTimeline = async (databaseName) => {
185
152
  const database = await new Promise((resolve, reject) => {
186
153
  let incompatible = false;
187
154
  const open = factory().open(databaseName, DATABASE_VERSION);
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."));
155
+ open.onerror = () => reject(incompatible ? new LocalSyncError("consistency", "Unsupported Doxum storage format. Existing data was left unchanged.") : open.error ?? /* @__PURE__ */ new Error("Unable to open IndexedDB."));
189
156
  open.onblocked = () => reject(/* @__PURE__ */ new Error(`IndexedDB database '${databaseName}' is blocked.`));
190
157
  open.onupgradeneeded = (event) => {
191
158
  if (event.oldVersion !== 0) {
@@ -201,7 +168,7 @@ const openIndexedDbTimeline = async (databaseName) => {
201
168
  });
202
169
  const readDocument = async (transaction, documentId) => {
203
170
  const value = await request(transaction.objectStore(DOCUMENTS).get(documentId));
204
- if (value === void 0) throw new LocalSyncConsistencyError(`Local document '${documentId}' does not exist.`);
171
+ if (value === void 0) throw new LocalSyncError("consistency", `Local document '${documentId}' does not exist.`);
205
172
  return documentRecord(value);
206
173
  };
207
174
  return {
@@ -211,7 +178,7 @@ const openIndexedDbTimeline = async (databaseName) => {
211
178
  const existing = await request(store.get(documentId));
212
179
  if (existing !== void 0) {
213
180
  const record = documentRecord(existing);
214
- if (record.schemaVersion !== schemaVersion) throw new LocalSyncSchemaError(documentId, schemaVersion, record.schemaVersion);
181
+ if (record.schemaVersion !== schemaVersion) throw new LocalSyncError("schema-mismatch", `Local document '${documentId}' uses schema version ${record.schemaVersion}, but version ${schemaVersion} was requested.`);
215
182
  await complete(transaction);
216
183
  return record;
217
184
  }
@@ -244,7 +211,7 @@ const openIndexedDbTimeline = async (databaseName) => {
244
211
  const transaction = database.transaction([DOCUMENTS, COMMITS], "readwrite");
245
212
  const documents = transaction.objectStore(DOCUMENTS);
246
213
  const current = await readDocument(transaction, input.documentId);
247
- if (current.headSeq !== input.expectedHeadSeq) throw new LocalSyncConsistencyError("Local document advanced before the leader could append its commit.");
214
+ if (current.headSeq !== input.expectedHeadSeq) throw new LocalSyncError("consistency", "Local document advanced before the leader could append its commit.");
248
215
  const commit = {
249
216
  formatVersion: FORMAT_VERSION,
250
217
  documentId: input.documentId,
@@ -270,12 +237,12 @@ const openIndexedDbTimeline = async (databaseName) => {
270
237
  //#region core/src/local-sync/session.ts
271
238
  const locks = () => {
272
239
  const value = globalThis.navigator?.locks;
273
- if (typeof value !== "object" || value === null || !("request" in value) || typeof value.request !== "function") throw new LocalSyncUnavailableError("Web Locks");
240
+ if (typeof value !== "object" || value === null || !("request" in value) || typeof value.request !== "function") throw new LocalSyncError("unavailable", "Web Locks is required by doxum/local-sync in this environment.");
274
241
  return value;
275
242
  };
276
243
  const channelConstructor = () => {
277
244
  const value = globalThis.BroadcastChannel;
278
- if (typeof value !== "function") throw new LocalSyncUnavailableError("BroadcastChannel");
245
+ if (typeof value !== "function") throw new LocalSyncError("unavailable", "BroadcastChannel is required by doxum/local-sync in this environment.");
279
246
  return value;
280
247
  };
281
248
  const requiredString = (value, label) => {
@@ -299,7 +266,9 @@ const attachLocalSync = async (input) => {
299
266
  const schemaVersion = positiveInteger(input.schemaVersion ?? 1, "schemaVersion");
300
267
  const lockManager = locks();
301
268
  const BroadcastChannel = channelConstructor();
302
- const timeline = await openIndexedDbTimeline(databaseName);
269
+ const timeline = await openIndexedDbTimeline(databaseName).catch((error) => {
270
+ throw normalizeLocalSyncError(error, "unavailable", "Unable to open local sync storage.");
271
+ });
303
272
  let channel;
304
273
  let unsubscribe;
305
274
  let driver;
@@ -339,51 +308,55 @@ const attachLocalSync = async (input) => {
339
308
  if (!stateListeners.has(listener)) continue;
340
309
  try {
341
310
  listener();
342
- } catch (error) {
343
- try {
344
- input.onError?.(error);
345
- } catch {}
346
- }
311
+ } catch {}
347
312
  }
348
313
  };
349
314
  const fail = (error) => {
350
- if (fault || disposed) return;
351
- fault = error;
315
+ const failure = normalizeLocalSyncError(error, "unavailable", "Local synchronization failed.");
316
+ if (fault || disposed) return fault ?? failure;
317
+ fault = failure;
352
318
  publishState();
353
319
  try {
354
- input.onError?.(error);
320
+ input.onError?.(failure);
355
321
  } catch {}
322
+ return failure;
356
323
  };
357
324
  const assertOpen = () => {
358
- if (closing || disposed) throw new LocalSyncDisposedError();
325
+ if (closing || disposed) throw new LocalSyncError("disposed", "Local sync has been disposed.");
359
326
  if (fault) throw fault;
360
327
  };
361
328
  const assertLeader = (intent) => {
362
329
  assertOpen();
363
- if (role !== "leader") throw new LocalSyncReadOnlyError();
364
- if (intent.kind === "replace" || intent.kind === "apply" && intent.source === "remote") throw new LocalSyncUnsupportedOperationError();
330
+ if (role !== "leader") throw new LocalSyncError("read-only", "This tab is following the local document and cannot write until it becomes the leader.");
331
+ if (intent.kind === "replace" || intent.kind === "apply" && intent.source === "remote") throw new LocalSyncError("unsupported-operation", "Local sync appends committed changes. runtime.replace() and externally supplied remote changes are unavailable while it is attached.");
365
332
  };
366
333
  const runRuntime = (run) => driver?.run(run) ?? run();
334
+ const replayRuntime = (run, message) => {
335
+ try {
336
+ return runRuntime(run);
337
+ } catch (error) {
338
+ throw normalizeLocalSyncError(error, "consistency", message);
339
+ }
340
+ };
367
341
  const applyStored = (commit) => {
368
- if (commit.seq !== headSeq + 1) throw new LocalSyncConsistencyError("Local commit log contains a sequence gap.");
369
- if (runRuntime(() => runtime.apply(commit.changes, {
342
+ if (commit.seq !== headSeq + 1) throw new LocalSyncError("consistency", "Local commit log contains a sequence gap.");
343
+ if (replayRuntime(() => runtime.apply(commit.changes, {
370
344
  expectedRevision: runtime.revision(),
371
- source: "remote",
372
- history: false
373
- })).status !== "committed") throw new LocalSyncConsistencyError("A stored local ChangeSet could not be applied.");
345
+ source: "remote"
346
+ }), "A stored local ChangeSet could not be replayed.").status !== "committed") throw new LocalSyncError("consistency", "A stored local ChangeSet could not be applied.");
374
347
  headSeq = commit.seq;
375
348
  };
376
349
  const restore = async (reset = false) => {
377
350
  const current = await timeline.read(documentId);
378
- if (current.schemaVersion !== schemaVersion) throw new LocalSyncConsistencyError("Local document schema changed while this attachment was open.");
379
- if (headSeq > current.headSeq) throw new LocalSyncConsistencyError("The local timeline moved behind this attachment.");
351
+ if (current.schemaVersion !== schemaVersion) throw new LocalSyncError("consistency", "Local document schema changed while this attachment was open.");
352
+ if (headSeq > current.headSeq) throw new LocalSyncError("consistency", "The local timeline moved behind this attachment.");
380
353
  if (reset || headSeq < current.checkpointSeq) {
381
- if (runRuntime(() => runtime.replace(current.checkpoint, { source: "remote" })).status === "rejected") throw new LocalSyncConsistencyError("The local checkpoint could not be restored.");
354
+ if (replayRuntime(() => runtime.replace(current.checkpoint, { source: "remote" }), "The local checkpoint could not be restored.").status === "rejected") throw new LocalSyncError("consistency", "The local checkpoint could not be restored.");
382
355
  headSeq = current.checkpointSeq;
383
356
  }
384
357
  const tail = await timeline.tail(documentId, headSeq);
385
358
  for (const commit of tail) applyStored(commit);
386
- if (headSeq !== current.headSeq) throw new LocalSyncConsistencyError("Local commit log does not reach its recorded head sequence.");
359
+ if (headSeq !== current.headSeq) throw new LocalSyncError("consistency", "Local commit log does not reach its recorded head sequence.");
387
360
  checkpointSeq = current.checkpointSeq;
388
361
  publishState();
389
362
  };
@@ -400,7 +373,7 @@ const attachLocalSync = async (input) => {
400
373
  expectedHeadSeq: headSeq,
401
374
  changes
402
375
  });
403
- if (storedCommit.seq !== headSeq + 1) throw new LocalSyncConsistencyError("A local commit was assigned an unexpected sequence.");
376
+ if (storedCommit.seq !== headSeq + 1) throw new LocalSyncError("consistency", "A local commit was assigned an unexpected sequence.");
404
377
  headSeq = storedCommit.seq;
405
378
  publishState();
406
379
  channel?.postMessage({
@@ -492,7 +465,7 @@ const attachLocalSync = async (input) => {
492
465
  current: () => snapshot,
493
466
  revision: () => stateRevision,
494
467
  subscribe: (listener) => {
495
- if (disposed) throw new LocalSyncDisposedError();
468
+ if (disposed) throw new LocalSyncError("disposed", "Local sync has been disposed.");
496
469
  stateListeners.add(listener);
497
470
  return () => {
498
471
  stateListeners.delete(listener);
@@ -504,8 +477,7 @@ const attachLocalSync = async (input) => {
504
477
  try {
505
478
  await enqueue(restore);
506
479
  } catch (error) {
507
- fail(error);
508
- throw error;
480
+ throw fail(error);
509
481
  }
510
482
  assertOpen();
511
483
  },
@@ -534,10 +506,10 @@ const attachLocalSync = async (input) => {
534
506
  driver?.dispose();
535
507
  channel?.close();
536
508
  timeline.close();
537
- throw error;
509
+ throw normalizeLocalSyncError(error, "unavailable", "Unable to attach local sync.");
538
510
  }
539
511
  };
540
512
  //#endregion
541
- export { LocalSyncConsistencyError, LocalSyncDataError, LocalSyncDisposedError, LocalSyncReadOnlyError, LocalSyncSchemaError, LocalSyncUnavailableError, LocalSyncUnsupportedOperationError, attachLocalSync, defaultJsonChangeLimits };
513
+ export { LocalSyncError, attachLocalSync, defaultJsonChangeLimits };
542
514
 
543
515
  //# sourceMappingURL=local-sync.js.map