@bobfrankston/rmfmail 1.2.16 → 1.2.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.
@@ -488,6 +488,18 @@ export class MailxDB {
488
488
  try {
489
489
  this.db.exec("CREATE INDEX IF NOT EXISTS idx_messages_thread_id ON messages(account_id, thread_id)");
490
490
  } catch { /* already exists */ }
491
+ // (account_id, message_id) composite — THE hot-path lookup. upsertMessage's
492
+ // move-detect and computeThreadId both query `WHERE account_id=? AND
493
+ // message_id=?`. The old idx_messages_message_id is on message_id ALONE, so
494
+ // with an account_id predicate the planner instead picks an account_id-only
495
+ // index and SCANS every row for the account (Bob's bobma INBOX = 136k rows).
496
+ // One new-message upsert ran several such scans (one per reference); a
497
+ // 50-message batch = the 62-SECOND write txn the slow-txn logger caught
498
+ // (2026-06-16) — which held the write lock and starved every other writer.
499
+ // A leading composite turns each into an O(log n) seek.
500
+ try {
501
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_messages_acct_msgid ON messages(account_id, message_id)");
502
+ } catch { /* already exists */ }
491
503
  // is_replied: set when ANY other message in this account has in_reply_to
492
504
  // pointing at this row's message_id. Primary source of truth for the ↩
493
505
  // marker — \Answered is plan B (some servers strip it, Gmail labels
@@ -2900,7 +2912,13 @@ export class MailxDB {
2900
2912
  // straddling an `await` — the wrapper is nesting-safe (savepoints)
2901
2913
  // so it can't collide with the sync backfill's own transactions
2902
2914
  // when the two interleave across the yield below.
2903
- const WRITE_CHUNK = 500;
2915
+ // 100, not 500: each chunk is one held write transaction, and the
2916
+ // slow-txn logger caught these running 2+ SECONDS apiece under WAL
2917
+ // pressure — long enough to lock out the sync worker's storeMessages
2918
+ // ("database is locked"). Smaller chunks = shorter lock holds + a
2919
+ // yield (below) after each, giving the worker write windows (Bob
2920
+ // 2026-06-16, live debug).
2921
+ const WRITE_CHUNK = 100;
2904
2922
  const entries = [...agg.entries()];
2905
2923
  for (let i = 0; i < entries.length; i += WRITE_CHUNK) {
2906
2924
  const slice = entries.slice(i, i + WRITE_CHUNK);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-store",
3
- "version": "0.1.51",
3
+ "version": "0.1.52",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",