selfstore 1.0.0 → 1.1.0

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 (55) hide show
  1. package/README.md +4 -0
  2. package/dist/advanced.d.ts +5 -5
  3. package/dist/advanced.js +7 -7
  4. package/dist/backups.d.ts +1 -1
  5. package/dist/backups.js +1 -1
  6. package/dist/backups.js.map +1 -1
  7. package/dist/{cache-lock-LZILZNLP.js → cache-lock-NXIR6JRB.js} +3 -3
  8. package/dist/{cache-lock-LZILZNLP.js.map → cache-lock-NXIR6JRB.js.map} +1 -1
  9. package/dist/{chunk-SGISYMT5.js → chunk-43BJDRPT.js} +2 -2
  10. package/dist/{chunk-SGISYMT5.js.map → chunk-43BJDRPT.js.map} +1 -1
  11. package/dist/{chunk-FDPAKHQS.js → chunk-6746AL4I.js} +9 -4
  12. package/dist/chunk-6746AL4I.js.map +1 -0
  13. package/dist/{chunk-ATUPHVLG.js → chunk-CUFKME4S.js} +5 -5
  14. package/dist/chunk-CUFKME4S.js.map +1 -0
  15. package/dist/{chunk-OWLX6PNT.js → chunk-KHER6MWR.js} +26 -8
  16. package/dist/chunk-KHER6MWR.js.map +1 -0
  17. package/dist/{chunk-IBIEKK36.js → chunk-SKWHKM25.js} +24 -5
  18. package/dist/chunk-SKWHKM25.js.map +1 -0
  19. package/dist/{chunk-JB4JHM7V.js → chunk-SRKUHFRG.js} +17 -8
  20. package/dist/chunk-SRKUHFRG.js.map +1 -0
  21. package/dist/{chunk-CUWB5EWF.js → chunk-TEIAFKFS.js} +63 -22
  22. package/dist/chunk-TEIAFKFS.js.map +1 -0
  23. package/dist/{chunk-BJ3RUMGI.js → chunk-UWVAKBU4.js} +3 -3
  24. package/dist/{chunk-BJ3RUMGI.js.map → chunk-UWVAKBU4.js.map} +1 -1
  25. package/dist/{chunk-N4RPMX3Y.js → chunk-XI3MGK4M.js} +120 -30
  26. package/dist/chunk-XI3MGK4M.js.map +1 -0
  27. package/dist/{drive-Bv94UM8b.d.ts → drive-DNiDXBDe.d.ts} +7 -2
  28. package/dist/flows.d.ts +2 -2
  29. package/dist/flows.js +7 -7
  30. package/dist/flows.js.map +1 -1
  31. package/dist/groups.js +3 -3
  32. package/dist/groups.js.map +1 -1
  33. package/dist/households.d.ts +1 -1
  34. package/dist/households.js +10 -4
  35. package/dist/households.js.map +1 -1
  36. package/dist/index.d.ts +4 -4
  37. package/dist/index.js +14 -11
  38. package/dist/index.js.map +1 -1
  39. package/dist/{local-2py_vGGy.d.ts → local-DsXG7QW7.d.ts} +2 -2
  40. package/dist/local-KIP25T7I.js +7 -0
  41. package/dist/{local-AM22RWRR.js.map → local-KIP25T7I.js.map} +1 -1
  42. package/dist/{store-ChIGEOph.d.ts → store-2RnED1Vq.d.ts} +5 -0
  43. package/dist/sync.js +1 -1
  44. package/dist/widgets.d.ts +2 -2
  45. package/dist/widgets.js +445 -86
  46. package/dist/widgets.js.map +1 -1
  47. package/package.json +129 -97
  48. package/dist/chunk-ATUPHVLG.js.map +0 -1
  49. package/dist/chunk-CUWB5EWF.js.map +0 -1
  50. package/dist/chunk-FDPAKHQS.js.map +0 -1
  51. package/dist/chunk-IBIEKK36.js.map +0 -1
  52. package/dist/chunk-JB4JHM7V.js.map +0 -1
  53. package/dist/chunk-N4RPMX3Y.js.map +0 -1
  54. package/dist/chunk-OWLX6PNT.js.map +0 -1
  55. package/dist/local-AM22RWRR.js +0 -7
