@interocitor/core 0.0.0-beta.3 → 0.0.0-beta.5
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 +446 -91
- package/dist/adapters/cloudflare.d.ts +8 -9
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +38 -12
- package/dist/adapters/google-drive.d.ts +1 -1
- package/dist/adapters/google-drive.js +1 -2
- package/dist/adapters/memory.d.ts +4 -1
- package/dist/adapters/memory.d.ts.map +1 -1
- package/dist/adapters/memory.js +13 -2
- package/dist/adapters/webdav.d.ts +5 -0
- package/dist/adapters/webdav.d.ts.map +1 -1
- package/dist/adapters/webdav.js +18 -1
- package/dist/core/codec.d.ts +1 -1
- package/dist/core/codec.d.ts.map +1 -1
- package/dist/core/codec.js +39 -3
- package/dist/core/compaction.d.ts +9 -1
- package/dist/core/compaction.d.ts.map +1 -1
- package/dist/core/compaction.js +63 -7
- package/dist/core/crdt.d.ts +6 -3
- package/dist/core/crdt.d.ts.map +1 -1
- package/dist/core/crdt.js +53 -67
- package/dist/core/errors.d.ts +47 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +61 -0
- package/dist/core/flush.d.ts +3 -3
- package/dist/core/flush.d.ts.map +1 -1
- package/dist/core/flush.js +64 -7
- package/dist/core/hlc.js +0 -1
- package/dist/core/ids.d.ts +49 -0
- package/dist/core/ids.d.ts.map +1 -0
- package/dist/core/ids.js +132 -0
- package/dist/core/internals.d.ts +10 -2
- package/dist/core/internals.d.ts.map +1 -1
- package/dist/core/internals.js +27 -9
- package/dist/core/manifest.d.ts +24 -5
- package/dist/core/manifest.d.ts.map +1 -1
- package/dist/core/manifest.js +80 -13
- package/dist/core/pull.d.ts +1 -1
- package/dist/core/pull.d.ts.map +1 -1
- package/dist/core/pull.js +21 -6
- package/dist/core/row-id.js +0 -1
- package/dist/core/schema-types.d.ts +22 -11
- package/dist/core/schema-types.d.ts.map +1 -1
- package/dist/core/schema-types.js +18 -9
- package/dist/core/schema-types.type-test.js +59 -5
- package/dist/core/sync-engine.d.ts +166 -12
- package/dist/core/sync-engine.d.ts.map +1 -1
- package/dist/core/sync-engine.js +1615 -219
- package/dist/core/table.d.ts +217 -17
- package/dist/core/table.d.ts.map +1 -1
- package/dist/core/table.js +376 -24
- package/dist/core/types.d.ts +413 -17
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +0 -1
- package/dist/crypto/encryption.d.ts.map +1 -1
- package/dist/crypto/encryption.js +6 -1
- package/dist/crypto/keys.js +0 -1
- package/dist/handshake/channel.js +0 -1
- package/dist/handshake/index.d.ts +5 -2
- package/dist/handshake/index.d.ts.map +1 -1
- package/dist/handshake/index.js +19 -2
- package/dist/handshake/qr.js +0 -1
- package/dist/index.d.ts +9 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -6
- package/dist/storage/credential-store.d.ts +25 -2
- package/dist/storage/credential-store.d.ts.map +1 -1
- package/dist/storage/credential-store.js +55 -8
- package/dist/storage/local-store.d.ts +4 -1
- package/dist/storage/local-store.d.ts.map +1 -1
- package/dist/storage/local-store.js +37 -21
- package/package.json +3 -3
- package/dist/adapters/cloudflare.js.map +0 -1
- package/dist/adapters/google-drive.js.map +0 -1
- package/dist/adapters/memory.js.map +0 -1
- package/dist/adapters/webdav.js.map +0 -1
- package/dist/core/codec.js.map +0 -1
- package/dist/core/compaction.js.map +0 -1
- package/dist/core/crdt.js.map +0 -1
- package/dist/core/flush.js.map +0 -1
- package/dist/core/hlc.js.map +0 -1
- package/dist/core/internals.js.map +0 -1
- package/dist/core/manifest.js.map +0 -1
- package/dist/core/pull.js.map +0 -1
- package/dist/core/row-id.js.map +0 -1
- package/dist/core/schema-types.js.map +0 -1
- package/dist/core/schema-types.type-test.js.map +0 -1
- package/dist/core/sync-engine.js.map +0 -1
- package/dist/core/table.js.map +0 -1
- package/dist/core/types.js.map +0 -1
- package/dist/crypto/encryption.js.map +0 -1
- package/dist/crypto/keys.js.map +0 -1
- package/dist/handshake/channel.js.map +0 -1
- package/dist/handshake/index.js.map +0 -1
- package/dist/handshake/qr.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/storage/credential-store.js.map +0 -1
- package/dist/storage/local-store.js.map +0 -1
|
@@ -14,6 +14,12 @@ export class CloudflareAdapter {
|
|
|
14
14
|
constructor(config) {
|
|
15
15
|
this.name = 'cloudflare';
|
|
16
16
|
this.authenticated = false;
|
|
17
|
+
// Per-session cache of folders we have already ensured. Cloudflare's
|
|
18
|
+
// /ensure-folder is idempotent but a POST per folder per connect is
|
|
19
|
+
// pure round-trip overhead — Interocitor.connect re-runs the same
|
|
20
|
+
// 4-folder loop on every reload. Cache wipes via `resetFolderCache()`
|
|
21
|
+
// (mesh swap / poison) or process exit.
|
|
22
|
+
this.ensuredFolders = new Set();
|
|
17
23
|
this.config = { ...config, baseUrl: config.baseUrl.replace(/\/$/, '') };
|
|
18
24
|
}
|
|
19
25
|
headers(extra) {
|
|
@@ -23,23 +29,33 @@ export class CloudflareAdapter {
|
|
|
23
29
|
}
|
|
24
30
|
return { ...base, ...extra };
|
|
25
31
|
}
|
|
26
|
-
|
|
27
|
-
const
|
|
32
|
+
parseBaseUrl() {
|
|
33
|
+
const base = /^https?:\/\//i.test(this.config.baseUrl)
|
|
34
|
+
? this.config.baseUrl
|
|
35
|
+
: new URL(this.config.baseUrl, 'http://interocitor').toString();
|
|
36
|
+
const u = new URL(base);
|
|
28
37
|
if (!u.pathname.includes('/io/')) {
|
|
29
38
|
throw new Error('CloudflareAdapter baseUrl must include /io/<prefix>');
|
|
30
39
|
}
|
|
40
|
+
return u;
|
|
41
|
+
}
|
|
42
|
+
get ioBaseUrl() {
|
|
43
|
+
const u = this.parseBaseUrl();
|
|
44
|
+
if (!/^https?:\/\//i.test(this.config.baseUrl)) {
|
|
45
|
+
return `${u.pathname}${u.search}${u.hash}`.replace(/\/$/, '');
|
|
46
|
+
}
|
|
31
47
|
return u.toString().replace(/\/$/, '');
|
|
32
48
|
}
|
|
33
49
|
get notifyUrl() {
|
|
34
|
-
const u =
|
|
35
|
-
if (!u.pathname.includes('/io/')) {
|
|
36
|
-
throw new Error('CloudflareAdapter baseUrl must include /io/<prefix>');
|
|
37
|
-
}
|
|
50
|
+
const u = this.parseBaseUrl();
|
|
38
51
|
u.pathname = u.pathname.replace('/io/', '/notify/');
|
|
39
52
|
u.protocol = u.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
40
53
|
if (this.config.token) {
|
|
41
54
|
u.searchParams.set('access_token', this.config.token);
|
|
42
55
|
}
|
|
56
|
+
if (!/^https?:\/\//i.test(this.config.baseUrl)) {
|
|
57
|
+
return `${u.pathname}${u.search}${u.hash}`.replace(/\/$/, '');
|
|
58
|
+
}
|
|
43
59
|
return u.toString().replace(/\/$/, '');
|
|
44
60
|
}
|
|
45
61
|
ioUrl(pathname) {
|
|
@@ -87,8 +103,8 @@ export class CloudflareAdapter {
|
|
|
87
103
|
try {
|
|
88
104
|
ws = new WebSocket(this.notifyUrl);
|
|
89
105
|
}
|
|
90
|
-
catch {
|
|
91
|
-
hooks?.onError?.();
|
|
106
|
+
catch (error) {
|
|
107
|
+
hooks?.onError?.(error);
|
|
92
108
|
scheduleReconnect();
|
|
93
109
|
return;
|
|
94
110
|
}
|
|
@@ -107,12 +123,14 @@ export class CloudflareAdapter {
|
|
|
107
123
|
onInvalidate({ type: 'unknown', path: '/', ts: Date.now() });
|
|
108
124
|
}
|
|
109
125
|
};
|
|
110
|
-
ws.onerror = () => {
|
|
111
|
-
hooks?.onError?.();
|
|
126
|
+
ws.onerror = (event) => {
|
|
127
|
+
hooks?.onError?.(event);
|
|
112
128
|
};
|
|
113
129
|
ws.onclose = () => {
|
|
114
|
-
if (!cancelled)
|
|
130
|
+
if (!cancelled) {
|
|
131
|
+
hooks?.onClose?.();
|
|
115
132
|
scheduleReconnect();
|
|
133
|
+
}
|
|
116
134
|
};
|
|
117
135
|
};
|
|
118
136
|
const scheduleReconnect = () => {
|
|
@@ -132,6 +150,8 @@ export class CloudflareAdapter {
|
|
|
132
150
|
};
|
|
133
151
|
}
|
|
134
152
|
async ensureFolder(path) {
|
|
153
|
+
if (this.ensuredFolders.has(path))
|
|
154
|
+
return;
|
|
135
155
|
const res = await fetch(this.ioUrl('/ensure-folder'), {
|
|
136
156
|
method: 'POST',
|
|
137
157
|
headers: this.headers({ 'Content-Type': 'application/json; charset=utf-8' }),
|
|
@@ -140,6 +160,13 @@ export class CloudflareAdapter {
|
|
|
140
160
|
if (!res.ok && res.status !== 405) {
|
|
141
161
|
throw new Error(`Failed to ensure folder ${path}: HTTP ${res.status}`);
|
|
142
162
|
}
|
|
163
|
+
this.ensuredFolders.add(path);
|
|
164
|
+
}
|
|
165
|
+
/** Drop the per-session ensureFolder cache. Call after mesh swap, poison,
|
|
166
|
+
* or any state where a previous "this folder exists" observation must
|
|
167
|
+
* not be trusted. */
|
|
168
|
+
resetFolderCache() {
|
|
169
|
+
this.ensuredFolders.clear();
|
|
143
170
|
}
|
|
144
171
|
async listFiles(path) {
|
|
145
172
|
const res = await fetch(this.ioUrl('/list-files'), {
|
|
@@ -224,4 +251,3 @@ export class CloudflareAdapter {
|
|
|
224
251
|
};
|
|
225
252
|
}
|
|
226
253
|
}
|
|
227
|
-
//# sourceMappingURL=cloudflare.js.map
|
|
@@ -22,7 +22,7 @@ interface GoogleDriveConfig {
|
|
|
22
22
|
* @example
|
|
23
23
|
* ```ts
|
|
24
24
|
* const adapter = new GoogleDriveAdapter({ clientId: 'YOUR_GOOGLE_CLIENT_ID' });
|
|
25
|
-
* const engine = new
|
|
25
|
+
* const engine = new Interocitor(adapter, { remotePath: '/MyApp' });
|
|
26
26
|
* ```
|
|
27
27
|
*/
|
|
28
28
|
export declare class GoogleDriveAdapter implements StorageAdapter {
|
|
@@ -19,7 +19,7 @@ const UPLOAD_API = 'https://www.googleapis.com/upload/drive/v3';
|
|
|
19
19
|
* @example
|
|
20
20
|
* ```ts
|
|
21
21
|
* const adapter = new GoogleDriveAdapter({ clientId: 'YOUR_GOOGLE_CLIENT_ID' });
|
|
22
|
-
* const engine = new
|
|
22
|
+
* const engine = new Interocitor(adapter, { remotePath: '/MyApp' });
|
|
23
23
|
* ```
|
|
24
24
|
*/
|
|
25
25
|
export class GoogleDriveAdapter {
|
|
@@ -337,4 +337,3 @@ export class GoogleDriveAdapter {
|
|
|
337
337
|
this.folderIdCache.clear();
|
|
338
338
|
}
|
|
339
339
|
}
|
|
340
|
-
//# sourceMappingURL=google-drive.js.map
|
|
@@ -14,7 +14,7 @@ import type { StorageAdapter, FileEntry } from '../core/types.ts';
|
|
|
14
14
|
* @example
|
|
15
15
|
* ```ts
|
|
16
16
|
* const adapter = new MemoryAdapter();
|
|
17
|
-
* const engine = new
|
|
17
|
+
* const engine = new Interocitor(adapter, { remotePath: '/Demo' });
|
|
18
18
|
* ```
|
|
19
19
|
*/
|
|
20
20
|
export declare class MemoryAdapter implements StorageAdapter {
|
|
@@ -22,6 +22,7 @@ export declare class MemoryAdapter implements StorageAdapter {
|
|
|
22
22
|
private files;
|
|
23
23
|
private folders;
|
|
24
24
|
private authenticated;
|
|
25
|
+
private ensuredFolders;
|
|
25
26
|
authenticate(): Promise<void>;
|
|
26
27
|
/**
|
|
27
28
|
* Memory adapter has no real backend — returns an empty config.
|
|
@@ -30,6 +31,8 @@ export declare class MemoryAdapter implements StorageAdapter {
|
|
|
30
31
|
getHandshakeConfig(): string;
|
|
31
32
|
isAuthenticated(): boolean;
|
|
32
33
|
ensureFolder(path: string): Promise<void>;
|
|
34
|
+
/** Drop the per-session ensureFolder cache. Tests / mesh-swap callers. */
|
|
35
|
+
resetFolderCache(): void;
|
|
33
36
|
listFiles(folderPath: string): Promise<FileEntry[]>;
|
|
34
37
|
/** List immediate subfolder names under a path. */
|
|
35
38
|
listFolders(folderPath: string): Promise<string[]>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/adapters/memory.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElE;;;;;;;;;;;GAWG;AACH,qBAAa,aAAc,YAAW,cAAc;IAClD,QAAQ,CAAC,IAAI,YAAY;IAEzB,OAAO,CAAC,KAAK,CAAsE;IACnF,OAAO,CAAC,OAAO,CAA0B;IACzC,OAAO,CAAC,aAAa,CAAS;
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/adapters/memory.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElE;;;;;;;;;;;GAWG;AACH,qBAAa,aAAc,YAAW,cAAc;IAClD,QAAQ,CAAC,IAAI,YAAY;IAEzB,OAAO,CAAC,KAAK,CAAsE;IACnF,OAAO,CAAC,OAAO,CAA0B;IACzC,OAAO,CAAC,aAAa,CAAS;IAK9B,OAAO,CAAC,cAAc,CAA0B;IAE1C,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAInC;;;OAGG;IACH,kBAAkB,IAAI,MAAM;IAI5B,eAAe,IAAI,OAAO;IAIpB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAM/C,0EAA0E;IAC1E,gBAAgB,IAAI,IAAI;IAIlB,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAsBzD,mDAAmD;IAC7C,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAuBlD,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAM3C,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUjE,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAY9D,kDAAkD;IAClD,IAAI,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAS9B,oCAAoC;IACpC,KAAK,IAAI,IAAI;CAId"}
|
package/dist/adapters/memory.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* @example
|
|
14
14
|
* ```ts
|
|
15
15
|
* const adapter = new MemoryAdapter();
|
|
16
|
-
* const engine = new
|
|
16
|
+
* const engine = new Interocitor(adapter, { remotePath: '/Demo' });
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
19
|
export class MemoryAdapter {
|
|
@@ -22,6 +22,11 @@ export class MemoryAdapter {
|
|
|
22
22
|
this.files = new Map();
|
|
23
23
|
this.folders = new Set();
|
|
24
24
|
this.authenticated = false;
|
|
25
|
+
// Mirrors the cloud-adapter convention: cache "ensured" paths so a
|
|
26
|
+
// re-`ensureFolder` is a no-op. Memory adapter is cheap, but keeping
|
|
27
|
+
// the same shape lets tests assert call-count parity with the real
|
|
28
|
+
// adapters (cloudflare, webdav).
|
|
29
|
+
this.ensuredFolders = new Set();
|
|
25
30
|
}
|
|
26
31
|
async authenticate() {
|
|
27
32
|
this.authenticated = true;
|
|
@@ -37,7 +42,14 @@ export class MemoryAdapter {
|
|
|
37
42
|
return this.authenticated;
|
|
38
43
|
}
|
|
39
44
|
async ensureFolder(path) {
|
|
45
|
+
if (this.ensuredFolders.has(path))
|
|
46
|
+
return;
|
|
40
47
|
this.folders.add(path);
|
|
48
|
+
this.ensuredFolders.add(path);
|
|
49
|
+
}
|
|
50
|
+
/** Drop the per-session ensureFolder cache. Tests / mesh-swap callers. */
|
|
51
|
+
resetFolderCache() {
|
|
52
|
+
this.ensuredFolders.clear();
|
|
41
53
|
}
|
|
42
54
|
async listFiles(folderPath) {
|
|
43
55
|
const prefix = folderPath.endsWith('/') ? folderPath : folderPath + '/';
|
|
@@ -126,4 +138,3 @@ export class MemoryAdapter {
|
|
|
126
138
|
this.folders.clear();
|
|
127
139
|
}
|
|
128
140
|
}
|
|
129
|
-
//# sourceMappingURL=memory.js.map
|
|
@@ -34,6 +34,7 @@ export declare class WebDAVAdapter implements StorageAdapter {
|
|
|
34
34
|
readonly name = "webdav";
|
|
35
35
|
private config;
|
|
36
36
|
private authenticated;
|
|
37
|
+
private ensuredFolders;
|
|
37
38
|
constructor(config: WebDAVConfig);
|
|
38
39
|
private url;
|
|
39
40
|
private authHeader;
|
|
@@ -47,6 +48,10 @@ export declare class WebDAVAdapter implements StorageAdapter {
|
|
|
47
48
|
getHandshakeConfig(): string;
|
|
48
49
|
isAuthenticated(): boolean;
|
|
49
50
|
ensureFolder(path: string): Promise<void>;
|
|
51
|
+
/** Drop the per-session ensureFolder cache. Call after mesh swap, poison,
|
|
52
|
+
* or any state where we cannot trust a previous "this folder exists"
|
|
53
|
+
* observation. */
|
|
54
|
+
resetFolderCache(): void;
|
|
50
55
|
listFiles(folderPath: string): Promise<FileEntry[]>;
|
|
51
56
|
private parsePropfindResponse;
|
|
52
57
|
listFolders(folderPath: string): Promise<string[]>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webdav.d.ts","sourceRoot":"","sources":["../../src/adapters/webdav.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElE,UAAU,YAAY;IACpB,sGAAsG;IACtG,OAAO,EAAE,MAAM,CAAC;IAChB,kFAAkF;IAClF,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAClE;AAED;;;;;;;;;;GAUG;AACH,qBAAa,aAAc,YAAW,cAAc;IAClD,QAAQ,CAAC,IAAI,YAAY;IAEzB,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,aAAa,CAAS;
|
|
1
|
+
{"version":3,"file":"webdav.d.ts","sourceRoot":"","sources":["../../src/adapters/webdav.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElE,UAAU,YAAY;IACpB,sGAAsG;IACtG,OAAO,EAAE,MAAM,CAAC;IAChB,kFAAkF;IAClF,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAClE;AAED;;;;;;;;;;GAUG;AACH,qBAAa,aAAc,YAAW,cAAc;IAClD,QAAQ,CAAC,IAAI,YAAY;IAEzB,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,aAAa,CAAS;IAM9B,OAAO,CAAC,cAAc,CAA0B;gBAEpC,MAAM,EAAE,YAAY;IAIhC,OAAO,CAAC,GAAG;IAMX,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,OAAO;IAOT,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAgBnC;;;;OAIG;IACH,kBAAkB,IAAI,MAAM;IAI5B,eAAe,IAAI,OAAO;IAIpB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAsB/C;;uBAEmB;IACnB,gBAAgB,IAAI,IAAI;IAIlB,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IA0BzD,OAAO,CAAC,qBAAqB;IAoCvB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAmClD,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAY3C,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBjE,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYvC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;CAyC/D"}
|
package/dist/adapters/webdav.js
CHANGED
|
@@ -22,6 +22,12 @@ export class WebDAVAdapter {
|
|
|
22
22
|
constructor(config) {
|
|
23
23
|
this.name = 'webdav';
|
|
24
24
|
this.authenticated = false;
|
|
25
|
+
// Per-session cache of folders we have already MKCOL'd. WebDAV folders
|
|
26
|
+
// are stable for the life of the mesh; a caller (Interocitor.connect)
|
|
27
|
+
// re-runs the same 4-folder ensureFolder loop on every reload, which
|
|
28
|
+
// costs 4 sequential MKCOL round-trips for no benefit. Cache wipes on
|
|
29
|
+
// explicit `resetFolderCache()` (mesh swap / poison) or process exit.
|
|
30
|
+
this.ensuredFolders = new Set();
|
|
25
31
|
this.config = config;
|
|
26
32
|
}
|
|
27
33
|
url(path) {
|
|
@@ -70,10 +76,14 @@ export class WebDAVAdapter {
|
|
|
70
76
|
return this.authenticated;
|
|
71
77
|
}
|
|
72
78
|
async ensureFolder(path) {
|
|
79
|
+
if (this.ensuredFolders.has(path))
|
|
80
|
+
return;
|
|
73
81
|
const parts = path.split('/').filter(Boolean);
|
|
74
82
|
let current = '';
|
|
75
83
|
for (const part of parts) {
|
|
76
84
|
current += '/' + part;
|
|
85
|
+
if (this.ensuredFolders.has(current))
|
|
86
|
+
continue; // ancestor cached
|
|
77
87
|
const res = await fetch(this.url(current), {
|
|
78
88
|
method: 'MKCOL',
|
|
79
89
|
headers: this.headers(),
|
|
@@ -82,7 +92,15 @@ export class WebDAVAdapter {
|
|
|
82
92
|
if (!res.ok && res.status !== 405) {
|
|
83
93
|
throw new Error(`Failed to create folder ${current}: ${res.status}`);
|
|
84
94
|
}
|
|
95
|
+
this.ensuredFolders.add(current);
|
|
85
96
|
}
|
|
97
|
+
this.ensuredFolders.add(path);
|
|
98
|
+
}
|
|
99
|
+
/** Drop the per-session ensureFolder cache. Call after mesh swap, poison,
|
|
100
|
+
* or any state where we cannot trust a previous "this folder exists"
|
|
101
|
+
* observation. */
|
|
102
|
+
resetFolderCache() {
|
|
103
|
+
this.ensuredFolders.clear();
|
|
86
104
|
}
|
|
87
105
|
async listFiles(folderPath) {
|
|
88
106
|
const res = await fetch(this.url(folderPath), {
|
|
@@ -244,4 +262,3 @@ export class WebDAVAdapter {
|
|
|
244
262
|
};
|
|
245
263
|
}
|
|
246
264
|
}
|
|
247
|
-
//# sourceMappingURL=webdav.js.map
|
package/dist/core/codec.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Codec — encryption/decryption of change and snapshot payloads.
|
|
3
3
|
*
|
|
4
|
-
* Extracted from
|
|
4
|
+
* Extracted from Interocitor to keep the orchestrator lean.
|
|
5
5
|
* Not part of the public API.
|
|
6
6
|
*/
|
|
7
7
|
import type { ChangeEntry, Manifest, Snapshot, LocalStoreAdapter } from './types.ts';
|
package/dist/core/codec.d.ts.map
CHANGED
|
@@ -1 +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,
|
|
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,CA8BtF;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,CAgBtB;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,CAgBnB"}
|
package/dist/core/codec.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Codec — encryption/decryption of change and snapshot payloads.
|
|
3
3
|
*
|
|
4
|
-
* Extracted from
|
|
4
|
+
* Extracted from Interocitor to keep the orchestrator lean.
|
|
5
5
|
* Not part of the public API.
|
|
6
6
|
*/
|
|
7
7
|
import { encryptEntry, decryptEntry } from "../crypto/encryption.js";
|
|
@@ -13,7 +13,36 @@ export async function encodeForCloud(state, plaintext) {
|
|
|
13
13
|
export async function decodeFromCloud(state, data) {
|
|
14
14
|
if (!state.encrypted || !state.encryptionKey)
|
|
15
15
|
return data;
|
|
16
|
-
|
|
16
|
+
try {
|
|
17
|
+
return await decryptEntry(state.encryptionKey, data);
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
// Re-throw with explicit context. Decode errors at this layer mean the
|
|
21
|
+
// active key cannot decrypt this payload — either the wrong key was
|
|
22
|
+
// loaded, or the payload was written under a different key (mesh swap,
|
|
23
|
+
// passphrase rotated, two devices bound to same dbName but different
|
|
24
|
+
// passphrases). Caller wraps this in poisonRemote with the file path.
|
|
25
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
26
|
+
let keyFingerprint = '<unknown>';
|
|
27
|
+
try {
|
|
28
|
+
const raw = await crypto.subtle.exportKey('raw', state.encryptionKey);
|
|
29
|
+
const hash = await crypto.subtle.digest('SHA-256', raw);
|
|
30
|
+
const bytes = new Uint8Array(hash);
|
|
31
|
+
const hex = Array.from(bytes.slice(0, 6)).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
32
|
+
keyFingerprint = `sha256-${hex}`;
|
|
33
|
+
}
|
|
34
|
+
catch { /* ignore */ }
|
|
35
|
+
console.log('[interocitor:decode] decodeFromCloud() — DECRYPT FAIL', {
|
|
36
|
+
keyFingerprint,
|
|
37
|
+
meshId: state.manifest?.meshId,
|
|
38
|
+
payloadFirst32: data.slice(0, 32),
|
|
39
|
+
payloadLen: data.length,
|
|
40
|
+
reason,
|
|
41
|
+
});
|
|
42
|
+
const e = new Error(`Decryption failed: payload not decryptable with the active mesh key (${reason}). The remote was likely written under a different key/mesh.`);
|
|
43
|
+
e.cause = err;
|
|
44
|
+
throw e;
|
|
45
|
+
}
|
|
17
46
|
}
|
|
18
47
|
export async function assertExpectedMeshId(local, manifest, meshId) {
|
|
19
48
|
if (!meshId) {
|
|
@@ -40,6 +69,10 @@ export async function encodeChangePayload(state, entry) {
|
|
|
40
69
|
export async function decodeChangePayload(state, local, data, path) {
|
|
41
70
|
const decoded = await decodeFromCloud(state, data);
|
|
42
71
|
const payload = JSON.parse(decoded);
|
|
72
|
+
const payloadRecord = payload;
|
|
73
|
+
if ('mesh' in payloadRecord && !('meshId' in payloadRecord)) {
|
|
74
|
+
throw new Error(`Remote change payload has invalid shape: ${path}`);
|
|
75
|
+
}
|
|
43
76
|
if (payload.kind !== 'change' || !payload.entry) {
|
|
44
77
|
throw new Error(`Remote change payload has invalid shape: ${path}`);
|
|
45
78
|
}
|
|
@@ -57,10 +90,13 @@ export async function encodeSnapshotPayload(state, snapshot) {
|
|
|
57
90
|
export async function decodeSnapshotPayload(state, local, data, path) {
|
|
58
91
|
const decoded = await decodeFromCloud(state, data);
|
|
59
92
|
const payload = JSON.parse(decoded);
|
|
93
|
+
const payloadRecord = payload;
|
|
94
|
+
if ('mesh' in payloadRecord && !('meshId' in payloadRecord)) {
|
|
95
|
+
throw new Error(`Remote snapshot payload has invalid shape: ${path}`);
|
|
96
|
+
}
|
|
60
97
|
if (payload.kind !== 'snapshot' || !payload.snapshot) {
|
|
61
98
|
throw new Error(`Remote snapshot payload has invalid shape: ${path}`);
|
|
62
99
|
}
|
|
63
100
|
await assertExpectedMeshId(local, state.manifest, String(payload.meshId || ''));
|
|
64
101
|
return payload.snapshot;
|
|
65
102
|
}
|
|
66
|
-
//# sourceMappingURL=codec.js.map
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Compaction — snapshot + manifest rotation + change file pruning.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Concurrency note:
|
|
5
|
+
* - No built-in lock or CAS on manifest pointer.
|
|
6
|
+
* - Concurrent compaction can race and overwrite the pointer.
|
|
7
|
+
* - Recommended: acquire a remote lease (e.g. mainline/compact-lock.json)
|
|
8
|
+
* and abort if another compactor is active, or if manifest generation
|
|
9
|
+
* changes after the lease is acquired.
|
|
10
|
+
*
|
|
11
|
+
* Extracted from Interocitor. Not part of the public API.
|
|
5
12
|
*/
|
|
6
13
|
import type { StorageAdapter, LocalStoreAdapter, Manifest, Row, SyncEvent } from './types.ts';
|
|
7
14
|
import type { HLC } from './types.ts';
|
|
@@ -17,6 +24,7 @@ export interface CompactContext {
|
|
|
17
24
|
serverId: string;
|
|
18
25
|
emit: (event: SyncEvent) => void;
|
|
19
26
|
pull: () => Promise<void>;
|
|
27
|
+
offlineGraceMs?: number;
|
|
20
28
|
}
|
|
21
29
|
export declare function compact(ctx: CompactContext): Promise<Manifest>;
|
|
22
30
|
export interface RehydrateContext {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compaction.d.ts","sourceRoot":"","sources":["../../src/core/compaction.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"compaction.d.ts","sourceRoot":"","sources":["../../src/core/compaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EACV,cAAc,EACd,iBAAiB,EACjB,QAAQ,EAGR,GAAG,EACH,SAAS,EAEV,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;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAkCD,wBAAsB,OAAO,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,QAAQ,CAAC,CAsGpE;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,CAiDnE"}
|
package/dist/core/compaction.js
CHANGED
|
@@ -1,13 +1,56 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Compaction — snapshot + manifest rotation + change file pruning.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Concurrency note:
|
|
5
|
+
* - No built-in lock or CAS on manifest pointer.
|
|
6
|
+
* - Concurrent compaction can race and overwrite the pointer.
|
|
7
|
+
* - Recommended: acquire a remote lease (e.g. mainline/compact-lock.json)
|
|
8
|
+
* and abort if another compactor is active, or if manifest generation
|
|
9
|
+
* changes after the lease is acquired.
|
|
10
|
+
*
|
|
11
|
+
* Extracted from Interocitor. Not part of the public API.
|
|
5
12
|
*/
|
|
6
13
|
import { hlcSerialize, hlcCompareStr } from "./hlc.js";
|
|
7
14
|
import { paths, textEncoder, textDecoder, generateId, computeContentHash, log } from "./internals.js";
|
|
8
15
|
import { encodeSnapshotPayload, decodeSnapshotPayload } from "./codec.js";
|
|
9
|
-
import { writeJson } from "./manifest.js";
|
|
16
|
+
import { readJsonIfExists, writeJson } from "./manifest.js";
|
|
10
17
|
import { hlcParse } from "./hlc.js";
|
|
18
|
+
async function computeGcFloor(ctx, nowMs) {
|
|
19
|
+
const p = paths(ctx.remotePath);
|
|
20
|
+
const graceMs = ctx.offlineGraceMs ?? 7 * 24 * 60 * 60000;
|
|
21
|
+
const cutoffMs = nowMs - graceMs;
|
|
22
|
+
const floors = [];
|
|
23
|
+
try {
|
|
24
|
+
const files = await ctx.adapter.listFiles(p.devicesFolder);
|
|
25
|
+
for (const file of files) {
|
|
26
|
+
if (!file.name.endsWith('.json'))
|
|
27
|
+
continue;
|
|
28
|
+
const deviceId = file.name.slice(0, -'.json'.length);
|
|
29
|
+
const meta = await readJsonIfExists(ctx.adapter, p.deviceFile(deviceId));
|
|
30
|
+
if (!meta || meta.retired)
|
|
31
|
+
continue;
|
|
32
|
+
const lastSeen = Date.parse(meta.lastSeenAt || '');
|
|
33
|
+
if (Number.isFinite(lastSeen) && lastSeen < cutoffMs)
|
|
34
|
+
continue;
|
|
35
|
+
// Active devices that have not yet acknowledged a watermark block
|
|
36
|
+
// advancement. They are still inside the offline grace window.
|
|
37
|
+
if (!meta.observedWatermarkHlc)
|
|
38
|
+
return ctx.manifest.gcFloorHlc ?? '';
|
|
39
|
+
floors.push(meta.observedWatermarkHlc);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return ctx.manifest.gcFloorHlc ?? '';
|
|
44
|
+
}
|
|
45
|
+
if (floors.length === 0)
|
|
46
|
+
return ctx.manifest.gcFloorHlc ?? '';
|
|
47
|
+
floors.sort(hlcCompareStr);
|
|
48
|
+
const candidate = floors[0];
|
|
49
|
+
const existing = ctx.manifest.gcFloorHlc ?? '';
|
|
50
|
+
if (existing && hlcCompareStr(existing, candidate) > 0)
|
|
51
|
+
return existing;
|
|
52
|
+
return candidate;
|
|
53
|
+
}
|
|
11
54
|
export async function compact(ctx) {
|
|
12
55
|
const { adapter, local, remotePath, manifest, codecState, deviceId, serverId } = ctx;
|
|
13
56
|
if (manifest.server.managed && deviceId !== serverId) {
|
|
@@ -16,7 +59,9 @@ export async function compact(ctx) {
|
|
|
16
59
|
// Ensure the compactor has merged latest remote changes before snapshotting.
|
|
17
60
|
await ctx.pull();
|
|
18
61
|
const p = paths(remotePath);
|
|
19
|
-
const
|
|
62
|
+
const nowDate = new Date();
|
|
63
|
+
const now = nowDate.toISOString();
|
|
64
|
+
const gcFloorHlc = await computeGcFloor(ctx, nowDate.getTime());
|
|
20
65
|
const nextEpoch = manifest.epoch + 1;
|
|
21
66
|
const nextGeneration = manifest.generation + 1;
|
|
22
67
|
const snapshotPath = `${p.mainlineFolder}/snapshot-${nextEpoch}-${serverId}.json`;
|
|
@@ -24,9 +69,16 @@ export async function compact(ctx) {
|
|
|
24
69
|
const allRows = await local.getAllRows();
|
|
25
70
|
const snapshotTables = {};
|
|
26
71
|
for (const row of allRows) {
|
|
27
|
-
if (
|
|
28
|
-
|
|
29
|
-
|
|
72
|
+
if (gcFloorHlc
|
|
73
|
+
&& row._meta.deleted
|
|
74
|
+
&& row._meta.deletedHlc
|
|
75
|
+
&& hlcCompareStr(row._meta.deletedHlc, gcFloorHlc) <= 0) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const t = row._meta.table;
|
|
79
|
+
if (!snapshotTables[t])
|
|
80
|
+
snapshotTables[t] = {};
|
|
81
|
+
snapshotTables[t][row._meta.rowId] = row;
|
|
30
82
|
}
|
|
31
83
|
const snapshot = {
|
|
32
84
|
snapshotId: generateId('snap'),
|
|
@@ -53,6 +105,10 @@ export async function compact(ctx) {
|
|
|
53
105
|
watermarkHlc: hlcSerialize(ctx.hlc),
|
|
54
106
|
snapshotPath,
|
|
55
107
|
deltaPath: null,
|
|
108
|
+
gcFloorHlc,
|
|
109
|
+
gcEpoch: gcFloorHlc ? nextEpoch : manifest.gcEpoch,
|
|
110
|
+
gcCreatedAt: gcFloorHlc ? now : manifest.gcCreatedAt,
|
|
111
|
+
offlineGraceMs: ctx.offlineGraceMs,
|
|
56
112
|
};
|
|
57
113
|
const nextManifest = {
|
|
58
114
|
...manifestPayload,
|
|
@@ -123,6 +179,7 @@ export async function rehydrate(ctx) {
|
|
|
123
179
|
ctx.emit({ type: 'rehydrate:complete', rowCount });
|
|
124
180
|
}
|
|
125
181
|
catch (err) {
|
|
182
|
+
ctx.emit({ type: 'decode:error', error: err instanceof Error ? err : new Error(String(err)), path: snapshotPath, context: { stage: 'rehydrate' } });
|
|
126
183
|
const poisoned = await ctx.poisonRemote(err, snapshotPath);
|
|
127
184
|
ctx.emit({ type: 'sync:error', error: poisoned });
|
|
128
185
|
throw poisoned;
|
|
@@ -131,4 +188,3 @@ export async function rehydrate(ctx) {
|
|
|
131
188
|
await ctx.pull();
|
|
132
189
|
return hlc;
|
|
133
190
|
}
|
|
134
|
-
//# sourceMappingURL=compaction.js.map
|
package/dist/core/crdt.d.ts
CHANGED
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
* - custom function — `(local, remote, ctx) => ColumnEntry`
|
|
11
11
|
*
|
|
12
12
|
* Deletes are soft (tombstone with HLC) and always use LWW.
|
|
13
|
+
*
|
|
14
|
+
* Row shape (`{_meta, payload}`) keeps user payload fully isolated from
|
|
15
|
+
* engine metadata. The merge loop only touches `row.payload`; `row._meta`
|
|
16
|
+
* is never indexed by user-controlled keys.
|
|
13
17
|
*/
|
|
14
18
|
import type { Row, Op, ChangeEntry, DatabaseSchemaDefinition } from './types.ts';
|
|
15
19
|
/**
|
|
@@ -22,12 +26,11 @@ export declare function applyOp(tables: Record<string, Record<string, Row>>, op:
|
|
|
22
26
|
* Returns list of affected rows.
|
|
23
27
|
*/
|
|
24
28
|
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
|
-
*/
|
|
29
|
+
/** Read a column value from a row, unwrapping the ColumnEntry. */
|
|
28
30
|
export declare function readColumn(row: Row, column: string): unknown;
|
|
29
31
|
/**
|
|
30
32
|
* Build a plain object from a row (strip HLC metadata).
|
|
33
|
+
* Returns user-facing fields with `_meta` projection (table, rowId, deleted).
|
|
31
34
|
*/
|
|
32
35
|
export declare function rowToPlain(row: Row): Record<string, unknown>;
|
|
33
36
|
//# sourceMappingURL=crdt.d.ts.map
|
package/dist/core/crdt.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crdt.d.ts","sourceRoot":"","sources":["../../src/core/crdt.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"crdt.d.ts","sourceRoot":"","sources":["../../src/core/crdt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EACV,GAAG,EACH,EAAE,EAEF,WAAW,EACX,wBAAwB,EAGzB,MAAM,YAAY,CAAC;AAoEpB;;;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,CAuEZ;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,kEAAkE;AAClE,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAG5D;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAU5D"}
|