@abraca/dabra 1.0.16 → 1.0.18
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 +72 -6
- package/dist/abracadabra-provider.cjs.map +1 -1
- package/dist/abracadabra-provider.esm.js +72 -6
- package/dist/abracadabra-provider.esm.js.map +1 -1
- package/dist/index.d.ts +47 -4
- package/package.json +1 -1
- package/src/AbracadabraClient.ts +32 -0
- package/src/AbracadabraProvider.ts +21 -2
- package/src/BackgroundSyncManager.ts +59 -9
- package/src/types.ts +16 -4
|
@@ -2835,9 +2835,10 @@ var AbracadabraProvider = class AbracadabraProvider extends AbracadabraBaseProvi
|
|
|
2835
2835
|
owner: "owner",
|
|
2836
2836
|
editor: "editor",
|
|
2837
2837
|
viewer: "viewer",
|
|
2838
|
+
observer: "observer",
|
|
2838
2839
|
"read-write": "editor",
|
|
2839
2840
|
readonly: "viewer"
|
|
2840
|
-
}[scope] ?? "
|
|
2841
|
+
}[scope] ?? "observer";
|
|
2841
2842
|
this.offlineStore?.savePermissionSnapshot(this.effectiveRole);
|
|
2842
2843
|
}
|
|
2843
2844
|
/**
|
|
@@ -2892,7 +2893,10 @@ var AbracadabraProvider = class AbracadabraProvider extends AbracadabraBaseProvi
|
|
|
2892
2893
|
if (role && !this.effectiveRole) this.effectiveRole = role;
|
|
2893
2894
|
}
|
|
2894
2895
|
get canWrite() {
|
|
2895
|
-
return this.effectiveRole != null && this.effectiveRole !== "viewer";
|
|
2896
|
+
return this.effectiveRole != null && this.effectiveRole !== "viewer" && this.effectiveRole !== "observer";
|
|
2897
|
+
}
|
|
2898
|
+
get canAwareness() {
|
|
2899
|
+
return this.effectiveRole != null && this.effectiveRole !== "observer";
|
|
2896
2900
|
}
|
|
2897
2901
|
/** The AbracadabraClient instance for REST API access, if configured. */
|
|
2898
2902
|
get client() {
|
|
@@ -3066,8 +3070,11 @@ var AbracadabraProvider = class AbracadabraProvider extends AbracadabraBaseProvi
|
|
|
3066
3070
|
}
|
|
3067
3071
|
destroy() {
|
|
3068
3072
|
this.document.off("subdocs", this.boundHandleYSubdocsChange);
|
|
3073
|
+
const childIds = [...this.childProviders.keys()];
|
|
3069
3074
|
for (const provider of this.childProviders.values()) provider.destroy();
|
|
3070
3075
|
this.childProviders.clear();
|
|
3076
|
+
const wsProviderMap = this.configuration.websocketProvider?.configuration?.providerMap;
|
|
3077
|
+
if (wsProviderMap) for (const childId of childIds) wsProviderMap.delete(childId);
|
|
3071
3078
|
this.offlineStore?.destroy();
|
|
3072
3079
|
this.offlineStore = null;
|
|
3073
3080
|
super.destroy();
|
|
@@ -3275,6 +3282,10 @@ var AbracadabraClient = class {
|
|
|
3275
3282
|
async createChild(docId, opts) {
|
|
3276
3283
|
return this.request("POST", `/docs/${encodeURIComponent(docId)}/children`, { body: opts ?? {} });
|
|
3277
3284
|
}
|
|
3285
|
+
/** Broadcast a stateless message to all connected clients on a document (requires manage permission). */
|
|
3286
|
+
async broadcast(docId, payload) {
|
|
3287
|
+
await this.request("POST", `/docs/${encodeURIComponent(docId)}/broadcast`, { body: { payload } });
|
|
3288
|
+
}
|
|
3278
3289
|
/** List all permissions for a document (requires read access). */
|
|
3279
3290
|
async listPermissions(docId) {
|
|
3280
3291
|
if (this.cache) {
|
|
@@ -3390,6 +3401,26 @@ var AbracadabraClient = class {
|
|
|
3390
3401
|
async deleteSpace(spaceId) {
|
|
3391
3402
|
await this.request("DELETE", `/spaces/${encodeURIComponent(spaceId)}`);
|
|
3392
3403
|
}
|
|
3404
|
+
/** List all users (requires elevated role: admin or service). */
|
|
3405
|
+
async adminListUsers() {
|
|
3406
|
+
return this.request("GET", "/admin/users");
|
|
3407
|
+
}
|
|
3408
|
+
/** Promote a user to admin (requires service role). */
|
|
3409
|
+
async adminPromote(userId) {
|
|
3410
|
+
await this.request("POST", `/admin/users/${encodeURIComponent(userId)}/admin`);
|
|
3411
|
+
}
|
|
3412
|
+
/** Demote an admin user back to regular (requires service role). */
|
|
3413
|
+
async adminDemote(userId) {
|
|
3414
|
+
await this.request("DELETE", `/admin/users/${encodeURIComponent(userId)}/admin`);
|
|
3415
|
+
}
|
|
3416
|
+
/** Sweep orphaned file blobs from storage (requires elevated role). */
|
|
3417
|
+
async adminStorageSweep() {
|
|
3418
|
+
return this.request("POST", "/admin/storage/sweep");
|
|
3419
|
+
}
|
|
3420
|
+
/** Repair blob ref-counts and sweep orphans (requires elevated role). */
|
|
3421
|
+
async adminStorageRepair() {
|
|
3422
|
+
return this.request("POST", "/admin/storage/repair");
|
|
3423
|
+
}
|
|
3393
3424
|
/** Health check — no auth required. */
|
|
3394
3425
|
async health() {
|
|
3395
3426
|
return this.request("GET", "/health", { auth: false });
|
|
@@ -8339,7 +8370,9 @@ var BackgroundSyncManager = class extends EventEmitter {
|
|
|
8339
8370
|
this.opts = {
|
|
8340
8371
|
concurrency: opts?.concurrency ?? 2,
|
|
8341
8372
|
syncTimeout: opts?.syncTimeout ?? 15e3,
|
|
8342
|
-
prefetchFiles: opts?.prefetchFiles ?? true
|
|
8373
|
+
prefetchFiles: opts?.prefetchFiles ?? true,
|
|
8374
|
+
throttleMs: opts?.throttleMs ?? 50,
|
|
8375
|
+
maxRetries: opts?.maxRetries ?? 2
|
|
8343
8376
|
};
|
|
8344
8377
|
let serverOrigin = "default";
|
|
8345
8378
|
try {
|
|
@@ -8373,7 +8406,38 @@ var BackgroundSyncManager = class extends EventEmitter {
|
|
|
8373
8406
|
for (const [docId, v] of entries) updatedAtMap.set(docId, v?.updatedAt ?? v?.createdAt ?? 0);
|
|
8374
8407
|
this._prefetchCovers(entries).catch(() => null);
|
|
8375
8408
|
const queue = this._buildQueue(entries);
|
|
8376
|
-
|
|
8409
|
+
const failed = [];
|
|
8410
|
+
let idx = 0;
|
|
8411
|
+
const next = async () => {
|
|
8412
|
+
while (idx < queue.length) {
|
|
8413
|
+
if (this._destroyed) return;
|
|
8414
|
+
const docId = queue[idx++];
|
|
8415
|
+
const updatedAt = updatedAtMap.get(docId) ?? 0;
|
|
8416
|
+
if (!await this._syncWithSemaphore(docId, updatedAt)) failed.push(docId);
|
|
8417
|
+
if (this.opts.throttleMs > 0 && idx < queue.length) await new Promise((r) => setTimeout(r, this.opts.throttleMs));
|
|
8418
|
+
}
|
|
8419
|
+
};
|
|
8420
|
+
const workers = Array.from({ length: this.opts.concurrency }, () => next());
|
|
8421
|
+
await Promise.all(workers);
|
|
8422
|
+
for (let retry = 0; retry < this.opts.maxRetries && failed.length > 0; retry++) {
|
|
8423
|
+
if (this._destroyed) return;
|
|
8424
|
+
const batch = failed.splice(0, failed.length);
|
|
8425
|
+
const backoff = 2e3 * 2 ** retry;
|
|
8426
|
+
await new Promise((r) => setTimeout(r, backoff));
|
|
8427
|
+
idx = 0;
|
|
8428
|
+
const retryQueue = batch;
|
|
8429
|
+
const retryNext = async () => {
|
|
8430
|
+
while (idx < retryQueue.length) {
|
|
8431
|
+
if (this._destroyed) return;
|
|
8432
|
+
const docId = retryQueue[idx++];
|
|
8433
|
+
const updatedAt = updatedAtMap.get(docId) ?? 0;
|
|
8434
|
+
if (!await this._syncWithSemaphore(docId, updatedAt)) failed.push(docId);
|
|
8435
|
+
if (this.opts.throttleMs > 0 && idx < retryQueue.length) await new Promise((r) => setTimeout(r, this.opts.throttleMs));
|
|
8436
|
+
}
|
|
8437
|
+
};
|
|
8438
|
+
const retryWorkers = Array.from({ length: this.opts.concurrency }, () => retryNext());
|
|
8439
|
+
await Promise.all(retryWorkers);
|
|
8440
|
+
}
|
|
8377
8441
|
}
|
|
8378
8442
|
/** Sync a single document by ID. */
|
|
8379
8443
|
async syncDoc(docId) {
|
|
@@ -8454,15 +8518,16 @@ var BackgroundSyncManager = class extends EventEmitter {
|
|
|
8454
8518
|
items.sort((a, b) => b.priority - a.priority);
|
|
8455
8519
|
return items.map((i) => i.docId);
|
|
8456
8520
|
}
|
|
8521
|
+
/** Returns true on success (or skip), false on error. */
|
|
8457
8522
|
async _syncWithSemaphore(docId, updatedAt) {
|
|
8458
|
-
if (this._destroyed) return;
|
|
8523
|
+
if (this._destroyed) return true;
|
|
8459
8524
|
const existing = this.syncStates.get(docId);
|
|
8460
8525
|
if (existing && existing.status === "synced" && existing.lastSynced !== null && existing.lastSynced >= updatedAt) {
|
|
8461
8526
|
this.emit("stateChanged", {
|
|
8462
8527
|
docId,
|
|
8463
8528
|
state: existing
|
|
8464
8529
|
});
|
|
8465
|
-
return;
|
|
8530
|
+
return true;
|
|
8466
8531
|
}
|
|
8467
8532
|
await this.semaphore.acquire();
|
|
8468
8533
|
try {
|
|
@@ -8473,6 +8538,7 @@ var BackgroundSyncManager = class extends EventEmitter {
|
|
|
8473
8538
|
docId,
|
|
8474
8539
|
state
|
|
8475
8540
|
});
|
|
8541
|
+
return state.status !== "error";
|
|
8476
8542
|
} finally {
|
|
8477
8543
|
this.semaphore.release();
|
|
8478
8544
|
}
|