@bobfrankston/mailx-store 0.1.49 → 0.1.50

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.
Files changed (3) hide show
  1. package/db.d.ts +9 -0
  2. package/db.js +32 -1
  3. package/package.json +3 -3
package/db.d.ts CHANGED
@@ -400,6 +400,15 @@ export declare class MailxDB {
400
400
  } | null;
401
401
  /** Clear a UID's prefetch-failure record after a successful fetch. */
402
402
  clearPrefetchFailure(accountId: string, folderId: number, uid: number): void;
403
+ /** Wipe ALL prefetch-failure backoffs. Called once per boot: the backoff is
404
+ * a within-SESSION optimisation (it stops a poison body being re-fetched
405
+ * every 60s cycle), so a fresh session should give every body another try —
406
+ * the network may have recovered, and crucially a body backed off by a
407
+ * transient cause (a two-writer DB lock, a Dovecot 300s timeout) shouldn't
408
+ * stay suppressed for up to 12h (Bob 2026-06-14: "lots of predownloading
409
+ * not happening"). Genuine poison bodies simply re-fail once and re-back-off
410
+ * for the session — no per-cycle loop. */
411
+ clearAllPrefetchFailures(): number;
403
412
  /** Highest server UID we've seen in this folder. After the
404
413
  * message_folders refactor, this reads from the membership table
405
414
  * rather than messages.uid — a server-side move from this folder
package/db.js CHANGED
@@ -419,7 +419,13 @@ export class MailxDB {
419
419
  // DDL/backfills concurrently would race on the WAL write lock.
420
420
  this.db.exec("PRAGMA journal_mode = WAL");
421
421
  this.db.exec("PRAGMA foreign_keys = ON");
422
- this.db.exec("PRAGMA busy_timeout = 5000");
422
+ // 10s (vs the main thread's 5s): this is the BACKGROUND writer (sync,
423
+ // prefetch); waiting a bit longer for the foreground's lock is fine.
424
+ // NOT higher because busy_timeout BLOCKS this thread synchronously
425
+ // (it can't forward sync events while parked) — for longer holds the
426
+ // prefetch write path uses an ASYNC retry that yields between
427
+ // attempts instead. Fixes prefetch "database is locked" (Bob 2026-06-14).
428
+ this.db.exec("PRAGMA busy_timeout = 10000");
423
429
  return;
424
430
  }
425
431
  if (this.readOnly) {
@@ -569,6 +575,15 @@ export class MailxDB {
569
575
  // this column landed. One UPDATE + an id roundtrip per row — cheap
570
576
  // at our row counts, runs once per DB upgrade.
571
577
  this.backfillUuids();
578
+ // Per-boot: clear prefetch-failure backoffs so transiently-failed bodies
579
+ // (two-writer DB lock, Dovecot timeout) get a fresh try this session
580
+ // instead of staying suppressed for up to 12h. See clearAllPrefetchFailures.
581
+ try {
582
+ const cleared = this.clearAllPrefetchFailures();
583
+ if (cleared > 0)
584
+ console.log(` [db] cleared ${cleared} prefetch-failure backoff(s) for a fresh session`);
585
+ }
586
+ catch { /* non-fatal */ }
572
587
  // One-shot cleanup: the retired insertOptimisticSentRow path wrote
573
588
  // synthetic-negative-UID rows into Sent. Those rows are stale (the
574
589
  // real server-synced row eventually appears with a positive UID),
@@ -2235,6 +2250,22 @@ export class MailxDB {
2235
2250
  clearPrefetchFailure(accountId, folderId, uid) {
2236
2251
  this.db.prepare("DELETE FROM prefetch_failures WHERE account_id = ? AND folder_id = ? AND uid = ?").run(accountId, folderId, uid);
2237
2252
  }
2253
+ /** Wipe ALL prefetch-failure backoffs. Called once per boot: the backoff is
2254
+ * a within-SESSION optimisation (it stops a poison body being re-fetched
2255
+ * every 60s cycle), so a fresh session should give every body another try —
2256
+ * the network may have recovered, and crucially a body backed off by a
2257
+ * transient cause (a two-writer DB lock, a Dovecot 300s timeout) shouldn't
2258
+ * stay suppressed for up to 12h (Bob 2026-06-14: "lots of predownloading
2259
+ * not happening"). Genuine poison bodies simply re-fail once and re-back-off
2260
+ * for the session — no per-cycle loop. */
2261
+ clearAllPrefetchFailures() {
2262
+ try {
2263
+ return this.db.prepare("DELETE FROM prefetch_failures").run().changes;
2264
+ }
2265
+ catch {
2266
+ return 0;
2267
+ }
2268
+ }
2238
2269
  /** Highest server UID we've seen in this folder. After the
2239
2270
  * message_folders refactor, this reads from the membership table
2240
2271
  * rather than messages.uid — a server-side move from this folder
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-store",
3
- "version": "0.1.49",
3
+ "version": "0.1.50",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -10,7 +10,7 @@
10
10
  "license": "ISC",
11
11
  "dependencies": {
12
12
  "@bobfrankston/mailx-types": "^0.1.19",
13
- "@bobfrankston/mailx-settings": "^0.1.26",
13
+ "@bobfrankston/mailx-settings": "^0.1.27",
14
14
  "@bobfrankston/mailx-bus": "^0.1.2",
15
15
  "mailparser": "^3.7.2"
16
16
  },
@@ -30,7 +30,7 @@
30
30
  ".transformedSnapshot": {
31
31
  "dependencies": {
32
32
  "@bobfrankston/mailx-types": "^0.1.19",
33
- "@bobfrankston/mailx-settings": "^0.1.26",
33
+ "@bobfrankston/mailx-settings": "^0.1.27",
34
34
  "@bobfrankston/mailx-bus": "^0.1.2",
35
35
  "mailparser": "^3.7.2"
36
36
  }