@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.
@@ -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] ?? "viewer";
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
- await Promise.all(queue.map((docId) => this._syncWithSemaphore(docId, updatedAtMap.get(docId) ?? 0)));
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
  }