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.
- package/README.md +208 -102
- package/dist/advanced.cjs +237 -2
- package/dist/advanced.cjs.map +1 -0
- package/dist/advanced.d.cts +90 -2
- package/dist/advanced.d.ts +90 -2
- package/dist/advanced.js +236 -1
- package/dist/advanced.js.map +1 -0
- package/dist/{contract-ULPQmiEs.d.ts → contract-CNR6BS75.d.cts} +216 -30
- package/dist/{contract-SYJFP0lM.d.cts → contract-COgxO5sv.d.ts} +216 -30
- package/dist/definition-CDOuvb5C.d.cts +111 -0
- package/dist/definition-xgk3YNBp.d.ts +111 -0
- package/dist/{driver-yFfvDMX5.js → driver-B5vdPohL.js} +59 -3
- package/dist/driver-B5vdPohL.js.map +1 -0
- package/dist/{driver-BD3DQOW6.cjs → driver-BL91gn7_.cjs} +107 -9
- package/dist/driver-BL91gn7_.cjs.map +1 -0
- package/dist/index.cjs +1637 -703
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +65 -33
- package/dist/index.d.ts +65 -33
- package/dist/index.js +1432 -497
- package/dist/index.js.map +1 -1
- package/dist/local-sync.cjs +69 -103
- package/dist/local-sync.cjs.map +1 -1
- package/dist/local-sync.d.cts +14 -26
- package/dist/local-sync.d.ts +14 -26
- package/dist/local-sync.js +68 -96
- package/dist/local-sync.js.map +1 -1
- package/dist/react.cjs +4 -5
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +6 -8
- package/dist/react.d.ts +6 -8
- package/dist/react.js +4 -5
- package/dist/react.js.map +1 -1
- package/dist/record-B2yRhYsc.js +28 -0
- package/dist/record-B2yRhYsc.js.map +1 -0
- package/dist/record-ChYUi8PP.cjs +45 -0
- package/dist/record-ChYUi8PP.cjs.map +1 -0
- package/dist/scheduler--137L3j-.js +500 -0
- package/dist/scheduler--137L3j-.js.map +1 -0
- package/dist/scheduler-D4VthaaK.cjs +589 -0
- package/dist/scheduler-D4VthaaK.cjs.map +1 -0
- package/package.json +6 -3
- package/skills/doxum-runtime/SKILL.md +32 -7
- package/skills/doxum-runtime/agents/openai.yaml +1 -1
- package/skills/doxum-runtime/references/api.en.md +249 -0
- package/skills/doxum-runtime/references/api.zh-CN.md +242 -0
- package/skills/doxum-runtime/references/guide.en.md +18 -8
- package/skills/doxum-runtime/references/guide.zh-CN.md +14 -6
- package/skills/doxum-runtime/references/invariants.en.md +29 -5
- package/skills/doxum-runtime/references/invariants.zh-CN.md +24 -6
- package/skills/doxum-runtime/references/patterns.en.md +78 -18
- package/skills/doxum-runtime/references/patterns.zh-CN.md +76 -18
- package/skills/doxum-runtime/references/projections.en.md +209 -119
- package/skills/doxum-runtime/references/projections.zh-CN.md +200 -101
- package/dist/advanced-CLJCWkHa.d.cts +0 -176
- package/dist/advanced-CRNHfJBu.js +0 -1224
- package/dist/advanced-CRNHfJBu.js.map +0 -1
- package/dist/advanced-Cle5g3B4.cjs +0 -1517
- package/dist/advanced-Cle5g3B4.cjs.map +0 -1
- package/dist/advanced-d6J61er9.d.ts +0 -176
- package/dist/context-BcLKGfq8.cjs +0 -149
- package/dist/context-BcLKGfq8.cjs.map +0 -1
- package/dist/context-DFEaDUv5.js +0 -84
- package/dist/context-DFEaDUv5.js.map +0 -1
- package/dist/driver-BD3DQOW6.cjs.map +0 -1
- package/dist/driver-yFfvDMX5.js.map +0 -1
package/dist/local-sync.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
22
|
-
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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,
|
|
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
|
package/dist/local-sync.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
22
|
-
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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,
|
|
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
|
package/dist/local-sync.js
CHANGED
|
@@ -1,42 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
//#region core/src/local-sync/
|
|
4
|
-
var
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
81
|
+
throw new LocalSyncError("invalid-data", `${label} contains an invalid ChangeSet.`);
|
|
115
82
|
}
|
|
116
|
-
if (limits && changeCount(changes) > limits.maxChanges) throw new
|
|
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
|
|
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
|
|
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
|
|
137
|
-
transaction.onerror = () => reject(transaction.error
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
343
|
-
try {
|
|
344
|
-
input.onError?.(error);
|
|
345
|
-
} catch {}
|
|
346
|
-
}
|
|
311
|
+
} catch {}
|
|
347
312
|
}
|
|
348
313
|
};
|
|
349
314
|
const fail = (error) => {
|
|
350
|
-
|
|
351
|
-
fault
|
|
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?.(
|
|
320
|
+
input.onError?.(failure);
|
|
355
321
|
} catch {}
|
|
322
|
+
return failure;
|
|
356
323
|
};
|
|
357
324
|
const assertOpen = () => {
|
|
358
|
-
if (closing || disposed) throw new
|
|
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
|
|
364
|
-
if (intent.kind === "replace" || intent.kind === "apply" && intent.source === "remote") throw new
|
|
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
|
|
369
|
-
if (
|
|
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
|
-
|
|
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
|
|
379
|
-
if (headSeq > current.headSeq) throw new
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
513
|
+
export { LocalSyncError, attachLocalSync, defaultJsonChangeLimits };
|
|
542
514
|
|
|
543
515
|
//# sourceMappingURL=local-sync.js.map
|