@bobfrankston/rmfmail 1.2.29 → 1.2.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/client/compose/compose.bundle.js +14 -3
- package/client/compose/compose.bundle.js.map +2 -2
- package/client/lib/rmf-tiny.js +14 -3
- package/package.json +3 -3
- package/packages/mailx-imap/index.d.ts +13 -0
- package/packages/mailx-imap/index.d.ts.map +1 -1
- package/packages/mailx-imap/index.js +41 -17
- package/packages/mailx-imap/index.js.map +1 -1
- package/packages/mailx-imap/index.ts +40 -14
- package/packages/mailx-imap/package-lock.json +2 -2
- package/packages/mailx-imap/package.json +1 -1
|
@@ -1520,6 +1520,15 @@ export class ImapManager extends EventEmitter {
|
|
|
1520
1520
|
this.db.updateFolderSync(folderId, qr.exists ? prevUidValidity : prevUidValidity, String(qr.newHighestModSeq));
|
|
1521
1521
|
}
|
|
1522
1522
|
|
|
1523
|
+
// QRESYNC confirmed UIDVALIDITY is unchanged (it falls back to
|
|
1524
|
+
// full sync otherwise), so serverUidNext is trustworthy — sweep
|
|
1525
|
+
// phantom (uid >= UIDNEXT) cross-wired rows here too. Without
|
|
1526
|
+
// this, folders with a stable modseq (most of them) take the
|
|
1527
|
+
// QRESYNC fast path forever and their phantoms never get swept
|
|
1528
|
+
// (Bob 2026-06-17: CEBog phantoms persisted through the
|
|
1529
|
+
// reconcile-only sweep because CEBog syncs via QRESYNC).
|
|
1530
|
+
this.sweepPhantomRows(accountId, folderId, folder.path, serverUidNext);
|
|
1531
|
+
|
|
1523
1532
|
this.emit("syncProgress", accountId, `sync:${folder.path}`, 100);
|
|
1524
1533
|
console.log(` [qresync] ${accountId}/${folder.path}: done in ${Date.now() - __sfStart}ms (path: QRESYNC)`);
|
|
1525
1534
|
return newOnes.length;
|
|
@@ -1842,20 +1851,7 @@ export class ImapManager extends EventEmitter {
|
|
|
1842
1851
|
// server hiccup — so delete them unconditionally (the real copy lives
|
|
1843
1852
|
// in its true folder and re-syncs from there; the local store is a
|
|
1844
1853
|
// cache). Only runs when STATUS gave a trustworthy UIDNEXT.
|
|
1845
|
-
|
|
1846
|
-
try {
|
|
1847
|
-
const phantoms = this.db.getUidsForFolder(accountId, folderId).filter(u => u >= serverUidNext!);
|
|
1848
|
-
if (phantoms.length > 0) {
|
|
1849
|
-
for (const uid of phantoms) {
|
|
1850
|
-
this.unlinkBodyFile(accountId, uid, folderId).catch(() => {});
|
|
1851
|
-
this.db.deleteMessage(accountId, folderId, uid, `phantom: uid>=server UIDNEXT ${serverUidNext}`, `mailx-imap phantom sweep (${folder.path})`);
|
|
1852
|
-
}
|
|
1853
|
-
deletedCount += phantoms.length;
|
|
1854
|
-
this.db.recalcFolderCounts(folderId);
|
|
1855
|
-
console.log(` [phantom-sweep] ${accountId}/${folder.path}: removed ${phantoms.length} cross-wired row(s) with uid >= UIDNEXT ${serverUidNext}`);
|
|
1856
|
-
}
|
|
1857
|
-
} catch (e: any) { console.error(` [phantom-sweep] ${accountId}/${folder.path}: ${e?.message || e}`); }
|
|
1858
|
-
}
|
|
1854
|
+
deletedCount += this.sweepPhantomRows(accountId, folderId, folder.path, serverUidNext);
|
|
1859
1855
|
try {
|
|
1860
1856
|
// Reuse the server UID list set-diff already fetched.
|
|
1861
1857
|
// Without this we made TWO `UID SEARCH` calls per folder
|
|
@@ -2973,6 +2969,36 @@ export class ImapManager extends EventEmitter {
|
|
|
2973
2969
|
this.deferredDeletes.set(key, t);
|
|
2974
2970
|
}
|
|
2975
2971
|
|
|
2972
|
+
/** Delete local rows whose uid >= the server's UIDNEXT for this folder.
|
|
2973
|
+
* Such a uid was NEVER assigned in this folder (UIDNEXT is the next one the
|
|
2974
|
+
* server will ever hand out), so the row is a cross-wired phantom ("UID is
|
|
2975
|
+
* not identity" corruption) — a real message stamped with the wrong
|
|
2976
|
+
* folder_id. They break prefetch (the server returns 0 FETCH responses for
|
|
2977
|
+
* uids it doesn't have → no body caches) and the normal set-diff reconcile
|
|
2978
|
+
* refuses to remove them when they're >50% of the folder. Deleting by the
|
|
2979
|
+
* UIDNEXT invariant is unconditional-safe: the uid is impossible, can't be a
|
|
2980
|
+
* move target, and the message's real copy re-syncs from its true folder
|
|
2981
|
+
* (the local store is a cache). Caller must have a TRUSTWORTHY uidNext
|
|
2982
|
+
* (STATUS result) AND confirmed UIDVALIDITY is unchanged for this folder
|
|
2983
|
+
* (full-sync and QRESYNC-success paths both satisfy that). Returns the count. */
|
|
2984
|
+
private sweepPhantomRows(accountId: string, folderId: number, folderPath: string, serverUidNext: number | null): number {
|
|
2985
|
+
if (serverUidNext == null || serverUidNext <= 0) return 0;
|
|
2986
|
+
try {
|
|
2987
|
+
const phantoms = this.db.getUidsForFolder(accountId, folderId).filter(u => u >= serverUidNext);
|
|
2988
|
+
if (phantoms.length === 0) return 0;
|
|
2989
|
+
for (const uid of phantoms) {
|
|
2990
|
+
this.unlinkBodyFile(accountId, uid, folderId).catch(() => {});
|
|
2991
|
+
this.db.deleteMessage(accountId, folderId, uid, `phantom: uid>=server UIDNEXT ${serverUidNext}`, `mailx-imap phantom sweep (${folderPath})`);
|
|
2992
|
+
}
|
|
2993
|
+
this.db.recalcFolderCounts(folderId);
|
|
2994
|
+
console.log(` [phantom-sweep] ${accountId}/${folderPath}: removed ${phantoms.length} cross-wired row(s) with uid >= UIDNEXT ${serverUidNext}`);
|
|
2995
|
+
return phantoms.length;
|
|
2996
|
+
} catch (e: any) {
|
|
2997
|
+
console.error(` [phantom-sweep] ${accountId}/${folderPath}: ${e?.message || e}`);
|
|
2998
|
+
return 0;
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
|
|
2976
3002
|
private async unlinkBodyFile(accountId: string, uid: number, folderId?: number): Promise<void> {
|
|
2977
3003
|
try {
|
|
2978
3004
|
const row: any = this.db.getMessageByUid(accountId, uid, folderId);
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx-imap",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.100",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bobfrankston/mailx-imap",
|
|
9
|
-
"version": "0.1.
|
|
9
|
+
"version": "0.1.100",
|
|
10
10
|
"license": "ISC",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@bobfrankston/iflow-direct": "^0.1.27",
|