@bobfrankston/mailx-imap 0.1.39 → 0.1.41

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 +39 -8
  2. package/package.json +3 -3
package/index.js CHANGED
@@ -5,12 +5,11 @@
5
5
  */
6
6
  import { createAutoImapConfig, CompatImapClient } from "@bobfrankston/iflow-direct";
7
7
  import { authenticateOAuth } from "@bobfrankston/oauthsupport";
8
- import { FileMessageStore } from "@bobfrankston/mailx-store";
8
+ import { FileMessageStore, parseSerial } from "@bobfrankston/mailx-store";
9
9
  import { loadSettings, getStorePath, getConfigDir, getHistoryDays, getPrefetch } from "@bobfrankston/mailx-settings";
10
10
  import { EventEmitter } from "node:events";
11
11
  import * as fs from "node:fs";
12
12
  import * as path from "node:path";
13
- import { simpleParser } from "mailparser";
14
13
  import { GmailApiProvider } from "./providers/gmail-api.js";
15
14
  import { SmtpClient } from "@bobfrankston/smtp-direct";
16
15
  import * as os from "node:os";
@@ -99,7 +98,7 @@ function decodeEntities(text) {
99
98
  /** Extract a plain-text preview from message source */
100
99
  async function extractPreview(source) {
101
100
  try {
102
- const parsed = await simpleParser(source);
101
+ const parsed = await parseSerial(source);
103
102
  const bodyText = parsed.text || "";
104
103
  const bodyHtml = parsed.html || "";
105
104
  // Use text part; fall back to stripping HTML tags if text is empty
@@ -861,9 +860,30 @@ export class ImapManager extends EventEmitter {
861
860
  }
862
861
  /** Store a batch of messages to DB immediately — used by onChunk for incremental sync */
863
862
  async storeMessages(accountId, folderId, folder, msgs, highestUid) {
863
+ // Chunked transactions: better-sqlite3 is synchronous and an open
864
+ // transaction blocks every other query on the same DB. A 1881-row
865
+ // INBOX sync inside ONE transaction locked the connection for ~1.5 s
866
+ // of straight upserts; any concurrent `getMessage` IPC had to wait
867
+ // out the whole loop before its own DB read could run. Bob 2026-05-13:
868
+ // "long long loading again." Solution: commit every BATCH_SIZE rows,
869
+ // yield the event loop with `setImmediate`, then begin a new
870
+ // transaction. User clicks land in those gaps with a single-row
871
+ // worth of latency instead of the full loop's worth.
872
+ const BATCH_SIZE = 50;
864
873
  let stored = 0;
865
- this.db.beginTransaction();
874
+ let inTxn = false;
875
+ const startTxn = () => { this.db.beginTransaction(); inTxn = true; };
876
+ const commitTxn = () => { if (inTxn) {
877
+ this.db.commitTransaction();
878
+ inTxn = false;
879
+ } };
880
+ const rollbackTxn = () => { if (inTxn) {
881
+ this.db.rollbackTransaction();
882
+ inTxn = false;
883
+ } };
866
884
  try {
885
+ startTxn();
886
+ let batchCount = 0;
867
887
  for (const msg of msgs) {
868
888
  // Debug: log subjects with non-ASCII to trace encoding issues
869
889
  if (msg.subject && /[^\x00-\x7F]/.test(msg.subject)) {
@@ -919,11 +939,23 @@ export class ImapManager extends EventEmitter {
919
939
  flags, size: msg.size || 0, hasAttachments, preview, bodyPath
920
940
  });
921
941
  stored++;
942
+ batchCount++;
943
+ if (batchCount >= BATCH_SIZE) {
944
+ commitTxn();
945
+ // Hand the event loop to any waiting IPC (user clicks,
946
+ // outbox drains, alarm polls). setImmediate runs AFTER
947
+ // pending I/O callbacks, so a stack of getMessage IPCs
948
+ // that arrived during the previous chunk gets serviced
949
+ // before we start the next batch.
950
+ await new Promise(r => setImmediate(r));
951
+ batchCount = 0;
952
+ startTxn();
953
+ }
922
954
  }
923
- this.db.commitTransaction();
955
+ commitTxn();
924
956
  }
925
957
  catch (e) {
926
- this.db.rollbackTransaction();
958
+ rollbackTxn();
927
959
  console.error(` storeMessages error: ${e.message}`);
928
960
  }
929
961
  return stored;
@@ -939,8 +971,7 @@ export class ImapManager extends EventEmitter {
939
971
  *
940
972
  * Fires the same emits as a normal sync so the UI updates. */
941
973
  async insertLocalRowFromSource(accountId, folder, uid, source, flags) {
942
- const { simpleParser } = await import("mailparser");
943
- const parsed = await simpleParser(source);
974
+ const parsed = await parseSerial(source);
944
975
  // Coerce mailparser AddressObject(s) into the flat `{name, address}[]`
945
976
  // shape storeMessages's downstream toEmailAddresses expects.
946
977
  // RFC 2047 encoded-word decoding (incl. inside quoted-strings) is
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.41",
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",