@abraca/dabra 1.8.2 → 2.0.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.
Files changed (37) hide show
  1. package/dist/abracadabra-provider.cjs +12722 -9050
  2. package/dist/abracadabra-provider.cjs.map +1 -1
  3. package/dist/abracadabra-provider.esm.js +12683 -9061
  4. package/dist/abracadabra-provider.esm.js.map +1 -1
  5. package/dist/index.d.ts +1485 -118
  6. package/package.json +1 -1
  7. package/src/AbracadabraBaseProvider.ts +51 -2
  8. package/src/AbracadabraClient.ts +516 -66
  9. package/src/AbracadabraProvider.ts +22 -7
  10. package/src/AbracadabraWS.ts +1 -1
  11. package/src/ChatClient.ts +193 -113
  12. package/src/ContentManager.ts +228 -0
  13. package/src/CryptoIdentityKeystore.ts +3 -3
  14. package/src/DocConverters.ts +1862 -0
  15. package/src/DocKeyManager.ts +60 -12
  16. package/src/DocTypes.ts +628 -0
  17. package/src/DocUtils.ts +89 -0
  18. package/src/DocumentManager.ts +319 -0
  19. package/src/E2EAbracadabraProvider.ts +189 -0
  20. package/src/EncryptedChatClient.ts +173 -0
  21. package/src/EncryptedY.ts +2 -2
  22. package/src/FileBlobStore.ts +10 -0
  23. package/src/IdentityDoc.ts +25 -0
  24. package/src/MetaManager.ts +100 -0
  25. package/src/MnemonicKeyDerivation.ts +4 -4
  26. package/src/NotificationsClient.ts +120 -98
  27. package/src/OutgoingMessages/SubdocMessage.ts +2 -2
  28. package/src/RpcClient.ts +659 -0
  29. package/src/TreeManager.ts +473 -0
  30. package/src/TreeTimestamps.ts +28 -25
  31. package/src/index.ts +71 -1
  32. package/src/messageRecord.ts +121 -0
  33. package/src/types.ts +174 -16
  34. package/src/webrtc/AbracadabraWebRTC.ts +2 -2
  35. package/src/webrtc/DataChannelRouter.ts +2 -2
  36. package/src/webrtc/E2EEChannel.ts +3 -3
  37. package/src/webrtc/FileTransferChannel.ts +9 -2