@@ -1,8 +1,8 @@
1
- import { createNode, createMeta, gcTombstones, stamp, merge, unsyncableCounts, idPathFor, detectConflicts, changes } from './chunk-IBIEKK36.js';
2
- import { inspect, checkPasswordPolicy, writeBox, BACKUP_MIME, readBoxWithSync, asBytes } from './chunk-JB4JHM7V.js';
3
- import { openManifest, GROUP_KEYING } from './chunk-OWLX6PNT.js';
1
+ import { createNode, createMeta, gcTombstones, stamp, merge, unsyncableCounts, idPathFor, detectConflicts, changes } from './chunk-SKWHKM25.js';
2
+ import { inspect, checkPasswordPolicy, writeBox, BACKUP_MIME, readBoxWithSync, asBytes } from './chunk-SRKUHFRG.js';
3
+ import { openManifest, GROUP_KEYING } from './chunk-KHER6MWR.js';
4
4
  import { seal, isEnvelope, unseal, newDeviceKey } from './chunk-QBCU7OOP.js';
5
- import { mintSlot, MAX_KEY_SLOTS, mintExternalSlot, openSlot } from './chunk-FDPAKHQS.js';
5
+ import { mintSlot, MAX_KEY_SLOTS, mintExternalSlot, openSlot } from './chunk-6746AL4I.js';
6
6
  import { SelfstoreError, isAuthExpired, errorLabelKey, isSelfstoreError } from './chunk-VBFMMR7Q.js';
7
7
  import { openDB } from 'idb';
8
8
 
@@ -86,9 +86,40 @@ var KEY = {
86
86
  groupSeqIds: "groupSeqIds"
87
87
  };
88
88
  var JOURNAL_MAX = 20;
89
+ var BOOT_NET_MS = 25e3;
90
+ var BootTimeout = class extends Error {
91
+ constructor(what) {
92
+ super(`${what} timed out at boot`);
93
+ this.name = "BootTimeout";
94
+ }
95
+ };
96
+ function bootDeadline(work, what, ms = BOOT_NET_MS) {
97
+ return new Promise((resolve, reject) => {
98
+ const timer = setTimeout(() => reject(new BootTimeout(what)), ms);
99
+ work.then(
100
+ (v) => {
101
+ clearTimeout(timer);
102
+ resolve(v);
103
+ },
104
+ (e) => {
105
+ clearTimeout(timer);
106
+ reject(e instanceof Error ? e : new Error(String(e)));
107
+ }
108
+ );
109
+ });
110
+ }
89
111
  var STALE_CHECK_MS = 2e4;
90
112
  function createLocalStore(opts) {
91
- const { app, schemaVersion: version, appVersion, gather, apply, cache, readme, debounceMs = 800 } = opts;
113
+ const {
114
+ app,
115
+ schemaVersion: version,
116
+ appVersion,
117
+ gather,
118
+ apply,
119
+ cache,
120
+ readme,
121
+ debounceMs = 800
122
+ } = opts;
92
123
  const logger = opts.logger ?? console;
93
124
  const requireEncryption = opts.requireEncryption ?? false;
94
125
  const passwordPolicy = opts.passwordPolicy;
@@ -96,7 +127,10 @@ function createLocalStore(opts) {
96
127
  if (!passwordPolicy) return;
97
128
  const { ok, unmet } = checkPasswordPolicy(pw, passwordPolicy);
98
129
  if (!ok) {
99
- throw new SelfstoreError("WEAK_PASSWORD", `Password does not meet the policy: ${unmet.join(", ")}.`);
130
+ throw new SelfstoreError(
131
+ "WEAK_PASSWORD",
132
+ `Password does not meet the policy: ${unmet.join(", ")}.`
133
+ );
100
134
  }
101
135
  }
102
136
  const journalSilent = new Set(opts.journalSilent ?? []);
@@ -446,7 +480,7 @@ function createLocalStore(opts) {
446
480
  }
447
481
  }
