selfstore 1.8.35 → 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 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-B4Zo0f6G.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-B4Zo0f6G.js';
3
- import { K as KV } from './cache-C1edLncd.js';
4
- export { C as CachedFile, L as LocalCache, e as LockableCache, i as indexedDbCache, f as isLockableCache, m as memoryCache } from './cache-C1edLncd.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-Cr5cCaQ2.js';
6
- import { c as connect$2, f as fromSession$2, a as connect$3, b as fromSession$3, p as peer } from './s3-C2PrgMpD.js';
7
- export { S as S3Config, d as S3ConnectOptions, W as WebdavConfig, e as WebdavConnectOptions, g as WebdavPeerOptions } from './s3-C2PrgMpD.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-3K6pBAmG.js';
9
- export { k as DriveAccount, a as DriveAuth, m as DriveBackupInfo, D as DriveOptions } from './drive-3K6pBAmG.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,6 +1,6 @@
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-SHX36MIE.js';
3
+ export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache, preloadGoogleIdentity } from './chunk-JE3BLKXO.js';
4
4
  import './chunk-3P2W3XQD.js';
5
5
  export { datedName, pickFromDisk, saveToDisk } from './chunk-XAONTUEC.js';
6
6
  import './chunk-FJGWTBAL.js';
package/dist/backups.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { B as BackupsHost } from './manager-Bl16FR-e.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-Bl16FR-e.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-C1edLncd.js';
5
- import './store-B4Zo0f6G.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[]>;
@@ -562,6 +562,11 @@ function createLocalStore(opts) {
562
562
  }
563
563
  return now !== null && now === p.marker ? p.blob : null;
564
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
+ }
565
570
  async function statRemote() {
566
571
  if (!durable?.stat) return null;
567
572
  try {
@@ -999,9 +1004,9 @@ function createLocalStore(opts) {
999
1004
  return entry;
1000
1005
  }
1001
1006
  const pull = (source) => serialize(() => doPull(source));
1002
- async function loadFromRemote() {
1007
+ async function loadFromRemote(incoming) {
1003
1008
  if (!durable) return;
1004
- const remote = await takeFreshPrefetch(durable) ?? await durable.load();
1009
+ const remote = incoming === void 0 ? await takeFreshPrefetch(durable) ?? await durable.load() : incoming;
1005
1010
  if (remote) {
1006
1011
  const decoded = await importLocked(remote);
1007
1012
  if (decoded === "locked") return;
@@ -1305,8 +1310,9 @@ function createLocalStore(opts) {
1305
1310
  "attachTarget(): this identity is not a member of the manifest - the admin must add it first."
1306
1311
  );
1307
1312
  }
1308
- await becomePersistent();
1309
1313
  const strategy = attachOpts?.strategy ?? "merge";
1314
+ const incoming = strategy === "replace-local" ? await readIncoming(target) : void 0;
1315
+ await becomePersistent();
1310
1316
  if (strategy === "replace-local" && durable && !locked() && !needsAttention) {
1311
1317
  await flushBeforeLeaving();
1312
1318
  }
@@ -1340,7 +1346,7 @@ function createLocalStore(opts) {
1340
1346
  await kv.set(KEY.targetKind, target.kind);
1341
1347
  needsAttention = false;
1342
1348
  notify();
1343
- if (strategy === "replace-local") await loadFromRemote();
1349
+ if (strategy === "replace-local") await loadFromRemote(incoming);
1344
1350
  else if (strategy === "replace-remote") await doSave();
1345
1351
  else await doPull("connect");
1346
1352
  notify();
@@ -1867,20 +1873,13 @@ function isLockableCache(cache) {
1867
1873
  }
1868
1874
  function memoryCache() {
1869
1875
  const kvMap = /* @__PURE__ */ new Map();
1876
+ const sealedMap = /* @__PURE__ */ new Map();
1870
1877
  let collections;
1871
1878
  const files = /* @__PURE__ */ new Map();
1872
1879
  return {
1873
- kv: {
1874
- async get(key) {
1875
- return kvMap.get(key);
1876
- },
1877
- async set(key, value) {
1878
- kvMap.set(key, value);
1879
- },
1880
- async del(key) {
1881
- kvMap.delete(key);
1882
- }
1883
- },
1880
+ kv: mapKv(kvMap),
1881
+ // Nothing is at rest, so "sealed" is a separate space, not a cipher.
1882
+ sealedKv: mapKv(sealedMap),
1884
1883
  async load() {
1885
1884
  if (collections === void 0) return null;
1886
1885
  return { collections: structuredClone(collections), files: [...files.values()] };
@@ -1894,14 +1893,29 @@ function memoryCache() {
1894
1893
  },
1895
1894
  async clear() {
1896
1895
  kvMap.clear();
1896
+ sealedMap.clear();
1897
1897
  collections = void 0;
1898
1898
  files.clear();
1899
1899
  }
1900
1900
  };
1901
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
+ }
1902
1915
  var COLLECTIONS_KEY = "__collections";
1903
1916
  var DEVICE_KEY = "__deviceKey";
1904
1917
  var CACHE_KDF = "__cacheKdf";
1918
+ var SEALED_PREFIX = "s:";
1905
1919
  async function sealFile(k, meta, bytes) {
1906
1920
  const sealedMeta = await seal(k, new TextEncoder().encode(JSON.stringify(meta)));
1907
1921
  return { meta: sealedMeta, ...await seal(k, bytes) };
@@ -1972,10 +1986,31 @@ function indexedDbCache(name, opts) {
1972
1986
  await (await db()).delete("kv", `m:${key2}`);
1973
1987
  }
1974
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
+ };
1975
2009
  const written = /* @__PURE__ */ new Map();
1976
2010
  const changed = (f, have) => !have.has(f.id) || written.has(f.id) && written.get(f.id) !== f.blob;
1977
2011
  const base = {
1978
2012
  kv,
2013
+ sealedKv,
1979
2014
  async load() {
1980
2015
  const d = await db();
1981
2016
  const rawCollections = await d.get("kv", COLLECTIONS_KEY);
@@ -2070,11 +2105,19 @@ function indexedDbCache(name, opts) {
2070
2105
  const meta = await openFileMeta(previous, v);
2071
2106
  files.push({ id: fileKey, rec: await sealFile(next, meta, await unseal(previous, v)) });
2072
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
+ }
2073
2115
  const tx = d.transaction(["kv", "files"], "readwrite");
2074
2116
  const store = tx.objectStore("kv");
2075
2117
  if (kdf) store.put(kdf, CACHE_KDF);
2076
2118
  else store.delete(CACHE_KDF);
2077
2119
  if (collections) store.put(collections, COLLECTIONS_KEY);
2120
+ for (const { id, rec } of sealedRecords) store.put(rec, id);
2078
2121
  const fileStore = tx.objectStore("files");
2079
2122
  for (const { id, rec } of files) fileStore.put(rec, id);
2080
2123
  await tx.done;
@@ -2236,5 +2279,5 @@ function gisDriveAuth(opts) {
2236
2279
  }
2237
2280
 
2238
2281
  export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache, preloadGoogleIdentity };
2239
- //# sourceMappingURL=chunk-SHX36MIE.js.map
2240
- //# sourceMappingURL=chunk-SHX36MIE.js.map
2282
+ //# sourceMappingURL=chunk-JE3BLKXO.js.map
2283
+ //# sourceMappingURL=chunk-JE3BLKXO.js.map