@bobfrankston/mailx-imap 0.1.53 → 0.1.55

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 +28 -16
  2. package/package.json +5 -5
package/index.js CHANGED
@@ -2874,16 +2874,16 @@ export class ImapManager extends EventEmitter {
2874
2874
  // owns deletion via a 30-min grace; defer to it.
2875
2875
  const someReceived = received.size > 0;
2876
2876
  if (batchSucceeded && someReceived) {
2877
+ // Mirror of the IMAP-path fix: never delete on a
2878
+ // partial batch — Gmail API has its own transient
2879
+ // miss modes (rate-limit retry losing a message,
2880
+ // /batch response parse error) that look exactly
2881
+ // like server-side expunge. Set-diff reconcile in
2882
+ // syncAccountViaApi owns deletion.
2877
2883
  for (const uid of uidsInFolder) {
2878
2884
  if (received.has(uid))
2879
2885
  continue;
2880
- try {
2881
- this.unlinkBodyFile(accountId, uid, folderId).catch(() => { });
2882
- this.db.deleteMessage(accountId, uid, "prefetch batch: server didn't return body for queued UID — assumed deleted", "mailx-imap prefetchBodies (Gmail batch)");
2883
- counters.deleted++;
2884
- madeProgress = true;
2885
- }
2886
- catch { /* ignore */ }
2886
+ this.markPrefetchEmpty(accountId, folderId, uid);
2887
2887
  }
2888
2888
  }
2889
2889
  else if (batchSucceeded && !someReceived) {
@@ -2981,7 +2981,13 @@ export class ImapManager extends EventEmitter {
2981
2981
  })());
2982
2982
  });
2983
2983
  await Promise.all(pending);
2984
- }, { slow: true });
2984
+ }, { slow: true, timeoutMs: 300_000 });
2985
+ // 5-min cap, not the 90s interactive default:
2986
+ // prefetch is background, and a 25-message body
2987
+ // chunk on a slow Dovecot legitimately runs past
2988
+ // 90s. The short cap was tripping the folder-error
2989
+ // cooldown 17×/session, so INBOX bodies stopped
2990
+ // prefetching entirely (Bob 2026-05-21 log).
2985
2991
  batchSucceeded = true;
2986
2992
  this.clearFolderErrors(accountId, folder.path);
2987
2993
  }
@@ -3007,18 +3013,24 @@ export class ImapManager extends EventEmitter {
3007
3013
  // authoritative deletion path with a 30-min
3008
3014
  // grace window; prefetch defers to it.
3009
3015
  const someReceived = received.size > 0;
3010
- if (batchSucceeded && someReceived)
3016
+ if (batchSucceeded && someReceived) {
3017
+ // DO NOT DELETE missing UIDs. A partial response is
3018
+ // an iflow parser miss / mid-stream hiccup MUCH more
3019
+ // often than a real server expunge — and deleting on
3020
+ // that signal cost Bob a Bambu Labs verification
3021
+ // code (audit id 3785, 2026-05-20), plus dozens of
3022
+ // other valid messages over the day. Set-diff
3023
+ // reconcile in syncFolder is the authoritative
3024
+ // deletion path with a 30-min grace window; prefetch
3025
+ // defers to it. Mark the UIDs as prefetch-empty so
3026
+ // the TTL backoff (5min → 12h) retries them — that
3027
+ // path is non-destructive.
3011
3028
  for (const uid of chunk) {
3012
3029
  if (received.has(uid))
3013
3030
  continue;
3014
- try {
3015
- this.unlinkBodyFile(accountId, uid, folderId).catch(() => { });
3016
- this.db.deleteMessage(accountId, uid, "prefetch batch: server didn't return body for queued UID — assumed deleted", "mailx-imap prefetchBodies (IMAP batch)");
3017
- counters.deleted++;
3018
- madeProgress = true;
3019
- }
3020
- catch { /* ignore */ }
3031
+ this.markPrefetchEmpty(accountId, folderId, uid);
3021
3032
  }
3033
+ }
3022
3034
  else if (batchSucceeded && !someReceived) {
3023
3035
  console.error(` [prefetch] ${accountId}/${folder.path}: chunk ${chunkStart}-${chunkStart + chunk.length - 1} returned 0/${chunk.length} bodies — NOT pruning (set-diff reconcile owns deletion). UIDs: ${chunk.slice(0, 5).join(",")}${chunk.length > 5 ? "..." : ""}`);
3024
3036
  for (const uid of chunk)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-imap",
3
- "version": "0.1.53",
3
+ "version": "0.1.55",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -10,8 +10,8 @@
10
10
  "license": "ISC",
11
11
  "dependencies": {
12
12
  "@bobfrankston/mailx-types": "^0.1.18",
13
- "@bobfrankston/mailx-settings": "^0.1.20",
14
- "@bobfrankston/mailx-store": "^0.1.30",
13
+ "@bobfrankston/mailx-settings": "^0.1.22",
14
+ "@bobfrankston/mailx-store": "^0.1.32",
15
15
  "@bobfrankston/iflow-direct": "^0.1.50",
16
16
  "@bobfrankston/tcp-transport": "^0.1.6",
17
17
  "@bobfrankston/smtp-direct": "^0.1.8",
@@ -38,8 +38,8 @@
38
38
  ".transformedSnapshot": {
39
39
  "dependencies": {
40
40
  "@bobfrankston/mailx-types": "^0.1.18",
41
- "@bobfrankston/mailx-settings": "^0.1.20",
42
- "@bobfrankston/mailx-store": "^0.1.30",
41
+ "@bobfrankston/mailx-settings": "^0.1.22",
42
+ "@bobfrankston/mailx-store": "^0.1.32",
43
43
  "@bobfrankston/iflow-direct": "^0.1.50",
44
44
  "@bobfrankston/tcp-transport": "^0.1.6",
45
45
  "@bobfrankston/smtp-direct": "^0.1.8",