@abraca/dabra 1.8.2 → 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,6 +1160,12 @@ 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;
@@ -1805,6 +1811,13 @@ declare class FileBlobStore extends EventEmitter {
1805
1811
  * the object URL reference is stale.
1806
1812
  */
1807
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;
1808
1821
  /** Revoke the object URL and remove the blob from cache. */
1809
1822
  evictBlob(docId: string, uploadId: string): Promise<void>;
1810
1823
  /**
@@ -1831,6 +1844,31 @@ interface E2EAbracadabraProviderConfiguration extends AbracadabraProviderConfigu
1831
1844
  docKeyManager: DocKeyManager;
1832
1845
  keystore: CryptoIdentityKeystore;
1833
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;
1834
1872
  }
1835
1873
  declare class E2EAbracadabraProvider extends AbracadabraProvider {
1836
1874
  private readonly docKeyManager;
@@ -1841,6 +1879,16 @@ declare class E2EAbracadabraProvider extends AbracadabraProvider {
1841
1879
  /** E2E-encrypted offline store; created after the doc key is available. */
1842
1880
  private e2eStore;
1843
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;
1844
1892
  constructor(configuration: E2EAbracadabraProviderConfiguration);
1845
1893
  /** Fetch the doc key from the server (requires WebAuthn if not cached). */
1846
1894
  private ensureDocKey;
@@ -1851,6 +1899,17 @@ declare class E2EAbracadabraProvider extends AbracadabraProvider {
1851
1899
  /** Encrypt local updates before sending over WebSocket. */
1852
1900
  documentUpdateHandler(update: Uint8Array, origin: unknown): void;
1853
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;
1854
1913
  destroy(): void;
1855
1914
  }
1856
1915
  //#endregion
@@ -3098,4 +3157,4 @@ declare function wrapSeed(seed: Uint8Array, wrappingKeyBytes: Uint8Array): Promi
3098
3157
  */
3099
3158
  declare function unwrapSeed(ciphertext: ArrayBuffer, iv: Uint8Array, wrappingKeyBytes: Uint8Array): Promise<Uint8Array>;
3100
3159
  //#endregion
3101
- 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.2",
3
+ "version": "1.9.1",
4
4
  "description": "abracadabra provider",
5
5
  "keywords": [
6
6
  "abracadabra",
@@ -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
+ }