@abraca/dabra 1.8.1 → 1.9.1

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/index.d.ts CHANGED
@@ -1160,10 +1160,17 @@ type onAwarenessChangeParameters = {
1160
1160
  type onStatelessParameters = {
1161
1161
  payload: string;
1162
1162
  };
1163
+ /** Fired by E2EAbracadabraProvider when the server acknowledges an E2E
1164
+ * client-side compaction (via the `snapshot:compacted` stateless broadcast). */
1165
+ type onCompactedParameters = {
1166
+ docId: string; /** User id that performed the compaction; undefined if payload was malformed. */
1167
+ by: string | undefined;
1168
+ };
1163
1169
  type onServerErrorParameters = {
1164
1170
  source: string;
1165
1171
  code: string;
1166
1172
  message: string;
1173
+ meta?: Record<string, unknown>;
1167
1174
  };
1168
1175
  type StatesArray = {
1169
1176
  clientId: number;
@@ -1804,6 +1811,13 @@ declare class FileBlobStore extends EventEmitter {
1804
1811
  * the object URL reference is stale.
1805
1812
  */
1806
1813
  invalidateUrl(docId: string, uploadId: string): void;
1814
+ /**
1815
+ * Clear the 404 negative-cache entry for (docId, uploadId) so the next
1816
+ * getBlobUrl() re-fetches from the server instead of short-circuiting.
1817
+ * Use on explicit user retry or after reconnect, when the file may have
1818
+ * become available since the last 404.
1819
+ */
1820
+ clearNotFound(docId: string, uploadId: string): void;
1807
1821
  /** Revoke the object URL and remove the blob from cache. */
1808
1822
  evictBlob(docId: string, uploadId: string): Promise<void>;
1809
1823
  /**
@@ -1830,6 +1844,31 @@ interface E2EAbracadabraProviderConfiguration extends AbracadabraProviderConfigu
1830
1844
  docKeyManager: DocKeyManager;
1831
1845
  keystore: CryptoIdentityKeystore;
1832
1846
  client: AbracadabraClient;
1847
+ /**
1848
+ * Enable client-side compaction: after `compactionThreshold` E2E updates,
1849
+ * the provider merges the whole doc, re-encrypts it, and asks the server to
1850
+ * atomically replace its update log via the `snapshot:compact` stateless
1851
+ * protocol. Requires the user to have a role that `can_manage` the doc
1852
+ * (Owner or above); otherwise the server silently rejects.
1853
+ *
1854
+ * Default: true.
1855
+ */
1856
+ compactionEnabled?: boolean;
1857
+ /**
1858
+ * Number of E2E updates applied in this session before compaction fires.
1859
+ * Default: 50. Ignored when `compactionEnabled === false`.
1860
+ */
1861
+ compactionThreshold?: number;
1862
+ /**
1863
+ * Quiescence delay: once the threshold is crossed, wait this many ms with
1864
+ * no further updates before firing compaction. Debounces active editing
1865
+ * and reduces the chance of racing an in-flight remote update that hasn't
1866
+ * reached us yet (the server does not currently validate state-vector
1867
+ * coverage, so a compaction fired mid-flight could drop an update).
1868
+ *
1869
+ * Default: 2000 ms.
1870
+ */
1871
+ compactionQuiescenceMs?: number;
1833
1872
  }
1834
1873
  declare class E2EAbracadabraProvider extends AbracadabraProvider {
1835
1874
  private readonly docKeyManager;
@@ -1840,6 +1879,16 @@ declare class E2EAbracadabraProvider extends AbracadabraProvider {
1840
1879
  /** E2E-encrypted offline store; created after the doc key is available. */
1841
1880
  private e2eStore;
1842
1881
  private readonly e2eServerOrigin;
1882
+ private readonly compactionEnabled;
1883
+ private readonly compactionThreshold;
1884
+ private readonly compactionQuiescenceMs;
1885
+ private updatesSinceCompaction;
1886
+ private compactionInFlight;
1887
+ /** Cleared on `snapshot:compacted` or on 30s timeout. */
1888
+ private compactionInFlightTimeout;
1889
+ /** Quiescence debounce: reset on every update, fires the actual compaction. */
1890
+ private compactionDebounceTimer;
1891
+ private destroyed;
1843
1892
  constructor(configuration: E2EAbracadabraProviderConfiguration);
1844
1893
  /** Fetch the doc key from the server (requires WebAuthn if not cached). */
1845
1894
  private ensureDocKey;
@@ -1850,6 +1899,17 @@ declare class E2EAbracadabraProvider extends AbracadabraProvider {
1850
1899
  /** Encrypt local updates before sending over WebSocket. */
1851
1900
  documentUpdateHandler(update: Uint8Array, origin: unknown): void;
1852
1901
  private _encryptAndSend;
1902
+ /**
1903
+ * Force an immediate compaction attempt, bypassing the threshold and
1904
+ * quiescence debounce. Resolves once the `snapshot:compact` frame has been
1905
+ * sent (or rejected locally for missing prerequisites — destroyed, no
1906
+ * doc key, or already in-flight). The server acknowledges via a
1907
+ * `snapshot:compacted` broadcast, which emits the `"compacted"` event.
1908
+ */
1909
+ compactNow(): Promise<void>;
1910
+ private _noteUpdateApplied;
1911
+ private _performCompaction;
1912
+ private _handleCompactedBroadcast;
1853
1913
  destroy(): void;
1854
1914
  }
1855
1915
  //#endregion
@@ -3097,4 +3157,4 @@ declare function wrapSeed(seed: Uint8Array, wrappingKeyBytes: Uint8Array): Promi
3097
3157
  */
3098
3158
  declare function unwrapSeed(ciphertext: ArrayBuffer, iv: Uint8Array, wrappingKeyBytes: Uint8Array): Promise<Uint8Array>;
3099
3159
  //#endregion
3100
- export { AbracadabraBaseProvider, AbracadabraBaseProviderConfiguration, AbracadabraClient, AbracadabraClientConfig, AbracadabraOutgoingMessageArguments, AbracadabraProvider, AbracadabraProviderConfiguration, AbracadabraWS, AbracadabraWSConfiguration, AbracadabraWebRTC, type AbracadabraWebRTCConfiguration, AbracadabraWebSocketConn, AuthMessageType, AuthorizedScope, AwarenessError, BackgroundSyncManager, type BackgroundSyncManagerOptions, BackgroundSyncPersistence, BroadcastChannelSync, CHANNEL_NAMES, type ChatChannel, ChatClient, type ChatClientTransport, type ChatMessage, type ChatReadCursor, type ChatReadReceipt, type ChatTypingEvent, CloseEvent, CompleteAbracadabraBaseProviderConfiguration, CompleteAbracadabraWSConfiguration, CompleteHocuspocusProviderConfiguration, CompleteHocuspocusProviderWebsocketConfiguration, ConnectionTimeout, Constructable, ConstructableOutgoingMessage, type CreateNotificationInput, CryptoIdentity, CryptoIdentityKeystore, DEFAULT_FILE_CHUNK_SIZE, DEFAULT_ICE_SERVERS, DataChannelRouter, DevicePairingChannel, type DevicePairingConfig, DeviceRegistrationService, type DeviceServerStatus, type DeviceTier, type DocEncryptionInfo, DocKeyManager, type DocSyncState, DocumentCache, type DocumentCacheOptions, DocumentMeta, E2EAbracadabraProvider, E2EEChannel, type E2EEIdentity, E2EOfflineStore, EffectivePermissionEntry, EffectivePermissionsResponse, EffectiveRole, EncryptedYMap, EncryptedYText, type FetchNotificationsInput, FileBlobStore, FileTransferChannel, FileTransferHandle, type FileTransferMeta, type FileTransferStatus, Forbidden, type GetChatHistoryInput, HealthStatus, HocusPocusWebSocket, HocuspocusProvider, HocuspocusProviderConfiguration, HocuspocusProviderWebsocket, HocuspocusProviderWebsocketConfiguration, HocuspocusWebSocket, type IdentityDeviceEntry, type IdentityDocConfiguration, IdentityDocProvider, type IdentityProfile, type IdentityServerEntry, type IdentitySpaceEntry, InviteRow, KEY_EXCHANGE_CHANNEL, ManualSignaling, type ManualSignalingBlob, MessageTooBig, MessageType, type NotificationReadUpdate, type NotificationRecord, NotificationsClient, OfflineStore, OutgoingMessageArguments, OutgoingMessageInterface, type PairingRequest, type PairingResult, PeerConnection, type PeerInfo, type PeerState, PendingSubdoc, PermissionEntry, PublicKeyInfo, ResetConnection, SearchIndex, SearchResult, type SendChatMessageInput, ServerInfo, type SignalingIncoming, type SignalingOutgoing, SignalingSocket, SnapshotCreateResult, SnapshotData, SnapshotForkResult, SnapshotMeta, SnapshotRestoreResult, SpaceMeta, StatesArray, SubdocMessage, SubdocRegisteredEvent, Unauthorized, UploadInfo, UploadMeta, UploadQueueEntry, UploadQueueStatus, UserProfile, WebSocketStatus, WsReadyStates, YjsDataChannel, attachUpdatedAtObserver, awarenessStatesToArray, wordlist as bip39Wordlist, decryptField, deriveIdentityDocId, deriveSeedWrappingKey, encryptField, generateMnemonic, makeEncryptedYMap, makeEncryptedYText, mnemonicToEd25519Seed, mnemonicToKeyPair, onAuthenticatedParameters, onAuthenticationFailedParameters, onAwarenessChangeParameters, onAwarenessUpdateParameters, onCloseParameters, onDisconnectParameters, onMessageParameters, onOpenParameters, onOutgoingMessageParameters, onServerErrorParameters, onStatelessParameters, onStatusParameters, onSubdocLoadedParameters, onSubdocRegisteredParameters, onSyncedParameters, onUnsyncedChangesParameters, readAuthMessage, unwrapSeed, validateMnemonic, wrapSeed, writeAuthenticated, writeAuthentication, writePermissionDenied, writeTokenSyncRequest };
3160
+ export { AbracadabraBaseProvider, AbracadabraBaseProviderConfiguration, AbracadabraClient, AbracadabraClientConfig, AbracadabraOutgoingMessageArguments, AbracadabraProvider, AbracadabraProviderConfiguration, AbracadabraWS, AbracadabraWSConfiguration, AbracadabraWebRTC, type AbracadabraWebRTCConfiguration, AbracadabraWebSocketConn, AuthMessageType, AuthorizedScope, AwarenessError, BackgroundSyncManager, type BackgroundSyncManagerOptions, BackgroundSyncPersistence, BroadcastChannelSync, CHANNEL_NAMES, type ChatChannel, ChatClient, type ChatClientTransport, type ChatMessage, type ChatReadCursor, type ChatReadReceipt, type ChatTypingEvent, CloseEvent, CompleteAbracadabraBaseProviderConfiguration, CompleteAbracadabraWSConfiguration, CompleteHocuspocusProviderConfiguration, CompleteHocuspocusProviderWebsocketConfiguration, ConnectionTimeout, Constructable, ConstructableOutgoingMessage, type CreateNotificationInput, CryptoIdentity, CryptoIdentityKeystore, DEFAULT_FILE_CHUNK_SIZE, DEFAULT_ICE_SERVERS, DataChannelRouter, DevicePairingChannel, type DevicePairingConfig, DeviceRegistrationService, type DeviceServerStatus, type DeviceTier, type DocEncryptionInfo, DocKeyManager, type DocSyncState, DocumentCache, type DocumentCacheOptions, DocumentMeta, E2EAbracadabraProvider, E2EEChannel, type E2EEIdentity, E2EOfflineStore, EffectivePermissionEntry, EffectivePermissionsResponse, EffectiveRole, EncryptedYMap, EncryptedYText, type FetchNotificationsInput, FileBlobStore, FileTransferChannel, FileTransferHandle, type FileTransferMeta, type FileTransferStatus, Forbidden, type GetChatHistoryInput, HealthStatus, HocusPocusWebSocket, HocuspocusProvider, HocuspocusProviderConfiguration, HocuspocusProviderWebsocket, HocuspocusProviderWebsocketConfiguration, HocuspocusWebSocket, type IdentityDeviceEntry, type IdentityDocConfiguration, IdentityDocProvider, type IdentityProfile, type IdentityServerEntry, type IdentitySpaceEntry, InviteRow, KEY_EXCHANGE_CHANNEL, ManualSignaling, type ManualSignalingBlob, MessageTooBig, MessageType, type NotificationReadUpdate, type NotificationRecord, NotificationsClient, OfflineStore, OutgoingMessageArguments, OutgoingMessageInterface, type PairingRequest, type PairingResult, PeerConnection, type PeerInfo, type PeerState, PendingSubdoc, PermissionEntry, PublicKeyInfo, ResetConnection, SearchIndex, SearchResult, type SendChatMessageInput, ServerInfo, type SignalingIncoming, type SignalingOutgoing, SignalingSocket, SnapshotCreateResult, SnapshotData, SnapshotForkResult, SnapshotMeta, SnapshotRestoreResult, SpaceMeta, StatesArray, SubdocMessage, SubdocRegisteredEvent, Unauthorized, UploadInfo, UploadMeta, UploadQueueEntry, UploadQueueStatus, UserProfile, WebSocketStatus, WsReadyStates, YjsDataChannel, attachUpdatedAtObserver, awarenessStatesToArray, wordlist as bip39Wordlist, decryptField, deriveIdentityDocId, deriveSeedWrappingKey, encryptField, generateMnemonic, makeEncryptedYMap, makeEncryptedYText, mnemonicToEd25519Seed, mnemonicToKeyPair, onAuthenticatedParameters, onAuthenticationFailedParameters, onAwarenessChangeParameters, onAwarenessUpdateParameters, onCloseParameters, onCompactedParameters, onDisconnectParameters, onMessageParameters, onOpenParameters, onOutgoingMessageParameters, onServerErrorParameters, onStatelessParameters, onStatusParameters, onSubdocLoadedParameters, onSubdocRegisteredParameters, onSyncedParameters, onUnsyncedChangesParameters, readAuthMessage, unwrapSeed, validateMnemonic, wrapSeed, writeAuthenticated, writeAuthentication, writePermissionDenied, writeTokenSyncRequest };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abraca/dabra",
3
- "version": "1.8.1",
3
+ "version": "1.9.1",
4
4
  "description": "abracadabra provider",
5
5
  "keywords": [
6
6
  "abracadabra",
@@ -375,9 +375,9 @@ export class AbracadabraBaseProvider extends EventEmitter {
375
375
  try {
376
376
  const parsed = JSON.parse(payload);
377
377
  if (parsed?.type === "error" && parsed.source && parsed.code) {
378
- const { source, code, message } = parsed;
379
- console.warn(`[Abracadabra] Server error: ${source} (${code}) — ${message}`);
380
- this.emit("serverError", { source, code, message: message ?? "" });
378
+ const { source, code, message, meta } = parsed;
379
+ console.warn(`[Abracadabra] Server error: ${source} (${code}) — ${message}`, meta ?? "");
380
+ this.emit("serverError", { source, code, message: message ?? "", meta });
381
381
  return;
382
382
  }
383
383
  } catch {
@@ -0,0 +1,160 @@
1
+ /**
2
+ * ContentManager — read/write document content via markdown ↔ Y.js conversion.
3
+ *
4
+ * Extracted from `mcp/tools/content.ts` and `cli/commands/content.ts`.
5
+ */
6
+ import * as Y from "yjs";
7
+ import type { PageMeta } from "./DocTypes.ts";
8
+ import { toPlain } from "./DocUtils.ts";
9
+ import {
10
+ yjsToMarkdown,
11
+ populateYDocFromMarkdown,
12
+ parseFrontmatter,
13
+ } from "./DocConverters.ts";
14
+ import type { DocumentManager } from "./DocumentManager.ts";
15
+
16
+ export interface DocumentContent {
17
+ label: string;
18
+ type?: string;
19
+ meta?: PageMeta;
20
+ title: string;
21
+ markdown: string;
22
+ children: Array<{
23
+ id: string;
24
+ label: string;
25
+ type?: string;
26
+ meta?: PageMeta;
27
+ }>;
28
+ }
29
+
30
+ export class ContentManager {
31
+ constructor(private dm: DocumentManager) {}
32
+
33
+ /**
34
+ * Read document content as markdown.
35
+ * Returns the title extracted from the TipTap documentHeader, the markdown
36
+ * body, tree metadata, and immediate children.
37
+ */
38
+ async read(docId: string): Promise<DocumentContent> {
39
+ const provider = await this.dm.getChildProvider(docId);
40
+ const fragment = provider.document.getXmlFragment("default");
41
+
42
+ const { title, markdown } = yjsToMarkdown(fragment);
43
+
44
+ // Get tree metadata + immediate children
45
+ const treeMap = this.dm.getTreeMap();
46
+ let label = title;
47
+ let type: string | undefined;
48
+ let meta: PageMeta | undefined;
49
+ let children: Array<{
50
+ id: string;
51
+ label: string;
52
+ type?: string;
53
+ meta?: PageMeta;
54
+ }> = [];
55
+
56
+ if (treeMap) {
57
+ const raw = treeMap.get(docId);
58
+ if (raw) {
59
+ const entry = toPlain(raw) as Record<string, unknown>;
60
+ label = (entry.label as string) || title;
61
+ type = entry.type as string | undefined;
62
+ meta = entry.meta as PageMeta | undefined;
63
+ }
64
+ // Collect immediate children sorted by order
65
+ treeMap.forEach((value: any, id: string) => {
66
+ if (value.parentId === docId) {
67
+ children.push({
68
+ id,
69
+ label: value.label || "Untitled",
70
+ type: value.type,
71
+ meta: value.meta,
72
+ });
73
+ }
74
+ });
75
+ children.sort((a, b) => {
76
+ const orderA = treeMap.get(a.id)?.order ?? 0;
77
+ const orderB = treeMap.get(b.id)?.order ?? 0;
78
+ return orderA - orderB;
79
+ });
80
+ }
81
+
82
+ return { label, type, meta, title, markdown, children };
83
+ }
84
+
85
+ /**
86
+ * Write markdown content to a document.
87
+ * Supports optional YAML frontmatter for title and metadata.
88
+ *
89
+ * @param mode - "replace" clears existing content first (default),
90
+ * "append" adds to the end.
91
+ */
92
+ async write(
93
+ docId: string,
94
+ markdown: string,
95
+ mode: "replace" | "append" = "replace",
96
+ ): Promise<void> {
97
+ const provider = await this.dm.getChildProvider(docId);
98
+ const doc = provider.document;
99
+ const fragment = doc.getXmlFragment("default");
100
+
101
+ // Parse optional frontmatter
102
+ const { title, meta, body } = parseFrontmatter(markdown);
103
+
104
+ // Update tree metadata if frontmatter provided title or meta
105
+ if (title || Object.keys(meta).length > 0) {
106
+ const treeMap = this.dm.getTreeMap();
107
+ const rootDoc = this.dm.rootDocument;
108
+ if (treeMap && rootDoc) {
109
+ const entry = treeMap.get(docId);
110
+ if (entry) {
111
+ rootDoc.transact(() => {
112
+ const updates: Record<string, unknown> = {
113
+ ...entry,
114
+ updatedAt: Date.now(),
115
+ };
116
+ if (title) updates.label = title;
117
+ if (Object.keys(meta).length > 0) {
118
+ updates.meta = {
119
+ ...(entry.meta ?? {}),
120
+ ...meta,
121
+ };
122
+ }
123
+ treeMap.set(docId, updates);
124
+ });
125
+ }
126
+ }
127
+ }
128
+
129
+ if (mode === "replace") {
130
+ // Clear existing content
131
+ doc.transact(() => {
132
+ while (fragment.length > 0) {
133
+ fragment.delete(0);
134
+ }
135
+ });
136
+ }
137
+
138
+ // Write new content
139
+ const contentToWrite = body || markdown;
140
+ const fallbackTitle = title || "Untitled";
141
+ populateYDocFromMarkdown(fragment, contentToWrite, fallbackTitle);
142
+ }
143
+
144
+ /**
145
+ * Get the raw Y.XmlFragment for a document (the 'default' fragment
146
+ * that TipTap uses for document content).
147
+ */
148
+ async getFragment(docId: string): Promise<Y.XmlFragment> {
149
+ const provider = await this.dm.getChildProvider(docId);
150
+ return provider.document.getXmlFragment("default");
151
+ }
152
+
153
+ /**
154
+ * Get the raw Y.Doc for a document (synced).
155
+ */
156
+ async getDoc(docId: string): Promise<Y.Doc> {
157
+ const provider = await this.dm.getChildProvider(docId);
158
+ return provider.document;
159
+ }
160
+ }