@bobfrankston/mailx-imap 0.1.39 → 0.1.40

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 (2) hide show
  1. package/index.js +36 -3
  2. package/package.json +3 -3
package/index.js CHANGED
@@ -861,9 +861,30 @@ export class ImapManager extends EventEmitter {
861
861
  }
862
862
  /** Store a batch of messages to DB immediately — used by onChunk for incremental sync */
863
863
  async storeMessages(accountId, folderId, folder, msgs, highestUid) {
864
+ // Chunked transactions: better-sqlite3 is synchronous and an open
865
+ // transaction blocks every other query on the same DB. A 1881-row
866
+ // INBOX sync inside ONE transaction locked the connection for ~1.5 s
867
+ // of straight upserts; any concurrent `getMessage` IPC had to wait
868
+ // out the whole loop before its own DB read could run. Bob 2026-05-13:
869
+ // "long long loading again." Solution: commit every BATCH_SIZE rows,
870
+ // yield the event loop with `setImmediate`, then begin a new
871
+ // transaction. User clicks land in those gaps with a single-row
872
+ // worth of latency instead of the full loop's worth.
873
+ const BATCH_SIZE = 50;
864
874
  let stored = 0;
865
- this.db.beginTransaction();
875
+ let inTxn = false;
876
+ const startTxn = () => { this.db.beginTransaction(); inTxn = true; };
877
+ const commitTxn = () => { if (inTxn) {
878
+ this.db.commitTransaction();
879
+ inTxn = false;
880
+ } };
881
+ const rollbackTxn = () => { if (inTxn) {
882
+ this.db.rollbackTransaction();
883
+ inTxn = false;
884
+ } };
866
885
  try {
886
+ startTxn();
887
+ let batchCount = 0;
867
888
  for (const msg of msgs) {
868
889
  // Debug: log subjects with non-ASCII to trace encoding issues
869
890
  if (msg.subject && /[^\x00-\x7F]/.test(msg.subject)) {
@@ -919,11 +940,23 @@ export class ImapManager extends EventEmitter {
919
940
  flags, size: msg.size || 0, hasAttachments, preview, bodyPath
920
941
  });
921
942
  stored++;
943
+ batchCount++;
944
+ if (batchCount >= BATCH_SIZE) {
945
+ commitTxn();
946
+ // Hand the event loop to any waiting IPC (user clicks,
947
+ // outbox drains, alarm polls). setImmediate runs AFTER
948
+ // pending I/O callbacks, so a stack of getMessage IPCs
949
+ // that arrived during the previous chunk gets serviced
950
+ // before we start the next batch.
951
+ await new Promise(r => setImmediate(r));
952
+ batchCount = 0;
953
+ startTxn();
954
+ }
922
955
  }
923
- this.db.commitTransaction();
956
+ commitTxn();
924
957
  }
925
958
  catch (e) {
926
- this.db.rollbackTransaction();
959
+ rollbackTxn();
927
960
  console.error(` storeMessages error: ${e.message}`);
928
961
  }
929
962
  return stored;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-imap",
3
- "version": "0.1.39",
3
+ "version": "0.1.40",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -11,7 +11,7 @@
11
11
  "dependencies": {
12
12
  "@bobfrankston/mailx-types": "^0.1.11",
13
13
  "@bobfrankston/mailx-settings": "^0.1.16",
14
- "@bobfrankston/mailx-store": "^0.1.20",
14
+ "@bobfrankston/mailx-store": "^0.1.21",
15
15
  "@bobfrankston/iflow-direct": "^0.1.41",
16
16
  "@bobfrankston/tcp-transport": "^0.1.6",
17
17
  "@bobfrankston/smtp-direct": "^0.1.8",
@@ -39,7 +39,7 @@
39
39
  "dependencies": {
40
40
  "@bobfrankston/mailx-types": "^0.1.11",
41
41
  "@bobfrankston/mailx-settings": "^0.1.16",
42
- "@bobfrankston/mailx-store": "^0.1.20",
42
+ "@bobfrankston/mailx-store": "^0.1.21",
43
43
  "@bobfrankston/iflow-direct": "^0.1.41",
44
44
  "@bobfrankston/tcp-transport": "^0.1.6",
45
45
  "@bobfrankston/smtp-direct": "^0.1.8",