@bobfrankston/rmfmail 1.2.2 → 1.2.3

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.
@@ -291,19 +291,45 @@ export class MailxService implements MailxApi {
291
291
  }
292
292
  }
293
293
 
294
+ /** In-flight read coalescing. A slow daemon makes the UI re-fire the same
295
+ * read (scroll loadMore retries, poll loops, re-render storms): the log
296
+ * showed the IDENTICAL getUnifiedInbox queued dozens of times and all
297
+ * draining at once after a 78s stall. Keyed by method+args, a duplicate
298
+ * request that arrives while one is already running shares that single
299
+ * promise instead of enqueuing another worker job. Same args ⇒ same
300
+ * result, so this is always safe; different args (other pages) key
301
+ * separately. This caps the worker queue at one job per distinct read. */
302
+ private inflightReads = new Map<string, Promise<any>>();
303
+
294
304
  /** Route a read through the worker bus if it's up, else run it in-process.
295
305
  * A worker-side error (or a dead worker) transparently falls back so a
296
- * read never fails just because the isolation layer hiccuped. */
306
+ * read never fails just because the isolation layer hiccuped. Identical
307
+ * concurrent reads are coalesced (see inflightReads). */
297
308
  private async read<R>(method: string, args: any, local: () => R): Promise<R> {
298
- const w = this.dbWorker;
299
- if (w) {
300
- try {
301
- return await w.bus.request<any, R>(method, args);
302
- } catch (e: any) {
303
- console.error(` [read-worker] ${method} failed (${e?.message || e}); in-process fallback`);
309
+ let key: string;
310
+ try { key = method + ":" + JSON.stringify(args); }
311
+ catch { key = ""; } // unserializable args → don't coalesce
312
+ if (key) {
313
+ const existing = this.inflightReads.get(key);
314
+ if (existing) return existing as Promise<R>;
315
+ }
316
+ const run = (async (): Promise<R> => {
317
+ const w = this.dbWorker;
318
+ if (w) {
319
+ try {
320
+ return await w.bus.request<any, R>(method, args);
321
+ } catch (e: any) {
322
+ console.error(` [read-worker] ${method} failed (${e?.message || e}); in-process fallback`);
323
+ }
304
324
  }
305
- }
306
- return local();
325
+ return local();
326
+ })();
327
+ if (key) {
328
+ this.inflightReads.set(key, run);
329
+ try { return await run; }
330
+ finally { this.inflightReads.delete(key); }
331
+ }
332
+ return run;
307
333
  }
308
334
  /** Transitional getter — direct DB access from MailxService for the
309
335
  * ~50 callsites that haven't yet migrated to Store methods. Future:
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-store",
3
- "version": "0.1.47",
3
+ "version": "0.1.48",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",