@abraca/dabra 1.0.18 → 1.0.20

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/index.d.ts CHANGED
@@ -699,7 +699,7 @@ declare class AbracadabraProvider extends AbracadabraBaseProvider {
699
699
  */
700
700
  loadChild(childId: string): Promise<AbracadabraProvider>;
701
701
  private _doLoadChild;
702
- private unloadChild;
702
+ unloadChild(childId: string): void;
703
703
  /** Return all currently-loaded child providers. */
704
704
  get children(): Map<string, AbracadabraProvider>;
705
705
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abraca/dabra",
3
- "version": "1.0.18",
3
+ "version": "1.0.20",
4
4
  "description": "abracadabra provider",
5
5
  "keywords": [
6
6
  "abracadabra",
@@ -497,7 +497,7 @@ export class AbracadabraProvider extends AbracadabraBaseProvider {
497
497
  return childProvider;
498
498
  }
499
499
 
500
- private unloadChild(childId: string) {
500
+ unloadChild(childId: string) {
501
501
  const provider = this.childProviders.get(childId);
502
502
  if (provider) {
503
503
  provider.destroy();
@@ -220,9 +220,8 @@ export class AbracadabraWS extends EventEmitter {
220
220
  attach(provider: AbracadabraBaseProvider) {
221
221
  const existing = this.configuration.providerMap.get(provider.configuration.name);
222
222
  if (existing && existing !== provider) {
223
- console.warn(
224
- `[AbracadabraWS] attach: overwriting provider for "${provider.configuration.name}". ` +
225
- `This may indicate a duplicate loadChild for the same document.`
223
+ console.debug(
224
+ `[AbracadabraWS] attach: replacing provider for "${provider.configuration.name}".`
226
225
  );
227
226
  }
228
227
  this.configuration.providerMap.set(provider.configuration.name, provider);
@@ -391,7 +391,28 @@ export class BackgroundSyncManager extends EventEmitter {
391
391
  }
392
392
 
393
393
  private async _syncNonE2EDoc(docId: string): Promise<DocSyncState> {
394
- // loadChild() returns cached provider if already open
394
+ // Check if the provider already exists (user is viewing it) before loading.
395
+ const alreadyCached = this.rootProvider.children.has(docId);
396
+
397
+ // Another provider (e.g. a nested renderer) may have already loaded
398
+ // this doc through a different parent. The shared WS providerMap
399
+ // tracks all attached providers; if one exists, the doc is already
400
+ // syncing — skip to avoid creating a duplicate.
401
+ if (!alreadyCached) {
402
+ const wsProviderMap = (this.rootProvider as any).configuration
403
+ ?.websocketProvider?.configuration?.providerMap as
404
+ | Map<string, unknown>
405
+ | undefined;
406
+ if (wsProviderMap?.has(docId)) {
407
+ return {
408
+ docId,
409
+ status: "synced",
410
+ lastSynced: Date.now(),
411
+ isE2E: false,
412
+ };
413
+ }
414
+ }
415
+
395
416
  const childProvider = await this.rootProvider.loadChild(docId);
396
417
 
397
418
  // Wait for ready (offline snapshot loaded) then synced (server sync done)
@@ -403,6 +424,13 @@ export class BackgroundSyncManager extends EventEmitter {
403
424
  this._prefetchDocFiles(docId, childProvider.document).catch(() => null);
404
425
  }
405
426
 
427
+ // Release provider if it was created solely for background sync.
428
+ // This closes its IDB database, freeing the file descriptor.
429
+ // Providers that were already cached (user is viewing them) are kept alive.
430
+ if (!alreadyCached) {
431
+ this.rootProvider.unloadChild(docId);
432
+ }
433
+
406
434
  return { docId, status: "synced", lastSynced: Date.now(), isE2E: false };
407
435
  }
408
436
 
@@ -277,11 +277,15 @@ export class OfflineStore {
277
277
 
278
278
  destroy() {
279
279
  // Set the destroyed flag first so getDb() returns null for any new operations.
280
- // Do NOT call db.close() here — in-flight IDB transactions hold their own
281
- // reference to the database and closing it prematurely causes InvalidStateError.
282
- // The DB will be closed and garbage-collected once all transactions complete.
283
280
  this._destroyed = true;
281
+ const db = this.db;
284
282
  this.db = null;
285
283
  this.dbPromise = null;
284
+ // Close the IDB connection to free the file descriptor. Deferred to the
285
+ // next microtask so any in-flight transaction callbacks settle first.
286
+ // IDB spec: close() waits for running transactions to finish before closing.
287
+ if (db) {
288
+ Promise.resolve().then(() => db.close());
289
+ }
286
290
  }
287
291
  }