@abraca/dabra 0.6.0 → 0.7.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.
@@ -26,13 +26,23 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  }) : target, mod));
27
27
 
28
28
  //#endregion
29
- let _abraca_dabra_common = require("@abraca/dabra-common");
30
29
  let yjs = require("yjs");
31
30
  yjs = __toESM(yjs);
32
31
  let _lifeomic_attempt = require("@lifeomic/attempt");
33
32
  let _noble_ed25519 = require("@noble/ed25519");
34
33
  _noble_ed25519 = __toESM(_noble_ed25519);
35
34
 
35
+ //#region packages/provider/src/awarenessStatesToArray.ts
36
+ const awarenessStatesToArray = (states) => {
37
+ return Array.from(states.entries()).map(([key, value]) => {
38
+ return {
39
+ clientId: key,
40
+ ...value
41
+ };
42
+ });
43
+ };
44
+
45
+ //#endregion
36
46
  //#region node_modules/lib0/math.js
37
47
  /**
38
48
  * Common Math expressions.
@@ -1054,6 +1064,37 @@ var EventEmitter = class {
1054
1064
  }
1055
1065
  };
1056
1066
 
1067
+ //#endregion
1068
+ //#region packages/provider/src/types.ts
1069
+ /**
1070
+ * State of the WebSocket connection.
1071
+ * https://developer.mozilla.org/de/docs/Web/API/WebSocket/readyState
1072
+ */
1073
+ let WsReadyStates = /* @__PURE__ */ function(WsReadyStates) {
1074
+ WsReadyStates[WsReadyStates["Connecting"] = 0] = "Connecting";
1075
+ WsReadyStates[WsReadyStates["Open"] = 1] = "Open";
1076
+ WsReadyStates[WsReadyStates["Closing"] = 2] = "Closing";
1077
+ WsReadyStates[WsReadyStates["Closed"] = 3] = "Closed";
1078
+ return WsReadyStates;
1079
+ }({});
1080
+ let MessageType = /* @__PURE__ */ function(MessageType) {
1081
+ MessageType[MessageType["Sync"] = 0] = "Sync";
1082
+ MessageType[MessageType["Awareness"] = 1] = "Awareness";
1083
+ MessageType[MessageType["Auth"] = 2] = "Auth";
1084
+ MessageType[MessageType["QueryAwareness"] = 3] = "QueryAwareness";
1085
+ MessageType[MessageType["Subdoc"] = 4] = "Subdoc";
1086
+ MessageType[MessageType["Stateless"] = 5] = "Stateless";
1087
+ MessageType[MessageType["CLOSE"] = 7] = "CLOSE";
1088
+ MessageType[MessageType["SyncStatus"] = 8] = "SyncStatus";
1089
+ return MessageType;
1090
+ }({});
1091
+ let WebSocketStatus = /* @__PURE__ */ function(WebSocketStatus) {
1092
+ WebSocketStatus["Connecting"] = "connecting";
1093
+ WebSocketStatus["Connected"] = "connected";
1094
+ WebSocketStatus["Disconnected"] = "disconnected";
1095
+ return WebSocketStatus;
1096
+ }({});
1097
+
1057
1098
  //#endregion
1058
1099
  //#region packages/provider/src/IncomingMessage.ts
1059
1100
  var IncomingMessage = class {
@@ -1088,26 +1129,6 @@ var IncomingMessage = class {
1088
1129
  }
1089
1130
  };
1090
1131
 
1091
- //#endregion
1092
- //#region packages/provider/src/types.ts
1093
- let MessageType = /* @__PURE__ */ function(MessageType) {
1094
- MessageType[MessageType["Sync"] = 0] = "Sync";
1095
- MessageType[MessageType["Awareness"] = 1] = "Awareness";
1096
- MessageType[MessageType["Auth"] = 2] = "Auth";
1097
- MessageType[MessageType["QueryAwareness"] = 3] = "QueryAwareness";
1098
- MessageType[MessageType["Subdoc"] = 4] = "Subdoc";
1099
- MessageType[MessageType["Stateless"] = 5] = "Stateless";
1100
- MessageType[MessageType["CLOSE"] = 7] = "CLOSE";
1101
- MessageType[MessageType["SyncStatus"] = 8] = "SyncStatus";
1102
- return MessageType;
1103
- }({});
1104
- let WebSocketStatus = /* @__PURE__ */ function(WebSocketStatus) {
1105
- WebSocketStatus["Connecting"] = "connecting";
1106
- WebSocketStatus["Connected"] = "connected";
1107
- WebSocketStatus["Disconnected"] = "disconnected";
1108
- return WebSocketStatus;
1109
- }({});
1110
-
1111
1132
  //#endregion
1112
1133
  //#region packages/provider/src/OutgoingMessage.ts
