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.
Files changed (38) hide show
  1. package/README.md +6 -2
  2. package/dist/advanced.d.ts +9 -9
  3. package/dist/advanced.js +2 -2
  4. package/dist/backups.d.ts +4 -4
  5. package/dist/{cache-D8Z4u5Jz.d.ts → cache-DBIn_Uo8.d.ts} +9 -1
  6. package/dist/{chunk-A2RRJLIF.js → chunk-3P2W3XQD.js} +36 -23
  7. package/dist/chunk-3P2W3XQD.js.map +1 -0
  8. package/dist/{chunk-HQSK7HK3.js → chunk-JE3BLKXO.js} +86 -32
  9. package/dist/chunk-JE3BLKXO.js.map +1 -0
  10. package/dist/{chunk-VRWAV3GI.js → chunk-UU4PS3UH.js} +19 -9
  11. package/dist/chunk-UU4PS3UH.js.map +1 -0
  12. package/dist/{connect-B9f58dFZ.d.ts → connect-CdlhiTBy.d.ts} +4 -4
  13. package/dist/{drive-D5YLXmp_.d.ts → drive-BCRil91Q.d.ts} +2 -2
  14. package/dist/flows.d.ts +4 -4
  15. package/dist/flows.js +1 -1
  16. package/dist/groups.d.ts +2 -2
  17. package/dist/households.d.ts +7 -0
  18. package/dist/households.js +13 -3
  19. package/dist/households.js.map +1 -1
  20. package/dist/index.d.ts +9 -9
  21. package/dist/index.js +3 -3
  22. package/dist/{local-CmS0xFkK.d.ts → local-CpoBeJWk.d.ts} +2 -2
  23. package/dist/{manager-DCV_7B6M.d.ts → manager-0puQEpZc.d.ts} +2 -2
  24. package/dist/passkey.d.ts +6 -4
  25. package/dist/passkey.js +5 -4
  26. package/dist/passkey.js.map +1 -1
  27. package/dist/proof.d.ts +6 -6
  28. package/dist/{s3-mHLrEywJ.d.ts → s3-kikEHI-g.d.ts} +2 -2
  29. package/dist/{simple-Bnh2j78j.d.ts → simple-BmUQpsBM.d.ts} +6 -6
  30. package/dist/{store-DSvZwwr1.d.ts → store-bp1VRW7R.d.ts} +1 -1
  31. package/dist/sync.js +1 -1
  32. package/dist/widgets.d.ts +5 -5
  33. package/dist/widgets.js +1 -1
  34. package/llms.txt +2 -1
  35. package/package.json +1 -1
  36. package/dist/chunk-A2RRJLIF.js.map +0 -1
  37. package/dist/chunk-HQSK7HK3.js.map +0 -1
  38. 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
@@ -1,12 +1,12 @@
1
- import { B as BackupTarget } from './store-DSvZwwr1.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-DSvZwwr1.js';
3
- import { K as KV } from './cache-D8Z4u5Jz.js';
4
- export { C as CachedFile, L as LocalCache, e as LockableCache, i as indexedDbCache, f as isLockableCache, m as memoryCache } from './cache-D8Z4u5Jz.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-CmS0xFkK.js';
6
- import { c as connect$2, f as fromSession$2, a as connect$3, b as fromSession$3, p as peer } from './s3-mHLrEywJ.js';
7
- export { S as S3Config, d as S3ConnectOptions, W as WebdavConfig, e as WebdavConnectOptions, g as WebdavPeerOptions } from './s3-mHLrEywJ.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-D5YLXmp_.js';
9
- export { k as DriveAccount, a as DriveAuth, m as DriveBackupInfo, D as DriveOptions } from './drive-D5YLXmp_.js';
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-HQSK7HK3.js';
4
- import './chunk-A2RRJLIF.js';
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-DCV_7B6M.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-DCV_7B6M.js';
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-D8Z4u5Jz.js';
5
- import './store-DSvZwwr1.js';
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 (immutable by id). */
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: {}, deleted: {}, hashes: {} };
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: { ...c.clocks },
74
- deleted: { ...c.deleted },
75
- hashes: { ...c.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] = { ...f };
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 ??= {})[id] ??= {};
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 in doc)) delete rec[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] ?? emptyCol();
222
- const bMeta = b.meta.cols[name] ?? emptyCol();
223
- const baseMeta = base.cols[name];
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] ?? emptyCol();
276
- const bMeta = b.meta.cols[name] ?? emptyCol();
277
- const baseMeta = base?.cols[name];
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 ??= {})[id] = merged.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 ??= {})[id] = { ...f };
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 in src)) continue;
339
- doc[key] = src[key];
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-A2RRJLIF.js.map
386
- //# sourceMappingURL=chunk-A2RRJLIF.js.map
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-A2RRJLIF.js';
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 toCachedFiles = (snap) => snap.files.map((f) => ({
294
- id: f.id,
295
- name: f.name,
296
- mime: f.mime,
297
- blob: new Blob([f.bytes], { type: f.mime })
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
- id: f.id,
305
- name: f.name,
306
- mime: f.mime,
307
- bytes: new Uint8Array(await f.blob.arrayBuffer())
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
- async get(key) {
1872
- return kvMap.get(key);
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) files.push({ id: String(fileKey), ...await decodeFile(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) => !have.has(f.id)).map(async (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-HQSK7HK3.js.map
2229
- //# sourceMappingURL=chunk-HQSK7HK3.js.map
2282
+ //# sourceMappingURL=chunk-JE3BLKXO.js.map
2283
+ //# sourceMappingURL=chunk-JE3BLKXO.js.map