@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,473 @@
1
+ /**
2
+ * TreeManager — typed ORM layer over the root Y.Doc's "doc-tree" / "doc-trash"
3
+ * Y.Maps.
4
+ *
5
+ * Extracted from `mcp/tools/tree.ts` and `cli/commands/documents.ts` logic.
6
+ * All tree CRUD operations go through this class.
7
+ */
8
+ import * as Y from "yjs";
9
+ import type {
10
+ TreeEntry,
11
+ TreeNode,
12
+ TreeSearchResult,
13
+ PageMeta,
14
+ } from "./DocTypes.ts";
15
+ import { resolvePageType } from "./DocTypes.ts";
16
+ import { toPlain, normalizeRootId } from "./DocUtils.ts";
17
+ import type { DocumentManager } from "./DocumentManager.ts";
18
+
19
+ export class TreeManager {
20
+ constructor(private dm: DocumentManager) {}
21
+
22
+ // ── Reading ───────────────────────────────────────────────────────────────
23
+
24
+ /** Read all tree entries as plain objects. */
25
+ readEntries(): TreeEntry[] {
26
+ const treeMap = this.dm.getTreeMap();
27
+ if (!treeMap) return [];
28
+
29
+ const entries: TreeEntry[] = [];
30
+ treeMap.forEach((raw: unknown, id: string) => {
31
+ const value = toPlain(raw) as Record<string, unknown>;
32
+ if (typeof value !== "object" || value === null) return;
33
+ entries.push({
34
+ id,
35
+ label: (value.label as string) || "Untitled",
36
+ parentId: (value.parentId as string | null) ?? null,
37
+ order: (value.order as number) ?? 0,
38
+ type: value.type as string | undefined,
39
+ meta: value.meta as PageMeta | undefined,
40
+ createdAt: value.createdAt as number | undefined,
41
+ updatedAt: value.updatedAt as number | undefined,
42
+ });
43
+ });
44
+ return entries;
45
+ }
46
+
47
+ /** Get immediate children of a parent (sorted by order). */
48
+ childrenOf(parentId: string | null): TreeEntry[] {
49
+ const normalized = normalizeRootId(parentId, this.dm.rootDocId);
50
+ return this.readEntries()
51
+ .filter((e) => e.parentId === normalized)
52
+ .sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
53
+ }
54
+
55
+ /** Get all descendants recursively. */
56
+ descendantsOf(parentId: string | null): TreeEntry[] {
57
+ const normalized = normalizeRootId(parentId, this.dm.rootDocId);
58
+ const entries = this.readEntries();
59
+ const result: TreeEntry[] = [];
60
+ const visited = new Set<string>();
61
+
62
+ const collect = (pid: string | null) => {
63
+ if (pid !== null && visited.has(pid)) return;
64
+ if (pid !== null) visited.add(pid);
65
+ for (const child of entries
66
+ .filter((e) => e.parentId === pid)
67
+ .sort((a, b) => (a.order ?? 0) - (b.order ?? 0))) {
68
+ result.push(child);
69
+ collect(child.id);
70
+ }
71
+ };
72
+ collect(normalized);
73
+ return result;
74
+ }
75
+
76
+ /** Build nested tree JSON. */
77
+ buildTree(
78
+ rootId?: string | null,
79
+ maxDepth = 3,
80
+ ): TreeNode[] {
81
+ const normalized = normalizeRootId(
82
+ rootId ?? null,
83
+ this.dm.rootDocId,
84
+ );
85
+ const entries = this.readEntries();
86
+ return this._buildTree(entries, normalized, maxDepth, 0, new Set());
87
+ }
88
+
89
+ private _buildTree(
90
+ entries: TreeEntry[],
91
+ rootId: string | null,
92
+ maxDepth: number,
93
+ currentDepth: number,
94
+ visited: Set<string>,
95
+ ): TreeNode[] {
96
+ if (maxDepth >= 0 && currentDepth >= maxDepth) return [];
97
+ const children = entries
98
+ .filter((e) => e.parentId === rootId)
99
+ .sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
100
+
101
+ return children
102
+ .filter((e) => !visited.has(e.id))
103
+ .map((entry) => {
104
+ const next = new Set(visited);
105
+ next.add(entry.id);
106
+ return {
107
+ id: entry.id,
108
+ label: entry.label,
109
+ type: entry.type,
110
+ meta: entry.meta,
111
+ order: entry.order,
112
+ children: this._buildTree(
113
+ entries,
114
+ entry.id,
115
+ maxDepth,
116
+ currentDepth + 1,
117
+ next,
118
+ ),
119
+ };
120
+ });
121
+ }
122
+
123
+ /** Find a single entry by ID. */
124
+ get(docId: string): TreeEntry | null {
125
+ const treeMap = this.dm.getTreeMap();
126
+ if (!treeMap) return null;
127
+ const raw = treeMap.get(docId);
128
+ if (!raw) return null;
129
+ const value = toPlain(raw) as Record<string, unknown>;
130
+ if (typeof value !== "object" || value === null) return null;
131
+ return {
132
+ id: docId,
133
+ label: (value.label as string) || "Untitled",
134
+ parentId: (value.parentId as string | null) ?? null,
135
+ order: (value.order as number) ?? 0,
136
+ type: value.type as string | undefined,
137
+ meta: value.meta as PageMeta | undefined,
138
+ createdAt: value.createdAt as number | undefined,
139
+ updatedAt: value.updatedAt as number | undefined,
140
+ };
141
+ }
142
+
143
+ /** Search by label (case-insensitive substring match). */
144
+ find(
145
+ query: string,
146
+ rootId?: string | null,
147
+ ): TreeSearchResult[] {
148
+ const entries = this.readEntries();
149
+ const lowerQuery = query.toLowerCase();
150
+
151
+ const normalized = normalizeRootId(
152
+ rootId ?? null,
153
+ this.dm.rootDocId,
154
+ );
155
+ const searchSet = normalized
156
+ ? this.descendantsOf(normalized)
157
+ : entries;
158
+
159
+ const matches = searchSet.filter((e) =>
160
+ e.label.toLowerCase().includes(lowerQuery),
161
+ );
162
+
163
+ const byId = new Map(entries.map((e) => [e.id, e]));
164
+ return matches.map((entry) => {
165
+ const path: string[] = [];
166
+ let current = entry.parentId;
167
+ const visited = new Set<string>();
168
+ while (current && !visited.has(current)) {
169
+ visited.add(current);
170
+ const parent = byId.get(current);
171
+ if (!parent) break;
172
+ path.unshift(parent.label);
173
+ current = parent.parentId;
174
+ }
175
+ return {
176
+ id: entry.id,
177
+ label: entry.label,
178
+ type: entry.type,
179
+ meta: entry.meta,
180
+ path,
181
+ };
182
+ });
183
+ }
184
+
185
+ // ── Writing ──────────────────────────────────────────────────────────────
186
+
187
+ /** Create a new document in the tree. Returns the new entry. */
188
+ create(opts: {
189
+ parentId?: string | null;
190
+ label: string;
191
+ type?: string;
192
+ meta?: Partial<PageMeta>;
193
+ }): TreeEntry {
194
+ const treeMap = this.dm.getTreeMap();
195
+ const rootDoc = this.dm.rootDocument;
196
+ if (!treeMap || !rootDoc) {
197
+ throw new Error("Not connected");
198
+ }
199
+
200
+ const id = crypto.randomUUID();
201
+ const normalizedParent = normalizeRootId(
202
+ opts.parentId ?? null,
203
+ this.dm.rootDocId,
204
+ );
205
+ const now = Date.now();
206
+
207
+ rootDoc.transact(() => {
208
+ treeMap.set(id, {
209
+ label: opts.label,
210
+ parentId: normalizedParent,
211
+ order: now,
212
+ type: opts.type,
213
+ meta: opts.meta as PageMeta,
214
+ createdAt: now,
215
+ updatedAt: now,
216
+ });
217
+ });
218
+
219
+ return {
220
+ id,
221
+ label: opts.label,
222
+ parentId: normalizedParent,
223
+ order: now,
224
+ type: opts.type,
225
+ meta: opts.meta as PageMeta | undefined,
226
+ createdAt: now,
227
+ updatedAt: now,
228
+ };
229
+ }
230
+
231
+ /**
232
+ * Create a document and auto-apply renderer defaults from the page type's
233
+ * `defaultMetaFields` (toggle fields with `default` values).
234
+ */
235
+ createWithTypeDefaults(opts: {
236
+ parentId?: string | null;
237
+ label: string;
238
+ type: string;
239
+ extraMeta?: Partial<PageMeta>;
240
+ }): TreeEntry {
241
+ const typeInfo = resolvePageType(opts.type);
242
+ const autoMeta: Record<string, unknown> = {};
243
+ if (typeInfo?.defaultMetaFields) {
244
+ for (const field of typeInfo.defaultMetaFields) {
245
+ if (field.type === "toggle" && field.key && field.default !== undefined) {
246
+ autoMeta[field.key] = field.default;
247
+ }
248
+ }
249
+ }
250
+ const mergedMeta =
251
+ Object.keys(autoMeta).length > 0 || opts.extraMeta
252
+ ? { ...autoMeta, ...(opts.extraMeta ?? {}) }
253
+ : undefined;
254
+ return this.create({
255
+ parentId: opts.parentId,
256
+ label: opts.label,
257
+ type: opts.type,
258
+ meta: mergedMeta,
259
+ });
260
+ }
261
+
262
+ /** Rename a document. */
263
+ rename(docId: string, label: string): void {
264
+ const treeMap = this.dm.getTreeMap();
265
+ const rootDoc = this.dm.rootDocument;
266
+ if (!treeMap || !rootDoc) throw new Error("Not connected");
267
+
268
+ const raw = treeMap.get(docId);
269
+ if (!raw) throw new Error(`Document ${docId} not found`);
270
+
271
+ const entry = toPlain(raw) as Record<string, unknown>;
272
+ rootDoc.transact(() => {
273
+ treeMap.set(docId, { ...entry, label, updatedAt: Date.now() });
274
+ });
275
+ }
276
+
277
+ /** Move a document to a new parent. */
278
+ move(
279
+ docId: string,
280
+ newParentId?: string | null,
281
+ order?: number,
282
+ ): void {
283
+ const treeMap = this.dm.getTreeMap();
284
+ const rootDoc = this.dm.rootDocument;
285
+ if (!treeMap || !rootDoc) throw new Error("Not connected");
286
+
287
+ const raw = treeMap.get(docId);
288
+ if (!raw) throw new Error(`Document ${docId} not found`);
289
+
290
+ const entry = toPlain(raw) as Record<string, unknown>;
291
+ rootDoc.transact(() => {
292
+ treeMap.set(docId, {
293
+ ...entry,
294
+ parentId: normalizeRootId(
295
+ newParentId ?? null,
296
+ this.dm.rootDocId,
297
+ ),
298
+ order: order ?? Date.now(),
299
+ updatedAt: Date.now(),
300
+ });
301
+ });
302
+ }
303
+
304
+ /** Change the page type of a document. */
305
+ changeType(docId: string, type: string): void {
306
+ const treeMap = this.dm.getTreeMap();
307
+ const rootDoc = this.dm.rootDocument;
308
+ if (!treeMap || !rootDoc) throw new Error("Not connected");
309
+
310
+ const raw = treeMap.get(docId);
311
+ if (!raw) throw new Error(`Document ${docId} not found`);
312
+
313
+ const entry = toPlain(raw) as Record<string, unknown>;
314
+ rootDoc.transact(() => {
315
+ treeMap.set(docId, { ...entry, type, updatedAt: Date.now() });
316
+ });
317
+ }
318
+
319
+ /**
320
+ * Soft-delete a document and descendants (move to trash).
321
+ * @returns count of deleted documents
322
+ */
323
+ delete(docId: string): number {
324
+ const treeMap = this.dm.getTreeMap();
325
+ const trashMap = this.dm.getTrashMap();
326
+ const rootDoc = this.dm.rootDocument;
327
+ if (!treeMap || !trashMap || !rootDoc) {
328
+ throw new Error("Not connected");
329
+ }
330
+
331
+ const entries = this.readEntries();
332
+ const toDelete = [
333
+ docId,
334
+ ...this._descendantIds(entries, docId),
335
+ ];
336
+
337
+ const now = Date.now();
338
+ rootDoc.transact(() => {
339
+ for (const nid of toDelete) {
340
+ const raw = treeMap.get(nid);
341
+ if (!raw) continue;
342
+ const entry = toPlain(raw) as Record<string, unknown>;
343
+ trashMap.set(nid, {
344
+ label: entry.label || "Untitled",
345
+ parentId: entry.parentId ?? null,
346
+ order: entry.order ?? 0,
347
+ type: entry.type,
348
+ meta: entry.meta,
349
+ deletedAt: now,
350
+ });
351
+ treeMap.delete(nid);
352
+ }
353
+ });
354
+
355
+ return toDelete.length;
356
+ }
357
+
358
+ /** Duplicate a document (shallow clone). Returns the new entry. */
359
+ duplicate(docId: string): TreeEntry {
360
+ const treeMap = this.dm.getTreeMap();
361
+ if (!treeMap) throw new Error("Not connected");
362
+
363
+ const raw = treeMap.get(docId);
364
+ if (!raw) throw new Error(`Document ${docId} not found`);
365
+
366
+ const entry = toPlain(raw) as Record<string, unknown>;
367
+ const newId = crypto.randomUUID();
368
+ const now = Date.now();
369
+ const newLabel = ((entry.label as string) || "Untitled") + " (copy)";
370
+ treeMap.set(newId, {
371
+ ...entry,
372
+ label: newLabel,
373
+ order: now,
374
+ createdAt: now,
375
+ updatedAt: now,
376
+ });
377
+
378
+ return {
379
+ id: newId,
380
+ label: newLabel,
381
+ parentId: (entry.parentId as string | null) ?? null,
382
+ order: now,
383
+ type: entry.type as string | undefined,
384
+ meta: entry.meta as PageMeta | undefined,
385
+ createdAt: now,
386
+ updatedAt: now,
387
+ };
388
+ }
389
+
390
+ /** Restore a document from trash back into the tree. */
391
+ restore(docId: string): void {
392
+ const treeMap = this.dm.getTreeMap();
393
+ const trashMap = this.dm.getTrashMap();
394
+ const rootDoc = this.dm.rootDocument;
395
+ if (!treeMap || !trashMap || !rootDoc) {
396
+ throw new Error("Not connected");
397
+ }
398
+
399
+ const raw = trashMap.get(docId);
400
+ if (!raw) throw new Error(`Document ${docId} not found in trash`);
401
+
402
+ const entry = toPlain(raw) as Record<string, unknown>;
403
+ const now = Date.now();
404
+ rootDoc.transact(() => {
405
+ treeMap.set(docId, {
406
+ label: entry.label || "Untitled",
407
+ parentId: entry.parentId ?? null,
408
+ order: entry.order ?? now,
409
+ type: entry.type,
410
+ meta: entry.meta,
411
+ createdAt: entry.createdAt ?? now,
412
+ updatedAt: now,
413
+ });
414
+ trashMap.delete(docId);
415
+ });
416
+ }
417
+
418
+ /** List trashed documents. */
419
+ listTrash(): TreeEntry[] {
420
+ const trashMap = this.dm.getTrashMap();
421
+ if (!trashMap) return [];
422
+
423
+ const entries: TreeEntry[] = [];
424
+ trashMap.forEach((raw: unknown, id: string) => {
425
+ const value = toPlain(raw) as Record<string, unknown>;
426
+ if (typeof value !== "object" || value === null) return;
427
+ entries.push({
428
+ id,
429
+ label: (value.label as string) || "Untitled",
430
+ parentId: (value.parentId as string | null) ?? null,
431
+ order: (value.order as number) ?? 0,
432
+ type: value.type as string | undefined,
433
+ meta: value.meta as PageMeta | undefined,
434
+ });
435
+ });
436
+ return entries;
437
+ }
438
+
439
+ /** Get enabled plugin names from space-plugins map. */
440
+ getEnabledPlugins(): string[] {
441
+ const pluginsMap = this.dm.getPluginsMap();
442
+ if (!pluginsMap) return [];
443
+
444
+ const plugins: string[] = [];
445
+ pluginsMap.forEach((raw: unknown, key: string) => {
446
+ const value = toPlain(raw) as Record<string, unknown>;
447
+ if (value && value.enabled) {
448
+ plugins.push(key);
449
+ }
450
+ });
451
+ return plugins;
452
+ }
453
+
454
+ // ── Internal helpers ──────────────────────────────────────────────────────
455
+
456
+ private _descendantIds(
457
+ entries: TreeEntry[],
458
+ parentId: string,
459
+ ): string[] {
460
+ const result: string[] = [];
461
+ const visited = new Set<string>();
462
+ const collect = (pid: string) => {
463
+ if (visited.has(pid)) return;
464
+ visited.add(pid);
465
+ for (const child of entries.filter((e) => e.parentId === pid)) {
466
+ result.push(child.id);
467
+ collect(child.id);
468
+ }
469
+ };
470
+ collect(parentId);
471
+ return result;
472
+ }
473
+ }
@@ -8,8 +8,9 @@
8
8
  * This propagates "last edited" timestamps to all peers via the root CRDT,