@@ -0,0 +1,228 @@
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
+ buildHeadingElement,
14
+ buildParagraphElement,
15
+ buildBulletListElement,
16
+ buildOrderedListElement,
17
+ buildTaskListElement,
18
+ buildCodeBlockElement,
19
+ buildBlockquoteElement,
20
+ buildHorizontalRuleElement,
21
+ buildBlocksFromMarkdown,
22
+ readBlocksFromFragment,
23
+ type DocumentBlock,
24
+ } from "./DocConverters.ts";
25
+ export type { DocumentBlock } from "./DocConverters.ts";
26
+ import type { DocumentManager } from "./DocumentManager.ts";
27
+
28
+ export interface DocumentContent {
29
+ label: string;
30
+ type?: string;
31
+ meta?: PageMeta;
32
+ title: string;
33
+ markdown: string;
34
+ children: Array<{
35
+ id: string;
36
+ label: string;
37
+ type?: string;
38
+ meta?: PageMeta;
39
+ }>;
40
+ }
41
+
42
+ export class ContentManager {
43
+ constructor(private dm: DocumentManager) {}
44
+
45
+ /**
46
+ * Read document content as markdown.
47
+ * Returns the title extracted from the TipTap documentHeader, the markdown
48
+ * body, tree metadata, and immediate children.
49
+ */
50
+ async read(docId: string): Promise<DocumentContent> {
51
+ const provider = await this.dm.getChildProvider(docId);
52
+ const fragment = provider.document.getXmlFragment("default");
53
+
54
+ const { title, markdown } = yjsToMarkdown(fragment);
55
+
56
+ // Get tree metadata + immediate children
57
+ const treeMap = this.dm.getTreeMap();
58
+ let label = title;
59
+ let type: string | undefined;
60
+ let meta: PageMeta | undefined;
61
+ const childrenWithOrder: Array<{
62
+ id: string;
63
+ label: string;
64
+ type?: string;
65
+ meta?: PageMeta;
66
+ order: number;
67
+ }> = [];
68
+
69
+ if (treeMap) {
70
+ const raw = treeMap.get(docId);
71
+ if (raw) {
72
+ const entry = toPlain(raw) as Record<string, unknown>;
73
+ label = (entry.label as string) || title;
74
+ type = entry.type as string | undefined;
75
+ meta = entry.meta as PageMeta | undefined;
76
+ }
77
+ // Collect immediate children sorted by order
78
+ treeMap.forEach((rawChild: unknown, id: string) => {
79
+ const value = toPlain(rawChild) as Record<string, unknown>;
80
+ if (value && value.parentId === docId) {
81
+ childrenWithOrder.push({
82
+ id,
83
+ label: (value.label as string) || "Untitled",
84
+ type: value.type as string | undefined,
85
+ meta: value.meta as PageMeta | undefined,
86
+ order: (value.order as number) ?? 0,
87
+ });
88
+ }
89
+ });
90
+ childrenWithOrder.sort((a, b) => a.order - b.order);
91
+ }
92
+
93
+ const children = childrenWithOrder.map(({ id, label, type, meta }) => ({
94
+ id,
95
+ label,
96
+ type,
97
+ meta,
98
+ }));
99
+
100
+ return { label, type, meta, title, markdown, children };
101
+ }
102
+
103
+ /**
104
+ * Write markdown content to a document.
105
+ * Supports optional YAML frontmatter for title and metadata.
106
+ *
107
+ * @param mode - "replace" clears existing content first (default),
108
+ * "append" adds to the end.
109
+ */
110
+ async write(
111
+ docId: string,
112
+ markdown: string,
113
+ mode: "replace" | "append" = "replace",
114
+ ): Promise<void> {
115
+ const provider = await this.dm.getChildProvider(docId);
116
+ const doc = provider.document;
117
+ const fragment = doc.getXmlFragment("default");
118
+
119
+ // Parse optional frontmatter
120
+ const { title, meta, body } = parseFrontmatter(markdown);
121
+
122
+ // Update tree metadata if frontmatter provided title or meta
123
+ if (title || Object.keys(meta).length > 0) {
124
+ const treeMap = this.dm.getTreeMap();
125
+ const rootDoc = this.dm.rootDocument;
126
+ if (treeMap && rootDoc) {
127
+ const entry = treeMap.get(docId);
128
+ if (entry) {
129
+ rootDoc.transact(() => {
130
+ const updates: Record<string, unknown> = {
131
+ ...entry,
132
+ updatedAt: Date.now(),
133
+ };
134
+ if (title) updates.label = title;
135
+ if (Object.keys(meta).length > 0) {
136
+ updates.meta = {
137
+ ...(entry.meta ?? {}),
138
+ ...meta,
139
+ };
140
+ }
141
+ treeMap.set(docId, updates);
142
+ });
143
+ }
144
+ }
145
+ }
146
+
147
+ if (mode === "replace") {
148
+ // Clear existing content
149
+ doc.transact(() => {
150
+ while (fragment.length > 0) {
151
+ fragment.delete(0);
152
+ }
153
+ });
154
+ }
155
+
156
+ // Write new content
157
+ const contentToWrite = body || markdown;
158
+ const fallbackTitle = title || "Untitled";
159
+ populateYDocFromMarkdown(fragment, contentToWrite, fallbackTitle);
160
+ }
161
+
162
+ private async _appendElements(docId: string, els: Y.XmlElement[]): Promise<void> {
163
+ const provider = await this.dm.getChildProvider(docId);
164
+ const doc = provider.document;
165
+ const fragment = doc.getXmlFragment("default");
166
+ doc.transact(() => {
167
+ fragment.insert(fragment.length, els);
168
+ });
169
+ }
170
+
171
+ async appendHeading(docId: string, text: string, opts?: { level?: 1|2|3|4|5|6 }): Promise<void> {
172
+ return this._appendElements(docId, [buildHeadingElement(text, opts?.level)]);
173
+ }
174
+
175
+ async appendParagraph(docId: string, text: string): Promise<void> {
176
+ return this._appendElements(docId, [buildParagraphElement(text)]);
177
+ }
178
+
179
+ async appendBulletList(docId: string, items: string[]): Promise<void> {
180
+ return this._appendElements(docId, [buildBulletListElement(items)]);
181
+ }
182
+
183
+ async appendOrderedList(docId: string, items: string[]): Promise<void> {
184
+ return this._appendElements(docId, [buildOrderedListElement(items)]);
185
+ }
186
+
187
+ async appendTaskList(docId: string, items: Array<{ text: string; checked?: boolean }>): Promise<void> {
188
+ return this._appendElements(docId, [buildTaskListElement(items)]);
189
+ }
190
+
191
+ async appendCodeBlock(docId: string, code: string, opts?: { language?: string }): Promise<void> {
192
+ return this._appendElements(docId, [buildCodeBlockElement(code, opts?.language)]);
193
+ }
194
+
195
+ async appendBlockquote(docId: string, text: string): Promise<void> {
196
+ return this._appendElements(docId, [buildBlockquoteElement(text)]);
197
+ }
198
+
199
+ async appendHorizontalRule(docId: string): Promise<void> {
200
+ return this._appendElements(docId, [buildHorizontalRuleElement()]);
201
+ }
202
+
203
+ async appendMarkdown(docId: string, markdown: string): Promise<void> {
204
+ return this._appendElements(docId, buildBlocksFromMarkdown(markdown));
205
+ }
206
+
207
+ async getBlocks(docId: string): Promise<DocumentBlock[]> {
208
+ const provider = await this.dm.getChildProvider(docId);
209
+ return readBlocksFromFragment(provider.document.getXmlFragment("default"));
210
+ }
211
+
212
+ /**
213
+ * Get the raw Y.XmlFragment for a document (the 'default' fragment
214
+ * that TipTap uses for document content).
215
+ */
216
+ async getFragment(docId: string): Promise<Y.XmlFragment> {
217
+ const provider = await this.dm.getChildProvider(docId);
218
+ return provider.document.getXmlFragment("default");
219
+ }
220
+
221
+ /**
222
+ * Get the raw Y.Doc for a document (synced).
223
+ */
224
+ async getDoc(docId: string): Promise<Y.Doc> {
225
+ const provider = await this.dm.getChildProvider(docId);
226
+ return provider.document;
227
+ }
228
+ }
@@ -357,7 +357,7 @@ export class CryptoIdentityKeystore {
357
357
  } else {
358
358
  const all = await dbGetAll(db);
359
359
  if (all.length > 0) {
360
- await dbPut(db, all[0].key, { ...all[0].value, username });
360
+ await dbPut(db, String(all[0].key), { ...all[0].value, username });
361
361
  }
362
362
  }
363
363
  } finally {
@@ -557,7 +557,7 @@ export class CryptoIdentityKeystore {
557
557
  challenge: crypto.getRandomValues(new Uint8Array(32)),
558
558
  rp: { id: rpId, name: rpName },
559
559
  user: {
560
- id: fromBase64url(kp.publicKeyB64),
560
+ id: fromBase64url(kp.publicKeyB64) as BufferSource,
561
561
  name: kp.publicKeyB64.slice(0, 16),
562
562
  displayName: kp.publicKeyB64.slice(0, 16),
563
563
  },
@@ -663,7 +663,7 @@ export class CryptoIdentityKeystore {
663
663
 
664
664
  if (credentialIdHint) {
665
665
  allowCredentials.push({
666
- id: fromBase64url(credentialIdHint),
666
+ id: fromBase64url(credentialIdHint) as BufferSource,
667
667
  type: "public-key",
668
668
  });
669
669
  } else {