@bobfrankston/mailx-imap 0.1.29 → 0.1.30
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.
- package/index.js +41 -49
- package/package.json +3 -3
package/index.js
CHANGED
|
@@ -1111,21 +1111,13 @@ export class ImapManager extends EventEmitter {
|
|
|
1111
1111
|
const existingSet = new Set(existingUids);
|
|
1112
1112
|
const newSet = new Set(messages.map(m => m.uid));
|
|
1113
1113
|
const missingUids = allServerUids.filter((uid) => !existingSet.has(uid) && !newSet.has(uid));
|
|
1114
|
-
// Backfill chunk size
|
|
1115
|
-
// a
|
|
1116
|
-
//
|
|
1117
|
-
//
|
|
1118
|
-
//
|
|
1119
|
-
// The 500-chunk version held the ops queue for the
|
|
1120
|
-
// entire backfill — Bob 2026-05-08 saw a click-to-render
|
|
1121
|
-
// wait of 100+ minutes on a busy backfill of the IP
|
|
1122
|
-
// folder.
|
|
1114
|
+
// Backfill chunk size. Use the passed-in `client` directly
|
|
1115
|
+
// (NOT a nested withConnection) — syncFolder is now wrapped
|
|
1116
|
+
// in withConnection at the call site, so the slow lane is
|
|
1117
|
+
// already locked for our duration. A nested withConnection
|
|
1118
|
+
// would deadlock waiting for the slot we hold.
|
|
1123
1119
|
const BACKFILL_CHUNK_SIZE = 100;
|
|
1124
1120
|
if (missingUids.length > 0 && missingUids.length <= 5000) {
|
|
1125
|
-
// For the log line we report a count; computing
|
|
1126
|
-
// min/max via a spread (`Math.min(...arr)`) blows V8's
|
|
1127
|
-
// argument limit on folders with tens of thousands of
|
|
1128
|
-
// UIDs. Use a manual reduce.
|
|
1129
1121
|
let minU = existingUids[0] ?? 0;
|
|
1130
1122
|
for (let i = 1; i < existingUids.length; i++)
|
|
1131
1123
|
if (existingUids[i] < minU)
|
|
@@ -1135,13 +1127,7 @@ export class ImapManager extends EventEmitter {
|
|
|
1135
1127
|
for (let i = 0; i < missingUids.length; i += BACKFILL_CHUNK_SIZE) {
|
|
1136
1128
|
const chunk = missingUids.slice(i, i + BACKFILL_CHUNK_SIZE);
|
|
1137
1129
|
const range = chunk.join(",");
|
|
1138
|
-
|
|
1139
|
-
// turn so any fast-lane click queued in the
|
|
1140
|
-
// meantime gets serviced between chunks. The
|
|
1141
|
-
// outer `client` param is bypassed here; the
|
|
1142
|
-
// queue-managed client is the same persistent
|
|
1143
|
-
// ops client (getOpsClient).
|
|
1144
|
-
const recovered = await this.withConnection(accountId, async (c) => await c.fetchMessages(folder.path, range, { source: false }), { slow: true });
|
|
1130
|
+
const recovered = await client.fetchMessages(folder.path, range, { source: false });
|
|
1145
1131
|
messages.push(...recovered);
|
|
1146
1132
|
recoveredTotal += recovered.length;
|
|
1147
1133
|
console.log(` ${folder.path}: fetch ${recoveredTotal}/${missingUids.length}`);
|
|
@@ -1149,21 +1135,15 @@ export class ImapManager extends EventEmitter {
|
|
|
1149
1135
|
}
|
|
1150
1136
|
else if (missingUids.length > 5000) {
|
|
1151
1137
|
console.log(` ${folder.path}: ${missingUids.length} server-only UIDs — capped; will resume next cycle`);
|
|
1152
|
-
//
|
|
1153
|
-
//
|
|
1154
|
-
//
|
|
1155
|
-
//
|
|
1156
|
-
// through syncAccountViaApi and doesn't reach here —
|
|
1157
|
-
// its synthesized hash-UIDs have no temporal meaning).
|
|
1158
|
-
// If we ever wire this for a non-monotonic UID source,
|
|
1159
|
-
// sort by date instead — but that means an extra
|
|
1160
|
-
// fetch-of-INTERNALDATE round-trip, which we avoid for
|
|
1161
|
-
// free here under the IMAP guarantee.
|
|
1138
|
+
// Vanilla IMAP under stable UIDVALIDITY: higher UID =
|
|
1139
|
+
// later assignment ≈ more recent message (Dovecot/
|
|
1140
|
+
// Cyrus). Gmail-API path is separate (no temporal
|
|
1141
|
+
// meaning to its hash-UIDs).
|
|
1162
1142
|
const cappedSlice = missingUids.sort((a, b) => b - a).slice(0, 5000);
|
|
1163
1143
|
let recoveredTotal = 0;
|
|
1164
1144
|
for (let i = 0; i < cappedSlice.length; i += BACKFILL_CHUNK_SIZE) {
|
|
1165
1145
|
const chunk = cappedSlice.slice(i, i + BACKFILL_CHUNK_SIZE);
|
|
1166
|
-
const recovered = await
|
|
1146
|
+
const recovered = await client.fetchMessages(folder.path, chunk.join(","), { source: false });
|
|
1167
1147
|
messages.push(...recovered);
|
|
1168
1148
|
recoveredTotal += recovered.length;
|
|
1169
1149
|
console.log(` ${folder.path}: fetch ${recoveredTotal}/5000 (capped)`);
|
|
@@ -1547,27 +1527,39 @@ export class ImapManager extends EventEmitter {
|
|
|
1547
1527
|
const highestUid = this.db.getHighestUid(accountId, folder.id);
|
|
1548
1528
|
if (isTrashChild && highestUid === 0)
|
|
1549
1529
|
return;
|
|
1550
|
-
let
|
|
1530
|
+
let clientForDiag = null;
|
|
1551
1531
|
try {
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
]);
|
|
1532
|
+
// Route syncFolder through the slow-lane queue so prefetch
|
|
1533
|
+
// (also slow lane) and sync take strict turns on the slow
|
|
1534
|
+
// client. Previously syncOne grabbed `getOpsClient` and
|
|
1535
|
+
// ran syncFolder directly OUTSIDE the queue; prefetch
|
|
1536
|
+
// chunks via withConnection raced against it on the same
|
|
1537
|
+
// client. Symptom: prefetch sent `SELECT INBOX` then sync
|
|
1538
|
+
// sent `SELECT Sent/Drafts`, then prefetch's `UID FETCH
|
|
1539
|
+
// <inbox-uids>` ran against Drafts → 0 bodies returned →
|
|
1540
|
+
// prefetch logs "0/N — NOT pruning" but bodies never
|
|
1541
|
+
// download. With C123 the fast lane has its own
|
|
1542
|
+
// independent client, so wrapping sync in slow-lane
|
|
1543
|
+
// withConnection doesn't block click-time body fetches.
|
|
1544
|
+
await this.withConnection(accountId, async (client) => {
|
|
1545
|
+
clientForDiag = client;
|
|
1546
|
+
await this.syncFolder(accountId, folder.id, client);
|
|
1547
|
+
}, { slow: true, timeoutMs: PER_FOLDER_TIMEOUT_MS });
|
|
1569
1548
|
}
|
|
1570
1549
|
catch (e) {
|
|
1550
|
+
// C120: per-folder timeout error appends transport
|
|
1551
|
+
// diagnostics so the [sync] log distinguishes "server
|
|
1552
|
+
// stopped responding" (sinceLastRead high) from "we
|
|
1553
|
+
// never finished writing" (writes climbing without
|
|
1554
|
+
// reads). The withConnection timeout already includes
|
|
1555
|
+
// its own message; we annotate further only for the
|
|
1556
|
+
// timeout path.
|
|
1557
|
+
if (/timeout/i.test(e?.message || "")) {
|
|
1558
|
+
const d = clientForDiag?.transport?.diagnostics;
|
|
1559
|
+
if (d) {
|
|
1560
|
+
e.message = `${e.message} [conn#${d.connId} r=${d.bytesRead}B w=${d.bytesWritten}B writes=${d.writeCount} sinceLastRead=${d.lastReadAt ? Date.now() - d.lastReadAt : -1}ms] folder=${folder.path}`;
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1571
1563
|
if (e.responseText?.includes("doesn't exist")) {
|
|
1572
1564
|
this.db.deleteFolder(folder.id);
|
|
1573
1565
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx-imap",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.30",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"@bobfrankston/mailx-store": "^0.1.15",
|
|
15
15
|
"@bobfrankston/iflow-direct": "^0.1.39",
|
|
16
16
|
"@bobfrankston/tcp-transport": "^0.1.6",
|
|
17
|
-
"@bobfrankston/smtp-direct": "^0.1.
|
|
17
|
+
"@bobfrankston/smtp-direct": "^0.1.8",
|
|
18
18
|
"@bobfrankston/mailx-sync": "^0.1.16",
|
|
19
19
|
"@bobfrankston/oauthsupport": "^1.0.26"
|
|
20
20
|
},
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"@bobfrankston/mailx-store": "^0.1.15",
|
|
43
43
|
"@bobfrankston/iflow-direct": "^0.1.39",
|
|
44
44
|
"@bobfrankston/tcp-transport": "^0.1.6",
|
|
45
|
-
"@bobfrankston/smtp-direct": "^0.1.
|
|
45
|
+
"@bobfrankston/smtp-direct": "^0.1.8",
|
|
46
46
|
"@bobfrankston/mailx-sync": "^0.1.16",
|
|
47
47
|
"@bobfrankston/oauthsupport": "^1.0.26"
|
|
48
48
|
}
|