@bobfrankston/mailx-imap 0.1.30 → 0.1.31
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.d.ts +18 -0
- package/index.js +44 -0
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -224,7 +224,25 @@ export declare class ImapManager extends EventEmitter {
|
|
|
224
224
|
path: string;
|
|
225
225
|
}, uid: number, source: string, flags: string[]): Promise<void>;
|
|
226
226
|
/** Sync messages for a specific folder */
|
|
227
|
+
/** Per-(accountId,folderId) sync lock. Multiple paths can call syncFolder
|
|
228
|
+
* for the same folder concurrently — `syncOne` (full sync), `syncInbox`
|
|
229
|
+
* (5-min fast poll), `quickInboxCheckAccount` (startup quick check),
|
|
230
|
+
* `syncInboxNewOnly` (IDLE callback). Each path takes a different
|
|
231
|
+
* connection, so withConnection can't serialize them. The DB layer
|
|
232
|
+
* enforces "one transaction per connection" but doesn't notice the
|
|
233
|
+
* concurrent UID set being mutated underneath. Symptom: two
|
|
234
|
+
* `[sync-enter]` log lines for the same folder within ms (Bob 2026-05-08
|
|
235
|
+
* 21:47:52), `cannot start a transaction within a transaction` SQLite
|
|
236
|
+
* errors, and prefetch SELECT/FETCH races when sync's SELECT runs
|
|
237
|
+
* between prefetch's SELECT and FETCH.
|
|
238
|
+
*
|
|
239
|
+
* This per-folder mutex means the second concurrent caller waits
|
|
240
|
+
* rather than silently racing. The waiter still gets the lock when
|
|
241
|
+
* the first caller finishes — important for outbox flushes that
|
|
242
|
+
* expect their syncFolder for `Sent` to actually run. */
|
|
243
|
+
private syncFolderLocks;
|
|
227
244
|
syncFolder(accountId: string, folderId: number, client?: any): Promise<number>;
|
|
245
|
+
private _syncFolderImpl;
|
|
228
246
|
/** Sync all folders for all accounts */
|
|
229
247
|
syncAll(): Promise<void>;
|
|
230
248
|
private _syncAll;
|
package/index.js
CHANGED
|
@@ -972,7 +972,41 @@ export class ImapManager extends EventEmitter {
|
|
|
972
972
|
console.log(` [local-insert] ${folder.path} UID ${uid}: ${parsed.subject || "(no subject)"} (no IMAP roundtrip)`);
|
|
973
973
|
}
|
|
974
974
|
/** Sync messages for a specific folder */
|
|
975
|
+
/** Per-(accountId,folderId) sync lock. Multiple paths can call syncFolder
|
|
976
|
+
* for the same folder concurrently — `syncOne` (full sync), `syncInbox`
|
|
977
|
+
* (5-min fast poll), `quickInboxCheckAccount` (startup quick check),
|
|
978
|
+
* `syncInboxNewOnly` (IDLE callback). Each path takes a different
|
|
979
|
+
* connection, so withConnection can't serialize them. The DB layer
|
|
980
|
+
* enforces "one transaction per connection" but doesn't notice the
|
|
981
|
+
* concurrent UID set being mutated underneath. Symptom: two
|
|
982
|
+
* `[sync-enter]` log lines for the same folder within ms (Bob 2026-05-08
|
|
983
|
+
* 21:47:52), `cannot start a transaction within a transaction` SQLite
|
|
984
|
+
* errors, and prefetch SELECT/FETCH races when sync's SELECT runs
|
|
985
|
+
* between prefetch's SELECT and FETCH.
|
|
986
|
+
*
|
|
987
|
+
* This per-folder mutex means the second concurrent caller waits
|
|
988
|
+
* rather than silently racing. The waiter still gets the lock when
|
|
989
|
+
* the first caller finishes — important for outbox flushes that
|
|
990
|
+
* expect their syncFolder for `Sent` to actually run. */
|
|
991
|
+
syncFolderLocks = new Map();
|
|
975
992
|
async syncFolder(accountId, folderId, client) {
|
|
993
|
+
const lockKey = `${accountId}:${folderId}`;
|
|
994
|
+
const inflight = this.syncFolderLocks.get(lockKey);
|
|
995
|
+
if (inflight) {
|
|
996
|
+
// Coalesce: callers that fire while a sync is in flight get the
|
|
997
|
+
// result of the in-flight call rather than starting a duplicate.
|
|
998
|
+
// For "quick check that finds new mail and triggers sync" this
|
|
999
|
+
// means the quick check waits for the existing sync to complete
|
|
1000
|
+
// — which is what the user wants anyway, no double work.
|
|
1001
|
+
console.log(` [sync-enter] ${accountId}/${folderId}: coalescing (sync already in flight)`);
|
|
1002
|
+
return inflight;
|
|
1003
|
+
}
|
|
1004
|
+
const promise = this._syncFolderImpl(accountId, folderId, client)
|
|
1005
|
+
.finally(() => { this.syncFolderLocks.delete(lockKey); });
|
|
1006
|
+
this.syncFolderLocks.set(lockKey, promise);
|
|
1007
|
+
return promise;
|
|
1008
|
+
}
|
|
1009
|
+
async _syncFolderImpl(accountId, folderId, client) {
|
|
976
1010
|
if (!client)
|
|
977
1011
|
client = await this.getOpsClient(accountId);
|
|
978
1012
|
const prefetch = getPrefetch();
|
|
@@ -2381,6 +2415,16 @@ export class ImapManager extends EventEmitter {
|
|
|
2381
2415
|
async prefetchBodies(accountId) {
|
|
2382
2416
|
if (this.prefetchingAccounts.has(accountId))
|
|
2383
2417
|
return;
|
|
2418
|
+
// Skip if the account isn't registered yet — the reconciler tick
|
|
2419
|
+
// can fire 2 s after daemon start, and addAccount may still be
|
|
2420
|
+
// running its OAuth flow. Without this guard we hit
|
|
2421
|
+
// ERROR_BUDGET (20) failures with "No config for account X" and
|
|
2422
|
+
// exit prefetch silently for the rest of the session — bodies
|
|
2423
|
+
// never download. Symptom: log shows 20 lines of `[prefetch] X
|
|
2424
|
+
// folder Y chunk 0: batch fetch failed: No config for account X`
|
|
2425
|
+
// followed by `stopping after 20 errors`.
|
|
2426
|
+
if (!this.configs.has(accountId))
|
|
2427
|
+
return;
|
|
2384
2428
|
this.prefetchingAccounts.add(accountId);
|
|
2385
2429
|
try {
|
|
2386
2430
|
await this._prefetchBodies(accountId);
|