Package not found. Please check the package name and try again.
@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
|
@@ -1,8 +1,18 @@
|
|
|
1
|
-
import { WsReadyStates, awarenessStatesToArray, readAuthMessage, writeAuthentication } from "@abraca/dabra-common";
|
|
2
1
|
import * as Y from "yjs";
|
|
3
2
|
import { retry } from "@lifeomic/attempt";
|
|
4
3
|
import * as ed from "@noble/ed25519";
|
|
5
4
|
|
|
5
|
+
//#region packages/provider/src/awarenessStatesToArray.ts
|
|
6
|
+
const awarenessStatesToArray = (states) => {
|
|
7
|
+
return Array.from(states.entries()).map(([key, value]) => {
|
|
8
|
+
return {
|
|
9
|
+
clientId: key,
|
|
10
|
+
...value
|
|
11
|
+
};
|
|
12
|
+
});
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
//#endregion
|
|
6
16
|
//#region node_modules/lib0/math.js
|
|
7
17
|
/**
|
|
8
18
|
* Common Math expressions.
|
|
@@ -1024,6 +1034,37 @@ var EventEmitter = class {
|
|
|
1024
1034
|
}
|
|
1025
1035
|
};
|
|
1026
1036
|
|
|
1037
|
+
//#endregion
|
|
1038
|
+
//#region packages/provider/src/types.ts
|
|
1039
|
+
/**
|
|
1040
|
+
* State of the WebSocket connection.
|
|
1041
|
+
* https://developer.mozilla.org/de/docs/Web/API/WebSocket/readyState
|
|
1042
|
+
*/
|
|
1043
|
+
let WsReadyStates = /* @__PURE__ */ function(WsReadyStates) {
|
|
1044
|
+
WsReadyStates[WsReadyStates["Connecting"] = 0] = "Connecting";
|
|
1045
|
+
WsReadyStates[WsReadyStates["Open"] = 1] = "Open";
|
|
1046
|
+
WsReadyStates[WsReadyStates["Closing"] = 2] = "Closing";
|
|
1047
|
+
WsReadyStates[WsReadyStates["Closed"] = 3] = "Closed";
|
|
1048
|
+
return WsReadyStates;
|
|
1049
|
+
}({});
|
|
1050
|
+
let MessageType = /* @__PURE__ */ function(MessageType) {
|
|
1051
|
+
MessageType[MessageType["Sync"] = 0] = "Sync";
|
|
1052
|
+
MessageType[MessageType["Awareness"] = 1] = "Awareness";
|
|
1053
|
+
MessageType[MessageType["Auth"] = 2] = "Auth";
|
|
1054
|
+
MessageType[MessageType["QueryAwareness"] = 3] = "QueryAwareness";
|
|
1055
|
+
MessageType[MessageType["Subdoc"] = 4] = "Subdoc";
|
|
1056
|
+
MessageType[MessageType["Stateless"] = 5] = "Stateless";
|
|
1057
|
+
MessageType[MessageType["CLOSE"] = 7] = "CLOSE";
|
|
1058
|
+
MessageType[MessageType["SyncStatus"] = 8] = "SyncStatus";
|
|
1059
|
+
return MessageType;
|
|
1060
|
+
}({});
|
|
1061
|
+
let WebSocketStatus = /* @__PURE__ */ function(WebSocketStatus) {
|
|
1062
|
+
WebSocketStatus["Connecting"] = "connecting";
|
|
1063
|
+
WebSocketStatus["Connected"] = "connected";
|
|
1064
|
+
WebSocketStatus["Disconnected"] = "disconnected";
|
|
1065
|
+
return WebSocketStatus;
|
|
1066
|
+
}({});
|
|
1067
|
+
|
|
1027
1068
|
//#endregion
|
|
1028
1069
|
//#region packages/provider/src/IncomingMessage.ts
|
|
1029
1070
|
var IncomingMessage = class {
|
|
@@ -1058,26 +1099,6 @@ var IncomingMessage = class {
|
|
|
1058
1099
|
}
|
|
1059
1100
|
};
|
|
1060
1101
|
|
|
1061
|
-
//#endregion
|
|
1062
|
-
//#region packages/provider/src/types.ts
|
|
1063
|
-
let MessageType = /* @__PURE__ */ function(MessageType) {
|
|
1064
|
-
MessageType[MessageType["Sync"] = 0] = "Sync";
|
|
1065
|
-
MessageType[MessageType["Awareness"] = 1] = "Awareness";
|
|
1066
|
-
MessageType[MessageType["Auth"] = 2] = "Auth";
|
|
1067
|
-
MessageType[MessageType["QueryAwareness"] = 3] = "QueryAwareness";
|
|
1068
|
-
MessageType[MessageType["Subdoc"] = 4] = "Subdoc";
|
|
1069
|
-
MessageType[MessageType["Stateless"] = 5] = "Stateless";
|
|
1070
|
-
MessageType[MessageType["CLOSE"] = 7] = "CLOSE";
|
|
1071
|
-
MessageType[MessageType["SyncStatus"] = 8] = "SyncStatus";
|
|
1072
|
-
return MessageType;
|
|
1073
|
-
}({});
|
|
1074
|
-
let WebSocketStatus = /* @__PURE__ */ function(WebSocketStatus) {
|
|
1075
|
-
WebSocketStatus["Connecting"] = "connecting";
|
|
1076
|
-
WebSocketStatus["Connected"] = "connected";
|
|
1077
|
-
WebSocketStatus["Disconnected"] = "disconnected";
|
|
1078
|
-
return WebSocketStatus;
|
|
1079
|
-
}({});
|
|
1080
|
-
|
|
1081
1102
|
//#endregion
|
|
1082
1103
|
//#region packages/provider/src/OutgoingMessage.ts
|
|
1083
1104
|
var OutgoingMessage = class {
|
|
@@ -1108,8 +1129,8 @@ var CloseMessage = class extends OutgoingMessage {
|
|
|
1108
1129
|
};
|
|
1109
1130
|
|
|
1110
1131
|
//#endregion
|
|
1111
|
-
//#region packages/provider/src/
|
|
1112
|
-
var
|
|
1132
|
+
//#region packages/provider/src/AbracadabraWS.ts
|
|
1133
|
+
var AbracadabraWS = class extends EventEmitter {
|
|
1113
1134
|
constructor(configuration) {
|
|
1114
1135
|
super();
|
|
1115
1136
|
this.messageQueue = [];
|
|
@@ -1370,6 +1391,46 @@ var HocuspocusProviderWebsocket = class extends EventEmitter {
|
|
|
1370
1391
|
this.cleanupWebSocket();
|
|
1371
1392
|
}
|
|
1372
1393
|
};
|
|
1394
|
+
/** @deprecated Use AbracadabraWS */
|
|
1395
|
+
const HocuspocusProviderWebsocket = AbracadabraWS;
|
|
1396
|
+
|
|
1397
|
+
//#endregion
|
|
1398
|
+
//#region packages/provider/src/auth.ts
|
|
1399
|
+
let AuthMessageType = /* @__PURE__ */ function(AuthMessageType) {
|
|
1400
|
+
AuthMessageType[AuthMessageType["Token"] = 0] = "Token";
|
|
1401
|
+
AuthMessageType[AuthMessageType["PermissionDenied"] = 1] = "PermissionDenied";
|
|
1402
|
+
AuthMessageType[AuthMessageType["Authenticated"] = 2] = "Authenticated";
|
|
1403
|
+
return AuthMessageType;
|
|
1404
|
+
}({});
|
|
1405
|
+
const writeAuthentication = (encoder, auth) => {
|
|
1406
|
+
writeVarUint(encoder, AuthMessageType.Token);
|
|
1407
|
+
writeVarString(encoder, auth);
|
|
1408
|
+
};
|
|
1409
|
+
const writePermissionDenied = (encoder, reason) => {
|
|
1410
|
+
writeVarUint(encoder, AuthMessageType.PermissionDenied);
|
|
1411
|
+
writeVarString(encoder, reason);
|
|
1412
|
+
};
|
|
1413
|
+
const writeAuthenticated = (encoder, scope) => {
|
|
1414
|
+
writeVarUint(encoder, AuthMessageType.Authenticated);
|
|
1415
|
+
writeVarString(encoder, scope);
|
|
1416
|
+
};
|
|
1417
|
+
const writeTokenSyncRequest = (encoder) => {
|
|
1418
|
+
writeVarUint(encoder, AuthMessageType.Token);
|
|
1419
|
+
};
|
|
1420
|
+
const readAuthMessage = (decoder, sendToken, permissionDeniedHandler, authenticatedHandler) => {
|
|
1421
|
+
switch (readVarUint(decoder)) {
|
|
1422
|
+
case AuthMessageType.Token:
|
|
1423
|
+
sendToken();
|
|
1424
|
+
break;
|
|
1425
|
+
case AuthMessageType.PermissionDenied:
|
|
1426
|
+
permissionDeniedHandler(readVarString(decoder));
|
|
1427
|
+
break;
|
|
1428
|
+
case AuthMessageType.Authenticated:
|
|
1429
|
+
authenticatedHandler(readVarString(decoder));
|
|
1430
|
+
break;
|
|
1431
|
+
default:
|
|
1432
|
+
}
|
|
1433
|
+
};
|
|
1373
1434
|
|
|
1374
1435
|
//#endregion
|
|
1375
1436
|
//#region node_modules/y-protocols/sync.js
|
|
@@ -1664,14 +1725,14 @@ var UpdateMessage = class extends OutgoingMessage {
|
|
|
1664
1725
|
};
|
|
1665
1726
|
|
|
1666
1727
|
//#endregion
|
|
1667
|
-
//#region packages/provider/src/
|
|
1728
|
+
//#region packages/provider/src/AbracadabraBaseProvider.ts
|
|
1668
1729
|
var AwarenessError = class extends Error {
|
|
1669
1730
|
constructor(..._args) {
|
|
1670
1731
|
super(..._args);
|
|
1671
1732
|
this.code = 1001;
|
|
1672
1733
|
}
|
|
1673
1734
|
};
|
|
1674
|
-
var
|
|
1735
|
+
var AbracadabraBaseProvider = class extends EventEmitter {
|
|
1675
1736
|
constructor(configuration) {
|
|
1676
1737
|
super();
|
|
1677
1738
|
this.configuration = {
|
|
@@ -1745,7 +1806,7 @@ var HocuspocusProvider = class extends EventEmitter {
|
|
|
1745
1806
|
setConfiguration(configuration = {}) {
|
|
1746
1807
|
if (!configuration.websocketProvider) {
|
|
1747
1808
|
this.manageSocket = true;
|
|
1748
|
-
this.configuration.websocketProvider = new
|
|
1809
|
+
this.configuration.websocketProvider = new AbracadabraWS(configuration);
|
|
1749
1810
|
}
|
|
1750
1811
|
this.configuration = {
|
|
1751
1812
|
...this.configuration,
|
|
@@ -1845,11 +1906,11 @@ var HocuspocusProvider = class extends EventEmitter {
|
|
|
1845
1906
|
}
|
|
1846
1907
|
async connect() {
|
|
1847
1908
|
if (this.manageSocket) return this.configuration.websocketProvider.connect();
|
|
1848
|
-
console.warn("
|
|
1909
|
+
console.warn("AbracadabraBaseProvider::connect() is deprecated and does not do anything. Please connect/disconnect on the websocketProvider, or attach/deattach providers.");
|
|
1849
1910
|
}
|
|
1850
1911
|
disconnect() {
|
|
1851
1912
|
if (this.manageSocket) return this.configuration.websocketProvider.disconnect();
|
|
1852
|
-
console.warn("
|
|
1913
|
+
console.warn("AbracadabraBaseProvider::disconnect() is deprecated and does not do anything. Please connect/disconnect on the websocketProvider, or attach/deattach providers.");
|
|
1853
1914
|
}
|
|
1854
1915
|
async onOpen(event) {
|
|
1855
1916
|
this.isAuthenticated = false;
|
|
@@ -1958,16 +2019,18 @@ var HocuspocusProvider = class extends EventEmitter {
|
|
|
1958
2019
|
this.awareness.setLocalStateField(key, value);
|
|
1959
2020
|
}
|
|
1960
2021
|
};
|
|
2022
|
+
/** @deprecated Use AbracadabraBaseProvider */
|
|
2023
|
+
const HocuspocusProvider = AbracadabraBaseProvider;
|
|
1961
2024
|
|
|
1962
2025
|
//#endregion
|
|
1963
2026
|
//#region packages/provider/src/OfflineStore.ts
|
|
1964
|
-
const DB_VERSION = 2;
|
|
1965
|
-
function idbAvailable() {
|
|
2027
|
+
const DB_VERSION$3 = 2;
|
|
2028
|
+
function idbAvailable$3() {
|
|
1966
2029
|
return typeof globalThis !== "undefined" && "indexedDB" in globalThis;
|
|
1967
2030
|
}
|
|
1968
|
-
function openDb$
|
|
2031
|
+
function openDb$4(storeKey) {
|
|
1969
2032
|
return new Promise((resolve, reject) => {
|
|
1970
|
-
const req = globalThis.indexedDB.open(`abracadabra:${storeKey}`, DB_VERSION);
|
|
2033
|
+
const req = globalThis.indexedDB.open(`abracadabra:${storeKey}`, DB_VERSION$3);
|
|
1971
2034
|
req.onupgradeneeded = (event) => {
|
|
1972
2035
|
const db = event.target.result;
|
|
1973
2036
|
if (!db.objectStoreNames.contains("updates")) db.createObjectStore("updates", { autoIncrement: true });
|
|
@@ -1979,7 +2042,7 @@ function openDb$1(storeKey) {
|
|
|
1979
2042
|
req.onerror = () => reject(req.error);
|
|
1980
2043
|
});
|
|
1981
2044
|
}
|
|
1982
|
-
function txPromise(store, request) {
|
|
2045
|
+
function txPromise$2(store, request) {
|
|
1983
2046
|
return new Promise((resolve, reject) => {
|
|
1984
2047
|
request.onsuccess = () => resolve(request.result);
|
|
1985
2048
|
request.onerror = () => reject(request.error);
|
|
@@ -1994,18 +2057,22 @@ var OfflineStore = class {
|
|
|
1994
2057
|
*/
|
|
1995
2058
|
constructor(docId, serverOrigin) {
|
|
1996
2059
|
this.db = null;
|
|
2060
|
+
this.dbPromise = null;
|
|
1997
2061
|
this.storeKey = serverOrigin ? `${serverOrigin}/${docId}` : docId;
|
|
1998
2062
|
}
|
|
1999
|
-
|
|
2000
|
-
if (!idbAvailable()) return null;
|
|
2001
|
-
if (!this.
|
|
2002
|
-
|
|
2063
|
+
getDb() {
|
|
2064
|
+
if (!idbAvailable$3()) return Promise.resolve(null);
|
|
2065
|
+
if (!this.dbPromise) this.dbPromise = openDb$4(this.storeKey).catch(() => null).then((db) => {
|
|
2066
|
+
this.db = db;
|
|
2067
|
+
return db;
|
|
2068
|
+
});
|
|
2069
|
+
return this.dbPromise;
|
|
2003
2070
|
}
|
|
2004
2071
|
async persistUpdate(update) {
|
|
2005
2072
|
const db = await this.getDb();
|
|
2006
2073
|
if (!db) return;
|
|
2007
2074
|
const store = db.transaction("updates", "readwrite").objectStore("updates");
|
|
2008
|
-
await txPromise(store, store.add(update));
|
|
2075
|
+
await txPromise$2(store, store.add(update));
|
|
2009
2076
|
}
|
|
2010
2077
|
async getPendingUpdates() {
|
|
2011
2078
|
const db = await this.getDb();
|
|
@@ -2020,7 +2087,7 @@ var OfflineStore = class {
|
|
|
2020
2087
|
const db = await this.getDb();
|
|
2021
2088
|
if (!db) return;
|
|
2022
2089
|
const tx = db.transaction("updates", "readwrite");
|
|
2023
|
-
await txPromise(tx.objectStore("updates"), tx.objectStore("updates").clear());
|
|
2090
|
+
await txPromise$2(tx.objectStore("updates"), tx.objectStore("updates").clear());
|
|
2024
2091
|
}
|
|
2025
2092
|
/**
|
|
2026
2093
|
* Persist a full Y.js state snapshot (Y.encodeStateAsUpdate output).
|
|
@@ -2030,7 +2097,7 @@ var OfflineStore = class {
|
|
|
2030
2097
|
const db = await this.getDb();
|
|
2031
2098
|
if (!db) return;
|
|
2032
2099
|
const tx = db.transaction("doc_state", "readwrite");
|
|
2033
|
-
await txPromise(tx.objectStore("doc_state"), tx.objectStore("doc_state").put(snapshot, "snapshot"));
|
|
2100
|
+
await txPromise$2(tx.objectStore("doc_state"), tx.objectStore("doc_state").put(snapshot, "snapshot"));
|
|
2034
2101
|
}
|
|
2035
2102
|
/**
|
|
2036
2103
|
* Retrieve the stored full document snapshot, or null if none exists.
|
|
@@ -2039,37 +2106,37 @@ var OfflineStore = class {
|
|
|
2039
2106
|
const db = await this.getDb();
|
|
2040
2107
|
if (!db) return null;
|
|
2041
2108
|
const tx = db.transaction("doc_state", "readonly");
|
|
2042
|
-
return await txPromise(tx.objectStore("doc_state"), tx.objectStore("doc_state").get("snapshot")) ?? null;
|
|
2109
|
+
return await txPromise$2(tx.objectStore("doc_state"), tx.objectStore("doc_state").get("snapshot")) ?? null;
|
|
2043
2110
|
}
|
|
2044
2111
|
async getStateVector() {
|
|
2045
2112
|
const db = await this.getDb();
|
|
2046
2113
|
if (!db) return null;
|
|
2047
2114
|
const tx = db.transaction("meta", "readonly");
|
|
2048
|
-
return await txPromise(tx.objectStore("meta"), tx.objectStore("meta").get("sv")) ?? null;
|
|
2115
|
+
return await txPromise$2(tx.objectStore("meta"), tx.objectStore("meta").get("sv")) ?? null;
|
|
2049
2116
|
}
|
|
2050
2117
|
async saveStateVector(sv) {
|
|
2051
2118
|
const db = await this.getDb();
|
|
2052
2119
|
if (!db) return;
|
|
2053
2120
|
const tx = db.transaction("meta", "readwrite");
|
|
2054
|
-
await txPromise(tx.objectStore("meta"), tx.objectStore("meta").put(sv, "sv"));
|
|
2121
|
+
await txPromise$2(tx.objectStore("meta"), tx.objectStore("meta").put(sv, "sv"));
|
|
2055
2122
|
}
|
|
2056
2123
|
async getPermissionSnapshot() {
|
|
2057
2124
|
const db = await this.getDb();
|
|
2058
2125
|
if (!db) return null;
|
|
2059
2126
|
const tx = db.transaction("meta", "readonly");
|
|
2060
|
-
return await txPromise(tx.objectStore("meta"), tx.objectStore("meta").get("role")) ?? null;
|
|
2127
|
+
return await txPromise$2(tx.objectStore("meta"), tx.objectStore("meta").get("role")) ?? null;
|
|
2061
2128
|
}
|
|
2062
2129
|
async savePermissionSnapshot(role) {
|
|
2063
2130
|
const db = await this.getDb();
|
|
2064
2131
|
if (!db) return;
|
|
2065
2132
|
const tx = db.transaction("meta", "readwrite");
|
|
2066
|
-
await txPromise(tx.objectStore("meta"), tx.objectStore("meta").put(role, "role"));
|
|
2133
|
+
await txPromise$2(tx.objectStore("meta"), tx.objectStore("meta").put(role, "role"));
|
|
2067
2134
|
}
|
|
2068
2135
|
async queueSubdoc(entry) {
|
|
2069
2136
|
const db = await this.getDb();
|
|
2070
2137
|
if (!db) return;
|
|
2071
2138
|
const tx = db.transaction("subdoc_queue", "readwrite");
|
|
2072
|
-
await txPromise(tx.objectStore("subdoc_queue"), tx.objectStore("subdoc_queue").put(entry));
|
|
2139
|
+
await txPromise$2(tx.objectStore("subdoc_queue"), tx.objectStore("subdoc_queue").put(entry));
|
|
2073
2140
|
}
|
|
2074
2141
|
async getPendingSubdocs() {
|
|
2075
2142
|
const db = await this.getDb();
|
|
@@ -2084,7 +2151,7 @@ var OfflineStore = class {
|
|
|
2084
2151
|
const db = await this.getDb();
|
|
2085
2152
|
if (!db) return;
|
|
2086
2153
|
const tx = db.transaction("subdoc_queue", "readwrite");
|
|
2087
|
-
await txPromise(tx.objectStore("subdoc_queue"), tx.objectStore("subdoc_queue").delete(childId));
|
|
2154
|
+
await txPromise$2(tx.objectStore("subdoc_queue"), tx.objectStore("subdoc_queue").delete(childId));
|
|
2088
2155
|
}
|
|
2089
2156
|
destroy() {
|
|
2090
2157
|
this.db?.close();
|
|
@@ -2127,7 +2194,7 @@ function isValidDocId(id) {
|
|
|
2127
2194
|
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id);
|
|
2128
2195
|
}
|
|
2129
2196
|
/**
|
|
2130
|
-
* AbracadabraProvider extends
|
|
2197
|
+
* AbracadabraProvider extends AbracadabraBaseProvider with:
|
|
2131
2198
|
*
|
|
2132
2199
|
* 1. Subdocument lifecycle – intercepts Y.Doc subdoc events and syncs them
|
|
2133
2200
|
* with the server via MSG_SUBDOC (4) frames. Child documents get their
|
|
@@ -2147,7 +2214,7 @@ function isValidDocId(id) {
|
|
|
2147
2214
|
* can gate write operations without a network round-trip. Role is
|
|
2148
2215
|
* refreshed from the server on every reconnect.
|
|
2149
2216
|
*/
|
|
2150
|
-
var AbracadabraProvider = class AbracadabraProvider extends
|
|
2217
|
+
var AbracadabraProvider = class AbracadabraProvider extends AbracadabraBaseProvider {
|
|
2151
2218
|
constructor(configuration) {
|
|
2152
2219
|
const resolved = { ...configuration };
|
|
2153
2220
|
const client = configuration.client ?? null;
|
|
@@ -2194,9 +2261,8 @@ var AbracadabraProvider = class AbracadabraProvider extends HocuspocusProvider {
|
|
|
2194
2261
|
*/
|
|
2195
2262
|
async _initFromOfflineStore() {
|
|
2196
2263
|
if (!this.offlineStore) return;
|
|
2197
|
-
const snapshot = await this.offlineStore.getDocSnapshot().catch(() => null);
|
|
2264
|
+
const [snapshot, pending] = await Promise.all([this.offlineStore.getDocSnapshot().catch(() => null), this.offlineStore.getPendingUpdates().catch(() => [])]);
|
|
2198
2265
|
if (snapshot) Y.applyUpdate(this.document, snapshot, this.offlineStore);
|
|
2199
|
-
const pending = await this.offlineStore.getPendingUpdates().catch(() => []);
|
|
2200
2266
|
for (const update of pending) Y.applyUpdate(this.document, update, this.offlineStore);
|
|
2201
2267
|
}
|
|
2202
2268
|
authenticatedHandler(scope) {
|
|
@@ -2335,18 +2401,7 @@ var AbracadabraProvider = class AbracadabraProvider extends HocuspocusProvider {
|
|
|
2335
2401
|
}
|
|
2336
2402
|
async _doLoadChild(childId) {
|
|
2337
2403
|
const childDoc = new Y.Doc({ guid: childId });
|
|
2338
|
-
|
|
2339
|
-
const onRegistered = ({ childId: cid }) => {
|
|
2340
|
-
if (cid === childId) {
|
|
2341
|
-
this.off("subdocRegistered", onRegistered);
|
|
2342
|
-
resolve();
|
|
2343
|
-
}
|
|
2344
|
-
};
|
|
2345
|
-
this.on("subdocRegistered", onRegistered);
|
|
2346
|
-
this.registerSubdoc(childDoc);
|
|
2347
|
-
setTimeout(resolve, 3e3);
|
|
2348
|
-
});
|
|
2349
|
-
else this.registerSubdoc(childDoc);
|
|
2404
|
+
this.registerSubdoc(childDoc);
|
|
2350
2405
|
const childProvider = new AbracadabraProvider({
|
|
2351
2406
|
name: childId,
|
|
2352
2407
|
document: childDoc,
|
|
@@ -2440,6 +2495,7 @@ var AbracadabraClient = class {
|
|
|
2440
2495
|
this.persistAuth = config.persistAuth ?? typeof localStorage !== "undefined";
|
|
2441
2496
|
this.storageKey = config.storageKey ?? "abracadabra:auth";
|
|
2442
2497
|
this._fetch = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
2498
|
+
this.cache = config.cache ?? null;
|
|
2443
2499
|
this._token = config.token ?? this.loadPersistedToken() ?? null;
|
|
2444
2500
|
}
|
|
2445
2501
|
get token() {
|
|
@@ -2537,7 +2593,13 @@ var AbracadabraClient = class {
|
|
|
2537
2593
|
}
|
|
2538
2594
|
/** Get the current user's profile. */
|
|
2539
2595
|
async getMe() {
|
|
2540
|
-
|
|
2596
|
+
if (this.cache) {
|
|
2597
|
+
const cached = await this.cache.getCurrentProfile();
|
|
2598
|
+
if (cached) return cached;
|
|
2599
|
+
}
|
|
2600
|
+
const profile = await this.request("GET", "/users/me");
|
|
2601
|
+
if (this.cache) await this.cache.setCurrentProfile(profile).catch(() => null);
|
|
2602
|
+
return profile;
|
|
2541
2603
|
}
|
|
2542
2604
|
/** Update the current user's display name. */
|
|
2543
2605
|
async updateMe(opts) {
|
|
@@ -2549,15 +2611,28 @@ var AbracadabraClient = class {
|
|
|
2549
2611
|
}
|
|
2550
2612
|
/** Get document metadata. */
|
|
2551
2613
|
async getDoc(docId) {
|
|
2552
|
-
|
|
2614
|
+
if (this.cache) {
|
|
2615
|
+
const cached = await this.cache.getDoc(docId);
|
|
2616
|
+
if (cached) return cached;
|
|
2617
|
+
}
|
|
2618
|
+
const meta = await this.request("GET", `/docs/${encodeURIComponent(docId)}`);
|
|
2619
|
+
if (this.cache) await this.cache.setDoc(meta).catch(() => null);
|
|
2620
|
+
return meta;
|
|
2553
2621
|
}
|
|
2554
2622
|
/** Delete a document (requires Owner role). Cascades to children and uploads. */
|
|
2555
2623
|
async deleteDoc(docId) {
|
|
2556
2624
|
await this.request("DELETE", `/docs/${encodeURIComponent(docId)}`);
|
|
2625
|
+
if (this.cache) await this.cache.invalidateDoc(docId).catch(() => null);
|
|
2557
2626
|
}
|
|
2558
2627
|
/** List immediate child documents. */
|
|
2559
2628
|
async listChildren(docId) {
|
|
2560
|
-
|
|
2629
|
+
if (this.cache) {
|
|
2630
|
+
const cached = await this.cache.getChildren(docId);
|
|
2631
|
+
if (cached) return cached;
|
|
2632
|
+
}
|
|
2633
|
+
const res = await this.request("GET", `/docs/${encodeURIComponent(docId)}/children`);
|
|
2634
|
+
if (this.cache) await this.cache.setChildren(docId, res.children).catch(() => null);
|
|
2635
|
+
return res.children;
|
|
2561
2636
|
}
|
|
2562
2637
|
/** Create a child document under a parent (requires write permission). */
|
|
2563
2638
|
async createChild(docId, opts) {
|
|
@@ -2565,7 +2640,13 @@ var AbracadabraClient = class {
|
|
|
2565
2640
|
}
|
|
2566
2641
|
/** List all permissions for a document (requires read access). */
|
|
2567
2642
|
async listPermissions(docId) {
|
|
2568
|
-
|
|
2643
|
+
if (this.cache) {
|
|
2644
|
+
const cached = await this.cache.getPermissions(docId);
|
|
2645
|
+
if (cached) return cached;
|
|
2646
|
+
}
|
|
2647
|
+
const res = await this.request("GET", `/docs/${encodeURIComponent(docId)}/permissions`);
|
|
2648
|
+
if (this.cache) await this.cache.setPermissions(docId, res.permissions).catch(() => null);
|
|
2649
|
+
return res.permissions;
|
|
2569
2650
|
}
|
|
2570
2651
|
/** Grant or change a user's role on a document (requires Owner). */
|
|
2571
2652
|
async setPermission(docId, opts) {
|
|
@@ -2587,11 +2668,19 @@ var AbracadabraClient = class {
|
|
|
2587
2668
|
body: formData
|
|
2588
2669
|
});
|
|
2589
2670
|
if (!res.ok) throw await this.toError(res);
|
|
2590
|
-
|
|
2671
|
+
const meta = await res.json();
|
|
2672
|
+
if (this.cache) await this.cache.invalidateUploads(docId).catch(() => null);
|
|
2673
|
+
return meta;
|
|
2591
2674
|
}
|
|
2592
2675
|
/** List all uploads for a document. */
|
|
2593
2676
|
async listUploads(docId) {
|
|
2594
|
-
|
|
2677
|
+
if (this.cache) {
|
|
2678
|
+
const cached = await this.cache.getUploads(docId);
|
|
2679
|
+
if (cached) return cached;
|
|
2680
|
+
}
|
|
2681
|
+
const res = await this.request("GET", `/docs/${encodeURIComponent(docId)}/uploads`);
|
|
2682
|
+
if (this.cache) await this.cache.setUploads(docId, res.uploads).catch(() => null);
|
|
2683
|
+
return res.uploads;
|
|
2595
2684
|
}
|
|
2596
2685
|
/** Download an upload as a Blob. */
|
|
2597
2686
|
async getUpload(docId, uploadId) {
|
|
@@ -2607,11 +2696,19 @@ var AbracadabraClient = class {
|
|
|
2607
2696
|
/** Delete an upload (requires uploader or document Owner). */
|
|
2608
2697
|
async deleteUpload(docId, uploadId) {
|
|
2609
2698
|
await this.request("DELETE", `/docs/${encodeURIComponent(docId)}/uploads/${encodeURIComponent(uploadId)}`);
|
|
2699
|
+
if (this.cache) await this.cache.invalidateUploads(docId).catch(() => null);
|
|
2610
2700
|
}
|
|
2611
2701
|
/** Health check — no auth required. */
|
|
2612
2702
|
async health() {
|
|
2613
2703
|
return this.request("GET", "/health", { auth: false });
|
|
2614
2704
|
}
|
|
2705
|
+
/**
|
|
2706
|
+
* Fetch server metadata including the optional `index_doc_id` entry point.
|
|
2707
|
+
* No auth required.
|
|
2708
|
+
*/
|
|
2709
|
+
async serverInfo() {
|
|
2710
|
+
return this.request("GET", "/info", { auth: false });
|
|
2711
|
+
}
|
|
2615
2712
|
async request(method, path, opts) {
|
|
2616
2713
|
const auth = opts?.auth ?? true;
|
|
2617
2714
|
const headers = {};
|
|
@@ -2659,6 +2756,49 @@ var AbracadabraClient = class {
|
|
|
2659
2756
|
}
|
|
2660
2757
|
};
|
|
2661
2758
|
|
|
2759
|
+
//#endregion
|
|
2760
|
+
//#region packages/provider/src/CloseEvents.ts
|
|
2761
|
+
/**
|
|
2762
|
+
* The server is terminating the connection because a data frame was received
|
|
2763
|
+
* that is too large.
|
|
2764
|
+
* See: https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code
|
|
2765
|
+
*/
|
|
2766
|
+
const MessageTooBig = {
|
|
2767
|
+
code: 1009,
|
|
2768
|
+
reason: "Message Too Big"
|
|
2769
|
+
};
|
|
2770
|
+
/**
|
|
2771
|
+
* The server successfully processed the request, asks that the requester reset
|
|
2772
|
+
* its document view, and is not returning any content.
|
|
2773
|
+
*/
|
|
2774
|
+
const ResetConnection = {
|
|
2775
|
+
code: 4205,
|
|
2776
|
+
reason: "Reset Connection"
|
|
2777
|
+
};
|
|
2778
|
+
/**
|
|
2779
|
+
* Similar to Forbidden, but specifically for use when authentication is required and has
|
|
2780
|
+
* failed or has not yet been provided.
|
|
2781
|
+
*/
|
|
2782
|
+
const Unauthorized = {
|
|
2783
|
+
code: 4401,
|
|
2784
|
+
reason: "Unauthorized"
|
|
2785
|
+
};
|
|
2786
|
+
/**
|
|
2787
|
+
* The request contained valid data and was understood by the server, but the server
|
|
2788
|
+
* is refusing action.
|
|
2789
|
+
*/
|
|
2790
|
+
const Forbidden = {
|
|
2791
|
+
code: 4403,
|
|
2792
|
+
reason: "Forbidden"
|
|
2793
|
+
};
|
|
2794
|
+
/**
|
|
2795
|
+
* The server timed out waiting for the request.
|
|
2796
|
+
*/
|
|
2797
|
+
const ConnectionTimeout = {
|
|
2798
|
+
code: 4408,
|
|
2799
|
+
reason: "Connection Timeout"
|
|
2800
|
+
};
|
|
2801
|
+
|
|
2662
2802
|
//#endregion
|
|
2663
2803
|
//#region node_modules/@noble/hashes/esm/utils.js
|
|
2664
2804
|
/** Checks if something is Uint8Array. Be careful: nodejs Buffer will return true. */
|
|
@@ -3336,7 +3476,7 @@ const DB_NAME = "abracadabra:identity";
|
|
|
3336
3476
|
const STORE_NAME = "identity";
|
|
3337
3477
|
const RECORD_KEY = "current";
|
|
3338
3478
|
const HKDF_INFO = new TextEncoder().encode("abracadabra-identity-v1");
|
|
3339
|
-
function openDb() {
|
|
3479
|
+
function openDb$3() {
|
|
3340
3480
|
return new Promise((resolve, reject) => {
|
|
3341
3481
|
const req = indexedDB.open(DB_NAME, 1);
|
|
3342
3482
|
req.onupgradeneeded = () => {
|
|
@@ -3419,7 +3559,7 @@ var CryptoIdentityKeystore = class {
|
|
|
3419
3559
|
name: "AES-GCM",
|
|
3420
3560
|
iv
|
|
3421
3561
|
}, aesKey, privateKey);
|
|
3422
|
-
const db = await openDb();
|
|
3562
|
+
const db = await openDb$3();
|
|
3423
3563
|
await dbPut(db, {
|
|
3424
3564
|
username,
|
|
3425
3565
|
publicKey: toBase64url(publicKey),
|
|
@@ -3442,7 +3582,7 @@ var CryptoIdentityKeystore = class {
|
|
|
3442
3582
|
* @returns base64url-encoded Ed25519 signature (64 bytes).
|
|
3443
3583
|
*/
|
|
3444
3584
|
async sign(challengeB64) {
|
|
3445
|
-
const db = await openDb();
|
|
3585
|
+
const db = await openDb$3();
|
|
3446
3586
|
const stored = await dbGet(db);
|
|
3447
3587
|
db.close();
|
|
3448
3588
|
if (!stored) throw new Error("No identity stored. Call register() first.");
|
|
@@ -3471,7 +3611,7 @@ var CryptoIdentityKeystore = class {
|
|
|
3471
3611
|
}
|
|
3472
3612
|
/** Returns the stored base64url public key, or null if no identity exists. */
|
|
3473
3613
|
async getPublicKey() {
|
|
3474
|
-
const db = await openDb();
|
|
3614
|
+
const db = await openDb$3();
|
|
3475
3615
|
const stored = await dbGet(db);
|
|
3476
3616
|
db.close();
|
|
3477
3617
|
return stored?.publicKey ?? null;
|
|
@@ -3484,26 +3624,524 @@ var CryptoIdentityKeystore = class {
|
|
|
3484
3624
|
* a real display name via PATCH /users/me.
|
|
3485
3625
|
*/
|
|
3486
3626
|
async getUsername() {
|
|
3487
|
-
const db = await openDb();
|
|
3627
|
+
const db = await openDb$3();
|
|
3488
3628
|
const stored = await dbGet(db);
|
|
3489
3629
|
db.close();
|
|
3490
3630
|
return stored?.username ?? null;
|
|
3491
3631
|
}
|
|
3492
3632
|
/** Returns true if an identity is stored in IndexedDB. */
|
|
3493
3633
|
async hasIdentity() {
|
|
3494
|
-
const db = await openDb();
|
|
3634
|
+
const db = await openDb$3();
|
|
3495
3635
|
const stored = await dbGet(db);
|
|
3496
3636
|
db.close();
|
|
3497
3637
|
return stored !== void 0;
|
|
3498
3638
|
}
|
|
3499
3639
|
/** Remove the stored identity from IndexedDB. */
|
|
3500
3640
|
async clear() {
|
|
3501
|
-
const db = await openDb();
|
|
3641
|
+
const db = await openDb$3();
|
|
3502
3642
|
await dbDelete(db);
|
|
3503
3643
|
db.close();
|
|
3504
3644
|
}
|
|
3505
3645
|
};
|
|
3506
3646
|
|
|
3507
3647
|
//#endregion
|
|
3508
|
-
|
|
3648
|
+
//#region packages/provider/src/DocumentCache.ts
|
|
3649
|
+
const DB_VERSION$2 = 1;
|
|
3650
|
+
const DEFAULT_TTL_MS = 300 * 1e3;
|
|
3651
|
+
function idbAvailable$2() {
|
|
3652
|
+
return typeof globalThis !== "undefined" && "indexedDB" in globalThis;
|
|
3653
|
+
}
|
|
3654
|
+
function openDb$2(origin) {
|
|
3655
|
+
return new Promise((resolve, reject) => {
|
|
3656
|
+
const req = globalThis.indexedDB.open(`abracadabra:meta-cache:${origin}`, DB_VERSION$2);
|
|
3657
|
+
req.onupgradeneeded = (event) => {
|
|
3658
|
+
const db = event.target.result;
|
|
3659
|
+
for (const name of [
|
|
3660
|
+
"doc_meta",
|
|
3661
|
+
"children",
|
|
3662
|
+
"user_profile",
|
|
3663
|
+
"permissions",
|
|
3664
|
+
"uploads"
|
|
3665
|
+
]) if (!db.objectStoreNames.contains(name)) db.createObjectStore(name);
|
|
3666
|
+
};
|
|
3667
|
+
req.onsuccess = () => resolve(req.result);
|
|
3668
|
+
req.onerror = () => reject(req.error);
|
|
3669
|
+
});
|
|
3670
|
+
}
|
|
3671
|
+
function txPromise$1(store, request) {
|
|
3672
|
+
return new Promise((resolve, reject) => {
|
|
3673
|
+
request.onsuccess = () => resolve(request.result);
|
|
3674
|
+
request.onerror = () => reject(request.error);
|
|
3675
|
+
});
|
|
3676
|
+
}
|
|
3677
|
+
var DocumentCache = class {
|
|
3678
|
+
constructor(serverOrigin, opts) {
|
|
3679
|
+
this.dbPromise = null;
|
|
3680
|
+
this.db = null;
|
|
3681
|
+
this.origin = serverOrigin;
|
|
3682
|
+
this.ttlMs = opts?.ttlMs ?? DEFAULT_TTL_MS;
|
|
3683
|
+
}
|
|
3684
|
+
getDb() {
|
|
3685
|
+
if (!idbAvailable$2()) return Promise.resolve(null);
|
|
3686
|
+
if (!this.dbPromise) this.dbPromise = openDb$2(this.origin).catch(() => null).then((db) => {
|
|
3687
|
+
this.db = db;
|
|
3688
|
+
return db;
|
|
3689
|
+
});
|
|
3690
|
+
return this.dbPromise;
|
|
3691
|
+
}
|
|
3692
|
+
isExpired(cachedAt) {
|
|
3693
|
+
return Date.now() - cachedAt > this.ttlMs;
|
|
3694
|
+
}
|
|
3695
|
+
async getWithTtl(storeName, key) {
|
|
3696
|
+
const db = await this.getDb();
|
|
3697
|
+
if (!db) return null;
|
|
3698
|
+
const tx = db.transaction(storeName, "readonly");
|
|
3699
|
+
const result = await txPromise$1(tx.objectStore(storeName), tx.objectStore(storeName).get(key));
|
|
3700
|
+
if (!result) return null;
|
|
3701
|
+
if (this.isExpired(result.cachedAt)) {
|
|
3702
|
+
this.deleteKey(storeName, key).catch(() => null);
|
|
3703
|
+
return null;
|
|
3704
|
+
}
|
|
3705
|
+
return result.value;
|
|
3706
|
+
}
|
|
3707
|
+
async setWithTtl(storeName, key, value) {
|
|
3708
|
+
const db = await this.getDb();
|
|
3709
|
+
if (!db) return;
|
|
3710
|
+
const entry = {
|
|
3711
|
+
value,
|
|
3712
|
+
cachedAt: Date.now()
|
|
3713
|
+
};
|
|
3714
|
+
const tx = db.transaction(storeName, "readwrite");
|
|
3715
|
+
await txPromise$1(tx.objectStore(storeName), tx.objectStore(storeName).put(entry, key));
|
|
3716
|
+
}
|
|
3717
|
+
async deleteKey(storeName, key) {
|
|
3718
|
+
const db = await this.getDb();
|
|
3719
|
+
if (!db) return;
|
|
3720
|
+
const tx = db.transaction(storeName, "readwrite");
|
|
3721
|
+
await txPromise$1(tx.objectStore(storeName), tx.objectStore(storeName).delete(key));
|
|
3722
|
+
}
|
|
3723
|
+
async getDoc(docId) {
|
|
3724
|
+
return this.getWithTtl("doc_meta", docId);
|
|
3725
|
+
}
|
|
3726
|
+
async setDoc(meta) {
|
|
3727
|
+
return this.setWithTtl("doc_meta", meta.id, meta);
|
|
3728
|
+
}
|
|
3729
|
+
async invalidateDoc(docId) {
|
|
3730
|
+
await this.deleteKey("doc_meta", docId).catch(() => null);
|
|
3731
|
+
}
|
|
3732
|
+
async getChildren(parentId) {
|
|
3733
|
+
return this.getWithTtl("children", parentId);
|
|
3734
|
+
}
|
|
3735
|
+
async setChildren(parentId, items) {
|
|
3736
|
+
return this.setWithTtl("children", parentId, items);
|
|
3737
|
+
}
|
|
3738
|
+
async invalidateChildren(parentId) {
|
|
3739
|
+
await this.deleteKey("children", parentId).catch(() => null);
|
|
3740
|
+
}
|
|
3741
|
+
async getProfile(userId) {
|
|
3742
|
+
return this.getWithTtl("user_profile", userId);
|
|
3743
|
+
}
|
|
3744
|
+
async setProfile(profile) {
|
|
3745
|
+
return this.setWithTtl("user_profile", profile.id, profile);
|
|
3746
|
+
}
|
|
3747
|
+
/** Get the cached profile for the currently authenticated user. */
|
|
3748
|
+
async getCurrentProfile() {
|
|
3749
|
+
return this.getWithTtl("user_profile", "__current__");
|
|
3750
|
+
}
|
|
3751
|
+
/** Cache a profile both by its ID and as the current user. */
|
|
3752
|
+
async setCurrentProfile(profile) {
|
|
3753
|
+
await Promise.all([this.setWithTtl("user_profile", profile.id, profile), this.setWithTtl("user_profile", "__current__", profile)]);
|
|
3754
|
+
}
|
|
3755
|
+
async getPermissions(docId) {
|
|
3756
|
+
return this.getWithTtl("permissions", docId);
|
|
3757
|
+
}
|
|
3758
|
+
async setPermissions(docId, items) {
|
|
3759
|
+
return this.setWithTtl("permissions", docId, items);
|
|
3760
|
+
}
|
|
3761
|
+
async getUploads(docId) {
|
|
3762
|
+
return this.getWithTtl("uploads", docId);
|
|
3763
|
+
}
|
|
3764
|
+
async setUploads(docId, items) {
|
|
3765
|
+
return this.setWithTtl("uploads", docId, items);
|
|
3766
|
+
}
|
|
3767
|
+
async invalidateUploads(docId) {
|
|
3768
|
+
await this.deleteKey("uploads", docId).catch(() => null);
|
|
3769
|
+
}
|
|
3770
|
+
destroy() {
|
|
3771
|
+
this.db?.close();
|
|
3772
|
+
this.db = null;
|
|
3773
|
+
}
|
|
3774
|
+
};
|
|
3775
|
+
|
|
3776
|
+
//#endregion
|
|
3777
|
+
//#region packages/provider/src/SearchIndex.ts
|
|
3778
|
+
const DB_VERSION$1 = 1;
|
|
3779
|
+
function idbAvailable$1() {
|
|
3780
|
+
return typeof globalThis !== "undefined" && "indexedDB" in globalThis;
|
|
3781
|
+
}
|
|
3782
|
+
function openDb$1(origin) {
|
|
3783
|
+
return new Promise((resolve, reject) => {
|
|
3784
|
+
const req = globalThis.indexedDB.open(`abracadabra:search:${origin}`, DB_VERSION$1);
|
|
3785
|
+
req.onupgradeneeded = (event) => {
|
|
3786
|
+
const db = event.target.result;
|
|
3787
|
+
if (!db.objectStoreNames.contains("postings")) db.createObjectStore("postings");
|
|
3788
|
+
if (!db.objectStoreNames.contains("doc_trigrams")) db.createObjectStore("doc_trigrams");
|
|
3789
|
+
};
|
|
3790
|
+
req.onsuccess = () => resolve(req.result);
|
|
3791
|
+
req.onerror = () => reject(req.error);
|
|
3792
|
+
});
|
|
3793
|
+
}
|
|
3794
|
+
/** Extract the set of trigrams for a piece of text. */
|
|
3795
|
+
function extractTrigrams(text) {
|
|
3796
|
+
const trigrams = /* @__PURE__ */ new Set();
|
|
3797
|
+
const padded = ` ${text.toLowerCase()} `;
|
|
3798
|
+
for (let i = 0; i <= padded.length - 3; i++) trigrams.add(padded.slice(i, i + 3));
|
|
3799
|
+
return trigrams;
|
|
3800
|
+
}
|
|
3801
|
+
/** Merge trigrams from multiple texts into a single set. */
|
|
3802
|
+
function extractAllTrigrams(texts) {
|
|
3803
|
+
const result = /* @__PURE__ */ new Set();
|
|
3804
|
+
for (const t of texts) for (const trigram of extractTrigrams(t)) result.add(trigram);
|
|
3805
|
+
return result;
|
|
3806
|
+
}
|
|
3807
|
+
var SearchIndex = class {
|
|
3808
|
+
constructor(serverOrigin) {
|
|
3809
|
+
this.dbPromise = null;
|
|
3810
|
+
this.db = null;
|
|
3811
|
+
this.origin = serverOrigin;
|
|
3812
|
+
}
|
|
3813
|
+
getDb() {
|
|
3814
|
+
if (!idbAvailable$1()) return Promise.resolve(null);
|
|
3815
|
+
if (!this.dbPromise) this.dbPromise = openDb$1(this.origin).catch(() => null).then((db) => {
|
|
3816
|
+
this.db = db;
|
|
3817
|
+
return db;
|
|
3818
|
+
});
|
|
3819
|
+
return this.dbPromise;
|
|
3820
|
+
}
|
|
3821
|
+
/**
|
|
3822
|
+
* Replace the index for docId with the given texts.
|
|
3823
|
+
* Old trigram associations are removed before new ones are added.
|
|
3824
|
+
*/
|
|
3825
|
+
async index(docId, texts) {
|
|
3826
|
+
const db = await this.getDb();
|
|
3827
|
+
if (!db) return;
|
|
3828
|
+
const newTrigrams = extractAllTrigrams(texts);
|
|
3829
|
+
return new Promise((resolve, reject) => {
|
|
3830
|
+
const tx = db.transaction(["postings", "doc_trigrams"], "readwrite");
|
|
3831
|
+
tx.oncomplete = () => resolve();
|
|
3832
|
+
tx.onerror = () => reject(tx.error);
|
|
3833
|
+
const postings = tx.objectStore("postings");
|
|
3834
|
+
const docTrigramsStore = tx.objectStore("doc_trigrams");
|
|
3835
|
+
const oldReq = docTrigramsStore.get(docId);
|
|
3836
|
+
oldReq.onsuccess = () => {
|
|
3837
|
+
const oldTrigrams = oldReq.result ?? [];
|
|
3838
|
+
let pending = oldTrigrams.length + newTrigrams.size + 1;
|
|
3839
|
+
function done() {
|
|
3840
|
+
pending--;
|
|
3841
|
+
}
|
|
3842
|
+
for (const trigram of oldTrigrams) {
|
|
3843
|
+
const req = postings.get(trigram);
|
|
3844
|
+
req.onsuccess = () => {
|
|
3845
|
+
const updated = (req.result ?? []).filter((id) => id !== docId);
|
|
3846
|
+
if (updated.length === 0) postings.delete(trigram);
|
|
3847
|
+
else postings.put(updated, trigram);
|
|
3848
|
+
done();
|
|
3849
|
+
};
|
|
3850
|
+
req.onerror = done;
|
|
3851
|
+
}
|
|
3852
|
+
for (const trigram of newTrigrams) {
|
|
3853
|
+
const req = postings.get(trigram);
|
|
3854
|
+
req.onsuccess = () => {
|
|
3855
|
+
const list = req.result ?? [];
|
|
3856
|
+
if (!list.includes(docId)) list.push(docId);
|
|
3857
|
+
postings.put(list, trigram);
|
|
3858
|
+
done();
|
|
3859
|
+
};
|
|
3860
|
+
req.onerror = done;
|
|
3861
|
+
}
|
|
3862
|
+
const writeReq = docTrigramsStore.put([...newTrigrams], docId);
|
|
3863
|
+
writeReq.onsuccess = done;
|
|
3864
|
+
writeReq.onerror = done;
|
|
3865
|
+
};
|
|
3866
|
+
oldReq.onerror = () => reject(oldReq.error);
|
|
3867
|
+
});
|
|
3868
|
+
}
|
|
3869
|
+
/** Remove all indexed content for a document. */
|
|
3870
|
+
async remove(docId) {
|
|
3871
|
+
const db = await this.getDb();
|
|
3872
|
+
if (!db) return;
|
|
3873
|
+
return new Promise((resolve, reject) => {
|
|
3874
|
+
const tx = db.transaction(["postings", "doc_trigrams"], "readwrite");
|
|
3875
|
+
tx.oncomplete = () => resolve();
|
|
3876
|
+
tx.onerror = () => reject(tx.error);
|
|
3877
|
+
const postings = tx.objectStore("postings");
|
|
3878
|
+
const docTrigramsStore = tx.objectStore("doc_trigrams");
|
|
3879
|
+
const oldReq = docTrigramsStore.get(docId);
|
|
3880
|
+
oldReq.onsuccess = () => {
|
|
3881
|
+
const oldTrigrams = oldReq.result ?? [];
|
|
3882
|
+
for (const trigram of oldTrigrams) {
|
|
3883
|
+
const req = postings.get(trigram);
|
|
3884
|
+
req.onsuccess = () => {
|
|
3885
|
+
const updated = (req.result ?? []).filter((id) => id !== docId);
|
|
3886
|
+
if (updated.length === 0) postings.delete(trigram);
|
|
3887
|
+
else postings.put(updated, trigram);
|
|
3888
|
+
};
|
|
3889
|
+
}
|
|
3890
|
+
docTrigramsStore.delete(docId);
|
|
3891
|
+
};
|
|
3892
|
+
oldReq.onerror = () => reject(oldReq.error);
|
|
3893
|
+
});
|
|
3894
|
+
}
|
|
3895
|
+
/**
|
|
3896
|
+
* Search for documents matching the query.
|
|
3897
|
+
* Returns results sorted by score (matching trigram count) descending.
|
|
3898
|
+
*/
|
|
3899
|
+
async search(query, limit = 20) {
|
|
3900
|
+
const db = await this.getDb();
|
|
3901
|
+
if (!db) return [];
|
|
3902
|
+
const queryTrigrams = [...extractTrigrams(query)];
|
|
3903
|
+
if (queryTrigrams.length === 0) return [];
|
|
3904
|
+
return new Promise((resolve, reject) => {
|
|
3905
|
+
const tx = db.transaction("postings", "readonly");
|
|
3906
|
+
const postings = tx.objectStore("postings");
|
|
3907
|
+
const scores = /* @__PURE__ */ new Map();
|
|
3908
|
+
let remaining = queryTrigrams.length;
|
|
3909
|
+
for (const trigram of queryTrigrams) {
|
|
3910
|
+
const req = postings.get(trigram);
|
|
3911
|
+
req.onsuccess = () => {
|
|
3912
|
+
const docIds = req.result ?? [];
|
|
3913
|
+
for (const docId of docIds) scores.set(docId, (scores.get(docId) ?? 0) + 1);
|
|
3914
|
+
remaining--;
|
|
3915
|
+
if (remaining === 0) resolve([...scores.entries()].map(([docId, score]) => ({
|
|
3916
|
+
docId,
|
|
3917
|
+
score
|
|
3918
|
+
})).sort((a, b) => b.score - a.score).slice(0, limit));
|
|
3919
|
+
};
|
|
3920
|
+
req.onerror = () => {
|
|
3921
|
+
remaining--;
|
|
3922
|
+
if (remaining === 0) resolve([...scores.entries()].map(([docId, score]) => ({
|
|
3923
|
+
docId,
|
|
3924
|
+
score
|
|
3925
|
+
})).sort((a, b) => b.score - a.score).slice(0, limit));
|
|
3926
|
+
};
|
|
3927
|
+
}
|
|
3928
|
+
tx.onerror = () => reject(tx.error);
|
|
3929
|
+
});
|
|
3930
|
+
}
|
|
3931
|
+
destroy() {
|
|
3932
|
+
this.db?.close();
|
|
3933
|
+
this.db = null;
|
|
3934
|
+
}
|
|
3935
|
+
};
|
|
3936
|
+
|
|
3937
|
+
//#endregion
|
|
3938
|
+
//#region packages/provider/src/FileBlobStore.ts
|
|
3939
|
+
const DB_VERSION = 1;
|
|
3940
|
+
function idbAvailable() {
|
|
3941
|
+
return typeof globalThis !== "undefined" && "indexedDB" in globalThis;
|
|
3942
|
+
}
|
|
3943
|
+
function openDb(origin) {
|
|
3944
|
+
return new Promise((resolve, reject) => {
|
|
3945
|
+
const req = globalThis.indexedDB.open(`abracadabra:files:${origin}`, DB_VERSION);
|
|
3946
|
+
req.onupgradeneeded = (event) => {
|
|
3947
|
+
const db = event.target.result;
|
|
3948
|
+
if (!db.objectStoreNames.contains("blobs")) db.createObjectStore("blobs");
|
|
3949
|
+
if (!db.objectStoreNames.contains("upload_queue")) db.createObjectStore("upload_queue", { keyPath: "id" });
|
|
3950
|
+
};
|
|
3951
|
+
req.onsuccess = () => resolve(req.result);
|
|
3952
|
+
req.onerror = () => reject(req.error);
|
|
3953
|
+
});
|
|
3954
|
+
}
|
|
3955
|
+
function txPromise(store, request) {
|
|
3956
|
+
return new Promise((resolve, reject) => {
|
|
3957
|
+
request.onsuccess = () => resolve(request.result);
|
|
3958
|
+
request.onerror = () => reject(request.error);
|
|
3959
|
+
});
|
|
3960
|
+
}
|
|
3961
|
+
var FileBlobStore = class extends EventEmitter {
|
|
3962
|
+
constructor(serverOrigin, client) {
|
|
3963
|
+
super();
|
|
3964
|
+
this.dbPromise = null;
|
|
3965
|
+
this.db = null;
|
|
3966
|
+
this.objectUrls = /* @__PURE__ */ new Map();
|
|
3967
|
+
this._flushing = false;
|
|
3968
|
+
this.origin = serverOrigin;
|
|
3969
|
+
this.client = client;
|
|
3970
|
+
this._onlineHandler = () => {
|
|
3971
|
+
this.flushQueue().catch(() => null);
|
|
3972
|
+
};
|
|
3973
|
+
if (typeof window !== "undefined") window.addEventListener("online", this._onlineHandler);
|
|
3974
|
+
}
|
|
3975
|
+
getDb() {
|
|
3976
|
+
if (!idbAvailable()) return Promise.resolve(null);
|
|
3977
|
+
if (!this.dbPromise) this.dbPromise = openDb(this.origin).catch(() => null).then((db) => {
|
|
3978
|
+
this.db = db;
|
|
3979
|
+
return db;
|
|
3980
|
+
});
|
|
3981
|
+
return this.dbPromise;
|
|
3982
|
+
}
|
|
3983
|
+
blobKey(docId, uploadId) {
|
|
3984
|
+
return `${docId}/${uploadId}`;
|
|
3985
|
+
}
|
|
3986
|
+
/**
|
|
3987
|
+
* Return a local object URL for a file.
|
|
3988
|
+
* On first call the blob is downloaded from the server and cached in IDB.
|
|
3989
|
+
* Returns null when offline and the blob is not yet cached, or when
|
|
3990
|
+
* URL.createObjectURL is unavailable (e.g. Node.js / SSR).
|
|
3991
|
+
*/
|
|
3992
|
+
async getBlobUrl(docId, uploadId) {
|
|
3993
|
+
if (typeof window === "undefined") return null;
|
|
3994
|
+
const key = this.blobKey(docId, uploadId);
|
|
3995
|
+
const existing = this.objectUrls.get(key);
|
|
3996
|
+
if (existing) return existing;
|
|
3997
|
+
const db = await this.getDb();
|
|
3998
|
+
if (db) {
|
|
3999
|
+
const tx = db.transaction("blobs", "readonly");
|
|
4000
|
+
const entry = await txPromise(tx.objectStore("blobs"), tx.objectStore("blobs").get(key));
|
|
4001
|
+
if (entry) {
|
|
4002
|
+
const url = URL.createObjectURL(entry.blob);
|
|
4003
|
+
this.objectUrls.set(key, url);
|
|
4004
|
+
return url;
|
|
4005
|
+
}
|
|
4006
|
+
}
|
|
4007
|
+
let blob;
|
|
4008
|
+
try {
|
|
4009
|
+
blob = await this.client.getUpload(docId, uploadId);
|
|
4010
|
+
} catch {
|
|
4011
|
+
return null;
|
|
4012
|
+
}
|
|
4013
|
+
if (db) {
|
|
4014
|
+
const entry = {
|
|
4015
|
+
blob,
|
|
4016
|
+
mime_type: blob.type,
|
|
4017
|
+
filename: uploadId,
|
|
4018
|
+
cachedAt: Date.now()
|
|
4019
|
+
};
|
|
4020
|
+
db.transaction("blobs", "readwrite").objectStore("blobs").put(entry, key);
|
|
4021
|
+
}
|
|
4022
|
+
const url = URL.createObjectURL(blob);
|
|
4023
|
+
this.objectUrls.set(key, url);
|
|
4024
|
+
return url;
|
|
4025
|
+
}
|
|
4026
|
+
/** Revoke the object URL and remove the blob from cache. */
|
|
4027
|
+
async evictBlob(docId, uploadId) {
|
|
4028
|
+
const key = this.blobKey(docId, uploadId);
|
|
4029
|
+
const url = this.objectUrls.get(key);
|
|
4030
|
+
if (url) {
|
|
4031
|
+
URL.revokeObjectURL(url);
|
|
4032
|
+
this.objectUrls.delete(key);
|
|
4033
|
+
}
|
|
4034
|
+
const db = await this.getDb();
|
|
4035
|
+
if (!db) return;
|
|
4036
|
+
const tx = db.transaction("blobs", "readwrite");
|
|
4037
|
+
await txPromise(tx.objectStore("blobs"), tx.objectStore("blobs").delete(key));
|
|
4038
|
+
}
|
|
4039
|
+
/**
|
|
4040
|
+
* Queue a file for upload. Works offline — the entry is persisted to IDB
|
|
4041
|
+
* and flushed the next time the queue is flushed.
|
|
4042
|
+
* Returns the generated queue entry id.
|
|
4043
|
+
*/
|
|
4044
|
+
async queueUpload(docId, file, filename) {
|
|
4045
|
+
const id = crypto.randomUUID();
|
|
4046
|
+
const entry = {
|
|
4047
|
+
id,
|
|
4048
|
+
docId,
|
|
4049
|
+
file,
|
|
4050
|
+
filename: file instanceof File ? file.name : filename ?? "file",
|
|
4051
|
+
status: "pending",
|
|
4052
|
+
createdAt: Date.now()
|
|
4053
|
+
};
|
|
4054
|
+
const db = await this.getDb();
|
|
4055
|
+
if (db) {
|
|
4056
|
+
const tx = db.transaction("upload_queue", "readwrite");
|
|
4057
|
+
await txPromise(tx.objectStore("upload_queue"), tx.objectStore("upload_queue").put(entry));
|
|
4058
|
+
}
|
|
4059
|
+
this.emit("upload:queued", entry);
|
|
4060
|
+
return id;
|
|
4061
|
+
}
|
|
4062
|
+
/** Return all upload queue entries. */
|
|
4063
|
+
async getQueue() {
|
|
4064
|
+
const db = await this.getDb();
|
|
4065
|
+
if (!db) return [];
|
|
4066
|
+
return new Promise((resolve, reject) => {
|
|
4067
|
+
const req = db.transaction("upload_queue", "readonly").objectStore("upload_queue").getAll();
|
|
4068
|
+
req.onsuccess = () => resolve(req.result);
|
|
4069
|
+
req.onerror = () => reject(req.error);
|
|
4070
|
+
});
|
|
4071
|
+
}
|
|
4072
|
+
/**
|
|
4073
|
+
* Upload all pending queue entries via AbracadabraClient.
|
|
4074
|
+
* Safe to call repeatedly — a concurrent call is a no-op.
|
|
4075
|
+
* Entries that fail are marked with status "error" and left in the queue.
|
|
4076
|
+
*/
|
|
4077
|
+
async flushQueue() {
|
|
4078
|
+
if (this._flushing) return;
|
|
4079
|
+
this._flushing = true;
|
|
4080
|
+
try {
|
|
4081
|
+
const pending = (await this.getQueue()).filter((e) => e.status === "pending");
|
|
4082
|
+
for (const entry of pending) {
|
|
4083
|
+
await this._updateQueueEntry(entry.id, { status: "uploading" });
|
|
4084
|
+
this.emit("upload:started", {
|
|
4085
|
+
...entry,
|
|
4086
|
+
status: "uploading"
|
|
4087
|
+
});
|
|
4088
|
+
try {
|
|
4089
|
+
await this.client.upload(entry.docId, entry.file, entry.filename);
|
|
4090
|
+
await this._updateQueueEntry(entry.id, { status: "done" });
|
|
4091
|
+
this.emit("upload:done", {
|
|
4092
|
+
...entry,
|
|
4093
|
+
status: "done"
|
|
4094
|
+
});
|
|
4095
|
+
} catch (err) {
|
|
4096
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4097
|
+
await this._updateQueueEntry(entry.id, {
|
|
4098
|
+
status: "error",
|
|
4099
|
+
error: message
|
|
4100
|
+
});
|
|
4101
|
+
this.emit("upload:error", {
|
|
4102
|
+
...entry,
|
|
4103
|
+
status: "error",
|
|
4104
|
+
error: message
|
|
4105
|
+
});
|
|
4106
|
+
}
|
|
4107
|
+
}
|
|
4108
|
+
} finally {
|
|
4109
|
+
this._flushing = false;
|
|
4110
|
+
}
|
|
4111
|
+
}
|
|
4112
|
+
async _updateQueueEntry(id, patch) {
|
|
4113
|
+
const db = await this.getDb();
|
|
4114
|
+
if (!db) return;
|
|
4115
|
+
return new Promise((resolve, reject) => {
|
|
4116
|
+
const tx = db.transaction("upload_queue", "readwrite");
|
|
4117
|
+
const store = tx.objectStore("upload_queue");
|
|
4118
|
+
const req = store.get(id);
|
|
4119
|
+
req.onsuccess = () => {
|
|
4120
|
+
if (!req.result) {
|
|
4121
|
+
resolve();
|
|
4122
|
+
return;
|
|
4123
|
+
}
|
|
4124
|
+
const updated = {
|
|
4125
|
+
...req.result,
|
|
4126
|
+
...patch
|
|
4127
|
+
};
|
|
4128
|
+
store.put(updated);
|
|
4129
|
+
tx.oncomplete = () => resolve();
|
|
4130
|
+
tx.onerror = () => reject(tx.error);
|
|
4131
|
+
};
|
|
4132
|
+
req.onerror = () => reject(req.error);
|
|
4133
|
+
});
|
|
4134
|
+
}
|
|
4135
|
+
destroy() {
|
|
4136
|
+
if (typeof window !== "undefined") window.removeEventListener("online", this._onlineHandler);
|
|
4137
|
+
for (const url of this.objectUrls.values()) URL.revokeObjectURL(url);
|
|
4138
|
+
this.objectUrls.clear();
|
|
4139
|
+
this.db?.close();
|
|
4140
|
+
this.db = null;
|
|
4141
|
+
this.removeAllListeners();
|
|
4142
|
+
}
|
|
4143
|
+
};
|
|
4144
|
+
|
|
4145
|
+
//#endregion
|
|
4146
|
+
export { AbracadabraBaseProvider, AbracadabraClient, AbracadabraProvider, AbracadabraWS, AuthMessageType, AwarenessError, ConnectionTimeout, CryptoIdentityKeystore, DocumentCache, FileBlobStore, Forbidden, HocuspocusProvider, HocuspocusProviderWebsocket, MessageTooBig, MessageType, OfflineStore, ResetConnection, SearchIndex, SubdocMessage, Unauthorized, WebSocketStatus, WsReadyStates, awarenessStatesToArray, readAuthMessage, writeAuthenticated, writeAuthentication, writePermissionDenied, writeTokenSyncRequest };
|
|
3509
4147
|
//# sourceMappingURL=abracadabra-provider.esm.js.map
|