@interocitor/core 0.0.0-beta.2
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/LICENSE +21 -0
- package/README.md +178 -0
- package/dist/adapters/cloudflare.d.ts +72 -0
- package/dist/adapters/cloudflare.d.ts.map +1 -0
- package/dist/adapters/cloudflare.js +227 -0
- package/dist/adapters/cloudflare.js.map +1 -0
- package/dist/adapters/google-drive.d.ts +64 -0
- package/dist/adapters/google-drive.d.ts.map +1 -0
- package/dist/adapters/google-drive.js +340 -0
- package/dist/adapters/google-drive.js.map +1 -0
- package/dist/adapters/memory.d.ts +45 -0
- package/dist/adapters/memory.d.ts.map +1 -0
- package/dist/adapters/memory.js +129 -0
- package/dist/adapters/memory.js.map +1 -0
- package/dist/adapters/webdav.d.ts +59 -0
- package/dist/adapters/webdav.d.ts.map +1 -0
- package/dist/adapters/webdav.js +247 -0
- package/dist/adapters/webdav.js.map +1 -0
- package/dist/core/codec.d.ts +20 -0
- package/dist/core/codec.d.ts.map +1 -0
- package/dist/core/codec.js +66 -0
- package/dist/core/codec.js.map +1 -0
- package/dist/core/compaction.d.ts +37 -0
- package/dist/core/compaction.d.ts.map +1 -0
- package/dist/core/compaction.js +134 -0
- package/dist/core/compaction.js.map +1 -0
- package/dist/core/crdt.d.ts +33 -0
- package/dist/core/crdt.d.ts.map +1 -0
- package/dist/core/crdt.js +188 -0
- package/dist/core/crdt.js.map +1 -0
- package/dist/core/flush.d.ts +9 -0
- package/dist/core/flush.d.ts.map +1 -0
- package/dist/core/flush.js +41 -0
- package/dist/core/flush.js.map +1 -0
- package/dist/core/hlc.d.ts +25 -0
- package/dist/core/hlc.d.ts.map +1 -0
- package/dist/core/hlc.js +76 -0
- package/dist/core/hlc.js.map +1 -0
- package/dist/core/internals.d.ts +25 -0
- package/dist/core/internals.d.ts.map +1 -0
- package/dist/core/internals.js +54 -0
- package/dist/core/internals.js.map +1 -0
- package/dist/core/manifest.d.ts +31 -0
- package/dist/core/manifest.d.ts.map +1 -0
- package/dist/core/manifest.js +111 -0
- package/dist/core/manifest.js.map +1 -0
- package/dist/core/pull.d.ts +26 -0
- package/dist/core/pull.d.ts.map +1 -0
- package/dist/core/pull.js +98 -0
- package/dist/core/pull.js.map +1 -0
- package/dist/core/row-id.d.ts +12 -0
- package/dist/core/row-id.d.ts.map +1 -0
- package/dist/core/row-id.js +12 -0
- package/dist/core/row-id.js.map +1 -0
- package/dist/core/schema-types.d.ts +13 -0
- package/dist/core/schema-types.d.ts.map +1 -0
- package/dist/core/schema-types.js +18 -0
- package/dist/core/schema-types.js.map +1 -0
- package/dist/core/schema-types.type-test.d.ts +2 -0
- package/dist/core/schema-types.type-test.d.ts.map +1 -0
- package/dist/core/schema-types.type-test.js +149 -0
- package/dist/core/schema-types.type-test.js.map +1 -0
- package/dist/core/sync-engine.d.ts +158 -0
- package/dist/core/sync-engine.d.ts.map +1 -0
- package/dist/core/sync-engine.js +714 -0
- package/dist/core/sync-engine.js.map +1 -0
- package/dist/core/table.d.ts +60 -0
- package/dist/core/table.d.ts.map +1 -0
- package/dist/core/table.js +106 -0
- package/dist/core/table.js.map +1 -0
- package/dist/core/types.d.ts +478 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +7 -0
- package/dist/core/types.js.map +1 -0
- package/dist/crypto/encryption.d.ts +57 -0
- package/dist/crypto/encryption.d.ts.map +1 -0
- package/dist/crypto/encryption.js +195 -0
- package/dist/crypto/encryption.js.map +1 -0
- package/dist/crypto/keys.d.ts +48 -0
- package/dist/crypto/keys.d.ts.map +1 -0
- package/dist/crypto/keys.js +55 -0
- package/dist/crypto/keys.js.map +1 -0
- package/dist/handshake/channel.d.ts +117 -0
- package/dist/handshake/channel.d.ts.map +1 -0
- package/dist/handshake/channel.js +246 -0
- package/dist/handshake/channel.js.map +1 -0
- package/dist/handshake/index.d.ts +213 -0
- package/dist/handshake/index.d.ts.map +1 -0
- package/dist/handshake/index.js +182 -0
- package/dist/handshake/index.js.map +1 -0
- package/dist/handshake/qr.d.ts +100 -0
- package/dist/handshake/qr.d.ts.map +1 -0
- package/dist/handshake/qr.js +103 -0
- package/dist/handshake/qr.js.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/index.js.map +1 -0
- package/dist/storage/credential-store.d.ts +99 -0
- package/dist/storage/credential-store.d.ts.map +1 -0
- package/dist/storage/credential-store.js +309 -0
- package/dist/storage/credential-store.js.map +1 -0
- package/dist/storage/local-store.d.ts +56 -0
- package/dist/storage/local-store.d.ts.map +1 -0
- package/dist/storage/local-store.js +411 -0
- package/dist/storage/local-store.js.map +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebDAV Storage Adapter
|
|
3
|
+
*
|
|
4
|
+
* For self-hosted clouds: Nextcloud, ownCloud, or any WebDAV server.
|
|
5
|
+
* "My own cloud" option — user runs their own server.
|
|
6
|
+
*
|
|
7
|
+
* WebDAV is HTTP-based, works from any browser.
|
|
8
|
+
* Most implementations support Basic auth or Bearer tokens.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Browser-friendly WebDAV adapter for Nextcloud, ownCloud, and compatible DAV servers.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* const adapter = new WebDAVAdapter({
|
|
16
|
+
* baseUrl: 'https://cloud.example.com/remote.php/dav/files/alice',
|
|
17
|
+
* auth: { username: 'alice', password: 'APP_PASSWORD' },
|
|
18
|
+
* });
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export class WebDAVAdapter {
|
|
22
|
+
constructor(config) {
|
|
23
|
+
this.name = 'webdav';
|
|
24
|
+
this.authenticated = false;
|
|
25
|
+
this.config = config;
|
|
26
|
+
}
|
|
27
|
+
url(path) {
|
|
28
|
+
const base = this.config.baseUrl.replace(/\/$/, '');
|
|
29
|
+
const clean = path.startsWith('/') ? path : '/' + path;
|
|
30
|
+
return base + clean;
|
|
31
|
+
}
|
|
32
|
+
authHeader() {
|
|
33
|
+
const auth = this.config.auth;
|
|
34
|
+
if ('token' in auth) {
|
|
35
|
+
return `Bearer ${auth.token}`;
|
|
36
|
+
}
|
|
37
|
+
return `Basic ${btoa(`${auth.username}:${auth.password}`)}`;
|
|
38
|
+
}
|
|
39
|
+
headers(extra) {
|
|
40
|
+
return {
|
|
41
|
+
Authorization: this.authHeader(),
|
|
42
|
+
...extra,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async authenticate() {
|
|
46
|
+
// Verify credentials by doing a PROPFIND on root
|
|
47
|
+
const res = await fetch(this.url('/'), {
|
|
48
|
+
method: 'PROPFIND',
|
|
49
|
+
headers: this.headers({ Depth: '0' }),
|
|
50
|
+
});
|
|
51
|
+
if (res.status === 207 || res.ok) {
|
|
52
|
+
this.authenticated = true;
|
|
53
|
+
}
|
|
54
|
+
else if (res.status === 401) {
|
|
55
|
+
throw new Error('WebDAV authentication failed');
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
throw new Error(`WebDAV error: ${res.status}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Returns the WebDAV base URL (without credentials) for embedding in a QR payload.
|
|
63
|
+
* The scanner uses this to point their WebDAVAdapter at the same server.
|
|
64
|
+
* Auth (username/password or token) must be configured separately by the user.
|
|
65
|
+
*/
|
|
66
|
+
getHandshakeConfig() {
|
|
67
|
+
return JSON.stringify({ baseUrl: this.config.baseUrl });
|
|
68
|
+
}
|
|
69
|
+
isAuthenticated() {
|
|
70
|
+
return this.authenticated;
|
|
71
|
+
}
|
|
72
|
+
async ensureFolder(path) {
|
|
73
|
+
const parts = path.split('/').filter(Boolean);
|
|
74
|
+
let current = '';
|
|
75
|
+
for (const part of parts) {
|
|
76
|
+
current += '/' + part;
|
|
77
|
+
const res = await fetch(this.url(current), {
|
|
78
|
+
method: 'MKCOL',
|
|
79
|
+
headers: this.headers(),
|
|
80
|
+
});
|
|
81
|
+
// 201 Created, 405 Already Exists — both fine
|
|
82
|
+
if (!res.ok && res.status !== 405) {
|
|
83
|
+
throw new Error(`Failed to create folder ${current}: ${res.status}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async listFiles(folderPath) {
|
|
88
|
+
const res = await fetch(this.url(folderPath), {
|
|
89
|
+
method: 'PROPFIND',
|
|
90
|
+
headers: this.headers({
|
|
91
|
+
Depth: '1',
|
|
92
|
+
'Content-Type': 'application/xml',
|
|
93
|
+
}),
|
|
94
|
+
body: `<?xml version="1.0" encoding="UTF-8"?>
|
|
95
|
+
<d:propfind xmlns:d="DAV:">
|
|
96
|
+
<d:prop>
|
|
97
|
+
<d:getcontentlength/>
|
|
98
|
+
<d:getlastmodified/>
|
|
99
|
+
<d:resourcetype/>
|
|
100
|
+
<d:getetag/>
|
|
101
|
+
</d:prop>
|
|
102
|
+
</d:propfind>`,
|
|
103
|
+
});
|
|
104
|
+
if (res.status !== 207) {
|
|
105
|
+
throw new Error(`PROPFIND failed: ${res.status}`);
|
|
106
|
+
}
|
|
107
|
+
const xml = await res.text();
|
|
108
|
+
return this.parsePropfindResponse(xml, folderPath);
|
|
109
|
+
}
|
|
110
|
+
parsePropfindResponse(xml, basePath) {
|
|
111
|
+
const parser = new DOMParser();
|
|
112
|
+
const doc = parser.parseFromString(xml, 'application/xml');
|
|
113
|
+
const responses = doc.getElementsByTagNameNS('DAV:', 'response');
|
|
114
|
+
const entries = [];
|
|
115
|
+
for (let i = 0; i < responses.length; i++) {
|
|
116
|
+
const response = responses[i];
|
|
117
|
+
const href = response.getElementsByTagNameNS('DAV:', 'href')[0]?.textContent || '';
|
|
118
|
+
// Skip the folder itself (first response)
|
|
119
|
+
if (i === 0)
|
|
120
|
+
continue;
|
|
121
|
+
// Skip sub-folders
|
|
122
|
+
const resourceType = response.getElementsByTagNameNS('DAV:', 'collection');
|
|
123
|
+
if (resourceType.length > 0)
|
|
124
|
+
continue;
|
|
125
|
+
const size = response.getElementsByTagNameNS('DAV:', 'getcontentlength')[0]?.textContent || '0';
|
|
126
|
+
const modified = response.getElementsByTagNameNS('DAV:', 'getlastmodified')[0]?.textContent || '';
|
|
127
|
+
const etag = response.getElementsByTagNameNS('DAV:', 'getetag')[0]?.textContent || undefined;
|
|
128
|
+
// Extract filename from href
|
|
129
|
+
const name = decodeURIComponent(href.split('/').filter(Boolean).pop() || '');
|
|
130
|
+
entries.push({
|
|
131
|
+
name,
|
|
132
|
+
path: `${basePath}/${name}`,
|
|
133
|
+
size: parseInt(size, 10),
|
|
134
|
+
modifiedTime: modified ? new Date(modified).toISOString() : '',
|
|
135
|
+
etag,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return entries;
|
|
139
|
+
}
|
|
140
|
+
async listFolders(folderPath) {
|
|
141
|
+
const res = await fetch(this.url(folderPath), {
|
|
142
|
+
method: 'PROPFIND',
|
|
143
|
+
headers: this.headers({
|
|
144
|
+
Depth: '1',
|
|
145
|
+
'Content-Type': 'application/xml',
|
|
146
|
+
}),
|
|
147
|
+
body: `<?xml version="1.0" encoding="UTF-8"?>
|
|
148
|
+
<d:propfind xmlns:d="DAV:">
|
|
149
|
+
<d:prop>
|
|
150
|
+
<d:resourcetype/>
|
|
151
|
+
</d:prop>
|
|
152
|
+
</d:propfind>`,
|
|
153
|
+
});
|
|
154
|
+
if (res.status !== 207)
|
|
155
|
+
return [];
|
|
156
|
+
const xml = await res.text();
|
|
157
|
+
const parser = new DOMParser();
|
|
158
|
+
const doc = parser.parseFromString(xml, 'application/xml');
|
|
159
|
+
const responses = doc.getElementsByTagNameNS('DAV:', 'response');
|
|
160
|
+
const folders = [];
|
|
161
|
+
for (let i = 1; i < responses.length; i++) {
|
|
162
|
+
const response = responses[i];
|
|
163
|
+
const isCollection = response.getElementsByTagNameNS('DAV:', 'collection').length > 0;
|
|
164
|
+
if (!isCollection)
|
|
165
|
+
continue;
|
|
166
|
+
const href = response.getElementsByTagNameNS('DAV:', 'href')[0]?.textContent || '';
|
|
167
|
+
const name = decodeURIComponent(href.split('/').filter(Boolean).pop() || '');
|
|
168
|
+
if (name)
|
|
169
|
+
folders.push(name);
|
|
170
|
+
}
|
|
171
|
+
return folders;
|
|
172
|
+
}
|
|
173
|
+
async readFile(path) {
|
|
174
|
+
const res = await fetch(this.url(path), {
|
|
175
|
+
method: 'GET',
|
|
176
|
+
headers: this.headers(),
|
|
177
|
+
});
|
|
178
|
+
if (!res.ok)
|
|
179
|
+
throw new Error(`Failed to read ${path}: ${res.status}`);
|
|
180
|
+
const buffer = await res.arrayBuffer();
|
|
181
|
+
return new Uint8Array(buffer);
|
|
182
|
+
}
|
|
183
|
+
async writeFile(path, data) {
|
|
184
|
+
const body = typeof data === 'string'
|
|
185
|
+
? new TextEncoder().encode(data)
|
|
186
|
+
: data;
|
|
187
|
+
const res = await fetch(this.url(path), {
|
|
188
|
+
method: 'PUT',
|
|
189
|
+
headers: this.headers({ 'Content-Type': 'application/octet-stream' }),
|
|
190
|
+
body: body,
|
|
191
|
+
});
|
|
192
|
+
if (!res.ok && res.status !== 201 && res.status !== 204) {
|
|
193
|
+
throw new Error(`Failed to write ${path}: ${res.status}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
async deleteFile(path) {
|
|
197
|
+
const res = await fetch(this.url(path), {
|
|
198
|
+
method: 'DELETE',
|
|
199
|
+
headers: this.headers(),
|
|
200
|
+
});
|
|
201
|
+
// 204 No Content or 404 Not Found — both acceptable
|
|
202
|
+
if (!res.ok && res.status !== 404) {
|
|
203
|
+
throw new Error(`Failed to delete ${path}: ${res.status}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
async getFileMetadata(path) {
|
|
207
|
+
const res = await fetch(this.url(path), {
|
|
208
|
+
method: 'PROPFIND',
|
|
209
|
+
headers: this.headers({
|
|
210
|
+
Depth: '0',
|
|
211
|
+
'Content-Type': 'application/xml',
|
|
212
|
+
}),
|
|
213
|
+
body: `<?xml version="1.0" encoding="UTF-8"?>
|
|
214
|
+
<d:propfind xmlns:d="DAV:">
|
|
215
|
+
<d:prop>
|
|
216
|
+
<d:getcontentlength/>
|
|
217
|
+
<d:getlastmodified/>
|
|
218
|
+
<d:getetag/>
|
|
219
|
+
</d:prop>
|
|
220
|
+
</d:propfind>`,
|
|
221
|
+
});
|
|
222
|
+
if (res.status === 404)
|
|
223
|
+
return null;
|
|
224
|
+
if (res.status !== 207)
|
|
225
|
+
return null;
|
|
226
|
+
const xml = await res.text();
|
|
227
|
+
// PROPFIND with Depth:0 on a file returns one entry for itself
|
|
228
|
+
// but our parser skips index 0, so we handle it differently
|
|
229
|
+
const parser = new DOMParser();
|
|
230
|
+
const doc = parser.parseFromString(xml, 'application/xml');
|
|
231
|
+
const response = doc.getElementsByTagNameNS('DAV:', 'response')[0];
|
|
232
|
+
if (!response)
|
|
233
|
+
return null;
|
|
234
|
+
const size = response.getElementsByTagNameNS('DAV:', 'getcontentlength')[0]?.textContent || '0';
|
|
235
|
+
const modified = response.getElementsByTagNameNS('DAV:', 'getlastmodified')[0]?.textContent || '';
|
|
236
|
+
const etag = response.getElementsByTagNameNS('DAV:', 'getetag')[0]?.textContent || undefined;
|
|
237
|
+
const name = path.split('/').filter(Boolean).pop() || '';
|
|
238
|
+
return {
|
|
239
|
+
name,
|
|
240
|
+
path,
|
|
241
|
+
size: parseInt(size, 10),
|
|
242
|
+
modifiedTime: modified ? new Date(modified).toISOString() : '',
|
|
243
|
+
etag,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
//# sourceMappingURL=webdav.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webdav.js","sourceRoot":"","sources":["../../src/adapters/webdav.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codec — encryption/decryption of change and snapshot payloads.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from SyncEngine to keep the orchestrator lean.
|
|
5
|
+
* Not part of the public API.
|
|
6
|
+
*/
|
|
7
|
+
import type { ChangeEntry, Manifest, Snapshot, LocalStoreAdapter } from './types.ts';
|
|
8
|
+
export interface CodecState {
|
|
9
|
+
encryptionKey: CryptoKey | null;
|
|
10
|
+
encrypted: boolean;
|
|
11
|
+
manifest: Manifest | null;
|
|
12
|
+
}
|
|
13
|
+
export declare function encodeForCloud(state: CodecState, plaintext: string): Promise<string>;
|
|
14
|
+
export declare function decodeFromCloud(state: CodecState, data: string): Promise<string>;
|
|
15
|
+
export declare function assertExpectedMeshId(local: LocalStoreAdapter, manifest: Manifest | null, meshId: string): Promise<void>;
|
|
16
|
+
export declare function encodeChangePayload(state: CodecState, entry: ChangeEntry): Promise<string>;
|
|
17
|
+
export declare function decodeChangePayload(state: CodecState, local: LocalStoreAdapter, data: string, path: string): Promise<ChangeEntry>;
|
|
18
|
+
export declare function encodeSnapshotPayload(state: CodecState, snapshot: Snapshot): Promise<string>;
|
|
19
|
+
export declare function decodeSnapshotPayload(state: CodecState, local: LocalStoreAdapter, data: string, path: string): Promise<Snapshot>;
|
|
20
|
+
//# sourceMappingURL=codec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codec.d.ts","sourceRoot":"","sources":["../../src/core/codec.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,WAAW,EACX,QAAQ,EACR,QAAQ,EAGR,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,UAAU;IACzB,aAAa,EAAE,SAAS,GAAG,IAAI,CAAC;IAChC,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;CAC3B;AAED,wBAAsB,cAAc,CAAC,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAG1F;AAED,wBAAsB,eAAe,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGtF;AAED,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,iBAAiB,EACxB,QAAQ,EAAE,QAAQ,GAAG,IAAI,EACzB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAgBf;AAED,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAQhG;AAED,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE,iBAAiB,EACxB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,WAAW,CAAC,CAUtB;AAED,wBAAsB,qBAAqB,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAQlG;AAED,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE,iBAAiB,EACxB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,QAAQ,CAAC,CAUnB"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codec — encryption/decryption of change and snapshot payloads.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from SyncEngine to keep the orchestrator lean.
|
|
5
|
+
* Not part of the public API.
|
|
6
|
+
*/
|
|
7
|
+
import { encryptEntry, decryptEntry } from "../crypto/encryption.js";
|
|
8
|
+
export async function encodeForCloud(state, plaintext) {
|
|
9
|
+
if (!state.encrypted || !state.encryptionKey)
|
|
10
|
+
return plaintext;
|
|
11
|
+
return encryptEntry(state.encryptionKey, plaintext);
|
|
12
|
+
}
|
|
13
|
+
export async function decodeFromCloud(state, data) {
|
|
14
|
+
if (!state.encrypted || !state.encryptionKey)
|
|
15
|
+
return data;
|
|
16
|
+
return decryptEntry(state.encryptionKey, data);
|
|
17
|
+
}
|
|
18
|
+
export async function assertExpectedMeshId(local, manifest, meshId) {
|
|
19
|
+
if (!meshId) {
|
|
20
|
+
throw new Error('Remote mesh is missing meshId');
|
|
21
|
+
}
|
|
22
|
+
const manifestMeshId = manifest?.meshId;
|
|
23
|
+
if (manifestMeshId && manifestMeshId !== meshId) {
|
|
24
|
+
throw new Error(`Remote mesh mismatch: expected ${manifestMeshId}, got ${meshId}`);
|
|
25
|
+
}
|
|
26
|
+
const storedMeshId = await local.getMeta('meshId');
|
|
27
|
+
if (typeof storedMeshId === 'string' && storedMeshId && storedMeshId !== meshId) {
|
|
28
|
+
throw new Error(`Remote mesh mismatch: expected ${storedMeshId}, got ${meshId}`);
|
|
29
|
+
}
|
|
30
|
+
await local.setMeta('meshId', meshId);
|
|
31
|
+
}
|
|
32
|
+
export async function encodeChangePayload(state, entry) {
|
|
33
|
+
const meshId = state.manifest?.meshId;
|
|
34
|
+
if (!meshId) {
|
|
35
|
+
throw new Error('Cannot encode change payload before manifest is loaded');
|
|
36
|
+
}
|
|
37
|
+
const payload = { meshId, kind: 'change', entry };
|
|
38
|
+
return encodeForCloud(state, JSON.stringify(payload));
|
|
39
|
+
}
|
|
40
|
+
export async function decodeChangePayload(state, local, data, path) {
|
|
41
|
+
const decoded = await decodeFromCloud(state, data);
|
|
42
|
+
const payload = JSON.parse(decoded);
|
|
43
|
+
if (payload.kind !== 'change' || !payload.entry) {
|
|
44
|
+
throw new Error(`Remote change payload has invalid shape: ${path}`);
|
|
45
|
+
}
|
|
46
|
+
await assertExpectedMeshId(local, state.manifest, String(payload.meshId || ''));
|
|
47
|
+
return payload.entry;
|
|
48
|
+
}
|
|
49
|
+
export async function encodeSnapshotPayload(state, snapshot) {
|
|
50
|
+
const meshId = state.manifest?.meshId;
|
|
51
|
+
if (!meshId) {
|
|
52
|
+
throw new Error('Cannot encode snapshot payload before manifest is loaded');
|
|
53
|
+
}
|
|
54
|
+
const payload = { meshId, kind: 'snapshot', snapshot };
|
|
55
|
+
return encodeForCloud(state, JSON.stringify(payload));
|
|
56
|
+
}
|
|
57
|
+
export async function decodeSnapshotPayload(state, local, data, path) {
|
|
58
|
+
const decoded = await decodeFromCloud(state, data);
|
|
59
|
+
const payload = JSON.parse(decoded);
|
|
60
|
+
if (payload.kind !== 'snapshot' || !payload.snapshot) {
|
|
61
|
+
throw new Error(`Remote snapshot payload has invalid shape: ${path}`);
|
|
62
|
+
}
|
|
63
|
+
await assertExpectedMeshId(local, state.manifest, String(payload.meshId || ''));
|
|
64
|
+
return payload.snapshot;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=codec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codec.js","sourceRoot":"","sources":["../../src/core/codec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compaction — snapshot + manifest rotation + change file pruning.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from SyncEngine. Not part of the public API.
|
|
5
|
+
*/
|
|
6
|
+
import type { StorageAdapter, LocalStoreAdapter, Manifest, Row, SyncEvent } from './types.ts';
|
|
7
|
+
import type { HLC } from './types.ts';
|
|
8
|
+
import type { CodecState } from './codec.ts';
|
|
9
|
+
export interface CompactContext {
|
|
10
|
+
adapter: StorageAdapter;
|
|
11
|
+
local: LocalStoreAdapter;
|
|
12
|
+
remotePath: string;
|
|
13
|
+
manifest: Manifest;
|
|
14
|
+
codecState: CodecState;
|
|
15
|
+
hlc: HLC;
|
|
16
|
+
deviceId: string;
|
|
17
|
+
serverId: string;
|
|
18
|
+
emit: (event: SyncEvent) => void;
|
|
19
|
+
pull: () => Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
export declare function compact(ctx: CompactContext): Promise<Manifest>;
|
|
22
|
+
export interface RehydrateContext {
|
|
23
|
+
adapter: StorageAdapter;
|
|
24
|
+
local: LocalStoreAdapter;
|
|
25
|
+
codecState: CodecState;
|
|
26
|
+
manifest: Manifest | null;
|
|
27
|
+
hlc: HLC;
|
|
28
|
+
deviceId: string;
|
|
29
|
+
tables: Record<string, Record<string, Row>>;
|
|
30
|
+
knownTables: Set<string>;
|
|
31
|
+
emit: (event: SyncEvent) => void;
|
|
32
|
+
poisonRemote: (error: unknown, path?: string) => Promise<Error>;
|
|
33
|
+
pull: () => Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
/** Returns the updated HLC after rehydration. */
|
|
36
|
+
export declare function rehydrate(ctx: RehydrateContext): Promise<HLC>;
|
|
37
|
+
//# sourceMappingURL=compaction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compaction.d.ts","sourceRoot":"","sources":["../../src/core/compaction.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,cAAc,EACd,iBAAiB,EACjB,QAAQ,EAGR,GAAG,EACH,SAAS,EACV,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAItC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAI7C,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,cAAc,CAAC;IACxB,KAAK,EAAE,iBAAiB,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,QAAQ,CAAC;IACnB,UAAU,EAAE,UAAU,CAAC;IACvB,GAAG,EAAE,GAAG,CAAC;IACT,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IACjC,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED,wBAAsB,OAAO,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,QAAQ,CAAC,CAuFpE;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,cAAc,CAAC;IACxB,KAAK,EAAE,iBAAiB,CAAC;IACzB,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1B,GAAG,EAAE,GAAG,CAAC;IACT,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;IAC5C,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACzB,IAAI,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IACjC,YAAY,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC;IAChE,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED,iDAAiD;AACjD,wBAAsB,SAAS,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC,CAgDnE"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compaction — snapshot + manifest rotation + change file pruning.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from SyncEngine. Not part of the public API.
|
|
5
|
+
*/
|
|
6
|
+
import { hlcSerialize, hlcCompareStr } from "./hlc.js";
|
|
7
|
+
import { paths, textEncoder, textDecoder, generateId, computeContentHash, log } from "./internals.js";
|
|
8
|
+
import { encodeSnapshotPayload, decodeSnapshotPayload } from "./codec.js";
|
|
9
|
+
import { writeJson } from "./manifest.js";
|
|
10
|
+
import { hlcParse } from "./hlc.js";
|
|
11
|
+
export async function compact(ctx) {
|
|
12
|
+
const { adapter, local, remotePath, manifest, codecState, deviceId, serverId } = ctx;
|
|
13
|
+
if (manifest.server.managed && deviceId !== serverId) {
|
|
14
|
+
throw new Error('Compaction is allowed only for the authorized server writer');
|
|
15
|
+
}
|
|
16
|
+
// Ensure the compactor has merged latest remote changes before snapshotting.
|
|
17
|
+
await ctx.pull();
|
|
18
|
+
const p = paths(remotePath);
|
|
19
|
+
const now = new Date().toISOString();
|
|
20
|
+
const nextEpoch = manifest.epoch + 1;
|
|
21
|
+
const nextGeneration = manifest.generation + 1;
|
|
22
|
+
const snapshotPath = `${p.mainlineFolder}/snapshot-${nextEpoch}-${serverId}.json`;
|
|
23
|
+
// Build a full snapshot from IDB — the in-memory cache is partial.
|
|
24
|
+
const allRows = await local.getAllRows();
|
|
25
|
+
const snapshotTables = {};
|
|
26
|
+
for (const row of allRows) {
|
|
27
|
+
if (!snapshotTables[row._table])
|
|
28
|
+
snapshotTables[row._table] = {};
|
|
29
|
+
snapshotTables[row._table][row._rowId] = row;
|
|
30
|
+
}
|
|
31
|
+
const snapshot = {
|
|
32
|
+
snapshotId: generateId('snap'),
|
|
33
|
+
timestamp: now,
|
|
34
|
+
hlc: hlcSerialize(ctx.hlc),
|
|
35
|
+
epoch: nextEpoch,
|
|
36
|
+
schemaVersion: manifest.schema,
|
|
37
|
+
tables: snapshotTables,
|
|
38
|
+
};
|
|
39
|
+
const snapshotPayload = await encodeSnapshotPayload(codecState, snapshot);
|
|
40
|
+
await adapter.writeFile(snapshotPath, textEncoder.encode(snapshotPayload));
|
|
41
|
+
const manifestPayload = {
|
|
42
|
+
generation: nextGeneration,
|
|
43
|
+
parentGeneration: manifest.generation,
|
|
44
|
+
writtenBy: serverId,
|
|
45
|
+
writtenAt: now,
|
|
46
|
+
version: 3,
|
|
47
|
+
meshId: manifest.meshId,
|
|
48
|
+
schema: manifest.schema,
|
|
49
|
+
encrypted: manifest.encrypted,
|
|
50
|
+
server: manifest.server,
|
|
51
|
+
createdAt: manifest.createdAt,
|
|
52
|
+
epoch: nextEpoch,
|
|
53
|
+
watermarkHlc: hlcSerialize(ctx.hlc),
|
|
54
|
+
snapshotPath,
|
|
55
|
+
deltaPath: null,
|
|
56
|
+
};
|
|
57
|
+
const nextManifest = {
|
|
58
|
+
...manifestPayload,
|
|
59
|
+
contentHash: await computeContentHash(manifestPayload),
|
|
60
|
+
};
|
|
61
|
+
const manifestFile = `manifest-${nextGeneration}.json`;
|
|
62
|
+
await writeJson(adapter, p.manifestFile(nextGeneration), nextManifest);
|
|
63
|
+
await writeJson(adapter, p.manifestPointer, {
|
|
64
|
+
currentGeneration: nextGeneration,
|
|
65
|
+
file: manifestFile,
|
|
66
|
+
});
|
|
67
|
+
await local.setMeta('epoch', nextEpoch);
|
|
68
|
+
// Prune all change files captured in the snapshot.
|
|
69
|
+
const watermarkHlc = nextManifest.watermarkHlc;
|
|
70
|
+
log('debug', 'compact() — pruning change files ≤ watermark', { watermarkHlc });
|
|
71
|
+
try {
|
|
72
|
+
const files = await adapter.listFiles(p.changesFolder);
|
|
73
|
+
for (const file of files) {
|
|
74
|
+
if (file.name === 'head.json')
|
|
75
|
+
continue;
|
|
76
|
+
const chgIdx = file.name.lastIndexOf('-chg_');
|
|
77
|
+
if (chgIdx === -1)
|
|
78
|
+
continue;
|
|
79
|
+
const fileHlc = file.name.slice(0, chgIdx);
|
|
80
|
+
if (hlcCompareStr(fileHlc, watermarkHlc) <= 0) {
|
|
81
|
+
await adapter.deleteFile(file.path);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
log('debug', 'compact() — pruning complete');
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
log('warn', 'compact() — pruning failed (non-fatal, snapshot is still valid)', err);
|
|
88
|
+
}
|
|
89
|
+
return nextManifest;
|
|
90
|
+
}
|
|
91
|
+
/** Returns the updated HLC after rehydration. */
|
|
92
|
+
export async function rehydrate(ctx) {
|
|
93
|
+
let hlc = ctx.hlc;
|
|
94
|
+
ctx.emit({ type: 'rehydrate:start' });
|
|
95
|
+
const snapshotPath = ctx.manifest?.snapshotPath;
|
|
96
|
+
if (!snapshotPath) {
|
|
97
|
+
ctx.emit({ type: 'rehydrate:complete', rowCount: 0 });
|
|
98
|
+
await ctx.pull();
|
|
99
|
+
return hlc;
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
const data = await ctx.adapter.readFile(snapshotPath);
|
|
103
|
+
const snapshot = await decodeSnapshotPayload(ctx.codecState, ctx.local, textDecoder.decode(data), snapshotPath);
|
|
104
|
+
// Clear local state
|
|
105
|
+
await ctx.local.clearAll();
|
|
106
|
+
ctx.tables = {};
|
|
107
|
+
ctx.knownTables.clear();
|
|
108
|
+
// Write snapshot rows to IDB
|
|
109
|
+
let rowCount = 0;
|
|
110
|
+
for (const [tableName, rows] of Object.entries(snapshot.tables)) {
|
|
111
|
+
ctx.knownTables.add(tableName);
|
|
112
|
+
for (const row of Object.values(rows)) {
|
|
113
|
+
await ctx.local.putRow(row);
|
|
114
|
+
rowCount++;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Restore HLC
|
|
118
|
+
if (snapshot.hlc) {
|
|
119
|
+
hlc = hlcParse(snapshot.hlc);
|
|
120
|
+
hlc.nodeId = ctx.deviceId;
|
|
121
|
+
}
|
|
122
|
+
await ctx.local.setMeta('epoch', snapshot.epoch);
|
|
123
|
+
ctx.emit({ type: 'rehydrate:complete', rowCount });
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
const poisoned = await ctx.poisonRemote(err, snapshotPath);
|
|
127
|
+
ctx.emit({ type: 'sync:error', error: poisoned });
|
|
128
|
+
throw poisoned;
|
|
129
|
+
}
|
|
130
|
+
// Pull any changes since the snapshot
|
|
131
|
+
await ctx.pull();
|
|
132
|
+
return hlc;
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=compaction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compaction.js","sourceRoot":"","sources":["../../src/core/compaction.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CRDT Merge Engine — configurable per-column merge strategy.
|
|
3
|
+
*
|
|
4
|
+
* Each column in each row carries its own HLC.
|
|
5
|
+
* The merge strategy determines which value wins on conflict:
|
|
6
|
+
*
|
|
7
|
+
* - `'remote-wins'` — Remote always overwrites local. (Default)
|
|
8
|
+
* - `'lww'` — Last-Writer-Wins. Highest HLC wins.
|
|
9
|
+
* - `'local-wins'` — Keep local value when both exist.
|
|
10
|
+
* - custom function — `(local, remote, ctx) => ColumnEntry`
|
|
11
|
+
*
|
|
12
|
+
* Deletes are soft (tombstone with HLC) and always use LWW.
|
|
13
|
+
*/
|
|
14
|
+
import type { Row, Op, ChangeEntry, DatabaseSchemaDefinition } from './types.ts';
|
|
15
|
+
/**
|
|
16
|
+
* Apply a single op to the in-memory state.
|
|
17
|
+
* Returns the affected row (mutated in place) or null if no change.
|
|
18
|
+
*/
|
|
19
|
+
export declare function applyOp(tables: Record<string, Record<string, Row>>, op: Op, schemaVersion: number, schema?: DatabaseSchemaDefinition): Row | null;
|
|
20
|
+
/**
|
|
21
|
+
* Apply a full change entry (potentially multiple ops).
|
|
22
|
+
* Returns list of affected rows.
|
|
23
|
+
*/
|
|
24
|
+
export declare function applyChangeEntry(tables: Record<string, Record<string, Row>>, entry: ChangeEntry, schemaVersion: number, schema?: DatabaseSchemaDefinition): Row[];
|
|
25
|
+
/**
|
|
26
|
+
* Read a column value from a row, unwrapping the ColumnEntry.
|
|
27
|
+
*/
|
|
28
|
+
export declare function readColumn(row: Row, column: string): unknown;
|
|
29
|
+
/**
|
|
30
|
+
* Build a plain object from a row (strip HLC metadata).
|
|
31
|
+
*/
|
|
32
|
+
export declare function rowToPlain(row: Row): Record<string, unknown>;
|
|
33
|
+
//# sourceMappingURL=crdt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crdt.d.ts","sourceRoot":"","sources":["../../src/core/crdt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EACV,GAAG,EACH,EAAE,EAEF,WAAW,EACX,wBAAwB,EAGzB,MAAM,YAAY,CAAC;AA0EpB;;;GAGG;AACH,wBAAgB,OAAO,CACrB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,EAC3C,EAAE,EAAE,EAAE,EACN,aAAa,EAAE,MAAM,EACrB,MAAM,CAAC,EAAE,wBAAwB,GAChC,GAAG,GAAG,IAAI,CA4EZ;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,EAC3C,KAAK,EAAE,WAAW,EAClB,aAAa,EAAE,MAAM,EACrB,MAAM,CAAC,EAAE,wBAAwB,GAChC,GAAG,EAAE,CAOP;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAM5D;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAa5D"}
|