@abraca/dabra 2.25.0 → 2.27.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 +38 -50
- package/dist/abracadabra-provider.cjs.map +1 -1
- package/dist/abracadabra-provider.esm.js +35 -47
- package/dist/abracadabra-provider.esm.js.map +1 -1
- package/dist/index.d.ts +13 -10
- package/package.json +2 -2
- package/src/DocConverters.ts +13 -16
- package/src/DocumentManager.ts +13 -10
- package/src/TreeTimestamps.ts +17 -8
package/dist/index.d.ts
CHANGED
|
@@ -3189,7 +3189,7 @@ declare function makeEncryptedYText(ydoc: Y.Doc, fieldName: string, docKey: Cryp
|
|
|
3189
3189
|
//#region packages/provider/src/TreeTimestamps.d.ts
|
|
3190
3190
|
/**
|
|
3191
3191
|
* Attach an observer that writes `updatedAt` to the root doc-tree entry for
|
|
3192
|
-
* `childDocId` whenever the child doc receives a
|
|
3192
|
+
* `childDocId` whenever the child doc receives a local edit.
|
|
3193
3193
|
*
|
|
3194
3194
|
* @param treeMap The root doc's "doc-tree" Y.Map.
|
|
3195
3195
|
* @param childDocId The child document's UUID (key in treeMap).
|
|
@@ -5280,13 +5280,16 @@ interface DocumentManagerConfig {
|
|
|
5280
5280
|
*/
|
|
5281
5281
|
rootDocId?: string;
|
|
5282
5282
|
/**
|
|
5283
|
-
*
|
|
5284
|
-
*
|
|
5285
|
-
*
|
|
5286
|
-
*
|
|
5287
|
-
*
|
|
5288
|
-
*
|
|
5289
|
-
*
|
|
5283
|
+
* TreeManager's in-memory parent→children index. When `true` (the
|
|
5284
|
+
* default), tree WALKS resolve against a lazily-rebuilt adjacency index
|
|
5285
|
+
* (O(k) per lookup, O(result) traversal), kept fresh by an `observeDeep`
|
|
5286
|
+
* dirty bit and rebuilt at most once per mutation batch; `childrenOfPage()`
|
|
5287
|
+
* becomes usable with stable (order,id) sibling ordering. Set `false` to
|
|
5288
|
+
* force the legacy whole-map scan (O(n) per call, O(n²) recursive) — only
|
|
5289
|
+
* needed to reproduce the exact historical order-only sibling ordering.
|
|
5290
|
+
* Benchmarked at ~700× on deep walks. Index affects WALK reads only;
|
|
5291
|
+
* `readEntries`/`get` keep raw parentId either way. Lazy: no index or
|
|
5292
|
+
* observer is bound until the first walk read.
|
|
5290
5293
|
*/
|
|
5291
5294
|
treeIndex?: boolean;
|
|
5292
5295
|
}
|
|
@@ -5325,8 +5328,8 @@ declare class DocumentManager {
|
|
|
5325
5328
|
get serverInfo(): ServerInfo | null;
|
|
5326
5329
|
get rootDocId(): string | null;
|
|
5327
5330
|
/**
|
|
5328
|
-
* Whether the TreeManager in-memory index is enabled
|
|
5329
|
-
*
|
|
5331
|
+
* Whether the TreeManager in-memory index is enabled.
|
|
5332
|
+
* On by default — see {@link DocumentManagerConfig.treeIndex}.
|
|
5330
5333
|
*/
|
|
5331
5334
|
get treeIndexEnabled(): boolean;
|
|
5332
5335
|
get rootDocument(): Y.Doc | null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abraca/dabra",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.27.0",
|
|
4
4
|
"description": "abracadabra provider",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"abracadabra",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"yjs": "^13.6.8"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@abraca/schema": "2.
|
|
44
|
+
"@abraca/schema": "2.27.0"
|
|
45
45
|
},
|
|
46
46
|
"scripts": {
|
|
47
47
|
"test": "node --no-warnings --conditions=source --experimental-transform-types --test 'tests/*.test.ts'"
|
package/src/DocConverters.ts
CHANGED
|
@@ -46,8 +46,7 @@ function xmlTextToMarkdown(xmlText: Y.XmlText): string {
|
|
|
46
46
|
|
|
47
47
|
function elementTextContent(el: Y.XmlElement | Y.XmlFragment): string {
|
|
48
48
|
const parts: string[] = [];
|
|
49
|
-
for (
|
|
50
|
-
const child = el.get(i);
|
|
49
|
+
for (const child of el.toArray()) {
|
|
51
50
|
if (child instanceof Y.XmlText) {
|
|
52
51
|
parts.push(xmlTextToMarkdown(child));
|
|
53
52
|
} else if (child instanceof Y.XmlElement) {
|
|
@@ -242,8 +241,9 @@ function serializeList(
|
|
|
242
241
|
indent: string,
|
|
243
242
|
): string {
|
|
244
243
|
const lines: string[] = [];
|
|
245
|
-
|
|
246
|
-
|
|
244
|
+
const items = el.toArray();
|
|
245
|
+
for (let i = 0; i < items.length; i++) {
|
|
246
|
+
const item = items[i];
|
|
247
247
|
if (
|
|
248
248
|
item instanceof Y.XmlElement &&
|
|
249
249
|
item.nodeName === "listItem"
|
|
@@ -258,8 +258,7 @@ function serializeList(
|
|
|
258
258
|
|
|
259
259
|
function serializeTaskList(el: Y.XmlElement, indent: string): string {
|
|
260
260
|
const lines: string[] = [];
|
|
261
|
-
for (
|
|
262
|
-
const item = el.get(i);
|
|
261
|
+
for (const item of el.toArray()) {
|
|
263
262
|
if (
|
|
264
263
|
item instanceof Y.XmlElement &&
|
|
265
264
|
item.nodeName === "taskItem"
|
|
@@ -277,8 +276,7 @@ function serializeTaskList(el: Y.XmlElement, indent: string): string {
|
|
|
277
276
|
function serializeTable(el: Y.XmlElement): string {
|
|
278
277
|
const rows: string[][] = [];
|
|
279
278
|
|
|
280
|
-
for (
|
|
281
|
-
const row = el.get(i);
|
|
279
|
+
for (const row of el.toArray()) {
|
|
282
280
|
if (
|
|
283
281
|
!(row instanceof Y.XmlElement) ||
|
|
284
282
|
row.nodeName !== "tableRow"
|
|
@@ -286,8 +284,7 @@ function serializeTable(el: Y.XmlElement): string {
|
|
|
286
284
|
continue;
|
|
287
285
|
|
|
288
286
|
const cells: string[] = [];
|
|
289
|
-
for (
|
|
290
|
-
const cell = row.get(j);
|
|
287
|
+
for (const cell of row.toArray()) {
|
|
291
288
|
if (cell instanceof Y.XmlElement) {
|
|
292
289
|
cells.push(elementTextContent(cell));
|
|
293
290
|
}
|
|
@@ -346,8 +343,7 @@ function serializeChildren(
|
|
|
346
343
|
indent: string,
|
|
347
344
|
): string {
|
|
348
345
|
const parts: string[] = [];
|
|
349
|
-
for (
|
|
350
|
-
const child = el.get(i);
|
|
346
|
+
for (const child of el.toArray()) {
|
|
351
347
|
if (child instanceof Y.XmlElement) {
|
|
352
348
|
const serialized = serializeElement(child, indent);
|
|
353
349
|
if (serialized) parts.push(serialized);
|
|
@@ -373,8 +369,9 @@ export function yjsToMarkdown(
|
|
|
373
369
|
let title = "Untitled";
|
|
374
370
|
const bodyParts: string[] = [];
|
|
375
371
|
|
|
376
|
-
|
|
377
|
-
|
|
372
|
+
// Iterate via toArray() (O(n)). Indexed fragment.get(i) is an O(i)
|
|
373
|
+
// linked-list walk, which made this top-level loop O(n²) on large docs.
|
|
374
|
+
for (const child of fragment.toArray()) {
|
|
378
375
|
if (!(child instanceof Y.XmlElement)) continue;
|
|
379
376
|
|
|
380
377
|
if (child.nodeName === "documentHeader") {
|
|
@@ -1805,8 +1802,8 @@ export interface DocumentBlock {
|
|
|
1805
1802
|
|
|
1806
1803
|
export function readBlocksFromFragment(fragment: Y.XmlFragment): DocumentBlock[] {
|
|
1807
1804
|
const result: DocumentBlock[] = [];
|
|
1808
|
-
|
|
1809
|
-
|
|
1805
|
+
// toArray() is O(n); indexed fragment.get(i) would make this O(n²).
|
|
1806
|
+
for (const child of fragment.toArray()) {
|
|
1810
1807
|
if (!(child instanceof Y.XmlElement)) continue;
|
|
1811
1808
|
const name = child.nodeName;
|
|
1812
1809
|
if (name === "documentHeader" || name === "documentMeta") continue;
|
package/src/DocumentManager.ts
CHANGED
|
@@ -75,13 +75,16 @@ export interface DocumentManagerConfig {
|
|
|
75
75
|
*/
|
|
76
76
|
rootDocId?: string;
|
|
77
77
|
/**
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
78
|
+
* TreeManager's in-memory parent→children index. When `true` (the
|
|
79
|
+
* default), tree WALKS resolve against a lazily-rebuilt adjacency index
|
|
80
|
+
* (O(k) per lookup, O(result) traversal), kept fresh by an `observeDeep`
|
|
81
|
+
* dirty bit and rebuilt at most once per mutation batch; `childrenOfPage()`
|
|
82
|
+
* becomes usable with stable (order,id) sibling ordering. Set `false` to
|
|
83
|
+
* force the legacy whole-map scan (O(n) per call, O(n²) recursive) — only
|
|
84
|
+
* needed to reproduce the exact historical order-only sibling ordering.
|
|
85
|
+
* Benchmarked at ~700× on deep walks. Index affects WALK reads only;
|
|
86
|
+
* `readEntries`/`get` keep raw parentId either way. Lazy: no index or
|
|
87
|
+
* observer is bound until the first walk read.
|
|
85
88
|
*/
|
|
86
89
|
treeIndex?: boolean;
|
|
87
90
|
}
|
|
@@ -206,11 +209,11 @@ export class DocumentManager {
|
|
|
206
209
|
}
|
|
207
210
|
|
|
208
211
|
/**
|
|
209
|
-
* Whether the TreeManager in-memory index is enabled
|
|
210
|
-
*
|
|
212
|
+
* Whether the TreeManager in-memory index is enabled.
|
|
213
|
+
* On by default — see {@link DocumentManagerConfig.treeIndex}.
|
|
211
214
|
*/
|
|
212
215
|
get treeIndexEnabled(): boolean {
|
|
213
|
-
return this._config.treeIndex ??
|
|
216
|
+
return this._config.treeIndex ?? true;
|
|
214
217
|
}
|
|
215
218
|
|
|
216
219
|
get rootDocument(): Y.Doc | null {
|
package/src/TreeTimestamps.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* TreeTimestamps
|
|
3
3
|
*
|
|
4
|
-
* Attaches an
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Attaches an afterTransaction observer on a child Y.Doc so that whenever a
|
|
5
|
+
* LOCAL edit is made, the `updatedAt` timestamp on the corresponding entry
|
|
6
|
+
* in the root doc's `doc-tree` map is written.
|
|
7
7
|
*
|
|
8
8
|
* This propagates "last edited" timestamps to all peers via the root CRDT,
|
|
9
9
|
* without requiring any server-side changes.
|
|
@@ -20,7 +20,7 @@ import type { OfflineStore } from "./OfflineStore.ts";
|
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Attach an observer that writes `updatedAt` to the root doc-tree entry for
|
|
23
|
-
* `childDocId` whenever the child doc receives a
|
|
23
|
+
* `childDocId` whenever the child doc receives a local edit.
|
|
24
24
|
*
|
|
25
25
|
* @param treeMap The root doc's "doc-tree" Y.Map.
|
|
26
26
|
* @param childDocId The child document's UUID (key in treeMap).
|
|
@@ -64,8 +64,17 @@ export function attachUpdatedAtObserver(
|
|
|
64
64
|
writeTs(ts);
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
function handler(
|
|
68
|
-
|
|
67
|
+
function handler(tr: Y.Transaction): void {
|
|
68
|
+
// Only LOCAL edits stamp updatedAt. Remote updates (tr.local === false:
|
|
69
|
+
// initial sync replay, other peers' edits, offline-store replay,
|
|
70
|
+
// cross-tab broadcast) are stamped by the client that made them and the
|
|
71
|
+
// timestamp propagates through the root CRDT — counting them here
|
|
72
|
+
// turned "last edited" into "last synced on this client": merely
|
|
73
|
+
// opening a doc bumped its updatedAt to now.
|
|
74
|
+
if (!tr.local) return;
|
|
75
|
+
if (offlineStore !== null && tr.origin === offlineStore) return;
|
|
76
|
+
// No-op transactions (nothing actually changed) don't count as edits.
|
|
77
|
+
if (tr.changed.size === 0) return;
|
|
69
78
|
|
|
70
79
|
const now = Date.now();
|
|
71
80
|
if (now - lastFlushedAt >= throttleMs) {
|
|
@@ -80,10 +89,10 @@ export function attachUpdatedAtObserver(
|
|
|
80
89
|
}
|
|
81
90
|
}
|
|
82
91
|
|
|
83
|
-
childDoc.on("
|
|
92
|
+
childDoc.on("afterTransaction", handler);
|
|
84
93
|
|
|
85
94
|
return () => {
|
|
86
|
-
childDoc.off("
|
|
95
|
+
childDoc.off("afterTransaction", handler);
|
|
87
96
|
if (timer !== null) {
|
|
88
97
|
clearTimeout(timer);
|
|
89
98
|
flushPending();
|