@abraca/dabra 0.5.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.
- package/dist/abracadabra-provider.cjs +737 -81
- package/dist/abracadabra-provider.cjs.map +1 -1
- package/dist/abracadabra-provider.esm.js +715 -77
- package/dist/abracadabra-provider.esm.js.map +1 -1
- package/dist/index.d.ts +246 -31
- package/package.json +1 -2
- package/src/{HocuspocusProvider.ts → AbracadabraBaseProvider.ts} +33 -22
- package/src/AbracadabraClient.ts +69 -3
- package/src/AbracadabraProvider.ts +18 -14
- package/src/{HocuspocusProviderWebsocket.ts → AbracadabraWS.ts} +36 -22
- package/src/CloseEvents.ts +49 -0
- package/src/DocumentCache.ts +210 -0
- package/src/FileBlobStore.ts +300 -0
- package/src/MessageReceiver.ts +8 -8
- package/src/OfflineStore.ts +11 -5
- package/src/OutgoingMessages/AuthenticationMessage.ts +1 -1
- package/src/SearchIndex.ts +247 -0
- package/src/auth.ts +62 -0
- package/src/awarenessStatesToArray.ts +10 -0
- package/src/index.ts +9 -2
- package/src/types.ts +46 -1
|
@@ -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/
|
|
1142
|
-
var
|
|
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 ===
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
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
|
|
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:
|
|
1825
|
+
this.emit("awarenessUpdate", { states: awarenessStatesToArray(this.awareness.getStates()) });
|
|
1765
1826
|
});
|
|
1766
1827
|
this.awareness?.on("change", () => {
|
|
1767
|
-
this.emit("awarenessChange", { states:
|
|
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
|
|
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("
|
|
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("
|
|
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$
|
|
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);
|
|
@@ -2024,18 +2087,22 @@ var OfflineStore = class {
|
|
|
2024
2087
|
*/
|
|
2025
2088
|
constructor(docId, serverOrigin) {
|
|
2026
2089
|
this.db = null;
|
|
2090
|
+
this.dbPromise = null;
|
|
2027
2091
|
this.storeKey = serverOrigin ? `${serverOrigin}/${docId}` : docId;
|
|
2028
2092
|
}
|
|
2029
|
-
|
|
2030
|
-
if (!idbAvailable()) return null;
|
|
2031
|
-
if (!this.
|
|
2032
|
-
|
|
2093
|
+
getDb() {
|
|
2094
|
+
if (!idbAvailable$3()) return Promise.resolve(null);
|
|
2095
|
+
if (!this.dbPromise) this.dbPromise = openDb$4(this.storeKey).catch(() => null).then((db) => {
|
|
2096
|
+
this.db = db;
|
|
2097
|
+
return db;
|
|
2098
|
+
});
|
|
2099
|
+
return this.dbPromise;
|
|
2033
2100
|
}
|
|
2034
2101
|
async persistUpdate(update) {
|
|
2035
2102
|
const db = await this.getDb();
|
|
2036
2103
|
if (!db) return;
|
|
2037
2104
|
const store = db.transaction("updates", "readwrite").objectStore("updates");
|
|
2038
|
-
await txPromise(store, store.add(update));
|
|
2105
|
+
await txPromise$2(store, store.add(update));
|
|
2039
2106
|
}
|
|
2040
2107
|
async getPendingUpdates() {
|
|
2041
2108
|
const db = await this.getDb();
|
|
@@ -2050,7 +2117,7 @@ var OfflineStore = class {
|
|
|
2050
2117
|
const db = await this.getDb();
|
|
2051
2118
|
if (!db) return;
|
|
2052
2119
|
const tx = db.transaction("updates", "readwrite");
|
|
2053
|
-
await txPromise(tx.objectStore("updates"), tx.objectStore("updates").clear());
|
|
2120
|
+
await txPromise$2(tx.objectStore("updates"), tx.objectStore("updates").clear());
|
|
2054
2121
|
}
|
|
2055
2122
|
/**
|
|
2056
2123
|
* Persist a full Y.js state snapshot (Y.encodeStateAsUpdate output).
|
|
@@ -2060,7 +2127,7 @@ var OfflineStore = class {
|
|
|
2060
2127
|
const db = await this.getDb();
|
|
2061
2128
|
if (!db) return;
|
|
2062
2129
|
const tx = db.transaction("doc_state", "readwrite");
|
|
2063
|
-
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"));
|
|
2064
2131
|
}
|
|
2065
2132
|
/**
|
|
2066
2133
|
* Retrieve the stored full document snapshot, or null if none exists.
|
|
@@ -2069,37 +2136,37 @@ var OfflineStore = class {
|
|
|
2069
2136
|
const db = await this.getDb();
|
|
2070
2137
|
if (!db) return null;
|
|
2071
2138
|
const tx = db.transaction("doc_state", "readonly");
|
|
2072
|
-
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;
|
|
2073
2140
|
}
|
|
2074
2141
|
async getStateVector() {
|
|
2075
2142
|
const db = await this.getDb();
|
|
2076
2143
|
if (!db) return null;
|
|
2077
2144
|
const tx = db.transaction("meta", "readonly");
|
|
2078
|
-
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;
|
|
2079
2146
|
}
|
|
2080
2147
|
async saveStateVector(sv) {
|
|
2081
2148
|
const db = await this.getDb();
|
|
2082
2149
|
if (!db) return;
|
|
2083
2150
|
const tx = db.transaction("meta", "readwrite");
|
|
2084
|
-
await txPromise(tx.objectStore("meta"), tx.objectStore("meta").put(sv, "sv"));
|
|
2151
|
+
await txPromise$2(tx.objectStore("meta"), tx.objectStore("meta").put(sv, "sv"));
|
|
2085
2152
|
}
|
|
2086
2153
|
async getPermissionSnapshot() {
|
|
2087
2154
|
const db = await this.getDb();
|
|
2088
2155
|
if (!db) return null;
|
|
2089
2156
|
const tx = db.transaction("meta", "readonly");
|
|
2090
|
-
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;
|
|
2091
2158
|
}
|
|
2092
2159
|
async savePermissionSnapshot(role) {
|
|
2093
2160
|
const db = await this.getDb();
|
|
2094
2161
|
if (!db) return;
|
|
2095
2162
|
const tx = db.transaction("meta", "readwrite");
|
|
2096
|
-
await txPromise(tx.objectStore("meta"), tx.objectStore("meta").put(role, "role"));
|
|
2163
|
+
await txPromise$2(tx.objectStore("meta"), tx.objectStore("meta").put(role, "role"));
|
|
2097
2164
|
}
|
|
2098
2165
|
async queueSubdoc(entry) {
|
|
2099
2166
|
const db = await this.getDb();
|
|
2100
2167
|
if (!db) return;
|
|
2101
2168
|
const tx = db.transaction("subdoc_queue", "readwrite");
|
|
2102
|
-
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));
|
|
2103
2170
|
}
|
|
2104
2171
|
async getPendingSubdocs() {
|
|
2105
2172
|
const db = await this.getDb();
|
|
@@ -2114,7 +2181,7 @@ var OfflineStore = class {
|
|
|
2114
2181
|
const db = await this.getDb();
|
|
2115
2182
|
if (!db) return;
|
|
2116
2183
|
const tx = db.transaction("subdoc_queue", "readwrite");
|
|
2117
|
-
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));
|
|
2118
2185
|
}
|
|
2119
2186
|
destroy() {
|
|
2120
2187
|
this.db?.close();
|
|
@@ -2157,7 +2224,7 @@ function isValidDocId(id) {
|
|
|
2157
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);
|
|
2158
2225
|
}
|
|
2159
2226
|
/**
|
|
2160
|
-
* AbracadabraProvider extends
|
|
2227
|
+
* AbracadabraProvider extends AbracadabraBaseProvider with:
|
|
2161
2228
|
*
|
|
2162
2229
|
* 1. Subdocument lifecycle – intercepts Y.Doc subdoc events and syncs them
|
|
2163
2230
|
* with the server via MSG_SUBDOC (4) frames. Child documents get their
|
|
@@ -2177,7 +2244,7 @@ function isValidDocId(id) {
|
|
|
2177
2244
|
* can gate write operations without a network round-trip. Role is
|
|
2178
2245
|
* refreshed from the server on every reconnect.
|
|
2179
2246
|
*/
|
|
2180
|
-
var AbracadabraProvider = class AbracadabraProvider extends
|
|
2247
|
+
var AbracadabraProvider = class AbracadabraProvider extends AbracadabraBaseProvider {
|
|
2181
2248
|
constructor(configuration) {
|
|
2182
2249
|
const resolved = { ...configuration };
|
|
2183
2250
|
const client = configuration.client ?? null;
|
|
@@ -2224,9 +2291,8 @@ var AbracadabraProvider = class AbracadabraProvider extends HocuspocusProvider {
|
|
|
2224
2291
|
*/
|
|
2225
2292
|
async _initFromOfflineStore() {
|
|
2226
2293
|
if (!this.offlineStore) return;
|
|
2227
|
-
const snapshot = await this.offlineStore.getDocSnapshot().catch(() => null);
|
|
2294
|
+
const [snapshot, pending] = await Promise.all([this.offlineStore.getDocSnapshot().catch(() => null), this.offlineStore.getPendingUpdates().catch(() => [])]);
|
|
2228
2295
|
if (snapshot) yjs.applyUpdate(this.document, snapshot, this.offlineStore);
|
|
2229
|
-
const pending = await this.offlineStore.getPendingUpdates().catch(() => []);
|
|
2230
2296
|
for (const update of pending) yjs.applyUpdate(this.document, update, this.offlineStore);
|
|
2231
2297
|
}
|
|
2232
2298
|
authenticatedHandler(scope) {
|
|
@@ -2365,18 +2431,7 @@ var AbracadabraProvider = class AbracadabraProvider extends HocuspocusProvider {
|
|
|
2365
2431
|
}
|
|
2366
2432
|
async _doLoadChild(childId) {
|
|
2367
2433
|
const childDoc = new yjs.Doc({ guid: childId });
|
|
2368
|
-
|
|
2369
|
-
const onRegistered = ({ childId: cid }) => {
|
|
2370
|
-
if (cid === childId) {
|
|
2371
|
-
this.off("subdocRegistered", onRegistered);
|
|
2372
|
-
resolve();
|
|
2373
|
-
}
|
|
2374
|
-
};
|
|
2375
|
-
this.on("subdocRegistered", onRegistered);
|
|
2376
|
-
this.registerSubdoc(childDoc);
|
|
2377
|
-
setTimeout(resolve, 3e3);
|
|
2378
|
-
});
|
|
2379
|
-
else this.registerSubdoc(childDoc);
|
|
2434
|
+
this.registerSubdoc(childDoc);
|
|
2380
2435
|
const childProvider = new AbracadabraProvider({
|
|
2381
2436
|
name: childId,
|
|
2382
2437
|
document: childDoc,
|
|
@@ -2470,6 +2525,7 @@ var AbracadabraClient = class {
|
|
|
2470
2525
|
this.persistAuth = config.persistAuth ?? typeof localStorage !== "undefined";
|
|
2471
2526
|
this.storageKey = config.storageKey ?? "abracadabra:auth";
|
|
2472
2527
|
this._fetch = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
2528
|
+
this.cache = config.cache ?? null;
|
|
2473
2529
|
this._token = config.token ?? this.loadPersistedToken() ?? null;
|
|
2474
2530
|
}
|
|
2475
2531
|
get token() {
|
|
@@ -2567,7 +2623,13 @@ var AbracadabraClient = class {
|
|
|
2567
2623
|
}
|
|
2568
2624
|
/** Get the current user's profile. */
|
|
2569
2625
|
async getMe() {
|
|
2570
|
-
|
|
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;
|
|
2571
2633
|
}
|
|
2572
2634
|
/** Update the current user's display name. */
|
|
2573
2635
|
async updateMe(opts) {
|
|
@@ -2579,15 +2641,28 @@ var AbracadabraClient = class {
|
|
|
2579
2641
|
}
|
|
2580
2642
|
/** Get document metadata. */
|
|
2581
2643
|
async getDoc(docId) {
|
|
2582
|
-
|
|
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;
|
|
2583
2651
|
}
|
|
2584
2652
|
/** Delete a document (requires Owner role). Cascades to children and uploads. */
|
|
2585
2653
|
async deleteDoc(docId) {
|
|
2586
2654
|
await this.request("DELETE", `/docs/${encodeURIComponent(docId)}`);
|
|
2655
|
+
if (this.cache) await this.cache.invalidateDoc(docId).catch(() => null);
|
|
2587
2656
|
}
|
|
2588
2657
|
/** List immediate child documents. */
|
|
2589
2658
|
async listChildren(docId) {
|
|
2590
|
-
|
|
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;
|
|
2591
2666
|
}
|
|
2592
2667
|
/** Create a child document under a parent (requires write permission). */
|
|
2593
2668
|
async createChild(docId, opts) {
|
|
@@ -2595,7 +2670,13 @@ var AbracadabraClient = class {
|
|
|
2595
2670
|
}
|
|
2596
2671
|
/** List all permissions for a document (requires read access). */
|
|
2597
2672
|
async listPermissions(docId) {
|
|
2598
|
-
|
|
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;
|
|
2599
2680
|
}
|
|
2600
2681
|
/** Grant or change a user's role on a document (requires Owner). */
|
|
2601
2682
|
async setPermission(docId, opts) {
|
|
@@ -2617,11 +2698,19 @@ var AbracadabraClient = class {
|
|
|
2617
2698
|
body: formData
|
|
2618
2699
|
});
|
|
2619
2700
|
if (!res.ok) throw await this.toError(res);
|
|
2620
|
-
|
|
2701
|
+
const meta = await res.json();
|
|
2702
|
+
if (this.cache) await this.cache.invalidateUploads(docId).catch(() => null);
|
|
2703
|
+
return meta;
|
|
2621
2704
|
}
|
|
2622
2705
|
/** List all uploads for a document. */
|
|
2623
2706
|
async listUploads(docId) {
|
|
2624
|
-
|
|
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;
|
|
2625
2714
|
}
|
|
2626
2715
|
/** Download an upload as a Blob. */
|
|
2627
2716
|
async getUpload(docId, uploadId) {
|
|
@@ -2637,11 +2726,19 @@ var AbracadabraClient = class {
|
|
|
2637
2726
|
/** Delete an upload (requires uploader or document Owner). */
|
|
2638
2727
|
async deleteUpload(docId, uploadId) {
|
|
2639
2728
|
await this.request("DELETE", `/docs/${encodeURIComponent(docId)}/uploads/${encodeURIComponent(uploadId)}`);
|
|
2729
|
+
if (this.cache) await this.cache.invalidateUploads(docId).catch(() => null);
|
|
2640
2730
|
}
|
|
2641
2731
|
/** Health check — no auth required. */
|
|
2642
2732
|
async health() {
|
|
2643
2733
|
return this.request("GET", "/health", { auth: false });
|
|
2644
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
|
+
}
|
|
2645
2742
|
async request(method, path, opts) {
|
|
2646
2743
|
const auth = opts?.auth ?? true;
|
|
2647
2744
|
const headers = {};
|
|
@@ -2689,6 +2786,49 @@ var AbracadabraClient = class {
|
|
|
2689
2786
|
}
|
|
2690
2787
|
};
|
|
2691
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
|
+
|
|
2692
2832
|
//#endregion
|
|
2693
2833
|
//#region node_modules/@noble/hashes/esm/utils.js
|
|
2694
2834
|
/** Checks if something is Uint8Array. Be careful: nodejs Buffer will return true. */
|
|
@@ -3366,7 +3506,7 @@ const DB_NAME = "abracadabra:identity";
|
|
|
3366
3506
|
const STORE_NAME = "identity";
|
|
3367
3507
|
const RECORD_KEY = "current";
|
|
3368
3508
|
const HKDF_INFO = new TextEncoder().encode("abracadabra-identity-v1");
|
|
3369
|
-
function openDb() {
|
|
3509
|
+
function openDb$3() {
|
|
3370
3510
|
return new Promise((resolve, reject) => {
|
|
3371
3511
|
const req = indexedDB.open(DB_NAME, 1);
|
|
3372
3512
|
req.onupgradeneeded = () => {
|
|
@@ -3449,7 +3589,7 @@ var CryptoIdentityKeystore = class {
|
|
|
3449
3589
|
name: "AES-GCM",
|
|
3450
3590
|
iv
|
|
3451
3591
|
}, aesKey, privateKey);
|
|
3452
|
-
const db = await openDb();
|
|
3592
|
+
const db = await openDb$3();
|
|
3453
3593
|
await dbPut(db, {
|
|
3454
3594
|
username,
|
|
3455
3595
|
publicKey: toBase64url(publicKey),
|
|
@@ -3472,7 +3612,7 @@ var CryptoIdentityKeystore = class {
|
|
|
3472
3612
|
* @returns base64url-encoded Ed25519 signature (64 bytes).
|
|
3473
3613
|
*/
|
|
3474
3614
|
async sign(challengeB64) {
|
|
3475
|
-
const db = await openDb();
|
|
3615
|
+
const db = await openDb$3();
|
|
3476
3616
|
const stored = await dbGet(db);
|
|
3477
3617
|
db.close();
|
|
3478
3618
|
if (!stored) throw new Error("No identity stored. Call register() first.");
|
|
@@ -3501,7 +3641,7 @@ var CryptoIdentityKeystore = class {
|
|
|
3501
3641
|
}
|
|
3502
3642
|
/** Returns the stored base64url public key, or null if no identity exists. */
|
|
3503
3643
|
async getPublicKey() {
|
|
3504
|
-
const db = await openDb();
|
|
3644
|
+
const db = await openDb$3();
|
|
3505
3645
|
const stored = await dbGet(db);
|
|
3506
3646
|
db.close();
|
|
3507
3647
|
return stored?.publicKey ?? null;
|
|
@@ -3514,35 +3654,551 @@ var CryptoIdentityKeystore = class {
|
|
|
3514
3654
|
* a real display name via PATCH /users/me.
|
|
3515
3655
|
*/
|
|
3516
3656
|
async getUsername() {
|
|
3517
|
-
const db = await openDb();
|
|
3657
|
+
const db = await openDb$3();
|
|
3518
3658
|
const stored = await dbGet(db);
|
|
3519
3659
|
db.close();
|
|
3520
3660
|
return stored?.username ?? null;
|
|
3521
3661
|
}
|
|
3522
3662
|
/** Returns true if an identity is stored in IndexedDB. */
|
|
3523
3663
|
async hasIdentity() {
|
|
3524
|
-
const db = await openDb();
|
|
3664
|
+
const db = await openDb$3();
|
|
3525
3665
|
const stored = await dbGet(db);
|
|
3526
3666
|
db.close();
|
|
3527
3667
|
return stored !== void 0;
|
|
3528
3668
|
}
|
|
3529
3669
|
/** Remove the stored identity from IndexedDB. */
|
|
3530
3670
|
async clear() {
|
|
3531
|
-
const db = await openDb();
|
|
3671
|
+
const db = await openDb$3();
|
|
3532
3672
|
await dbDelete(db);
|
|
3533
3673
|
db.close();
|
|
3534
3674
|
}
|
|
3535
3675
|
};
|
|
3536
3676
|
|
|
3537
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;
|
|
3538
4177
|
exports.AbracadabraClient = AbracadabraClient;
|
|
3539
4178
|
exports.AbracadabraProvider = AbracadabraProvider;
|
|
4179
|
+
exports.AbracadabraWS = AbracadabraWS;
|
|
4180
|
+
exports.AuthMessageType = AuthMessageType;
|
|
3540
4181
|
exports.AwarenessError = AwarenessError;
|
|
4182
|
+
exports.ConnectionTimeout = ConnectionTimeout;
|
|
3541
4183
|
exports.CryptoIdentityKeystore = CryptoIdentityKeystore;
|
|
4184
|
+
exports.DocumentCache = DocumentCache;
|
|
4185
|
+
exports.FileBlobStore = FileBlobStore;
|
|
4186
|
+
exports.Forbidden = Forbidden;
|
|
3542
4187
|
exports.HocuspocusProvider = HocuspocusProvider;
|
|
3543
4188
|
exports.HocuspocusProviderWebsocket = HocuspocusProviderWebsocket;
|
|
4189
|
+
exports.MessageTooBig = MessageTooBig;
|
|
3544
4190
|
exports.MessageType = MessageType;
|
|
3545
4191
|
exports.OfflineStore = OfflineStore;
|
|
4192
|
+
exports.ResetConnection = ResetConnection;
|
|
4193
|
+
exports.SearchIndex = SearchIndex;
|
|
3546
4194
|
exports.SubdocMessage = SubdocMessage;
|
|
4195
|
+
exports.Unauthorized = Unauthorized;
|
|
3547
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;
|
|
3548
4204
|
//# sourceMappingURL=abracadabra-provider.cjs.map
|