9
9
  * without requiring any server-side changes.
10
10
  *
11
- * A trailing-edge throttle (default 5 s) limits writes to avoid CRDT bloat
12
- * on the root doc during rapid typing.
11
+ * Leading-edge + trailing-edge throttle (default 5 s window): the first
12
+ * update flushes immediately so the "last edited" badge updates instantly;
13
+ * follow-up updates within the window are coalesced into one trailing flush.
13
14
  */
14
15
 
15
16
  import * as Y from "yjs";
@@ -19,18 +20,15 @@ import type { OfflineStore } from "./OfflineStore.ts";
19
20
  * Attach an observer that writes `updatedAt` to the root doc-tree entry for
20
21
  * `childDocId` whenever the child doc receives a non-offline update.
21
22
  *
22
- * Writes are throttled: the first qualifying update records the timestamp;
23
- * a trailing-edge timer flushes it to the tree map after `throttleMs`.
24
- *
25
23
  * @param treeMap The root doc's "doc-tree" Y.Map.
26
24
  * @param childDocId The child document's UUID (key in treeMap).
27
25
  * @param childDoc The child Y.Doc to observe.
28
26
  * @param offlineStore The child provider's OfflineStore (used to detect
29
27
  * offline-replay origins and skip them). Pass null when
30
28
  * the offline store is disabled.
