@fairfox/polly 0.62.0 → 0.64.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.
package/dist/src/mesh.js CHANGED
@@ -70,25 +70,22 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
70
70
  });
71
71
 
72
72
  // src/shared/lib/idb-helpers.ts
73
- function openIDB(options) {
73
+ function openIDB(options, openFn = defaultOpenFn) {
74
74
  return new Promise((resolve, reject) => {
75
75
  const start = Date.now();
76
- const request = indexedDB.open(options.name, options.version);
76
+ const request = openFn(options.name, options.version);
77
77
  let settled = false;
78
- const timer = setTimeout(() => {
79
- if (settled)
80
- return;
81
- settled = true;
82
- const elapsedMs = Date.now() - start;
83
- reject(new Error(`Polly IndexedDB open of '${options.name}' timed out after ${elapsedMs}ms (cross-tab lock or zombie connection?)`));
84
- }, IDB_OPEN_TIMEOUT_MS);
85
- request.onerror = () => {
78
+ const elapsed = () => Date.now() - start;
79
+ const rejectWith = (reason, cause) => {
86
80
  if (settled)
87
81
  return;
88
82
  settled = true;
89
83
  clearTimeout(timer);
90
- reject(request.error);
84
+ reject(new IDBOpenError(reason, options.name, elapsed(), cause));
91
85
  };
86
+ const timer = setTimeout(() => rejectWith("timeout"), IDB_OPEN_TIMEOUT_MS);
87
+ request.onerror = () => rejectWith("error", request.error);
88
+ request.onblocked = () => rejectWith("blocked");
92
89
  request.onsuccess = () => {
93
90
  if (settled)
94
91
  return;
@@ -102,10 +99,10 @@ function openIDB(options) {
102
99
  };
103
100
  });
104
101
  }
105
- function cachedOpen(ref, options) {
102
+ function cachedOpen(ref, options, openFn) {
106
103
  if (ref.promise)
107
104
  return ref.promise;
108
- const pending = openIDB(options);
105
+ const pending = openIDB(options, openFn);
109
106
  pending.catch(() => {
110
107
  if (ref.promise === pending)
111
108
  ref.promise = null;
@@ -151,7 +148,21 @@ function iterateCursor(db, storeName, visit) {
151
148
  request.onerror = () => reject(request.error);
152
149
  });
153
150
  }
154
- var IDB_OPEN_TIMEOUT_MS = 5000;
151
+ var IDB_OPEN_TIMEOUT_MS = 5000, IDBOpenError, defaultOpenFn = (name, version) => indexedDB.open(name, version);
152
+ var init_idb_helpers = __esm(() => {
153
+ IDBOpenError = class IDBOpenError extends Error {
154
+ reason;
155
+ dbName;
156
+ elapsedMs;
157
+ constructor(reason, dbName, elapsedMs, cause) {
158
+ super(`Polly IndexedDB open of '${dbName}' ${reason} after ${elapsedMs}ms`, cause === undefined ? undefined : { cause });
159
+ this.name = "IDBOpenError";
160
+ this.reason = reason;
161
+ this.dbName = dbName;
162
+ this.elapsedMs = elapsedMs;
163
+ }
164
+ };
165
+ });
155
166
 
156
167
  // src/shared/lib/encryption.ts
157
168
  var exports_encryption = {};
@@ -253,6 +264,8 @@ var wasmUrl = new URL(wasmPath, import.meta.url).href;
253
264
  await initializeWasm(wasmUrl);
254
265
 
255
266
  // src/shared/lib/blob-cache.ts
267
+ init_idb_helpers();
268
+
256
269
  class MemoryBlobCache {
257
270
  store = new Map;
258
271
  pinned = new Set;
@@ -1716,25 +1729,10 @@ function $crdtState(options) {
1716
1729
  const inner = signal2(options.initialValue);
1717
1730
  let updating = false;
1718
1731
  let currentHandle;
1719
- const loaded = (async () => {
1720
- const handle = await options.getHandle();
1721
- await handle.whenReady();
1722
- currentHandle = handle;
1723
- if (options.schemaVersion !== undefined) {
1724
- const targetVersion = options.schemaVersion;
1725
- const migrations = options.migrations ?? {};
1726
- handle.change((doc) => {
1727
- runMigrations(doc, targetVersion, migrations);
1728
- setDocVersion(doc, targetVersion);
1729
- });
1730
- }
1731
- updating = true;
1732
- try {
1733
- inner.value = cloneDoc(handle.doc());
1734
- } finally {
1735
- updating = false;
1736
- }
1737
- handle.on("change", (payload) => {
1732
+ let detachChangeListener;
1733
+ let swapping = false;
1734
+ function attachChangeListener(handle) {
1735
+ const listener = (payload) => {
1738
1736
  if (updating)
1739
1737
  return;
1740
1738
  updating = true;
@@ -1743,7 +1741,75 @@ function $crdtState(options) {
1743
1741
  } finally {
1744
1742
  updating = false;
1745
1743
  }
1744
+ if (options.resolveRedirect && !swapping) {
1745
+ maybeRebind(handle, payload.doc);
1746
+ }
1747
+ };
1748
+ handle.on("change", listener);
1749
+ detachChangeListener = () => {
1750
+ handle.off("change", listener);
1751
+ };
1752
+ }
1753
+ async function maybeRebind(fromHandle, doc) {
1754
+ if (!options.resolveRedirect)
1755
+ return;
1756
+ if (swapping)
1757
+ return;
1758
+ swapping = true;
1759
+ try {
1760
+ const next = await options.resolveRedirect(fromHandle, doc);
1761
+ if (!next)
1762
+ return;
1763
+ if (next === currentHandle)
1764
+ return;
1765
+ if (next.documentId === fromHandle.documentId)
1766
+ return;
1767
+ detachChangeListener?.();
1768
+ detachChangeListener = undefined;
1769
+ currentHandle = next;
1770
+ attachChangeListener(next);
1771
+ updating = true;
1772
+ try {
1773
+ inner.value = cloneDoc(next.doc());
1774
+ } finally {
1775
+ updating = false;
1776
+ }
1777
+ } finally {
1778
+ swapping = false;
1779
+ }
1780
+ }
1781
+ async function followInitialRedirects(start) {
1782
+ if (!options.resolveRedirect)
1783
+ return start;
1784
+ let handle = start;
1785
+ const seen = new Set([handle.documentId]);
1786
+ for (;; ) {
1787
+ const doc = handle.doc();
1788
+ if (!doc)
1789
+ break;
1790
+ const next = await options.resolveRedirect(handle, doc);
1791
+ if (!next || next === handle)
1792
+ break;
1793
+ const nextIdString = next.documentId;
1794
+ if (seen.has(nextIdString))
1795
+ break;
1796
+ seen.add(nextIdString);
1797
+ handle = next;
1798
+ await handle.whenReady();
1799
+ }
1800
+ return handle;
1801
+ }
1802
+ function applyPendingMigrations(handle) {
1803
+ if (options.schemaVersion === undefined)
1804
+ return;
1805
+ const targetVersion = options.schemaVersion;
1806
+ const migrations = options.migrations ?? {};
1807
+ handle.change((doc) => {
1808
+ runMigrations(doc, targetVersion, migrations);
1809
+ setDocVersion(doc, targetVersion);
1746
1810
  });
1811
+ }
1812
+ function bindSignalToHandle() {
1747
1813
  effect2(() => {
1748
1814
  const value = inner.value;
1749
1815
  if (updating)
@@ -1759,6 +1825,21 @@ function $crdtState(options) {
1759
1825
  updating = false;
1760
1826
  }
1761
1827
  });
1828
+ }
1829
+ const loaded = (async () => {
1830
+ const initialHandle = await options.getHandle();
1831
+ await initialHandle.whenReady();
1832
+ const handle = await followInitialRedirects(initialHandle);
1833
+ currentHandle = handle;
1834
+ applyPendingMigrations(handle);
1835
+ updating = true;
1836
+ try {
1837
+ inner.value = cloneDoc(handle.doc());
1838
+ } finally {
1839
+ updating = false;
1840
+ }
1841
+ attachChangeListener(handle);
1842
+ bindSignalToHandle();
1762
1843
  })();
1763
1844
  return {
1764
1845
  key: options.key,
@@ -1822,6 +1903,7 @@ function resetMeshState() {
1822
1903
  lastLoadedRejection = undefined;
1823
1904
  lastStorageOpenError = undefined;
1824
1905
  lazyWrappers = [];
1906
+ docIdResolver = undefined;
1825
1907
  }
1826
1908
  function isMeshStateConfigured() {
1827
1909
  return defaultRepo !== undefined;
@@ -1927,8 +2009,25 @@ function deriveDocumentId(key) {
1927
2009
  const bytes = digest.slice(0, 16);
1928
2010
  return interpretAsDocumentId(bytes);
1929
2011
  }
2012
+ var docIdResolver;
2013
+ function registerDocIdResolver(resolver) {
2014
+ docIdResolver = resolver;
2015
+ }
2016
+ function getDocIdResolver() {
2017
+ return docIdResolver;
2018
+ }
2019
+ function resolveDocumentId(key) {
2020
+ return docIdResolver?.(key) ?? deriveDocumentId(key);
2021
+ }
2022
+ var redirectDetector;
2023
+ function registerRedirectDetector(detector) {
2024
+ redirectDetector = detector;
2025
+ }
2026
+ function getRedirectDetector() {
2027
+ return redirectDetector;
2028
+ }
1930
2029
  function buildHandleFactory(repo, key, initialDoc) {
1931
- const documentId = deriveDocumentId(key);
2030
+ const documentId = resolveDocumentId(key);
1932
2031
  return async () => {
1933
2032
  lazyInvocations++;
1934
2033
  let exitReason = "threw";
@@ -1937,23 +2036,22 @@ function buildHandleFactory(repo, key, initialDoc) {
1937
2036
  const cached = repo.handles[documentId];
1938
2037
  lazyReachedRepo++;
1939
2038
  const docIdString = documentId;
2039
+ let handle;
1940
2040
  if (cached) {
1941
2041
  await withStorageTimeout("whenReady", docIdString, cached.whenReady(["ready", "unavailable"]));
1942
2042
  if (cached.state === "ready") {
1943
2043
  exitReason = "returned-cached";
1944
- return cached;
2044
+ handle = cached;
2045
+ } else {
2046
+ handle = await loadOrSeed(repo, documentId, initialDoc, docIdString, (r) => {
2047
+ exitReason = r;
2048
+ });
1945
2049
  }
2050
+ } else {
2051
+ handle = await loadOrSeed(repo, documentId, initialDoc, docIdString, (r) => {
2052
+ exitReason = r;
2053
+ });
1946
2054
  }
1947
- const loadPromise = repo.storageSubsystem?.loadDoc(documentId);
1948
- const stored = loadPromise ? await withStorageTimeout("loadDoc", docIdString, loadPromise) : undefined;
1949
- if (stored) {
1950
- exitReason = "loaded-from-storage";
1951
- return repo.find(documentId, { allowableStates: ["ready"] });
1952
- }
1953
- const seeded = Automerge.save(Automerge.from(initialDoc));
1954
- const handle = repo.import(seeded, { docId: documentId });
1955
- handle.doneLoading();
1956
- exitReason = "seeded-and-imported";
1957
2055
  return handle;
1958
2056
  } catch (err) {
1959
2057
  errorMessage = err instanceof Error ? err.message : String(err);
@@ -1973,6 +2071,19 @@ function buildHandleFactory(repo, key, initialDoc) {
1973
2071
  }
1974
2072
  };
1975
2073
  }
2074
+ async function loadOrSeed(repo, documentId, initialDoc, docIdString, setExitReason) {
2075
+ const loadPromise = repo.storageSubsystem?.loadDoc(documentId);
2076
+ const stored = loadPromise ? await withStorageTimeout("loadDoc", docIdString, loadPromise) : undefined;
2077
+ if (stored) {
2078
+ setExitReason("loaded-from-storage");
2079
+ return repo.find(documentId, { allowableStates: ["ready"] });
2080
+ }
2081
+ const seeded = Automerge.save(Automerge.from(initialDoc));
2082
+ const handle = repo.import(seeded, { docId: documentId });
2083
+ handle.doneLoading();
2084
+ setExitReason("seeded-and-imported");
2085
+ return handle;
2086
+ }
1976
2087
  function attachLoadedRejectionSink(primitive) {
1977
2088
  primitive.loaded.catch((err) => {
1978
2089
  recordLoadedRejection(err);
@@ -1986,11 +2097,35 @@ function $meshState(key, initialValue, options = {}) {
1986
2097
  primitive: "meshState",
1987
2098
  initialValue,
1988
2099
  getHandle: buildHandleFactory(repo, key, initialValue),
2100
+ resolveRedirect: buildRedirectResolver(repo),
1989
2101
  schemaVersion: options.schemaVersion,
1990
2102
  migrations: options.migrations,
1991
2103
  access: options.access
1992
2104
  }));
1993
2105
  }
2106
+ function buildRedirectResolver(repo) {
2107
+ if (!redirectDetector) {}
2108
+ return async (_handle, doc) => {
2109
+ const detector = redirectDetector;
2110
+ if (!detector)
2111
+ return;
2112
+ let nextId;
2113
+ try {
2114
+ nextId = detector(doc);
2115
+ } catch {
2116
+ return;
2117
+ }
2118
+ if (!nextId)
2119
+ return;
2120
+ try {
2121
+ return await repo.find(nextId, {
2122
+ allowableStates: ["ready", "unavailable"]
2123
+ });
2124
+ } catch {
2125
+ return;
2126
+ }
2127
+ };
2128
+ }
1994
2129
  function $meshText(key, initialValue, options = {}) {
1995
2130
  const repo = resolveRepo(options.repo);
1996
2131
  return attachLoadedRejectionSink($crdtText(key, initialValue, {
@@ -3604,25 +3739,31 @@ export {
3604
3739
  serialisePairingToken,
3605
3740
  serialiseKeyring,
3606
3741
  revokePeerLocally,
3742
+ resolveDocumentId,
3607
3743
  resetMeshState,
3744
+ registerRedirectDetector,
3745
+ registerDocIdResolver,
3608
3746
  parsePairingToken,
3609
3747
  memoryKeyringStorage,
3610
3748
  isPairingTokenExpired,
3611
3749
  isMeshStateConfigured,
3612
3750
  isBlobRef,
3613
3751
  getStorageOpenError,
3752
+ getRedirectDetector,
3614
3753
  getMeshStateModuleId,
3615
3754
  getLazyWrappers,
3616
3755
  getLazyReachedRepo,
3617
3756
  getLazyInvocations,
3618
3757
  getLastLoadedRejection,
3619
3758
  getLastConfiguredRepoPeerId,
3759
+ getDocIdResolver,
3620
3760
  generateSigningKeyPair,
3621
3761
  generateDocumentKey,
3622
3762
  encrypt,
3623
3763
  encodeRevocation,
3624
3764
  encodePairingToken,
3625
3765
  deserialiseKeyring,
3766
+ deriveDocumentId,
3626
3767
  decryptOrThrow,
3627
3768
  decrypt,
3628
3769
  decodeRevocation,
@@ -3666,4 +3807,4 @@ export {
3666
3807
  $meshCounter
3667
3808
  };
3668
3809
 
3669
- //# debugId=EFE12C3DC2653D4B64756E2164756E21
3810
+ //# debugId=32FAEA2DE6283FD764756E2164756E21