selfstore 1.8.34 → 1.8.36
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 +6 -2
- package/dist/advanced.d.ts +9 -9
- package/dist/advanced.js +2 -2
- package/dist/backups.d.ts +4 -4
- package/dist/{cache-D8Z4u5Jz.d.ts → cache-DBIn_Uo8.d.ts} +9 -1
- package/dist/{chunk-A2RRJLIF.js → chunk-3P2W3XQD.js} +36 -23
- package/dist/chunk-3P2W3XQD.js.map +1 -0
- package/dist/{chunk-HQSK7HK3.js → chunk-JE3BLKXO.js} +86 -32
- package/dist/chunk-JE3BLKXO.js.map +1 -0
- package/dist/{chunk-VRWAV3GI.js → chunk-UU4PS3UH.js} +19 -9
- package/dist/chunk-UU4PS3UH.js.map +1 -0
- package/dist/{connect-B9f58dFZ.d.ts → connect-CdlhiTBy.d.ts} +4 -4
- package/dist/{drive-D5YLXmp_.d.ts → drive-BCRil91Q.d.ts} +2 -2
- package/dist/flows.d.ts +4 -4
- package/dist/flows.js +1 -1
- package/dist/groups.d.ts +2 -2
- package/dist/households.d.ts +7 -0
- package/dist/households.js +13 -3
- package/dist/households.js.map +1 -1
- package/dist/index.d.ts +9 -9
- package/dist/index.js +3 -3
- package/dist/{local-CmS0xFkK.d.ts → local-CpoBeJWk.d.ts} +2 -2
- package/dist/{manager-DCV_7B6M.d.ts → manager-0puQEpZc.d.ts} +2 -2
- package/dist/passkey.d.ts +6 -4
- package/dist/passkey.js +5 -4
- package/dist/passkey.js.map +1 -1
- package/dist/proof.d.ts +6 -6
- package/dist/{s3-mHLrEywJ.d.ts → s3-kikEHI-g.d.ts} +2 -2
- package/dist/{simple-Bnh2j78j.d.ts → simple-BmUQpsBM.d.ts} +6 -6
- package/dist/{store-DSvZwwr1.d.ts → store-bp1VRW7R.d.ts} +1 -1
- package/dist/sync.js +1 -1
- package/dist/widgets.d.ts +5 -5
- package/dist/widgets.js +1 -1
- package/llms.txt +2 -1
- package/package.json +1 -1
- package/dist/chunk-A2RRJLIF.js.map +0 -1
- package/dist/chunk-HQSK7HK3.js.map +0 -1
- package/dist/chunk-VRWAV3GI.js.map +0 -1
package/README.md
CHANGED
|
@@ -66,13 +66,17 @@ in the environment where writing a real file is easiest. Hand the shell's own
|
|
|
66
66
|
calls over once and it comes back, on a real path:
|
|
67
67
|
|
|
68
68
|
```ts
|
|
69
|
-
import { readFile, writeFile, stat, exists } from '@tauri-apps/plugin-fs';
|
|
69
|
+
import { readFile, writeFile, rename, stat, exists } from '@tauri-apps/plugin-fs';
|
|
70
70
|
import { save, open } from '@tauri-apps/plugin-dialog';
|
|
71
71
|
import { useDesktopFiles } from 'selfstore';
|
|
72
72
|
|
|
73
|
-
useDesktopFiles({ readFile, writeFile, stat, exists, save, open });
|
|
73
|
+
useDesktopFiles({ readFile, writeFile, rename, stat, exists, save, open });
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
+
`rename` is what makes a save safe: the backup is written beside the previous
|
|
77
|
+
one and moved over it, so a full disk or a crash mid-write leaves the last good
|
|
78
|
+
copy whole. Without it the file is overwritten in place.
|
|
79
|
+
|
|
76
80
|
That is the whole integration. `connectFile()`, `file: true` in the connect
|
|
77
81
|
flow and the widgets all keep meaning what they meant; they just write a path.
|
|
78
82
|
The path outlives the session, so the app reopens on its file with nothing
|
package/dist/advanced.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { B as BackupTarget } from './store-
|
|
2
|
-
export { A as AuthExpiredError, f as BuiltinTargetKind, E as EncodeOptions, H as Header, K as KdfParams, L as LocalStore, g as LocalStoreOptions, e as LocalStoreState, M as MirrorState, h as Mode, i as PasswordCheck, b as PasswordPolicy, j as PasswordRequirement, P as PeerSource, k as PeerState, R as RESERVED_COLLECTION_PREFIX, l as RESERVED_STORE_MODES, m as RecipientStanza, n as ReplicaState, o as SelfstoreError, p as SelfstoreErrorCode, q as Severity, a as Snapshot, c as SnapshotFile, r as StatusAction, d as StatusDescriptor, s as StatusInput, t as StorageState, S as StoreError, u as StoreGroupConfig, v as SyncJournalEntry, w as SyncSource, T as TargetKind, x as checkPasswordPolicy, y as createLocalStore, z as deriveStatus, C as errorLabelKey, D as isAuthExpired, F as isReservedStoreMode, G as isSelfstoreError } from './store-
|
|
3
|
-
import { K as KV } from './cache-
|
|
4
|
-
export { C as CachedFile, L as LocalCache, e as LockableCache, i as indexedDbCache, f as isLockableCache, m as memoryCache } from './cache-
|
|
5
|
-
export { G as GisDriveAuthOptions, a as GisTokenPersistence, d as datedName, e as exportSnapshot, g as gisDriveAuth, i as importSnapshot, b as inspect, c as isEncrypted, p as pickFromDisk, f as preloadGoogleIdentity, s as saveToDisk } from './local-
|
|
6
|
-
import { c as connect$2, f as fromSession$2, a as connect$3, b as fromSession$3, p as peer } from './s3-
|
|
7
|
-
export { S as S3Config, d as S3ConnectOptions, W as WebdavConfig, e as WebdavConnectOptions, g as WebdavPeerOptions } from './s3-
|
|
8
|
-
import { c as connect$1, f as fromSession$1, p as preview, b as adopt, d as findOrCreateOwnFile, l as listBackups, e as createBackup, g as deleteBackup, r as renameBackup, h as account, i as createCompanion, s as share, u as unshare, o as owner, j as secondary } from './drive-
|
|
9
|
-
export { k as DriveAccount, a as DriveAuth, m as DriveBackupInfo, D as DriveOptions } from './drive-
|
|
1
|
+
import { B as BackupTarget } from './store-bp1VRW7R.js';
|
|
2
|
+
export { A as AuthExpiredError, f as BuiltinTargetKind, E as EncodeOptions, H as Header, K as KdfParams, L as LocalStore, g as LocalStoreOptions, e as LocalStoreState, M as MirrorState, h as Mode, i as PasswordCheck, b as PasswordPolicy, j as PasswordRequirement, P as PeerSource, k as PeerState, R as RESERVED_COLLECTION_PREFIX, l as RESERVED_STORE_MODES, m as RecipientStanza, n as ReplicaState, o as SelfstoreError, p as SelfstoreErrorCode, q as Severity, a as Snapshot, c as SnapshotFile, r as StatusAction, d as StatusDescriptor, s as StatusInput, t as StorageState, S as StoreError, u as StoreGroupConfig, v as SyncJournalEntry, w as SyncSource, T as TargetKind, x as checkPasswordPolicy, y as createLocalStore, z as deriveStatus, C as errorLabelKey, D as isAuthExpired, F as isReservedStoreMode, G as isSelfstoreError } from './store-bp1VRW7R.js';
|
|
3
|
+
import { K as KV } from './cache-DBIn_Uo8.js';
|
|
4
|
+
export { C as CachedFile, L as LocalCache, e as LockableCache, i as indexedDbCache, f as isLockableCache, m as memoryCache } from './cache-DBIn_Uo8.js';
|
|
5
|
+
export { G as GisDriveAuthOptions, a as GisTokenPersistence, d as datedName, e as exportSnapshot, g as gisDriveAuth, i as importSnapshot, b as inspect, c as isEncrypted, p as pickFromDisk, f as preloadGoogleIdentity, s as saveToDisk } from './local-CpoBeJWk.js';
|
|
6
|
+
import { c as connect$2, f as fromSession$2, a as connect$3, b as fromSession$3, p as peer } from './s3-kikEHI-g.js';
|
|
7
|
+
export { S as S3Config, d as S3ConnectOptions, W as WebdavConfig, e as WebdavConnectOptions, g as WebdavPeerOptions } from './s3-kikEHI-g.js';
|
|
8
|
+
import { c as connect$1, f as fromSession$1, p as preview, b as adopt, d as findOrCreateOwnFile, l as listBackups, e as createBackup, g as deleteBackup, r as renameBackup, h as account, i as createCompanion, s as share, u as unshare, o as owner, j as secondary } from './drive-BCRil91Q.js';
|
|
9
|
+
export { k as DriveAccount, a as DriveAuth, m as DriveBackupInfo, D as DriveOptions } from './drive-BCRil91Q.js';
|
|
10
10
|
import './sync.js';
|
|
11
11
|
|
|
12
12
|
/**
|
package/dist/advanced.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { peer, fromSession as fromSession$3, connect as connect$3 } from './chunk-GHSHIDF3.js';
|
|
2
2
|
import { fromSession as fromSession$2, connect as connect$2 } from './chunk-IDW3WQKF.js';
|
|
3
|
-
export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache, preloadGoogleIdentity } from './chunk-
|
|
4
|
-
import './chunk-
|
|
3
|
+
export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache, preloadGoogleIdentity } from './chunk-JE3BLKXO.js';
|
|
4
|
+
import './chunk-3P2W3XQD.js';
|
|
5
5
|
export { datedName, pickFromDisk, saveToDisk } from './chunk-XAONTUEC.js';
|
|
6
6
|
import './chunk-FJGWTBAL.js';
|
|
7
7
|
import { openExisting, isOpenSupported, isSupported, fromSession as fromSession$1, connect as connect$1 } from './chunk-HVHQLPP6.js';
|
package/dist/backups.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { B as BackupsHost } from './manager-
|
|
2
|
-
export { a as BackupFileInfo, b as BackupOwner, c as BackupRow, d as BackupsErrorCode, e as BackupsKeys, f as BackupsManager, g as BackupsNaming, h as BackupsSnapshot, K as KnownBackups, i as createBackupsManager } from './manager-
|
|
1
|
+
import { B as BackupsHost } from './manager-0puQEpZc.js';
|
|
2
|
+
export { a as BackupFileInfo, b as BackupOwner, c as BackupRow, d as BackupsErrorCode, e as BackupsKeys, f as BackupsManager, g as BackupsNaming, h as BackupsSnapshot, K as KnownBackups, i as createBackupsManager } from './manager-0puQEpZc.js';
|
|
3
3
|
import { DriveOptions } from './advanced.js';
|
|
4
|
-
import './cache-
|
|
5
|
-
import './store-
|
|
4
|
+
import './cache-DBIn_Uo8.js';
|
|
5
|
+
import './store-bp1VRW7R.js';
|
|
6
6
|
import './sync.js';
|
|
7
7
|
import './machine-Dhbx3Eut.js';
|
|
8
8
|
|
|
@@ -81,6 +81,12 @@ interface KV {
|
|
|
81
81
|
}
|
|
82
82
|
interface LocalCache {
|
|
83
83
|
readonly kv: KV;
|
|
84
|
+
/** A key/value space whose VALUES are sealed under the same key as the
|
|
85
|
+
* payload (the device key, or the session secret of a locked cache): for
|
|
86
|
+
* the bookkeeping that is itself a secret - a share key, a mailbox id, an
|
|
87
|
+
* invitation capability. A locked cache rejects it with PASSWORD_REQUIRED
|
|
88
|
+
* until unlock(). Optional: a cache with nothing at rest has no need of it. */
|
|
89
|
+
readonly sealedKv?: KV;
|
|
84
90
|
/** Load the persisted collections + files, or null if nothing was ever saved. */
|
|
85
91
|
load(): Promise<{
|
|
86
92
|
collections: Record<string, unknown[]>;
|
|
@@ -88,7 +94,9 @@ interface LocalCache {
|
|
|
88
94
|
} | null>;
|
|
89
95
|
/** Overwrite the collections snapshot (small; written on every change). */
|
|
90
96
|
saveCollections(collections: Record<string, unknown[]>): Promise<void>;
|
|
91
|
-
/** Reconcile the file store: add new blobs, drop removed ones
|
|
97
|
+
/** Reconcile the file store: add new blobs, drop removed ones. A known id
|
|
98
|
+
* is rewritten when its Blob is not the one this cache last loaded or
|
|
99
|
+
* wrote for it, which is how a replaced body reaches the disk. */
|
|
92
100
|
saveFiles(files: CachedFile[]): Promise<void>;
|
|
93
101
|
/** Wipe every locally-stored byte (the "forget me on this device" action). */
|
|
94
102
|
clear(): Promise<void>;
|
|
@@ -64,22 +64,30 @@ function hash(v) {
|
|
|
64
64
|
}
|
|
65
65
|
return h >>> 0;
|
|
66
66
|
}
|
|
67
|
+
function dict(from) {
|
|
68
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
69
|
+
if (from) for (const [k, v] of Object.entries(from)) out[k] = v;
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
67
72
|
function emptyCol() {
|
|
68
|
-
return { clocks:
|
|
73
|
+
return { clocks: dict(), deleted: dict(), hashes: dict() };
|
|
69
74
|
}
|
|
70
75
|
function cloneCol(c) {
|
|
71
76
|
if (!c) return emptyCol();
|
|
72
77
|
const out = {
|
|
73
|
-
clocks:
|
|
74
|
-
deleted:
|
|
75
|
-
hashes:
|
|
78
|
+
clocks: dict(c.clocks),
|
|
79
|
+
deleted: dict(c.deleted),
|
|
80
|
+
hashes: dict(c.hashes)
|
|
76
81
|
};
|
|
77
82
|
if (c.fields) {
|
|
78
|
-
out.fields =
|
|
79
|
-
for (const [id, f] of Object.entries(c.fields)) out.fields[id] =
|
|
83
|
+
out.fields = dict();
|
|
84
|
+
for (const [id, f] of Object.entries(c.fields)) out.fields[id] = dict(f);
|
|
80
85
|
}
|
|
81
86
|
return out;
|
|
82
87
|
}
|
|
88
|
+
function baseColOf(base, name) {
|
|
89
|
+
return base ? cloneCol(base.cols[name]) : void 0;
|
|
90
|
+
}
|
|
83
91
|
function strategyOf(config, collection) {
|
|
84
92
|
return config.strategies?.[collection] ?? config.fallback ?? "lww-set";
|
|
85
93
|
}
|
|
@@ -136,12 +144,12 @@ function stampPresent(col, present, config, name, tick) {
|
|
|
136
144
|
return seen;
|
|
137
145
|
}
|
|
138
146
|
function stampFields(col, id, doc, tick) {
|
|
139
|
-
const rec = (col.fields ??=
|
|
147
|
+
const rec = (col.fields ??= dict())[id] ??= dict();
|
|
140
148
|
for (const key of Object.keys(doc)) {
|
|
141
149
|
const h = hash(doc[key]);
|
|
142
150
|
if (rec[key]?.[1] !== h) rec[key] = [tick(), h];
|
|
143
151
|
}
|
|
144
|
-
for (const key of Object.keys(rec)) if (!(key
|
|
152
|
+
for (const key of Object.keys(rec)) if (!Object.hasOwn(doc, key)) delete rec[key];
|
|
145
153
|
}
|
|
146
154
|
function tombstoneMissing(col, seen, tick) {
|
|
147
155
|
for (const id of Object.keys(col.clocks)) {
|
|
@@ -218,9 +226,9 @@ function detectConflicts(a, b, config, base) {
|
|
|
218
226
|
const strategy = strategyOf(config, name);
|
|
219
227
|
const aDocs = byId(config, name, a.collections[name]);
|
|
220
228
|
const bDocs = byId(config, name, b.collections[name]);
|
|
221
|
-
const aMeta = a.meta.cols[name]
|
|
222
|
-
const bMeta = b.meta.cols[name]
|
|
223
|
-
const baseMeta = base
|
|
229
|
+
const aMeta = cloneCol(a.meta.cols[name]);
|
|
230
|
+
const bMeta = cloneCol(b.meta.cols[name]);
|
|
231
|
+
const baseMeta = baseColOf(base, name);
|
|
224
232
|
for (const id of /* @__PURE__ */ new Set([...aDocs.keys(), ...bDocs.keys()])) {
|
|
225
233
|
const aClk = aMeta.clocks[id] ?? null;
|
|
226
234
|
const bClk = bMeta.clocks[id] ?? null;
|
|
@@ -272,9 +280,9 @@ function mergeCollection(name, a, b, config, base, conflicts) {
|
|
|
272
280
|
const strategy = strategyOf(config, name);
|
|
273
281
|
const aDocs = byId(config, name, a.collections[name]);
|
|
274
282
|
const bDocs = byId(config, name, b.collections[name]);
|
|
275
|
-
const aMeta = a.meta.cols[name]
|
|
276
|
-
const bMeta = b.meta.cols[name]
|
|
277
|
-
const baseMeta = base
|
|
283
|
+
const aMeta = cloneCol(a.meta.cols[name]);
|
|
284
|
+
const bMeta = cloneCol(b.meta.cols[name]);
|
|
285
|
+
const baseMeta = baseColOf(base, name);
|
|
278
286
|
const col = emptyCol();
|
|
279
287
|
const docs = [];
|
|
280
288
|
const ids = /* @__PURE__ */ new Set([
|
|
@@ -310,10 +318,10 @@ function mergeCollection(name, a, b, config, base, conflicts) {
|
|
|
310
318
|
const merged = mergeFields(id, aDocs.get(id), bDocs.get(id), aMeta, bMeta, winnerSide);
|
|
311
319
|
if (merged) {
|
|
312
320
|
doc = merged.doc;
|
|
313
|
-
(col.fields ??=
|
|
321
|
+
(col.fields ??= dict())[id] = merged.fields;
|
|
314
322
|
} else {
|
|
315
323
|
const f = (winnerSide === "local" ? aMeta : bMeta).fields?.[id];
|
|
316
|
-
if (f) (col.fields ??=
|
|
324
|
+
if (f) (col.fields ??= dict())[id] = dict(f);
|
|
317
325
|
}
|
|
318
326
|
}
|
|
319
327
|
if (doc !== void 0) {
|
|
@@ -326,17 +334,22 @@ function mergeCollection(name, a, b, config, base, conflicts) {
|
|
|
326
334
|
}
|
|
327
335
|
function mergeFields(id, aDoc, bDoc, aMeta, bMeta, winnerSide) {
|
|
328
336
|
if (!isRecord(aDoc) || !isRecord(bDoc)) return null;
|
|
329
|
-
const aF = aMeta.fields?.[id] ??
|
|
330
|
-
const bF = bMeta.fields?.[id] ??
|
|
337
|
+
const aF = aMeta.fields?.[id] ?? dict();
|
|
338
|
+
const bF = bMeta.fields?.[id] ?? dict();
|
|
331
339
|
const doc = {};
|
|
332
|
-
const fields =
|
|
340
|
+
const fields = dict();
|
|
333
341
|
for (const key of /* @__PURE__ */ new Set([...Object.keys(aDoc), ...Object.keys(bDoc)])) {
|
|
334
342
|
const a = aF[key] ?? null;
|
|
335
343
|
const b = bF[key] ?? null;
|
|
336
344
|
const side = pickFieldSide(a, b, winnerSide);
|
|
337
345
|
const src = side === "local" ? aDoc : bDoc;
|
|
338
|
-
if (!(key
|
|
339
|
-
doc
|
|
346
|
+
if (!Object.hasOwn(src, key)) continue;
|
|
347
|
+
Object.defineProperty(doc, key, {
|
|
348
|
+
value: src[key],
|
|
349
|
+
enumerable: true,
|
|
350
|
+
writable: true,
|
|
351
|
+
configurable: true
|
|
352
|
+
});
|
|
340
353
|
const stamped = side === "local" ? a : b;
|
|
341
354
|
if (stamped) fields[key] = stamped;
|
|
342
355
|
}
|
|
@@ -382,5 +395,5 @@ function changes(before, after, config) {
|
|
|
382
395
|
}
|
|
383
396
|
|
|
384
397
|
export { changes, createMeta, createNode, detectConflicts, gcTombstones, idOf, idPathFor, merge, stamp, unsyncableCounts };
|
|
385
|
-
//# sourceMappingURL=chunk-
|
|
386
|
-
//# sourceMappingURL=chunk-
|
|
398
|
+
//# sourceMappingURL=chunk-3P2W3XQD.js.map
|
|
399
|
+
//# sourceMappingURL=chunk-3P2W3XQD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sync/hlc.ts","../src/sync/merge.ts"],"names":[],"mappings":"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","file":"chunk-3P2W3XQD.js","sourcesContent":["/**\n * Hybrid Logical Clock: wall time + a counter + a node id, giving a\n * deterministic total order that tolerates clock skew. Encoded as a fixed-width\n * string so a plain string comparison equals causal order.\n */\n\nexport type Hlc = string;\n\ninterface Parts {\n\twall: number;\n\tcounter: number;\n\tnode: string;\n}\n\nconst WALL_W = 15;\nconst CTR_W = 6;\n\nfunction encode(p: Parts): Hlc {\n\treturn `${String(p.wall).padStart(WALL_W, '0')}|${String(p.counter).padStart(CTR_W, '0')}|${p.node}`;\n}\n\nfunction decode(h: Hlc): Parts {\n\tconst i = h.indexOf('|');\n\tconst j = h.indexOf('|', i + 1);\n\treturn { wall: Number(h.slice(0, i)), counter: Number(h.slice(i + 1, j)), node: h.slice(j + 1) };\n}\n\n/**\n * A fresh random replica id (one per device). Not a secret - but two replicas\n * sharing an id lose the tiebreak that makes the order total, so it comes from\n * the same source as everything else here. `randomUUID` is exposed only in a\n * secure context; `getRandomValues` is not, which is why the fallback is that\n * one and never `Math.random`.\n */\nexport function createNode(): string {\n\tif (typeof crypto === 'undefined') {\n\t\tthrow new TypeError(\n\t\t\t'createNode(): this platform provides no WebCrypto, so no replica id can be minted ' +\n\t\t\t\t'(evergreen browsers, Node 20+).'\n\t\t);\n\t}\n\tif (crypto.randomUUID) return crypto.randomUUID();\n\tconst bytes = new Uint8Array(16);\n\tcrypto.getRandomValues(bytes);\n\treturn [...bytes].map((b) => b.toString(16).padStart(2, '0')).join('');\n}\n\n/**\n * How far ahead of local wall time a clock minted elsewhere may sit before this\n * replica refuses it.\n *\n * A hybrid logical clock absorbs the highest value it sees, so ONE impossible\n * wall time does three things at once: it wins every last-writer-wins\n * comparison from then on, it raises the clock of every replica that merges it\n * (and of every replica those merge with, in turn), and it keeps doing so after\n * whoever sent it has been removed from the group. Five minutes leaves room for\n * a device whose clock was never synchronised; nothing legitimate needs more.\n */\nexport const MAX_DRIFT_MS = 5 * 60_000;\n\n/** Whether `h` claims a wall time further ahead than the drift bound allows. */\nexport function driftedAhead(\n\th: Hlc,\n\twallNow: number = Date.now(),\n\tmaxDriftMs: number = MAX_DRIFT_MS\n): boolean {\n\treturn hlcWall(h) > wallNow + maxDriftMs;\n}\n\n/** Issue a clock for a local event. Monotonic per node even if the wall clock moves back. */\nexport function issue(prev: Hlc | null, node: string, wallNow: number = Date.now()): Hlc {\n\tconst p = prev ? decode(prev) : null;\n\tconst prevWall = p ? p.wall : 0;\n\tconst wall = Math.max(wallNow, prevWall);\n\tconst counter = wall === prevWall && p ? p.counter + 1 : 0;\n\treturn encode({ wall, counter, node });\n}\n\n/** Fold a clock observed from another replica into the local one. */\nexport function receive(\n\tlocal: Hlc | null,\n\tremote: Hlc,\n\tnode: string,\n\twallNow: number = Date.now()\n): Hlc {\n\tconst l = local ? decode(local) : { wall: 0, counter: 0, node };\n\tconst r = decode(remote);\n\tconst wall = Math.max(wallNow, l.wall, r.wall);\n\tlet counter: number;\n\tif (wall === l.wall && wall === r.wall) counter = Math.max(l.counter, r.counter) + 1;\n\telse if (wall === l.wall) counter = l.counter + 1;\n\telse if (wall === r.wall) counter = r.counter + 1;\n\telse counter = 0;\n\treturn encode({ wall, counter, node });\n}\n\n/** The wall-time component (ms since epoch) encoded in a clock, for age-based\n * pruning (tombstone GC). Not a precise event time - HLC wall can run ahead of\n * real time under skew - but a safe lower bound on \"how old is this at least\". */\nexport function hlcWall(h: Hlc): number {\n\treturn Number(h.slice(0, h.indexOf('|')));\n}\n\n/** Total order: <0 if a precedes b, >0 if a follows b, 0 only if identical. */\nexport function compare(a: Hlc, b: Hlc): number {\n\tif (a < b) return -1;\n\tif (a > b) return 1;\n\treturn 0;\n}\n\n/** The later of two clocks. */\nexport function max(a: Hlc | null, b: Hlc | null): Hlc {\n\tif (!a) return b as Hlc;\n\tif (!b) return a;\n\treturn compare(a, b) >= 0 ? a : b;\n}\n","/**\n * Schema-agnostic convergent merge for plain JSON. It keeps a small sidecar of\n * metadata (one logical clock per record) and reconciles two replica states with\n * well-known set semantics. Documents stay plain objects: identity is a\n * configured id field, change detection is by content hash, nothing is injected.\n *\n * Per-collection strategy:\n * 'lww-set' records keyed by id; later write per id wins; deletes tombstoned.\n * 'lww-map' records keyed by id, merged field BY field: concurrent edits\n * to different fields of one record both survive; the same field\n * goes to the later clock. Deletes stay record-level.\n * 'grow-set' append-only; entries immutable; union, never a conflict.\n * 'lww-register' a single value; later write wins.\n * 'manual' surface concurrent edits to the same id instead of resolving.\n *\n * For collaborative rich text, store an Automerge/Yjs document as a binary file\n * in the snapshot instead; this engine stops short of sequence CRDTs.\n */\n\nimport { type Hlc, issue, max, compare, createNode, hlcWall, driftedAhead } from './hlc';\nimport { SelfstoreError } from '../selfstore/errors';\nimport { byCodePoint } from '../ordering';\n\nexport type { Hlc } from './hlc';\nexport type Id = string;\nexport type MergeStrategy = 'lww-set' | 'lww-map' | 'grow-set' | 'lww-register' | 'manual';\n\nexport interface SyncConfig {\n\t/** Field holding each record's stable id (default 'id'). Dot-notation allowed. */\n\tidField?: string;\n\t/** Per-collection id path override for records whose id is not at the top level. */\n\tids?: Record<string, string>;\n\t/** Strategy per collection name; anything unlisted uses `fallback`. */\n\tstrategies?: Record<string, MergeStrategy>;\n\t/** Strategy for unlisted collections (default 'lww-set'). */\n\tfallback?: MergeStrategy;\n}\n\n/** Sidecar metadata for one collection, keyed by record id. */\nexport interface ColMeta {\n\tclocks: Record<Id, Hlc>;\n\tdeleted: Record<Id, Hlc>;\n\thashes: Record<Id, number>;\n\t/** 'lww-map' only: per-field [clock, contentHash], so concurrent edits to\n\t * different fields of one record merge instead of one side losing. */\n\tfields?: Record<Id, Record<string, [Hlc, number]>>;\n}\n\n/** Everything one replica tracks. Travels with the data so the other side can merge. */\nexport interface SyncMeta {\n\tnode: string;\n\tclock: Hlc | null;\n\tcols: Record<string, ColMeta>;\n}\n\n/** A same-id concurrent edit the 'manual' strategy refused to auto-resolve.\n * `local`/`remote` carry the two full versions during the session; consumers\n * persisting conflicts (e.g. the store's journal) redact them, so they are\n * absent after a reload - never write user records to unencrypted storage. */\nexport interface Conflict {\n\tcollection: string;\n\tid: Id;\n\tlocal?: unknown;\n\tremote?: unknown;\n\t/** The side kept as the provisional value (so it still converges). */\n\tkept: 'local' | 'remote';\n}\n\nexport interface MergeResult {\n\tcollections: Record<string, unknown[]>;\n\tmeta: SyncMeta;\n\tconflicts: Conflict[];\n}\n\nfunction stableStringify(v: unknown): string {\n\tif (v === null || typeof v !== 'object') return JSON.stringify(v) ?? 'null';\n\tif (Array.isArray(v)) return '[' + v.map(stableStringify).join(',') + ']';\n\tconst o = v as Record<string, unknown>;\n\treturn (\n\t\t'{' +\n\t\tObject.keys(o)\n\t\t\t.sort(byCodePoint)\n\t\t\t.map((k) => JSON.stringify(k) + ':' + stableStringify(o[k]))\n\t\t\t.join(',') +\n\t\t'}'\n\t);\n}\n\n/** FNV-1a 32-bit hash, enough to detect that a record changed. */\nfunction hash(v: unknown): number {\n\tconst s = stableStringify(v);\n\tlet h = 0x811c9dc5;\n\tfor (let i = 0; i < s.length; i++) {\n\t\th ^= s.charCodeAt(i);\n\t\th = Math.imul(h, 0x01000193);\n\t}\n\treturn h >>> 0;\n}\n\n/**\n * A table keyed by a record id or a field name. Ids and field names are free\n * text: on a plain object `__proto__` reads `Object.prototype`, and writing\n * through it pollutes every object of the host. No prototype, no such key.\n */\nfunction dict<T>(from?: Record<string, T>): Record<string, T> {\n\tconst out = Object.create(null) as Record<string, T>;\n\tif (from) for (const [k, v] of Object.entries(from)) out[k] = v;\n\treturn out;\n}\n\nfunction emptyCol(): ColMeta {\n\treturn { clocks: dict(), deleted: dict(), hashes: dict() };\n}\n\n/** A copy of one collection's metadata in prototype-free tables, whatever it\n * was read from (a deserialized replica holds plain objects). */\nfunction cloneCol(c: ColMeta | undefined): ColMeta {\n\tif (!c) return emptyCol();\n\tconst out: ColMeta = {\n\t\tclocks: dict(c.clocks),\n\t\tdeleted: dict(c.deleted),\n\t\thashes: dict(c.hashes)\n\t};\n\tif (c.fields) {\n\t\tout.fields = dict();\n\t\tfor (const [id, f] of Object.entries(c.fields)) out.fields[id] = dict(f);\n\t}\n\treturn out;\n}\n\n/** The base's metadata for one collection, copied like the two sides'. */\nfunction baseColOf(base: SyncMeta | undefined, name: string): ColMeta | undefined {\n\treturn base ? cloneCol(base.cols[name]) : undefined;\n}\n\nfunction strategyOf(config: SyncConfig, collection: string): MergeStrategy {\n\treturn config.strategies?.[collection] ?? config.fallback ?? 'lww-set';\n}\n\n/** The id of a record at the path the config gives its collection, dotted\n * paths included; null when there is no string there. */\nexport function idOf(config: SyncConfig, collection: string, doc: unknown): Id | null {\n\tconst path = config.ids?.[collection] ?? config.idField ?? 'id';\n\tlet v: unknown = doc;\n\tfor (const key of path.split('.')) {\n\t\tif (v === null || typeof v !== 'object') return null;\n\t\tv = (v as Record<string, unknown>)[key];\n\t}\n\treturn typeof v === 'string' ? v : null;\n}\n\n/** The id path this config resolves for a collection (for diagnostics). */\nexport function idPathFor(config: SyncConfig, collection: string): string {\n\treturn config.ids?.[collection] ?? config.idField ?? 'id';\n}\n\n/** Records the engine cannot track because they have no string id at the\n * configured path. Identity is a string id field, so such a record is invisible\n * to stamp/merge - it would silently never replicate. Returns the count per\n * collection (nonzero entries only), for a dev-time warning. A number or missing\n * id is the classic footgun: give each record a string id, or map the field via\n * `sync.ids`. */\nexport function unsyncableCounts(\n\tcollections: Record<string, unknown[]>,\n\tconfig: SyncConfig = {}\n): Record<string, number> {\n\tconst out: Record<string, number> = {};\n\tfor (const [name, docs] of Object.entries(collections)) {\n\t\tlet n = 0;\n\t\tfor (const doc of docs ?? []) if (idOf(config, name, doc) === null) n++;\n\t\tif (n > 0) out[name] = n;\n\t}\n\treturn out;\n}\n\nexport function createMeta(node: string = createNode()): SyncMeta {\n\treturn { node, clock: null, cols: {} };\n}\n\n/**\n * Prune tombstones (delete markers) whose clock is older than `beforeWall` (a\n * wall-time ms cutoff, e.g. `Date.now() - horizon`). Tombstones otherwise\n * accumulate forever, since a delete must be remembered to out-vote a stale\n * replica still holding the record.\n *\n * Safe only when the cutoff is older than the longest a replica can stay offline\n * before it next syncs: a replica that never saw a delete, and whose tombstone\n * has been GC'd everywhere, will resurrect that record on its next merge. Pick a\n * horizon comfortably beyond your worst-case offline window. Returns new meta\n * (input untouched); present-record clocks are untouched.\n */\nexport function gcTombstones(meta: SyncMeta, beforeWall: number): SyncMeta {\n\tconst cols: Record<string, ColMeta> = {};\n\tfor (const [name, col] of Object.entries(meta.cols)) {\n\t\tconst next = cloneCol(col);\n\t\tfor (const [id, clk] of Object.entries(next.deleted)) {\n\t\t\tif (hlcWall(clk) < beforeWall) delete next.deleted[id];\n\t\t}\n\t\tcols[name] = next;\n\t}\n\treturn { node: meta.node, clock: meta.clock, cols };\n}\n\n/**\n * Diff `collections` against the metadata's content hashes, assign a fresh clock\n * to every added or changed record, and tombstone records that disappeared.\n * Returns new metadata (input untouched). Call on every local save.\n */\ntype Tick = () => Hlc;\n\n/** Stamp each present record: a fresh clock for anything added or changed (by\n * content hash). Returns the set of ids seen, for the tombstone pass. */\nfunction stampPresent(\n\tcol: ColMeta,\n\tpresent: unknown[],\n\tconfig: SyncConfig,\n\tname: string,\n\ttick: Tick\n): Set<Id> {\n\tconst fieldLevel = strategyOf(config, name) === 'lww-map';\n\tconst seen = new Set<Id>();\n\tfor (const doc of present) {\n\t\tconst id = idOf(config, name, doc);\n\t\tif (id === null) continue;\n\t\tseen.add(id);\n\t\tconst h = hash(doc);\n\t\tif (col.hashes[id] !== h) {\n\t\t\tcol.clocks[id] = tick();\n\t\t\tcol.hashes[id] = h;\n\t\t\tdelete col.deleted[id];\n\t\t\tif (fieldLevel && isRecord(doc)) stampFields(col, id, doc, tick);\n\t\t}\n\t}\n\treturn seen;\n}\n\n/** Stamp each added/changed field of one record ('lww-map'): a field keeps its\n * clock until its own content changes, so field clocks stay independent and a\n * merge can tell which side last touched which field. */\nfunction stampFields(col: ColMeta, id: Id, doc: Record<string, unknown>, tick: Tick): void {\n\tconst rec = ((col.fields ??= dict())[id] ??= dict());\n\tfor (const key of Object.keys(doc)) {\n\t\tconst h = hash(doc[key]);\n\t\tif (rec[key]?.[1] !== h) rec[key] = [tick(), h];\n\t}\n\tfor (const key of Object.keys(rec)) if (!Object.hasOwn(doc, key)) delete rec[key];\n}\n\n/** Tombstone every previously-tracked id that is no longer present. */\nfunction tombstoneMissing(col: ColMeta, seen: Set<Id>, tick: Tick): void {\n\tfor (const id of Object.keys(col.clocks)) {\n\t\tif (!seen.has(id)) {\n\t\t\tcol.deleted[id] = tick();\n\t\t\tdelete col.clocks[id];\n\t\t\tdelete col.hashes[id];\n\t\t\tdelete col.fields?.[id];\n\t\t}\n\t}\n}\n\nexport function stamp(\n\tmeta: SyncMeta,\n\tcollections: Record<string, unknown[]>,\n\tconfig: SyncConfig,\n\twallNow: number = Date.now()\n): SyncMeta {\n\tlet clock = meta.clock;\n\tconst tick: Tick = () => (clock = issue(clock, meta.node, wallNow));\n\tconst cols: Record<string, ColMeta> = {};\n\tconst names = new Set([...Object.keys(meta.cols), ...Object.keys(collections)]);\n\n\tfor (const name of names) {\n\t\tconst strategy = strategyOf(config, name);\n\t\tconst col = cloneCol(meta.cols[name]);\n\t\tconst present = collections[name] ?? [];\n\n\t\tconst seen = stampPresent(col, present, config, name, tick);\n\t\tif (strategy !== 'grow-set') tombstoneMissing(col, seen, tick);\n\t\tcols[name] = col;\n\t}\n\treturn { node: meta.node, clock, cols };\n}\n\n/** One replica's full state: the collections plus their sync metadata. What\n * `merge` and `detectConflicts` take on each side. */\nexport interface ReplicaState {\n\tcollections: Record<string, unknown[]>;\n\tmeta: SyncMeta;\n}\ntype State = ReplicaState;\n\n/**\n * Merge replica `b` into `a` (commutative for the convergent strategies). The\n * optional `base` lets 'manual' tell a true concurrent conflict from a one-sided\n * change; without it, 'manual' errs toward surfacing.\n */\n/** Every clock a replica carries, flattened: meta, records, tombstones, fields. */\nfunction* allClocks(meta: SyncMeta): Generator<Hlc> {\n\tif (meta.clock) yield meta.clock;\n\tfor (const col of Object.values(meta.cols)) {\n\t\tyield* Object.values(col.clocks);\n\t\tyield* Object.values(col.deleted);\n\t\tfor (const fields of Object.values(col.fields ?? {})) {\n\t\t\tfor (const [clock] of Object.values(fields)) yield clock;\n\t\t}\n\t}\n}\n\n/**\n * Refuse a replica whose clocks cannot be real. `b` is the incoming side by\n * signature, and only it is checked: a local replica that already absorbed a\n * bad clock must still be able to merge, or the fix would strand exactly the\n * stores it exists to protect. Refusing rather than clamping is deliberate -\n * a silently lowered clock would leave two replicas each convinced they hold\n * the later write.\n */\nfunction assertNoDrift(meta: SyncMeta, wallNow: number): void {\n\tfor (const clock of allClocks(meta)) {\n\t\tif (driftedAhead(clock, wallNow)) {\n\t\t\tthrow new SelfstoreError(\n\t\t\t\t'CLOCK_DRIFT',\n\t\t\t\t`This copy carries a clock ${Math.round((hlcWall(clock) - wallNow) / 60_000)} minutes ` +\n\t\t\t\t\t'ahead of this device. Check the clock of the device that wrote it: merging would ' +\n\t\t\t\t\t'let those writes win every future conflict, on every device.'\n\t\t\t);\n\t\t}\n\t}\n}\n\nexport function merge(a: State, b: State, config: SyncConfig, base?: SyncMeta): MergeResult {\n\tassertNoDrift(b.meta, Date.now());\n\tconst conflicts: Conflict[] = [];\n\tconst outCols: Record<string, unknown[]> = {};\n\tconst outMeta: SyncMeta = {\n\t\tnode: a.meta.node,\n\t\tclock: max(a.meta.clock, b.meta.clock),\n\t\tcols: {}\n\t};\n\n\tconst names = new Set([\n\t\t...Object.keys(a.collections),\n\t\t...Object.keys(b.collections),\n\t\t...Object.keys(a.meta.cols),\n\t\t...Object.keys(b.meta.cols)\n\t]);\n\n\tfor (const name of names) {\n\t\tconst merged = mergeCollection(name, a, b, config, base, conflicts);\n\t\toutCols[name] = merged.docs;\n\t\toutMeta.cols[name] = merged.col;\n\t}\n\n\treturn { collections: outCols, meta: outMeta, conflicts };\n}\n\n/**\n * Concurrent same-id edits that a convergent strategy resolves silently by keeping\n * the later clock. Informational only: it never changes what `merge` keeps, it\n * reports which records both replicas changed since `base` (the loser's version is\n * the one merge dropped). Empty without a base, since two-way state alone cannot\n * tell a concurrent edit from a one-sided change.\n */\nexport function detectConflicts(\n\ta: State,\n\tb: State,\n\tconfig: SyncConfig,\n\tbase?: SyncMeta\n): Conflict[] {\n\tif (!base) return [];\n\tconst out: Conflict[] = [];\n\tconst names = new Set([...Object.keys(a.collections), ...Object.keys(b.collections)]);\n\tfor (const name of names) {\n\t\tconst strategy = strategyOf(config, name);\n\t\tconst aDocs = byId(config, name, a.collections[name]);\n\t\tconst bDocs = byId(config, name, b.collections[name]);\n\t\tconst aMeta = cloneCol(a.meta.cols[name]);\n\t\tconst bMeta = cloneCol(b.meta.cols[name]);\n\t\tconst baseMeta = baseColOf(base, name);\n\t\tfor (const id of new Set<Id>([...aDocs.keys(), ...bDocs.keys()])) {\n\t\t\tconst aClk = aMeta.clocks[id] ?? null;\n\t\t\tconst bClk = bMeta.clocks[id] ?? null;\n\t\t\tconst winnerSide = pickWinner(aClk, bClk, aDocs.has(id));\n\t\t\t// lww-map merges different-field edits cleanly: only the same field\n\t\t\t// changed on both sides is a real (auto-resolved) conflict.\n\t\t\tconst conflict =\n\t\t\t\tstrategy === 'lww-map'\n\t\t\t\t\t? fieldConflict(\n\t\t\t\t\t\t\tname,\n\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\taDocs,\n\t\t\t\t\t\t\tbDocs,\n\t\t\t\t\t\t\taMeta.fields?.[id],\n\t\t\t\t\t\t\tbMeta.fields?.[id],\n\t\t\t\t\t\t\tbaseMeta?.fields?.[id],\n\t\t\t\t\t\t\twinnerSide\n\t\t\t\t\t\t)\n\t\t\t\t\t: manualConflict(name, id, aDocs, bDocs, aClk, bClk, baseMeta, winnerSide);\n\t\t\tif (conflict) out.push(conflict);\n\t\t}\n\t}\n\treturn out;\n}\n\n/** The 'lww-map' conflict check: a Conflict only when the same field changed on\n * both sides since base (different-field edits merge without loss). */\nfunction fieldConflict(\n\tname: string,\n\tid: Id,\n\taDocs: Map<Id, unknown>,\n\tbDocs: Map<Id, unknown>,\n\taF: Record<string, [Hlc, number]> | undefined,\n\tbF: Record<string, [Hlc, number]> | undefined,\n\tbaseF: Record<string, [Hlc, number]> | undefined,\n\twinnerSide: 'local' | 'remote'\n): Conflict | null {\n\tif (!(aDocs.has(id) && bDocs.has(id) && aF && bF)) return null;\n\tfor (const key of Object.keys(aF)) {\n\t\tconst a = aF[key];\n\t\tconst b = bF[key];\n\t\tif (!b || a[0] === b[0]) continue;\n\t\tconst baseClk = baseF?.[key]?.[0] ?? null;\n\t\tconst aChanged = !baseClk || compare(a[0], baseClk) > 0;\n\t\tconst bChanged = !baseClk || compare(b[0], baseClk) > 0;\n\t\tif (aChanged && bChanged)\n\t\t\treturn {\n\t\t\t\tcollection: name,\n\t\t\t\tid,\n\t\t\t\tlocal: aDocs.get(id),\n\t\t\t\tremote: bDocs.get(id),\n\t\t\t\tkept: winnerSide\n\t\t\t};\n\t}\n\treturn null;\n}\n\n/** The 'manual' concurrent-edit check for one record: a Conflict when both sides\n * changed the same id since `base` (or, without a base, both simply hold it and\n * their clocks differ). Pure; null when there is nothing to surface. */\nfunction manualConflict(\n\tname: string,\n\tid: Id,\n\taDocs: Map<Id, unknown>,\n\tbDocs: Map<Id, unknown>,\n\taClk: Hlc | null,\n\tbClk: Hlc | null,\n\tbaseMeta: ColMeta | undefined,\n\twinnerSide: 'local' | 'remote'\n): Conflict | null {\n\tif (!(aDocs.has(id) && bDocs.has(id) && aClk && bClk && aClk !== bClk)) return null;\n\tconst baseClk = baseMeta?.clocks[id] ?? null;\n\tconst aChanged = !baseClk || compare(aClk, baseClk) > 0;\n\tconst bChanged = !baseClk || compare(bClk, baseClk) > 0;\n\tif (!(aChanged && bChanged)) return null;\n\treturn { collection: name, id, local: aDocs.get(id), remote: bDocs.get(id), kept: winnerSide };\n}\n\n/** Reconcile every id of one collection across the two replicas. */\nfunction mergeCollection(\n\tname: string,\n\ta: State,\n\tb: State,\n\tconfig: SyncConfig,\n\tbase: SyncMeta | undefined,\n\tconflicts: Conflict[]\n): { docs: unknown[]; col: ColMeta } {\n\tconst strategy = strategyOf(config, name);\n\tconst aDocs = byId(config, name, a.collections[name]);\n\tconst bDocs = byId(config, name, b.collections[name]);\n\tconst aMeta = cloneCol(a.meta.cols[name]);\n\tconst bMeta = cloneCol(b.meta.cols[name]);\n\tconst baseMeta = baseColOf(base, name);\n\tconst col = emptyCol();\n\tconst docs: unknown[] = [];\n\n\tconst ids = new Set<Id>([\n\t\t...Object.keys(aMeta.clocks),\n\t\t...Object.keys(bMeta.clocks),\n\t\t...Object.keys(aMeta.deleted),\n\t\t...Object.keys(bMeta.deleted),\n\t\t...aDocs.keys(),\n\t\t...bDocs.keys()\n\t]);\n\n\tfor (const id of ids) {\n\t\tconst aClk = aMeta.clocks[id] ?? null;\n\t\tconst bClk = bMeta.clocks[id] ?? null;\n\t\tconst aliveClk = max(aClk, bClk);\n\t\tconst deadClk = max(aMeta.deleted[id] ?? null, bMeta.deleted[id] ?? null);\n\n\t\tconst deleteWins =\n\t\t\tstrategy !== 'grow-set' && deadClk && (!aliveClk || compare(deadClk, aliveClk) > 0);\n\n\t\tif (deleteWins) {\n\t\t\tcol.deleted[id] = deadClk as Hlc;\n\t\t\tcontinue;\n\t\t}\n\t\tif (!aliveClk) {\n\t\t\tif (deadClk) col.deleted[id] = deadClk;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst aHas = aDocs.has(id);\n\t\tconst winnerSide = pickWinner(aClk, bClk, aHas);\n\t\tlet doc = winnerSide === 'local' ? aDocs.get(id) : bDocs.get(id);\n\n\t\tif (strategy === 'manual') {\n\t\t\tconst conflict = manualConflict(name, id, aDocs, bDocs, aClk, bClk, baseMeta, winnerSide);\n\t\t\tif (conflict) conflicts.push(conflict);\n\t\t}\n\n\t\tif (strategy === 'lww-map') {\n\t\t\tconst merged = mergeFields(id, aDocs.get(id), bDocs.get(id), aMeta, bMeta, winnerSide);\n\t\t\tif (merged) {\n\t\t\t\tdoc = merged.doc;\n\t\t\t\t(col.fields ??= dict())[id] = merged.fields;\n\t\t\t} else {\n\t\t\t\t// One side has no document: the record-level winner stands; carry its\n\t\t\t\t// field metadata forward so later merges stay field-aware.\n\t\t\t\tconst f = (winnerSide === 'local' ? aMeta : bMeta).fields?.[id];\n\t\t\t\tif (f) (col.fields ??= dict())[id] = dict(f);\n\t\t\t}\n\t\t}\n\n\t\tif (doc !== undefined) {\n\t\t\tdocs.push(doc);\n\t\t\tcol.clocks[id] = aliveClk;\n\t\t\tcol.hashes[id] = hash(doc);\n\t\t}\n\t}\n\n\treturn { docs, col };\n}\n\n/** Field-by-field merge of one record ('lww-map'): each field goes to the side\n * whose field clock is later; a field only one side stamped keeps that side's\n * value; unstamped fields (written under a record-level strategy) follow the\n * record-level winner.\n * Null when either document is missing (the record-level result stands). */\nfunction mergeFields(\n\tid: Id,\n\taDoc: unknown,\n\tbDoc: unknown,\n\taMeta: ColMeta,\n\tbMeta: ColMeta,\n\twinnerSide: 'local' | 'remote'\n): { doc: Record<string, unknown>; fields: Record<string, [Hlc, number]> } | null {\n\tif (!isRecord(aDoc) || !isRecord(bDoc)) return null;\n\tconst aF = aMeta.fields?.[id] ?? dict();\n\tconst bF = bMeta.fields?.[id] ?? dict();\n\tconst doc: Record<string, unknown> = {};\n\tconst fields = dict<[Hlc, number]>();\n\tfor (const key of new Set([...Object.keys(aDoc), ...Object.keys(bDoc)])) {\n\t\tconst a = aF[key] ?? null;\n\t\tconst b = bF[key] ?? null;\n\t\tconst side = pickFieldSide(a, b, winnerSide);\n\t\tconst src = side === 'local' ? aDoc : bDoc;\n\t\tif (!Object.hasOwn(src, key)) continue; // the winning side removed the field: drop it\n\t\t// Defined, never assigned: `doc.__proto__ = v` would swap the prototype.\n\t\tObject.defineProperty(doc, key, {\n\t\t\tvalue: src[key],\n\t\t\tenumerable: true,\n\t\t\twritable: true,\n\t\t\tconfigurable: true\n\t\t});\n\t\tconst stamped = side === 'local' ? a : b;\n\t\tif (stamped) fields[key] = stamped;\n\t}\n\treturn { doc, fields };\n}\n\n/** Which side supplies one field of an 'lww-map' record: the later field clock\n * when both sides stamped it, the stamping side when only one did, else the\n * record-level winner (unstamped fields follow the record). */\nfunction pickFieldSide(\n\ta: [Hlc, number] | null,\n\tb: [Hlc, number] | null,\n\twinnerSide: 'local' | 'remote'\n): 'local' | 'remote' {\n\tif (a && b) return compare(a[0], b[0]) >= 0 ? 'local' : 'remote';\n\tif (a) return 'local';\n\tif (b) return 'remote';\n\treturn winnerSide;\n}\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n\treturn typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\n/** Which side's value to keep for a record. With both clocks present, the later\n * clock wins (ties to local); otherwise the side that actually holds the doc. */\nfunction pickWinner(aClk: Hlc | null, bClk: Hlc | null, aHas: boolean): 'local' | 'remote' {\n\tif (aClk && bClk) return compare(aClk, bClk) >= 0 ? 'local' : 'remote';\n\treturn aHas ? 'local' : 'remote';\n}\n\nfunction byId(\n\tconfig: SyncConfig,\n\tcollection: string,\n\tdocs: unknown[] | undefined\n): Map<Id, unknown> {\n\tconst m = new Map<Id, unknown>();\n\tfor (const d of docs ?? []) {\n\t\tconst id = idOf(config, collection, d);\n\t\tif (id !== null) m.set(id, d);\n\t}\n\treturn m;\n}\n\n/** What a merge did to one collection, counted by record. */\nexport interface CollectionChanges {\n\tadded: number;\n\tupdated: number;\n\tremoved: number;\n}\n\n/** Per-collection record counts of what changed between two states (same id\n * resolution as the merge). Collections with no change are omitted, so an empty\n * result means the states are equivalent. Feeds the sync journal. */\nexport function changes(\n\tbefore: Record<string, unknown[]>,\n\tafter: Record<string, unknown[]>,\n\tconfig: SyncConfig\n): Record<string, CollectionChanges> {\n\tconst out: Record<string, CollectionChanges> = {};\n\tfor (const name of new Set([...Object.keys(before), ...Object.keys(after)])) {\n\t\tconst b = byId(config, name, before[name]);\n\t\tconst a = byId(config, name, after[name]);\n\t\tlet added = 0;\n\t\tlet updated = 0;\n\t\tlet removed = 0;\n\t\tfor (const [id, doc] of a) {\n\t\t\tif (!b.has(id)) added++;\n\t\t\telse if (hash(doc) !== hash(b.get(id))) updated++;\n\t\t}\n\t\tfor (const id of b.keys()) if (!a.has(id)) removed++;\n\t\tif (added || updated || removed) out[name] = { added, updated, removed };\n\t}\n\treturn out;\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createNode, createMeta, gcTombstones, stamp, merge, unsyncableCounts, idPathFor, detectConflicts, changes } from './chunk-
|
|
1
|
+
import { createNode, createMeta, gcTombstones, stamp, merge, unsyncableCounts, idPathFor, detectConflicts, changes } from './chunk-3P2W3XQD.js';
|
|
2
2
|
import { inspect, checkPasswordPolicy, writeBox, BACKUP_MIME, readBoxWithSync, asBytes } from './chunk-E4S77JSN.js';
|
|
3
3
|
import { seal, isEnvelope, unseal, newDeviceKey } from './chunk-QBCU7OOP.js';
|
|
4
4
|
import { openManifest, GROUP_KEYING } from './chunk-F4ZZEJ5H.js';
|
|
@@ -290,22 +290,25 @@ function createLocalStore(opts) {
|
|
|
290
290
|
if (p === null) envelope = null;
|
|
291
291
|
armIdleLock();
|
|
292
292
|
}
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
293
|
+
const blobOf = /* @__PURE__ */ new WeakMap();
|
|
294
|
+
const blobFor = (f) => {
|
|
295
|
+
let blob = blobOf.get(f.bytes);
|
|
296
|
+
if (!blob) {
|
|
297
|
+
blob = new Blob([f.bytes], { type: f.mime });
|
|
298
|
+
blobOf.set(f.bytes, blob);
|
|
299
|
+
}
|
|
300
|
+
return blob;
|
|
301
|
+
};
|
|
302
|
+
const toCachedFiles = (snap) => snap.files.map((f) => ({ id: f.id, name: f.name, mime: f.mime, blob: blobFor(f) }));
|
|
299
303
|
async function cachedToSnapshot(local) {
|
|
300
304
|
return {
|
|
301
305
|
collections: local.collections,
|
|
302
306
|
files: await Promise.all(
|
|
303
|
-
local.files.map(async (f) =>
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
mime: f.mime,
|
|
307
|
-
|
|
308
|
-
}))
|
|
307
|
+
local.files.map(async (f) => {
|
|
308
|
+
const bytes = new Uint8Array(await f.blob.arrayBuffer());
|
|
309
|
+
blobOf.set(bytes, f.blob);
|
|
310
|
+
return { id: f.id, name: f.name, mime: f.mime, bytes };
|
|
311
|
+
})
|
|
309
312
|
)
|
|
310
313
|
};
|
|
311
314
|
}
|
|
@@ -559,6 +562,11 @@ function createLocalStore(opts) {
|
|
|
559
562
|
}
|
|
560
563
|
return now !== null && now === p.marker ? p.blob : null;
|
|
561
564
|
}
|
|
565
|
+
async function readIncoming(t) {
|
|
566
|
+
const blob = await takeFreshPrefetch(t) ?? await t.load();
|
|
567
|
+
if (blob && blob.size > 0) await inspect(blob);
|
|
568
|
+
return blob;
|
|
569
|
+
}
|
|
562
570
|
async function statRemote() {
|
|
563
571
|
if (!durable?.stat) return null;
|
|
564
572
|
try {
|
|
@@ -996,9 +1004,9 @@ function createLocalStore(opts) {
|
|
|
996
1004
|
return entry;
|
|
997
1005
|
}
|
|
998
1006
|
const pull = (source) => serialize(() => doPull(source));
|
|
999
|
-
async function loadFromRemote() {
|
|
1007
|
+
async function loadFromRemote(incoming) {
|
|
1000
1008
|
if (!durable) return;
|
|
1001
|
-
const remote = await takeFreshPrefetch(durable) ?? await durable.load();
|
|
1009
|
+
const remote = incoming === void 0 ? await takeFreshPrefetch(durable) ?? await durable.load() : incoming;
|
|
1002
1010
|
if (remote) {
|
|
1003
1011
|
const decoded = await importLocked(remote);
|
|
1004
1012
|
if (decoded === "locked") return;
|
|
@@ -1302,8 +1310,9 @@ function createLocalStore(opts) {
|
|
|
1302
1310
|
"attachTarget(): this identity is not a member of the manifest - the admin must add it first."
|
|
1303
1311
|
);
|
|
1304
1312
|
}
|
|
1305
|
-
await becomePersistent();
|
|
1306
1313
|
const strategy = attachOpts?.strategy ?? "merge";
|
|
1314
|
+
const incoming = strategy === "replace-local" ? await readIncoming(target) : void 0;
|
|
1315
|
+
await becomePersistent();
|
|
1307
1316
|
if (strategy === "replace-local" && durable && !locked() && !needsAttention) {
|
|
1308
1317
|
await flushBeforeLeaving();
|
|
1309
1318
|
}
|
|
@@ -1337,7 +1346,7 @@ function createLocalStore(opts) {
|
|
|
1337
1346
|
await kv.set(KEY.targetKind, target.kind);
|
|
1338
1347
|
needsAttention = false;
|
|
1339
1348
|
notify();
|
|
1340
|
-
if (strategy === "replace-local") await loadFromRemote();
|
|
1349
|
+
if (strategy === "replace-local") await loadFromRemote(incoming);
|
|
1341
1350
|
else if (strategy === "replace-remote") await doSave();
|
|
1342
1351
|
else await doPull("connect");
|
|
1343
1352
|
notify();
|
|
@@ -1864,20 +1873,13 @@ function isLockableCache(cache) {
|
|
|
1864
1873
|
}
|
|
1865
1874
|
function memoryCache() {
|
|
1866
1875
|
const kvMap = /* @__PURE__ */ new Map();
|
|
1876
|
+
const sealedMap = /* @__PURE__ */ new Map();
|
|
1867
1877
|
let collections;
|
|
1868
1878
|
const files = /* @__PURE__ */ new Map();
|
|
1869
1879
|
return {
|
|
1870
|
-
kv:
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
},
|
|
1874
|
-
async set(key, value) {
|
|
1875
|
-
kvMap.set(key, value);
|
|
1876
|
-
},
|
|
1877
|
-
async del(key) {
|
|
1878
|
-
kvMap.delete(key);
|
|
1879
|
-
}
|
|
1880
|
-
},
|
|
1880
|
+
kv: mapKv(kvMap),
|
|
1881
|
+
// Nothing is at rest, so "sealed" is a separate space, not a cipher.
|
|
1882
|
+
sealedKv: mapKv(sealedMap),
|
|
1881
1883
|
async load() {
|
|
1882
1884
|
if (collections === void 0) return null;
|
|
1883
1885
|
return { collections: structuredClone(collections), files: [...files.values()] };
|
|
@@ -1891,14 +1893,29 @@ function memoryCache() {
|
|
|
1891
1893
|
},
|
|
1892
1894
|
async clear() {
|
|
1893
1895
|
kvMap.clear();
|
|
1896
|
+
sealedMap.clear();
|
|
1894
1897
|
collections = void 0;
|
|
1895
1898
|
files.clear();
|
|
1896
1899
|
}
|
|
1897
1900
|
};
|
|
1898
1901
|
}
|
|
1902
|
+
function mapKv(map) {
|
|
1903
|
+
return {
|
|
1904
|
+
async get(key) {
|
|
1905
|
+
return map.get(key);
|
|
1906
|
+
},
|
|
1907
|
+
async set(key, value) {
|
|
1908
|
+
map.set(key, value);
|
|
1909
|
+
},
|
|
1910
|
+
async del(key) {
|
|
1911
|
+
map.delete(key);
|
|
1912
|
+
}
|
|
1913
|
+
};
|
|
1914
|
+
}
|
|
1899
1915
|
var COLLECTIONS_KEY = "__collections";
|
|
1900
1916
|
var DEVICE_KEY = "__deviceKey";
|
|
1901
1917
|
var CACHE_KDF = "__cacheKdf";
|
|
1918
|
+
var SEALED_PREFIX = "s:";
|
|
1902
1919
|
async function sealFile(k, meta, bytes) {
|
|
1903
1920
|
const sealedMeta = await seal(k, new TextEncoder().encode(JSON.stringify(meta)));
|
|
1904
1921
|
return { meta: sealedMeta, ...await seal(k, bytes) };
|
|
@@ -1969,8 +1986,31 @@ function indexedDbCache(name, opts) {
|
|
|
1969
1986
|
await (await db()).delete("kv", `m:${key2}`);
|
|
1970
1987
|
}
|
|
1971
1988
|
};
|
|
1989
|
+
const sealedKv = {
|
|
1990
|
+
async get(k) {
|
|
1991
|
+
const raw = await (await db()).get("kv", `${SEALED_PREFIX}${k}`);
|
|
1992
|
+
if (raw === void 0) return void 0;
|
|
1993
|
+
if (!isEnvelope(raw)) {
|
|
1994
|
+
throw new SelfstoreError(
|
|
1995
|
+
"BAD_FORMAT",
|
|
1996
|
+
"Unrecognized cache record (not a sealed envelope)."
|
|
1997
|
+
);
|
|
1998
|
+
}
|
|
1999
|
+
return JSON.parse(new TextDecoder().decode(await unseal(await key(), raw)));
|
|
2000
|
+
},
|
|
2001
|
+
async set(k, value) {
|
|
2002
|
+
const env = await seal(await key(), new TextEncoder().encode(JSON.stringify(value)));
|
|
2003
|
+
await (await db()).put("kv", env, `${SEALED_PREFIX}${k}`);
|
|
2004
|
+
},
|
|
2005
|
+
async del(k) {
|
|
2006
|
+
await (await db()).delete("kv", `${SEALED_PREFIX}${k}`);
|
|
2007
|
+
}
|
|
2008
|
+
};
|
|
2009
|
+
const written = /* @__PURE__ */ new Map();
|
|
2010
|
+
const changed = (f, have) => !have.has(f.id) || written.has(f.id) && written.get(f.id) !== f.blob;
|
|
1972
2011
|
const base = {
|
|
1973
2012
|
kv,
|
|
2013
|
+
sealedKv,
|
|
1974
2014
|
async load() {
|
|
1975
2015
|
const d = await db();
|
|
1976
2016
|
const rawCollections = await d.get("kv", COLLECTIONS_KEY);
|
|
@@ -1987,7 +2027,10 @@ function indexedDbCache(name, opts) {
|
|
|
1987
2027
|
const files = [];
|
|
1988
2028
|
for (const fileKey of await d.getAllKeys("files")) {
|
|
1989
2029
|
const v = await d.get("files", fileKey);
|
|
1990
|
-
if (v)
|
|
2030
|
+
if (!v) continue;
|
|
2031
|
+
const file = { id: String(fileKey), ...await decodeFile(v) };
|
|
2032
|
+
written.set(file.id, file.blob);
|
|
2033
|
+
files.push(file);
|
|
1991
2034
|
}
|
|
1992
2035
|
return { collections, files };
|
|
1993
2036
|
},
|
|
@@ -2002,7 +2045,7 @@ function indexedDbCache(name, opts) {
|
|
|
2002
2045
|
const want = new Set(files.map((f) => f.id));
|
|
2003
2046
|
const k = await key();
|
|
2004
2047
|
const fresh = await Promise.all(
|
|
2005
|
-
files.filter((f) =>
|
|
2048
|
+
files.filter((f) => changed(f, have)).map(async (f) => {
|
|
2006
2049
|
const bytes = new Uint8Array(await f.blob.arrayBuffer());
|
|
2007
2050
|
return { id: f.id, rec: await sealFile(k, { name: f.name, mime: f.mime }, bytes) };
|
|
2008
2051
|
})
|
|
@@ -2011,11 +2054,14 @@ function indexedDbCache(name, opts) {
|
|
|
2011
2054
|
for (const { id, rec } of fresh) tx.store.put(rec, id);
|
|
2012
2055
|
for (const id of have) if (!want.has(id)) tx.store.delete(id);
|
|
2013
2056
|
await tx.done;
|
|
2057
|
+
written.clear();
|
|
2058
|
+
for (const f of files) written.set(f.id, f.blob);
|
|
2014
2059
|
},
|
|
2015
2060
|
async clear() {
|
|
2016
2061
|
const d = await db();
|
|
2017
2062
|
await d.clear("kv");
|
|
2018
2063
|
await d.clear("files");
|
|
2064
|
+
written.clear();
|
|
2019
2065
|
_key = null;
|
|
2020
2066
|
sealKey = null;
|
|
2021
2067
|
},
|
|
@@ -2059,11 +2105,19 @@ function indexedDbCache(name, opts) {
|
|
|
2059
2105
|
const meta = await openFileMeta(previous, v);
|
|
2060
2106
|
files.push({ id: fileKey, rec: await sealFile(next, meta, await unseal(previous, v)) });
|
|
2061
2107
|
}
|
|
2108
|
+
const sealedRecords = [];
|
|
2109
|
+
for (const kvKey of await d.getAllKeys("kv")) {
|
|
2110
|
+
if (typeof kvKey !== "string" || !kvKey.startsWith(SEALED_PREFIX)) continue;
|
|
2111
|
+
const v = await d.get("kv", kvKey);
|
|
2112
|
+
if (!isEnvelope(v)) continue;
|
|
2113
|
+
sealedRecords.push({ id: kvKey, rec: await seal(next, await unseal(previous, v)) });
|
|
2114
|
+
}
|
|
2062
2115
|
const tx = d.transaction(["kv", "files"], "readwrite");
|
|
2063
2116
|
const store = tx.objectStore("kv");
|
|
2064
2117
|
if (kdf) store.put(kdf, CACHE_KDF);
|
|
2065
2118
|
else store.delete(CACHE_KDF);
|
|
2066
2119
|
if (collections) store.put(collections, COLLECTIONS_KEY);
|
|
2120
|
+
for (const { id, rec } of sealedRecords) store.put(rec, id);
|
|
2067
2121
|
const fileStore = tx.objectStore("files");
|
|
2068
2122
|
for (const { id, rec } of files) fileStore.put(rec, id);
|
|
2069
2123
|
await tx.done;
|
|
@@ -2225,5 +2279,5 @@ function gisDriveAuth(opts) {
|
|
|
2225
2279
|
}
|
|
2226
2280
|
|
|
2227
2281
|
export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache, preloadGoogleIdentity };
|
|
2228
|
-
//# sourceMappingURL=chunk-
|
|
2229
|
-
//# sourceMappingURL=chunk-
|
|
2282
|
+
//# sourceMappingURL=chunk-JE3BLKXO.js.map
|
|
2283
|
+
//# sourceMappingURL=chunk-JE3BLKXO.js.map
|