31
- * @param options Optional config. `throttleMs` controls the write
32
- * interval (default 5000).
33
- * @returns Cleanup function — call on provider destroy. Flushes
29
+ * @param options Optional config. `throttleMs` controls the throttle
30
+ * window (default 5000).
31
+ * @returns Cleanup function — call on provider destroy. Flushes
34
32
  * any pending write before detaching.
35
33
  */
36
34
  export function attachUpdatedAtObserver(
@@ -42,34 +40,39 @@ export function attachUpdatedAtObserver(
42
40
  ): () => void {
43
41
  const throttleMs = options?.throttleMs ?? 5000;
44
42
 
45
- let latestTs = 0;
43
+ let lastFlushedAt = 0;
44
+ let pendingTs = 0;
46
45
  let timer: ReturnType<typeof setTimeout> | null = null;
47
46
 
48
- function flush(): void {
49
- if (latestTs === 0) return;
50
- const ts = latestTs;
51
- latestTs = 0;
52
- timer = null;
53
-
47
+ function writeTs(ts: number): void {
54
48
  const raw = treeMap.get(childDocId);
55
49
  if (!raw) return;
56
-
57
- // Guard: if the entry is a nested Y.Map (possible after Yrs
58
- // document compaction), convert to plain object so spread works.
59
50
  const entry = raw instanceof Y.Map ? (raw as any).toJSON() : raw;
60
51
  treeMap.set(childDocId, { ...entry, updatedAt: ts });
52
+ lastFlushedAt = ts;
53
+ }
54
+
55
+ function flushPending(): void {
56
+ timer = null;
57
+ if (pendingTs === 0) return;
58
+ const ts = pendingTs;
59
+ pendingTs = 0;
60
+ writeTs(ts);
61
61
  }
62
62
 
63
63
  function handler(_update: Uint8Array, origin: unknown): void {
64
- // Skip updates replayed from the local offline store — they represent
65
- // content that was already "seen" and shouldn't advance updatedAt.
66
64
  if (offlineStore !== null && origin === offlineStore) return;
67
65
 
68
- latestTs = Date.now();
69
-
70
- // Schedule a trailing-edge flush if none is pending.
66
+ const now = Date.now();
67
+ if (now - lastFlushedAt >= throttleMs) {
68
+ // Leading edge flush immediately.
69
+ writeTs(now);
70
+ return;
71
+ }
72
+ // Inside the throttle window — coalesce into a trailing flush.
73
+ pendingTs = now;
71
74
  if (timer === null) {
72
- timer = setTimeout(flush, throttleMs);
75
+ timer = setTimeout(flushPending, throttleMs - (now - lastFlushedAt));
73
76
  }
74
77
  }
75
78
 
@@ -79,7 +82,7 @@ export function attachUpdatedAtObserver(
79
82
  childDoc.off("update", handler);
80
83
  if (timer !== null) {
81
84
  clearTimeout(timer);
82
- flush(); // persist the last pending timestamp
85
+ flushPending();
83
86
  }
84
87
  };
85
88
  }
package/src/index.ts CHANGED
@@ -37,8 +37,52 @@ export type {
37
37
  } from "./IdentityDoc.ts";
38
38
  export { DeviceRegistrationService } from "./DeviceRegistrationService.ts";
39
39
  export { ChatClient } from "./ChatClient.ts";
40
- export type { ChatClientTransport } from "./ChatClient.ts";
40
+ export { RpcClient, RpcError, RPC_PREFIX } from "./RpcClient.ts";
41
+ export type {
42
+ RpcKind,
43
+ RpcFrame,
44
+ RpcErrorPayload,
45
+ RpcErrorCode,
46
+ RpcTransport,
47
+ RpcTarget,
48
+ RpcCallOptions,
49
+ RpcCallHandle,
50
+ RpcHandler,
51
+ RpcHandlerContext,
52
+ } from "./RpcClient.ts";
53
+ export type {
54
+ ChatClientTransport,
55
+ SendMessageInput,
56
+ SendMessageResult,
57
+ EditMessageInput,
58
+ DeleteMessageInput,
59
+ MarkReadInput as ChatMarkReadInput,
60
+ } from "./ChatClient.ts";
61
+ export {
62
+ EncryptedChatClient,
63
+ ChannelKeyResolver,
64
+ encryptChatContent,
65
+ decryptChatContent,
66
+ isEncryptedContent,
67
+ } from "./EncryptedChatClient.ts";
68
+ export {
69
+ foldRecords,
70
+ recordFromYAny,
71
+ } from "./messageRecord.ts";
72
+ export type {
73
+ MessageRecord,
74
+ FoldedMessage,
75
+ } from "./messageRecord.ts";
41
76
  export { NotificationsClient } from "./NotificationsClient.ts";
77
+ export type {
78
+ InboxEntry,
79
+ FetchInboxInput,
80
+ MarkReadInput as InboxMarkReadInput,
81
+ } from "./NotificationsClient.ts";
82
+ // Legacy type re-exports — wire-shape types from the deprecated chat:* / notify:*
83
+ // protocol that consumers may still import. The dashboard's reactive layer
84
+ // reads/writes these shapes directly off Y.Array entries on channel-period and
85
+ // inbox docs, independently of ChatClient/NotificationsClient.
42
86
  export type {
43
87
  ChatMessage,
44
88
  ChatChannel,
@@ -62,3 +106,29 @@ export {
62
106
  unwrapSeed,
63
107
  bip39Wordlist,
64
108
  } from "./MnemonicKeyDerivation.ts";
109
+ export { DocumentManager } from "./DocumentManager.ts";
110
+ export type { DocumentManagerConfig } from "./DocumentManager.ts";
111
+ export { TreeManager } from "./TreeManager.ts";
112
+ export { ContentManager } from "./ContentManager.ts";
113
+ export type { DocumentContent } from "./ContentManager.ts";
114
+ export { MetaManager } from "./MetaManager.ts";
115
+ export type { DocumentMetaInfo } from "./MetaManager.ts";
116
+ export * from "./DocTypes.ts";
117
+ export { waitForSync, withTimeout, normalizeRootId, toPlain } from "./DocUtils.ts";
118
+ export {
119
+ yjsToMarkdown,
120
+ populateYDocFromMarkdown,
121
+ parseFrontmatter,
122
+ filenameToLabel,
123
+ buildHeadingElement,
124
+ buildParagraphElement,
125
+ buildBulletListElement,
126
+ buildOrderedListElement,
127
+ buildTaskListElement,
128
+ buildCodeBlockElement,
129
+ buildBlockquoteElement,
130
+ buildHorizontalRuleElement,
131
+ buildBlocksFromMarkdown,
132
+ readBlocksFromFragment,
133
+ } from "./DocConverters.ts";
134
+ export type { DocumentBlock } from "./DocConverters.ts";