@abraca/dabra 2.3.0 → 2.5.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/README.md +50 -0
- package/dist/abracadabra-provider.cjs +491 -142
- package/dist/abracadabra-provider.cjs.map +1 -1
- package/dist/abracadabra-provider.esm.js +486 -153
- package/dist/abracadabra-provider.esm.js.map +1 -1
- package/dist/index.d.ts +192 -2
- package/package.json +2 -2
- package/src/AbracadabraClient.ts +195 -13
- package/src/AbracadabraProvider.ts +25 -16
- package/src/ContentManager.ts +19 -11
- package/src/DocUtils.ts +62 -0
- package/src/DocumentManager.ts +18 -0
- package/src/FileBlobStore.ts +43 -6
- package/src/MetaManager.ts +4 -7
- package/src/TreeManager.ts +343 -47
- package/src/TreeTimestamps.ts +6 -2
- package/src/index.ts +9 -1
|
@@ -3251,22 +3251,23 @@ var AbracadabraProvider = class AbracadabraProvider extends AbracadabraBaseProvi
|
|
|
3251
3251
|
* errors across async await boundaries.
|
|
3252
3252
|
*/
|
|
3253
3253
|
async flushPendingUpdates() {
|
|
3254
|
-
if (!this.canWrite) return;
|
|
3255
3254
|
const store = this.offlineStore;
|
|
3256
3255
|
if (!store) return;
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
update,
|
|
3261
|
-
|
|
3256
|
+
if (this.canWrite) {
|
|
3257
|
+
const updates = await store.getPendingUpdates();
|
|
3258
|
+
if (updates.length > 0) {
|
|
3259
|
+
for (const update of updates) this.send(UpdateMessage, {
|
|
3260
|
+
update,
|
|
3261
|
+
documentName: this.configuration.name
|
|
3262
|
+
});
|
|
3263
|
+
await store.clearPendingUpdates();
|
|
3264
|
+
}
|
|
3265
|
+
const pendingSubdocs = await store.getPendingSubdocs();
|
|
3266
|
+
for (const { childId } of pendingSubdocs) this.send(SubdocMessage, {
|
|
3267
|
+
documentName: this.configuration.name,
|
|
3268
|
+
childDocumentName: childId
|
|
3262
3269
|
});
|
|
3263
|
-
await store.clearPendingUpdates();
|
|
3264
3270
|
}
|
|
3265
|
-
const pendingSubdocs = await store.getPendingSubdocs();
|
|
3266
|
-
for (const { childId } of pendingSubdocs) this.send(SubdocMessage, {
|
|
3267
|
-
documentName: this.configuration.name,
|
|
3268
|
-
childDocumentName: childId
|
|
3269
|
-
});
|
|
3270
3271
|
const snapshot = Y.encodeStateAsUpdate(this.document);
|
|
3271
3272
|
await store.saveDocSnapshot(snapshot).catch(() => null);
|
|
3272
3273
|
}
|
|
@@ -10336,6 +10337,7 @@ function fromBase64$3(b64) {
|
|
|
10336
10337
|
}
|
|
10337
10338
|
var AbracadabraClient = class {
|
|
10338
10339
|
constructor(config) {
|
|
10340
|
+
this.rootListInflight = /* @__PURE__ */ new Map();
|
|
10339
10341
|
this.baseUrl = config.url.replace(/\/+$/, "");
|
|
10340
10342
|
this.persistAuth = config.persistAuth ?? typeof localStorage !== "undefined";
|
|
10341
10343
|
this.storageKey = config.storageKey ?? "abracadabra:auth";
|
|
@@ -10739,11 +10741,21 @@ var AbracadabraClient = class {
|
|
|
10739
10741
|
* recursive tree walks; callers that need it can read `meta.id` from
|
|
10740
10742
|
* the returned metas.
|
|
10741
10743
|
*/
|
|
10742
|
-
async listChildren(parentId) {
|
|
10743
|
-
const
|
|
10744
|
-
|
|
10745
|
-
|
|
10746
|
-
|
|
10744
|
+
async listChildren(parentId, opts) {
|
|
10745
|
+
const kind = opts?.kind;
|
|
10746
|
+
if (parentId) {
|
|
10747
|
+
const res = await this.request("GET", `/docs/${encodeURIComponent(parentId)}/children`);
|
|
10748
|
+
if (this.cache && res.children) await this.cache.setChildren(parentId, res.children).catch(() => null);
|
|
10749
|
+
return kind ? res.documents.filter((d) => d.kind === kind) : res.documents;
|
|
10750
|
+
}
|
|
10751
|
+
const key = kind ?? "";
|
|
10752
|
+
const existing = this.rootListInflight.get(key);
|
|
10753
|
+
const docs = existing ? await existing : await (() => {
|
|
10754
|
+
const p = this.request("GET", kind ? `/docs?root=true&kind=${encodeURIComponent(kind)}` : "/docs?root=true").then((res) => res.documents).finally(() => this.rootListInflight.delete(key));
|
|
10755
|
+
this.rootListInflight.set(key, p);
|
|
10756
|
+
return p;
|
|
10757
|
+
})();
|
|
10758
|
+
return kind ? docs.filter((d) => d.kind === kind) : docs;
|
|
10747
10759
|
}
|
|
10748
10760
|
/**
|
|
10749
10761
|
* Create a child document under a parent (requires write permission).
|
|
@@ -10872,7 +10884,7 @@ var AbracadabraClient = class {
|
|
|
10872
10884
|
* spaces resolving to any role; anonymous users see public ones.
|
|
10873
10885
|
*/
|
|
10874
10886
|
async listSpaces() {
|
|
10875
|
-
return
|
|
10887
|
+
return this.listChildren(void 0, { kind: Kind.Space });
|
|
10876
10888
|
}
|
|
10877
10889
|
/**
|
|
10878
10890
|
* Create a new top-level Space. Equivalent to a `POST /docs` with
|
|
@@ -10990,6 +11002,46 @@ var AbracadabraClient = class {
|
|
|
10990
11002
|
return this.request("POST", "/admin/storage/repair");
|
|
10991
11003
|
}
|
|
10992
11004
|
/**
|
|
11005
|
+
* Admin one-shot: populate the `snapshot_files` table for snapshots
|
|
11006
|
+
* created before that migration ran, so `SnapshotMeta.file_count` and
|
|
11007
|
+
* upload-ref tracking become accurate. Idempotent (insert-or-ignore).
|
|
11008
|
+
* Requires elevated role.
|
|
11009
|
+
*/
|
|
11010
|
+
async adminSnapshotsBackfillRefs() {
|
|
11011
|
+
return this.request("POST", "/admin/snapshots/backfill-refs");
|
|
11012
|
+
}
|
|
11013
|
+
/**
|
|
11014
|
+
* Admin one-shot: migrate pre-dedup inline snapshot data into the
|
|
11015
|
+
* content-addressed `snapshot_blobs` store. Idempotent (only migrates
|
|
11016
|
+
* rows with `data_hash IS NULL`). Requires elevated role.
|
|
11017
|
+
*/
|
|
11018
|
+
async adminSnapshotsBackfillBlobs() {
|
|
11019
|
+
return this.request("POST", "/admin/snapshots/backfill-blobs");
|
|
11020
|
+
}
|
|
11021
|
+
/**
|
|
11022
|
+
* Admin: server-wide upload listing, joined with the owning document
|
|
11023
|
+
* (label is best-effort — labels live in the CRDT, not the SQL row)
|
|
11024
|
+
* and the content-addressed blob (`ref_count` exposes dedup).
|
|
11025
|
+
* Server-side paginated + filtered. Requires elevated role.
|
|
11026
|
+
*/
|
|
11027
|
+
async adminListUploads(opts = {}) {
|
|
11028
|
+
const p = new URLSearchParams();
|
|
11029
|
+
if (opts.q) p.set("q", opts.q);
|
|
11030
|
+
if (opts.docId) p.set("doc_id", opts.docId);
|
|
11031
|
+
if (opts.limit != null) p.set("limit", String(opts.limit));
|
|
11032
|
+
if (opts.offset != null) p.set("offset", String(opts.offset));
|
|
11033
|
+
const qs = p.toString();
|
|
11034
|
+
return this.request("GET", `/admin/uploads${qs ? `?${qs}` : ""}`);
|
|
11035
|
+
}
|
|
11036
|
+
/**
|
|
11037
|
+
* Admin: aggregate storage figures. `logicalBytes` is what users
|
|
11038
|
+
* uploaded; `physicalBytes` is on-disk after content-addressed dedup;
|
|
11039
|
+
* `dedupSaved` is the difference. Requires elevated role.
|
|
11040
|
+
*/
|
|
11041
|
+
async adminStorageStats() {
|
|
11042
|
+
return this.request("GET", "/admin/storage/stats");
|
|
11043
|
+
}
|
|
11044
|
+
/**
|
|
10993
11045
|
* Clear the lockout state on a user account: zeroes the failed-login
|
|
10994
11046
|
* counter and `locked_until`. Requires elevated role (Admin or
|
|
10995
11047
|
* Service). The action is recorded in the audit log under
|
|
@@ -10999,6 +11051,17 @@ var AbracadabraClient = class {
|
|
|
10999
11051
|
await this.request("POST", `/admin/users/${encodeURIComponent(userId)}/unlock`);
|
|
11000
11052
|
}
|
|
11001
11053
|
/**
|
|
11054
|
+
* Admin: every non-deleted document the user owns (`source: "owner"`)
|
|
11055
|
+
* or has an explicit permission grant on (`source: "grant"`, with
|
|
11056
|
+
* `role`). Answers "what does this identity touch" without an N+1
|
|
11057
|
+
* client tree walk. Labels are best-effort (they live in the CRDT).
|
|
11058
|
+
* Requires elevated role.
|
|
11059
|
+
*/
|
|
11060
|
+
async adminUserDocs(userId, opts = {}) {
|
|
11061
|
+
const qs = opts.limit != null ? `?limit=${opts.limit}` : "";
|
|
11062
|
+
return this.request("GET", `/admin/users/${encodeURIComponent(userId)}/docs${qs}`);
|
|
11063
|
+
}
|
|
11064
|
+
/**
|
|
11002
11065
|
* Page through the audit log. Filters AND-combine; `limit` defaults to
|
|
11003
11066
|
* 100 server-side. Requires elevated role.
|
|
11004
11067
|
*/
|
|
@@ -11119,6 +11182,30 @@ var AbracadabraClient = class {
|
|
|
11119
11182
|
async adminConfigEnvSnapshot() {
|
|
11120
11183
|
return this.request("GET", "/admin/config/env-snapshot");
|
|
11121
11184
|
}
|
|
11185
|
+
/**
|
|
11186
|
+
* List every route pattern that currently has at least one per-route
|
|
11187
|
+
* config override. Use {@link adminConfigGetRoute} to read individual
|
|
11188
|
+
* fields. Requires elevated role.
|
|
11189
|
+
*/
|
|
11190
|
+
async adminConfigListRoutes() {
|
|
11191
|
+
return (await this.request("GET", "/admin/config/routes")).routes;
|
|
11192
|
+
}
|
|
11193
|
+
/**
|
|
11194
|
+
* Read a field's effective value scoped to `route`, falling back to
|
|
11195
|
+
* the global value when no per-route override exists. `origin_kind`
|
|
11196
|
+
* is `"route_override"` only when an override is actually set.
|
|
11197
|
+
*/
|
|
11198
|
+
async adminConfigGetRoute(route, path) {
|
|
11199
|
+
return this.request("GET", `/admin/config/routes/${encodeURIComponent(route)}/fields/${encodeURIComponent(path)}`);
|
|
11200
|
+
}
|
|
11201
|
+
/** Set or replace a per-route override. Mirrors {@link adminConfigSet}. */
|
|
11202
|
+
async adminConfigSetRoute(route, path, value) {
|
|
11203
|
+
return this.request("PUT", `/admin/config/routes/${encodeURIComponent(route)}/fields/${encodeURIComponent(path)}`, { body: { value } });
|
|
11204
|
+
}
|
|
11205
|
+
/** Clear a per-route override (falls back to global). True if one existed. */
|
|
11206
|
+
async adminConfigUnsetRoute(route, path) {
|
|
11207
|
+
return (await this.request("DELETE", `/admin/config/routes/${encodeURIComponent(route)}/fields/${encodeURIComponent(path)}`)).existed;
|
|
11208
|
+
}
|
|
11122
11209
|
/** List snapshot metadata for a document. */
|
|
11123
11210
|
async listSnapshots(docId, opts) {
|
|
11124
11211
|
const params = new URLSearchParams();
|
|
@@ -14630,6 +14717,10 @@ var FileBlobStore = class FileBlobStore extends EventEmitter {
|
|
|
14630
14717
|
if (!idbAvailable$1()) return Promise.resolve(null);
|
|
14631
14718
|
if (!this.dbPromise) this.dbPromise = openDb$1(this.origin).catch(() => null).then((db) => {
|
|
14632
14719
|
this.db = db;
|
|
14720
|
+
if (db) db.onclose = () => {
|
|
14721
|
+
if (this.db === db) this.db = null;
|
|
14722
|
+
this.dbPromise = null;
|
|
14723
|
+
};
|
|
14633
14724
|
return db;
|
|
14634
14725
|
});
|
|
14635
14726
|
return this.dbPromise;
|
|
@@ -14648,10 +14739,23 @@ var FileBlobStore = class FileBlobStore extends EventEmitter {
|
|
|
14648
14739
|
const key = this.blobKey(docId, uploadId);
|
|
14649
14740
|
const existing = this.objectUrls.get(key);
|
|
14650
14741
|
if (existing) return existing;
|
|
14651
|
-
|
|
14742
|
+
let db = await this.getDb();
|
|
14652
14743
|
if (db) {
|
|
14653
|
-
|
|
14654
|
-
|
|
14744
|
+
let entry;
|
|
14745
|
+
try {
|
|
14746
|
+
const tx = db.transaction("blobs", "readonly");
|
|
14747
|
+
entry = await txPromise(tx.objectStore("blobs"), tx.objectStore("blobs").get(key));
|
|
14748
|
+
} catch (err) {
|
|
14749
|
+
if (err?.name === "InvalidStateError") {
|
|
14750
|
+
if (this.db === db) this.db = null;
|
|
14751
|
+
this.dbPromise = null;
|
|
14752
|
+
db = await this.getDb();
|
|
14753
|
+
if (db) {
|
|
14754
|
+
const tx = db.transaction("blobs", "readonly");
|
|
14755
|
+
entry = await txPromise(tx.objectStore("blobs"), tx.objectStore("blobs").get(key));
|
|
14756
|
+
}
|
|
14757
|
+
} else throw err;
|
|
14758
|
+
}
|
|
14655
14759
|
if (entry) {
|
|
14656
14760
|
const url = URL.createObjectURL(entry.blob);
|
|
14657
14761
|
this.objectUrls.set(key, url);
|
|
@@ -14902,6 +15006,7 @@ var FileBlobStore = class FileBlobStore extends EventEmitter {
|
|
|
14902
15006
|
this.objectUrls.clear();
|
|
14903
15007
|
this.db?.close();
|
|
14904
15008
|
this.db = null;
|
|
15009
|
+
this.dbPromise = null;
|
|
14905
15010
|
this.removeAllListeners();
|
|
14906
15011
|
}
|
|
14907
15012
|
};
|
|
@@ -15444,21 +15549,113 @@ var E2EAbracadabraProvider = class E2EAbracadabraProvider extends AbracadabraPro
|
|
|
15444
15549
|
};
|
|
15445
15550
|
|
|
15446
15551
|
//#endregion
|
|
15447
|
-
//#region packages/provider/src/
|
|
15552
|
+
//#region packages/provider/src/DocUtils.ts
|
|
15448
15553
|
/**
|
|
15449
|
-
*
|
|
15554
|
+
* Shared utilities for the DocumentManager ORM layer.
|
|
15450
15555
|
*
|
|
15451
|
-
*
|
|
15452
|
-
*
|
|
15453
|
-
|
|
15556
|
+
* These functions were previously duplicated across `mcp/src/utils.ts`,
|
|
15557
|
+
* `mcp/src/server.ts`, `cli/src/connection.ts`, and `mcp/src/tools/tree.ts`.
|
|
15558
|
+
*/
|
|
15559
|
+
/**
|
|
15560
|
+
* Wait for a provider's `synced` event with a timeout.
|
|
15561
|
+
* Resolves immediately if the provider is already synced.
|
|
15562
|
+
*/
|
|
15563
|
+
function waitForSync(provider, timeoutMs = 15e3) {
|
|
15564
|
+
if (provider.isSynced) return Promise.resolve();
|
|
15565
|
+
return new Promise((resolve, reject) => {
|
|
15566
|
+
const timer = setTimeout(() => {
|
|
15567
|
+
provider.off("synced", handler);
|
|
15568
|
+
reject(/* @__PURE__ */ new Error(`Sync timed out after ${timeoutMs}ms`));
|
|
15569
|
+
}, timeoutMs);
|
|
15570
|
+
function handler() {
|
|
15571
|
+
clearTimeout(timer);
|
|
15572
|
+
resolve();
|
|
15573
|
+
}
|
|
15574
|
+
provider.on("synced", handler);
|
|
15575
|
+
});
|
|
15576
|
+
}
|
|
15577
|
+
/**
|
|
15578
|
+
* Wrap a promise with a timeout.
|
|
15579
|
+
*/
|
|
15580
|
+
function withTimeout(promise, timeoutMs, message) {
|
|
15581
|
+
return new Promise((resolve, reject) => {
|
|
15582
|
+
const timer = setTimeout(() => reject(new Error(message ?? `Operation timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
15583
|
+
promise.then((val) => {
|
|
15584
|
+
clearTimeout(timer);
|
|
15585
|
+
resolve(val);
|
|
15586
|
+
}, (err) => {
|
|
15587
|
+
clearTimeout(timer);
|
|
15588
|
+
reject(err);
|
|
15589
|
+
});
|
|
15590
|
+
});
|
|
15591
|
+
}
|
|
15592
|
+
/**
|
|
15593
|
+
* Normalize a document ID so the hub/root doc ID is treated as the tree root
|
|
15594
|
+
* (null). This lets callers pass the hub doc_id from list_spaces as
|
|
15595
|
+
* parentId/rootId and get the expected root-level results instead of an empty
|
|
15596
|
+
* set.
|
|
15597
|
+
*/
|
|
15598
|
+
function normalizeRootId(id, rootDocId) {
|
|
15599
|
+
if (id == null) return null;
|
|
15600
|
+
return id === rootDocId ? null : id;
|
|
15601
|
+
}
|
|
15602
|
+
/**
|
|
15603
|
+
* Safely read a tree map value, converting Y.Map to plain object if needed.
|
|
15604
|
+
*/
|
|
15605
|
+
function toPlain(val) {
|
|
15606
|
+
return val instanceof Y.Map ? val.toJSON() : val;
|
|
15607
|
+
}
|
|
15608
|
+
/**
|
|
15609
|
+
* Build a tree/trash entry as a nested `Y.Map`. Use for a brand-new or
|
|
15610
|
+
* re-created key (create / duplicate / restore) where no concurrent
|
|
15611
|
+
* writer exists, so a whole-value write is safe. `undefined` fields are
|
|
15612
|
+
* omitted; `null` is kept (a real value, e.g. top-level `parentId`).
|
|
15613
|
+
*/
|
|
15614
|
+
function makeEntryMap(fields) {
|
|
15615
|
+
const m = new Y.Map();
|
|
15616
|
+
for (const [k, v] of Object.entries(fields)) if (v !== void 0) m.set(k, v);
|
|
15617
|
+
return m;
|
|
15618
|
+
}
|
|
15619
|
+
/**
|
|
15620
|
+
* Patch an EXISTING entry's fields per-key on its nested `Y.Map`, so a
|
|
15621
|
+
* concurrent edit to a *different* field by a peer is preserved instead
|
|
15622
|
+
* of being clobbered by a whole-entry write — the whole-entry-LWW fix
|
|
15623
|
+
* (audit ⑦), the mirror of the Rust provider's `with_entry_mut`.
|
|
15454
15624
|
*
|
|
15455
|
-
*
|
|
15456
|
-
*
|
|
15625
|
+
* - nested `Y.Map` entry → set/delete only the touched keys in place;
|
|
15626
|
+
* - legacy opaque (plain-object) entry → migrated once to a `Y.Map`;
|
|
15627
|
+
* - missing entry → created from the patch (lenient; matches the prior
|
|
15628
|
+
* call-site behaviour of spreading `undefined`).
|
|
15457
15629
|
*
|
|
15458
|
-
*
|
|
15459
|
-
*
|
|
15460
|
-
*
|
|
15630
|
+
* A patch value of `undefined` deletes the key; `null` is written.
|
|
15631
|
+
* Self-transacting: it batches its writes in one `Y.Doc` transaction
|
|
15632
|
+
* (a safe reentrant no-op join when already inside one), so callers
|
|
15633
|
+
* don't need to pass or own a transaction.
|
|
15461
15634
|
*/
|
|
15635
|
+
function patchEntry(treeMap, id, patch, removeKeys = []) {
|
|
15636
|
+
const apply = () => {
|
|
15637
|
+
const raw = treeMap.get(id);
|
|
15638
|
+
if (raw instanceof Y.Map) {
|
|
15639
|
+
for (const [k, v] of Object.entries(patch)) if (v === void 0) raw.delete(k);
|
|
15640
|
+
else raw.set(k, v);
|
|
15641
|
+
for (const k of removeKeys) raw.delete(k);
|
|
15642
|
+
return;
|
|
15643
|
+
}
|
|
15644
|
+
const merged = {
|
|
15645
|
+
...raw == null ? {} : toPlain(raw),
|
|
15646
|
+
...patch
|
|
15647
|
+
};
|
|
15648
|
+
for (const [k, v] of Object.entries(patch)) if (v === void 0) delete merged[k];
|
|
15649
|
+
for (const k of removeKeys) delete merged[k];
|
|
15650
|
+
treeMap.set(id, makeEntryMap(merged));
|
|
15651
|
+
};
|
|
15652
|
+
const doc = treeMap.doc;
|
|
15653
|
+
if (doc) doc.transact(apply);
|
|
15654
|
+
else apply();
|
|
15655
|
+
}
|
|
15656
|
+
|
|
15657
|
+
//#endregion
|
|
15658
|
+
//#region packages/provider/src/TreeTimestamps.ts
|
|
15462
15659
|
/**
|
|
15463
15660
|
* Attach an observer that writes `updatedAt` to the root doc-tree entry for
|
|
15464
15661
|
* `childDocId` whenever the child doc receives a non-offline update.
|
|
@@ -15480,13 +15677,8 @@ function attachUpdatedAtObserver(treeMap, childDocId, childDoc, offlineStore, op
|
|
|
15480
15677
|
let pendingTs = 0;
|
|
15481
15678
|
let timer = null;
|
|
15482
15679
|
function writeTs(ts) {
|
|
15483
|
-
|
|
15484
|
-
|
|
15485
|
-
const entry = raw instanceof Y.Map ? raw.toJSON() : raw;
|
|
15486
|
-
treeMap.set(childDocId, {
|
|
15487
|
-
...entry,
|
|
15488
|
-
updatedAt: ts
|
|
15489
|
-
});
|
|
15680
|
+
if (!treeMap.get(childDocId)) return;
|
|
15681
|
+
patchEntry(treeMap, childDocId, { updatedAt: ts });
|
|
15490
15682
|
lastFlushedAt = ts;
|
|
15491
15683
|
}
|
|
15492
15684
|
function flushPending() {
|
|
@@ -18113,64 +18305,6 @@ function resolvePageType(key) {
|
|
|
18113
18305
|
return PAGE_TYPES[TYPE_ALIASES[key] ?? key];
|
|
18114
18306
|
}
|
|
18115
18307
|
|
|
18116
|
-
//#endregion
|
|
18117
|
-
//#region packages/provider/src/DocUtils.ts
|
|
18118
|
-
/**
|
|
18119
|
-
* Shared utilities for the DocumentManager ORM layer.
|
|
18120
|
-
*
|
|
18121
|
-
* These functions were previously duplicated across `mcp/src/utils.ts`,
|
|
18122
|
-
* `mcp/src/server.ts`, `cli/src/connection.ts`, and `mcp/src/tools/tree.ts`.
|
|
18123
|
-
*/
|
|
18124
|
-
/**
|
|
18125
|
-
* Wait for a provider's `synced` event with a timeout.
|
|
18126
|
-
* Resolves immediately if the provider is already synced.
|
|
18127
|
-
*/
|
|
18128
|
-
function waitForSync(provider, timeoutMs = 15e3) {
|
|
18129
|
-
if (provider.isSynced) return Promise.resolve();
|
|
18130
|
-
return new Promise((resolve, reject) => {
|
|
18131
|
-
const timer = setTimeout(() => {
|
|
18132
|
-
provider.off("synced", handler);
|
|
18133
|
-
reject(/* @__PURE__ */ new Error(`Sync timed out after ${timeoutMs}ms`));
|
|
18134
|
-
}, timeoutMs);
|
|
18135
|
-
function handler() {
|
|
18136
|
-
clearTimeout(timer);
|
|
18137
|
-
resolve();
|
|
18138
|
-
}
|
|
18139
|
-
provider.on("synced", handler);
|
|
18140
|
-
});
|
|
18141
|
-
}
|
|
18142
|
-
/**
|
|
18143
|
-
* Wrap a promise with a timeout.
|
|
18144
|
-
*/
|
|
18145
|
-
function withTimeout(promise, timeoutMs, message) {
|
|
18146
|
-
return new Promise((resolve, reject) => {
|
|
18147
|
-
const timer = setTimeout(() => reject(new Error(message ?? `Operation timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
18148
|
-
promise.then((val) => {
|
|
18149
|
-
clearTimeout(timer);
|
|
18150
|
-
resolve(val);
|
|
18151
|
-
}, (err) => {
|
|
18152
|
-
clearTimeout(timer);
|
|
18153
|
-
reject(err);
|
|
18154
|
-
});
|
|
18155
|
-
});
|
|
18156
|
-
}
|
|
18157
|
-
/**
|
|
18158
|
-
* Normalize a document ID so the hub/root doc ID is treated as the tree root
|
|
18159
|
-
* (null). This lets callers pass the hub doc_id from list_spaces as
|
|
18160
|
-
* parentId/rootId and get the expected root-level results instead of an empty
|
|
18161
|
-
* set.
|
|
18162
|
-
*/
|
|
18163
|
-
function normalizeRootId(id, rootDocId) {
|
|
18164
|
-
if (id == null) return null;
|
|
18165
|
-
return id === rootDocId ? null : id;
|
|
18166
|
-
}
|
|
18167
|
-
/**
|
|
18168
|
-
* Safely read a tree map value, converting Y.Map to plain object if needed.
|
|
18169
|
-
*/
|
|
18170
|
-
function toPlain(val) {
|
|
18171
|
-
return val instanceof Y.Map ? val.toJSON() : val;
|
|
18172
|
-
}
|
|
18173
|
-
|
|
18174
18308
|
//#endregion
|
|
18175
18309
|
//#region packages/provider/src/SchemaTypes.ts
|
|
18176
18310
|
/**
|
|
@@ -18209,9 +18343,112 @@ function projectTreeEntry(entry, expectedType) {
|
|
|
18209
18343
|
|
|
18210
18344
|
//#endregion
|
|
18211
18345
|
//#region packages/provider/src/TreeManager.ts
|
|
18346
|
+
/**
|
|
18347
|
+
* Stable total order over tree siblings: `order` ascending, then `id`
|
|
18348
|
+
* ascending as a deterministic tiebreak. The legacy scan sorted by
|
|
18349
|
+
* `order` alone and left ties to insertion/iteration order — a superset
|
|
18350
|
+
* change that makes cursor pagination well-defined.
|
|
18351
|
+
*/
|
|
18352
|
+
function cmpKey(oa, ia, ob, ib) {
|
|
18353
|
+
if (oa !== ob) return oa < ob ? -1 : 1;
|
|
18354
|
+
return ia < ib ? -1 : ia > ib ? 1 : 0;
|
|
18355
|
+
}
|
|
18356
|
+
function cmpEntry(a, b) {
|
|
18357
|
+
return cmpKey(a.order ?? 0, a.id, b.order ?? 0, b.id);
|
|
18358
|
+
}
|
|
18359
|
+
/** Opaque, dependency-free cursor over the (order,id) sibling order. */
|
|
18360
|
+
function encodeCursor(order, id) {
|
|
18361
|
+
return encodeURIComponent(JSON.stringify([order ?? 0, id]));
|
|
18362
|
+
}
|
|
18363
|
+
function decodeCursor(c) {
|
|
18364
|
+
try {
|
|
18365
|
+
const v = JSON.parse(decodeURIComponent(c));
|
|
18366
|
+
if (Array.isArray(v) && typeof v[0] === "number" && typeof v[1] === "string") return {
|
|
18367
|
+
order: v[0],
|
|
18368
|
+
id: v[1]
|
|
18369
|
+
};
|
|
18370
|
+
} catch {}
|
|
18371
|
+
return null;
|
|
18372
|
+
}
|
|
18212
18373
|
var TreeManager = class {
|
|
18213
18374
|
constructor(dm) {
|
|
18214
18375
|
this.dm = dm;
|
|
18376
|
+
this._idxMap = null;
|
|
18377
|
+
this._idxObserver = null;
|
|
18378
|
+
this._idxDirty = true;
|
|
18379
|
+
this._byId = /* @__PURE__ */ new Map();
|
|
18380
|
+
this._childrenByParent = /* @__PURE__ */ new Map();
|
|
18381
|
+
}
|
|
18382
|
+
/**
|
|
18383
|
+
* Ensure the index is enabled, bound to the current root doc's tree
|
|
18384
|
+
* map, and fresh. Returns `false` when the index is disabled or there
|
|
18385
|
+
* is no tree map yet — callers then use the legacy scan path.
|
|
18386
|
+
*/
|
|
18387
|
+
ensureIndex() {
|
|
18388
|
+
if (!this.dm.treeIndexEnabled) return false;
|
|
18389
|
+
const treeMap = this.dm.getTreeMap();
|
|
18390
|
+
if (!treeMap) {
|
|
18391
|
+
this.unbindIndex();
|
|
18392
|
+
return false;
|
|
18393
|
+
}
|
|
18394
|
+
if (treeMap !== this._idxMap) {
|
|
18395
|
+
this.unbindIndex();
|
|
18396
|
+
const obs = () => {
|
|
18397
|
+
this._idxDirty = true;
|
|
18398
|
+
};
|
|
18399
|
+
treeMap.observeDeep(obs);
|
|
18400
|
+
this._idxMap = treeMap;
|
|
18401
|
+
this._idxObserver = obs;
|
|
18402
|
+
this._idxDirty = true;
|
|
18403
|
+
}
|
|
18404
|
+
if (this._idxDirty) this.rebuildIndex(treeMap);
|
|
18405
|
+
return true;
|
|
18406
|
+
}
|
|
18407
|
+
unbindIndex() {
|
|
18408
|
+
if (this._idxMap && this._idxObserver) this._idxMap.unobserveDeep(this._idxObserver);
|
|
18409
|
+
this._idxMap = null;
|
|
18410
|
+
this._idxObserver = null;
|
|
18411
|
+
this._byId = /* @__PURE__ */ new Map();
|
|
18412
|
+
this._childrenByParent = /* @__PURE__ */ new Map();
|
|
18413
|
+
this._idxDirty = true;
|
|
18414
|
+
}
|
|
18415
|
+
rebuildIndex(treeMap) {
|
|
18416
|
+
const root = this.dm.rootDocId;
|
|
18417
|
+
const byId = /* @__PURE__ */ new Map();
|
|
18418
|
+
const childrenByParent = /* @__PURE__ */ new Map();
|
|
18419
|
+
treeMap.forEach((raw, id) => {
|
|
18420
|
+
const value = toPlain(raw);
|
|
18421
|
+
if (typeof value !== "object" || value === null) return;
|
|
18422
|
+
const entry = {
|
|
18423
|
+
id,
|
|
18424
|
+
label: value.label || "Untitled",
|
|
18425
|
+
parentId: normalizeRootId(value.parentId ?? null, root),
|
|
18426
|
+
order: value.order ?? 0,
|
|
18427
|
+
type: value.type,
|
|
18428
|
+
meta: value.meta,
|
|
18429
|
+
createdAt: value.createdAt,
|
|
18430
|
+
updatedAt: value.updatedAt
|
|
18431
|
+
};
|
|
18432
|
+
byId.set(id, entry);
|
|
18433
|
+
let bucket = childrenByParent.get(entry.parentId);
|
|
18434
|
+
if (!bucket) {
|
|
18435
|
+
bucket = [];
|
|
18436
|
+
childrenByParent.set(entry.parentId, bucket);
|
|
18437
|
+
}
|
|
18438
|
+
bucket.push(entry);
|
|
18439
|
+
});
|
|
18440
|
+
for (const bucket of childrenByParent.values()) bucket.sort(cmpEntry);
|
|
18441
|
+
this._byId = byId;
|
|
18442
|
+
this._childrenByParent = childrenByParent;
|
|
18443
|
+
this._idxDirty = false;
|
|
18444
|
+
}
|
|
18445
|
+
/**
|
|
18446
|
+
* Release the deep observer. Optional — the observer is auto-rebound
|
|
18447
|
+
* on space switch and becomes moot when the root Y.Doc is GC'd — but
|
|
18448
|
+
* available for consumers that want deterministic teardown.
|
|
18449
|
+
*/
|
|
18450
|
+
dispose() {
|
|
18451
|
+
this.unbindIndex();
|
|
18215
18452
|
}
|
|
18216
18453
|
/** Read all tree entries as plain objects. */
|
|
18217
18454
|
readEntries() {
|
|
@@ -18234,15 +18471,83 @@ var TreeManager = class {
|
|
|
18234
18471
|
});
|
|
18235
18472
|
return entries;
|
|
18236
18473
|
}
|
|
18474
|
+
/**
|
|
18475
|
+
* Like {@link readEntries} but with every entry's *stored* parentId
|
|
18476
|
+
* run through {@link normalizeRootId} (parentId === rootDocId → null),
|
|
18477
|
+
* so a cou-sh / orphan-rescue top-level doc (parentId === spaceRoot)
|
|
18478
|
+
* resolves to top-level identically to a provider-created one
|
|
18479
|
+
* (parentId: null). Without this, the raw `parentId === spaceRoot`
|
|
18480
|
+
* form never matches the normalized `null` query and such docs are
|
|
18481
|
+
* silently invisible cross-client. Mirrors the Rust provider's
|
|
18482
|
+
* `normalized_entries`. readEntries/get keep raw values for
|
|
18483
|
+
* round-trip consumers; only tree-walk reads use this.
|
|
18484
|
+
*/
|
|
18485
|
+
normalizedEntries() {
|
|
18486
|
+
if (this.ensureIndex()) return Array.from(this._byId.values());
|
|
18487
|
+
const root = this.dm.rootDocId;
|
|
18488
|
+
return this.readEntries().map((e) => ({
|
|
18489
|
+
...e,
|
|
18490
|
+
parentId: normalizeRootId(e.parentId, root)
|
|
18491
|
+
}));
|
|
18492
|
+
}
|
|
18237
18493
|
/** Get immediate children of a parent (sorted by order). */
|
|
18238
18494
|
childrenOf(parentId) {
|
|
18239
18495
|
const normalized = normalizeRootId(parentId, this.dm.rootDocId);
|
|
18240
|
-
|
|
18496
|
+
if (this.ensureIndex()) {
|
|
18497
|
+
const bucket = this._childrenByParent.get(normalized);
|
|
18498
|
+
return bucket ? bucket.slice() : [];
|
|
18499
|
+
}
|
|
18500
|
+
return this.normalizedEntries().filter((e) => e.parentId === normalized).sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
18501
|
+
}
|
|
18502
|
+
/**
|
|
18503
|
+
* Paginated immediate children — the Path-1 surface for large fan-out
|
|
18504
|
+
* parents. Walks the same stable (order,id) sibling order as
|
|
18505
|
+
* {@link childrenOf}; `cursor` is opaque (round-trip `nextCursor`).
|
|
18506
|
+
* `limit` defaults to 100. A stale/garbage cursor restarts from the
|
|
18507
|
+
* head rather than throwing. Cursor stability is exact when the index
|
|
18508
|
+
* is enabled; on the legacy scan path siblings with equal `order`
|
|
18509
|
+
* may shift between calls.
|
|
18510
|
+
*/
|
|
18511
|
+
childrenOfPage(parentId, opts = {}) {
|
|
18512
|
+
const all = this.childrenOf(parentId);
|
|
18513
|
+
const limit = opts.limit != null && opts.limit > 0 ? Math.floor(opts.limit) : 100;
|
|
18514
|
+
let start = 0;
|
|
18515
|
+
if (opts.cursor) {
|
|
18516
|
+
const dec = decodeCursor(opts.cursor);
|
|
18517
|
+
if (dec) {
|
|
18518
|
+
const at = all.findIndex((e) => cmpKey(e.order ?? 0, e.id, dec.order, dec.id) > 0);
|
|
18519
|
+
start = at < 0 ? all.length : at;
|
|
18520
|
+
}
|
|
18521
|
+
}
|
|
18522
|
+
const entries = all.slice(start, start + limit);
|
|
18523
|
+
const last = entries[entries.length - 1];
|
|
18524
|
+
return {
|
|
18525
|
+
entries,
|
|
18526
|
+
nextCursor: last && start + limit < all.length ? encodeCursor(last.order ?? 0, last.id) : null
|
|
18527
|
+
};
|
|
18241
18528
|
}
|
|
18242
18529
|
/** Get all descendants recursively. */
|
|
18243
18530
|
descendantsOf(parentId) {
|
|
18244
18531
|
const normalized = normalizeRootId(parentId, this.dm.rootDocId);
|
|
18245
|
-
|
|
18532
|
+
if (this.ensureIndex()) {
|
|
18533
|
+
const result = [];
|
|
18534
|
+
const visited = /* @__PURE__ */ new Set();
|
|
18535
|
+
const walk = (pid) => {
|
|
18536
|
+
if (pid !== null) {
|
|
18537
|
+
if (visited.has(pid)) return;
|
|
18538
|
+
visited.add(pid);
|
|
18539
|
+
}
|
|
18540
|
+
const bucket = this._childrenByParent.get(pid);
|
|
18541
|
+
if (!bucket) return;
|
|
18542
|
+
for (const child of bucket) {
|
|
18543
|
+
result.push(child);
|
|
18544
|
+
walk(child.id);
|
|
18545
|
+
}
|
|
18546
|
+
};
|
|
18547
|
+
walk(normalized);
|
|
18548
|
+
return result;
|
|
18549
|
+
}
|
|
18550
|
+
const entries = this.normalizedEntries();
|
|
18246
18551
|
const result = [];
|
|
18247
18552
|
const visited = /* @__PURE__ */ new Set();
|
|
18248
18553
|
const collect = (pid) => {
|
|
@@ -18259,9 +18564,25 @@ var TreeManager = class {
|
|
|
18259
18564
|
/** Build nested tree JSON. */
|
|
18260
18565
|
buildTree(rootId, maxDepth = 3) {
|
|
18261
18566
|
const normalized = normalizeRootId(rootId ?? null, this.dm.rootDocId);
|
|
18262
|
-
|
|
18567
|
+
if (this.ensureIndex()) return this._buildTreeIndexed(normalized, maxDepth, 0, /* @__PURE__ */ new Set());
|
|
18568
|
+
const entries = this.normalizedEntries();
|
|
18263
18569
|
return this._buildTree(entries, normalized, maxDepth, 0, /* @__PURE__ */ new Set());
|
|
18264
18570
|
}
|
|
18571
|
+
_buildTreeIndexed(rootId, maxDepth, currentDepth, visited) {
|
|
18572
|
+
if (maxDepth >= 0 && currentDepth >= maxDepth) return [];
|
|
18573
|
+
return (this._childrenByParent.get(rootId) ?? []).filter((e) => !visited.has(e.id)).map((entry) => {
|
|
18574
|
+
const next = new Set(visited);
|
|
18575
|
+
next.add(entry.id);
|
|
18576
|
+
return {
|
|
18577
|
+
id: entry.id,
|
|
18578
|
+
label: entry.label,
|
|
18579
|
+
type: entry.type,
|
|
18580
|
+
meta: entry.meta,
|
|
18581
|
+
order: entry.order,
|
|
18582
|
+
children: this._buildTreeIndexed(entry.id, maxDepth, currentDepth + 1, next)
|
|
18583
|
+
};
|
|
18584
|
+
});
|
|
18585
|
+
}
|
|
18265
18586
|
_buildTree(entries, rootId, maxDepth, currentDepth, visited) {
|
|
18266
18587
|
if (maxDepth >= 0 && currentDepth >= maxDepth) return [];
|
|
18267
18588
|
return entries.filter((e) => e.parentId === rootId).sort((a, b) => (a.order ?? 0) - (b.order ?? 0)).filter((e) => !visited.has(e.id)).map((entry) => {
|
|
@@ -18312,7 +18633,7 @@ var TreeManager = class {
|
|
|
18312
18633
|
}
|
|
18313
18634
|
/** Search by label (case-insensitive substring match). */
|
|
18314
18635
|
find(query, rootId) {
|
|
18315
|
-
const entries = this.
|
|
18636
|
+
const entries = this.normalizedEntries();
|
|
18316
18637
|
const lowerQuery = query.toLowerCase();
|
|
18317
18638
|
const normalized = normalizeRootId(rootId ?? null, this.dm.rootDocId);
|
|
18318
18639
|
const matches = (normalized ? this.descendantsOf(normalized) : entries).filter((e) => e.label.toLowerCase().includes(lowerQuery));
|
|
@@ -18346,7 +18667,7 @@ var TreeManager = class {
|
|
|
18346
18667
|
const normalizedParent = normalizeRootId(opts.parentId ?? null, this.dm.rootDocId);
|
|
18347
18668
|
const now = Date.now();
|
|
18348
18669
|
rootDoc.transact(() => {
|
|
18349
|
-
treeMap.set(id, {
|
|
18670
|
+
treeMap.set(id, makeEntryMap({
|
|
18350
18671
|
label: opts.label,
|
|
18351
18672
|
parentId: normalizedParent,
|
|
18352
18673
|
order: now,
|
|
@@ -18354,7 +18675,7 @@ var TreeManager = class {
|
|
|
18354
18675
|
meta: opts.meta,
|
|
18355
18676
|
createdAt: now,
|
|
18356
18677
|
updatedAt: now
|
|
18357
|
-
});
|
|
18678
|
+
}));
|
|
18358
18679
|
});
|
|
18359
18680
|
return {
|
|
18360
18681
|
id,
|
|
@@ -18393,12 +18714,9 @@ var TreeManager = class {
|
|
|
18393
18714
|
const treeMap = this.dm.getTreeMap();
|
|
18394
18715
|
const rootDoc = this.dm.rootDocument;
|
|
18395
18716
|
if (!treeMap || !rootDoc) throw new Error("Not connected");
|
|
18396
|
-
|
|
18397
|
-
if (!raw) throw new Error(`Document ${docId} not found`);
|
|
18398
|
-
const entry = toPlain(raw);
|
|
18717
|
+
if (!treeMap.get(docId)) throw new Error(`Document ${docId} not found`);
|
|
18399
18718
|
rootDoc.transact(() => {
|
|
18400
|
-
treeMap
|
|
18401
|
-
...entry,
|
|
18719
|
+
patchEntry(treeMap, docId, {
|
|
18402
18720
|
label,
|
|
18403
18721
|
updatedAt: Date.now()
|
|
18404
18722
|
});
|
|
@@ -18409,12 +18727,9 @@ var TreeManager = class {
|
|
|
18409
18727
|
const treeMap = this.dm.getTreeMap();
|
|
18410
18728
|
const rootDoc = this.dm.rootDocument;
|
|
18411
18729
|
if (!treeMap || !rootDoc) throw new Error("Not connected");
|
|
18412
|
-
|
|
18413
|
-
if (!raw) throw new Error(`Document ${docId} not found`);
|
|
18414
|
-
const entry = toPlain(raw);
|
|
18730
|
+
if (!treeMap.get(docId)) throw new Error(`Document ${docId} not found`);
|
|
18415
18731
|
rootDoc.transact(() => {
|
|
18416
|
-
treeMap
|
|
18417
|
-
...entry,
|
|
18732
|
+
patchEntry(treeMap, docId, {
|
|
18418
18733
|
parentId: normalizeRootId(newParentId ?? null, this.dm.rootDocId),
|
|
18419
18734
|
order: order ?? Date.now(),
|
|
18420
18735
|
updatedAt: Date.now()
|
|
@@ -18426,12 +18741,9 @@ var TreeManager = class {
|
|
|
18426
18741
|
const treeMap = this.dm.getTreeMap();
|
|
18427
18742
|
const rootDoc = this.dm.rootDocument;
|
|
18428
18743
|
if (!treeMap || !rootDoc) throw new Error("Not connected");
|
|
18429
|
-
|
|
18430
|
-
if (!raw) throw new Error(`Document ${docId} not found`);
|
|
18431
|
-
const entry = toPlain(raw);
|
|
18744
|
+
if (!treeMap.get(docId)) throw new Error(`Document ${docId} not found`);
|
|
18432
18745
|
rootDoc.transact(() => {
|
|
18433
|
-
treeMap
|
|
18434
|
-
...entry,
|
|
18746
|
+
patchEntry(treeMap, docId, {
|
|
18435
18747
|
type,
|
|
18436
18748
|
updatedAt: Date.now()
|
|
18437
18749
|
});
|
|
@@ -18446,10 +18758,12 @@ var TreeManager = class {
|
|
|
18446
18758
|
const trashMap = this.dm.getTrashMap();
|
|
18447
18759
|
const rootDoc = this.dm.rootDocument;
|
|
18448
18760
|
if (!treeMap || !trashMap || !rootDoc) throw new Error("Not connected");
|
|
18449
|
-
const entries = this.readEntries();
|
|
18450
|
-
const toDelete = [docId, ...this._descendantIds(entries, docId)];
|
|
18451
18761
|
const now = Date.now();
|
|
18762
|
+
let deletedCount = 0;
|
|
18452
18763
|
rootDoc.transact(() => {
|
|
18764
|
+
const entries = this.readEntries();
|
|
18765
|
+
const toDelete = [docId, ...this._descendantIds(entries, docId)];
|
|
18766
|
+
deletedCount = toDelete.length;
|
|
18453
18767
|
for (const nid of toDelete) {
|
|
18454
18768
|
const raw = treeMap.get(nid);
|
|
18455
18769
|
if (!raw) continue;
|
|
@@ -18465,7 +18779,7 @@ var TreeManager = class {
|
|
|
18465
18779
|
treeMap.delete(nid);
|
|
18466
18780
|
}
|
|
18467
18781
|
});
|
|
18468
|
-
return
|
|
18782
|
+
return deletedCount;
|
|
18469
18783
|
}
|
|
18470
18784
|
/** Duplicate a document (shallow clone). Returns the new entry. */
|
|
18471
18785
|
duplicate(docId) {
|
|
@@ -18477,13 +18791,13 @@ var TreeManager = class {
|
|
|
18477
18791
|
const newId = crypto.randomUUID();
|
|
18478
18792
|
const now = Date.now();
|
|
18479
18793
|
const newLabel = (entry.label || "Untitled") + " (copy)";
|
|
18480
|
-
treeMap.set(newId, {
|
|
18794
|
+
treeMap.set(newId, makeEntryMap({
|
|
18481
18795
|
...entry,
|
|
18482
18796
|
label: newLabel,
|
|
18483
18797
|
order: now,
|
|
18484
18798
|
createdAt: now,
|
|
18485
18799
|
updatedAt: now
|
|
18486
|
-
});
|
|
18800
|
+
}));
|
|
18487
18801
|
return {
|
|
18488
18802
|
id: newId,
|
|
18489
18803
|
label: newLabel,
|
|
@@ -18501,21 +18815,37 @@ var TreeManager = class {
|
|
|
18501
18815
|
const trashMap = this.dm.getTrashMap();
|
|
18502
18816
|
const rootDoc = this.dm.rootDocument;
|
|
18503
18817
|
if (!treeMap || !trashMap || !rootDoc) throw new Error("Not connected");
|
|
18504
|
-
|
|
18505
|
-
if (!raw) throw new Error(`Document ${docId} not found in trash`);
|
|
18506
|
-
const entry = toPlain(raw);
|
|
18818
|
+
if (!trashMap.get(docId)) throw new Error(`Document ${docId} not found in trash`);
|
|
18507
18819
|
const now = Date.now();
|
|
18508
18820
|
rootDoc.transact(() => {
|
|
18509
|
-
|
|
18510
|
-
|
|
18511
|
-
|
|
18512
|
-
|
|
18513
|
-
type: entry.type,
|
|
18514
|
-
meta: entry.meta,
|
|
18515
|
-
createdAt: entry.createdAt ?? now,
|
|
18516
|
-
updatedAt: now
|
|
18821
|
+
const trashed = /* @__PURE__ */ new Map();
|
|
18822
|
+
trashMap.forEach((raw, id) => {
|
|
18823
|
+
const v = toPlain(raw);
|
|
18824
|
+
if (typeof v === "object" && v !== null) trashed.set(id, v);
|
|
18517
18825
|
});
|
|
18518
|
-
|
|
18826
|
+
const toRestore = [];
|
|
18827
|
+
const visited = /* @__PURE__ */ new Set();
|
|
18828
|
+
const collect = (id) => {
|
|
18829
|
+
if (visited.has(id)) return;
|
|
18830
|
+
visited.add(id);
|
|
18831
|
+
if (!trashed.has(id)) return;
|
|
18832
|
+
toRestore.push(id);
|
|
18833
|
+
for (const [cid, v] of trashed) if ((v.parentId ?? null) === id) collect(cid);
|
|
18834
|
+
};
|
|
18835
|
+
collect(docId);
|
|
18836
|
+
for (const id of toRestore) {
|
|
18837
|
+
const entry = trashed.get(id);
|
|
18838
|
+
treeMap.set(id, makeEntryMap({
|
|
18839
|
+
label: entry.label || "Untitled",
|
|
18840
|
+
parentId: entry.parentId ?? null,
|
|
18841
|
+
order: entry.order ?? now,
|
|
18842
|
+
type: entry.type,
|
|
18843
|
+
meta: entry.meta,
|
|
18844
|
+
createdAt: entry.createdAt ?? now,
|
|
18845
|
+
updatedAt: now
|
|
18846
|
+
}));
|
|
18847
|
+
trashMap.delete(id);
|
|
18848
|
+
}
|
|
18519
18849
|
});
|
|
18520
18850
|
}
|
|
18521
18851
|
/** List trashed documents. */
|
|
@@ -19886,9 +20216,9 @@ var ContentManager = class {
|
|
|
19886
20216
|
* body, tree metadata, and immediate children.
|
|
19887
20217
|
*/
|
|
19888
20218
|
async read(docId) {
|
|
19889
|
-
const
|
|
20219
|
+
const fragment = (await this.dm.getChildProvider(docId)).document.getXmlFragment("default");
|
|
19890
20220
|
const treeMap = this.dm.getTreeMap();
|
|
19891
|
-
let label =
|
|
20221
|
+
let label = "Untitled";
|
|
19892
20222
|
let type;
|
|
19893
20223
|
let meta;
|
|
19894
20224
|
const childrenWithOrder = [];
|
|
@@ -19896,7 +20226,7 @@ var ContentManager = class {
|
|
|
19896
20226
|
const raw = treeMap.get(docId);
|
|
19897
20227
|
if (raw) {
|
|
19898
20228
|
const entry = toPlain(raw);
|
|
19899
|
-
label = entry.label ||
|
|
20229
|
+
label = entry.label || label;
|
|
19900
20230
|
type = entry.type;
|
|
19901
20231
|
meta = entry.meta;
|
|
19902
20232
|
}
|
|
@@ -19918,11 +20248,12 @@ var ContentManager = class {
|
|
|
19918
20248
|
type,
|
|
19919
20249
|
meta
|
|
19920
20250
|
}));
|
|
20251
|
+
const markdown = yjsToMarkdown(fragment, label, meta, type);
|
|
19921
20252
|
return {
|
|
19922
20253
|
label,
|
|
19923
20254
|
type,
|
|
19924
20255
|
meta,
|
|
19925
|
-
title,
|
|
20256
|
+
title: label,
|
|
19926
20257
|
markdown,
|
|
19927
20258
|
children
|
|
19928
20259
|
};
|
|
@@ -19944,16 +20275,14 @@ var ContentManager = class {
|
|
|
19944
20275
|
if (treeMap && rootDoc) {
|
|
19945
20276
|
const entry = treeMap.get(docId);
|
|
19946
20277
|
if (entry) rootDoc.transact(() => {
|
|
19947
|
-
const
|
|
19948
|
-
|
|
19949
|
-
|
|
19950
|
-
|
|
19951
|
-
|
|
19952
|
-
if (Object.keys(meta).length > 0) updates.meta = {
|
|
19953
|
-
...entry.meta ?? {},
|
|
20278
|
+
const cur = toPlain(entry);
|
|
20279
|
+
const patch = { updatedAt: Date.now() };
|
|
20280
|
+
if (title) patch.label = title;
|
|
20281
|
+
if (Object.keys(meta).length > 0) patch.meta = {
|
|
20282
|
+
...cur.meta ?? {},
|
|
19954
20283
|
...meta
|
|
19955
20284
|
};
|
|
19956
|
-
treeMap
|
|
20285
|
+
patchEntry(treeMap, docId, patch);
|
|
19957
20286
|
});
|
|
19958
20287
|
}
|
|
19959
20288
|
}
|
|
@@ -20072,8 +20401,7 @@ var MetaManager = class {
|
|
|
20072
20401
|
...meta
|
|
20073
20402
|
};
|
|
20074
20403
|
this.validateOrThrow(docId, entry, mergedMeta);
|
|
20075
|
-
treeMap
|
|
20076
|
-
...entry,
|
|
20404
|
+
patchEntry(treeMap, docId, {
|
|
20077
20405
|
meta: mergedMeta,
|
|
20078
20406
|
updatedAt: Date.now()
|
|
20079
20407
|
});
|
|
@@ -20092,8 +20420,7 @@ var MetaManager = class {
|
|
|
20092
20420
|
if (!raw) throw new Error(`Document ${docId} not found`);
|
|
20093
20421
|
const entry = toPlain(raw);
|
|
20094
20422
|
this.validateOrThrow(docId, entry, meta);
|
|
20095
|
-
treeMap
|
|
20096
|
-
...entry,
|
|
20423
|
+
patchEntry(treeMap, docId, {
|
|
20097
20424
|
meta,
|
|
20098
20425
|
updatedAt: Date.now()
|
|
20099
20426
|
});
|
|
@@ -20110,8 +20437,7 @@ var MetaManager = class {
|
|
|
20110
20437
|
const updated = { ...entry.meta ?? {} };
|
|
20111
20438
|
for (const key of keys) delete updated[key];
|
|
20112
20439
|
this.validateOrThrow(docId, entry, updated);
|
|
20113
|
-
treeMap
|
|
20114
|
-
...entry,
|
|
20440
|
+
patchEntry(treeMap, docId, {
|
|
20115
20441
|
meta: updated,
|
|
20116
20442
|
updatedAt: Date.now()
|
|
20117
20443
|
});
|
|
@@ -20218,6 +20544,13 @@ var DocumentManager = class {
|
|
|
20218
20544
|
get rootDocId() {
|
|
20219
20545
|
return this._rootDocId;
|
|
20220
20546
|
}
|
|
20547
|
+
/**
|
|
20548
|
+
* Whether the TreeManager in-memory index is enabled (Path-1 prototype).
|
|
20549
|
+
* Off by default — see {@link DocumentManagerConfig.treeIndex}.
|
|
20550
|
+
*/
|
|
20551
|
+
get treeIndexEnabled() {
|
|
20552
|
+
return this._config.treeIndex ?? false;
|
|
20553
|
+
}
|
|
20221
20554
|
get rootDocument() {
|
|
20222
20555
|
return this._rootDoc;
|
|
20223
20556
|
}
|
|
@@ -20347,5 +20680,5 @@ var DocumentManager = class {
|
|
|
20347
20680
|
};
|
|
20348
20681
|
|
|
20349
20682
|
//#endregion
|
|
20350
|
-
export { AbracadabraBaseProvider, AbracadabraClient, AbracadabraProvider, AbracadabraWS, AbracadabraWebRTC, AuthMessageType, AwarenessError, BackgroundSyncManager, BackgroundSyncPersistence, BroadcastChannelSync, CHANNEL_NAMES, ChannelKeyResolver, ChatClient, ConnectionTimeout, ContentManager, CryptoIdentityKeystore, DEFAULT_FILE_CHUNK_SIZE, DEFAULT_ICE_SERVERS, DataChannelRouter, DevicePairingChannel, DeviceRegistrationService, DocKeyManager, DocumentCache, DocumentManager, E2EAbracadabraProvider, E2EEChannel, E2EOfflineStore, EncryptedChatClient, EncryptedYMap, EncryptedYText, FileBlobStore, FileTransferChannel, FileTransferHandle, Forbidden, GEO_TYPE_META_SCHEMAS, HocuspocusProvider, HocuspocusProviderWebsocket, IdentityDocProvider, KEY_EXCHANGE_CHANNEL, Kind, LocalStorageDeviceSessionStorage, ManualSignaling, MessageTooBig, MessageType, MetaManager, MetaValidationError, NotificationsClient, OfflineStore, PAGE_TYPES, PeerConnection, QUERY_PREFIX, QueryClient, QueryError, RPC_PREFIX, ResetConnection, RpcClient, RpcError, SERVER_ROOT_ID, SearchIndex, SignalingSocket, SubdocMessage, TYPE_ALIASES, TokenManager, TreeManager, TypedDocTypeMismatchError, Unauthorized, WebSocketStatus, WsReadyStates, YjsDataChannel, attachUpdatedAtObserver, awarenessStatesToArray, wordlist as bip39Wordlist, buildBlockquoteElement, buildBlocksFromMarkdown, buildBulletListElement, buildCodeBlockElement, buildHeadingElement, buildHorizontalRuleElement, buildOrderedListElement, buildParagraphElement, buildTaskListElement, decryptChatContent, decryptField, deriveIdentityDocId, deriveSeedWrappingKey, encryptChatContent, encryptField, filenameToLabel, foldRecords, generateMnemonic, isEncryptedContent, makeEncryptedYMap, makeEncryptedYText, mnemonicToEd25519Seed, mnemonicToKeyPair, normalizeRootId, parseFrontmatter, populateYDocFromMarkdown, readAuthMessage, readBlocksFromFragment, recordFromYAny, resolvePageType, toPlain, unwrapSeed, validateMnemonic, waitForSync, withTimeout, wrapSeed, writeAuthenticated, writeAuthentication, writePermissionDenied, writeTokenSyncRequest, yjsToMarkdown };
|
|
20683
|
+
export { AbracadabraBaseProvider, AbracadabraClient, AbracadabraProvider, AbracadabraWS, AbracadabraWebRTC, AuthMessageType, AwarenessError, BackgroundSyncManager, BackgroundSyncPersistence, BroadcastChannelSync, CHANNEL_NAMES, ChannelKeyResolver, ChatClient, ConnectionTimeout, ContentManager, CryptoIdentityKeystore, DEFAULT_FILE_CHUNK_SIZE, DEFAULT_ICE_SERVERS, DataChannelRouter, DevicePairingChannel, DeviceRegistrationService, DocKeyManager, DocumentCache, DocumentManager, E2EAbracadabraProvider, E2EEChannel, E2EOfflineStore, EncryptedChatClient, EncryptedYMap, EncryptedYText, FileBlobStore, FileTransferChannel, FileTransferHandle, Forbidden, GEO_TYPE_META_SCHEMAS, HocuspocusProvider, HocuspocusProviderWebsocket, IdentityDocProvider, KEY_EXCHANGE_CHANNEL, Kind, LocalStorageDeviceSessionStorage, ManualSignaling, MessageTooBig, MessageType, MetaManager, MetaValidationError, NotificationsClient, OfflineStore, PAGE_TYPES, PeerConnection, QUERY_PREFIX, QueryClient, QueryError, RPC_PREFIX, ResetConnection, RpcClient, RpcError, SERVER_ROOT_ID, SearchIndex, SignalingSocket, SubdocMessage, TYPE_ALIASES, TokenManager, TreeManager, TypedDocTypeMismatchError, Unauthorized, WebSocketStatus, WsReadyStates, YjsDataChannel, attachUpdatedAtObserver, awarenessStatesToArray, wordlist as bip39Wordlist, buildBlockquoteElement, buildBlocksFromMarkdown, buildBulletListElement, buildCodeBlockElement, buildHeadingElement, buildHorizontalRuleElement, buildOrderedListElement, buildParagraphElement, buildTaskListElement, decryptChatContent, decryptField, deriveIdentityDocId, deriveSeedWrappingKey, encryptChatContent, encryptField, filenameToLabel, foldRecords, generateMnemonic, isEncryptedContent, makeEncryptedYMap, makeEncryptedYText, makeEntryMap, mnemonicToEd25519Seed, mnemonicToKeyPair, normalizeRootId, parseFrontmatter, patchEntry, populateYDocFromMarkdown, readAuthMessage, readBlocksFromFragment, recordFromYAny, resolvePageType, toPlain, unwrapSeed, validateMnemonic, waitForSync, withTimeout, wrapSeed, writeAuthenticated, writeAuthentication, writePermissionDenied, writeTokenSyncRequest, yjsToMarkdown };
|
|
20351
20684
|
//# sourceMappingURL=abracadabra-provider.esm.js.map
|