1113
1134
  var OutgoingMessage = class {
@@ -1138,8 +1159,8 @@ var CloseMessage = class extends OutgoingMessage {
1138
1159
  };
1139
1160
 
1140
1161
  //#endregion
1141
- //#region packages/provider/src/HocuspocusProviderWebsocket.ts
1142
- var HocuspocusProviderWebsocket = class extends EventEmitter {
1162
+ //#region packages/provider/src/AbracadabraWS.ts
1163
+ var AbracadabraWS = class extends EventEmitter {
1143
1164
  constructor(configuration) {
1144
1165
  super();
1145
1166
  this.messageQueue = [];
@@ -1372,7 +1393,7 @@ var HocuspocusProviderWebsocket = class extends EventEmitter {
1372
1393
  }
1373
1394
  }
1374
1395
  send(message) {
1375
- if (this.webSocket?.readyState === _abraca_dabra_common.WsReadyStates.Open) this.webSocket.send(message);
1396
+ if (this.webSocket?.readyState === WsReadyStates.Open) this.webSocket.send(message);
1376
1397
  else this.messageQueue.push(message);
1377
1398
  }
1378
1399
  onClose({ event }) {
@@ -1400,6 +1421,46 @@ var HocuspocusProviderWebsocket = class extends EventEmitter {
1400
1421
  this.cleanupWebSocket();
1401
1422
  }
1402
1423
  };
1424
+ /** @deprecated Use AbracadabraWS */
1425
+ const HocuspocusProviderWebsocket = AbracadabraWS;
1426
+
1427
+ //#endregion
1428
+ //#region packages/provider/src/auth.ts
1429
+ let AuthMessageType = /* @__PURE__ */ function(AuthMessageType) {
1430
+ AuthMessageType[AuthMessageType["Token"] = 0] = "Token";
1431
+ AuthMessageType[AuthMessageType["PermissionDenied"] = 1] = "PermissionDenied";
1432
+ AuthMessageType[AuthMessageType["Authenticated"] = 2] = "Authenticated";
1433
+ return AuthMessageType;
1434
+ }({});
1435
+ const writeAuthentication = (encoder, auth) => {
1436
+ writeVarUint(encoder, AuthMessageType.Token);
1437
+ writeVarString(encoder, auth);
1438
+ };
1439
+ const writePermissionDenied = (encoder, reason) => {
1440
+ writeVarUint(encoder, AuthMessageType.PermissionDenied);
1441
+ writeVarString(encoder, reason);
1442
+ };
1443
+ const writeAuthenticated = (encoder, scope) => {
1444
+ writeVarUint(encoder, AuthMessageType.Authenticated);
1445
+ writeVarString(encoder, scope);
1446
+ };
1447
+ const writeTokenSyncRequest = (encoder) => {
1448
+ writeVarUint(encoder, AuthMessageType.Token);
1449
+ };
1450
+ const readAuthMessage = (decoder, sendToken, permissionDeniedHandler, authenticatedHandler) => {
1451
+ switch (readVarUint(decoder)) {
1452
+ case AuthMessageType.Token:
1453
+ sendToken();
1454
+ break;
1455
+ case AuthMessageType.PermissionDenied:
1456
+ permissionDeniedHandler(readVarString(decoder));
1457
+ break;
1458
+ case AuthMessageType.Authenticated:
1459
+ authenticatedHandler(readVarString(decoder));
1460
+ break;
1461
+ default:
1462
+ }
1463
+ };
1403
1464
 
1404
1465
  //#endregion
1405
1466
  //#region node_modules/y-protocols/sync.js
@@ -1581,7 +1642,7 @@ var MessageReceiver = class {
1581
1642
  }
1582
1643
  applyAuthMessage(provider) {
1583
1644
  const { message } = this;
1584
- (0, _abraca_dabra_common.readAuthMessage)(message.decoder, provider.sendToken.bind(provider), provider.permissionDeniedHandler.bind(provider), provider.authenticatedHandler.bind(provider));
1645
+ readAuthMessage(message.decoder, provider.sendToken.bind(provider), provider.permissionDeniedHandler.bind(provider), provider.authenticatedHandler.bind(provider));
1585
1646
  }
1586
1647
  applyQueryAwarenessMessage(provider) {
1587
1648
  if (!provider.awareness) return;
@@ -1618,7 +1679,7 @@ var AuthenticationMessage = class extends OutgoingMessage {
1618
1679
  if (typeof args.token === "undefined") throw new Error("The authentication message requires `token` as an argument.");
1619
1680
  writeVarString(this.encoder, args.documentName);
1620
1681
  writeVarUint(this.encoder, this.type);
1621
- (0, _abraca_dabra_common.writeAuthentication)(this.encoder, args.token);
1682
+ writeAuthentication(this.encoder, args.token);
1622
1683
  return this.encoder;
1623
1684
  }
1624
1685
  };
@@ -1694,14 +1755,14 @@ var UpdateMessage = class extends OutgoingMessage {
1694
1755
  };
1695
1756
 
1696
1757
  //#endregion
1697
- //#region packages/provider/src/HocuspocusProvider.ts
1758
+ //#region packages/provider/src/AbracadabraBaseProvider.ts
1698
1759
  var AwarenessError = class extends Error {
1699
1760
  constructor(..._args) {
1700
1761
  super(..._args);
1701
1762
  this.code = 1001;
1702
1763
  }
1703
1764
  };
1704
- var HocuspocusProvider = class extends EventEmitter {
1765
+ var AbracadabraBaseProvider = class extends EventEmitter {
1705
1766
  constructor(configuration) {
1706
1767
  super();
1707
1768
  this.configuration = {
@@ -1761,10 +1822,10 @@ var HocuspocusProvider = class extends EventEmitter {
1761
1822
  this.on("authenticationFailed", this.configuration.onAuthenticationFailed);
1762
1823
  this.on("rateLimited", this.configuration.onRateLimited);
1763
1824
  this.awareness?.on("update", () => {
1764
- this.emit("awarenessUpdate", { states: (0, _abraca_dabra_common.awarenessStatesToArray)(this.awareness.getStates()) });
1825
+ this.emit("awarenessUpdate", { states: awarenessStatesToArray(this.awareness.getStates()) });
1765
1826
  });
1766
1827
  this.awareness?.on("change", () => {
1767
- this.emit("awarenessChange", { states: (0, _abraca_dabra_common.awarenessStatesToArray)(this.awareness.getStates()) });
1828
+ this.emit("awarenessChange", { states: awarenessStatesToArray(this.awareness.getStates()) });
1768
1829
  });
1769
1830
  this.document.on("update", this.boundDocumentUpdateHandler);
1770
1831
  this.awareness?.on("update", this.boundAwarenessUpdateHandler);
@@ -1775,7 +1836,7 @@ var HocuspocusProvider = class extends EventEmitter {
1775
1836
  setConfiguration(configuration = {}) {
1776
1837
  if (!configuration.websocketProvider) {
1777
1838
  this.manageSocket = true;
1778
- this.configuration.websocketProvider = new HocuspocusProviderWebsocket(configuration);
1839
+ this.configuration.websocketProvider = new AbracadabraWS(configuration);
1779
1840
  }
1780
1841
  this.configuration = {
1781
1842
  ...this.configuration,
@@ -1875,11 +1936,11 @@ var HocuspocusProvider = class extends EventEmitter {
1875
1936
  }
1876
1937
  async connect() {
1877
1938
  if (this.manageSocket) return this.configuration.websocketProvider.connect();
1878
- console.warn("HocuspocusProvider::connect() is deprecated and does not do anything. Please connect/disconnect on the websocketProvider, or attach/deattach providers.");
1939
+ console.warn("AbracadabraBaseProvider::connect() is deprecated and does not do anything. Please connect/disconnect on the websocketProvider, or attach/deattach providers.");
1879
1940
  }
1880
1941
  disconnect() {
1881
1942
  if (this.manageSocket) return this.configuration.websocketProvider.disconnect();
1882
- console.warn("HocuspocusProvider::disconnect() is deprecated and does not do anything. Please connect/disconnect on the websocketProvider, or attach/deattach providers.");
1943
+ console.warn("AbracadabraBaseProvider::disconnect() is deprecated and does not do anything. Please connect/disconnect on the websocketProvider, or attach/deattach providers.");
1883
1944
  }
1884
1945
  async onOpen(event) {
1885
1946
  this.isAuthenticated = false;
@@ -1988,16 +2049,18 @@ var HocuspocusProvider = class extends EventEmitter {
1988
2049
  this.awareness.setLocalStateField(key, value);
1989
2050
  }
1990
2051
  };
2052
+ /** @deprecated Use AbracadabraBaseProvider */
2053
+ const HocuspocusProvider = AbracadabraBaseProvider;
1991
2054
 
1992
2055
  //#endregion
1993
2056
  //#region packages/provider/src/OfflineStore.ts
1994
- const DB_VERSION = 2;
1995
- function idbAvailable() {
2057
+ const DB_VERSION$3 = 2;
2058
+ function idbAvailable$3() {
1996
2059
  return typeof globalThis !== "undefined" && "indexedDB" in globalThis;
1997
2060
  }
1998
- function openDb$1(storeKey) {
2061
+ function openDb$4(storeKey) {
1999
2062
  return new Promise((resolve, reject) => {
2000
- const req = globalThis.indexedDB.open(`abracadabra:${storeKey}`, DB_VERSION);
2063
+ const req = globalThis.indexedDB.open(`abracadabra:${storeKey}`, DB_VERSION$3);
2001
2064
  req.onupgradeneeded = (event) => {
2002
2065
  const db = event.target.result;
2003
2066
  if (!db.objectStoreNames.contains("updates")) db.createObjectStore("updates", { autoIncrement: true });
@@ -2009,7 +2072,7 @@ function openDb$1(storeKey) {
2009
2072
  req.onerror = () => reject(req.error);
2010
2073
  });
2011
2074
  }
2012
- function txPromise(store, request) {
2075
+ function txPromise$2(store, request) {
2013
2076
  return new Promise((resolve, reject) => {
2014
2077
  request.onsuccess = () => resolve(request.result);
2015
2078
  request.onerror = () => reject(request.error);
@@ -2028,8 +2091,8 @@ var OfflineStore = class {
2028
2091
  this.storeKey = serverOrigin ? `${serverOrigin}/${docId}` : docId;
2029
2092
  }
2030
2093
  getDb() {
2031
- if (!idbAvailable()) return Promise.resolve(null);
2032
- if (!this.dbPromise) this.dbPromise = openDb$1(this.storeKey).catch(() => null).then((db) => {
2094
+ if (!idbAvailable$3()) return Promise.resolve(null);
2095
+ if (!this.dbPromise) this.dbPromise = openDb$4(this.storeKey).catch(() => null).then((db) => {
2033
2096
  this.db = db;
2034
2097
  return db;
2035
2098
  });
@@ -2039,7 +2102,7 @@ var OfflineStore = class {
2039
2102
  const db = await this.getDb();
2040
2103
  if (!db) return;
2041
2104
  const store = db.transaction("updates", "readwrite").objectStore("updates");
2042
- await txPromise(store, store.add(update));
2105
+ await txPromise$2(store, store.add(update));
2043
2106
  }
2044
2107
  async getPendingUpdates() {
2045
2108
  const db = await this.getDb();
@@ -2054,7 +2117,7 @@ var OfflineStore = class {
2054
2117
  const db = await this.getDb();
2055
2118
  if (!db) return;
2056
2119
  const tx = db.transaction("updates", "readwrite");
2057
- await txPromise(tx.objectStore("updates"), tx.objectStore("updates").clear());
2120
+ await txPromise$2(tx.objectStore("updates"), tx.objectStore("updates").clear());
2058
2121
  }
2059
2122
  /**
2060
2123
  * Persist a full Y.js state snapshot (Y.encodeStateAsUpdate output).
@@ -2064,7 +2127,7 @@ var OfflineStore = class {
2064
2127
  const db = await this.getDb();
2065
2128
  if (!db) return;
2066
2129
  const tx = db.transaction("doc_state", "readwrite");
2067
- await txPromise(tx.objectStore("doc_state"), tx.objectStore("doc_state").put(snapshot, "snapshot"));
2130
+ await txPromise$2(tx.objectStore("doc_state"), tx.objectStore("doc_state").put(snapshot, "snapshot"));
2068
2131
  }
2069
2132
  /**
2070
2133
  * Retrieve the stored full document snapshot, or null if none exists.
@@ -2073,37 +2136,37 @@ var OfflineStore = class {
2073
2136
  const db = await this.getDb();
2074
2137
  if (!db) return null;
2075
2138
  const tx = db.transaction("doc_state", "readonly");
2076
- return await txPromise(tx.objectStore("doc_state"), tx.objectStore("doc_state").get("snapshot")) ?? null;
2139
+ return await txPromise$2(tx.objectStore("doc_state"), tx.objectStore("doc_state").get("snapshot")) ?? null;
2077
2140
  }
2078
2141
  async getStateVector() {
2079
2142
  const db = await this.getDb();
2080
2143
  if (!db) return null;
2081
2144
  const tx = db.transaction("meta", "readonly");
2082
- return await txPromise(tx.objectStore("meta"), tx.objectStore("meta").get("sv")) ?? null;
2145
+ return await txPromise$2(tx.objectStore("meta"), tx.objectStore("meta").get("sv")) ?? null;
2083
2146
  }
2084
2147
  async saveStateVector(sv) {
2085
2148
  const db = await this.getDb();
2086
2149
  if (!db) return;
2087
2150
  const tx = db.transaction("meta", "readwrite");
2088
- await txPromise(tx.objectStore("meta"), tx.objectStore("meta").put(sv, "sv"));
2151
+ await txPromise$2(tx.objectStore("meta"), tx.objectStore("meta").put(sv, "sv"));
2089
2152
  }
2090
2153
  async getPermissionSnapshot() {
2091
2154
  const db = await this.getDb();
2092
2155
  if (!db) return null;
2093
2156
  const tx = db.transaction("meta", "readonly");
2094
- return await txPromise(tx.objectStore("meta"), tx.objectStore("meta").get("role")) ?? null;
2157
+ return await txPromise$2(tx.objectStore("meta"), tx.objectStore("meta").get("role")) ?? null;
2095
2158
  }
2096
2159
  async savePermissionSnapshot(role) {
2097
2160
  const db = await this.getDb();
2098
2161
  if (!db) return;
2099
2162
  const tx = db.transaction("meta", "readwrite");
2100
- await txPromise(tx.objectStore("meta"), tx.objectStore("meta").put(role, "role"));
2163
+ await txPromise$2(tx.objectStore("meta"), tx.objectStore("meta").put(role, "role"));
2101
2164
  }
2102
2165
  async queueSubdoc(entry) {
2103
2166
  const db = await this.getDb();
2104
2167
  if (!db) return;
2105
2168
  const tx = db.transaction("subdoc_queue", "readwrite");
2106
- await txPromise(tx.objectStore("subdoc_queue"), tx.objectStore("subdoc_queue").put(entry));
2169
+ await txPromise$2(tx.objectStore("subdoc_queue"), tx.objectStore("subdoc_queue").put(entry));
2107
2170
  }
2108
2171
  async getPendingSubdocs() {
2109
2172
  const db = await this.getDb();
@@ -2118,7 +2181,7 @@ var OfflineStore = class {
2118
2181
  const db = await this.getDb();
2119
2182
  if (!db) return;
2120
2183
  const tx = db.transaction("subdoc_queue", "readwrite");
2121
- await txPromise(tx.objectStore("subdoc_queue"), tx.objectStore("subdoc_queue").delete(childId));
2184
+ await txPromise$2(tx.objectStore("subdoc_queue"), tx.objectStore("subdoc_queue").delete(childId));
2122
2185
  }
2123
2186
  destroy() {
2124
2187
  this.db?.close();
@@ -2161,7 +2224,7 @@ function isValidDocId(id) {
2161
2224
  return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id);
2162
2225
  }
2163
2226
  /**
2164
- * AbracadabraProvider extends HocuspocusProvider with:
2227
+ * AbracadabraProvider extends AbracadabraBaseProvider with:
2165
2228
  *
2166
2229
  * 1. Subdocument lifecycle – intercepts Y.Doc subdoc events and syncs them
2167
2230
  * with the server via MSG_SUBDOC (4) frames. Child documents get their
@@ -2181,7 +2244,7 @@ function isValidDocId(id) {
2181
2244
  * can gate write operations without a network round-trip. Role is
2182
2245
  * refreshed from the server on every reconnect.
2183
2246
  */
2184
- var AbracadabraProvider = class AbracadabraProvider extends HocuspocusProvider {
2247
+ var AbracadabraProvider = class AbracadabraProvider extends AbracadabraBaseProvider {
2185
2248
  constructor(configuration) {
2186
2249
  const resolved = { ...configuration };
2187
2250
  const client = configuration.client ?? null;
@@ -2462,6 +2525,7 @@ var AbracadabraClient = class {
2462
2525
  this.persistAuth = config.persistAuth ?? typeof localStorage !== "undefined";
2463
2526
  this.storageKey = config.storageKey ?? "abracadabra:auth";
2464
2527
  this._fetch = config.fetch ?? globalThis.fetch.bind(globalThis);
2528
+ this.cache = config.cache ?? null;
2465
2529
  this._token = config.token ?? this.loadPersistedToken() ?? null;
2466
2530
  }
2467
2531
  get token() {
@@ -2559,7 +2623,13 @@ var AbracadabraClient = class {
2559
2623
  }
2560
2624
  /** Get the current user's profile. */
2561
2625
  async getMe() {
2562
- return this.request("GET", "/users/me");
2626
+ if (this.cache) {
2627
+ const cached = await this.cache.getCurrentProfile();
2628
+ if (cached) return cached;
2629
+ }
2630
+ const profile = await this.request("GET", "/users/me");
2631
+ if (this.cache) await this.cache.setCurrentProfile(profile).catch(() => null);
2632
+ return profile;
2563
2633
  }
2564
2634
  /** Update the current user's display name. */
2565
2635
  async updateMe(opts) {
@@ -2571,15 +2641,28 @@ var AbracadabraClient = class {
2571
2641
  }
2572
2642
  /** Get document metadata. */
2573
2643
  async getDoc(docId) {
2574
- return this.request("GET", `/docs/${encodeURIComponent(docId)}`);
2644
+ if (this.cache) {
2645
+ const cached = await this.cache.getDoc(docId);
2646
+ if (cached) return cached;
2647
+ }
2648
+ const meta = await this.request("GET", `/docs/${encodeURIComponent(docId)}`);
2649
+ if (this.cache) await this.cache.setDoc(meta).catch(() => null);
2650
+ return meta;
2575
2651
  }
2576
2652
  /** Delete a document (requires Owner role). Cascades to children and uploads. */
2577
2653
  async deleteDoc(docId) {
2578
2654
  await this.request("DELETE", `/docs/${encodeURIComponent(docId)}`);
2655
+ if (this.cache) await this.cache.invalidateDoc(docId).catch(() => null);
2579
2656
  }
2580
2657
  /** List immediate child documents. */
2581
2658
  async listChildren(docId) {
2582
- return (await this.request("GET", `/docs/${encodeURIComponent(docId)}/children`)).children;
2659
+ if (this.cache) {
2660
+ const cached = await this.cache.getChildren(docId);
2661
+ if (cached) return cached;
2662
+ }
2663
+ const res = await this.request("GET", `/docs/${encodeURIComponent(docId)}/children`);
2664
+ if (this.cache) await this.cache.setChildren(docId, res.children).catch(() => null);
2665
+ return res.children;
2583
2666
  }
2584
2667
  /** Create a child document under a parent (requires write permission). */
2585
2668
  async createChild(docId, opts) {
@@ -2587,7 +2670,13 @@ var AbracadabraClient = class {
2587
2670
  }
2588
2671
  /** List all permissions for a document (requires read access). */
2589
2672
  async listPermissions(docId) {
2590
- return (await this.request("GET", `/docs/${encodeURIComponent(docId)}/permissions`)).permissions;
2673
+ if (this.cache) {
2674
+ const cached = await this.cache.getPermissions(docId);
2675
+ if (cached) return cached;
2676
+ }
2677
+ const res = await this.request("GET", `/docs/${encodeURIComponent(docId)}/permissions`);
2678
+ if (this.cache) await this.cache.setPermissions(docId, res.permissions).catch(() => null);
2679
+ return res.permissions;
2591
2680
  }
2592
2681
  /** Grant or change a user's role on a document (requires Owner). */
2593
2682
  async setPermission(docId, opts) {
@@ -2609,11 +2698,19 @@ var AbracadabraClient = class {
2609
2698
  body: formData
2610
2699
  });
2611
2700
  if (!res.ok) throw await this.toError(res);
2612
- return res.json();
2701
+ const meta = await res.json();
2702
+ if (this.cache) await this.cache.invalidateUploads(docId).catch(() => null);
2703
+ return meta;
2613
2704
  }
2614
2705
  /** List all uploads for a document. */
2615
2706
  async listUploads(docId) {
2616
- return (await this.request("GET", `/docs/${encodeURIComponent(docId)}/uploads`)).uploads;
2707
+ if (this.cache) {
2708
+ const cached = await this.cache.getUploads(docId);
2709
+ if (cached) return cached;
2710
+ }
2711
+ const res = await this.request("GET", `/docs/${encodeURIComponent(docId)}/uploads`);
2712
+ if (this.cache) await this.cache.setUploads(docId, res.uploads).catch(() => null);
2713
+ return res.uploads;
2617
2714
  }
2618
2715
  /** Download an upload as a Blob. */
2619
2716
  async getUpload(docId, uploadId) {
@@ -2629,11 +2726,19 @@ var AbracadabraClient = class {
2629
2726
  /** Delete an upload (requires uploader or document Owner). */
2630
2727
  async deleteUpload(docId, uploadId) {
2631
2728
  await this.request("DELETE", `/docs/${encodeURIComponent(docId)}/uploads/${encodeURIComponent(uploadId)}`);
2729
+ if (this.cache) await this.cache.invalidateUploads(docId).catch(() => null);
2632
2730
  }
2633
2731
  /** Health check — no auth required. */
2634
2732
  async health() {
2635
2733
  return this.request("GET", "/health", { auth: false });
2636
2734
  }
2735
+ /**
2736
+ * Fetch server metadata including the optional `index_doc_id` entry point.
2737
+ * No auth required.
2738
+ */
2739
+ async serverInfo() {
2740
+ return this.request("GET", "/info", { auth: false });
2741
+ }
2637
2742
  async request(method, path, opts) {
2638
2743
  const auth = opts?.auth ?? true;
2639
2744
  const headers = {};
@@ -2681,6 +2786,49 @@ var AbracadabraClient = class {
2681
2786
  }
2682
2787
  };
2683
2788
 
2789
+ //#endregion
2790
+ //#region packages/provider/src/CloseEvents.ts
2791
+ /**
2792
+ * The server is terminating the connection because a data frame was received
2793
+ * that is too large.
2794
+ * See: https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code
2795
+ */
2796
+ const MessageTooBig = {
2797
+ code: 1009,
2798
+ reason: "Message Too Big"
2799
+ };
2800
+ /**
2801
+ * The server successfully processed the request, asks that the requester reset
2802
+ * its document view, and is not returning any content.
2803
+ */
2804
+ const ResetConnection = {
2805
+ code: 4205,
2806
+ reason: "Reset Connection"
2807
+ };
2808
+ /**
2809
+ * Similar to Forbidden, but specifically for use when authentication is required and has
2810
+ * failed or has not yet been provided.
2811
+ */
2812
+ const Unauthorized = {
2813
+ code: 4401,
2814
+ reason: "Unauthorized"
2815
+ };
2816
+ /**
2817
+ * The request contained valid data and was understood by the server, but the server
2818
+ * is refusing action.
2819
+ */
2820
+ const Forbidden = {
2821
+ code: 4403,
2822
+ reason: "Forbidden"
2823
+ };
2824
+ /**
2825
+ * The server timed out waiting for the request.
2826
+ */
2827
+ const ConnectionTimeout = {
2828
+ code: 4408,
2829
+ reason: "Connection Timeout"
2830
+ };
2831
+
2684
2832
  //#endregion
2685
2833
  //#region node_modules/@noble/hashes/esm/utils.js
2686
2834
  /** Checks if something is Uint8Array. Be careful: nodejs Buffer will return true. */
@@ -3358,7 +3506,7 @@ const DB_NAME = "abracadabra:identity";
3358
3506
  const STORE_NAME = "identity";
3359
3507
  const RECORD_KEY = "current";
3360
3508
  const HKDF_INFO = new TextEncoder().encode("abracadabra-identity-v1");
3361
- function openDb() {
3509
+ function openDb$3() {
3362
3510
  return new Promise((resolve, reject) => {
3363
3511
  const req = indexedDB.open(DB_NAME, 1);
3364
3512
  req.onupgradeneeded = () => {
@@ -3441,7 +3589,7 @@ var CryptoIdentityKeystore = class {
3441
3589
  name: "AES-GCM",
3442
3590
  iv
3443
3591
  }, aesKey, privateKey);
3444
- const db = await openDb();
3592
+ const db = await openDb$3();
3445
3593
  await dbPut(db, {
3446
3594
  username,
3447
3595
  publicKey: toBase64url(publicKey),
@@ -3464,7 +3612,7 @@ var CryptoIdentityKeystore = class {
3464
3612
  * @returns base64url-encoded Ed25519 signature (64 bytes).
3465
3613
  */
3466
3614
  async sign(challengeB64) {
3467
- const db = await openDb();
3615
+ const db = await openDb$3();
3468
3616
  const stored = await dbGet(db);
3469
3617
  db.close();
3470
3618
  if (!stored) throw new Error("No identity stored. Call register() first.");
@@ -3493,7 +3641,7 @@ var CryptoIdentityKeystore = class {
3493
3641
  }
3494
3642
  /** Returns the stored base64url public key, or null if no identity exists. */
3495
3643
  async getPublicKey() {
3496
- const db = await openDb();
3644
+ const db = await openDb$3();
3497
3645
  const stored = await dbGet(db);
3498
3646
  db.close();
3499
3647
  return stored?.publicKey ?? null;
@@ -3506,35 +3654,551 @@ var CryptoIdentityKeystore = class {
3506
3654
  * a real display name via PATCH /users/me.
3507
3655
  */
3508
3656
  async getUsername() {
3509
- const db = await openDb();
3657
+ const db = await openDb$3();
3510
3658
  const stored = await dbGet(db);
3511
3659
  db.close();
3512
3660
  return stored?.username ?? null;
3513
3661
  }
3514
3662
  /** Returns true if an identity is stored in IndexedDB. */
3515
3663
  async hasIdentity() {
3516
- const db = await openDb();
3664
+ const db = await openDb$3();
3517
3665
  const stored = await dbGet(db);
3518
3666
  db.close();
3519
3667
  return stored !== void 0;
3520
3668
  }
3521
3669
  /** Remove the stored identity from IndexedDB. */
3522
3670
  async clear() {
3523
- const db = await openDb();
3671
+ const db = await openDb$3();
3524
3672
  await dbDelete(db);
3525
3673
  db.close();
3526
3674
  }
3527
3675
  };
3528
3676
 
3529
3677
  //#endregion
3678
+ //#region packages/provider/src/DocumentCache.ts
3679
+ const DB_VERSION$2 = 1;
3680
+ const DEFAULT_TTL_MS = 300 * 1e3;
3681
+ function idbAvailable$2() {
3682
+ return typeof globalThis !== "undefined" && "indexedDB" in globalThis;
3683
+ }
3684
+ function openDb$2(origin) {
3685
+ return new Promise((resolve, reject) => {
3686
+ const req = globalThis.indexedDB.open(`abracadabra:meta-cache:${origin}`, DB_VERSION$2);
3687
+ req.onupgradeneeded = (event) => {
3688
+ const db = event.target.result;
3689
+ for (const name of [
3690
+ "doc_meta",
3691
+ "children",
3692
+ "user_profile",
3693
+ "permissions",
3694
+ "uploads"
3695
+ ]) if (!db.objectStoreNames.contains(name)) db.createObjectStore(name);
3696
+ };
3697
+ req.onsuccess = () => resolve(req.result);
3698
+ req.onerror = () => reject(req.error);
3699
+ });
3700
+ }
3701
+ function txPromise$1(store, request) {
3702
+ return new Promise((resolve, reject) => {
3703
+ request.onsuccess = () => resolve(request.result);
3704
+ request.onerror = () => reject(request.error);
3705
+ });
3706
+ }
3707
+ var DocumentCache = class {
3708
+ constructor(serverOrigin, opts) {
3709
+ this.dbPromise = null;
3710
+ this.db = null;
3711
+ this.origin = serverOrigin;
3712
+ this.ttlMs = opts?.ttlMs ?? DEFAULT_TTL_MS;
3713
+ }
3714
+ getDb() {
3715
+ if (!idbAvailable$2()) return Promise.resolve(null);
3716
+ if (!this.dbPromise) this.dbPromise = openDb$2(this.origin).catch(() => null).then((db) => {
3717
+ this.db = db;
3718
+ return db;
3719
+ });
3720
+ return this.dbPromise;
3721
+ }
3722
+ isExpired(cachedAt) {
3723
+ return Date.now() - cachedAt > this.ttlMs;
3724
+ }
3725
+ async getWithTtl(storeName, key) {
3726
+ const db = await this.getDb();
3727
+ if (!db) return null;
3728
+ const tx = db.transaction(storeName, "readonly");
3729
+ const result = await txPromise$1(tx.objectStore(storeName), tx.objectStore(storeName).get(key));
3730
+ if (!result) return null;
3731
+ if (this.isExpired(result.cachedAt)) {
3732
+ this.deleteKey(storeName, key).catch(() => null);
3733
+ return null;
3734
+ }
3735
+ return result.value;
3736
+ }
3737
+ async setWithTtl(storeName, key, value) {
3738
+ const db = await this.getDb();
3739
+ if (!db) return;
3740
+ const entry = {
3741
+ value,
3742
+ cachedAt: Date.now()
3743
+ };
3744
+ const tx = db.transaction(storeName, "readwrite");
3745
+ await txPromise$1(tx.objectStore(storeName), tx.objectStore(storeName).put(entry, key));
3746
+ }
3747
+ async deleteKey(storeName, key) {
3748
+ const db = await this.getDb();
3749
+ if (!db) return;
3750
+ const tx = db.transaction(storeName, "readwrite");
3751
+ await txPromise$1(tx.objectStore(storeName), tx.objectStore(storeName).delete(key));
3752
+ }
3753
+ async getDoc(docId) {
3754
+ return this.getWithTtl("doc_meta", docId);
3755
+ }
3756
+ async setDoc(meta) {
3757
+ return this.setWithTtl("doc_meta", meta.id, meta);
3758
+ }
3759
+ async invalidateDoc(docId) {
3760
+ await this.deleteKey("doc_meta", docId).catch(() => null);
3761
+ }
3762
+ async getChildren(parentId) {
3763
+ return this.getWithTtl("children", parentId);
3764
+ }
3765
+ async setChildren(parentId, items) {
3766
+ return this.setWithTtl("children", parentId, items);
3767
+ }
3768
+ async invalidateChildren(parentId) {
3769
+ await this.deleteKey("children", parentId).catch(() => null);
3770
+ }
3771
+ async getProfile(userId) {
3772
+ return this.getWithTtl("user_profile", userId);
3773
+ }
3774
+ async setProfile(profile) {
3775
+ return this.setWithTtl("user_profile", profile.id, profile);
3776
+ }
3777
+ /** Get the cached profile for the currently authenticated user. */
3778
+ async getCurrentProfile() {
3779
+ return this.getWithTtl("user_profile", "__current__");
3780
+ }
3781
+ /** Cache a profile both by its ID and as the current user. */
3782
+ async setCurrentProfile(profile) {
3783
+ await Promise.all([this.setWithTtl("user_profile", profile.id, profile), this.setWithTtl("user_profile", "__current__", profile)]);
3784
+ }
3785
+ async getPermissions(docId) {
3786
+ return this.getWithTtl("permissions", docId);
3787
+ }
3788
+ async setPermissions(docId, items) {
3789
+ return this.setWithTtl("permissions", docId, items);
3790
+ }
3791
+ async getUploads(docId) {
3792
+ return this.getWithTtl("uploads", docId);
3793
+ }
3794
+ async setUploads(docId, items) {
3795
+ return this.setWithTtl("uploads", docId, items);
3796
+ }
3797
+ async invalidateUploads(docId) {
3798
+ await this.deleteKey("uploads", docId).catch(() => null);
3799
+ }
3800
+ destroy() {
3801
+ this.db?.close();
3802
+ this.db = null;
3803
+ }
3804
+ };
3805
+
3806
+ //#endregion
3807
+ //#region packages/provider/src/SearchIndex.ts
3808
+ const DB_VERSION$1 = 1;
3809
+ function idbAvailable$1() {
3810
+ return typeof globalThis !== "undefined" && "indexedDB" in globalThis;
3811
+ }
3812
+ function openDb$1(origin) {
3813
+ return new Promise((resolve, reject) => {
3814
+ const req = globalThis.indexedDB.open(`abracadabra:search:${origin}`, DB_VERSION$1);
3815
+ req.onupgradeneeded = (event) => {
3816
+ const db = event.target.result;
3817
+ if (!db.objectStoreNames.contains("postings")) db.createObjectStore("postings");
3818
+ if (!db.objectStoreNames.contains("doc_trigrams")) db.createObjectStore("doc_trigrams");
3819
+ };
3820
+ req.onsuccess = () => resolve(req.result);
3821
+ req.onerror = () => reject(req.error);
3822
+ });
3823
+ }
3824
+ /** Extract the set of trigrams for a piece of text. */
3825
+ function extractTrigrams(text) {
3826
+ const trigrams = /* @__PURE__ */ new Set();
3827
+ const padded = ` ${text.toLowerCase()} `;
3828
+ for (let i = 0; i <= padded.length - 3; i++) trigrams.add(padded.slice(i, i + 3));
3829
+ return trigrams;
3830
+ }
3831
+ /** Merge trigrams from multiple texts into a single set. */
3832
+ function extractAllTrigrams(texts) {
3833
+ const result = /* @__PURE__ */ new Set();
3834
+ for (const t of texts) for (const trigram of extractTrigrams(t)) result.add(trigram);
3835
+ return result;
3836
+ }
3837
+ var SearchIndex = class {
3838
+ constructor(serverOrigin) {
3839
+ this.dbPromise = null;
3840
+ this.db = null;
3841
+ this.origin = serverOrigin;
3842
+ }
3843
+ getDb() {
3844
+ if (!idbAvailable$1()) return Promise.resolve(null);
3845
+ if (!this.dbPromise) this.dbPromise = openDb$1(this.origin).catch(() => null).then((db) => {
3846
+ this.db = db;
3847
+ return db;
3848
+ });
3849
+ return this.dbPromise;
3850
+ }
3851
+ /**
3852
+ * Replace the index for docId with the given texts.
3853
+ * Old trigram associations are removed before new ones are added.
3854
+ */
3855
+ async index(docId, texts) {
3856
+ const db = await this.getDb();
3857
+ if (!db) return;
3858
+ const newTrigrams = extractAllTrigrams(texts);
3859
+ return new Promise((resolve, reject) => {
3860
+ const tx = db.transaction(["postings", "doc_trigrams"], "readwrite");
3861
+ tx.oncomplete = () => resolve();
3862
+ tx.onerror = () => reject(tx.error);
3863
+ const postings = tx.objectStore("postings");
3864
+ const docTrigramsStore = tx.objectStore("doc_trigrams");
3865
+ const oldReq = docTrigramsStore.get(docId);
3866
+ oldReq.onsuccess = () => {
3867
+ const oldTrigrams = oldReq.result ?? [];
3868
+ let pending = oldTrigrams.length + newTrigrams.size + 1;
3869
+ function done() {
3870
+ pending--;
3871
+ }
3872
+ for (const trigram of oldTrigrams) {
3873
+ const req = postings.get(trigram);
3874
+ req.onsuccess = () => {
3875
+ const updated = (req.result ?? []).filter((id) => id !== docId);
3876
+ if (updated.length === 0) postings.delete(trigram);
3877
+ else postings.put(updated, trigram);
3878
+ done();
3879
+ };
3880
+ req.onerror = done;
3881
+ }
3882
+ for (const trigram of newTrigrams) {
3883
+ const req = postings.get(trigram);
3884
+ req.onsuccess = () => {
3885
+ const list = req.result ?? [];
3886
+ if (!list.includes(docId)) list.push(docId);
3887
+ postings.put(list, trigram);
3888
+ done();
3889
+ };
3890
+ req.onerror = done;
3891
+ }
3892
+ const writeReq = docTrigramsStore.put([...newTrigrams], docId);
3893
+ writeReq.onsuccess = done;
3894
+ writeReq.onerror = done;
3895
+ };
3896
+ oldReq.onerror = () => reject(oldReq.error);
3897
+ });
3898
+ }
3899
+ /** Remove all indexed content for a document. */
3900
+ async remove(docId) {
3901
+ const db = await this.getDb();
3902
+ if (!db) return;
3903
+ return new Promise((resolve, reject) => {
3904
+ const tx = db.transaction(["postings", "doc_trigrams"], "readwrite");
3905
+ tx.oncomplete = () => resolve();
3906
+ tx.onerror = () => reject(tx.error);
3907
+ const postings = tx.objectStore("postings");
3908
+ const docTrigramsStore = tx.objectStore("doc_trigrams");
3909
+ const oldReq = docTrigramsStore.get(docId);
3910
+ oldReq.onsuccess = () => {
3911
+ const oldTrigrams = oldReq.result ?? [];
3912
+ for (const trigram of oldTrigrams) {
3913
+ const req = postings.get(trigram);
3914
+ req.onsuccess = () => {
3915
+ const updated = (req.result ?? []).filter((id) => id !== docId);
3916
+ if (updated.length === 0) postings.delete(trigram);
3917
+ else postings.put(updated, trigram);
3918
+ };
3919
+ }
3920
+ docTrigramsStore.delete(docId);
3921
+ };
3922
+ oldReq.onerror = () => reject(oldReq.error);
3923
+ });
3924
+ }
3925
+ /**
3926
+ * Search for documents matching the query.
3927
+ * Returns results sorted by score (matching trigram count) descending.
3928
+ */
3929
+ async search(query, limit = 20) {
3930
+ const db = await this.getDb();
3931
+ if (!db) return [];
3932
+ const queryTrigrams = [...extractTrigrams(query)];
3933
+ if (queryTrigrams.length === 0) return [];
3934
+ return new Promise((resolve, reject) => {
3935
+ const tx = db.transaction("postings", "readonly");
3936
+ const postings = tx.objectStore("postings");
3937
+ const scores = /* @__PURE__ */ new Map();
3938
+ let remaining = queryTrigrams.length;
3939
+ for (const trigram of queryTrigrams) {
3940
+ const req = postings.get(trigram);
3941
+ req.onsuccess = () => {
3942
+ const docIds = req.result ?? [];
3943
+ for (const docId of docIds) scores.set(docId, (scores.get(docId) ?? 0) + 1);
3944
+ remaining--;
3945
+ if (remaining === 0) resolve([...scores.entries()].map(([docId, score]) => ({
3946
+ docId,
3947
+ score
3948
+ })).sort((a, b) => b.score - a.score).slice(0, limit));
3949
+ };
3950
+ req.onerror = () => {
3951
+ remaining--;
3952
+ if (remaining === 0) resolve([...scores.entries()].map(([docId, score]) => ({
3953
+ docId,
3954
+ score
3955
+ })).sort((a, b) => b.score - a.score).slice(0, limit));
3956
+ };
3957
+ }
3958
+ tx.onerror = () => reject(tx.error);
3959
+ });
3960
+ }
3961
+ destroy() {
3962
+ this.db?.close();
3963
+ this.db = null;
3964
+ }
3965
+ };
3966
+
3967
+ //#endregion
3968
+ //#region packages/provider/src/FileBlobStore.ts
3969
+ const DB_VERSION = 1;
3970
+ function idbAvailable() {
3971
+ return typeof globalThis !== "undefined" && "indexedDB" in globalThis;
3972
+ }
3973
+ function openDb(origin) {
3974
+ return new Promise((resolve, reject) => {
3975
+ const req = globalThis.indexedDB.open(`abracadabra:files:${origin}`, DB_VERSION);
3976
+ req.onupgradeneeded = (event) => {
3977
+ const db = event.target.result;
3978
+ if (!db.objectStoreNames.contains("blobs")) db.createObjectStore("blobs");
3979
+ if (!db.objectStoreNames.contains("upload_queue")) db.createObjectStore("upload_queue", { keyPath: "id" });
3980
+ };
3981
+ req.onsuccess = () => resolve(req.result);
3982
+ req.onerror = () => reject(req.error);
3983
+ });
3984
+ }
3985
+ function txPromise(store, request) {
3986
+ return new Promise((resolve, reject) => {
3987
+ request.onsuccess = () => resolve(request.result);
3988
+ request.onerror = () => reject(request.error);
3989
+ });
3990
+ }
3991
+ var FileBlobStore = class extends EventEmitter {
3992
+ constructor(serverOrigin, client) {
3993
+ super();
3994
+ this.dbPromise = null;
3995
+ this.db = null;
3996
+ this.objectUrls = /* @__PURE__ */ new Map();
3997
+ this._flushing = false;
3998
+ this.origin = serverOrigin;
3999
+ this.client = client;
4000
+ this._onlineHandler = () => {
4001
+ this.flushQueue().catch(() => null);
4002
+ };
4003
+ if (typeof window !== "undefined") window.addEventListener("online", this._onlineHandler);
4004
+ }
4005
+ getDb() {
4006
+ if (!idbAvailable()) return Promise.resolve(null);
4007
+ if (!this.dbPromise) this.dbPromise = openDb(this.origin).catch(() => null).then((db) => {
4008
+ this.db = db;
4009
+ return db;
4010
+ });
4011
+ return this.dbPromise;
4012
+ }
4013
+ blobKey(docId, uploadId) {
4014
+ return `${docId}/${uploadId}`;
4015
+ }
4016
+ /**
4017
+ * Return a local object URL for a file.
4018
+ * On first call the blob is downloaded from the server and cached in IDB.
4019
+ * Returns null when offline and the blob is not yet cached, or when
4020
+ * URL.createObjectURL is unavailable (e.g. Node.js / SSR).
4021
+ */
4022
+ async getBlobUrl(docId, uploadId) {
4023
+ if (typeof window === "undefined") return null;
4024
+ const key = this.blobKey(docId, uploadId);
4025
+ const existing = this.objectUrls.get(key);
4026
+ if (existing) return existing;
4027
+ const db = await this.getDb();
4028
+ if (db) {
4029
+ const tx = db.transaction("blobs", "readonly");
4030
+ const entry = await txPromise(tx.objectStore("blobs"), tx.objectStore("blobs").get(key));
4031
+ if (entry) {
4032
+ const url = URL.createObjectURL(entry.blob);
4033
+ this.objectUrls.set(key, url);
4034
+ return url;
4035
+ }
4036
+ }
4037
+ let blob;
4038
+ try {
4039
+ blob = await this.client.getUpload(docId, uploadId);
4040
+ } catch {
4041
+ return null;
4042
+ }
4043
+ if (db) {
4044
+ const entry = {
4045
+ blob,
4046
+ mime_type: blob.type,
4047
+ filename: uploadId,
4048
+ cachedAt: Date.now()
4049
+ };
4050
+ db.transaction("blobs", "readwrite").objectStore("blobs").put(entry, key);
4051
+ }
4052
+ const url = URL.createObjectURL(blob);
4053
+ this.objectUrls.set(key, url);
4054
+ return url;
4055
+ }
4056
+ /** Revoke the object URL and remove the blob from cache. */
4057
+ async evictBlob(docId, uploadId) {
4058
+ const key = this.blobKey(docId, uploadId);
4059
+ const url = this.objectUrls.get(key);
4060
+ if (url) {
4061
+ URL.revokeObjectURL(url);
4062
+ this.objectUrls.delete(key);
4063
+ }
4064
+ const db = await this.getDb();
4065
+ if (!db) return;
4066
+ const tx = db.transaction("blobs", "readwrite");
4067
+ await txPromise(tx.objectStore("blobs"), tx.objectStore("blobs").delete(key));
4068
+ }
4069
+ /**
4070
+ * Queue a file for upload. Works offline — the entry is persisted to IDB
4071
+ * and flushed the next time the queue is flushed.
4072
+ * Returns the generated queue entry id.
4073
+ */
4074
+ async queueUpload(docId, file, filename) {
4075
+ const id = crypto.randomUUID();
4076
+ const entry = {
4077
+ id,
4078
+ docId,
4079
+ file,
4080
+ filename: file instanceof File ? file.name : filename ?? "file",
4081
+ status: "pending",
4082
+ createdAt: Date.now()
4083
+ };
4084
+ const db = await this.getDb();
4085
+ if (db) {
4086
+ const tx = db.transaction("upload_queue", "readwrite");
4087
+ await txPromise(tx.objectStore("upload_queue"), tx.objectStore("upload_queue").put(entry));
4088
+ }
4089
+ this.emit("upload:queued", entry);
4090
+ return id;
4091
+ }
4092
+ /** Return all upload queue entries. */
4093
+ async getQueue() {
4094
+ const db = await this.getDb();
4095
+ if (!db) return [];
4096
+ return new Promise((resolve, reject) => {
4097
+ const req = db.transaction("upload_queue", "readonly").objectStore("upload_queue").getAll();
4098
+ req.onsuccess = () => resolve(req.result);
4099
+ req.onerror = () => reject(req.error);
4100
+ });
4101
+ }
4102
+ /**
4103
+ * Upload all pending queue entries via AbracadabraClient.
4104
+ * Safe to call repeatedly — a concurrent call is a no-op.
4105
+ * Entries that fail are marked with status "error" and left in the queue.
4106
+ */
4107
+ async flushQueue() {
4108
+ if (this._flushing) return;
4109
+ this._flushing = true;
4110
+ try {
4111
+ const pending = (await this.getQueue()).filter((e) => e.status === "pending");
4112
+ for (const entry of pending) {
4113
+ await this._updateQueueEntry(entry.id, { status: "uploading" });
4114
+ this.emit("upload:started", {
4115
+ ...entry,
4116
+ status: "uploading"
4117
+ });
4118
+ try {
4119
+ await this.client.upload(entry.docId, entry.file, entry.filename);
4120
+ await this._updateQueueEntry(entry.id, { status: "done" });
4121
+ this.emit("upload:done", {
4122
+ ...entry,
4123
+ status: "done"
4124
+ });
4125
+ } catch (err) {
4126
+ const message = err instanceof Error ? err.message : String(err);
4127
+ await this._updateQueueEntry(entry.id, {
4128
+ status: "error",
4129
+ error: message
4130
+ });
4131
+ this.emit("upload:error", {
4132
+ ...entry,
4133
+ status: "error",
4134
+ error: message
4135
+ });
4136
+ }
4137
+ }
4138
+ } finally {
4139
+ this._flushing = false;
4140
+ }
4141
+ }
4142
+ async _updateQueueEntry(id, patch) {
4143
+ const db = await this.getDb();
4144
+ if (!db) return;
4145
+ return new Promise((resolve, reject) => {
4146
+ const tx = db.transaction("upload_queue", "readwrite");
4147
+ const store = tx.objectStore("upload_queue");
4148
+ const req = store.get(id);
4149
+ req.onsuccess = () => {
4150
+ if (!req.result) {
4151
+ resolve();
4152
+ return;
4153
+ }
4154
+ const updated = {
4155
+ ...req.result,
4156
+ ...patch
4157
+ };
4158
+ store.put(updated);
4159
+ tx.oncomplete = () => resolve();
4160
+ tx.onerror = () => reject(tx.error);
4161
+ };
4162
+ req.onerror = () => reject(req.error);
4163
+ });
4164
+ }
4165
+ destroy() {
4166
+ if (typeof window !== "undefined") window.removeEventListener("online", this._onlineHandler);
4167
+ for (const url of this.objectUrls.values()) URL.revokeObjectURL(url);
4168
+ this.objectUrls.clear();
4169
+ this.db?.close();
4170
+ this.db = null;
4171
+ this.removeAllListeners();
4172
+ }
4173
+ };
4174
+
4175
+ //#endregion
4176
+ exports.AbracadabraBaseProvider = AbracadabraBaseProvider;
3530
4177
  exports.AbracadabraClient = AbracadabraClient;
3531
4178
  exports.AbracadabraProvider = AbracadabraProvider;
4179
+ exports.AbracadabraWS = AbracadabraWS;
4180
+ exports.AuthMessageType = AuthMessageType;
3532
4181
  exports.AwarenessError = AwarenessError;
4182
+ exports.ConnectionTimeout = ConnectionTimeout;
3533
4183
  exports.CryptoIdentityKeystore = CryptoIdentityKeystore;
4184
+ exports.DocumentCache = DocumentCache;
4185
+ exports.FileBlobStore = FileBlobStore;
4186
+ exports.Forbidden = Forbidden;
3534
4187
  exports.HocuspocusProvider = HocuspocusProvider;
3535
4188
  exports.HocuspocusProviderWebsocket = HocuspocusProviderWebsocket;
4189
+ exports.MessageTooBig = MessageTooBig;
3536
4190
  exports.MessageType = MessageType;
3537
4191
  exports.OfflineStore = OfflineStore;
4192
+ exports.ResetConnection = ResetConnection;
4193
+ exports.SearchIndex = SearchIndex;
3538
4194
  exports.SubdocMessage = SubdocMessage;
4195
+ exports.Unauthorized = Unauthorized;
3539
4196
  exports.WebSocketStatus = WebSocketStatus;
4197
+ exports.WsReadyStates = WsReadyStates;
4198
+ exports.awarenessStatesToArray = awarenessStatesToArray;
4199
+ exports.readAuthMessage = readAuthMessage;
4200
+ exports.writeAuthenticated = writeAuthenticated;
4201
+ exports.writeAuthentication = writeAuthentication;
4202
+ exports.writePermissionDenied = writePermissionDenied;
4203
+ exports.writeTokenSyncRequest = writeTokenSyncRequest;
3540
4204
  //# sourceMappingURL=abracadabra-provider.cjs.map