448
482
  async function requireReady(where) {
449
- let ok = false;
483
+ let ok;
450
484
  try {
451
485
  ok = await durable.isReady();
452
486
  } catch (e) {
@@ -469,7 +503,10 @@ function createLocalStore(opts) {
469
503
  }
470
504
  function redactForStorage(entries) {
471
505
  return entries.map(
472
- (e) => e.conflicts ? { ...e, conflicts: e.conflicts.map(({ collection, id, kept }) => ({ collection, id, kept })) } : e
506
+ (e) => e.conflicts ? {
507
+ ...e,
508
+ conflicts: e.conflicts.map(({ collection, id, kept }) => ({ collection, id, kept }))
509
+ } : e
473
510
  );
474
511
  }
475
512
  function recordJournal(delta, source, conflicts = []) {
@@ -716,7 +753,12 @@ function createLocalStore(opts) {
716
753
  );
717
754
  continue;
718
755
  }
719
- const { snapshot, sidecar, author, envelope: peerEnv } = await readBoxWithSync(
756
+ const {
757
+ snapshot,
758
+ sidecar,
759
+ author,
760
+ envelope: peerEnv
761
+ } = await readBoxWithSync(
720
762
  await asBytes(remote),
721
763
  p.password ?? pass ?? void 0,
722
764
  peerGroupRead(),
@@ -824,7 +866,9 @@ function createLocalStore(opts) {
824
866
  }
825
867
  await cache.requestPersistent?.();
826
868
  }
827
- async function dropDurable({ keepSession = false } = {}) {
869
+ async function dropDurable({
870
+ keepSession = false
871
+ } = {}) {
828
872
  if (durable && !keepSession) await durable.disconnect();
829
873
  durable = null;
830
874
  label = null;
@@ -947,7 +991,10 @@ function createLocalStore(opts) {
947
991
  let t = null;
948
992
  let restoreThrew = false;
949
993
  try {
950
- t = opts.restoreTarget ? await opts.restoreTarget(savedKind) : null;
994
+ t = opts.restoreTarget ? await bootDeadline(
995
+ Promise.resolve(opts.restoreTarget(savedKind)),
996
+ "destination restore"
997
+ ) : null;
951
998
  } catch (e) {
952
999
  restoreThrew = true;
953
1000
  lastError = transient("Could not restore the destination; will retry.");
@@ -959,14 +1006,19 @@ function createLocalStore(opts) {
959
1006
  label = t.label;
960
1007
  let ok = false;
961
1008
  try {
962
- ok = await t.isReady();
963
- } catch {
964
- raiseGate("AUTH_EXPIRED", "Access to the destination expired or was revoked.");
1009
+ ok = await bootDeadline(t.isReady(), "destination check");
1010
+ } catch (e) {
1011
+ if (e instanceof BootTimeout) {
1012
+ lastError = transient("Destination is slow to answer; will retry.");
1013
+ logger.error("[selfstore] boot destination check stalled", e);
1014
+ } else {
1015
+ raiseGate("AUTH_EXPIRED", "Access to the destination expired or was revoked.");
1016
+ }
965
1017
  }
966
1018
  needsAttention = needsAttention || locked();
967
1019
  if (ok && !locked()) {
968
1020
  try {
969
- await pull("boot");
1021
+ await bootDeadline(pull("boot"), "boot sync");
970
1022
  } catch (e) {
971
1023
  lastError = transient("Boot sync failed; will retry.");
972
1024
  logger.error("[selfstore] boot sync failed", e);
@@ -982,7 +1034,7 @@ function createLocalStore(opts) {
982
1034
  }
983
1035
  if (!durable && peerList.length > 0) {
984
1036
  try {
985
- await pull("boot");
1037
+ await bootDeadline(pull("boot"), "boot sync");
986
1038
  } catch (e) {
987
1039
  lastError = transient("Boot sync failed; will retry.");
988
1040
  logger.error("[selfstore] boot sync failed", e);
@@ -1115,7 +1167,7 @@ function createLocalStore(opts) {
1115
1167
  async inspectTarget(target) {
1116
1168
  const blob = await target.load();
1117
1169
  if (!blob || blob.size === 0) return { hasBackup: false, date: null, encrypted: false };
1118
- let marker = null;
1170
+ let marker;
1119
1171
  try {
1120
1172
  marker = target.stat ? await target.stat() : null;
1121
1173
  } catch {
@@ -1158,6 +1210,7 @@ function createLocalStore(opts) {
1158
1210
  return false;
1159
1211
  },
1160
1212
  detachTarget(opts2) {
1213
+ durable?.abortInFlight?.();
1161
1214
  return serialize(async () => {
1162
1215
  await dropDurable({ keepSession: opts2?.keepSession });
1163
1216
  targetKind = "device";
@@ -1252,7 +1305,13 @@ function createLocalStore(opts) {
1252
1305
  if (replicaList.some((r) => r.id === id)) {
1253
1306
  throw new TypeError(`attachReplica(): a replica with id "${id}" is already attached`);
1254
1307
  }
1255
- replicaList.push({ id, target, label: target.label ?? id, lastPublishAt: null, lastError: null });
1308
+ replicaList.push({
1309
+ id,
1310
+ target,
1311
+ label: target.label ?? id,
1312
+ lastPublishAt: null,
1313
+ lastError: null
1314
+ });
1256
1315
  notify();
1257
1316
  if (ready) {
1258
1317
  void serialize(publishReplicas).catch(
@@ -1359,7 +1418,10 @@ function createLocalStore(opts) {
1359
1418
  }
1360
1419
  assertPasswordPolicy(password);
1361
1420
  if (!durable) {
1362
- throw new SelfstoreError("NOT_CONNECTED", "addEncryptionKey(): no destination is connected.");
1421
+ throw new SelfstoreError(
1422
+ "NOT_CONNECTED",
1423
+ "addEncryptionKey(): no destination is connected."
1424
+ );
1363
1425
  }
1364
1426
  if (locked() || needsAttention) {
1365
1427
  throw new SelfstoreError(
@@ -1389,7 +1451,9 @@ function createLocalStore(opts) {
1389
1451
  envelope = { dataKey, slots: [await mintSlot(pass, dataKey)] };
1390
1452
  }
1391
1453
  if (envelope.slots.length >= MAX_KEY_SLOTS) {
1392
- throw new TypeError(`addEncryptionKey(): this backup already has ${MAX_KEY_SLOTS} key slots.`);
1454
+ throw new TypeError(
1455
+ `addEncryptionKey(): this backup already has ${MAX_KEY_SLOTS} key slots.`
1456
+ );
1393
1457
  }
1394
1458
  if (id && envelope.slots.some((s) => s.id === id)) {
1395
1459
  throw new TypeError(`addEncryptionKey(): a key slot named "${id}" already exists.`);
@@ -1422,7 +1486,10 @@ function createLocalStore(opts) {
1422
1486
  );
1423
1487
  }
1424
1488
  if (!durable) {
1425
- throw new SelfstoreError("NOT_CONNECTED", "removeEncryptionKey(): no destination is connected.");
1489
+ throw new SelfstoreError(
1490
+ "NOT_CONNECTED",
1491
+ "removeEncryptionKey(): no destination is connected."
1492
+ );
1426
1493
  }
1427
1494
  if (locked() || needsAttention) {
1428
1495
  throw new SelfstoreError(
@@ -1499,7 +1566,10 @@ function createLocalStore(opts) {
1499
1566
  );
1500
1567
  }
1501
1568
  if (!durable) {
1502
- throw new SelfstoreError("NOT_CONNECTED", "setExternalEncryption(): no destination is connected.");
1569
+ throw new SelfstoreError(
1570
+ "NOT_CONNECTED",
1571
+ "setExternalEncryption(): no destination is connected."
1572
+ );
1503
1573
  }
1504
1574
  if (locked() || needsAttention) {
1505
1575
  throw new SelfstoreError(
@@ -1532,7 +1602,13 @@ function createLocalStore(opts) {
1532
1602
  await persistLocal();
1533
1603
  pendingEdit = false;
1534
1604
  const blob = await buildBlob();
1535
- await readBoxWithSync(await asBytes(blob), void 0, void 0, void 0, async () => secret);
1605
+ await readBoxWithSync(
1606
+ await asBytes(blob),
1607
+ void 0,
1608
+ void 0,
1609
+ void 0,
1610
+ async () => secret
1611
+ );
1536
1612
  const written = await durable.save(blob);
1537
1613
  await setRemoteVersion(typeof written === "string" ? written : await statRemote());
1538
1614
  lastError = null;
@@ -1592,7 +1668,9 @@ function createLocalStore(opts) {
1592
1668
  throw new TypeError("addExternalKey(): no data key in hand - unlock the backup first.");
1593
1669
  }
1594
1670
  if (envelope.slots.length >= MAX_KEY_SLOTS) {
1595
- throw new TypeError(`addExternalKey(): this backup already has ${MAX_KEY_SLOTS} key slots.`);
1671
+ throw new TypeError(
1672
+ `addExternalKey(): this backup already has ${MAX_KEY_SLOTS} key slots.`
1673
+ );
1596
1674
  }
1597
1675
  if (id && envelope.slots.some((s) => s.id === id)) {
1598
1676
  throw new TypeError(`addExternalKey(): a key slot named "${id}" already exists.`);
@@ -1602,7 +1680,13 @@ function createLocalStore(opts) {
1602
1680
  envelope = { dataKey: envelope.dataKey, slots: [...envelope.slots, slot] };
1603
1681
  try {
1604
1682
  const blob = await buildBlob();
1605
- await readBoxWithSync(await asBytes(blob), void 0, void 0, void 0, async () => secret);
1683
+ await readBoxWithSync(
1684
+ await asBytes(blob),
1685
+ void 0,
1686
+ void 0,
1687
+ void 0,
1688
+ async () => secret
1689
+ );
1606
1690
  const written = await durable.save(blob);
1607
1691
  await setRemoteVersion(typeof written === "string" ? written : await statRemote());
1608
1692
  lastError = null;
@@ -1697,7 +1781,10 @@ function indexedDbCache(name, opts) {
1697
1781
  function key() {
1698
1782
  if (lockMode) {
1699
1783
  return sealKey ? Promise.resolve(sealKey) : Promise.reject(
1700
- new SelfstoreError("PASSWORD_REQUIRED", "The local cache is locked - unlock() it first.")
1784
+ new SelfstoreError(
1785
+ "PASSWORD_REQUIRED",
1786
+ "The local cache is locked - unlock() it first."
1787
+ )
1701
1788
  );
1702
1789
  }
1703
1790
  return _key ??= loadOrCreateKey();
@@ -1737,7 +1824,10 @@ function indexedDbCache(name, opts) {
1737
1824
  const rawCollections = await d.get("kv", COLLECTIONS_KEY);
1738
1825
  if (rawCollections === void 0) return null;
1739
1826
  if (!isEnvelope(rawCollections)) {
1740
- throw new SelfstoreError("BAD_FORMAT", "Unrecognized cache record (not a sealed envelope).");
1827
+ throw new SelfstoreError(
1828
+ "BAD_FORMAT",
1829
+ "Unrecognized cache record (not a sealed envelope)."
1830
+ );
1741
1831
  }
1742
1832
  const collections = JSON.parse(
1743
1833
  new TextDecoder().decode(await unseal(await key(), rawCollections))
@@ -1797,7 +1887,7 @@ function indexedDbCache(name, opts) {
1797
1887
  sealKey = null;
1798
1888
  },
1799
1889
  async unlock(secret) {
1800
- const { keyFromPassword, freshCacheKdf, opens } = await import('./cache-lock-LZILZNLP.js');
1890
+ const { keyFromPassword, freshCacheKdf, opens } = await import('./cache-lock-NXIR6JRB.js');
1801
1891
  const d = await db();
1802
1892
  let candidate;
1803
1893
  if (typeof secret === "string") {
@@ -1904,5 +1994,5 @@ function gisDriveAuth(opts) {
1904
1994
  }
1905
1995
 
1906
1996
  export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache };
1907
- //# sourceMappingURL=chunk-N4RPMX3Y.js.map
1908
- //# sourceMappingURL=chunk-N4RPMX3Y.js.map
1997
+ //# sourceMappingURL=chunk-XI3MGK4M.js.map
1998
+ //# sourceMappingURL=chunk-XI3MGK4M.js.map